Add basic typing to config objects

Also fixed the blips to be clickable
This commit is contained in:
Jarosław Marek
2021-04-29 21:48:44 +12:00
parent 13ba3120c3
commit 725b6f9924
12 changed files with 83 additions and 97 deletions

View File

@@ -1,5 +1,7 @@
import React from 'react'; import React from 'react';
import { quadrantsMap, ringsMap, chartConfig, blipFlags } from '../..//config'; import {FlagType, Ring} from '../../model';
import { quadrantsMap, chartConfig } from '../../config';
import Link from '../Link/Link';
import { NewBlip, ChangedBlip, DefaultBlip } from './BlipShapes'; import { NewBlip, ChangedBlip, DefaultBlip } from './BlipShapes';
/* /*
@@ -11,7 +13,7 @@ const generateCoordinates = (enrichedBlip, xScale, yScale) => {
const pi = Math.PI, const pi = Math.PI,
ringRadius = chartConfig.ringsAttributes[enrichedBlip.ringPosition - 1].radius, ringRadius = chartConfig.ringsAttributes[enrichedBlip.ringPosition - 1].radius,
previousRingRadius = enrichedBlip.ringPosition == 1 ? 0 : chartConfig.ringsAttributes[enrichedBlip.ringPosition - 2].radius, previousRingRadius = enrichedBlip.ringPosition == 1 ? 0 : chartConfig.ringsAttributes[enrichedBlip.ringPosition - 2].radius,
ringPadding = 0.6; ringPadding = 0.7;
// radian between 0 and 90 degrees // radian between 0 and 90 degrees
const randomDegree = ((Math.random() * 90) * pi) / 180; const randomDegree = ((Math.random() * 90) * pi) / 180;
@@ -39,7 +41,7 @@ const distanceBetween = (point1, point2) => {
return Math.sqrt((a * a) + (b * b)); return Math.sqrt((a * a) + (b * b));
}; };
export default function BlipPoints({blips, xScale, yScale, navigate}) { export default function BlipPoints({blips, xScale, yScale}) {
const enrichedBlips = blips.reduce((list, blip) => { const enrichedBlips = blips.reduce((list, blip) => {
if (!blip.ring || !blip.quadrant) { if (!blip.ring || !blip.quadrant) {
@@ -47,8 +49,8 @@ export default function BlipPoints({blips, xScale, yScale, navigate}) {
return list; return list;
} }
let enrichedBlip = { ...blip, let enrichedBlip = { ...blip,
ringPosition: ringsMap[blip.ring].position,
quadrantPosition: quadrantsMap[blip.quadrant].position, quadrantPosition: quadrantsMap[blip.quadrant].position,
ringPosition: Ring[blip.ring],
colour: quadrantsMap[blip.quadrant].colour, colour: quadrantsMap[blip.quadrant].colour,
txtColour: quadrantsMap[blip.quadrant].txtColour txtColour: quadrantsMap[blip.quadrant].txtColour
}; };
@@ -76,11 +78,6 @@ export default function BlipPoints({blips, xScale, yScale, navigate}) {
return list; return list;
}, []); }, []);
const handleClick = (pageName, event) => {
event.preventDefault();
navigate(pageName);
}
const renderBlip = (blip, index) => { const renderBlip = (blip, index) => {
const props = { const props = {
blip, blip,
@@ -89,13 +86,12 @@ export default function BlipPoints({blips, xScale, yScale, navigate}) {
'data-background-color': blip.colour, 'data-background-color': blip.colour,
'data-text-color': blip.txtColour, 'data-text-color': blip.txtColour,
'data-tip': blip.title, 'data-tip': blip.title,
onClick: handleClick.bind(this, `${blip.quadrant}/${blip.name}`),
key: index key: index
} }
switch (blip.flag) { switch (blip.flag) {
case blipFlags.new.name: case FlagType.new:
return <NewBlip {...props} />; return <NewBlip {...props} />;
case blipFlags.changed.name: case FlagType.changed:
return <ChangedBlip {...props} />; return <ChangedBlip {...props} />;
default: default:
return <DefaultBlip {...props} />; return <DefaultBlip {...props} />;
@@ -105,7 +101,9 @@ export default function BlipPoints({blips, xScale, yScale, navigate}) {
return ( return (
<g className="blips"> <g className="blips">
{enrichedBlips.map((blip, index) => ( {enrichedBlips.map((blip, index) => (
renderBlip(blip, index) <Link pageName={`${blip.quadrant}/${blip.name}`} key={index}>
{renderBlip(blip, index)}
</Link>
))} ))}
</g> </g>
); );

View File

@@ -1,7 +1,8 @@
import React from 'react'; import React from 'react';
import * as d3 from "d3"; import * as d3 from "d3";
import ReactTooltip from 'react-tooltip'; import ReactTooltip from 'react-tooltip';
import { chartConfig, quadrantsMap, ringsMap } from '../../config'; import { Ring } from '../../model';
import { chartConfig, quadrantsMap } from '../../config';
import { YAxis, XAxis } from './Axes'; import { YAxis, XAxis } from './Axes';
import QuadrantRings from './QuadrantRings'; import QuadrantRings from './QuadrantRings';
import BlipPoints from './BlipPoints'; import BlipPoints from './BlipPoints';
@@ -9,8 +10,8 @@ import BlipPoints from './BlipPoints';
import './chart.scss'; import './chart.scss';
const RingLabel = ({ring, xScale, yScale}) => { const RingLabel = ({ring, xScale, yScale}) => {
const ringRadius = chartConfig.ringsAttributes[ring.position - 1].radius, const ringRadius = chartConfig.ringsAttributes[ring - 1].radius,
previousRingRadius = ring.position == 1 ? 0 : chartConfig.ringsAttributes[ring.position - 2].radius, previousRingRadius = ring == 1 ? 0 : chartConfig.ringsAttributes[ring - 2].radius,
// middle point in between two ring arcs // middle point in between two ring arcs
distanceFromCentre = previousRingRadius + (ringRadius - previousRingRadius) / 2; distanceFromCentre = previousRingRadius + (ringRadius - previousRingRadius) / 2;
@@ -19,11 +20,11 @@ const RingLabel = ({ring, xScale, yScale}) => {
<g className="ring-label"> <g className="ring-label">
{/* Right hand-side label */} {/* Right hand-side label */}
<text x={xScale(distanceFromCentre)} y={yScale(0)} textAnchor="middle" dy=".35em"> <text x={xScale(distanceFromCentre)} y={yScale(0)} textAnchor="middle" dy=".35em">
{ring.displayName} {Ring[ring]}
</text> </text>
{/* Left hand-side label */} {/* Left hand-side label */}
<text x={xScale(-distanceFromCentre)} y={yScale(0)} textAnchor="middle" dy=".35em"> <text x={xScale(-distanceFromCentre)} y={yScale(0)} textAnchor="middle" dy=".35em">
{ring.displayName} {Ring[ring]}
</text> </text>
</g> </g>
); );
@@ -51,8 +52,8 @@ export default function RadarChart({ blips }) {
<QuadrantRings key={index} quadrant={quadrantsMap[id]} xScale={xScale} /> <QuadrantRings key={index} quadrant={quadrantsMap[id]} xScale={xScale} />
))} ))}
{Object.keys(ringsMap).map((id, index) => ( {[1, 2, 3, 4].map((ring, index) => (
<RingLabel key={index} ring={ringsMap[id]} xScale={xScale} yScale={yScale} /> <RingLabel key={index} ring={ring} xScale={xScale} yScale={yScale} />
))} ))}
<BlipPoints blips={blips} xScale={xScale} yScale={yScale}/> <BlipPoints blips={blips} xScale={xScale} yScale={yScale}/>

View File

@@ -14,3 +14,7 @@
padding: 5px 10px; padding: 5px 10px;
border-radius: 11px; border-radius: 11px;
} }
.chart .ring-label {
text-transform: uppercase;
}

View File

@@ -1,7 +1,6 @@
import React from 'react'; import React from 'react';
import './flag.scss'; import './flag.scss';
import {FlagType} from "../../model"; import {FlagType} from "../../model";
import {blipFlags} from "../../config";
interface ItemFlag { interface ItemFlag {
flag: FlagType; flag: FlagType;
@@ -10,7 +9,7 @@ interface ItemFlag {
export default function Flag({ item, short = false }: { item: ItemFlag; short?: boolean }) { export default function Flag({ item, short = false }: { item: ItemFlag; short?: boolean }) {
const ucFirst = (s: string) => s.charAt(0).toUpperCase() + s.slice(1); const ucFirst = (s: string) => s.charAt(0).toUpperCase() + s.slice(1);
if (item.flag !== blipFlags.default.name) { if (item.flag !== FlagType.default) {
let name = item.flag.toUpperCase(); let name = item.flag.toUpperCase();
let title = ucFirst(item.flag); let title = ucFirst(item.flag);
if (short === true) { if (short === true) {

View File

@@ -1,12 +1,12 @@
import React from 'react'; import React from 'react';
import { formatRelease } from '../../date'; import { formatRelease } from '../../date';
import { featuredOnly, Item } from '../../model'; import { featuredOnly, Item, HomepageOption } from '../../model';
import HeroHeadline from '../HeroHeadline/HeroHeadline'; import HeroHeadline from '../HeroHeadline/HeroHeadline';
import QuadrantGrid from '../QuadrantGrid/QuadrantGrid'; import QuadrantGrid from '../QuadrantGrid/QuadrantGrid';
import RadarGrid from '../RadarGrid/RadarGrid'; import RadarGrid from '../RadarGrid/RadarGrid';
import Fadeable from '../Fadeable/Fadeable'; import Fadeable from '../Fadeable/Fadeable';
import SetTitle from '../SetTitle'; import SetTitle from '../SetTitle';
import { radarName, radarNameShort } from '../../config'; import { radarName, radarNameShort, homepageContent } from '../../config';
import { MomentInput } from 'moment'; import { MomentInput } from 'moment';
type PageIndexProps = { type PageIndexProps = {
@@ -19,16 +19,20 @@ type PageIndexProps = {
export default function PageIndex({ leaving, onLeave, items, releases }: PageIndexProps) { export default function PageIndex({ leaving, onLeave, items, releases }: PageIndexProps) {
const newestRelease = releases.slice(-1)[0]; const newestRelease = releases.slice(-1)[0];
const numberOfReleases = releases.length; const numberOfReleases = releases.length;
const showChart = homepageContent === HomepageOption.chart || homepageContent === HomepageOption.both;
const showColumns = homepageContent === HomepageOption.columns || homepageContent === HomepageOption.both;
return ( return (
<Fadeable leaving={leaving} onLeave={onLeave}> <Fadeable leaving={leaving} onLeave={onLeave}>
<SetTitle title={radarNameShort} /> <SetTitle title={radarNameShort} />
<div className='headline-group'> <div className='headline-group'>
<HeroHeadline alt={`Version #${numberOfReleases}`}>{radarName}</HeroHeadline> <HeroHeadline alt={`Version #${numberOfReleases}`}>{radarName}</HeroHeadline>
</div> </div>
<RadarGrid blips={featuredOnly(items)} /> {showChart && (
{/* <RadarGrid blips={featuredOnly(items)} />
<QuadrantGrid items={featuredOnly(items)} /> )}
*/} {showColumns && (
<QuadrantGrid items={featuredOnly(items)} />
)}
<div className='publish-date'>Published {formatRelease(newestRelease)}</div> <div className='publish-date'>Published {formatRelease(newestRelease)}</div>
</Fadeable> </Fadeable>
); );

View File

@@ -7,8 +7,8 @@ import Search from '../Search/Search';
import Fadeable from '../Fadeable/Fadeable'; import Fadeable from '../Fadeable/Fadeable';
import SetTitle from '../SetTitle'; import SetTitle from '../SetTitle';
import Flag from '../Flag/Flag'; import Flag from '../Flag/Flag';
import { groupByFirstLetter, Item } from '../../model'; import { groupByFirstLetter, Item, Ring } from '../../model';
import { quadrantsMap, Ring } from '../../config'; import { quadrantsMap } from '../../config';
const containsSearchTerm = (text = '', term = '') => { const containsSearchTerm = (text = '', term = '') => {
// TODO search refinement // TODO search refinement
@@ -75,10 +75,10 @@ export default function PageOverview({ rings, search: searchProp, items, leaving
</div> </div>
<div className='split__right'> <div className='split__right'>
<div className='nav'> <div className='nav'>
{rings.map((ringName) => ( {Object.keys(rings).map((key) => (
<div className='nav__item' key={ringName}> <div className='nav__item' key={Ring[key]}>
<Badge big onClick={handleRingClick(ringName)} type={isRingActive(ringName) ? ringName : 'empty'}> <Badge big onClick={handleRingClick(Ring[key])} type={isRingActive(Ring[key]) ? Ring[key] : 'empty'}>
{ringName} {Ring[key]}
</Badge> </Badge>
</div> </div>
))} ))}

View File

@@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import { groupByQuadrants, Item, Group } from '../../model'; import { groupByQuadrants, Item, Group } from '../../model';
import { quadrants } from '../../config'; import { quadrantsMap } from '../../config';
import QuadrantSection from '../QuadrantSection/QuadrantSection'; import QuadrantSection from '../QuadrantSection/QuadrantSection';
import './quadrant-grid.scss'; import './quadrant-grid.scss';
const renderQuadrant = (quadrantName: string, groups: Group) => { const renderQuadrant = (quadrantName: string, groups: Group) => {
@@ -13,5 +13,5 @@ const renderQuadrant = (quadrantName: string, groups: Group) => {
export default function QuadrantGrid({ items }: { items: Item[] }) { export default function QuadrantGrid({ items }: { items: Item[] }) {
const groups = groupByQuadrants(items); const groups = groupByQuadrants(items);
return <div className='quadrant-grid'>{quadrants.map((quadrantName) => renderQuadrant(quadrantName, groups))}</div>; return <div className='quadrant-grid'>{Object.keys(quadrantsMap).map((quadrantName) => renderQuadrant(quadrantName, groups))}</div>;
} }

View File

@@ -1,11 +1,11 @@
import React from 'react'; import React from 'react';
import { rings, quadrantsMap, Ring, showEmptyRings } from '../../config'; import { quadrantsMap, showEmptyRings } from '../../config';
import Badge from '../Badge/Badge'; import Badge from '../Badge/Badge';
import Link from '../Link/Link'; import Link from '../Link/Link';
import IconLink from '../IconLink/IconLink'; import IconLink from '../IconLink/IconLink';
import ItemList from '../ItemList/ItemList'; import ItemList from '../ItemList/ItemList';
import Flag from '../Flag/Flag'; import Flag from '../Flag/Flag';
import { Group } from '../../model'; import { Group, Ring } from '../../model';
import './quadrant-section.scss'; import './quadrant-section.scss';
const renderList = (ringName: Ring, quadrantName: string, groups: Group, big: boolean) => { const renderList = (ringName: Ring, quadrantName: string, groups: Group, big: boolean) => {
const itemsInRing = groups[quadrantName][ringName] || []; const itemsInRing = groups[quadrantName][ringName] || [];
@@ -65,7 +65,7 @@ export default function QuadrantSection({ quadrantName, groups, big = false, sho
)} )}
</div> </div>
</div> </div>
<div className='quadrant-section__rings'>{rings.map((ringName) => renderRing(ringName, quadrantName, groups, big))}</div> <div className='quadrant-section__rings'>{Object.keys(Ring).map((key) => renderRing(Ring[key], quadrantName, groups, big))}</div>
</div> </div>
); );
} }

View File

@@ -5,8 +5,8 @@ import PageHelp from './PageHelp/PageHelp';
import PageQuadrant from './PageQuadrant/PageQuadrant'; import PageQuadrant from './PageQuadrant/PageQuadrant';
import PageItem from './PageItem/PageItem'; import PageItem from './PageItem/PageItem';
import PageItemMobile from './PageItemMobile/PageItemMobile'; import PageItemMobile from './PageItemMobile/PageItemMobile';
import {quadrantsMap, getItemPageNames, isMobileViewport, rings} from '../config'; import {quadrantsMap, getItemPageNames, isMobileViewport} from '../config';
import {Item} from '../model'; import {Item, Ring} from '../model';
type RouterProps = { type RouterProps = {
pageName: string pageName: string
@@ -75,7 +75,7 @@ export default function Router({pageName, items, releases, search}: RouterProps)
case page.index: case page.index:
return <PageIndex leaving={leaving} items={items} onLeave={handlePageLeave} releases={releases}/>; return <PageIndex leaving={leaving} items={items} onLeave={handlePageLeave} releases={releases}/>;
case page.overview: case page.overview:
return <PageOverview items={items} rings={rings} search={search} leaving={leaving} return <PageOverview items={items} rings={Object.values(Ring)} search={search} leaving={leaving}
onLeave={handlePageLeave}/>; onLeave={handlePageLeave}/>;
case page.help: case page.help:
return <PageHelp leaving={leaving} onLeave={handlePageLeave}/>; return <PageHelp leaving={leaving} onLeave={handlePageLeave}/>;

View File

@@ -1,8 +1,10 @@
import {Item} from './model'; import {Item, HomepageOption} from './model';
export const radarName = process.env.RADAR_NAME || 'AOE Technology Radar' export const radarName = process.env.RADAR_NAME || 'AOE Technology Radar'
export const radarNameShort = radarName; export const radarNameShort = radarName;
export const homepageContent = HomepageOption.both; // by defaul show both versions so that people can choose which one they like more (or keep both)
// Quadrants positions start from the top right and go clockwise // Quadrants positions start from the top right and go clockwise
export const quadrantsMap = { export const quadrantsMap = {
'methods-and-patterns': { 'methods-and-patterns': {
@@ -11,7 +13,7 @@ export const quadrantsMap = {
colour: '#248EA6', colour: '#248EA6',
txtColour: 'white', txtColour: 'white',
position: 1, position: 1,
description: 'Optional description goes here' description: 'Here we put information on methods and patterns concerning development, continuous x, testing, organization, architecture, etc.'
}, },
'platforms-and-aoe-services': { 'platforms-and-aoe-services': {
id: 'platforms-and-aoe-services', id: 'platforms-and-aoe-services',
@@ -19,7 +21,7 @@ export const quadrantsMap = {
colour: '#F25244', colour: '#F25244',
txtColour: '#444444', txtColour: '#444444',
position: 2, position: 2,
description: 'Optional description goes here' description: 'Here we include infrastructure platforms and services. We also use this category to communicate news about AOE services that we want all AOE teams to be aware of.'
}, },
'tools': { 'tools': {
id: 'tools', id: 'tools',
@@ -27,7 +29,7 @@ export const quadrantsMap = {
colour: '#F2A25C', colour: '#F2A25C',
txtColour: 'white', txtColour: 'white',
position: 3, position: 3,
description: 'Optional descrption goes here' description: 'Here we put different software tools - from small helpers to bigger software projects'
}, },
'languages-and-frameworks': { 'languages-and-frameworks': {
id: 'languages-and-frameworks', id: 'languages-and-frameworks',
@@ -35,7 +37,7 @@ export const quadrantsMap = {
colour: '#84BFA4', colour: '#84BFA4',
txtColour: '#444444', txtColour: '#444444',
position: 4, position: 4,
description: 'Optional description goes here' description: "We've placed development languages (such as Scala or Golang) here, as well as more low-level development frameworks (such as Play or Symfony), which are useful for implementing custom software of all kinds."
}, },
}; };
@@ -44,50 +46,13 @@ export const chartConfig = {
scale: [-16, 16], 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 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 ringsAttributes: [ // order from the centre outwards
{ radius: 8, arcWidth: 6 }, // radius values are based on the scale (not px!) { radius: 8, arcWidth: 6 }, // radius values are based on the scale (not px!), arc width is in px
{ radius: 11, arcWidth: 4 }, { radius: 11, arcWidth: 4 },
{ radius: 14, arcWidth: 2 }, { radius: 14, arcWidth: 2 },
{ radius: 16, arcWidth: 2 } { radius: 16, arcWidth: 2 }
] ]
}; };
export const rings = [
'all',
'adopt',
'trial',
'assess',
'hold'
] as const;
// rings positions start at the centre and go outwards
export const ringsMap = {
'adopt': {
displayName: 'ADOPT',
position: 1
},
'trial': {
displayName: 'EXPLORE',
position: 2
},
'assess': {
displayName: 'ENDURE',
position: 3
},
'hold': {
displayName: 'RETIRE',
position: 4
}
};
// TODO replace with TS enum
export const blipFlags = {
new: { name: 'new', short: 'N' },
changed: { name: 'changed', short: 'C' },
default: { name: 'default', short: '' }
}
export type Ring = typeof rings[number]
export const getItemPageNames = (items: Item[]) => items.map(item => `${item.quadrant}/${item.name}`); export const getItemPageNames = (items: Item[]) => items.map(item => `${item.quadrant}/${item.name}`);
export const showEmptyRings = false; export const showEmptyRings = false;

View File

@@ -1,4 +1,16 @@
import { Ring } from "./config" export enum HomepageOption {
chart,
columns,
both
}
export enum Ring {
all = 0,
adopt = 1,
trial = 2,
assess = 3,
hold = 4
}
export type ItemAttributes = { export type ItemAttributes = {
name: string name: string
@@ -8,7 +20,11 @@ export type ItemAttributes = {
featured: boolean featured: boolean
} }
export type FlagType = 'new' | 'changed' | 'default' export enum FlagType {
new = 'new',
changed = 'changed',
default = 'default'
}
export type Item = ItemAttributes & { export type Item = ItemAttributes & {
featured: boolean featured: boolean

View File

@@ -3,9 +3,9 @@ import path from 'path';
import frontmatter from 'front-matter'; import frontmatter from 'front-matter';
import marked from 'marked'; import marked from 'marked';
import hljs from 'highlight.js'; import hljs from 'highlight.js';
import { quadrantsMap, ringsMap, blipFlags } from '../src/config'; import { quadrantsMap } from '../src/config';
import { radarPath, getAllMarkdownFiles } from './file'; import { radarPath, getAllMarkdownFiles } from './file';
import { Item, Revision, ItemAttributes, Radar } from '../src/model'; import { Item, Revision, ItemAttributes, Radar, FlagType, Ring } from '../src/model';
type FMAttributes = ItemAttributes type FMAttributes = ItemAttributes
@@ -28,10 +28,9 @@ export const createRadar = async (): Promise<Radar> => {
const checkAttributes = (fileName: string, attributes: FMAttributes) => { const checkAttributes = (fileName: string, attributes: FMAttributes) => {
const validQuadrants = Object.keys(quadrantsMap); const validQuadrants = Object.keys(quadrantsMap);
const validRings = Object.keys(ringsMap);
if (attributes.ring && !validRings.includes(attributes.ring)) { if (attributes.ring && !Ring[attributes.ring]) {
throw new Error(`Error: ${fileName} has an illegal value for 'ring' - must be one of ${validRings}`); throw new Error(`Error: ${fileName} has an illegal value for 'ring' - must be one of ${Object.values(Ring)}`);
} }
if (attributes.quadrant && !validQuadrants.includes(attributes.quadrant)) { if (attributes.quadrant && !validQuadrants.includes(attributes.quadrant)) {
@@ -116,12 +115,12 @@ const ignoreEmptyRevisionBody = (revision: Revision, item: Item) => {
const addRevisionToItem = ( const addRevisionToItem = (
item: Item = { item: Item = {
flag: 'default', flag: FlagType.default,
featured: true, featured: true,
revisions: [], revisions: [],
name: '', name: '',
title: '', title: '',
ring: 'trial', ring: Ring.trial,
quadrant: '', quadrant: '',
body: '', body: '',
info: '', info: '',
@@ -168,10 +167,10 @@ const hasItemChanged = (item: Item, allReleases: string[]) =>
const getItemFlag = (item: Item, allReleases: string[]): string => { const getItemFlag = (item: Item, allReleases: string[]): string => {
if (isNewItem(item, allReleases)) { if (isNewItem(item, allReleases)) {
return blipFlags.new.name; return FlagType.new;
} }
if (hasItemChanged(item, allReleases)) { if (hasItemChanged(item, allReleases)) {
return blipFlags.changed.name; return FlagType.changed;
} }
return blipFlags.default.name; return FlagType.default;
}; };