Full Radar grid with chart

This commit is contained in:
Jarosław Marek
2021-04-28 23:25:52 +12:00
parent ad4c8475f5
commit 13ba3120c3
27 changed files with 493 additions and 369 deletions

View File

@@ -67,6 +67,7 @@
"react-dom": "^16.0.0",
"react-faux-dom": "^4.5.0",
"react-router-dom": "^5.2.0",
"react-tooltip": "^4.2.18",
"resolve": "1.15.0",
"resolve-url-loader": "3.1.2",
"sass-loader": "8.0.2",

View File

@@ -1,7 +1,8 @@
// TODO remove faux-dom and start using the React hook approach
import ReactFauxDOM from 'react-faux-dom';
import * as d3 from 'd3';
import * as d3 from "d3";
export const LeftAxis = ({ scale }) => {
export const YAxis = ({ scale }) => {
const el = ReactFauxDOM.createElement('g');
const axisGenerator = d3.axisLeft(scale).ticks(6);
@@ -14,7 +15,7 @@ export const LeftAxis = ({ scale }) => {
return el.toReact();
};
export const BottomAxis = ({ scale }) => {
export const XAxis = ({ scale }) => {
const el = ReactFauxDOM.createElement('g');
const axisGenerator = d3.axisBottom(scale).ticks(6);

View File

@@ -1,66 +1,112 @@
import ReactFauxDOM from 'react-faux-dom';
import * as d3 from "d3";
import { quadrantsMap, ringsMap } from '../../config';
import React from 'react';
import { quadrantsMap, ringsMap, chartConfig, blipFlags } from '../..//config';
import { NewBlip, ChangedBlip, DefaultBlip } from './BlipShapes';
/*
See https://medium.com/create-code/build-a-radar-diagram-with-d3-js-9db6458a9248
for a good explanation of formulas used to calculate various things in this component
*/
const generateCoordinates = (enrichedBlip, xScale, yScale) => {
const pi = Math.PI;
// radian between 5 and 85
const randomDegree = ((Math.random() * 80 + 5) * pi) / 180;
const radius = enrichedBlip.ringPosition - 0.2;
const r = Math.random() * 0.6 + (radius - 0.6);
// multiples of PI/2
const shift = pi * [1, 4, 3, 2][enrichedBlip.quadrantPosition - 1] / 2;
const pi = Math.PI,
ringRadius = chartConfig.ringsAttributes[enrichedBlip.ringPosition - 1].radius,
previousRingRadius = enrichedBlip.ringPosition == 1 ? 0 : chartConfig.ringsAttributes[enrichedBlip.ringPosition - 2].radius,
ringPadding = 0.6;
// radian between 0 and 90 degrees
const randomDegree = ((Math.random() * 90) * pi) / 180;
// random distance from the centre of the radar, but within given ring. Also, with some "padding" so the points don't touch ring borders.
const radius = randomBetween(previousRingRadius + ringPadding, ringRadius - ringPadding);
/*
Multiples of PI/2. To apply the calculated position to the specific quadrant.
Order here is counter-clockwise and starts at the top left, so we need to "invert" quadrant positions
*/
const shift = pi * [4, 3, 2, 1][enrichedBlip.quadrantPosition - 1] / 2;
return {
x: xScale(Math.cos(randomDegree + shift) * r),
y: yScale(Math.sin(randomDegree + shift) * r)
x: xScale(Math.cos(randomDegree + shift) * radius),
y: yScale(Math.sin(randomDegree + shift) * radius)
};
};
const distanceBetweenPoints = (point1, point2) => {
const randomBetween = (min, max) => {
return Math.random() * (max - min) + min;
};
const distanceBetween = (point1, point2) => {
const a = point2.x - point1.x;
const b = point2.y - point1.y;
return Math.sqrt((a * a) + (b * b));
};
export default function BlipPoints({blips, xScale, yScale}) {
export default function BlipPoints({blips, xScale, yScale, navigate}) {
const enrichedBlips = blips.reduce((list, blip) => {
if (!blip.ring || !blip.quadrant) {
// skip the blip if it doesn't have a ring or quadrant assigned
return list;
}
blip.ringPosition = ringsMap[blip.ring].position;
blip.quadrantPosition = quadrantsMap[blip.quadrant].position;
blip.colour = quadrantsMap[blip.quadrant].colour;
let enrichedBlip = { ...blip,
ringPosition: ringsMap[blip.ring].position,
quadrantPosition: quadrantsMap[blip.quadrant].position,
colour: quadrantsMap[blip.quadrant].colour,
txtColour: quadrantsMap[blip.quadrant].txtColour
};
let point;
let counter = 1;
do {
point = generateCoordinates(blip, xScale, yScale);
point = generateCoordinates(enrichedBlip, xScale, yScale);
counter++;
// generate position of the new blip until it has a satisfactory distance to every other blip
// this feels pretty inefficient, but good enough for now
} while (list.some(item => distanceBetweenPoints(point, item) < 8) || counter > 100);
/*
Generate position of the new blip until it has a satisfactory distance to every other blip (so that they don't touch each other)
and quadrant borders (so that they don't overlap quadrants)
This feels pretty inefficient, but good enough for now.
*/
} while (counter < 100
&& (Math.abs(point.x - xScale(0)) < 15
|| Math.abs(point.y - yScale(0)) < 15
|| list.some(item => distanceBetween(point, item) < chartConfig.blipSize + chartConfig.blipSize / 2)
));
blip.x = point.x;
blip.y = point.y;
enrichedBlip.x = point.x;
enrichedBlip.y = point.y;
list.push(blip);
list.push(enrichedBlip);
return list;
}, []);
const el = ReactFauxDOM.createElement('g');
d3.select(el)
.attr('class', 'circles')
.selectAll('circle')
.data(enrichedBlips)
.enter().append('circle')
.attr('fill', blip => blip.colour)
.attr('r', 3)
.attr('data-value', blip => blip.title)
.attr('cx', blip => blip.x)
.attr('cy', blip => blip.y)
return el.toReact();
const handleClick = (pageName, event) => {
event.preventDefault();
navigate(pageName);
}
const renderBlip = (blip, index) => {
const props = {
blip,
className: 'blip',
fill: blip.colour,
'data-background-color': blip.colour,
'data-text-color': blip.txtColour,
'data-tip': blip.title,
onClick: handleClick.bind(this, `${blip.quadrant}/${blip.name}`),
key: index
}
switch (blip.flag) {
case blipFlags.new.name:
return <NewBlip {...props} />;
case blipFlags.changed.name:
return <ChangedBlip {...props} />;
default:
return <DefaultBlip {...props} />;
}
}
return (
<g className="blips">
{enrichedBlips.map((blip, index) => (
renderBlip(blip, index)
))}
</g>
);
};

View File

@@ -0,0 +1,48 @@
import React from 'react';
import { chartConfig } from '../../config';
export const ChangedBlip = ({blip, ...props}) => {
const centeredX = blip.x - chartConfig.blipSize/2,
centeredY = blip.y - chartConfig.blipSize/2;
return (
<rect
transform={`rotate(-45 ${centeredX} ${centeredY})`}
x={centeredX}
y={centeredY}
width={chartConfig.blipSize}
height={chartConfig.blipSize}
rx="3"
{...props}
/>
);
};
export const NewBlip = ({blip, ...props}) => {
const centeredX = blip.x - chartConfig.blipSize/2,
centeredY = blip.y - chartConfig.blipSize/2;
/*
The below is a predefined path of a triangle with rounded corners.
I didn't find any more human friendly way of doing this as all examples I found have tons of lines of code
e.g. https://observablehq.com/@perlmonger42/interactive-rounded-corners-on-svg-polygons-using-d3-js
*/
return (
<path
transform={`translate(${centeredX}, ${centeredY})`}
d="M.247 10.212l5.02-8.697a2 2 0 013.465 0l5.021 8.697a2 2 0 01-1.732 3H1.98a2 2 0 01-1.732-3z"
{...props}
/>
);
};
export const DefaultBlip = ({blip, ...props}) => {
return (
<circle
r={chartConfig.blipSize / 2}
cx={blip.x}
cy={blip.y}
{...props}
/>
);
};

View File

@@ -2,45 +2,34 @@ import React from 'react';
import * as d3 from 'd3';
import { chartConfig } from '../../config';
const size = chartConfig.canvasSize / 2;
const arcPath = (quadrantPosition, ringPosition, xScale) => {
const startAngle = (quadrantPosition - 1) * Math.PI / 2,
endAngle = quadrantPosition * Math.PI / 2,
arcAttrs = chartConfig.ringsAttributes[ringPosition - 1],
ringRadiusPx = xScale(arcAttrs.radius) - xScale(0),
arc = d3.arc();
const arcPath = (quadrantPosition, ringPosition) => {
// order from the centre outwards
const arcAttributes = [
{radius: size / 4, width: 6},
{radius: size / 2, width: 4},
{radius: (size / 4 * 3), width: 2},
{radius: size, width: 2}
]
const startAngle = quadrantPosition == 1 ?
3 * Math.PI / 2
: (quadrantPosition - 2) * Math.PI / 2
const endAngle = quadrantPosition == 1 ?
4 * Math.PI / 2
: (quadrantPosition -1) * Math.PI / 2
const arcAttrs = arcAttributes[ringPosition - 1];
const arc = d3.arc();
return arc({
innerRadius: arcAttrs.radius + (arcAttrs.width / 2),
outerRadius: arcAttrs.radius - (arcAttrs.width / 2),
innerRadius: ringRadiusPx - arcAttrs.arcWidth,
outerRadius: ringRadiusPx,
startAngle,
endAngle
});
}
export default function QuadrantRings ({ quadrant }) {
// order from top left clockwise
export default function QuadrantRings ({ quadrant, xScale}) {
// order from top-right clockwise
const gradientAttributes = [
{x: 0, y: 0, cx: 1, cy: 1, r: 1},
{x: size, y: 0, cx: 0, cy: 1, r: 1},
{x: size, y: size, cx: 0, cy: 0, r: 1},
{x: 0, y: size, cx: 1, cy: 0, r: 1}
{x: xScale(0), y: 0, cx: 0, cy: 1, r: 1},
{x: xScale(0), y: xScale(0), cx: 0, cy: 0, r: 1},
{x: 0, y: xScale(0), cx: 1, cy: 0, r: 1},
{x: 0, y: 0, cx: 1, cy: 1, r: 1}
];
const gradientId = `${quadrant.position}-radial-gradient`;
const gradientId = `${quadrant.position}-radial-gradient`,
quadrantSize = chartConfig.size / 2;
return (
<g>
<g className="quadrant-ring">
{/* Definition of the quadrant gradient */}
<defs>
<radialGradient id={gradientId} {...gradientAttributes[quadrant.position - 1]}>
@@ -51,8 +40,8 @@ export default function QuadrantRings ({ quadrant }) {
{/* Gradient background area */}
<rect
width={size}
height={size}
width={quadrantSize}
height={quadrantSize}
x={gradientAttributes[quadrant.position - 1].x}
y={gradientAttributes[quadrant.position - 1].y}
fill={`url(#${gradientId})`}
@@ -64,8 +53,8 @@ export default function QuadrantRings ({ quadrant }) {
<path
key={index}
fill={quadrant.colour}
d={arcPath(quadrant.position, ringPosition)}
style={{transform: `translate(${size}px, ${size}px)`}}
d={arcPath(quadrant.position, ringPosition, xScale)}
style={{transform: `translate(${quadrantSize}px, ${quadrantSize}px)`}}
/>
))}

View File

@@ -1,23 +1,28 @@
import React from 'react';
import * as d3 from "d3";
import './chart.scss';
import { blipFlags, chartConfig, quadrantsMap, ringsMap } from '../../config';
import { LeftAxis, BottomAxis } from './Axes';
import ReactTooltip from 'react-tooltip';
import { chartConfig, quadrantsMap, ringsMap } from '../../config';
import { YAxis, XAxis } from './Axes';
import QuadrantRings from './QuadrantRings';
import BlipPoints from './BlipPoints';
const RingLabel = ({ring}) => {
const middlePoint = chartConfig.canvasSize / 2;
const shift = (ring.position - 1) * chartConfig.canvasSize / 8 + chartConfig.canvasSize / 16;
import './chart.scss';
const RingLabel = ({ring, xScale, yScale}) => {
const ringRadius = chartConfig.ringsAttributes[ring.position - 1].radius,
previousRingRadius = ring.position == 1 ? 0 : chartConfig.ringsAttributes[ring.position - 2].radius,
// middle point in between two ring arcs
distanceFromCentre = previousRingRadius + (ringRadius - previousRingRadius) / 2;
return (
<g>
<g className="ring-label">
{/* Right hand-side label */}
<text x={middlePoint + shift} y={middlePoint} textAnchor="middle" dy=".35em">
<text x={xScale(distanceFromCentre)} y={yScale(0)} textAnchor="middle" dy=".35em">
{ring.displayName}
</text>
{/* Left hand-side label */}
<text x={middlePoint - shift} y={middlePoint} textAnchor="middle" dy=".35em">
<text x={xScale(-distanceFromCentre)} y={yScale(0)} textAnchor="middle" dy=".35em">
{ring.displayName}
</text>
</g>
@@ -26,35 +31,33 @@ const RingLabel = ({ring}) => {
export default function RadarChart({ blips }) {
const xScale = d3.scaleLinear()
.domain([-4, 4])
.range([0, chartConfig.canvasSize]);
.domain(chartConfig.scale)
.range([0, chartConfig.size]);
const yScale = d3.scaleLinear()
.domain([-4, 4])
.range([chartConfig.canvasSize, 0]);
.domain(chartConfig.scale)
.range([chartConfig.size, 0]);
return (
<div className="chart">
<div className="chart" style={{maxWidth: `${chartConfig.size}px`}}>
<svg viewBox={`0 0 ${chartConfig.size} ${chartConfig.size}`}>
<g transform={`translate(${chartConfig.margin}, ${chartConfig.margin})`}>
<g transform={`translate(${xScale.range()[1] / 2}, 0)`}>
<LeftAxis scale={yScale}/>
<g transform={`translate(${xScale(0)}, 0)`}>
<YAxis scale={yScale}/>
</g>
<g transform={`translate(0, ${yScale.range()[0] / 2})`}>
<BottomAxis scale={xScale}/>
<g transform={`translate(0, ${yScale(0)})`}>
<XAxis scale={xScale}/>
</g>
{Object.keys(quadrantsMap).map((id, index) => (
<QuadrantRings key={index} quadrant={quadrantsMap[id]} />
<QuadrantRings key={index} quadrant={quadrantsMap[id]} xScale={xScale} />
))}
{Object.keys(ringsMap).map((id, index) => (
<RingLabel key={index} ring={ringsMap[id]} />
<RingLabel key={index} ring={ringsMap[id]} xScale={xScale} yScale={yScale} />
))}
<BlipPoints blips={blips} xScale={xScale} yScale={yScale}/>
</g>
</svg>
<ReactTooltip className="tooltip" offset={{top: -5}}/>
</div>
);
}

View File

@@ -2,4 +2,15 @@
fill: white;
font-size: 12px;
text-align: center;
position: relative;
margin: 0 auto;
}
.chart .blip:hover {
cursor: pointer;
}
.chart .tooltip {
padding: 5px 10px;
border-radius: 11px;
}

View File

@@ -1,7 +1,7 @@
import React, { useState, useRef } from 'react';
import classNames from 'classnames';
import Branding from '../Branding/Branding';
import Link from '../Link/Link';
import IconLink from '../IconLink/IconLink';
import LogoLink from '../LogoLink/LogoLink';
import Search from '../Search/Search';
import { radarNameShort } from '../../config';
@@ -50,14 +50,10 @@ export default function Header({ pageName }: { pageName: string }) {
<Branding logoContent={<LogoLink small={smallLogo} />}>
<div className='nav'>
<div className='nav__item'>
<Link pageName='help-and-about-tech-radar' className='icon-link'>
<span className='icon icon--question icon-link__icon'/>How to Use {radarNameShort}?
</Link>
<IconLink pageName="help-and-about-tech-radar" icon="question" text={`How to Use ${radarNameShort}?`} />
</div>
<div className='nav__item'>
<Link pageName='overview' className='icon-link'>
<span className='icon icon--overview icon-link__icon'/>Technologies Overview
</Link>
<IconLink pageName="overview" icon="overview" text="All Technologies" />
</div>
<div className='nav__item'>
<button className='icon-link' onClick={handleOpenClick}>

View File

@@ -0,0 +1,11 @@
import React from 'react';
import Link from '../Link/Link';
export default function IconLink({icon, pageName, text}) {
return (
<Link className="icon-link" pageName={pageName}>
<span className={`icon icon--${icon} icon-link__icon`} />
{text}
</Link>
);
}

View File

@@ -3,7 +3,7 @@ import { formatRelease } from '../../date';
import { featuredOnly, Item } from '../../model';
import HeroHeadline from '../HeroHeadline/HeroHeadline';
import QuadrantGrid from '../QuadrantGrid/QuadrantGrid';
import RadarChart from '../Chart/RadarChart';
import RadarGrid from '../RadarGrid/RadarGrid';
import Fadeable from '../Fadeable/Fadeable';
import SetTitle from '../SetTitle';
import { radarName, radarNameShort } from '../../config';
@@ -25,8 +25,10 @@ export default function PageIndex({ leaving, onLeave, items, releases }: PageInd
<div className='headline-group'>
<HeroHeadline alt={`Version #${numberOfReleases}`}>{radarName}</HeroHeadline>
</div>
<RadarChart blips={featuredOnly(items)} />
<RadarGrid blips={featuredOnly(items)} />
{/*
<QuadrantGrid items={featuredOnly(items)} />
*/}
<div className='publish-date'>Published {formatRelease(newestRelease)}</div>
</Fadeable>
);

View File

@@ -1,7 +1,7 @@
import React, {useEffect, useState} from 'react';
import Badge from '../Badge/Badge';
import ItemList from '../ItemList/ItemList';
import Link from '../Link/Link';
import IconLink from '../IconLink/IconLink';
import FooterEnd from '../FooterEnd/FooterEnd';
import SetTitle from '../SetTitle';
import ItemRevisions from '../ItemRevisions/ItemRevisions';
@@ -11,7 +11,7 @@ import {
createAnimationRunner
} from '../../animation';
import './item-page.scss';
import {translate} from '../../config';
import {quadrantsMap} from '../../config';
import {groupByQuadrants, Item} from '../../model';
const getItem = (pageName: string, items: Item[]) => {
@@ -191,7 +191,7 @@ export default function PageItem({pageName, items, leaving, onLeave}: PageItemPr
<div className='item-page__nav'>
<div className='item-page__nav__inner'>
<div className='item-page__header' style={getAnimationState('navHeader')}>
<h3 className='headline'>{translate(item.quadrant)}</h3>
<h3 className='headline'>{quadrantsMap[item.quadrant].displayName}</h3>
</div>
<ItemList items={itemsInRing} activeItem={item} headerStyle={getAnimationState('navHeader')}
@@ -203,10 +203,7 @@ export default function PageItem({pageName, items, leaving, onLeave}: PageItemPr
</Badge>
</div>
<div className='split__right'>
<Link className='icon-link' pageName={item.quadrant}>
<span className='icon icon--pie icon-link__icon'/>
Quadrant Overview
</Link>
<IconLink pageName={item.quadrant} icon="pie" text="Quadrant Overview" />
</div>
</div>
</ItemList>

View File

@@ -1,12 +1,12 @@
import React from 'react';
import Badge from '../Badge/Badge';
import ItemList from '../ItemList/ItemList';
import Link from '../Link/Link';
import IconLink from '../IconLink/IconLink';
import Fadeable from '../Fadeable/Fadeable';
import SetTitle from '../SetTitle';
import ItemRevisions from '../ItemRevisions/ItemRevisions';
import { translate } from '../../config';
import { quadrantsMap } from '../../config';
import { groupByQuadrants, Item } from '../../model';
type PageItemMobileProps = {
@@ -40,7 +40,7 @@ export default function PageItemMobile({ pageName, items, leaving, onLeave }: Pa
<div className='mobile-item-page__header'>
<div className='split'>
<div className='split__left'>
<h3 className='headline'>{translate(item.quadrant)}</h3>
<h3 className='headline'>{quadrantsMap[item.quadrant].displayName}</h3>
<h1 className='hero-headline hero-headline--inverse'>{item.title}</h1>
</div>
<div className='split__right'>
@@ -59,12 +59,10 @@ export default function PageItemMobile({ pageName, items, leaving, onLeave }: Pa
<ItemList items={itemsInRing} activeItem={item}>
<div className='split'>
<div className='split__left'>
<h3 className='headline'>{translate(item.quadrant)}</h3>
<h3 className='headline'>{quadrantsMap[item.quadrant].displayName}</h3>
</div>
<div className='split__right'>
<Link className='icon-link' pageName={item.quadrant}>
<span className='icon icon--pie icon-link__icon'></span>Zoom In
</Link>
<IconLink pageName={item.quadrant} icon="pie" text="Zoom in" />
</div>
</div>
</ItemList>

View File

@@ -8,7 +8,7 @@ import Fadeable from '../Fadeable/Fadeable';
import SetTitle from '../SetTitle';
import Flag from '../Flag/Flag';
import { groupByFirstLetter, Item } from '../../model';
import { translate, Ring } from '../../config';
import { quadrantsMap, Ring } from '../../config';
const containsSearchTerm = (text = '', term = '') => {
// TODO search refinement
@@ -109,7 +109,7 @@ export default function PageOverview({ rings, search: searchProp, items, leaving
</div>
<div className='split__right'>
<div className='nav nav--relations'>
<div className='nav__item'>{translate(item.quadrant)}</div>
<div className='nav__item'>{quadrantsMap[item.quadrant].displayName}</div>
<div className='nav__item'>
<Badge type={item.ring}>{item.ring}</Badge>
</div>

View File

@@ -5,7 +5,7 @@ import QuadrantSection from '../QuadrantSection/QuadrantSection';
import Fadeable from '../Fadeable/Fadeable';
import SetTitle from '../SetTitle';
import { translate } from '../../config';
import { quadrantsMap } from '../../config';
import { featuredOnly, groupByQuadrants, Item } from '../../model';
type PageQuadrantProps = {
@@ -19,11 +19,11 @@ export default function PageQuadrant({ leaving, onLeave, pageName, items }: Page
const groups = groupByQuadrants(featuredOnly(items));
return (
<Fadeable leaving={leaving} onLeave={onLeave}>
<SetTitle title={translate(pageName)} />
<SetTitle title={quadrantsMap[pageName].displayName} />
<HeadlineGroup>
<HeroHeadline>{translate(pageName)}</HeroHeadline>
<HeroHeadline>{quadrantsMap[pageName].displayName}</HeroHeadline>
</HeadlineGroup>
<QuadrantSection groups={groups} quadrantName={pageName} big />
<QuadrantSection groups={groups} quadrantName={pageName} big showTitle={false} />
</Fadeable>
);
}

View File

@@ -1,7 +1,8 @@
import React from 'react';
import { translate, rings, Ring, showEmptyRings } from '../../config';
import { rings, quadrantsMap, Ring, showEmptyRings } from '../../config';
import Badge from '../Badge/Badge';
import Link from '../Link/Link';
import IconLink from '../IconLink/IconLink';
import ItemList from '../ItemList/ItemList';
import Flag from '../Flag/Flag';
import { Group } from '../../model';
@@ -47,20 +48,19 @@ const renderRing = (ringName: Ring, quadrantName: string, groups: Group, big: bo
);
};
export default function QuadrantSection({ quadrantName, groups, big = false }: { quadrantName: string; groups: Group; big?: boolean }) {
export default function QuadrantSection({ quadrantName, groups, big = false, showTitle = true}: { quadrantName: string; groups: Group; big?: boolean; showTitle?: boolean }) {
return (
<div className='quadrant-section'>
<div className='quadrant-section__header'>
<div className='split'>
<div className='split__left'>
<h4 className='headline'>{translate(quadrantName)}</h4>
{showTitle && (
<div className="split__left">
<h4 className="headline">{quadrantsMap[quadrantName].displayName}</h4>
</div>
)}
{!big && (
<div className='split__right'>
<Link className='icon-link' pageName={`${quadrantName}`}>
<span className='icon icon--pie icon-link__icon' />
Zoom In
</Link>
<IconLink pageName={quadrantName} icon="pie" text="Zoom In" />
</div>
)}
</div>

View File

@@ -0,0 +1,63 @@
import React from 'react';
import RadarChart from '../Chart/RadarChart';
import IconLink from '../IconLink/IconLink';
import { quadrantsMap } from '../../config';
import './radar-grid.scss';
const QuadrantLabel = ({quadrant}) => {
const stylesMap = [
{top: 0, right: 0},
{bottom: 0, right: 0},
{bottom: 0, left: 0},
{top: 0, left: 0},
]
return (
<div className="quadrant-label" style={stylesMap[quadrant.position - 1]}>
<div className="split">
<div className="split__left">
<small>Quadrant {quadrant.position}</small>
</div>
<div className="split__right">
<IconLink icon="pie" pageName={`${quadrant.id}`} text="Zoom In" />
</div>
</div>
<hr style={{borderColor: quadrant.colour}}/>
<h4 className="headline">{quadrant.displayName}</h4>
<div className="description">{quadrant.description}</div>
</div>
);
};
const Legend = () => {
return (
<div className="radar-legend">
<div className="wrapper">
<span className="icon icon--blip_new"></span>
New in this version
</div>
<div className="wrapper">
<span className="icon icon--blip_changed"></span>
Recently changed
</div>
<div className="wrapper">
<span className="icon icon--blip_default"></span>
Unchanged
</div>
</div>
);
}
export default function RadarGrid({ blips }) {
return (
<div className="radar-grid">
<RadarChart blips={blips} />
{Object.keys(quadrantsMap).map((id, index) => (
<QuadrantLabel key={index} quadrant={quadrantsMap[id]} />
))}
<Legend />
</div>
);
}

View File

@@ -0,0 +1,52 @@
.radar-grid {
position: relative;
margin-bottom: 50px;
color: white;
}
.radar-grid .quadrant-label {
position: absolute;
width: 20%;
}
.quadrant-label .split {
font-size: 12 px;
text-transform: uppercase;
}
.quadrant-label hr {
width: 100%;
margin: 10px 0 10px 0;
}
.quadrant-label .description {
font-size: 14px;
color: #a6b1bb
}
.quadrant-label .icon-link {
font-size: 12px;
}
.quadrant-label .icon-link .icon {
background-size: 18px 18px;
width: 18px;
height: 18px;
}
.radar-grid .radar-legend {
position: absolute;
width: 15%;
right: 0;
top: 45%;
}
.radar-legend .wrapper {
margin-bottom: 10px;
}
.radar-legend .icon {
background-position: center;
margin-right: 5px;
}

View File

@@ -5,7 +5,7 @@ import PageHelp from './PageHelp/PageHelp';
import PageQuadrant from './PageQuadrant/PageQuadrant';
import PageItem from './PageItem/PageItem';
import PageItemMobile from './PageItemMobile/PageItemMobile';
import {quadrants, getItemPageNames, isMobileViewport, rings} from '../config';
import {quadrantsMap, getItemPageNames, isMobileViewport, rings} from '../config';
import {Item} from '../model';
type RouterProps = {
@@ -35,7 +35,7 @@ const getPageByName = (items: Item[], pageName: string): page => {
if (pageName === 'help-and-about-tech-radar') {
return page.help;
}
if (quadrants.includes(pageName)) {
if (Object.keys(quadrantsMap).includes(pageName)) {
return page.quadrant;
}
if (getItemPageNames(items).includes(pageName)) {

View File

@@ -3,43 +3,52 @@ import {Item} from './model';
export const radarName = process.env.RADAR_NAME || 'AOE Technology Radar'
export const radarNameShort = radarName;
export const quadrants = [
'languages-and-frameworks',
'methods-and-patterns',
'platforms-and-aoe-services',
'tools',
];
// Quadrants positions start from the top left and go clockwise
// Quadrants positions start from the top right and go clockwise
export const quadrantsMap = {
'languages-and-frameworks': {
displayName: 'Languages & Frameworks',
colour: '#84BFA4',
position: 1
},
'methods-and-patterns': {
id: 'methods-and-patterns',
displayName: 'Methods & Patterns',
colour: '#248EA6',
position: 2
txtColour: 'white',
position: 1,
description: 'Optional description goes here'
},
'platforms-and-aoe-services': {
id: 'platforms-and-aoe-services',
displayName: 'Platforms and Operations',
colour: '#F25244',
position: 3
txtColour: '#444444',
position: 2,
description: 'Optional description goes here'
},
'tools': {
id: 'tools',
displayName: 'Tools',
colour: '#F2A25C',
position: 4
txtColour: 'white',
position: 3,
description: 'Optional descrption goes here'
},
'languages-and-frameworks': {
id: 'languages-and-frameworks',
displayName: 'Languages & Frameworks',
colour: '#84BFA4',
txtColour: '#444444',
position: 4,
description: 'Optional description goes here'
},
};
const chartMargin = 20,
chartSize = 900;
export const chartConfig = {
margin: chartMargin,
size: chartSize,
canvasSize: chartSize - chartMargin * 2
size: 800, //in px
scale: [-16, 16],
blipSize: 12, // in px, be careful when increasing this value as it may cause a lot of calculations during placing the blips on the chart
ringsAttributes: [ // order from the centre outwards
{ radius: 8, arcWidth: 6 }, // radius values are based on the scale (not px!)
{ radius: 11, arcWidth: 4 },
{ radius: 14, arcWidth: 2 },
{ radius: 16, arcWidth: 2 }
]
};
export const rings = [
@@ -57,15 +66,15 @@ export const ringsMap = {
position: 1
},
'trial': {
displayName: 'TRIAL',
displayName: 'EXPLORE',
position: 2
},
'assess': {
displayName: 'ASSESS',
displayName: 'ENDURE',
position: 3
},
'hold': {
displayName: 'HOLD',
displayName: 'RETIRE',
position: 4
}
};
@@ -83,15 +92,6 @@ export const getItemPageNames = (items: Item[]) => items.map(item => `${item.qua
export const showEmptyRings = false;
const messages: { [k: string]: string } = {
'languages-and-frameworks': 'Languages & Frameworks',
'methods-and-patterns': 'Methods & Patterns',
'platforms-and-aoe-services': 'Platforms and Operations',
'tools': 'Tools',
};
export const translate = (key: string) => (messages[key] || '-');
export function isMobileViewport() {
// return false for server side rendering
if (typeof window == 'undefined') return false;

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
<rect transform="rotate(-45 8 8)" x="2" y="2" width="12" height="12" rx="3" fill="#a6b1bb"></rect>
</svg>

After

Width:  |  Height:  |  Size: 319 B

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
<circle cx="8" cy="8" r="6" fill="#a6b1bb"></circle>
</svg>

After

Width:  |  Height:  |  Size: 273 B

4
src/icons/blip_new.svg Normal file
View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg viewbox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
<path fill="#a6b1bb" d="M.247 10.212l5.02-8.697a2 2 0 013.465 0l5.021 8.697a2 2 0 01-1.732 3H1.98a2 2 0 01-1.732-3z"></path>
</svg>

After

Width:  |  Height:  |  Size: 345 B

View File

@@ -30,4 +30,25 @@
&--close {
background-image: url('../../icons/close.svg');
}
&--blip_new {
background-image: url('../../icons/blip_new.svg');
width: 18px;
height: 18px;
background-size: 18px;
}
&--blip_changed {
background-image: url('../../icons/blip_changed.svg');
width: 18px;
height: 18px;
background-size: 18px;
}
&--blip_default {
background-image: url('../../icons/blip_default.svg');
width: 18px;
height: 18px;
background-size: 18px;
}
}

View File

@@ -1,9 +1,8 @@
#!/usr/bin/env node
import {createRadar} from "./radar";
import {save} from "./file";
import {copyFileSync, mkdir, mkdirSync} from "fs";
import {quadrants} from "../src/config";
import {copyFileSync, mkdirSync} from "fs";
import {quadrantsMap} from "../src/config";
(async () => {
@@ -14,7 +13,7 @@ import {quadrants} from "../src/config";
copyFileSync('build/index.html', 'build/overview.html')
copyFileSync('build/index.html', 'build/help-and-about-tech-radar.html')
quadrants.forEach(quadrant => {
Object.keys(quadrantsMap).forEach(quadrant => {
copyFileSync('build/index.html', 'build/' + quadrant + '.html')
mkdirSync('build/' + quadrant)
})

View File

@@ -3,7 +3,7 @@ import path from 'path';
import frontmatter from 'front-matter';
import marked from 'marked';
import hljs from 'highlight.js';
import { quadrants, rings, blipFlags } from '../src/config';
import { quadrantsMap, ringsMap, blipFlags } from '../src/config';
import { radarPath, getAllMarkdownFiles } from './file';
import { Item, Revision, ItemAttributes, Radar } from '../src/model';
@@ -27,12 +27,15 @@ export const createRadar = async (): Promise<Radar> => {
};
const checkAttributes = (fileName: string, attributes: FMAttributes) => {
if (attributes.ring && !rings.includes(attributes.ring)) {
throw new Error(`Error: ${fileName} has an illegal value for 'ring' - must be one of ${rings}`);
const validQuadrants = Object.keys(quadrantsMap);
const validRings = Object.keys(ringsMap);
if (attributes.ring && !validRings.includes(attributes.ring)) {
throw new Error(`Error: ${fileName} has an illegal value for 'ring' - must be one of ${validRings}`);
}
if (attributes.quadrant && !quadrants.includes(attributes.quadrant)) {
throw new Error(`Error: ${fileName} has an illegal value for 'quadrant' - must be one of ${quadrants}`);
if (attributes.quadrant && !validQuadrants.includes(attributes.quadrant)) {
throw new Error(`Error: ${fileName} has an illegal value for 'quadrant' - must be one of ${validQuadrants}`);
}
if (!attributes.quadrant || attributes.quadrant === '') {

View File

@@ -2,9 +2,6 @@
import {createRadar} from "./radar";
import {save} from "./file";
import {copyFileSync, mkdir, mkdirSync} from "fs";
import {quadrants} from "../src/config";
export const radarJsonGenerator = (async () => {
try {

162
yarn.lock
View File

@@ -1004,11 +1004,6 @@
resolved "https://extranet.aoe.com/artifactory/api/npm/om3-npm/@csstools/normalize.css/-/normalize.css-10.1.0.tgz#f0950bba18819512d42f7197e56c518aa491cf18"
integrity sha1-8JULuhiBlRLUL3GX5WxRiqSRzxg=
"@discoveryjs/json-ext@^0.5.0":
version "0.5.2"
resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.2.tgz#8f03a22a04de437254e8ce8cc84ba39689288752"
integrity sha512-HyYEUDeIj5rRQU2Hk5HTB2uHsbRQpF70nvMhVzi+VJR0X+xNEhjPui4/kBf3VeH/wqD28PT4sVOm8qqLjBrSZg==
"@hapi/address@2.x.x":
version "2.1.4"
resolved "https://extranet.aoe.com/artifactory/api/npm/om3-npm/@hapi/address/-/address-2.1.4.tgz#5d67ed43f3fd41a69d4b9ff7b56e7c0d1d0a81e5"
@@ -1597,23 +1592,6 @@
"@webassemblyjs/wast-parser" "1.8.5"
"@xtuc/long" "4.2.2"
"@webpack-cli/configtest@^1.0.2":
version "1.0.2"
resolved "https://registry.yarnpkg.com/@webpack-cli/configtest/-/configtest-1.0.2.tgz#2a20812bfb3a2ebb0b27ee26a52eeb3e3f000836"
integrity sha512-3OBzV2fBGZ5TBfdW50cha1lHDVf9vlvRXnjpVbJBa20pSZQaSkMJZiwA8V2vD9ogyeXn8nU5s5A6mHyf5jhMzA==
"@webpack-cli/info@^1.2.3":
version "1.2.3"
resolved "https://registry.yarnpkg.com/@webpack-cli/info/-/info-1.2.3.tgz#ef819d10ace2976b6d134c7c823a3e79ee31a92c"
integrity sha512-lLek3/T7u40lTqzCGpC6CAbY6+vXhdhmwFRxZLMnRm6/sIF/7qMpT8MocXCRQfz0JAh63wpbXLMnsQ5162WS7Q==
dependencies:
envinfo "^7.7.3"
"@webpack-cli/serve@^1.3.1":
version "1.3.1"
resolved "https://registry.yarnpkg.com/@webpack-cli/serve/-/serve-1.3.1.tgz#911d1b3ff4a843304b9c3bacf67bb34672418441"
integrity sha512-0qXvpeYO6vaNoRBI52/UsbcaBydJCggoBBnIo/ovQQdn6fug0BgwsjorV1hVS7fMqGVTZGcVxv8334gjmbj5hw==
"@xtuc/ieee754@^1.2.0":
version "1.2.0"
resolved "https://extranet.aoe.com/artifactory/api/npm/om3-npm/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790"
@@ -1721,11 +1699,6 @@ ansi-colors@^3.0.0:
resolved "https://extranet.aoe.com/artifactory/api/npm/om3-npm/ansi-colors/-/ansi-colors-3.2.4.tgz#e3a3da4bfbae6c86a9c285625de124a234026fbf"
integrity sha1-46PaS/uubIapwoViXeEkojQCb78=
ansi-colors@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348"
integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==
ansi-escapes@^4.2.1:
version "4.3.1"
resolved "https://extranet.aoe.com/artifactory/api/npm/om3-npm/ansi-escapes/-/ansi-escapes-4.3.1.tgz#a5c47cc43181f1f38ffd7076837700d395522a61"
@@ -2824,11 +2797,6 @@ commander@^4.1.1:
resolved "https://extranet.aoe.com/artifactory/api/npm/om3-npm/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068"
integrity sha1-n9YCvZNilOnp70aj9NaWQESxgGg=
commander@^7.0.0:
version "7.2.0"
resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7"
integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==
common-tags@^1.8.0:
version "1.8.0"
resolved "https://extranet.aoe.com/artifactory/api/npm/om3-npm/common-tags/-/common-tags-1.8.0.tgz#8e3153e542d4a39e9b10554434afaaf98956a937"
@@ -3063,7 +3031,7 @@ cross-spawn@^6.0.0:
shebang-command "^1.2.0"
which "^1.2.9"
cross-spawn@^7.0.0, cross-spawn@^7.0.3:
cross-spawn@^7.0.0:
version "7.0.3"
resolved "https://extranet.aoe.com/artifactory/api/npm/om3-npm/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6"
integrity sha1-9zqFudXUHQRVUcF34ogtSshXKKY=
@@ -3950,13 +3918,6 @@ enhanced-resolve@^4.1.0:
memory-fs "^0.5.0"
tapable "^1.0.0"
enquirer@^2.3.6:
version "2.3.6"
resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.3.6.tgz#2a7fe5dd634a1e4125a975ec994ff5456dc3734d"
integrity sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==
dependencies:
ansi-colors "^4.1.1"
entities@^1.1.1:
version "1.1.2"
resolved "https://extranet.aoe.com/artifactory/api/npm/om3-npm/entities/-/entities-1.1.2.tgz#bdfa735299664dfafd34529ed4f8522a275fea56"
@@ -3967,11 +3928,6 @@ entities@^2.0.0:
resolved "https://extranet.aoe.com/artifactory/api/npm/om3-npm/entities/-/entities-2.1.0.tgz#992d3129cf7df6870b96c57858c249a120f8b8b5"
integrity sha1-mS0xKc999ocLlsV4WMJJoSD4uLU=
envinfo@^7.7.3:
version "7.8.1"
resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-7.8.1.tgz#06377e3e5f4d379fea7ac592d5ad8927e0c4d475"
integrity sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw==
errno@^0.1.3, errno@~0.1.7:
version "0.1.8"
resolved "https://extranet.aoe.com/artifactory/api/npm/om3-npm/errno/-/errno-0.1.8.tgz#8bb3e9c7d463be4976ff888f76b4809ebc2e811f"
@@ -4186,21 +4142,6 @@ execa@^4.0.0:
signal-exit "^3.0.2"
strip-final-newline "^2.0.0"
execa@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/execa/-/execa-5.0.0.tgz#4029b0007998a841fbd1032e5f4de86a3c1e3376"
integrity sha512-ov6w/2LCiuyO4RLYGdpFGjkcs0wMTgGE8PrkTHikeUy5iJekXyPIKUjifk5CsE0pt7sMCrMZ3YNqoCj6idQOnQ==
dependencies:
cross-spawn "^7.0.3"
get-stream "^6.0.0"
human-signals "^2.1.0"
is-stream "^2.0.0"
merge-stream "^2.0.0"
npm-run-path "^4.0.1"
onetime "^5.1.2"
signal-exit "^3.0.3"
strip-final-newline "^2.0.0"
exit@^0.1.2:
version "0.1.2"
resolved "https://extranet.aoe.com/artifactory/api/npm/om3-npm/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c"
@@ -4354,11 +4295,6 @@ fast-levenshtein@~2.0.6:
resolved "https://extranet.aoe.com/artifactory/api/npm/om3-npm/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917"
integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=
fastest-levenshtein@^1.0.12:
version "1.0.12"
resolved "https://registry.yarnpkg.com/fastest-levenshtein/-/fastest-levenshtein-1.0.12.tgz#9990f7d3a88cc5a9ffd1f1745745251700d497e2"
integrity sha512-On2N+BpYJ15xIC974QNVuYGMOlEVt4s0EOI3wwMqOmK1fdDY+FN/zltPV8vosq4ad4c/gJ1KHScUn/6AWIgiow==
faye-websocket@^0.11.3:
version "0.11.3"
resolved "https://extranet.aoe.com/artifactory/api/npm/om3-npm/faye-websocket/-/faye-websocket-0.11.3.tgz#5c0e9a8968e8912c286639fde977a8b209f2508e"
@@ -4708,11 +4644,6 @@ get-stream@^5.0.0:
dependencies:
pump "^3.0.0"
get-stream@^6.0.0:
version "6.0.1"
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7"
integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==
get-value@^2.0.3, get-value@^2.0.6:
version "2.0.6"
resolved "https://extranet.aoe.com/artifactory/api/npm/om3-npm/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28"
@@ -5136,11 +5067,6 @@ human-signals@^1.1.1:
resolved "https://extranet.aoe.com/artifactory/api/npm/om3-npm/human-signals/-/human-signals-1.1.1.tgz#c5b1cd14f50aeae09ab6c59fe63ba3395fe4dfa3"
integrity sha1-xbHNFPUK6uCatsWf5jujOV/k36M=
human-signals@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0"
integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==
iconv-lite@0.4, iconv-lite@0.4.24, iconv-lite@^0.4.24:
version "0.4.24"
resolved "https://extranet.aoe.com/artifactory/api/npm/om3-npm/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
@@ -5313,11 +5239,6 @@ internmap@^1.0.0:
resolved "https://registry.yarnpkg.com/internmap/-/internmap-1.0.1.tgz#0017cc8a3b99605f0302f2b198d272e015e5df95"
integrity sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==
interpret@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/interpret/-/interpret-2.2.0.tgz#1a78a0b5965c40a5416d007ad6f50ad27c417df9"
integrity sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==
invariant@^2.2.2:
version "2.2.4"
resolved "https://extranet.aoe.com/artifactory/api/npm/om3-npm/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6"
@@ -5431,13 +5352,6 @@ is-core-module@^2.1.0:
dependencies:
has "^1.0.3"
is-core-module@^2.2.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.3.0.tgz#d341652e3408bca69c4671b79a0954a3d349f887"
integrity sha512-xSphU2KG9867tsYdLD4RWQ1VqdFl4HTO9Thf3I/3dLEfr0dbPTWKsuCKrgqMljg4nPE+Gq0VCnzT3gr0CyBmsw==
dependencies:
has "^1.0.3"
is-data-descriptor@^0.1.4:
version "0.1.4"
resolved "https://extranet.aoe.com/artifactory/api/npm/om3-npm/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56"
@@ -7049,7 +6963,7 @@ npm-run-path@^2.0.0:
dependencies:
path-key "^2.0.0"
npm-run-path@^4.0.0, npm-run-path@^4.0.1:
npm-run-path@^4.0.0:
version "4.0.1"
resolved "https://extranet.aoe.com/artifactory/api/npm/om3-npm/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea"
integrity sha1-t+zR5e1T2o43pV4cImnguX7XSOo=
@@ -7202,7 +7116,7 @@ once@^1.3.0, once@^1.3.1, once@^1.4.0:
dependencies:
wrappy "1"
onetime@^5.1.0, onetime@^5.1.2:
onetime@^5.1.0:
version "5.1.2"
resolved "https://extranet.aoe.com/artifactory/api/npm/om3-npm/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e"
integrity sha1-0Oluu1awdHbfHdnEgG5SN5hcpF4=
@@ -8317,7 +8231,7 @@ prompts@^2.0.1:
kleur "^3.0.3"
sisteransi "^1.0.5"
prop-types@^15.6.2:
prop-types@^15.6.2, prop-types@^15.7.2:
version "15.7.2"
resolved "https://extranet.aoe.com/artifactory/api/npm/om3-npm/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5"
integrity sha1-UsQedbjIfnK52TYOAga5ncv/psU=
@@ -8582,6 +8496,14 @@ react-router@5.2.0:
tiny-invariant "^1.0.2"
tiny-warning "^1.0.0"
react-tooltip@^4.2.18:
version "4.2.18"
resolved "https://registry.yarnpkg.com/react-tooltip/-/react-tooltip-4.2.18.tgz#2fb8c5e115c4e5476f94081f4bb2ba77f5b2297f"
integrity sha512-MBdWuH925GL2ai5TWJelVJD9Opfk+3cLw0SP0rXR7s2RcNb7FefaNmljFndqYo8ghVcIEj5yM7aqV5Ith2bnqg==
dependencies:
prop-types "^15.7.2"
uuid "^7.0.3"
react@^16.13.1:
version "16.14.0"
resolved "https://extranet.aoe.com/artifactory/api/npm/om3-npm/react/-/react-16.14.0.tgz#94d776ddd0aaa37da3eda8fc5b6b18a4c9a3114d"
@@ -8665,13 +8587,6 @@ readdirp@~3.5.0:
dependencies:
picomatch "^2.2.1"
rechoir@^0.7.0:
version "0.7.0"
resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.7.0.tgz#32650fd52c21ab252aa5d65b19310441c7e03aca"
integrity sha512-ADsDEH2bvbjltXEP+hTIAmeFekTFK0V2BTxMkok6qILyAJEXV0AFfoWcAq4yfll5VdIMd/RVXq0lR+wQi5ZU3Q==
dependencies:
resolve "^1.9.0"
recursive-readdir@2.2.2:
version "2.2.2"
resolved "https://extranet.aoe.com/artifactory/api/npm/om3-npm/recursive-readdir/-/recursive-readdir-2.2.2.tgz#9946fb3274e1628de6e36b2f6714953b4845094f"
@@ -8926,14 +8841,6 @@ resolve@^1.10.0, resolve@^1.12.0, resolve@^1.18.1, resolve@^1.3.2, resolve@^1.8.
is-core-module "^2.1.0"
path-parse "^1.0.6"
resolve@^1.9.0:
version "1.20.0"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975"
integrity sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==
dependencies:
is-core-module "^2.2.0"
path-parse "^1.0.6"
restore-cursor@^3.1.0:
version "3.1.0"
resolved "https://extranet.aoe.com/artifactory/api/npm/om3-npm/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e"
@@ -9302,7 +9209,7 @@ shellwords@^0.1.1:
resolved "https://extranet.aoe.com/artifactory/api/npm/om3-npm/shellwords/-/shellwords-0.1.1.tgz#d6b9181c1a48d397324c84871efbcfc73fc0654b"
integrity sha1-1rkYHBpI05cyTISHHvvPxz/AZUs=
signal-exit@^3.0.0, signal-exit@^3.0.2, signal-exit@^3.0.3:
signal-exit@^3.0.0, signal-exit@^3.0.2:
version "3.0.3"
resolved "https://extranet.aoe.com/artifactory/api/npm/om3-npm/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c"
integrity sha1-oUEMLt2PB3sItOJTyOrPyvBXRhw=
@@ -10333,16 +10240,16 @@ uuid@^3.3.2, uuid@^3.4.0:
resolved "https://extranet.aoe.com/artifactory/api/npm/om3-npm/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee"
integrity sha1-sj5DWK+oogL+ehAK8fX4g/AgB+4=
uuid@^7.0.3:
version "7.0.3"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-7.0.3.tgz#c5c9f2c8cf25dc0a372c4df1441c41f5bd0c680b"
integrity sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg==
uuid@^8.3.0:
version "8.3.2"
resolved "https://extranet.aoe.com/artifactory/api/npm/om3-npm/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
integrity sha1-gNW1ztJxu5r2xEXyGhoExgbO++I=
v8-compile-cache@^2.2.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee"
integrity sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==
v8-to-istanbul@^7.0.0:
version "7.1.0"
resolved "https://extranet.aoe.com/artifactory/api/npm/om3-npm/v8-to-istanbul/-/v8-to-istanbul-7.1.0.tgz#5b95cef45c0f83217ec79f8fc7ee1c8b486aee07"
@@ -10452,26 +10359,6 @@ webidl-conversions@^6.1.0:
resolved "https://extranet.aoe.com/artifactory/api/npm/om3-npm/webidl-conversions/-/webidl-conversions-6.1.0.tgz#9111b4d7ea80acd40f5270d666621afa78b69514"
integrity sha1-kRG01+qArNQPUnDWZmIa+ni2lRQ=
webpack-cli@^4.6.0:
version "4.6.0"
resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-4.6.0.tgz#27ae86bfaec0cf393fcfd58abdc5a229ad32fd16"
integrity sha512-9YV+qTcGMjQFiY7Nb1kmnupvb1x40lfpj8pwdO/bom+sQiP4OBMKjHq29YQrlDWDPZO9r/qWaRRywKaRDKqBTA==
dependencies:
"@discoveryjs/json-ext" "^0.5.0"
"@webpack-cli/configtest" "^1.0.2"
"@webpack-cli/info" "^1.2.3"
"@webpack-cli/serve" "^1.3.1"
colorette "^1.2.1"
commander "^7.0.0"
enquirer "^2.3.6"
execa "^5.0.0"
fastest-levenshtein "^1.0.12"
import-local "^3.0.2"
interpret "^2.2.0"
rechoir "^0.7.0"
v8-compile-cache "^2.2.0"
webpack-merge "^5.7.3"
webpack-dev-middleware@^3.7.2:
version "3.7.3"
resolved "https://extranet.aoe.com/artifactory/api/npm/om3-npm/webpack-dev-middleware/-/webpack-dev-middleware-3.7.3.tgz#0639372b143262e2b84ab95d3b91a7597061c2c5"
@@ -10540,14 +10427,6 @@ webpack-manifest-plugin@2.2.0:
object.entries "^1.1.0"
tapable "^1.0.0"
webpack-merge@^5.7.3:
version "5.7.3"
resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-5.7.3.tgz#2a0754e1877a25a8bbab3d2475ca70a052708213"
integrity sha512-6/JUQv0ELQ1igjGDzHkXbVDRxkfA57Zw7PfiupdLFJYrgFqY5ZP8xxbpp2lU3EPwYx89ht5Z/aDkD40hFCm5AA==
dependencies:
clone-deep "^4.0.1"
wildcard "^2.0.0"
webpack-sources@^1.1.0, webpack-sources@^1.4.0, webpack-sources@^1.4.1, webpack-sources@^1.4.3:
version "1.4.3"
resolved "https://extranet.aoe.com/artifactory/api/npm/om3-npm/webpack-sources/-/webpack-sources-1.4.3.tgz#eedd8ec0b928fbf1cbfe994e22d2d890f330a933"
@@ -10646,11 +10525,6 @@ wide-align@^1.1.0:
dependencies:
string-width "^1.0.2 || 2"
wildcard@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/wildcard/-/wildcard-2.0.0.tgz#a77d20e5200c6faaac979e4b3aadc7b3dd7f8fec"
integrity sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw==
word-wrap@~1.2.3:
version "1.2.3"
resolved "https://extranet.aoe.com/artifactory/api/npm/om3-npm/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c"