8 Commits

Author SHA1 Message Date
Bogdan Lyashenko
ba1cab4fcb Finish cc tree 2018-08-18 18:35:35 +02:00
Bogdan Lyashenko
d40df8622c Add few moew edges 2018-08-16 19:53:04 +02:00
Bogdan Lyashenko
578e33eb4b CC tree 2018-08-16 19:28:13 +02:00
Bogdan Lyashenko
95e0cf5918 Start moving codecrumbs 2018-08-15 21:00:40 +02:00
Bogdan Lyashenko
c8a1f9c3b8 Finish dep tree 2018-08-14 19:53:52 +02:00
Bogdan Lyashenko
23eed87017 Move dep tree 2018-08-11 21:06:13 +02:00
Bogdan Lyashenko
dce00ac8b2 Migrate source tree 2018-08-11 18:45:01 +02:00
Bogdan Lyashenko
9352424aa7 Start refactoring 2018-08-09 19:16:55 +02:00
29 changed files with 1757 additions and 7711 deletions

View File

@@ -23,6 +23,7 @@
"babel-traverse": "^6.26.0",
"babylon": "^6.18.0",
"chokidar": "^2.0.3",
"classnames": "^2.2.6",
"css-loader": "^0.28.11",
"d3-flextree": "^2.1.1",
"directory-tree": "^2.1.0",
@@ -38,7 +39,6 @@
"redux-saga": "^0.16.0",
"redux-thunk": "^2.2.0",
"style-loader": "^0.21.0",
"svg.js": "^2.6.4",
"webpack": "^4.6.0",
"websocket": "^1.0.26"
},

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -3,6 +3,7 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Capa_1" x="0px" y="0px" viewBox="0 0 448.011 448.011" style="enable-background:new 0 0 448.011 448.011;" xml:space="preserve" width="512px" height="512px">
<g>
<g>
<rect x="120" y="0" width="320" height="445" style="fill:#fff" />
<path d="M438.731,209.463l-416-192c-6.624-3.008-14.528-1.216-19.136,4.48c-4.64,5.696-4.8,13.792-0.384,19.648l136.8,182.4 l-136.8,182.4c-4.416,5.856-4.256,13.984,0.352,19.648c3.104,3.872,7.744,5.952,12.448,5.952c2.272,0,4.544-0.48,6.688-1.472 l416-192c5.696-2.624,9.312-8.288,9.312-14.528S444.395,212.087,438.731,209.463z" fill="#1890ff"/>
</g>
</g>

Before

Width:  |  Height:  |  Size: 770 B

After

Width:  |  Height:  |  Size: 838 B

View File

@@ -47,7 +47,7 @@ const DefaultState = {
key: CONTROLS_KEYS.CODE_CRUMBS_MINIMIZE
},
{
name: 'show details',
name: 'show details (remove it)',
title: 'Show all Details',
key: CONTROLS_KEYS.CODE_CRUMBS_DETAILS
}

View File

@@ -1,148 +0,0 @@
import React from 'react';
import { withSvgDraw } from '../utils/SvgDraw';
import { drawCodeCrumbEdge, drawPartEdge, drawCodeCrumbLoc, drawPopOver } from './drawHelpers';
import { drawFileText, drawFileIcon } from '../SourceTree/drawHelpers';
import { getFilesList } from '../../../../utils/treeLayout';
import { createSet } from '../utils/SvgSet';
class CodeCrumbsTree extends React.Component {
componentDidMount() {
this.drawSet = createSet(this.props.primaryDraw);
this.drawTree();
}
componentDidUpdate() {
this.clearDraw();
this.drawTree();
}
componentWillUnmount() {
this.clearDraw();
}
shouldComponentUpdate(nextProps) {
return true;
//TODO: missing overlapping elements: text&icons
/*const oldProps = this.props;
return oldProps.filesTreeLayoutNodes !== nextProps.filesTreeLayoutNodes;*/
}
clearDraw() {
this.drawSet.clearAll();
}
drawTree() {
const {
primaryDraw,
filesTreeLayoutNodes,
shiftToCenterPoint,
sourceDiagramOn,
dependenciesDiagramOn,
codeCrumbsMinimize,
codeCrumbsDetails,
onCodeCrumbSelect
} = this.props;
const { add } = this.drawSet;
const filesList = getFilesList(filesTreeLayoutNodes);
filesList.forEach(node => {
const [nX, nY] = [node.y, node.x];
if (node.children) {
if (!sourceDiagramOn && !dependenciesDiagramOn) {
add(
drawFileText(primaryDraw, shiftToCenterPoint, {
x: nX,
y: nY,
name: node.data.name
})
);
add(
drawFileIcon(primaryDraw, shiftToCenterPoint, {
x: nX,
y: nY,
codeCrumbsMinimize
})
);
}
!codeCrumbsMinimize &&
add(
drawPartEdge(primaryDraw, shiftToCenterPoint, {
source: {
x: nX,
y: nY
},
parentName: node.data.name
})
);
!codeCrumbsMinimize &&
node.children.forEach((crumb, i, list) => {
const [cX, cY] = [crumb.y, crumb.x];
const singleCrumb = list.length === 1;
!singleCrumb &&
add(
drawCodeCrumbEdge(primaryDraw, shiftToCenterPoint, {
source: {
x: nX,
y: nY
},
target: {
x: cX,
y: cY
},
parentName: node.data.name
})
);
//TODO: refactor mess
const loc = crumb.data.crumbedLineNode.loc.start;
add(
drawCodeCrumbLoc(primaryDraw, shiftToCenterPoint, {
x: cX,
y: cY,
loc: `(${loc.line},${loc.column})`,
name: crumb.data.name,
singleCrumb,
onMouseOver() {
if (!crumb.data.params.details || codeCrumbsDetails) return null;
return drawPopOver(primaryDraw, shiftToCenterPoint, {
x: cX,
y: cY,
name: crumb.data.params.details,
singleCrumb
});
},
onClick() {
onCodeCrumbSelect(node.data, crumb.data);
}
})
);
if (codeCrumbsDetails && crumb.data.params.details) {
add(
drawPopOver(primaryDraw, shiftToCenterPoint, {
x: cX,
y: cY,
name: crumb.data.params.details,
singleCrumb
})
);
}
});
}
});
}
render() {
return null;
}
}
export default withSvgDraw(CodeCrumbsTree);

View File

@@ -1,113 +0,0 @@
import { PURPLE_COLOR, BLUE_COLOR, SYMBOL_WIDTH } from '../../store/constants';
export const drawCodeCrumbEdge = (draw, shiftToCenterPoint, { target, source, parentName }) => {
const nameWidth = SYMBOL_WIDTH * parentName.length;
const padding = 40;
const edgeTurnDistance = 20;
const P1 = shiftToCenterPoint(source.x + nameWidth + padding, source.y);
const P2 = shiftToCenterPoint(target.x - edgeTurnDistance, source.y);
const P3 = shiftToCenterPoint(target.x - edgeTurnDistance, target.y);
const P4 = shiftToCenterPoint(target.x, target.y);
const polyline = draw.polyline([[P1.x, P1.y], [P2.x, P2.y], [P3.x, P3.y], [P4.x, P4.y]]);
polyline.fill('none').stroke({
color: PURPLE_COLOR
});
return polyline;
};
export const drawPartEdge = (draw, shiftToCenterPoint, { source, parentName }) => {
const nameWidth = SYMBOL_WIDTH * parentName.length;
const padding = 17;
const P1 = shiftToCenterPoint(source.x + nameWidth + padding, source.y);
const P2 = { x: P1.x + padding + 6, y: P1.y };
const polyline = draw.polyline([[P1.x, P1.y], [P2.x, P2.y]]);
polyline.fill('none').stroke({
color: PURPLE_COLOR
});
const smallLine = draw.line(P1.x, P1.y - 2, P1.x, P1.y + 2).stroke({ color: PURPLE_COLOR });
return [polyline, smallLine];
};
export const drawCodeCrumbLoc = (
draw,
shiftToCenterPoint,
{ x, y, name = '', loc, singleCrumb, onClick, onMouseOver }
) => {
const textPointShiftX = 3;
const textPointShiftY = 5;
const textPoint = shiftToCenterPoint(singleCrumb ? x - 20 : x, y);
const locWidth = loc.length * 6;
const locRec = draw
.rect(locWidth, 12)
.fill('#fff')
.stroke(PURPLE_COLOR)
.move(textPoint.x, textPoint.y - 6);
const locText = draw.text(loc);
locText
.font({ fill: '#595959', family: 'Menlo', size: '8px' })
.style({ cursor: 'pointer' })
.move(textPoint.x + textPointShiftX, textPoint.y - textPointShiftY);
if (onMouseOver) {
let popOver = null;
locText.on('mouseover', () => {
popOver = onMouseOver();
});
locText.on('mouseout', () => {
popOver && popOver[0].remove() && popOver[1].remove();
});
}
if (onClick) {
locText.on('click', onClick);
}
if (name) {
const nameText = draw.text(':' + name);
nameText.font({ fill: '#595959', family: 'Menlo', size: '12px' });
//TODO: refactor to use one way, plus or minus
nameText.move(textPoint.x + textPointShiftX + locWidth - 1, textPoint.y - textPointShiftY - 2);
return [locRec, locText, nameText];
}
return [locRec, locText];
};
export const drawPopOver = (draw, shiftToCenterPoint, { x, y, name = '', singleCrumb }) => {
const tPt = shiftToCenterPoint(x - 15 + (singleCrumb ? 0 : 20), y - 24);
const nameWidth = name.length * 6;
const nameHeight = 8;
const padding = 5;
const polyline = draw.polyline([
[tPt.x - padding, tPt.y + nameHeight + padding + 3],
[tPt.x - padding, tPt.y - padding],
[tPt.x + nameWidth + 2 * padding, tPt.y - padding],
[tPt.x + nameWidth + 2 * padding, tPt.y + nameHeight + padding],
[tPt.x - padding + 3, tPt.y + nameHeight + padding],
[tPt.x - padding, tPt.y + nameHeight + padding + 3]
]);
polyline.fill('#fff').stroke({
color: PURPLE_COLOR
});
const text = draw.text(name);
text.font({ fill: '#595959', family: 'Menlo', size: '10px' });
text.move(tPt.x + 2, tPt.y - 1);
return [text, polyline];
};

View File

@@ -1,108 +0,0 @@
import React from 'react';
import { drawDependenciesEdge } from './drawHelpers';
import { drawFileText, drawFileIcon } from '../SourceTree/drawHelpers';
import { getFilesList } from '../../../../utils/treeLayout';
import { withSvgDraw } from '../utils/SvgDraw';
class DependenciesTree extends React.Component {
componentDidMount() {
this.drawTree();
}
componentDidUpdate() {
const { primaryDraw } = this.props;
primaryDraw.clear();
this.drawTree();
}
//move to utils
findNodeByPathName = (list = [], pathName) => {
return list.find(l => l.data.path === pathName);
};
getFilteredDependenciesList() {
const { dependenciesList, dependenciesEntryPoint, dependenciesShowOneModule } = this.props;
const entryPoint = dependenciesEntryPoint || {
path: dependenciesList[0].moduleName
};
if (dependenciesShowOneModule) {
return [dependenciesList.find(d => d.moduleName === entryPoint.path)];
}
return this.collectDependencies(entryPoint.path, dependenciesList);
}
collectDependencies(entryModuleName, dependenciesList) {
let queue = [].concat(entryModuleName),
store = [];
while (queue.length) {
let moduleName = queue.shift(),
entryModule = dependenciesList.find(d => d.moduleName === moduleName);
store.push(entryModule);
const nodeBody = entryModule.importedModuleNames;
if (nodeBody) {
queue = [...queue, ...nodeBody];
}
}
return store;
}
drawTree() {
const { primaryDraw, filesTreeLayoutNodes, shiftToCenterPoint, sourceDiagramOn } = this.props;
const moduleFilesList = getFilesList(filesTreeLayoutNodes);
const filteredDependenciesList = this.getFilteredDependenciesList();
filteredDependenciesList.forEach(({ moduleName, importedModuleNames }) => {
const moduleNode = this.findNodeByPathName(moduleFilesList, moduleName);
if (!moduleNode) return;
const [mX, mY] = [moduleNode.y, moduleNode.x];
if (!sourceDiagramOn) {
drawFileText(primaryDraw, shiftToCenterPoint, {
x: mX,
y: mY,
name: moduleNode.data.name
});
drawFileIcon(primaryDraw, shiftToCenterPoint, {
x: mX,
y: mY
});
}
importedModuleNames.reduce((prevSource, name) => {
const importedNode = this.findNodeByPathName(moduleFilesList, name);
if (!importedNode) return;
const [iX, iY] = [importedNode.y, importedNode.x];
//TODO: implementation iterations:
//1) done: first with sharp angles + overlay
//2) done: without overlaying, not fot all cases
//3) rounded angles
const source = { x: iX, y: iY };
drawDependenciesEdge(primaryDraw, shiftToCenterPoint, {
source,
target: { x: mX, y: mY },
prevSource
});
return source;
}, null);
});
}
render() {
return null;
}
}
export default withSvgDraw(DependenciesTree);

View File

@@ -1,86 +0,0 @@
import { getCurvedPath } from '../../../../utils/svgPrimitives';
import { BLUE_COLOR, PURPLE_COLOR } from '../../store/constants';
//TODO: move numbers to config per function
const COLOR = BLUE_COLOR;
export const drawDependenciesEdge = (draw, shiftToCenterPoint, { source, target, prevSource }) => {
const padding = 30;
const halfPadding = padding / 2 - 5;
const sourcePt = shiftToCenterPoint(
target.y > source.y ? source.x + 10 : source.x + 8,
target.y > source.y ? source.y + 7 : source.y - 12
);
drawSourceDotLine(draw, sourcePt);
if (!prevSource) {
const targetPt = shiftToCenterPoint(target.x, target.y);
const P1 = { x: sourcePt.x, y: targetPt.y + padding - 6 };
const P2 = { x: targetPt.x - halfPadding, y: targetPt.y + padding - 6 };
const P3 = { x: targetPt.x - halfPadding, y: targetPt.y };
drawConnectionLine(draw, [
[sourcePt.x, sourcePt.y],
[P1.x, P1.y],
[P2.x, P2.y],
[P3.x, P3.y],
[targetPt.x, targetPt.y]
]);
drawArrow(draw, shiftToCenterPoint, target.x, target.y + 6);
} else {
if (prevSource.x < sourcePt.x) {
//TODO: handle other cases
const prevSourcePt = shiftToCenterPoint(prevSource.x, prevSource.y);
const P1 = { x: sourcePt.x, y: sourcePt.y + halfPadding - 3 };
const P2 = {
x: prevSourcePt.x + halfPadding,
y: sourcePt.y + halfPadding - 3
};
drawConnectionLine(draw, [[sourcePt.x, sourcePt.y], [P1.x, P1.y], [P2.x, P2.y]]);
drawDot(draw, P2);
}
}
};
export const drawDot = (draw, { x, y }) => {
const radius = 4;
const halfRadius = radius / 2;
draw
.circle(radius)
.fill(BLUE_COLOR)
.move(x - halfRadius, y - halfRadius);
};
const drawConnectionLine = (draw, points) => {
const polyline = draw.polyline(points);
polyline.fill('none').stroke({
color: COLOR
});
};
const drawSourceDotLine = (draw, { x, y }) => {
draw.line(x - 3, y, x + 3, y).stroke({ width: 1, color: COLOR });
};
const drawArrow = (draw, shiftToCenterPoint, nX, nY) => {
const fileIconPath = 'resources/right-arrow.svg';
const fileIconSize = 7;
const fileIconPointShiftX = -4;
const fileIconPointShiftY = 9.5;
const fileIconPoint = shiftToCenterPoint(nX + fileIconPointShiftX, nY - fileIconPointShiftY);
draw
.rect(5, 6)
.fill('#fff')
.move(fileIconPoint.x + 2, fileIconPoint.y);
draw.image(fileIconPath, fileIconSize, fileIconSize).move(fileIconPoint.x, fileIconPoint.y);
};

View File

@@ -0,0 +1,11 @@
.Dot {
fill: #BFBFBF;
}
.Dot-disabled {
fill: #ccc;
}
.Dot-highlighted {
fill: #1890ff;
}

View File

@@ -0,0 +1,21 @@
import React from 'react';
import classNames from 'classnames';
import './index.css';
export const Dot = props => {
const { position, disabled, highlighted } = props;
const radius = 2.5;
return (
<circle
r={radius}
cx={position.x}
cy={position.y}
className={classNames('Dot', {
'Dot-disabled': !!disabled,
'Dot-highlighted': !!highlighted
})}
/>
);
};

View File

@@ -0,0 +1,41 @@
import React from 'react';
import './index.css';
import { SYMBOL_WIDTH } from '../../store/constants';
export const PartEdge = props => {
const { sourcePosition, parentName } = props;
const nameWidth = SYMBOL_WIDTH * parentName.length;
const padding = 17;
const P1 = { x: sourcePosition.x + nameWidth + padding, y: sourcePosition.y };
const P2 = { x: P1.x + padding + 6, y: P1.y };
const polylinePoints = [[P1.x, P1.y], [P2.x, P2.y]];
return (
<React.Fragment>
<polyline points={polylinePoints.join(', ')} className={'CodeCrumbEdge'} />
<line x1={P1.x} y1={P1.y - 2} x2={P1.x} y2={P1.y + 2} className={'CodeCrumbEdge'} />
</React.Fragment>
);
};
export const CodeCrumbEdge = props => {
const { sourcePosition, targetPosition, parentName } = props;
const nameWidth = SYMBOL_WIDTH * parentName.length;
const padding = 40;
const edgeTurnDistance = 20;
const P1 = { x: sourcePosition.x + nameWidth + padding, y: sourcePosition.y };
const P2 = { x: targetPosition.x - edgeTurnDistance, y: sourcePosition.y };
const P3 = { x: targetPosition.x - edgeTurnDistance, y: targetPosition.y };
const P4 = targetPosition;
const polylinePoints = [[P1.x, P1.y], [P2.x, P2.y], [P3.x, P3.y], [P4.x, P4.y]];
return <polyline points={polylinePoints.join(', ')} className={'CodeCrumbEdge'} />;
};

View File

@@ -0,0 +1,106 @@
import React from 'react';
import './index.css';
const PADDING = 30;
const HALF_PADDING = PADDING / 2 - 5;
export const getSourcePt = (sourcePosition, targetPosition) => ({
x: targetPosition.y > sourcePosition.y ? sourcePosition.x + 10 : sourcePosition.x + 8,
y: targetPosition.y > sourcePosition.y ? sourcePosition.y + 7 : sourcePosition.y - 12
});
export const getSourceDotLinePoints = sourcePt => [
[sourcePt.x - 3, sourcePt.y],
[sourcePt.x + 3, sourcePt.y]
];
export const getConnectionLinePoints = (targetPosition, prevSourcePosition, sourcePt) => {
if (!prevSourcePosition) {
const P1 = { x: sourcePt.x, y: targetPosition.y + PADDING - 6 };
const P2 = { x: targetPosition.x - HALF_PADDING, y: targetPosition.y + PADDING - 6 };
const P3 = { x: targetPosition.x - HALF_PADDING, y: targetPosition.y };
return [
[sourcePt.x, sourcePt.y],
[P1.x, P1.y],
[P2.x, P2.y],
[P3.x, P3.y],
[targetPosition.x, targetPosition.y]
];
}
if (prevSourcePosition.x < sourcePt.x) {
//TODO: handle other cases
const P1 = { x: sourcePt.x, y: sourcePt.y + HALF_PADDING - 3 };
const P2 = {
x: prevSourcePosition.x + HALF_PADDING,
y: sourcePt.y + HALF_PADDING - 3
};
return [[sourcePt.x, sourcePt.y], [P1.x, P1.y], [P2.x, P2.y]];
}
};
export const DependenciesEdge = props => {
const {
targetPosition,
sourcePosition,
prevSourcePosition,
onClick = () => console.log('on dependencies edge')
} = props;
const sourcePt = getSourcePt(sourcePosition, targetPosition);
const sourceDotLinePoints = getSourceDotLinePoints(sourcePt);
const connectionLinePoints = getConnectionLinePoints(
targetPosition,
prevSourcePosition,
sourcePt
);
if (!connectionLinePoints) {
return null;
}
const lastPt = connectionLinePoints[connectionLinePoints.length - 1];
const endPointConfig = {
radius: 2,
x: lastPt[0],
y: lastPt[1]
};
if (prevSourcePosition) {
endPointConfig.radius = 2; // TODO: maybe we can use right away in SVG? it's static anyway!!
} else {
endPointConfig.x -= 5;
endPointConfig.y -= 4;
endPointConfig.iconSize = 8;
endPointConfig.iconPath = 'resources/right-arrow.svg'; // TODO: move to getter
}
return (
<React.Fragment>
<polyline points={sourceDotLinePoints.join(', ')} className={'DependenciesEdge'} />
<polyline points={connectionLinePoints.join(', ')} className={'DependenciesEdge'} />
<polyline
onClick={onClick}
points={connectionLinePoints.join(', ')}
className={'EdgeMouseHandler'}
/>
{prevSourcePosition ? (
<circle
className={'DependenciesEdge-end-dot'}
r={endPointConfig.radius}
cx={endPointConfig.x}
cy={endPointConfig.y}
/>
) : (
<image
x={endPointConfig.x}
y={endPointConfig.y}
xlinkHref={endPointConfig.iconPath}
height={endPointConfig.iconSize}
width={endPointConfig.iconSize}
/>
)}
</React.Fragment>
);
};

View File

@@ -0,0 +1,36 @@
import React from 'react';
import classNames from 'classnames';
import './index.css';
export const SourceEdge = props => {
const {
targetPosition,
sourcePosition,
disabled,
singleChild,
onClick = () => console.log('on source edge')
} = props;
const edgeTurnDistance = 20;
const START_PT = sourcePosition;
const P2 = { x: targetPosition.x - edgeTurnDistance, y: sourcePosition.y };
const P3 = { x: targetPosition.x - edgeTurnDistance, y: targetPosition.y };
const END_PT = targetPosition;
const points = singleChild
? [[START_PT.x, START_PT.y], [END_PT.x, END_PT.y]]
: [[START_PT.x, START_PT.y], [P2.x, P2.y], [P3.x, P3.y], [END_PT.x, END_PT.y]];
return (
<React.Fragment>
<polyline
points={points.join(', ')}
className={classNames('SourceEdge', {
'SourceEdge-disabled': disabled
})}
/>
<polyline onClick={onClick} points={points.join(', ')} className={'EdgeMouseHandler'} />
</React.Fragment>
);
};

View File

@@ -0,0 +1,29 @@
.EdgeMouseHandler {
cursor: pointer;
fill: none;
stroke-width: 8px;
stroke: rgba(0,0,0,0);
}
.SourceEdge {
fill: none;
stroke: #BFBFBF;
}
.SourceEdge-disabled {
stroke: #ccc;
}
.DependenciesEdge {
fill: none;
stroke: #1890ff;
}
.DependenciesEdge-end-dot {
fill: #1890ff;
}
.CodeCrumbEdge {
fill: none;
stroke: #ff18a6;
}

View File

@@ -0,0 +1,43 @@
import React from 'react';
import './index.css';
export const CodeCrumbName = props => {
// onMouseOver maybe use onMouseOver to show crumb details in popover
const { position, loc, name, singleCrumb, onMouseOver, onClick } = props;
const textPoint = { x: singleCrumb ? position.x - 20 : position.x, y: position.y };
const symbolWidth = 6;
const locWidth = loc.length * symbolWidth;
return (
<React.Fragment>
<rect
x={textPoint.x}
y={textPoint.y - 6}
width={locWidth}
height={12}
className={'CodeCrumbName-rect'}
/>
<text
x={textPoint.x + 3}
y={textPoint.y + 3}
onClick={onClick}
className={'CodeCrumbName-loc'}
>
{loc}
</text>
{(name && (
<text
x={textPoint.x + 3 + locWidth - 1}
y={textPoint.y + 4}
onClick={onClick}
className={'CodeCrumbName-text'}
>
:{name}
</text>
)) ||
null}
</React.Fragment>
);
};

View File

@@ -0,0 +1,37 @@
import React from 'react';
import classNames from 'classnames';
import './index.css';
const ICONS_DIR = 'resources/';
export const FileName = props => {
const { position, name, onTextClick, onIconClick, purple } = props;
const iconPath = ICONS_DIR + (purple ? 'js-file-purple.svg' : 'js-file.svg');
const iconSize = 15;
return (
<React.Fragment>
<image
x={position.x + 2}
y={position.y - 10}
onClick={onIconClick}
xlinkHref={iconPath}
height={iconSize}
width={iconSize}
className={'NodeIcon'}
/>
<text
x={position.x + 16}
y={position.y + 5}
onClick={onTextClick}
className={classNames('NodeText-file-name', {
'NodeText-file-name-purple': purple
})}
>
{name}
</text>
</React.Fragment>
);
};

View File

@@ -0,0 +1,54 @@
import React from 'react';
import classNames from 'classnames';
import './index.css';
const ICONS_DIR = 'resources/';
export const FolderName = props => {
const { position, name, disabled, closed, onClick } = props;
const iconPath = `${ICONS_DIR}${closed ? 'closed-' : ''}folder${disabled ? '-disabled' : ''}.svg`;
const iconSize = closed ? 14 : 15;
const iconPositionX = position.x + 3;
const iconPositionY = position.y + (closed ? -16 : -17);
return (
<React.Fragment>
{closed ? (
<polyline
points={[
iconPositionX - 1,
iconPositionY + 16,
iconPositionX + 16,
iconPositionY + 16,
iconPositionX + 16,
iconPositionY + 14
].join(', ')}
className={classNames('NodeIcon-folder-line', {
'NodeIcon-folder-line-disabled': disabled
})}
/>
) : null}
<image
x={iconPositionX}
y={iconPositionY}
onClick={onClick}
xlinkHref={iconPath}
height={iconSize}
width={iconSize}
className={'NodeIcon'}
/>
<text
x={position.x + 20}
y={position.y - 3}
className={classNames('NodeText-folder-name', {
'NodeText-folder-name-disabled': disabled
})}
>
{name}
</text>
</React.Fragment>
);
};

View File

@@ -0,0 +1,50 @@
.NodeIcon {
cursor: pointer;
}
.NodeIcon-folder-line {
fill: none;
stroke: #BFBFBF;
}
.NodeIcon-folder-line-disabled {
stroke: #ccc;
}
.NodeText-file-name {
fill: #595959;
font-family: 'Menlo';
cursor: pointer;
}
.NodeText-file-name-purple {
fill: #ff18a6;
}
.NodeText-folder-name {
fill: #595959;
font-family: 'Menlo';
}
.NodeText-folder-name-disabled {
fill: #A9A8A8;
}
.CodeCrumbName-rect {
fill: #fff;
stroke: #ff18a6;
}
.CodeCrumbName-loc {
fill: #595959;
font-family: 'Menlo';
font-size: 8px;
cursor: pointer;
}
.CodeCrumbName-text {
fill: #ff18a6;
font-family: 'Menlo';
font-size: 12px;
cursor: pointer;
}

View File

@@ -1,143 +0,0 @@
import React from 'react';
import { withSvgDraw } from '../utils/SvgDraw';
import {
drawDot,
drawSourceEdge,
drawFileText,
drawFileIcon,
drawFolderText,
drawFolderIcon
} from './drawHelpers';
import { FILE_NODE_TYPE, DIR_NODE_TYPE } from '../../../../../../shared/constants';
import { createSet } from '../utils/SvgSet';
class SourceTree extends React.Component {
componentDidMount() {
this.drawSet = createSet(this.props.primaryDraw);
this.drawTree();
}
componentDidUpdate() {
this.clearPrimaryDraw();
this.clearSecondaryDraw();
this.drawTree();
}
componentWillUnmount() {
this.clearPrimaryDraw();
}
clearPrimaryDraw() {
this.drawSet.clearAll();
}
clearSecondaryDraw() {
this.props.secondaryDraw.clear();
}
drawTree() {
const {
primaryDraw,
secondaryDraw,
layoutNodes,
closedFolders,
shiftToCenterPoint,
dependenciesDiagramOn,
codeCrumbsMinimize,
onFileSelect,
onFileIconClick,
onFolderClick
} = this.props;
const { add } = this.drawSet;
//note: instance from d3-flex tree, not Array
layoutNodes.each(node => {
const [nX, nY] = [node.y, node.x];
const parent = node.parent;
if (parent && parent.data.type === DIR_NODE_TYPE) {
const [pX, pY] = [parent.y, parent.x];
drawSourceEdge(secondaryDraw, shiftToCenterPoint, {
disabled: dependenciesDiagramOn,
target: {
x: nX,
y: nY
},
source: {
x: pX,
y: pY
},
singleChild: parent.children.length === 1
});
}
if (node.data.type === FILE_NODE_TYPE) {
drawDot(secondaryDraw, shiftToCenterPoint, {
x: nX,
y: nY,
disabled: dependenciesDiagramOn
});
add(
drawFileText(primaryDraw, shiftToCenterPoint, {
x: nX,
y: nY,
purple: node.children && codeCrumbsMinimize,
name: node.data.name,
onClick() {
onFileSelect(node.data);
}
})
);
add(
drawFileIcon(primaryDraw, shiftToCenterPoint, {
x: nX,
y: nY,
purple: node.children && codeCrumbsMinimize,
onClick() {
dependenciesDiagramOn && onFileIconClick(node.data);
}
})
);
return;
}
if (node.data.type === DIR_NODE_TYPE) {
drawDot(secondaryDraw, shiftToCenterPoint, {
x: nX,
y: nY,
disabled: dependenciesDiagramOn
});
add(
drawFolderText(primaryDraw, shiftToCenterPoint, {
x: nX,
y: nY,
name: node.data.name,
disabled: dependenciesDiagramOn
})
);
add(
drawFolderIcon(primaryDraw, shiftToCenterPoint, {
x: nX,
y: nY,
disabled: dependenciesDiagramOn,
closed: closedFolders[node.data.path],
onClick() {
onFolderClick(node.data);
}
})
);
}
});
}
render() {
return null;
}
}
export default withSvgDraw(SourceTree);

View File

@@ -1,140 +0,0 @@
import { getCurvedPath } from '../../../../utils/svgPrimitives';
import { BLUE_COLOR, PURPLE_COLOR, SYMBOL_WIDTH } from '../../store/constants';
//TODO: move numbers to config per function
//create object instead
const ICONS_DIR = 'resources/';
export const drawDot = (draw, shiftToCenterPoint, { x, y, disabled, highlighted }) => {
const radius = 5;
const halfRadius = radius / 2;
const circlePoint = shiftToCenterPoint(x - halfRadius, y - halfRadius);
let color = '#BFBFBF';
if (disabled) {
color = '#ccc';
}
if (highlighted) {
color = BLUE_COLOR;
}
return draw
.circle(radius)
.fill(color)
.move(circlePoint.x, circlePoint.y);
};
export const drawSourceEdge = (
draw,
shiftToCenterPoint,
{ target, source, disabled, singleChild }
) => {
const edgeTurnDistance = 20;
const START_PT = shiftToCenterPoint(source.x, source.y);
const P2 = shiftToCenterPoint(target.x - edgeTurnDistance, source.y);
const P3 = shiftToCenterPoint(target.x - edgeTurnDistance, target.y);
const END_PT = shiftToCenterPoint(target.x, target.y);
const points = singleChild
? [[START_PT.x, START_PT.y], [END_PT.x, END_PT.y]]
: [[START_PT.x, START_PT.y], [P2.x, P2.y], [P3.x, P3.y], [END_PT.x, END_PT.y]];
const polyline = draw.polyline(points);
const color = !disabled ? '#BFBFBF' : '#ccc';
polyline.fill('none').stroke({
color
});
return polyline;
};
export const drawFileText = (draw, shiftToCenterPoint, { x, y, purple, name = '', onClick }) => {
const text = draw.text(name);
text.font({ fill: purple ? PURPLE_COLOR : '#595959', family: 'Menlo' });
const fileTextPointShiftX = 16;
const fileTextPointShiftY = 8;
const fileTextPoint = shiftToCenterPoint(x + fileTextPointShiftX, y - fileTextPointShiftY);
text.move(fileTextPoint.x, fileTextPoint.y);
if (onClick) {
text.style({ cursor: 'pointer' }).on('click', onClick);
}
return text;
};
export const drawFileIcon = (draw, shiftToCenterPoint, { x, y, purple, onClick }) => {
const fileIconPath = ICONS_DIR + (purple ? 'js-file-purple.svg' : 'js-file.svg');
const fileIconSize = 15;
const fileIconPointShiftX = 2;
const fileIconPointShiftY = 10;
const fileIconPoint = shiftToCenterPoint(x + fileIconPointShiftX, y - fileIconPointShiftY);
const icon = draw
.image(fileIconPath, fileIconSize, fileIconSize)
.move(fileIconPoint.x, fileIconPoint.y);
if (onClick) {
icon.style({ cursor: 'pointer' }).on('click', onClick);
}
return icon;
};
export const drawFolderText = (draw, shiftToCenterPoint, { x, y, name = '', disabled }) => {
const folderTextPointShiftX = 20;
const folderTextPointShiftY = 16;
const folderTextPoint = shiftToCenterPoint(x + folderTextPointShiftX, y - folderTextPointShiftY);
const fill = !disabled ? '#595959' : '#A9A8A8';
const text = draw.text(name);
text.font({ fill, family: 'Menlo' });
text.move(folderTextPoint.x, folderTextPoint.y);
return text;
};
export const drawFolderIcon = (draw, shiftToCenterPoint, { x, y, disabled, closed, onClick }) => {
const folderIconPath = `${ICONS_DIR}${closed ? 'closed-' : ''}folder${
disabled ? '-disabled' : ''
}.svg`;
const folderIconSize = closed ? 14 : 15;
const folderIconPointShiftX = closed ? 3 : 3;
const folderIconPointShiftY = closed ? 16 : 17;
const folderIconPoint = shiftToCenterPoint(x + folderIconPointShiftX, y - folderIconPointShiftY);
let polyline = null;
if (closed) {
polyline = draw.polyline([
folderIconPoint.x - 1,
folderIconPoint.y + 16,
folderIconPoint.x + 16,
folderIconPoint.y + 16,
folderIconPoint.x + 16,
folderIconPoint.y + 14
]);
const color = !disabled ? '#BFBFBF' : '#ccc';
polyline.fill('none').stroke({
color
});
}
const icon = draw
.image(folderIconPath, folderIconSize, folderIconSize)
.move(folderIconPoint.x, folderIconPoint.y);
if (onClick) {
icon.style({ cursor: 'pointer' }).on('click', onClick);
}
if (!polyline) return icon;
return [icon, polyline];
};

View File

@@ -0,0 +1,75 @@
import React from 'react';
import { getFilesList } from '../../../../utils/treeLayout';
import { CodeCrumbName } from '../Node/CodeCrumb';
import { FileName } from '../Node/File';
import { PartEdge, CodeCrumbEdge } from '../Edge/CodeCrumbEdge';
class CodeCrumbsTree extends React.Component {
render() {
const {
filesTreeLayoutNodes,
shiftToCenterPoint,
sourceDiagramOn,
dependenciesDiagramOn,
codeCrumbsMinimize,
codeCrumbsDetails,
onCodeCrumbSelect
} = this.props;
const filesList = getFilesList(filesTreeLayoutNodes);
return (
<React.Fragment>
{filesList.map(node => {
const [nX, nY] = [node.y, node.x];
const position = shiftToCenterPoint(nX, nY);
if (!node.children) {
return null;
}
return (
<React.Fragment key={`code-crumb-${node.data.name}`}>
{!sourceDiagramOn && !dependenciesDiagramOn ? (
<FileName position={position} name={node.data.name} purple={codeCrumbsMinimize} />
) : null}
{(!codeCrumbsMinimize && (
<PartEdge sourcePosition={position} parentName={node.data.name} />
)) ||
null}
{!codeCrumbsMinimize &&
node.children.map((crumb, i, list) => {
const [cX, cY] = [crumb.y, crumb.x];
const crumbPosition = shiftToCenterPoint(cX, cY);
const singleCrumb = list.length === 1;
return (
<React.Fragment key={`code-crumb-edge-${i}`}>
{(!singleCrumb && (
<CodeCrumbEdge
sourcePosition={position}
targetPosition={crumbPosition}
parentName={node.data.name}
/>
)) ||
null}
<CodeCrumbName
position={crumbPosition}
loc={crumb.data.displayLoc}
name={crumb.data.name}
singleCrumb={singleCrumb}
onClick={() => onCodeCrumbSelect(node.data, crumb.data)}
/>
</React.Fragment>
);
})}
</React.Fragment>
);
})}
</React.Fragment>
);
}
}
export default CodeCrumbsTree;

View File

@@ -0,0 +1,103 @@
import React from 'react';
import { getFilesList } from '../../../../utils/treeLayout';
import { DependenciesEdge } from '../Edge/DepenenciesEdge';
import { FileName } from '../Node/File';
//move to utils
export const findNodeByPathName = (list = [], pathName) => {
return list.find(l => l.data.path === pathName);
};
export const getFilteredDependenciesList = ({
dependenciesList,
dependenciesEntryPoint,
dependenciesShowOneModule
}) => {
const entryPoint = dependenciesEntryPoint || {
path: dependenciesList[0].moduleName
};
if (dependenciesShowOneModule) {
return [dependenciesList.find(d => d.moduleName === entryPoint.path)];
}
return collectDependencies(entryPoint.path, dependenciesList);
};
export const collectDependencies = (entryModuleName, dependenciesList) => {
let queue = [].concat(entryModuleName),
store = [];
while (queue.length) {
let moduleName = queue.shift(),
entryModule = dependenciesList.find(d => d.moduleName === moduleName);
store.push(entryModule);
const nodeBody = entryModule.importedModuleNames;
if (nodeBody) {
queue = [...queue, ...nodeBody];
}
}
return store;
};
class DependenciesTree extends React.Component {
render() {
const { filesTreeLayoutNodes, shiftToCenterPoint, sourceDiagramOn } = this.props;
const moduleFilesList = getFilesList(filesTreeLayoutNodes);
const filteredDependenciesList = getFilteredDependenciesList(this.props);
return (
<React.Fragment>
{filteredDependenciesList.map(({ moduleName, importedModuleNames }, i) => {
const moduleNode = findNodeByPathName(moduleFilesList, moduleName);
if (!moduleNode) return;
const [mX, mY] = [moduleNode.y, moduleNode.x];
const targetPosition = shiftToCenterPoint(mX, mY);
let prevSourcePosition = null;
return (
<React.Fragment key={moduleName + i}>
{!sourceDiagramOn ? (
<FileName position={targetPosition} name={moduleNode.data.name} />
) : null}
{importedModuleNames.map((name, i) => {
const importedNode = findNodeByPathName(moduleFilesList, name);
if (!importedNode) return null;
const [iX, iY] = [importedNode.y, importedNode.x];
//TODO: implementation iterations:
//1) done: first with sharp angles + overlay
//2) done: without overlaying, not fot all cases
//3) rounded angles
const sourcePosition = shiftToCenterPoint(iX, iY);
const dependenciesEdge = (
<DependenciesEdge
key={name + i}
sourcePosition={sourcePosition}
targetPosition={targetPosition}
prevSourcePosition={prevSourcePosition}
/>
);
prevSourcePosition = sourcePosition;
return dependenciesEdge;
})}
</React.Fragment>
);
})}
</React.Fragment>
);
}
}
export default DependenciesTree;

View File

@@ -0,0 +1,106 @@
import React from 'react';
// TODO: add webpack resolve to not have these ../ .../ ../
import { FILE_NODE_TYPE, DIR_NODE_TYPE } from '../../../../../../shared/constants';
import { FileName } from '../Node/File';
import { FolderName } from '../Node/Folder';
import { Dot } from '../Dot/';
import { SourceEdge } from '../Edge/SourceEdge';
import DependenciesTree from './DependenciesTree';
import CodeCrumbsTree from './CodeCrumbsTree';
class SourceTree extends React.Component {
render() {
const {
sourceDiagramOn,
dependenciesDiagramOn,
codeCrumbsDiagramOn,
filesTreeLayoutNodes,
closedFolders,
shiftToCenterPoint,
codeCrumbsMinimize,
onFileSelect,
onFileIconClick,
onFolderClick,
dependenciesList
} = this.props;
const sourceEdges = [];
const sourceNodes = [];
const sourceDotes = [];
// TODO: add normal id generators for keys to not use i
let i = 0;
filesTreeLayoutNodes.each(node => {
i++;
const [nX, nY] = [node.y, node.x];
const position = shiftToCenterPoint(nX, nY);
const name = node.data.name;
const parent = node.parent;
if (parent && parent.data.type === DIR_NODE_TYPE) {
const [pX, pY] = [parent.y, parent.x];
const sourcePosition = shiftToCenterPoint(pX, pY);
sourceEdges.push(
<SourceEdge
key={`edge-${i}`}
targetPosition={position}
sourcePosition={sourcePosition}
disabled={dependenciesDiagramOn}
singleChild={parent.children.length === 1}
/>
);
}
if ([FILE_NODE_TYPE, DIR_NODE_TYPE].includes(node.data.type)) {
sourceDotes.push(
<Dot key={`dot-${i}`} position={position} disabled={dependenciesDiagramOn} />
);
}
let nodeBasedOnType = null;
if (node.data.type === FILE_NODE_TYPE) {
nodeBasedOnType = (
<FileName
position={position}
name={name}
purple={node.children && codeCrumbsMinimize}
onTextClick={() => onFileSelect(node.data)}
onIconClick={() => dependenciesDiagramOn && onFileIconClick(node.data)}
/>
);
} else if (node.data.type === DIR_NODE_TYPE) {
nodeBasedOnType = (
<FolderName
position={position}
name={name}
disabled={dependenciesDiagramOn}
closed={closedFolders[node.data.path]}
onClick={() => onFolderClick(node.data)}
/>
);
}
sourceNodes.push(<React.Fragment key={name + i}>{nodeBasedOnType}</React.Fragment>);
});
return (
<React.Fragment>
{(sourceDiagramOn && sourceEdges) || null}
{(sourceDiagramOn && sourceDotes) || null}
{dependenciesList && dependenciesDiagramOn && <DependenciesTree {...this.props} />}
{(sourceDiagramOn && sourceNodes) || null}
{(codeCrumbsDiagramOn && <CodeCrumbsTree {...this.props} />) || null}
</React.Fragment>
);
}
}
export default SourceTree;

View File

@@ -1,119 +1,33 @@
import React from 'react';
import SourceTree from './SourceTree/SourceTree';
import DependenciesTree from './DependenciesTree/DependenciesTree';
import CodeCrumbsTree from './CodeCrumbsTree/CodeCrumbsTree';
import SourceTree from './Tree/SourceTree';
import './TreeDiagram.css';
import { buildShiftToPoint } from '../../../utils/geometry';
export const BOX_SIZE = { W: 1000, H: 800 };
export const DOT = {
x: 50,
y: 500
};
class TreeDiagram extends React.Component {
constructor(props) {
super(props);
this.state = {};
}
componentDidMount() {
this.setState({ layersReady: true });
}
//cc: layers
renderLayers() {
return (
<div className="TreeDiagram-layers">
<div
data-name="sourceEdgesLayer"
className="TreeDiagram-layer"
ref={ref => (this.sourceEdgesLayer = ref)}
/>
<div
data-name="dependenciesEdgesLayer"
className="TreeDiagram-layer"
ref={ref => (this.dependenciesEdgesLayer = ref)}
/>
<div
data-name="iconsAndTextLayer"
className="TreeDiagram-layer"
ref={ref => (this.iconsAndTextLayer = ref)}
/>
</div>
);
}
renderDiagrams() {
const {
filesTreeLayoutNodes,
dependenciesList,
closedFolders,
dependenciesEntryPoint,
sourceDiagramOn,
dependenciesDiagramOn,
dependenciesShowOneModule,
codeCrumbsDiagramOn,
codeCrumbsMinimize,
codeCrumbsDetails,
onFileSelect,
onFileIconClick,
onFolderClick,
onCodeCrumbSelect
} = this.props;
const sharedProps = {
sourceDiagramOn,
dependenciesDiagramOn,
codeCrumbsDiagramOn,
codeCrumbsMinimize,
codeCrumbsDetails
};
return (
<React.Fragment>
{filesTreeLayoutNodes &&
sourceDiagramOn && (
<SourceTree
layoutNodes={filesTreeLayoutNodes}
closedFolders={closedFolders}
secondaryLayer={this.sourceEdgesLayer}
primaryLayer={this.iconsAndTextLayer}
onFileSelect={onFileSelect}
onFileIconClick={onFileIconClick}
onFolderClick={onFolderClick}
{...sharedProps}
/>
)}
{dependenciesList &&
filesTreeLayoutNodes &&
dependenciesDiagramOn && (
<DependenciesTree
dependenciesList={dependenciesList}
filesTreeLayoutNodes={filesTreeLayoutNodes}
dependenciesEntryPoint={dependenciesEntryPoint}
dependenciesShowOneModule={dependenciesShowOneModule}
primaryLayer={this.dependenciesEdgesLayer}
{...sharedProps}
/>
)}
{filesTreeLayoutNodes &&
codeCrumbsDiagramOn && (
<CodeCrumbsTree
filesTreeLayoutNodes={filesTreeLayoutNodes}
primaryLayer={this.iconsAndTextLayer}
onCodeCrumbSelect={onCodeCrumbSelect}
{...sharedProps}
/>
)}
</React.Fragment>
);
}
render() {
const { layersReady } = this.state;
const { width = BOX_SIZE.W, height = BOX_SIZE.H, dot = DOT } = this.props;
const shiftToCenterPoint = buildShiftToPoint(dot);
const { filesTreeLayoutNodes, ...otherProps } = this.props;
return (
<div className="TreeDiagram-container">
{this.renderLayers()}
{layersReady && this.renderDiagrams()}
<svg width={width} height={height} xmlns="http://www.w3.org/2000/svg">
{filesTreeLayoutNodes && (
<SourceTree
filesTreeLayoutNodes={filesTreeLayoutNodes}
shiftToCenterPoint={shiftToCenterPoint}
{...otherProps}
/>
)}
</svg>
</div>
);
}

View File

@@ -1,84 +0,0 @@
import React from 'react';
import SVG from 'svg.js';
import { buildShiftToPoint } from '../../../../utils/geometry';
const IconsAndTextLayer = 'iconsAndTextLayer';
const cachedSvgDraws = {};
const BOX_SIZE = { W: 1000, H: 800 };
const DOT = {
x: 50,
y: 500
};
const shiftToCenterPoint = buildShiftToPoint(DOT);
export const withSvgDraw = Component =>
class extends React.Component {
state = {};
createSvg(layer) {
const { width = BOX_SIZE.W, height = BOX_SIZE.H } = this.props;
return SVG(layer).size(width, height);
}
getPrimaryDraw() {
const { primaryLayer } = this.props;
const primaryLayerName = primaryLayer.dataset.name;
if (primaryLayerName !== IconsAndTextLayer) {
return this.createSvg(primaryLayer);
}
if (cachedSvgDraws[primaryLayerName]) {
return cachedSvgDraws[primaryLayerName];
}
cachedSvgDraws[primaryLayerName] = this.createSvg(primaryLayer);
return cachedSvgDraws[primaryLayerName];
}
componentDidMount() {
const { secondaryLayer } = this.props;
let subState = {
primaryDraw: this.getPrimaryDraw()
};
if (secondaryLayer) {
subState = {
...subState,
secondaryDraw: this.createSvg(secondaryLayer)
};
}
this.setState(subState);
}
componentWillUnmount() {
const { primaryLayer, secondaryLayer } = this.props;
if (primaryLayer.dataset.name !== IconsAndTextLayer) {
primaryLayer.removeChild(this.state.primaryDraw.node);
}
secondaryLayer && secondaryLayer.removeChild(this.state.secondaryDraw.node);
}
render() {
const { primaryDraw, secondaryDraw } = this.state;
return (
(primaryDraw && (
<Component
{...this.props}
primaryDraw={primaryDraw}
secondaryDraw={secondaryDraw}
shiftToCenterPoint={shiftToCenterPoint}
/>
)) ||
null
);
}
};

View File

@@ -1,16 +0,0 @@
export const createSet = draw => {
const drawSet = draw.set();
return {
add(list) {
drawSet.add.apply(drawSet, [].concat(list));
},
clearAll() {
drawSet.each(function() {
this.off();
this.remove();
});
drawSet.clear();
}
};
};

View File

@@ -59,8 +59,10 @@ const getCrumbs = fileCode => {
if (comment && isCodecrumb(comment)) {
const params = parseCodecrumbComment(comment);
const loc = node.loc.start;
crumbsList.push({
name: params.name || '', //TODO: check, can be bug with layout calc
displayLoc: `(${loc.line},${loc.column})`,
crumbedLineNode: node,
crumbNode: comment,
params

View File

@@ -1273,6 +1273,10 @@ classnames@2.x, classnames@^2.2.0, classnames@^2.2.1, classnames@^2.2.3, classna
version "2.2.5"
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.5.tgz#fb3801d453467649ef3603c7d61a02bd129bde6d"
classnames@^2.2.6:
version "2.2.6"
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.6.tgz#43935bffdd291f326dad0a205309b38d00f650ce"
cli-boxes@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-1.0.0.tgz#4fa917c3e59c94a004cd61f8ee509da651687143"
@@ -6014,10 +6018,6 @@ supports-color@^5.2.0, supports-color@^5.3.0, supports-color@^5.4.0:
dependencies:
has-flag "^3.0.0"
svg.js@^2.6.4:
version "2.6.4"
resolved "https://registry.yarnpkg.com/svg.js/-/svg.js-2.6.4.tgz#034085b13391c6fcca1a0185a34dbea6c3e78dc3"
svgo@^0.7.0:
version "0.7.2"
resolved "https://registry.yarnpkg.com/svgo/-/svgo-0.7.2.tgz#9f5772413952135c6fefbf40afe6a4faa88b4bb5"