Skip to content

Commit

Permalink
Big refactor to tree rendering structure
Browse files Browse the repository at this point in the history
DnD tree now converts the nested tree to a flat one and only
consists of _1_ droppable area with a flat array of draggables
that can be combined.

Using the existing logic in the reducer combined with translating
the flat structure changes to a format the nested reducer can
understand looks like a really promising avenue.

There still seems to be a bug with a longer list where items do
not interact properly.
  • Loading branch information
jloleysens committed Apr 30, 2020
1 parent d9ef80a commit 3ccc16d
Show file tree
Hide file tree
Showing 4 changed files with 148 additions and 144 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@
* you may not use this file except in compliance with the Elastic License.
*/

import React, { FunctionComponent, useState } from 'react';
import { EuiDragDropContext, EuiDroppable, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import React, { FunctionComponent } from 'react';
import { EuiDragDropContext, EuiDroppable } from '@elastic/eui';

import { ProcessorInternal, DraggableLocation, ProcessorSelector } from '../../types';

import { mapDestinationIndexToTreeLocation } from './utils';

import { TreeNode, TreeNodeComponentArgs } from './tree_node';

interface OnDragEndArgs {
Expand All @@ -22,98 +24,83 @@ export interface Props {
nodeComponent: (arg: TreeNodeComponentArgs) => React.ReactNode;
}

export interface PrivateProps extends Omit<Props, 'onDragEnd'> {
selector: ProcessorSelector;
isDroppable: boolean;
currentDragSelector?: string;
}

export const ROOT_PATH_ID = 'ROOT_PATH_ID';

export const PrivateDragAndDropTree: FunctionComponent<PrivateProps> = ({
processors,
selector,
nodeComponent,
isDroppable,
currentDragSelector,
}) => {
const serializedSelector = selector.join('.');
const isRoot = !serializedSelector;
const id = isRoot ? ROOT_PATH_ID : serializedSelector;
const droppable = (
<EuiDroppable
// type={serializedSelector}
isDropDisabled={!isDroppable}
droppableId={id}
spacing="l"
>
{processors.map((processor, idx) => {
const nodeSelector = selector.concat(String(idx));
return (
<TreeNode
currentDragSelector={currentDragSelector}
isDroppable={!isDroppable ? false : currentDragSelector !== nodeSelector.join('.')}
key={idx}
processor={processor}
selector={nodeSelector}
index={idx}
component={nodeComponent}
/>
);
})}
</EuiDroppable>
);

if (isRoot || !processors.length) {
return droppable;
} else {
return (
<EuiFlexGroup gutterSize="none" responsive={false}>
<EuiFlexItem grow={false}>
<div style={{ width: '30px' }} />
</EuiFlexItem>
<EuiFlexItem>{droppable}</EuiFlexItem>
</EuiFlexGroup>
);
}
};
/** This value comes from the {@link ProcessorInternal} type */
const ON_FAILURE = 'onFailure';

export const DragAndDropTree: FunctionComponent<Props> = ({
processors,
onDragEnd,
nodeComponent,
}) => {
const [currentDragSelector, setCurrentDragSelector] = useState<string | undefined>();
let flatTreeIndex = 0;
const items: Array<[ProcessorSelector, React.ReactElement]> = [];

const addRenderedItems = (
_processors: ProcessorInternal[],
_selector: ProcessorSelector,
level = 0
) => {
_processors.forEach((processor, idx) => {
const index = flatTreeIndex++;
const nodeSelector = _selector.concat(String(idx));
items.push([
nodeSelector,
<TreeNode
key={index}
index={index}
level={level}
processor={processor}
selector={nodeSelector}
component={nodeComponent}
/>,
]);

if (processor.onFailure?.length) {
addRenderedItems(processor.onFailure, nodeSelector.concat(ON_FAILURE), level + 1);
}
});
};

addRenderedItems(processors, [], 0);

return (
<EuiDragDropContext
onBeforeCapture={({ draggableId: selector }) => {
setCurrentDragSelector(selector);
}}
onDragEnd={({ source, destination }) => {
setCurrentDragSelector(undefined);
if (source && destination) {
onDragEnd={({ source, destination, combine }) => {
if (source && combine) {
const [sourceSelector] = items[source.index];
const destinationSelector = combine.draggableId.split('.');
onDragEnd({
source: {
index: source.index,
selector: source.droppableId === ROOT_PATH_ID ? [] : source.droppableId.split('.'),
index: parseInt(sourceSelector[sourceSelector.length - 1], 10),
selector: sourceSelector.slice(0, -1),
},
destination: {
index: destination.index,
selector:
destination.droppableId === ROOT_PATH_ID ? [] : destination.droppableId.split('.'),
index: parseInt(destinationSelector[destinationSelector.length - 1], 10),
selector: destinationSelector.concat(ON_FAILURE),
},
});
return;
}

if (source && destination) {
const [sourceSelector] = items[source.index];
onDragEnd({
source: {
index: parseInt(sourceSelector[sourceSelector.length - 1], 10),
selector: sourceSelector.slice(0, -1),
},
destination: mapDestinationIndexToTreeLocation(
items.map(([selector]) => selector),
!sourceSelector.slice(0, -1).length,
destination.index
),
});
}
}}
>
<PrivateDragAndDropTree
selector={[]}
isDroppable={true}
currentDragSelector={currentDragSelector}
processors={processors}
nodeComponent={nodeComponent}
/>
<EuiDroppable droppableId="PIPELINE_PROCESSORS_EDITOR" spacing="l" isCombineEnabled>
{items.map(([, component]) => component)}
</EuiDroppable>
</EuiDragDropContext>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,11 @@
*/

import React, { FunctionComponent } from 'react';

import { EuiDraggable, EuiFlexGroup, EuiFlexItem, EuiIcon, EuiPanel } from '@elastic/eui';

import { ProcessorInternal, ProcessorSelector } from '../../types';

import { PrivateDragAndDropTree } from './drag_and_drop_tree';

export interface TreeNodeComponentArgs {
processor: ProcessorInternal;
selector: ProcessorSelector;
Expand All @@ -20,46 +19,36 @@ interface Props {
component: (args: TreeNodeComponentArgs) => React.ReactNode;
selector: ProcessorSelector;
processor: ProcessorInternal;
isDroppable: boolean;
index: number;
currentDragSelector?: string;
level: number;
}

/** This value comes from the {@link ProcessorInternal} type */
const ON_FAILURE = 'onFailure';

export const TreeNode: FunctionComponent<Props> = ({
processor,
selector,
index,
component,
isDroppable,
currentDragSelector,
level,
}) => {
const id = selector.join('.');
return (
<EuiDraggable spacing="l" draggableId={id} key={id} index={index} customDragHandle={true}>
{provided => (
<EuiPanel paddingSize="s">
<EuiFlexGroup gutterSize="none" direction="column" alignItems="flexStart">
<EuiFlexGroup gutterSize="s" alignItems="center">
<EuiFlexItem grow={false}>
<div {...provided.dragHandleProps}>
<EuiIcon type="grab" />
</div>
</EuiFlexItem>
<EuiFlexItem grow={false}>{component({ processor, selector })}</EuiFlexItem>
<>
<EuiDraggable spacing="l" draggableId={id} key={id} index={index} customDragHandle={true}>
{provided => (
<EuiPanel style={{ marginLeft: 30 * level + 'px' }} paddingSize="s">
<EuiFlexGroup gutterSize="none" direction="column" alignItems="flexStart">
<EuiFlexGroup gutterSize="s" alignItems="center">
<EuiFlexItem grow={false}>
<div {...provided.dragHandleProps}>
<EuiIcon type="grab" />
</div>
</EuiFlexItem>
<EuiFlexItem grow={false}>{component({ processor, selector })}</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexGroup>
<PrivateDragAndDropTree
currentDragSelector={currentDragSelector}
isDroppable={isDroppable}
selector={selector.concat([ON_FAILURE])}
processors={processor.onFailure ?? []}
nodeComponent={component}
/>
</EuiFlexGroup>
</EuiPanel>
)}
</EuiDraggable>
</EuiPanel>
)}
</EuiDraggable>
</>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import { DraggableLocation, ProcessorSelector } from '../../types';

export const mapDestinationIndexToTreeLocation = (
items: ProcessorSelector[],
isRootLevelSource: boolean,
destinationIndex: number
): DraggableLocation => {
// TODO: This needs to be vastly improved.
const destinationSelector = items[destinationIndex];
const destinationProcessorsSelector = destinationSelector.slice(0, -1);

if (destinationIndex === 0) {
return { selector: [], index: 0 };
}

if (destinationIndex === items.length - 1 && isRootLevelSource) {
return { selector: [], index: items.length - 1 };
}

return {
selector: destinationProcessorsSelector,
index: parseInt(destinationSelector[destinationSelector.length - 1], 10),
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -114,43 +114,41 @@ export const PipelineProcessorsEditor: FunctionComponent<Props> = ({

return (
<>
<EuiPanel>
<DragAndDropTree
onDragEnd={args => {
dispatch({
type: 'moveProcessor',
payload: args,
});
}}
processors={processors}
nodeComponent={({ processor, selector }) => (
<PipelineProcessorEditorItem
onClick={type => {
switch (type) {
case 'edit':
setMode({ id: 'editingProcessor', arg: { processor, selector } });
break;
case 'delete':
// TODO: This should have a delete confirmation modal
dispatch({
type: 'removeProcessor',
payload: { selector },
});
break;
case 'addOnFailure':
setMode({ id: 'creatingOnFailureProcessor', arg: selector });
break;
}
}}
processor={processor}
/>
)}
/>
{/* TODO: Translate */}
<EuiButton onClick={() => setMode({ id: 'creatingTopLevelProcessor' })}>
Add a processor
</EuiButton>
</EuiPanel>
<DragAndDropTree
onDragEnd={args => {
dispatch({
type: 'moveProcessor',
payload: args,
});
}}
processors={processors}
nodeComponent={({ processor, selector }) => (
<PipelineProcessorEditorItem
onClick={type => {
switch (type) {
case 'edit':
setMode({ id: 'editingProcessor', arg: { processor, selector } });
break;
case 'delete':
// TODO: This should have a delete confirmation modal
dispatch({
type: 'removeProcessor',
payload: { selector },
});
break;
case 'addOnFailure':
setMode({ id: 'creatingOnFailureProcessor', arg: selector });
break;
}
}}
processor={processor}
/>
)}
/>
{/* TODO: Translate */}
<EuiButton onClick={() => setMode({ id: 'creatingTopLevelProcessor' })}>
Add a processor
</EuiButton>
{mode.id !== 'idle' ? (
<SettingsFormFlyout
onFormUpdate={onFormUpdate}
Expand Down

0 comments on commit 3ccc16d

Please sign in to comment.