Skip to content
This repository was archived by the owner on Jul 9, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
"dagre-d3": "^0.6.3",
"format-message": "^6.2.1",
"lodash": "^4.17.11",
"office-ui-fabric-react": "^6.180.0",
"office-ui-fabric-react": "^6.192.0",
"prop-types": "^15.7.2",
"source-map-loader": "^0.2.4"
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
ChoiceInput,
} from '../nodes/index';
import { NodeRendererContext } from '../../store/NodeRendererContext';
import { SelectionContext } from '../../store/SelectionContext';

import { NodeProps, defaultNodeProps } from './sharedProps';

Expand All @@ -39,24 +40,40 @@ function chooseRendererByType($type): FC<NodeProps> | ComponentClass<NodeProps>
return renderer;
}

const nodeBorderStyle = css`
outline: 2px solid grey;
const nodeBorderFocusedStyle = css`
outline: 1px solid #323130;
`;

const nodeBorderSelectedStyle = css`
outline: 1px solid #0078d4;
`;

export const StepRenderer: FC<NodeProps> = ({ id, data, onEvent, onResize }): JSX.Element => {
const ChosenRenderer = chooseRendererByType(data.$type);

const { focusedId, focusedEvent } = useContext(NodeRendererContext);
const { getNodeIndex, selectedIds } = useContext(SelectionContext);
const nodeFocused = focusedId === id || focusedEvent === id;
const nodeSelected = selectedIds.includes(id);

return (
<div
className={classnames('step-renderer-container', { 'step-renderer-container--focused': nodeFocused })}
className={classnames(
'step-renderer-container',
{ 'step-renderer-container--focused': nodeFocused },
{ 'step-renderer-container--selected': nodeSelected }
)}
css={css`
display: inline-block;
position: relative;
${nodeFocused && nodeBorderStyle}
${nodeFocused && nodeBorderFocusedStyle};
${nodeSelected && nodeBorderSelectedStyle};
&:hover {
${nodeBorderFocusedStyle}
}
`}
data-is-focusable={true}
data-selection-index={getNodeIndex(id)}
>
<ChosenRenderer
id={id}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,24 @@
/** @jsx jsx */
import { jsx } from '@emotion/core';
import { useContext, FC } from 'react';
import { useContext, FC, useEffect, useState, useRef } from 'react';
import { MarqueeSelection, Selection } from 'office-ui-fabric-react/lib/MarqueeSelection';

import { NodeEventTypes } from '../shared/NodeEventTypes';
import { deleteNode, insert } from '../shared/jsonTracker';
import DragScroll from '../components/DragScroll';
import { NodeRendererContext } from '../store/NodeRendererContext';
import { SelectionContext, SelectionContextData } from '../store/SelectionContext';
import { NodeIndexGenerator } from '../shared/NodeIndexGetter';

import { AdaptiveDialogEditor } from './AdaptiveDialogEditor';

export const ObiEditor: FC<ObiEditorProps> = ({ path, data, onFocusEvent, onFocusSteps, onOpen, onChange }) => {
export const ObiEditor: FC<ObiEditorProps> = ({
path,
data,
onFocusEvent,
onFocusSteps,
onOpen,
onChange,
}): JSX.Element | null => {
let divRef;

const { focusedId, removeLgTemplate } = useContext(NodeRendererContext);
Expand All @@ -18,6 +27,7 @@ export const ObiEditor: FC<ObiEditorProps> = ({ path, data, onFocusEvent, onFocu
let handler;
switch (eventName) {
case NodeEventTypes.Focus:
resetSelectionData();
handler = id => onFocusSteps(id ? [id] : []);
break;
case NodeEventTypes.FocusEvent:
Expand Down Expand Up @@ -64,37 +74,81 @@ export const ObiEditor: FC<ObiEditorProps> = ({ path, data, onFocusEvent, onFocu
return null;
};

if (!data) return renderFallbackContent();
const resetSelectionData = () => {
nodeIndexGenerator.current.reset();
setSelectionContext({
getNodeIndex: selectionContext.getNodeIndex,
selectedIds: [],
});
};
const nodeIndexGenerator = useRef(new NodeIndexGenerator());
const nodeItems = nodeIndexGenerator.current.getItemList();
const [selectionContext, setSelectionContext] = useState<SelectionContextData>({
getNodeIndex: (nodeId: string): number => nodeIndexGenerator.current.getNodeIndex(nodeId),
selectedIds: [],
});

useEffect(
(): void => {
selection.setItems(nodeIndexGenerator.current.getItemList());
}
);

useEffect((): void => {
resetSelectionData();
}, [data]);

const selection = new Selection({
onSelectionChanged: (): void => {
const selectedIndices = selection.getSelectedIndices();
const selectedIds = selectedIndices.map(index => nodeItems[index].key as string);
const newContext = {
getNodeIndex: selectionContext.getNodeIndex,
selectedIds,
};
console.log(selectedIds);
setSelectionContext(newContext);
},
});

if (!data) return renderFallbackContent();
return (
<div
tabIndex={0}
className="obi-editor-container"
data-testid="obi-editor-container"
css={{ width: '100%', height: '100%', padding: '20px', boxSizing: 'border-box', '&:focus': { outline: 'none' } }}
ref={el => (divRef = el)}
onKeyUp={e => {
const keyString = e.key;
if (keyString === 'Delete' && focusedId) {
dispatchEvent(NodeEventTypes.Delete, { id: focusedId });
}
}}
onClick={e => {
e.stopPropagation();
dispatchEvent(NodeEventTypes.Focus, '');
}}
>
<DragScroll>
<AdaptiveDialogEditor
id={path}
data={data}
onEvent={(eventName, eventData) => {
divRef.focus({ preventScroll: true });
dispatchEvent(eventName, eventData);
<SelectionContext.Provider value={selectionContext}>
<MarqueeSelection selection={selection}>
<div
tabIndex={0}
className="obi-editor-container"
data-testid="obi-editor-container"
css={{
width: '100%',
height: '100%',
padding: '20px',
boxSizing: 'border-box',
'&:focus': { outline: 'none' },
}}
ref={el => (divRef = el)}
onKeyUp={e => {
const keyString = e.key;
if (keyString === 'Delete' && focusedId) {
dispatchEvent(NodeEventTypes.Delete, { id: focusedId });
}
}}
onClick={e => {
e.stopPropagation();
dispatchEvent(NodeEventTypes.Focus, '');
}}
/>
</DragScroll>
</div>
>
<AdaptiveDialogEditor
id={path}
data={data}
onEvent={(eventName, eventData) => {
divRef.focus({ preventScroll: true });
dispatchEvent(eventName, eventData);
}}
/>
</div>
</MarqueeSelection>
</SelectionContext.Provider>
);
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ const VisualDesigner: React.FC<VisualDesignerProps> = ({

return (
<NodeRendererContext.Provider value={context}>
<div data-testid="visualdesigner-container" css={{ width: '100%', height: '100%' }}>
<div data-testid="visualdesigner-container" css={{ width: '100%', height: '100%', overflow: 'scroll' }}>
<ObiEditor
key={dialogId}
path={dialogId}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { IObjectWithKey } from 'office-ui-fabric-react/lib/MarqueeSelection';

export class NodeIndexGenerator {
private _id = 0;

private _indexByNodeId = {};

constructor(initialId = 0) {
this._id = initialId;
}

reset(): void {
this._id = 0;
this._indexByNodeId = {};
}

getNodeIndex(nodeId: string): number {
let index;

if (nodeId in this._indexByNodeId) {
index = this._indexByNodeId[nodeId];
} else {
index = this._id++;
this._indexByNodeId[nodeId] = index;
}

return index;
}

getItemList(): IObjectWithKey[] {
const itemList = [];

const ids = Object.keys(this._indexByNodeId);
for (const id of ids) {
const index = this._indexByNodeId[id];
(itemList[index] as any) = { key: id };
}

return itemList;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import React from 'react';

export interface SelectionContextData {
getNodeIndex: (id: string) => number;
selectedIds: string[];
}

export const SelectionContext = React.createContext<SelectionContextData>({
getNodeIndex: (_: string): number => 0,
selectedIds: [] as string[],
});