feat: define rings and quadrants via config file

closes #96
This commit is contained in:
Danny Koppenhagen
2021-10-03 20:18:22 +02:00
committed by Bastian
parent 8d28e4c3a3
commit 70ea8d5bcd
21 changed files with 1087 additions and 948 deletions

View File

@@ -110,6 +110,22 @@ To change the logo, create a public folder in your application and put your `log
For reference have a look at [public/logo.svg](./public/logo.svg).
### Change the rings and quadrants config
To change the default rings and quadrants of the radar, you can place a custom `config.json` file within the `public` folder.
The content should look as follows:
```json
{
"quadrants": {
"languages-and-frameworks": "Languages & Frameworks",
"methods-and-patterns": "Methods & Patterns",
"platforms-and-aoe-services": "Platforms & Operations",
"tools": "Tools"
},
"rings":["all", "adopt", "trial", "assess", "hold"]
}
```
### Change the index.html
To change the index.html, create a public folder in your application and put your `index.html` in it.

9
config_example.json Normal file
View File

@@ -0,0 +1,9 @@
{
"quadrants": {
"languages-and-frameworks": "Languages & Frameworks",
"methods-and-patterns": "Methods & Patterns",
"platforms-and-aoe-services": "Platforms & Operations",
"tools": "Tools"
},
"rings":["all", "adopt", "trial", "assess", "hold"]
}

View File

@@ -39,7 +39,6 @@ var __generator = (this && this.__generator) || function (thisArg, body) {
Object.defineProperty(exports, "__esModule", { value: true });
var fs_1 = require("fs");
var radar_1 = require("./generateJson/radar");
var config_1 = require("../src/config");
// Do this as the first thing so that any code reading it knows the right env.
process.env.BABEL_ENV = "production";
process.env.NODE_ENV = "production";
@@ -50,7 +49,7 @@ process.on("unhandledRejection", function (err) {
throw err;
});
(function () { return __awaiter(void 0, void 0, void 0, function () {
var radar, e_1;
var radar, rawConf, config, e_1;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
@@ -61,7 +60,9 @@ process.on("unhandledRejection", function (err) {
radar = _a.sent();
fs_1.copyFileSync("build/index.html", "build/overview.html");
fs_1.copyFileSync("build/index.html", "build/help-and-about-tech-radar.html");
config_1.quadrants.forEach(function (quadrant) {
rawConf = fs_1.readFileSync("build/config.json", "utf-8");
config = JSON.parse(rawConf);
Object.keys(config.quadrants).forEach(function (quadrant) {
var destFolder = "build/" + quadrant;
fs_1.copyFileSync("build/index.html", destFolder + ".html");
if (!fs_1.existsSync(destFolder)) {

View File

@@ -76,13 +76,14 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
Object.defineProperty(exports, "__esModule", { value: true });
exports.createRadar = void 0;
var fs_extra_1 = require("fs-extra");
var fs_1 = require("fs");
var path = __importStar(require("path"));
var front_matter_1 = __importDefault(require("front-matter"));
// @ts-ignore esModuleInterop is activated in tsconfig.scripts.json, but IDE typescript uses default typescript config
var marked_1 = __importDefault(require("marked"));
var highlight_js_1 = __importDefault(require("highlight.js"));
var file_1 = require("./file");
var config_1 = require("../../src/config");
var paths_1 = require("../paths");
marked_1.default.setOptions({
highlight: function (code) { return highlight_js_1.default.highlightAuto(code).value; },
});
@@ -108,11 +109,14 @@ var createRadar = function () { return __awaiter(void 0, void 0, void 0, functio
}); };
exports.createRadar = createRadar;
var checkAttributes = function (fileName, attributes) {
if (attributes.ring && !config_1.rings.includes(attributes.ring)) {
throw new Error("Error: " + fileName + " has an illegal value for 'ring' - must be one of " + config_1.rings);
var rawConf = fs_1.readFileSync(path.resolve(paths_1.appBuild, 'config.json'), 'utf-8');
var config = JSON.parse(rawConf);
if (attributes.ring && !config.rings.includes(attributes.ring)) {
throw new Error("Error: " + fileName + " has an illegal value for 'ring' - must be one of " + config.rings);
}
if (attributes.quadrant && !config_1.quadrants.includes(attributes.quadrant)) {
throw new Error("Error: " + fileName + " has an illegal value for 'quadrant' - must be one of " + config_1.quadrants);
var quadrants = Object.keys(config.quadrants);
if (attributes.quadrant && !quadrants.includes(attributes.quadrant)) {
throw new Error("Error: " + fileName + " has an illegal value for 'quadrant' - must be one of " + quadrants);
}
return attributes;
};
@@ -120,19 +124,21 @@ var createRevisionsFromFiles = function (fileNames) {
var publicUrl = process.env.PUBLIC_URL;
return Promise.all(fileNames.map(function (fileName) {
return new Promise(function (resolve, reject) {
fs_extra_1.readFile(fileName, "utf8", function (err, data) {
fs_extra_1.readFile(fileName, "utf8", function (err, data) { return __awaiter(void 0, void 0, void 0, function () {
var fm, html;
return __generator(this, function (_a) {
if (err) {
reject(err);
}
else {
var fm = front_matter_1.default(data);
// add target attribute to external links
// todo: check path
var html = marked_1.default(fm.body.replace(/\]\(\//g, "](" + publicUrl + "/"));
fm = front_matter_1.default(data);
html = marked_1.default(fm.body.replace(/\]\(\//g, "](" + publicUrl + "/"));
html = html.replace(/a href="http/g, 'a target="_blank" rel="noopener noreferrer" href="http');
resolve(__assign(__assign(__assign({}, itemInfoFromFilename(fileName)), checkAttributes(fileName, fm.attributes)), { fileName: fileName, body: html }));
}
return [2 /*return*/];
});
}); });
});
}));
};

9
public/config.json Normal file
View File

@@ -0,0 +1,9 @@
{
"quadrants": {
"languages-and-frameworks": "Languages & Frameworks",
"methods-and-patterns": "Methods & Patterns",
"platforms-and-aoe-services": "Platforms & Operations",
"tools": "Tools"
},
"rings":["all", "adopt", "trial", "assess", "hold"]
}

View File

@@ -1,8 +1,7 @@
#!/usr/bin/env node
import { copyFileSync, mkdirSync, existsSync } from "fs";
import { copyFileSync, mkdirSync, existsSync, readFileSync } from "fs";
import { createRadar } from "./generateJson/radar";
import { quadrants } from "../src/config";
// Do this as the first thing so that any code reading it knows the right env.
process.env.BABEL_ENV = "production";
@@ -22,8 +21,9 @@ process.on("unhandledRejection", (err) => {
copyFileSync("build/index.html", "build/overview.html");
copyFileSync("build/index.html", "build/help-and-about-tech-radar.html");
quadrants.forEach((quadrant) => {
const rawConf = readFileSync("build/config.json", "utf-8");
const config = JSON.parse(rawConf);
Object.keys(config.quadrants).forEach((quadrant) => {
const destFolder = `build/${quadrant}`;
copyFileSync("build/index.html", `${destFolder}.html`);
if (!existsSync(destFolder)) {

View File

@@ -1,4 +1,5 @@
import { readFile } from "fs-extra";
import { readFileSync } from "fs";
import * as path from "path";
import frontMatter from "front-matter";
// @ts-ignore esModuleInterop is activated in tsconfig.scripts.json, but IDE typescript uses default typescript config
@@ -6,8 +7,8 @@ import marked from "marked";
import highlight from "highlight.js";
import { radarPath, getAllMarkdownFiles } from "./file";
import { quadrants, rings } from "../../src/config";
import { Item, Revision, ItemAttributes, Radar } from "../../src/model";
import { appBuild } from "../paths";
type FMAttributes = ItemAttributes;
@@ -29,12 +30,16 @@ export const createRadar = async (): Promise<Radar> => {
};
const checkAttributes = (fileName: string, attributes: FMAttributes) => {
if (attributes.ring && !rings.includes(attributes.ring)) {
const rawConf = readFileSync(path.resolve(appBuild, 'config.json'), 'utf-8');
const config = JSON.parse(rawConf);
if (attributes.ring && !config.rings.includes(attributes.ring)) {
throw new Error(
`Error: ${fileName} has an illegal value for 'ring' - must be one of ${rings}`
`Error: ${fileName} has an illegal value for 'ring' - must be one of ${config.rings}`
);
}
const quadrants = Object.keys(config.quadrants);
if (attributes.quadrant && !quadrants.includes(attributes.quadrant)) {
throw new Error(
`Error: ${fileName} has an illegal value for 'quadrant' - must be one of ${quadrants}`
@@ -50,7 +55,7 @@ const createRevisionsFromFiles = (fileNames: string[]) => {
fileNames.map(
(fileName) =>
new Promise<Revision>((resolve, reject) => {
readFile(fileName, "utf8", (err, data) => {
readFile(fileName, "utf8", async (err, data) => {
if (err) {
reject(err);
} else {

View File

@@ -13,6 +13,7 @@ import {
} from "react-router-dom";
import { Item } from "../model";
import { Messages, MessagesProvider } from "../context/MessagesContext";
import { ConfigData } from "../config";
interface Params {
page: string;
@@ -40,9 +41,11 @@ const useQuery = () => new URLSearchParams(useLocation().search);
const RouterWithPageParam = ({
items,
releases,
config,
}: {
items: Item[];
releases: string[];
config: ConfigData;
}) => {
const { page } = useParams<Params>();
const query = useQuery();
@@ -53,6 +56,7 @@ const RouterWithPageParam = ({
search={query.get("search") || ""}
items={items}
releases={releases}
config={config}
/>
);
};
@@ -79,8 +83,11 @@ export default function App() {
const messages = useFetch<Messages>(
`${process.env.PUBLIC_URL}/messages.json`
);
const config = useFetch<ConfigData>(
`${process.env.PUBLIC_URL}/config.json`
);
if (data) {
if (data && config) {
const { items, releases } = data;
return (
<MessagesProvider messages={messages}>
@@ -93,7 +100,7 @@ export default function App() {
<HeaderWithPageParam />
</div>
<div className={classNames("page__content")}>
<RouterWithPageParam items={items} releases={releases} />
<RouterWithPageParam config={config} items={items} releases={releases} />
</div>
<div className="page__footer">
<FooterWithPageParam items={items} />

View File

@@ -1,11 +1,10 @@
import React, { MouseEventHandler } from "react";
import classNames from "classnames";
import "./badge.scss";
import { Ring } from "../../config";
type BadgeProps = {
onClick?: MouseEventHandler;
big?: boolean;
type: "big" | "all" | "empty" | Ring;
type: "big" | "all" | "empty" | string;
};
export default function Badge({

View File

@@ -1,11 +1,10 @@
import React from "react";
import { formatRelease } from "../../date";
import { featuredOnly, Item } from "../../model";
import HeroHeadline from "../HeroHeadline/HeroHeadline";
import QuadrantGrid from "../QuadrantGrid/QuadrantGrid";
import Fadeable from "../Fadeable/Fadeable";
import SetTitle from "../SetTitle";
import { radarName, radarNameShort } from "../../config";
import { ConfigData, radarName, radarNameShort } from "../../config";
import { MomentInput } from "moment";
import { useMessages } from "../../context/MessagesContext";
@@ -13,6 +12,7 @@ type PageIndexProps = {
leaving: boolean;
onLeave: () => void;
items: Item[];
config: ConfigData;
releases: MomentInput[];
};
@@ -20,6 +20,7 @@ export default function PageIndex({
leaving,
onLeave,
items,
config,
releases,
}: PageIndexProps) {
const { pageIndex } = useMessages();
@@ -35,7 +36,7 @@ export default function PageIndex({
{radarName}
</HeroHeadline>
</div>
<QuadrantGrid items={featuredOnly(items)} />
<QuadrantGrid items={featuredOnly(items)} config={config} />
<div className="publish-date">
{publishedLabel} {formatRelease(newestRelease)}
</div>

View File

@@ -7,7 +7,7 @@ import SetTitle from "../SetTitle";
import ItemRevisions from "../ItemRevisions/ItemRevisions";
import { useAnimations } from "./useAnimations";
import "./item-page.scss";
import { translate } from "../../config";
import { ConfigData, translate } from "../../config";
import {
groupByQuadrants,
Item,
@@ -29,11 +29,12 @@ const getItemsInRing = (pageName: string, items: Item[]) => {
type Props = {
pageName: string;
items: Item[];
config: ConfigData;
leaving: boolean;
onLeave: () => void;
};
const PageItem: React.FC<Props> = ({ pageName, items, leaving, onLeave }) => {
const PageItem: React.FC<Props> = ({ pageName, items, config, leaving, onLeave }) => {
const { pageItem } = useMessages();
const quadrantOverview = pageItem?.quadrantOverview || 'Quadrant Overview';
@@ -57,7 +58,7 @@ const PageItem: React.FC<Props> = ({ pageName, items, leaving, onLeave }) => {
className="item-page__header"
style={getAnimationState("navHeader")}
>
<h3 className="headline">{translate(item.quadrant)}</h3>
<h3 className="headline">{translate(config, item.quadrant)}</h3>
</div>
<ItemList
items={itemsInRing}

View File

@@ -6,12 +6,13 @@ import Fadeable from "../Fadeable/Fadeable";
import SetTitle from "../SetTitle";
import ItemRevisions from "../ItemRevisions/ItemRevisions";
import { translate } from "../../config";
import { ConfigData, translate } from "../../config";
import { groupByQuadrants, Item } from "../../model";
type PageItemMobileProps = {
pageName: string;
items: Item[];
config: ConfigData;
leaving: boolean;
onLeave: () => void;
};
@@ -19,6 +20,7 @@ type PageItemMobileProps = {
export default function PageItemMobile({
pageName,
items,
config,
leaving,
onLeave,
}: PageItemMobileProps) {
@@ -47,7 +49,7 @@ export default function PageItemMobile({
<div className="mobile-item-page__header">
<div className="split">
<div className="split__left">
<h3 className="headline">{translate(item.quadrant)}</h3>
<h3 className="headline">{translate(config, item.quadrant)}</h3>
<h1 className="hero-headline hero-headline--inverse">
{item.title}
</h1>
@@ -73,7 +75,7 @@ export default function PageItemMobile({
<ItemList items={itemsInRing} activeItem={item}>
<div className="split">
<div className="split__left">
<h3 className="headline">{translate(item.quadrant)}</h3>
<h3 className="headline">{translate(config, item.quadrant)}</h3>
</div>
<div className="split__right">
<Link className="icon-link" pageName={item.quadrant}>

View File

@@ -8,8 +8,8 @@ import Fadeable from "../Fadeable/Fadeable";
import SetTitle from "../SetTitle";
import Flag from "../Flag/Flag";
import { groupByFirstLetter, Item } from "../../model";
import { ConfigData, translate } from "../../config";
import { useMessages } from "../../context/MessagesContext";
import { translate, Ring } from "../../config";
const containsSearchTerm = (text = "", term = "") => {
// TODO search refinement
@@ -20,9 +20,10 @@ const containsSearchTerm = (text = "", term = "") => {
};
type PageOverviewProps = {
rings: readonly ("all" | Ring)[];
rings: readonly ("all" | string)[];
search: string;
items: Item[];
config: ConfigData;
leaving: boolean;
onLeave: () => void;
};
@@ -31,10 +32,11 @@ export default function PageOverview({
rings,
search: searchProp,
items,
config,
leaving,
onLeave,
}: PageOverviewProps) {
const [ring, setRing] = useState<Ring | "all">("all");
const [ring, setRing] = useState<string | "all">("all");
const [search, setSearch] = useState(searchProp);
const { pageOverview } = useMessages();
const title = pageOverview?.title || 'Technologies Overview';
@@ -46,7 +48,7 @@ export default function PageOverview({
setSearch(searchProp);
}, [rings, searchProp]);
const handleRingClick = (ring: Ring) => () => {
const handleRingClick = (ring: string) => () => {
setRing(ring);
};
@@ -134,7 +136,7 @@ export default function PageOverview({
<div className="split__right">
<div className="nav nav--relations">
<div className="nav__item">
{translate(item.quadrant)}
{translate(config, item.quadrant)}
</div>
<div className="nav__item">
<Badge type={item.ring}>{item.ring}</Badge>

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 { ConfigData, translate } from "../../config";
import { featuredOnly, groupByQuadrants, Item } from "../../model";
type PageQuadrantProps = {
@@ -13,6 +13,7 @@ type PageQuadrantProps = {
onLeave: () => void;
pageName: string;
items: Item[];
config: ConfigData;
};
export default function PageQuadrant({
@@ -20,15 +21,16 @@ export default function PageQuadrant({
onLeave,
pageName,
items,
config,
}: PageQuadrantProps) {
const groups = groupByQuadrants(featuredOnly(items));
return (
<Fadeable leaving={leaving} onLeave={onLeave}>
<SetTitle title={translate(pageName)} />
<SetTitle title={translate(config, pageName)} />
<HeadlineGroup>
<HeroHeadline>{translate(pageName)}</HeroHeadline>
<HeroHeadline>{translate(config, pageName)}</HeroHeadline>
</HeadlineGroup>
<QuadrantSection groups={groups} quadrantName={pageName} big />
<QuadrantSection groups={groups} quadrantName={pageName} config={config} big />
</Fadeable>
);
}

View File

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

View File

@@ -1,5 +1,4 @@
import React from "react";
import { translate, rings, Ring, showEmptyRings } from "../../config";
import { translate, showEmptyRings, ConfigData } from "../../config";
import Badge from "../Badge/Badge";
import Link from "../Link/Link";
import ItemList from "../ItemList/ItemList";
@@ -7,7 +6,7 @@ import Flag from "../Flag/Flag";
import { Group } from "../../model";
import "./quadrant-section.scss";
const renderList = (
ringName: Ring,
ringName: string,
quadrantName: string,
groups: Group,
big: boolean
@@ -42,7 +41,7 @@ const renderList = (
};
const renderRing = (
ringName: Ring,
ringName: string,
quadrantName: string,
groups: Group,
big: boolean
@@ -65,10 +64,12 @@ const renderRing = (
export default function QuadrantSection({
quadrantName,
groups,
config,
big = false,
}: {
quadrantName: string;
groups: Group;
config: ConfigData;
big?: boolean;
}) {
return (
@@ -76,7 +77,7 @@ export default function QuadrantSection({
<div className="quadrant-section__header">
<div className="split">
<div className="split__left">
<h4 className="headline">{translate(quadrantName)}</h4>
<h4 className="headline">{translate(config, quadrantName)}</h4>
</div>
{!big && (
<div className="split__right">
@@ -89,7 +90,7 @@ export default function QuadrantSection({
</div>
</div>
<div className="quadrant-section__rings">
{rings.map((ringName) =>
{config.rings.map((ringName: string) =>
renderRing(ringName, quadrantName, groups, big)
)}
</div>

View File

@@ -1,4 +1,4 @@
import React, { useState, useEffect } from "react";
import { useState, useEffect } from "react";
import PageIndex from "./PageIndex/PageIndex";
import PageOverview from "./PageOverview/PageOverview";
import PageHelp from "./PageHelp/PageHelp";
@@ -6,10 +6,9 @@ import PageQuadrant from "./PageQuadrant/PageQuadrant";
import PageItem from "./PageItem/PageItem";
import PageItemMobile from "./PageItemMobile/PageItemMobile";
import {
quadrants,
ConfigData,
getItemPageNames,
isMobileViewport,
rings,
} from "../config";
import { Item } from "../model";
@@ -18,6 +17,7 @@ type RouterProps = {
items: Item[];
releases: string[];
search: string;
config: ConfigData;
};
enum page {
@@ -30,7 +30,7 @@ enum page {
notFound,
}
const getPageByName = (items: Item[], pageName: string): page => {
const getPageByName = (items: Item[], pageName: string, config: ConfigData): page => {
if (pageName === "index") {
return page.index;
}
@@ -40,7 +40,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(config.quadrants).includes(pageName)) {
return page.quadrant;
}
if (getItemPageNames(items).includes(pageName)) {
@@ -55,6 +55,7 @@ export default function Router({
items,
releases,
search,
config
}: RouterProps) {
const [statePageName, setStatePageName] = useState(pageName);
const [leaving, setLeaving] = useState(false);
@@ -62,14 +63,14 @@ export default function Router({
useEffect(() => {
const nowLeaving =
getPageByName(items, pageName) !== getPageByName(items, statePageName);
getPageByName(items, pageName, config) !== getPageByName(items, statePageName, config);
if (nowLeaving) {
setLeaving(true);
setNextPageName(pageName);
} else {
setStatePageName(pageName);
}
}, [pageName, items, statePageName]);
}, [pageName, items, config, statePageName]);
const handlePageLeave = () => {
setStatePageName(nextPageName);
@@ -82,12 +83,13 @@ export default function Router({
}, 0);
};
switch (getPageByName(items, statePageName)) {
switch (getPageByName(items, statePageName, config)) {
case page.index:
return (
<PageIndex
leaving={leaving}
items={items}
config={config}
onLeave={handlePageLeave}
releases={releases}
/>
@@ -96,7 +98,8 @@ export default function Router({
return (
<PageOverview
items={items}
rings={rings}
config={config}
rings={config.rings}
search={search}
leaving={leaving}
onLeave={handlePageLeave}
@@ -110,6 +113,7 @@ export default function Router({
leaving={leaving}
onLeave={handlePageLeave}
items={items}
config={config}
pageName={statePageName}
/>
);
@@ -117,6 +121,7 @@ export default function Router({
return (
<PageItemMobile
items={items}
config={config}
pageName={statePageName}
leaving={leaving}
onLeave={handlePageLeave}
@@ -126,6 +131,7 @@ export default function Router({
return (
<PageItem
items={items}
config={config}
pageName={statePageName}
leaving={leaving}
onLeave={handlePageLeave}

View File

@@ -1,34 +1,19 @@
import { Item } from "./model";
export interface ConfigData {
quadrants: { [key: string]: string };
rings: string[];
}
export const radarName =
process.env.REACT_APP_RADAR_NAME || "AOE Technology Radar";
export const radarNameShort = radarName;
export const quadrants = [
"languages-and-frameworks",
"methods-and-patterns",
"platforms-and-aoe-services",
"tools",
];
export const rings = ["all", "adopt", "trial", "assess", "hold"] as const;
export type Ring = typeof rings[number];
export const getItemPageNames = (items: Item[]) =>
items.map((item) => `${item.quadrant}/${item.name}`);
export const showEmptyRings = false;
const messages: { [k: string]: string } = {
"languages-and-frameworks": "Languages & Frameworks",
"methods-and-patterns": "Methods & Patterns",
"platforms-and-aoe-services": "Platforms & 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;
@@ -43,3 +28,7 @@ export function isMobileViewport() {
export function assetUrl(file: string) {
return process.env.PUBLIC_URL + "/" + file;
}
export function translate(config: ConfigData, key: string) {
return config.quadrants[key] || "-";
}

View File

@@ -6,11 +6,6 @@ interface Quadrant {
description: string;
}
interface Ring {
name: string;
description: string;
}
interface Paragraph {
headline: string;
values: string[];
@@ -20,7 +15,7 @@ interface PageHelp {
paragraphs: Paragraph[];
quadrantsPreDescription?: string;
quadrants: Quadrant[];
rings: Ring[];
rings: {name: string, description: string }[];
ringsPreDescription?: string;
sourcecodeLink?: {
href: string;

View File

@@ -1,8 +1,6 @@
import { Ring } from "./config";
export type ItemAttributes = {
name: string;
ring: Ring;
ring: string;
quadrant: string;
title: string;
featured?: boolean;

1786
yarn.lock

File diff suppressed because it is too large Load Diff