Skip to content

Commit

Permalink
Endpoint: Fix resolver SVG position issue (#61886) (#62021)
Browse files Browse the repository at this point in the history
Panning, zooming, centering did now always work correctly.
  • Loading branch information
Robert Austin authored Mar 31, 2020
1 parent f1f1229 commit 40ef05e
Show file tree
Hide file tree
Showing 4 changed files with 77 additions and 57 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -43,14 +43,15 @@ interface UserChangedSelectedEvent {
interface AppRequestedResolverData {
readonly type: 'appRequestedResolverData';
}

/**
* When the user switches the active descendent of the Resolver.
*/
interface UserFocusedOnResolverNode {
readonly type: 'userFocusedOnResolverNode';
readonly payload: {
/**
* Used to identify the process node that should be brought into view.
* Used to identify the process node that the user focused on (in the DOM)
*/
readonly nodeId: string;
};
Expand Down
35 changes: 22 additions & 13 deletions x-pack/plugins/endpoint/public/embeddables/resolver/view/defs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
euiPaletteForStatus,
colorPalette,
} from '@elastic/eui';
import styled from 'styled-components';

/**
* Generating from `colorPalette` function: This could potentially
Expand Down Expand Up @@ -396,17 +397,25 @@ const SymbolsAndShapes = memo(() => (
));

/**
* This <defs> element is used to define the reusable assets for the Resolver
* It confers sevral advantages, including but not limited to:
* 1) Freedom of form for creative assets (beyond box-model constraints)
* 2) Separation of concerns between creative assets and more functional areas of the app
* 3) <use> elements can be handled by compositor (faster)
* This `<defs>` element is used to define the reusable assets for the Resolver
* It confers several advantages, including but not limited to:
* 1. Freedom of form for creative assets (beyond box-model constraints)
* 2. Separation of concerns between creative assets and more functional areas of the app
* 3. `<use>` elements can be handled by compositor (faster)
*/
export const SymbolDefinitions = memo(() => (
<svg>
<defs>
<PaintServers />
<SymbolsAndShapes />
</defs>
</svg>
));
export const SymbolDefinitions = styled(
memo(({ className }: { className?: string }) => (
<svg className={className}>
<defs>
<PaintServers />
<SymbolsAndShapes />
</defs>
</svg>
))
)`
position: absolute;
left: 100%;
top: 100%;
width: 0;
height: 0;
`;
25 changes: 16 additions & 9 deletions x-pack/plugins/endpoint/public/embeddables/resolver/view/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -88,15 +88,22 @@ export const Resolver = styled(
projectionMatrix={projectionMatrix}
/>
))}
{Array.from(processNodePositions).map(([processEvent, position], index) => (
<ProcessEventDot
key={index}
position={position}
projectionMatrix={projectionMatrix}
event={processEvent}
adjacentNodeMap={processToAdjacencyMap.get(processEvent)}
/>
))}
{[...processNodePositions].map(([processEvent, position], index) => {
const adjacentNodeMap = processToAdjacencyMap.get(processEvent);
if (!adjacentNodeMap) {
// This should never happen
throw new Error('Issue calculating adjacency node map.');
}
return (
<ProcessEventDot
key={index}
position={position}
projectionMatrix={projectionMatrix}
event={processEvent}
adjacentNodeMap={adjacentNodeMap}
/>
);
})}
</StyledResolverContainer>
)}
<StyledPanel />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ export const ProcessEventDot = styled(
/**
* map of what nodes are "adjacent" to this one in "up, down, previous, next" directions
*/
adjacentNodeMap?: AdjacentProcessMap;
adjacentNodeMap: AdjacentProcessMap;
}) => {
/**
* Convert the position, which is in 'world' coordinates, to screen coordinates.
Expand All @@ -91,7 +91,7 @@ export const ProcessEventDot = styled(

const [magFactorX] = projectionMatrix;

const selfId = adjacentNodeMap?.self;
const selfId = adjacentNodeMap.self;

const nodeViewportStyle = useMemo(
() => ({
Expand All @@ -117,27 +117,31 @@ export const ProcessEventDot = styled(

const labelYHeight = markerSize / 1.7647;

const levelAttribute = adjacentNodeMap?.level
? {
'aria-level': adjacentNodeMap.level,
}
: {};

const flowToAttribute = adjacentNodeMap?.nextSibling
? {
'aria-flowto': adjacentNodeMap.nextSibling,
}
: {};
/**
* An element that should be animated when the node is clicked.
*/
const animationTarget: {
current:
| (SVGAnimationElement & {
/**
* `beginElement` is by [w3](https://www.w3.org/TR/SVG11/animate.html#__smil__ElementTimeControl__beginElement)
* but missing in [TSJS-lib-generator](https://github.com/microsoft/TSJS-lib-generator/blob/15a4678e0ef6de308e79451503e444e9949ee849/inputfiles/addedTypes.json#L1819)
*/
beginElement: () => void;
})
| null;
} = React.createRef();
const { cubeSymbol, labelFill, descriptionFill, descriptionText } = nodeAssets[
nodeType(event)
];
const resolverNodeIdGenerator = useMemo(() => htmlIdGenerator('resolverNode'), []);

const nodeType = getNodeType(event);
const clickTargetRef: { current: SVGAnimationElement | null } = React.createRef();
const { cubeSymbol, labelFill, descriptionFill, descriptionText } = nodeAssets[nodeType];
const resolverNodeIdGenerator = htmlIdGenerator('resolverNode');
const [nodeId, labelId, descriptionId] = [
!!selfId ? resolverNodeIdGenerator(String(selfId)) : resolverNodeIdGenerator(),
resolverNodeIdGenerator(),
resolverNodeIdGenerator(),
] as string[];
const nodeId = useMemo(() => resolverNodeIdGenerator(selfId), [
resolverNodeIdGenerator,
selfId,
]);
const labelId = useMemo(() => resolverNodeIdGenerator(), [resolverNodeIdGenerator]);
const descriptionId = useMemo(() => resolverNodeIdGenerator(), [resolverNodeIdGenerator]);

const dispatch = useResolverDispatch();

Expand All @@ -154,14 +158,11 @@ export const ProcessEventDot = styled(
[dispatch, nodeId]
);

const handleClick = useCallback(
(clickEvent: React.MouseEvent<SVGSVGElement, MouseEvent>) => {
if (clickTargetRef.current !== null) {
(clickTargetRef.current as any).beginElement();
}
},
[clickTargetRef]
);
const handleClick = useCallback(() => {
if (animationTarget.current !== null) {
animationTarget.current.beginElement();
}
}, [animationTarget]);

return (
<EuiKeyboardAccessible>
Expand All @@ -171,8 +172,10 @@ export const ProcessEventDot = styled(
viewBox="-15 -15 90 30"
preserveAspectRatio="xMidYMid meet"
role="treeitem"
{...levelAttribute}
{...flowToAttribute}
aria-level={adjacentNodeMap.level}
aria-flowto={
adjacentNodeMap.nextSibling === null ? undefined : adjacentNodeMap.nextSibling
}
aria-labelledby={labelId}
aria-describedby={descriptionId}
aria-haspopup={'true'}
Expand Down Expand Up @@ -202,7 +205,7 @@ export const ProcessEventDot = styled(
begin="click"
repeatCount="1"
className="squish"
ref={clickTargetRef}
ref={animationTarget}
/>
</use>
<use
Expand Down Expand Up @@ -273,7 +276,7 @@ const processTypeToCube: Record<ResolverProcessType, keyof typeof nodeAssets> =
unknownEvent: 'runningProcessCube',
};

function getNodeType(processEvent: ResolverEvent): keyof typeof nodeAssets {
function nodeType(processEvent: ResolverEvent): keyof typeof nodeAssets {
const processType = processModel.eventType(processEvent);

if (processType in processTypeToCube) {
Expand Down

0 comments on commit 40ef05e

Please sign in to comment.