refactor(tech-radar): style + code quality
This commit is contained in:
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { FC, useState, useRef } from 'react';
|
||||
import React, { useState, useRef } from 'react';
|
||||
import RadarPlot from '../RadarPlot';
|
||||
import { Ring, Quadrant, Entry } from '../../utils/types';
|
||||
import { adjustQuadrants, adjustRings, adjustEntries } from './utils';
|
||||
@@ -28,7 +28,7 @@ type Props = {
|
||||
svgProps?: object;
|
||||
};
|
||||
|
||||
const Radar: FC<Props> = props => {
|
||||
const Radar = (props: Props): JSX.Element => {
|
||||
const { width, height, quadrants, rings, entries } = props;
|
||||
const radius = Math.min(width, height) / 2;
|
||||
|
||||
@@ -38,7 +38,7 @@ const Radar: FC<Props> = props => {
|
||||
// TODO(dflemstr): most of this can be heavily memoized if performance becomes a problem
|
||||
adjustQuadrants(quadrants, radius, width, height);
|
||||
adjustRings(rings, radius);
|
||||
adjustEntries(entries, activeEntry, quadrants, rings, radius);
|
||||
adjustEntries(entries, quadrants, rings, radius, activeEntry);
|
||||
|
||||
return (
|
||||
<svg ref={node} width={width} height={height} {...props.svgProps}>
|
||||
|
||||
@@ -98,10 +98,10 @@ export const adjustQuadrants = (
|
||||
|
||||
export const adjustEntries = (
|
||||
entries: Entry[],
|
||||
activeEntry: Entry | null | undefined,
|
||||
quadrants: Quadrant[],
|
||||
rings: Ring[],
|
||||
radius: number,
|
||||
activeEntry?: Entry | null,
|
||||
) => {
|
||||
let seed = 42;
|
||||
entries.forEach((entry, idx) => {
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { FC, useRef, useLayoutEffect } from 'react';
|
||||
import React, { useRef, useLayoutEffect } from 'react';
|
||||
import { makeStyles, Theme } from '@material-ui/core';
|
||||
|
||||
type Props = {
|
||||
@@ -46,7 +46,7 @@ const useStyles = makeStyles<Theme>(() => ({
|
||||
},
|
||||
}));
|
||||
|
||||
const RadarBubble: FC<Props> = props => {
|
||||
const RadarBubble = (props: Props): JSX.Element => {
|
||||
const classes = useStyles(props);
|
||||
const { visible, text } = props;
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { useEffect, useState, FC } from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Progress, useApi, errorApiRef, ErrorApi } from '@backstage/core';
|
||||
import Radar from '../components/Radar';
|
||||
import { TechRadarComponentProps, TechRadarLoaderResponse } from '../api';
|
||||
@@ -22,15 +22,9 @@ import getSampleData from '../sampleData';
|
||||
|
||||
const useTechRadarLoader = (props: TechRadarComponentProps) => {
|
||||
const errorApi = useApi<ErrorApi>(errorApiRef);
|
||||
const [state, setState] = useState<{
|
||||
loading: boolean;
|
||||
error?: Error;
|
||||
data?: TechRadarLoaderResponse;
|
||||
}>({
|
||||
loading: true,
|
||||
error: undefined,
|
||||
data: undefined,
|
||||
});
|
||||
const [error, setError] = useState<Error>();
|
||||
const [loading, setLoading] = useState<boolean>(true);
|
||||
const [data, setData] = useState<TechRadarLoaderResponse>();
|
||||
|
||||
const { getData } = props;
|
||||
|
||||
@@ -41,22 +35,20 @@ const useTechRadarLoader = (props: TechRadarComponentProps) => {
|
||||
|
||||
getData()
|
||||
.then((payload: TechRadarLoaderResponse) => {
|
||||
setState({ loading: false, error: undefined, data: payload });
|
||||
setLoading(false);
|
||||
setData(payload);
|
||||
})
|
||||
.catch((err: Error) => {
|
||||
errorApi.post(err);
|
||||
setState({
|
||||
loading: false,
|
||||
error: err,
|
||||
data: undefined,
|
||||
});
|
||||
setLoading(false);
|
||||
setError(err);
|
||||
});
|
||||
}, [getData, errorApi]);
|
||||
|
||||
return state;
|
||||
return { data, loading, error };
|
||||
};
|
||||
|
||||
const RadarComponent: FC<TechRadarComponentProps> = props => {
|
||||
const RadarComponent = (props: TechRadarComponentProps): JSX.Element => {
|
||||
const { loading, error, data } = useTechRadarLoader(props);
|
||||
|
||||
return (
|
||||
|
||||
@@ -14,13 +14,14 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { FC } from 'react';
|
||||
import React from 'react';
|
||||
import { makeStyles, Theme } from '@material-ui/core';
|
||||
import { WithLink } from '../../utils/components';
|
||||
|
||||
type Props = {
|
||||
x: number;
|
||||
y: number;
|
||||
number: number;
|
||||
value: number;
|
||||
color: string;
|
||||
url?: string;
|
||||
moved?: number;
|
||||
@@ -43,14 +44,27 @@ const useStyles = makeStyles<Theme>(() => ({
|
||||
},
|
||||
}));
|
||||
|
||||
const RadarEntry: FC<Props> = props => {
|
||||
const makeBlip = (color: string, moved?: number) => {
|
||||
const style = { fill: color };
|
||||
|
||||
let blip = <circle r={9} style={style} />;
|
||||
if (moved && moved > 0) {
|
||||
blip = <path d="M -11,5 11,5 0,-13 z" style={style} />; // triangle pointing up
|
||||
} else if (moved && moved < 0) {
|
||||
blip = <path d="M -11,-5 11,-5 0,13 z" style={style} />; // triangle pointing down
|
||||
}
|
||||
|
||||
return blip;
|
||||
};
|
||||
|
||||
const RadarEntry = (props: Props): JSX.Element => {
|
||||
const classes = useStyles(props);
|
||||
|
||||
const {
|
||||
moved,
|
||||
color,
|
||||
url,
|
||||
number,
|
||||
value,
|
||||
x,
|
||||
y,
|
||||
onMouseEnter,
|
||||
@@ -58,24 +72,7 @@ const RadarEntry: FC<Props> = props => {
|
||||
onClick,
|
||||
} = props;
|
||||
|
||||
const style = { fill: color };
|
||||
|
||||
let blip;
|
||||
if (moved && moved > 0) {
|
||||
blip = <path d="M -11,5 11,5 0,-13 z" style={style} />; // triangle pointing up
|
||||
} else if (moved && moved < 0) {
|
||||
blip = <path d="M -11,-5 11,-5 0,13 z" style={style} />; // triangle pointing down
|
||||
} else {
|
||||
blip = <circle r={9} style={style} />;
|
||||
}
|
||||
|
||||
if (url) {
|
||||
blip = (
|
||||
<a href={url} className={classes.link}>
|
||||
{blip}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
const blip = makeBlip(color, moved);
|
||||
|
||||
return (
|
||||
<g
|
||||
@@ -84,9 +81,11 @@ const RadarEntry: FC<Props> = props => {
|
||||
onMouseLeave={onMouseLeave}
|
||||
onClick={onClick}
|
||||
>
|
||||
{blip}
|
||||
<WithLink url={url} className={classes.link}>
|
||||
{blip}
|
||||
</WithLink>
|
||||
<text y={3} className={classes.text}>
|
||||
{number}
|
||||
{value}
|
||||
</text>
|
||||
</g>
|
||||
);
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { FC } from 'react';
|
||||
import React from 'react';
|
||||
import { makeStyles, Theme } from '@material-ui/core';
|
||||
|
||||
type Props = {
|
||||
@@ -31,7 +31,7 @@ const useStyles = makeStyles<Theme>(() => ({
|
||||
},
|
||||
}));
|
||||
|
||||
const RadarFooter: FC<Props> = props => {
|
||||
const RadarFooter = (props: Props): JSX.Element => {
|
||||
const { x, y } = props;
|
||||
const classes = useStyles(props);
|
||||
|
||||
|
||||
@@ -13,9 +13,10 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import React, { FC } from 'react';
|
||||
import React from 'react';
|
||||
import { makeStyles, Theme } from '@material-ui/core';
|
||||
import { Quadrant, Ring, Entry } from '../../utils/types';
|
||||
import { WithLink } from '../../utils/components';
|
||||
|
||||
type Segments = {
|
||||
[k: number]: { [k: number]: Entry[] };
|
||||
@@ -29,20 +30,7 @@ type Props = {
|
||||
onEntryMouseLeave?: (entry: Entry) => void;
|
||||
};
|
||||
|
||||
const useStyles = makeStyles<Theme>(() => ({
|
||||
quadrant: {
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
overflow: 'hidden',
|
||||
pointerEvents: 'none',
|
||||
},
|
||||
quadrantHeading: {
|
||||
pointerEvents: 'none',
|
||||
userSelect: 'none',
|
||||
marginTop: 0,
|
||||
marginBottom: 'calc(18px * 0.375)',
|
||||
fontSize: '18px',
|
||||
},
|
||||
const ringStyles = {
|
||||
rings: {
|
||||
columns: 3,
|
||||
},
|
||||
@@ -69,6 +57,25 @@ const useStyles = makeStyles<Theme>(() => ({
|
||||
'-webkit-font-feature-settings': 'pnum',
|
||||
'font-feature-settings': 'pnum',
|
||||
},
|
||||
};
|
||||
|
||||
const quadrantStyles = {
|
||||
quadrant: {
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
overflow: 'hidden',
|
||||
pointerEvents: 'none',
|
||||
},
|
||||
quadrantHeading: {
|
||||
pointerEvents: 'none',
|
||||
userSelect: 'none',
|
||||
marginTop: 0,
|
||||
marginBottom: 'calc(18px * 0.375)',
|
||||
fontSize: '18px',
|
||||
},
|
||||
};
|
||||
|
||||
const entryStyle = {
|
||||
entry: {
|
||||
pointerEvents: 'none',
|
||||
userSelect: 'none',
|
||||
@@ -77,12 +84,18 @@ const useStyles = makeStyles<Theme>(() => ({
|
||||
entryLink: {
|
||||
pointerEvents: 'none',
|
||||
},
|
||||
};
|
||||
|
||||
const useStyles = makeStyles<Theme>(() => ({
|
||||
ringStyles,
|
||||
quadrantStyles,
|
||||
entryStyle,
|
||||
}));
|
||||
|
||||
const RadarLegend: FC<Props> = props => {
|
||||
const RadarLegend = (props: Props): JSX.Element => {
|
||||
const classes = useStyles(props);
|
||||
|
||||
const _getSegment = (
|
||||
const getSegment = (
|
||||
segmented: Segments,
|
||||
quadrant: Quadrant,
|
||||
ring: Ring,
|
||||
@@ -94,7 +107,7 @@ const RadarLegend: FC<Props> = props => {
|
||||
return ridx === undefined ? [] : segmentedData[ridx + ringOffset] || [];
|
||||
};
|
||||
|
||||
const _renderRing = (
|
||||
const renderRing = (
|
||||
ring: Ring,
|
||||
entries: Entry[],
|
||||
onEntryMouseEnter?: Props['onEntryMouseEnter'],
|
||||
@@ -107,39 +120,29 @@ const RadarLegend: FC<Props> = props => {
|
||||
<p>(empty)</p>
|
||||
) : (
|
||||
<ol className={classes.ringList}>
|
||||
{entries.map(entry => {
|
||||
let node = <span className={classes.entry}>{entry.title}</span>;
|
||||
|
||||
if (entry.url) {
|
||||
node = (
|
||||
<a className={classes.entryLink} href={entry.url}>
|
||||
{node}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<li
|
||||
key={entry.id}
|
||||
value={(entry.idx || 0) + 1}
|
||||
onMouseEnter={
|
||||
onEntryMouseEnter && (() => onEntryMouseEnter(entry))
|
||||
}
|
||||
onMouseLeave={
|
||||
onEntryMouseLeave && (() => onEntryMouseLeave(entry))
|
||||
}
|
||||
>
|
||||
{node}
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
{entries.map(entry => (
|
||||
<li
|
||||
key={entry.id}
|
||||
value={(entry.idx || 0) + 1}
|
||||
onMouseEnter={
|
||||
onEntryMouseEnter && (() => onEntryMouseEnter(entry))
|
||||
}
|
||||
onMouseLeave={
|
||||
onEntryMouseLeave && (() => onEntryMouseLeave(entry))
|
||||
}
|
||||
>
|
||||
<WithLink url={entry.url} className={classes.entryLink}>
|
||||
<span className={classes.entry}>{entry.title}</span>
|
||||
</WithLink>
|
||||
</li>
|
||||
))}
|
||||
</ol>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const _renderQuadrant = (
|
||||
const renderQuadrant = (
|
||||
segments: Segments,
|
||||
quadrant: Quadrant,
|
||||
rings: Ring[],
|
||||
@@ -158,9 +161,9 @@ const RadarLegend: FC<Props> = props => {
|
||||
<h2 className={classes.quadrantHeading}>{quadrant.name}</h2>
|
||||
<div className={classes.rings}>
|
||||
{rings.map(ring =>
|
||||
_renderRing(
|
||||
renderRing(
|
||||
ring,
|
||||
_getSegment(segments, quadrant, ring),
|
||||
getSegment(segments, quadrant, ring),
|
||||
onEntryMouseEnter,
|
||||
onEntryMouseLeave,
|
||||
),
|
||||
@@ -171,7 +174,7 @@ const RadarLegend: FC<Props> = props => {
|
||||
);
|
||||
};
|
||||
|
||||
const _setupSegments = (entries: Entry[]) => {
|
||||
const setupSegments = (entries: Entry[]) => {
|
||||
const segments: Segments = {};
|
||||
|
||||
for (const entry of entries) {
|
||||
@@ -209,12 +212,12 @@ const RadarLegend: FC<Props> = props => {
|
||||
onEntryMouseLeave,
|
||||
} = props;
|
||||
|
||||
const segments: Segments = _setupSegments(entries);
|
||||
const segments: Segments = setupSegments(entries);
|
||||
|
||||
return (
|
||||
<g>
|
||||
{quadrants.map(quadrant =>
|
||||
_renderQuadrant(
|
||||
renderQuadrant(
|
||||
segments,
|
||||
quadrant,
|
||||
rings,
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { FC } from 'react';
|
||||
import React from 'react';
|
||||
import { Grid } from '@material-ui/core';
|
||||
import {
|
||||
Content,
|
||||
@@ -29,7 +29,7 @@ import {
|
||||
import RadarComponent from '../components/RadarComponent';
|
||||
import { techRadarApiRef, TechRadarApi } from '../api';
|
||||
|
||||
const RadarPage: FC<{}> = () => {
|
||||
const RadarPage = (): JSX.Element => {
|
||||
const techRadarApi = useApi<TechRadarApi>(techRadarApiRef);
|
||||
|
||||
return (
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { FC } from 'react';
|
||||
import React from 'react';
|
||||
import { Quadrant, Ring, Entry } from '../../utils/types';
|
||||
|
||||
import RadarGrid from '../RadarGrid';
|
||||
@@ -36,7 +36,7 @@ type Props = {
|
||||
};
|
||||
|
||||
// A component that draws the radar circle.
|
||||
const RadarPlot: FC<Props> = props => {
|
||||
const RadarPlot = (props: Props): JSX.Element => {
|
||||
const {
|
||||
width,
|
||||
height,
|
||||
@@ -71,7 +71,7 @@ const RadarPlot: FC<Props> = props => {
|
||||
x={entry.x || 0}
|
||||
y={entry.y || 0}
|
||||
color={entry.color || ''}
|
||||
number={((entry && entry.idx) || 0) + 1}
|
||||
value={((entry && entry.idx) || 0) + 1}
|
||||
url={entry.url}
|
||||
moved={entry.moved}
|
||||
onMouseEnter={onEntryMouseEnter && (() => onEntryMouseEnter(entry))}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
|
||||
type WithLinkProps = {
|
||||
url?: string;
|
||||
className: string;
|
||||
children: React.ReactNode;
|
||||
};
|
||||
|
||||
export const WithLink = ({
|
||||
url,
|
||||
className,
|
||||
children,
|
||||
}: WithLinkProps): JSX.Element =>
|
||||
url ? (
|
||||
<a href={url} className={className}>
|
||||
{children}
|
||||
</a>
|
||||
) : (
|
||||
<>{children}</>
|
||||
);
|
||||
Reference in New Issue
Block a user