Skip to content

Commit

Permalink
Feat(Data Mapper): Drag functions onto canvas (#5001)
Browse files Browse the repository at this point in the history
* getting drop coordinates

* creating new fn node

* drag function works

* styling

* button styling

* PR improvements

* placing nodes works

* node position improvements

* fixed import

* build passes

* tests pass
  • Loading branch information
DanielleCogs committed Jun 20, 2024
1 parent 1f82ee6 commit ccf276f
Show file tree
Hide file tree
Showing 20 changed files with 758 additions and 505 deletions.
6 changes: 3 additions & 3 deletions apps/Standalone/src/dataMapperV1/state/DataMapDataLoader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { MapDefDropdownOption } from '../components/DevToolbox';
import type { RootState } from './Store';
import type { FunctionData } from '@microsoft/logic-apps-data-mapper';
import { functionMock, loadMapDefinition } from '@microsoft/logic-apps-data-mapper';
import type { MapDefinitionEntry, MapMetadata } from '@microsoft/logic-apps-shared';
import type { MapDefinitionEntry, MapMetadataV1 } from '@microsoft/logic-apps-shared';
import { Theme as ThemeType } from '@microsoft/logic-apps-shared';
import type { PayloadAction } from '@reduxjs/toolkit';
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit';
Expand All @@ -21,14 +21,14 @@ export interface DataMapLoadingState {
rawDefinition?: MapDefDropdownOption;
loadingMethod: LoadingMethod;
mapDefinition: MapDefinitionEntry;
mapMetadata?: MapMetadata;
mapMetadata?: MapMetadataV1;
xsltFilename: string;
xsltContent: string;
fetchedFunctions?: FunctionData[];
customXsltPaths: string[];
}

const mockMetadata: MapMetadata = {
const mockMetadata: MapMetadataV1 = {
functionNodes: [
{
reactFlowGuid: 'Ceiling-52B496E3-E270-4A8E-AFB2-414989219B15',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { customTokens } from '../../../core';
import type { FunctionData } from '../../../models';
import { FunctionIcon } from '../../functionIcon/FunctionIcon';
import { Button, Caption1, tokens } from '@fluentui/react-components';
import { useCardContextMenu } from '@microsoft/designer-ui';

import type { NodeProps } from 'reactflow';
import { useStyles } from './styles';
import { getFunctionBrandingForCategory } from '../../../utils/Function.Utils';

export interface FunctionCardProps extends CardProps {
functionData: FunctionData;
dataTestId: string;
}

export interface CardProps {
onClick?: () => void;
displayHandle: boolean;
disabled: boolean;
}

export const FunctionNode = (props: NodeProps<FunctionCardProps>) => {
const { functionData, disabled, onClick, dataTestId } = props.data;
const styles = useStyles();
const fnBranding = getFunctionBrandingForCategory(functionData.category);

const contextMenu = useCardContextMenu();

return (
<div onContextMenu={contextMenu.handle} data-testid={dataTestId}>
<Button onClick={onClick} disabled={!!disabled} className={styles.functionButton}>
<div className={styles.iconContainer} style={{ backgroundColor: customTokens[fnBranding.colorTokenName] }}>
<FunctionIcon
iconSize={11}
functionKey={functionData.key}
functionName={functionData.functionName}
categoryName={functionData.category}
color={tokens.colorNeutralForegroundInverted}
/>
</div>
<Caption1 className={styles.functionName} truncate block>
{functionData.displayName}
</Caption1>
</Button>
</div>
);
};
27 changes: 26 additions & 1 deletion libs/data-mapper-v2/src/components/common/reactflow/styles.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { makeStyles, shorthands } from '@fluentui/react-components';
import { makeStyles, shorthands, tokens } from '@fluentui/react-components';

const fnIconSize = '17px';

export const useStyles = makeStyles({
wrapper: {
Expand Down Expand Up @@ -36,4 +38,27 @@ export const useStyles = makeStyles({
height: '10px',
backgroundColor: 'transparent',
},
functionButton: {
...shorthands.borderRadius('16px'),
height: '26px',
minWidth: '80px',
display: 'inline-flex',
justifyContent: 'left',
paddingRight: '20px'
},
functionName: {
textWrap: 'nowrap',
display: 'inline-table'
},
iconContainer: {
display: 'inline-flex',
height: fnIconSize,
flexShrink: '0 !important',
flexBasis: fnIconSize,
...shorthands.borderRadius(tokens.borderRadiusCircular),
color: tokens.colorNeutralBackground1,
justifyContent: 'center',
alignItems: 'center',
marginRight: '3px'
},
});
Original file line number Diff line number Diff line change
Expand Up @@ -5,36 +5,61 @@ import { FunctionIcon } from '../functionIcon/FunctionIcon';
import { Caption1, TreeItem, TreeItemLayout, tokens } from '@fluentui/react-components';
import { useStyles } from './styles';
import { AddRegular } from '@fluentui/react-icons';
import { useDrag } from 'react-dnd';
import { useDispatch } from 'react-redux';
import { addFunctionNode } from '../../core/state/DataMapSlice';
import type { XYPosition } from 'reactflow';

interface FunctionListItemProps {
functionData: FunctionData;
}

export type DropResult = { position: XYPosition } | undefined;

const FunctionListItem = ({ functionData }: FunctionListItemProps) => {
const styles = useStyles();
const fnBranding = getFunctionBrandingForCategory(functionData.category);
const dispatch = useDispatch();

return (
<TreeItem className={styles.functionTreeItem} itemType="leaf">
<TreeItemLayout className={styles.functionTreeItem} aside={<AddRegular className={styles.addIconAside} />}>
<div key={functionData.key} className={styles.listButton}>
<div className={styles.iconContainer} style={{ backgroundColor: customTokens[fnBranding.colorTokenName] }}>
<FunctionIcon
functionKey={functionData.key}
functionName={functionData.functionName}
categoryName={functionData.category}
color={tokens.colorNeutralForegroundInverted}
iconSize={11}
/>
</div>
const [, drag] = useDrag(() => ({
type: 'function',
item: functionData.key,
end: (item, monitor) => {
const dropResult = monitor.getDropResult<DropResult>();
if (item && dropResult) {
functionData.position = dropResult.position;
dispatch(addFunctionNode(functionData));
}
},
}));

<Caption1 truncate block className={styles.functionNameText}>
{functionData.displayName}
</Caption1>
return (
<TreeItem itemType="leaf">
<div className={styles.dragWrapper} ref={drag}>
<TreeItemLayout className={styles.functionTreeItem} aside={<AddRegular className={styles.addIconAside} />}>
<div key={functionData.key} className={styles.listButton}>
<div
className={styles.iconContainer}
style={{
backgroundColor: customTokens[fnBranding.colorTokenName],
}}
>
<FunctionIcon
functionKey={functionData.key}
functionName={functionData.functionName}
categoryName={functionData.category}
color={tokens.colorNeutralForegroundInverted}
iconSize={11}
/>
</div>

<span style={{ marginLeft: 'auto' }} />
</div>
</TreeItemLayout>
<Caption1 truncate block className={styles.functionNameText}>
{functionData.displayName}
</Caption1>
<span style={{ marginLeft: 'auto' }} />
</div>
</TreeItemLayout>
</div>
</TreeItem>
);
};
Expand Down
17 changes: 13 additions & 4 deletions libs/data-mapper-v2/src/components/functionList/styles.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,25 @@ export const useStyles = makeStyles({
functionTreeItem: {
backgroundColor: '#E8F3FE',
paddingLeft: '10px',
':hover': {
backgroundColor: '#D5E4FF',
...shorthands.borderRadius(tokens.borderRadiusCircular),
},
':active': {
backgroundColor: '#D5E4FF',
...shorthands.borderRadius(tokens.borderRadiusCircular),
},
},
dragWrapper: {
// allows for oval shape without background during drag
opacity: 0.99,
},
listButton: {
height: '30px',
width: '100%',
backgroundColor: '#E8F3FE',
backgroundColor: 'inherit',
...shorthands.border('0px'),
...shorthands.padding('1px 4px 1px 4px'),
':hover': {
backgroundColor: '#E8F3FE',
},
display: 'flex',
alignItems: 'center',
},
Expand Down
7 changes: 6 additions & 1 deletion libs/data-mapper-v2/src/core/DataMapDataProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
//import { MapDefinitionDeserializer } from '../mapDefinitions';
import { ReactFlowProvider } from 'reactflow';
import type { FunctionData } from '../models/Function';
import { convertSchemaToSchemaExtended } from '../utils/Schema.Utils';
import { DataMapperWrappedContext } from './DataMapperDesignerContext';
Expand Down Expand Up @@ -114,5 +115,9 @@ export const DataMapDataProvider = (props: DataMapDataProviderProps) => {
throw new Error('DataMapDataProvider must be used inside of a DataMapperWrappedContext');
}

return <DataProviderInner {...props} />;
return (
<ReactFlowProvider>
<DataProviderInner {...props} />
</ReactFlowProvider>
);
};
4 changes: 4 additions & 0 deletions libs/data-mapper-v2/src/core/DataMapperDesignerProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import { IntlProvider, Theme as ThemeType } from '@microsoft/logic-apps-shared';
import type React from 'react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { Provider as ReduxProvider } from 'react-redux';
import { DndProvider } from 'react-dnd';
import { HTML5Backend } from 'react-dnd-html5-backend';

interface ExtendedTheme extends Theme {
[key: string]: any;
Expand Down Expand Up @@ -56,6 +58,7 @@ export const DataMapperDesignerProvider = ({
children,
}: DataMapperDesignerProviderProps) => {
return (
<DndProvider backend={HTML5Backend}>
<AppInsightsContext.Provider value={reactPlugin}>
<ReduxProvider store={store}>
<DataMapperWrappedContext.Provider value={options}>
Expand Down Expand Up @@ -90,5 +93,6 @@ export const DataMapperDesignerProvider = ({
</DataMapperWrappedContext.Provider>
</ReduxProvider>
</AppInsightsContext.Provider>
</DndProvider>
);
};
Loading

0 comments on commit ccf276f

Please sign in to comment.