chore(codestyle): cleanup and reformat

This commit is contained in:
Bastian Ike
2022-09-13 11:18:36 +02:00
committed by Bastian
parent 6081f1edbb
commit 37c43712d1
56 changed files with 1142 additions and 560 deletions

View File

@@ -1,3 +1,3 @@
{
"extends": ["react-app", "react-app/jest"]
"extends": ["react-app", "react-app/jest", "prettier"]
}

1
.npmrc Normal file
View File

@@ -0,0 +1 @@
save-exact=true

View File

@@ -24,8 +24,8 @@ var __importStar = (this && this.__importStar) || function (mod) {
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
var fs = __importStar(require("fs-extra"));
var child_process_1 = require("child_process");
var fs = __importStar(require("fs-extra"));
var paths = __importStar(require("./paths"));
// Do this as the first thing so that any code reading it knows the right env.
process.env.BABEL_ENV = "production";

View File

@@ -83,16 +83,15 @@ 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 = require("marked");
var fs_1 = require("fs");
var fs_extra_1 = require("fs-extra");
var highlight_js_1 = __importDefault(require("highlight.js"));
var file_1 = require("./file");
var marked_1 = require("marked");
var path = __importStar(require("path"));
var model_1 = require("../../src/model");
var paths_1 = require("../paths");
var file_1 = require("./file");
marked_1.marked.setOptions({
highlight: function (code) { return highlight_js_1.default.highlightAuto(code).value; },
});
@@ -180,7 +179,7 @@ var createItems = function (revisions) {
return __assign(__assign({}, items), (_a = {}, _a[revision.name] = addRevisionToItem(items[revision.name], revision), _a));
}, {});
return Object.values(itemMap)
.map(function (item) { return (__assign(__assign({}, item), { "title": item.title || item.name })); })
.map(function (item) { return (__assign(__assign({}, item), { title: item.title || item.name })); })
.sort(function (x, y) { return (x.name > y.name ? 1 : -1); });
};
var ignoreEmptyRevisionBody = function (revision, item) {
@@ -208,7 +207,10 @@ var addRevisionToItem = function (item, revision) {
return newItem;
};
var revisionCreatesNewHistoryEntry = function (revision, item) {
return revision.body.trim() !== "" || (typeof revision.ring !== "undefined" && revision.ring !== item.ring) || (typeof revision.quadrant !== "undefined" && revision.quadrant !== item.quadrant);
return (revision.body.trim() !== "" ||
(typeof revision.ring !== "undefined" && revision.ring !== item.ring) ||
(typeof revision.quadrant !== "undefined" &&
revision.quadrant !== item.quadrant));
};
var flagItem = function (items, allReleases) {
return items.map(function (item) {

View File

@@ -1,8 +1,8 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.appNodeModules = exports.appYarnLock = exports.appPublic = exports.appBuild = exports.appRdJson = exports.templateNodeModules = exports.templateBuild = exports.template = exports.radarJson = void 0;
var path_1 = require("path");
var fs_1 = require("fs");
var path_1 = require("path");
exports.radarJson = "rd.json";
var appDirectory = (0, fs_1.realpathSync)(process.cwd());
var resolveApp = function (relativePath) {

389
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "aoe_technology_radar",
"version": "3.3.2",
"version": "3.3.3",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "aoe_technology_radar",
"version": "3.3.2",
"version": "3.3.3",
"license": "Apache-2.0",
"dependencies": {
"@apideck/better-ajv-errors": "0.3.6",
@@ -15,7 +15,6 @@
"@types/d3": "7.4.0",
"@types/fs-extra": "9.0.13",
"@types/jest": "29.0.1",
"@types/node": "17.0.33",
"@types/react": "18.0.19",
"@types/react-dom": "18.0.6",
"@types/sanitize-html": "2.6.2",
@@ -48,9 +47,14 @@
"aoe_technology_radar-generateJson": "dist_scripts/scripts/generateJson.js"
},
"devDependencies": {
"@trivago/prettier-plugin-sort-imports": "3.3.0",
"@types/marked": "4.0.7",
"@types/node": "18.7.17",
"@typescript-eslint/parser": "5.37.0",
"eslint": "8.23.1",
"eslint-config-prettier": "8.5.0",
"eslint-config-react-app": "7.0.1",
"eslint-plugin-prettier": "4.2.1",
"eslint-plugin-react": "7.31.8",
"yallist": "4.0.0"
}
@@ -3319,6 +3323,141 @@
"node": ">= 6"
}
},
"node_modules/@trivago/prettier-plugin-sort-imports": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/@trivago/prettier-plugin-sort-imports/-/prettier-plugin-sort-imports-3.3.0.tgz",
"integrity": "sha512-1y44bVZuIN0RsS3oIiGd5k8Vm3IZXYZnp4VsP2Z/S5L9WAOw43HE2clso66M2S/dDeJ+8sKPqnHsEfh39Vjs3w==",
"dev": true,
"dependencies": {
"@babel/core": "7.17.8",
"@babel/generator": "7.17.7",
"@babel/parser": "7.17.8",
"@babel/traverse": "7.17.3",
"@babel/types": "7.17.0",
"javascript-natural-sort": "0.7.1",
"lodash": "4.17.21"
},
"peerDependencies": {
"prettier": "2.x"
}
},
"node_modules/@trivago/prettier-plugin-sort-imports/node_modules/@babel/core": {
"version": "7.17.8",
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.17.8.tgz",
"integrity": "sha512-OdQDV/7cRBtJHLSOBqqbYNkOcydOgnX59TZx4puf41fzcVtN3e/4yqY8lMQsK+5X2lJtAdmA+6OHqsj1hBJ4IQ==",
"dev": true,
"dependencies": {
"@ampproject/remapping": "^2.1.0",
"@babel/code-frame": "^7.16.7",
"@babel/generator": "^7.17.7",
"@babel/helper-compilation-targets": "^7.17.7",
"@babel/helper-module-transforms": "^7.17.7",
"@babel/helpers": "^7.17.8",
"@babel/parser": "^7.17.8",
"@babel/template": "^7.16.7",
"@babel/traverse": "^7.17.3",
"@babel/types": "^7.17.0",
"convert-source-map": "^1.7.0",
"debug": "^4.1.0",
"gensync": "^1.0.0-beta.2",
"json5": "^2.1.2",
"semver": "^6.3.0"
},
"engines": {
"node": ">=6.9.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/babel"
}
},
"node_modules/@trivago/prettier-plugin-sort-imports/node_modules/@babel/generator": {
"version": "7.17.7",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.17.7.tgz",
"integrity": "sha512-oLcVCTeIFadUoArDTwpluncplrYBmTCCZZgXCbgNGvOBBiSDDK3eWO4b/+eOTli5tKv1lg+a5/NAXg+nTcei1w==",
"dev": true,
"dependencies": {
"@babel/types": "^7.17.0",
"jsesc": "^2.5.1",
"source-map": "^0.5.0"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@trivago/prettier-plugin-sort-imports/node_modules/@babel/parser": {
"version": "7.17.8",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.17.8.tgz",
"integrity": "sha512-BoHhDJrJXqcg+ZL16Xv39H9n+AqJ4pcDrQBGZN+wHxIysrLZ3/ECwCBUch/1zUNhnsXULcONU3Ei5Hmkfk6kiQ==",
"dev": true,
"bin": {
"parser": "bin/babel-parser.js"
},
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@trivago/prettier-plugin-sort-imports/node_modules/@babel/traverse": {
"version": "7.17.3",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.17.3.tgz",
"integrity": "sha512-5irClVky7TxRWIRtxlh2WPUUOLhcPN06AGgaQSB8AEwuyEBgJVuJ5imdHm5zxk8w0QS5T+tDfnDxAlhWjpb7cw==",
"dev": true,
"dependencies": {
"@babel/code-frame": "^7.16.7",
"@babel/generator": "^7.17.3",
"@babel/helper-environment-visitor": "^7.16.7",
"@babel/helper-function-name": "^7.16.7",
"@babel/helper-hoist-variables": "^7.16.7",
"@babel/helper-split-export-declaration": "^7.16.7",
"@babel/parser": "^7.17.3",
"@babel/types": "^7.17.0",
"debug": "^4.1.0",
"globals": "^11.1.0"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@trivago/prettier-plugin-sort-imports/node_modules/@babel/types": {
"version": "7.17.0",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.17.0.tgz",
"integrity": "sha512-TmKSNO4D5rzhL5bjWFcVHHLETzfQ/AmbKpKPOSjlP0WoHZ6L911fgoOKY4Alp/emzG4cHJdyN49zpgkbXFEHHw==",
"dev": true,
"dependencies": {
"@babel/helper-validator-identifier": "^7.16.7",
"to-fast-properties": "^2.0.0"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@trivago/prettier-plugin-sort-imports/node_modules/globals": {
"version": "11.12.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
"integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
"dev": true,
"engines": {
"node": ">=4"
}
},
"node_modules/@trivago/prettier-plugin-sort-imports/node_modules/semver": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
"dev": true,
"bin": {
"semver": "bin/semver.js"
}
},
"node_modules/@trivago/prettier-plugin-sort-imports/node_modules/source-map": {
"version": "0.5.7",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
"integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/@trysound/sax": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz",
@@ -3916,15 +4055,21 @@
"resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz",
"integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4="
},
"node_modules/@types/marked": {
"version": "4.0.7",
"resolved": "https://registry.npmjs.org/@types/marked/-/marked-4.0.7.tgz",
"integrity": "sha512-eEAhnz21CwvKVW+YvRvcTuFKNU9CV1qH+opcgVK3pIMI6YZzDm6gc8o2vHjldFk6MGKt5pueSB7IOpvpx5Qekw==",
"dev": true
},
"node_modules/@types/mime": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz",
"integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw=="
},
"node_modules/@types/node": {
"version": "17.0.33",
"resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.33.tgz",
"integrity": "sha512-miWq2m2FiQZmaHfdZNcbpp9PuXg34W5JZ5CrJ/BaS70VuhoJENBEQybeiYSaPBRNq6KQGnjfEnc/F3PN++D+XQ=="
"version": "18.7.17",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.7.17.tgz",
"integrity": "sha512-0UyfUnt02zIuqp7yC8RYtDkp/vo8bFaQ13KkSEvUAohPOAlnVNbj5Fi3fgPSuwzakS+EvvnnZ4x9y7i6ASaSPQ=="
},
"node_modules/@types/parse-json": {
"version": "4.0.0",
@@ -7292,6 +7437,18 @@
"url": "https://opencollective.com/eslint"
}
},
"node_modules/eslint-config-prettier": {
"version": "8.5.0",
"resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.5.0.tgz",
"integrity": "sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q==",
"dev": true,
"bin": {
"eslint-config-prettier": "bin/cli.js"
},
"peerDependencies": {
"eslint": ">=7.0.0"
}
},
"node_modules/eslint-config-react-app": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/eslint-config-react-app/-/eslint-config-react-app-7.0.1.tgz",
@@ -7483,6 +7640,27 @@
"node": ">=6.0"
}
},
"node_modules/eslint-plugin-prettier": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-4.2.1.tgz",
"integrity": "sha512-f/0rXLXUt0oFYs8ra4w49wYZBG5GKZpAYsJSm6rnYL5uVDjd+zowwMwVZHnAjf4edNrKpCDYfXDgmRE/Ak7QyQ==",
"dev": true,
"dependencies": {
"prettier-linter-helpers": "^1.0.0"
},
"engines": {
"node": ">=12.0.0"
},
"peerDependencies": {
"eslint": ">=7.28.0",
"prettier": ">=2.0.0"
},
"peerDependenciesMeta": {
"eslint-config-prettier": {
"optional": true
}
}
},
"node_modules/eslint-plugin-react": {
"version": "7.31.8",
"resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.31.8.tgz",
@@ -7958,6 +8136,12 @@
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
},
"node_modules/fast-diff": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz",
"integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==",
"dev": true
},
"node_modules/fast-glob": {
"version": "3.2.11",
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz",
@@ -9589,6 +9773,12 @@
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
"node_modules/javascript-natural-sort": {
"version": "0.7.1",
"resolved": "https://registry.npmjs.org/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz",
"integrity": "sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw==",
"dev": true
},
"node_modules/jest": {
"version": "27.5.1",
"resolved": "https://registry.npmjs.org/jest/-/jest-27.5.1.tgz",
@@ -12942,6 +13132,34 @@
"node": ">= 0.8.0"
}
},
"node_modules/prettier": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz",
"integrity": "sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==",
"dev": true,
"peer": true,
"bin": {
"prettier": "bin-prettier.js"
},
"engines": {
"node": ">=10.13.0"
},
"funding": {
"url": "https://github.com/prettier/prettier?sponsor=1"
}
},
"node_modules/prettier-linter-helpers": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz",
"integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==",
"dev": true,
"dependencies": {
"fast-diff": "^1.1.2"
},
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/pretty-bytes": {
"version": "5.6.0",
"resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz",
@@ -18345,6 +18563,109 @@
"resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz",
"integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw=="
},
"@trivago/prettier-plugin-sort-imports": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/@trivago/prettier-plugin-sort-imports/-/prettier-plugin-sort-imports-3.3.0.tgz",
"integrity": "sha512-1y44bVZuIN0RsS3oIiGd5k8Vm3IZXYZnp4VsP2Z/S5L9WAOw43HE2clso66M2S/dDeJ+8sKPqnHsEfh39Vjs3w==",
"dev": true,
"requires": {
"@babel/core": "7.17.8",
"@babel/generator": "7.17.7",
"@babel/parser": "7.17.8",
"@babel/traverse": "7.17.3",
"@babel/types": "7.17.0",
"javascript-natural-sort": "0.7.1",
"lodash": "4.17.21"
},
"dependencies": {
"@babel/core": {
"version": "7.17.8",
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.17.8.tgz",
"integrity": "sha512-OdQDV/7cRBtJHLSOBqqbYNkOcydOgnX59TZx4puf41fzcVtN3e/4yqY8lMQsK+5X2lJtAdmA+6OHqsj1hBJ4IQ==",
"dev": true,
"requires": {
"@ampproject/remapping": "^2.1.0",
"@babel/code-frame": "^7.16.7",
"@babel/generator": "^7.17.7",
"@babel/helper-compilation-targets": "^7.17.7",
"@babel/helper-module-transforms": "^7.17.7",
"@babel/helpers": "^7.17.8",
"@babel/parser": "^7.17.8",
"@babel/template": "^7.16.7",
"@babel/traverse": "^7.17.3",
"@babel/types": "^7.17.0",
"convert-source-map": "^1.7.0",
"debug": "^4.1.0",
"gensync": "^1.0.0-beta.2",
"json5": "^2.1.2",
"semver": "^6.3.0"
}
},
"@babel/generator": {
"version": "7.17.7",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.17.7.tgz",
"integrity": "sha512-oLcVCTeIFadUoArDTwpluncplrYBmTCCZZgXCbgNGvOBBiSDDK3eWO4b/+eOTli5tKv1lg+a5/NAXg+nTcei1w==",
"dev": true,
"requires": {
"@babel/types": "^7.17.0",
"jsesc": "^2.5.1",
"source-map": "^0.5.0"
}
},
"@babel/parser": {
"version": "7.17.8",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.17.8.tgz",
"integrity": "sha512-BoHhDJrJXqcg+ZL16Xv39H9n+AqJ4pcDrQBGZN+wHxIysrLZ3/ECwCBUch/1zUNhnsXULcONU3Ei5Hmkfk6kiQ==",
"dev": true
},
"@babel/traverse": {
"version": "7.17.3",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.17.3.tgz",
"integrity": "sha512-5irClVky7TxRWIRtxlh2WPUUOLhcPN06AGgaQSB8AEwuyEBgJVuJ5imdHm5zxk8w0QS5T+tDfnDxAlhWjpb7cw==",
"dev": true,
"requires": {
"@babel/code-frame": "^7.16.7",
"@babel/generator": "^7.17.3",
"@babel/helper-environment-visitor": "^7.16.7",
"@babel/helper-function-name": "^7.16.7",
"@babel/helper-hoist-variables": "^7.16.7",
"@babel/helper-split-export-declaration": "^7.16.7",
"@babel/parser": "^7.17.3",
"@babel/types": "^7.17.0",
"debug": "^4.1.0",
"globals": "^11.1.0"
}
},
"@babel/types": {
"version": "7.17.0",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.17.0.tgz",
"integrity": "sha512-TmKSNO4D5rzhL5bjWFcVHHLETzfQ/AmbKpKPOSjlP0WoHZ6L911fgoOKY4Alp/emzG4cHJdyN49zpgkbXFEHHw==",
"dev": true,
"requires": {
"@babel/helper-validator-identifier": "^7.16.7",
"to-fast-properties": "^2.0.0"
}
},
"globals": {
"version": "11.12.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
"integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
"dev": true
},
"semver": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
"dev": true
},
"source-map": {
"version": "0.5.7",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
"integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==",
"dev": true
}
}
},
"@trysound/sax": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz",
@@ -18901,15 +19222,21 @@
"resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz",
"integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4="
},
"@types/marked": {
"version": "4.0.7",
"resolved": "https://registry.npmjs.org/@types/marked/-/marked-4.0.7.tgz",
"integrity": "sha512-eEAhnz21CwvKVW+YvRvcTuFKNU9CV1qH+opcgVK3pIMI6YZzDm6gc8o2vHjldFk6MGKt5pueSB7IOpvpx5Qekw==",
"dev": true
},
"@types/mime": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz",
"integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw=="
},
"@types/node": {
"version": "17.0.33",
"resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.33.tgz",
"integrity": "sha512-miWq2m2FiQZmaHfdZNcbpp9PuXg34W5JZ5CrJ/BaS70VuhoJENBEQybeiYSaPBRNq6KQGnjfEnc/F3PN++D+XQ=="
"version": "18.7.17",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.7.17.tgz",
"integrity": "sha512-0UyfUnt02zIuqp7yC8RYtDkp/vo8bFaQ13KkSEvUAohPOAlnVNbj5Fi3fgPSuwzakS+EvvnnZ4x9y7i6ASaSPQ=="
},
"@types/parse-json": {
"version": "4.0.0",
@@ -21450,6 +21777,13 @@
}
}
},
"eslint-config-prettier": {
"version": "8.5.0",
"resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.5.0.tgz",
"integrity": "sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q==",
"dev": true,
"requires": {}
},
"eslint-config-react-app": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/eslint-config-react-app/-/eslint-config-react-app-7.0.1.tgz",
@@ -21599,6 +21933,15 @@
}
}
},
"eslint-plugin-prettier": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-4.2.1.tgz",
"integrity": "sha512-f/0rXLXUt0oFYs8ra4w49wYZBG5GKZpAYsJSm6rnYL5uVDjd+zowwMwVZHnAjf4edNrKpCDYfXDgmRE/Ak7QyQ==",
"dev": true,
"requires": {
"prettier-linter-helpers": "^1.0.0"
}
},
"eslint-plugin-react": {
"version": "7.31.8",
"resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.31.8.tgz",
@@ -21861,6 +22204,12 @@
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
},
"fast-diff": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz",
"integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==",
"dev": true
},
"fast-glob": {
"version": "3.2.11",
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz",
@@ -23013,6 +23362,12 @@
}
}
},
"javascript-natural-sort": {
"version": "0.7.1",
"resolved": "https://registry.npmjs.org/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz",
"integrity": "sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw==",
"dev": true
},
"jest": {
"version": "27.5.1",
"resolved": "https://registry.npmjs.org/jest/-/jest-27.5.1.tgz",
@@ -25338,6 +25693,22 @@
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
"integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="
},
"prettier": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz",
"integrity": "sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==",
"dev": true,
"peer": true
},
"prettier-linter-helpers": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz",
"integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==",
"dev": true,
"requires": {
"fast-diff": "^1.1.2"
}
},
"pretty-bytes": {
"version": "5.6.0",
"resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz",

View File

@@ -29,7 +29,6 @@
"@types/d3": "7.4.0",
"@types/fs-extra": "9.0.13",
"@types/jest": "29.0.1",
"@types/node": "17.0.33",
"@types/react": "18.0.19",
"@types/react-dom": "18.0.6",
"@types/sanitize-html": "2.6.2",
@@ -57,9 +56,14 @@
"yaml": "2.1.1"
},
"devDependencies": {
"@trivago/prettier-plugin-sort-imports": "3.3.0",
"@types/marked": "4.0.7",
"@types/node": "18.7.17",
"@typescript-eslint/parser": "5.37.0",
"eslint": "8.23.1",
"eslint-config-prettier": "8.5.0",
"eslint-config-react-app": "7.0.1",
"eslint-plugin-prettier": "4.2.1",
"eslint-plugin-react": "7.31.8",
"yallist": "4.0.0"
},

View File

@@ -1,4 +1,5 @@
module.exports = {
importOrder: ["^[./]"],
importOrderSeparation: true,
importOrderSortSpecifiers: true,
};

View File

@@ -1,7 +1,7 @@
#!/usr/bin/env node
import * as fs from "fs-extra";
import { spawn } from "child_process";
import * as fs from "fs-extra";
import * as paths from "./paths";
// Do this as the first thing so that any code reading it knows the right env.
@@ -52,7 +52,7 @@ const buildTemplate = () => {
buildTemplate().then(() => {
fs.copySync(paths.templateBuild, paths.appBuild);
fs.ensureDirSync(paths.appPublic)
fs.ensureDirSync(paths.appPublic);
fs.copySync(paths.appPublic, paths.appBuild);
console.log(`${paths.appBuild} was created and can be deployed.`);
});

View File

@@ -1,6 +1,6 @@
#!/usr/bin/env node
import { copyFileSync, existsSync, mkdirSync, readFileSync } from "fs";
import { copyFileSync, mkdirSync, existsSync, readFileSync } from "fs";
import { createRadar } from "./generateJson/radar";
// Do this as the first thing so that any code reading it knows the right env.

View File

@@ -1,6 +1,6 @@
#!/usr/bin/env node
import * as fs from "fs-extra";
import * as paths from "./paths";
// Do this as the first thing so that any code reading it knows the right env.

View File

@@ -1,32 +1,39 @@
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
import { marked } from "marked";
import { readFileSync } from "fs";
import { readFile } from "fs-extra";
import highlight from "highlight.js";
import { marked } from "marked";
import * as path from "path";
import { radarPath, getAllMarkdownFiles } from "./file";
import { Item, Revision, ItemAttributes, Radar, FlagType } from "../../src/model";
import {
FlagType,
Item,
ItemAttributes,
Radar,
Revision,
} from "../../src/model";
import { appBuild } from "../paths";
import { getAllMarkdownFiles, radarPath } from "./file";
import type { ConfigData } from "../../src/config";
type FMAttributes = ItemAttributes;
marked.setOptions({
highlight: (code: any) => highlight.highlightAuto(code).value,
});
export const createRadar = async (): Promise<Radar> => {
const fileNames = await getAllMarkdownFiles(radarPath());
const revisions: (Revision|undefined)[] = await createRevisionsFromFiles(fileNames);
const filterdRevisions : Revision[] = revisions.filter(r => r !== undefined) as Revision[];
const revisions: (Revision | undefined)[] = await createRevisionsFromFiles(
fileNames
);
const filterdRevisions: Revision[] = revisions.filter(
(r) => r !== undefined
) as Revision[];
const allReleases = getAllReleases(filterdRevisions);
const items = createItems(filterdRevisions);
const flaggedItems = flagItem(items, allReleases);
items.forEach(item => checkAttributes(item.name, item))
items.forEach((item) => checkAttributes(item.name, item));
return {
items: flaggedItems,
@@ -34,7 +41,7 @@ export const createRadar = async (): Promise<Radar> => {
};
};
const checkAttributes = (fileName: string, attributes: FMAttributes) => {
const checkAttributes = (fileName: string, attributes: ItemAttributes) => {
const rawConf = readFileSync(path.resolve(appBuild, "config.json"), "utf-8");
const config = JSON.parse(rawConf) as ConfigData;
@@ -61,16 +68,14 @@ const checkAttributes = (fileName: string, attributes: FMAttributes) => {
} else {
return attributes;
}
};
const createRevisionsFromFiles = (fileNames: string[]) => {
const publicUrl = process.env.PUBLIC_URL;
return Promise.all(
fileNames.map(
(fileName) =>
readFile(fileName, "utf8").then(data => {
const fm = frontMatter<FMAttributes>(data);
fileNames.map((fileName) =>
readFile(fileName, "utf8").then((data) => {
const fm = frontMatter<ItemAttributes>(data);
let html = marked(fm.body.replace(/\]\(\//g, `](${publicUrl}/`));
html = html.replace(
/a href="http/g,
@@ -120,7 +125,7 @@ const createItems = (revisions: Revision[]) => {
);
return Object.values(itemMap)
.map((item) => ({ ...item, "title": item.title || item.name }))
.map((item) => ({ ...item, title: item.title || item.name }))
.sort((x, y) => (x.name > y.name ? 1 : -1));
};
@@ -162,7 +167,12 @@ const addRevisionToItem = (
};
const revisionCreatesNewHistoryEntry = (revision: Revision, item: Item) => {
return revision.body.trim() !== "" || (typeof revision.ring !== "undefined" && revision.ring !== item.ring) || (typeof revision.quadrant !== "undefined" && revision.quadrant !== item.quadrant);
return (
revision.body.trim() !== "" ||
(typeof revision.ring !== "undefined" && revision.ring !== item.ring) ||
(typeof revision.quadrant !== "undefined" &&
revision.quadrant !== item.quadrant)
);
};
const flagItem = (items: Item[], allReleases: string[]) =>

View File

@@ -1,5 +1,5 @@
import { resolve } from "path";
import { realpathSync } from "fs";
import { resolve } from "path";
export const radarJson = "rd.json";
const appDirectory = realpathSync(process.cwd());

View File

@@ -1,19 +1,20 @@
import React from "react";
import classNames from "classnames";
import Header from "./Header/Header";
import Footer from "./Footer/Footer";
import Router from "./Router";
import React from "react";
import {
BrowserRouter,
Routes,
Route,
Navigate,
useParams,
Route,
Routes,
useLocation,
useParams,
} from "react-router-dom";
import { Item } from "../model";
import { Messages, MessagesProvider } from "../context/MessagesContext";
import { ConfigData } from "../config";
import { Messages, MessagesProvider } from "../context/MessagesContext";
import { Item } from "../model";
import Footer from "./Footer/Footer";
import Header from "./Header/Header";
import Router from "./Router";
const useFetch = <D extends unknown>(url: string): D | undefined => {
const [data, setData] = React.useState<D>();
@@ -35,7 +36,7 @@ const useFetch = <D extends unknown>(url: string): D | undefined => {
const useQuery = () => new URLSearchParams(useLocation().search);
const usePage = (params: Record<string, string | undefined>) => {
return (params['*'] || '').replace(".html", "");
return (params["*"] || "").replace(".html", "");
};
const RouterWithPageParam = ({

View File

@@ -1,5 +1,6 @@
import React, { MouseEventHandler } from "react";
import classNames from "classnames";
import React, { MouseEventHandler } from "react";
import "./badge.scss";
type BadgeProps = {

View File

@@ -1,6 +1,8 @@
import React from "react";
import classNames from "classnames";
import React from "react";
import "./branding.scss";
type BrandingProps = {
logoContent: React.ReactNode;
modifier?: "backlink" | "logo" | "content" | "footer";

View File

@@ -1,45 +1,43 @@
import React, { useRef, useLayoutEffect } from 'react';
import * as d3 from "d3";
import React, { useLayoutEffect, useRef } from "react";
export const YAxis: React.FC<{
scale: d3.ScaleLinear<number, number>
scale: d3.ScaleLinear<number, number>;
}> = ({ scale }) => {
const ref = useRef<SVGSVGElement>(null);
useLayoutEffect(() => {
if (ref.current == null) {
return
return;
}
const axisGenerator = d3.axisLeft(scale).ticks(6);
d3.select(ref.current)
.attr('class', 'y-axis')
.attr("class", "y-axis")
.call(axisGenerator)
.call(g => g.selectAll('.tick text').remove())
.call(g => g.selectAll('.tick line').remove())
.call(g => g.selectAll('.domain').remove());
.call((g) => g.selectAll(".tick text").remove())
.call((g) => g.selectAll(".tick line").remove())
.call((g) => g.selectAll(".domain").remove());
}, [scale]);
return <g ref={ref} />;
};
export const XAxis: React.FC<{
scale: d3.ScaleLinear<number, number>
scale: d3.ScaleLinear<number, number>;
}> = ({ scale }) => {
const ref = useRef<SVGSVGElement>(null);
useLayoutEffect(() => {
if (ref.current == null) {
return
return;
}
const axisGenerator = d3.axisBottom(scale).ticks(6);
d3.select(ref.current)
.attr('class', 'x-axis')
.attr("class", "x-axis")
.call(axisGenerator)
.call(g => g.selectAll('.tick text').remove())
.call(g => g.selectAll('.tick line').remove())
.call(g => g.selectAll('.domain').remove());
.call((g) => g.selectAll(".tick text").remove())
.call((g) => g.selectAll(".tick line").remove())
.call((g) => g.selectAll(".domain").remove());
}, [scale]);
return <g ref={ref} />;

View File

@@ -1,57 +1,73 @@
import React from 'react';
import { ScaleLinear } from 'd3';
import { FlagType, Item, Blip, Point } from '../../model';
import Link from '../Link/Link';
import { NewBlip, ChangedBlip, DefaultBlip } from './BlipShapes';
import { ConfigData } from '../../config';
import { ScaleLinear } from "d3";
import React from "react";
import { ConfigData } from "../../config";
import { Blip, FlagType, Item, Point } from "../../model";
import Link from "../Link/Link";
import { ChangedBlip, DefaultBlip, NewBlip } 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
*/
function generateCoordinates(blip: Blip, xScale: ScaleLinear<number, number>, yScale: ScaleLinear<number, number>, config: ConfigData): Point {
function generateCoordinates(
blip: Blip,
xScale: ScaleLinear<number, number>,
yScale: ScaleLinear<number, number>,
config: ConfigData
): Point {
const pi = Math.PI,
ringRadius = config.chartConfig.ringsAttributes[blip.ringPosition].radius,
previousRingRadius = blip.ringPosition === 0 ? 0 : config.chartConfig.ringsAttributes[blip.ringPosition - 1].radius,
previousRingRadius =
blip.ringPosition === 0
? 0
: config.chartConfig.ringsAttributes[blip.ringPosition - 1].radius,
ringPadding = 0.7;
// radian between 0 and 90 degrees
const randomDegree = ((Math.random() * 90) * pi) / 180;
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);
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, so we need to "invert" quadrant positions (i.e. swap 2 with 4)
*/
const shift = pi * [1, 4, 3, 2][blip.quadrantPosition - 1] / 2;
const shift = (pi * [1, 4, 3, 2][blip.quadrantPosition - 1]) / 2;
return {
x: xScale(Math.cos(randomDegree + shift) * radius),
y: yScale(Math.sin(randomDegree + shift) * radius)
};
y: yScale(Math.sin(randomDegree + shift) * radius),
};
}
function randomBetween(min: number, max: number): number {
return Math.random() * (max - min) + min;
};
}
function distanceBetween(point1: Point, point2: Point): number {
const a = point2.x - point1.x;
const b = point2.y - point1.y;
return Math.sqrt((a * a) + (b * b));
};
return Math.sqrt(a * a + b * b);
}
function renderBlip(blip: Blip, index: number, config: ConfigData): JSX.Element {
function renderBlip(
blip: Blip,
index: number,
config: ConfigData
): JSX.Element {
const props = {
blip,
className: 'blip',
className: "blip",
fill: blip.colour,
'data-background-color': blip.colour,
'data-text-color': blip.txtColour,
'data-tip': blip.title,
key: index
}
"data-background-color": blip.colour,
"data-text-color": blip.txtColour,
"data-tip": blip.title,
key: index,
};
switch (blip.flag) {
case FlagType.new:
return <NewBlip {...props} config={config} />;
@@ -60,15 +76,14 @@ function renderBlip(blip: Blip, index: number, config: ConfigData): JSX.Element
default:
return <DefaultBlip {...props} config={config} />;
}
};
}
const BlipPoints: React.FC<{
items: Item[]
xScale:ScaleLinear<number, number>
yScale:ScaleLinear<number, number>
config:ConfigData
items: Item[];
xScale: ScaleLinear<number, number>;
yScale: ScaleLinear<number, number>;
config: ConfigData;
}> = ({ items, xScale, yScale, config }) => {
const blips: Blip[] = items.reduce((list: Blip[], item: Item) => {
if (!item.ring || !item.quadrant) {
// skip the blip if it doesn't have a ring or quadrant assigned
@@ -79,9 +94,10 @@ const BlipPoints: React.FC<{
return list;
}
let blip: Blip = { ...item,
let blip: Blip = {
...item,
quadrantPosition: quadrantConfig.position,
ringPosition: config.rings.findIndex(r => r === item.ring),
ringPosition: config.rings.findIndex((r) => r === item.ring),
colour: quadrantConfig.colour,
txtColour: quadrantConfig.txtColour,
coordinates: { x: 0, y: 0 },
@@ -92,19 +108,24 @@ const BlipPoints: React.FC<{
let distanceBetweenCheck: boolean;
do {
const localpoint = generateCoordinates(blip, xScale, yScale, config);
point = localpoint
point = localpoint;
counter++;
/*
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.
*/
distanceBetweenCheck = list.some(b => distanceBetween(localpoint, b.coordinates) < config.chartConfig.blipSize + config.chartConfig.blipSize / 2)
} while (counter < 100
&& (Math.abs(point.x - xScale(0)) < 15
|| Math.abs(point.y - yScale(0)) < 15
|| distanceBetweenCheck
));
distanceBetweenCheck = list.some(
(b) =>
distanceBetween(localpoint, b.coordinates) <
config.chartConfig.blipSize + config.chartConfig.blipSize / 2
);
} while (
counter < 100 &&
(Math.abs(point.x - xScale(0)) < 15 ||
Math.abs(point.y - yScale(0)) < 15 ||
distanceBetweenCheck)
);
blip.coordinates = point;

View File

@@ -1,18 +1,19 @@
import React from 'react';
import { ConfigData } from '../../config';
import { Blip } from '../../model';
import React from "react";
import { ConfigData } from "../../config";
import { Blip } from "../../model";
type VisualBlipProps = {
className: string,
fill: string,
'data-background-color': string,
'data-text-color': string,
'data-tip': string,
key: number
}
className: string;
fill: string;
"data-background-color": string;
"data-text-color": string;
"data-tip": string;
key: number;
};
export const ChangedBlip: React.FC<
{blip: Blip, config: ConfigData} & VisualBlipProps
{ blip: Blip; config: ConfigData } & VisualBlipProps
> = ({ blip, config, ...props }) => {
const centeredX = blip.coordinates.x - config.chartConfig.blipSize / 2,
centeredY = blip.coordinates.y - config.chartConfig.blipSize / 2;
@@ -31,7 +32,7 @@ export const ChangedBlip: React.FC<
};
export const NewBlip: React.FC<
{blip: Blip, config: ConfigData} & VisualBlipProps
{ blip: Blip; config: ConfigData } & VisualBlipProps
> = ({ blip, config, ...props }) => {
const centeredX = blip.coordinates.x - config.chartConfig.blipSize / 2,
centeredY = blip.coordinates.y - config.chartConfig.blipSize / 2;
@@ -51,7 +52,7 @@ export const NewBlip: React.FC<
};
export const DefaultBlip: React.FC<
{blip: Blip, config: ConfigData} & VisualBlipProps
{ blip: Blip; config: ConfigData } & VisualBlipProps
> = ({ blip, config, ...props }) => {
return (
<circle

View File

@@ -1,38 +1,48 @@
import React from 'react';
import * as d3 from 'd3';
import { QuadrantConfig } from '../../model';
import { ConfigData } from '../../config';
import * as d3 from "d3";
import React from "react";
function arcPath(quadrantPosition: number, ringPosition: number, xScale: d3.ScaleLinear<number, number>, config: ConfigData) {
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
import { ConfigData } from "../../config";
import { QuadrantConfig } from "../../model";
function arcPath(
quadrantPosition: number,
ringPosition: number,
xScale: d3.ScaleLinear<number, number>,
config: ConfigData
) {
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 = config.chartConfig.ringsAttributes[ringPosition],
ringRadiusPx = xScale(arcAttrs.radius) - xScale(0),
arc = d3.arc();
return arc({
return (
arc({
innerRadius: ringRadiusPx - arcAttrs.arcWidth,
outerRadius: ringRadiusPx,
startAngle,
endAngle
}) || undefined;
endAngle,
}) || undefined
);
}
const QuadrantRings: React.FC<{
quadrant: QuadrantConfig
xScale: d3.ScaleLinear<number, number>
config: ConfigData
quadrant: QuadrantConfig;
xScale: d3.ScaleLinear<number, number>;
config: ConfigData;
}> = ({ quadrant, xScale, config }) => {
// order from top-right clockwise
const gradientAttributes = [
{ x: 0, y: 0, cx: 1, cy: 1, 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: xScale(0), cx: 1, cy: 0, r: 1 },
];
const gradientId = `${quadrant.position}-radial-gradient`,
quadrantSize = config.chartConfig.size / 2;
@@ -41,9 +51,16 @@ const QuadrantRings: React.FC<{
<g className="quadrant-ring">
{/* Definition of the quadrant gradient */}
<defs>
<radialGradient id={gradientId} {...gradientAttributes[quadrant.position - 1]}>
<radialGradient
id={gradientId}
{...gradientAttributes[quadrant.position - 1]}
>
<stop offset="0%" stopColor={quadrant.colour}></stop>
<stop offset="100%" stopColor={quadrant.colour} stopOpacity="0"></stop>
<stop
offset="100%"
stopColor={quadrant.colour}
stopOpacity="0"
></stop>
</radialGradient>
</defs>
@@ -63,12 +80,13 @@ const QuadrantRings: React.FC<{
key={index}
fill={quadrant.colour}
d={arcPath(quadrant.position, index, xScale, config)}
style={{transform: `translate(${quadrantSize}px, ${quadrantSize}px)`}}
style={{
transform: `translate(${quadrantSize}px, ${quadrantSize}px)`,
}}
/>
))}
</g>
);
}
};
export default QuadrantRings;

View File

@@ -1,36 +1,49 @@
import React from 'react';
import * as d3 from "d3";
import ReactTooltip from 'react-tooltip';
import { Item } from '../../model';
import { YAxis, XAxis } from './Axes';
import QuadrantRings from './QuadrantRings';
import BlipPoints from './BlipPoints';
import React from "react";
import ReactTooltip from "react-tooltip";
import './chart.scss';
import { ConfigData } from '../../config';
import { ConfigData } from "../../config";
import { Item } from "../../model";
import { XAxis, YAxis } from "./Axes";
import BlipPoints from "./BlipPoints";
import QuadrantRings from "./QuadrantRings";
import "./chart.scss";
const RingLabel: React.FC<{
ring: string
xScale: d3.ScaleLinear<number, number>
yScale: d3.ScaleLinear<number, number>
config: ConfigData
ring: string;
xScale: d3.ScaleLinear<number, number>;
yScale: d3.ScaleLinear<number, number>;
config: ConfigData;
}> = ({ ring, xScale, yScale, config }) => {
const ringIndex = config.rings.findIndex(r => r === ring)
const ringIndex = config.rings.findIndex((r) => r === ring);
const ringRadius = config.chartConfig.ringsAttributes[ringIndex].radius,
previousRingRadius = ringIndex === 0 ? 0 : config.chartConfig.ringsAttributes[ringIndex - 1].radius,
previousRingRadius =
ringIndex === 0
? 0
: config.chartConfig.ringsAttributes[ringIndex - 1].radius,
// middle point in between two ring arcs
distanceFromCentre = previousRingRadius + (ringRadius - previousRingRadius) / 2;
distanceFromCentre =
previousRingRadius + (ringRadius - previousRingRadius) / 2;
return (
<g className="ring-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}
</text>
{/* 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}
</text>
</g>
@@ -38,20 +51,23 @@ const RingLabel: React.FC<{
};
const RadarChart: React.FC<{
items: Item[]
config: ConfigData
items: Item[];
config: ConfigData;
}> = ({ items, config }) => {
const xScale = d3.scaleLinear()
const xScale = d3
.scaleLinear()
.domain(config.chartConfig.scale)
.range([0, config.chartConfig.size]);
const yScale = d3.scaleLinear()
const yScale = d3
.scaleLinear()
.domain(config.chartConfig.scale)
.range([config.chartConfig.size, 0]);
return (
<div className="chart" style={{ maxWidth: `${config.chartConfig.size}px` }}>
<svg viewBox={`0 0 ${config.chartConfig.size} ${config.chartConfig.size}`}>
<svg
viewBox={`0 0 ${config.chartConfig.size} ${config.chartConfig.size}`}
>
<g transform={`translate(${xScale(0)}, 0)`}>
<YAxis scale={yScale} />
</g>
@@ -60,18 +76,34 @@ const RadarChart: React.FC<{
</g>
{Object.values(config.quadrantsMap).map((value, index) => (
<QuadrantRings key={index} quadrant={value} xScale={xScale} config={config} />
<QuadrantRings
key={index}
quadrant={value}
xScale={xScale}
config={config}
/>
))}
{Array.from(config.rings).map((ring: string, index) => (
<RingLabel key={index} ring={ring} xScale={xScale} yScale={yScale} config={config} />
<RingLabel
key={index}
ring={ring}
xScale={xScale}
yScale={yScale}
config={config}
/>
))}
<BlipPoints items={items} xScale={xScale} yScale={yScale} config={config} />
<BlipPoints
items={items}
xScale={xScale}
yScale={yScale}
config={config}
/>
</svg>
<ReactTooltip className="tooltip" offset={{ top: -5 }} />
</div>
);
}
};
export default RadarChart;

View File

@@ -1,16 +1,28 @@
import React from "react";
import { Item } from "../../model";
import "./editButton.scss";
type EditButtonProps = {
baseUrl: string;
item: Item & { release? : string }
item: Item & { release?: string };
title?: string;
};
export default function EditButton({baseUrl, item, title}: React.PropsWithChildren<EditButtonProps>) {
export default function EditButton({
baseUrl,
item,
title,
}: React.PropsWithChildren<EditButtonProps>) {
const href = `${baseUrl}/${item.release}/${item.name}.md`;
return (
<a className="icon-link" href={href} target="_blank" rel="noopener noreferrer">{title || 'Edit'}</a>
<a
className="icon-link"
href={href}
target="_blank"
rel="noopener noreferrer"
>
{title || "Edit"}
</a>
);
}

View File

@@ -1,5 +1,6 @@
import React, { useState, useEffect } from "react";
import classNames from "classnames";
import React, { useEffect, useState } from "react";
import "./fadeable.scss";
type FadeableProps = {

View File

@@ -1,12 +1,13 @@
import React from "react";
import classNames from "classnames";
import React from "react";
import { assetUrl, getItemPageNames, isMobileViewport } from "../../config";
import { useMessages } from "../../context/MessagesContext";
import { Item } from "../../model";
import { sanitize } from "../../sanitize";
import Branding from "../Branding/Branding";
import FooterEnd from "../FooterEnd/FooterEnd";
import { assetUrl, getItemPageNames, isMobileViewport } from "../../config";
import { Item } from "../../model";
import "./footer.scss";
import { useMessages } from "../../context/MessagesContext";
import { sanitize } from "../../sanitize";
interface Props {
items: Item[];
@@ -35,7 +36,10 @@ const Footer: React.FC<Props> = ({ items, pageName }) => {
/>
}
>
<div className="footnote" dangerouslySetInnerHTML={sanitize(footerFootnote)}></div>
<div
className="footnote"
dangerouslySetInnerHTML={sanitize(footerFootnote)}
></div>
</Branding>
)}

View File

@@ -1,15 +1,21 @@
import React from "react";
import classNames from "classnames";
import React from "react";
import { useMessages } from "../../context/MessagesContext";
import SocialLink from "../SocialLink/SocialLink";
import "./footerend.scss";
import { useMessages } from "../../context/MessagesContext";
interface Props {
modifier?: "in-sidebar";
}
const FooterEnd: React.FC<Props> = ({ modifier }) => {
const { socialLinksLabel, socialLinks, legalInformationLink, legalInformationLabel } = useMessages();
const {
socialLinksLabel,
socialLinks,
legalInformationLink,
legalInformationLabel,
} = useMessages();
return (
<div
@@ -21,7 +27,7 @@ const FooterEnd: React.FC<Props> = ({ modifier }) => {
{socialLinks && (
<>
<div className="footer-social__label">
<p>{socialLinksLabel ?? 'Follow us:' }</p>
<p>{socialLinksLabel ?? "Follow us:"}</p>
</div>
<div className="footer-social__links">
{socialLinks.map(({ href, iconName }) => (
@@ -40,7 +46,7 @@ const FooterEnd: React.FC<Props> = ({ modifier }) => {
target="_blank"
rel="noopener noreferrer"
>
{legalInformationLabel || 'Legal Information'}
{legalInformationLabel || "Legal Information"}
</a>
</p>
</div>

View File

@@ -1,13 +1,14 @@
import React, { useState, useRef } from "react";
import classNames from "classnames";
import qs from "query-string";
import React, { useRef, useState } from "react";
import { useNavigate } from "react-router-dom";
import { radarNameShort } from "../../config";
import { useMessages } from "../../context/MessagesContext";
import Branding from "../Branding/Branding";
import Link from "../Link/Link";
import LogoLink from "../LogoLink/LogoLink";
import Search from "../Search/Search";
import { radarNameShort } from "../../config";
import { useNavigate } from "react-router-dom";
import qs from "query-string";
import { useMessages } from "../../context/MessagesContext";
export default function Header({ pageName }: { pageName: string }) {
const [searchOpen, setSearchOpen] = useState(false);

View File

@@ -1,5 +1,6 @@
import React from "react";
import classNames from "classnames";
import React from "react";
import "./headline-group.scss";
interface Props {

View File

@@ -1,4 +1,5 @@
import React from "react";
import "./hero-headline.scss";
interface Props {

View File

@@ -1,8 +1,9 @@
import React from "react";
import classNames from "classnames";
import Link from "../Link/Link";
import Flag from "../Flag/Flag";
import React from "react";
import { Item as mItem } from "../../model";
import Flag from "../Flag/Flag";
import Link from "../Link/Link";
import "./item.scss";
type Props = {

View File

@@ -1,6 +1,7 @@
import React from "react";
import { featuredOnly, Item as mItem, nonFeaturedOnly } from "../../model";
import Item from "../Item/Item";
import { featuredOnly, nonFeaturedOnly, Item as mItem } from "../../model";
import "./item-list.scss";
type ItemListProps = {

View File

@@ -1,13 +1,13 @@
import Badge from "../Badge/Badge";
import { formatRelease } from "../../date";
import { Revision } from "../../model";
import Badge from "../Badge/Badge";
export default function ItemRevision({
revision,
dateFormat,
}: {
revision: Revision;
dateFormat?: string
dateFormat?: string;
}) {
return (
<div className="item-revision">

View File

@@ -1,25 +1,31 @@
import { useMessages } from "../../context/MessagesContext";
import { Revision } from "../../model";
import HeadlineGroup from "../HeadlineGroup/HeadlineGroup";
import ItemRevision from "../ItemRevision/ItemRevision";
import { Revision } from "../../model";
import "./item-revisions.scss";
import { useMessages } from "../../context/MessagesContext";
export default function ItemRevisions({
revisions,
dateFormat,
}: {
revisions: Revision[];
dateFormat?: string
dateFormat?: string;
}) {
const { revisionsText } = useMessages();
return (
<div className="item-revisions">
<HeadlineGroup secondary>
<h4 className="headline headline--dark">{revisionsText ?? 'Revisions:'}</h4>
<h4 className="headline headline--dark">
{revisionsText ?? "Revisions:"}
</h4>
</HeadlineGroup>
{revisions.map((revision) => (
<ItemRevision key={revision.release} revision={revision} dateFormat={dateFormat} />
<ItemRevision
key={revision.release}
revision={revision}
dateFormat={dateFormat}
/>
))}
</div>
);

View File

@@ -1,5 +1,6 @@
import React from "react";
import { Link as RLink } from "react-router-dom";
import "./link.scss";
type LinkProps = {

View File

@@ -1,8 +1,10 @@
import React from "react";
import classNames from "classnames";
import Link from "../Link/Link";
import React from "react";
import { assetUrl, radarNameShort } from "../../config";
import Link from "../Link/Link";
import "./logo-link.scss";
export default function LogoLink({ small = false }: { small?: boolean }) {
return (
<Link

View File

@@ -1,10 +1,11 @@
import React from "react";
import HeroHeadline from "../HeroHeadline/HeroHeadline";
import Fadeable from "../Fadeable/Fadeable";
import SetTitle from "../SetTitle";
import { radarName } from "../../config";
import { useMessages } from "../../context/MessagesContext";
import { sanitize } from "../../sanitize";
import Fadeable from "../Fadeable/Fadeable";
import HeroHeadline from "../HeroHeadline/HeroHeadline";
import SetTitle from "../SetTitle";
interface Props {
leaving: boolean;
@@ -15,8 +16,16 @@ const PageHelp: React.FC<Props> = ({ leaving, onLeave }) => {
const { pageHelp } = useMessages();
if (pageHelp) {
const { paragraphs, quadrants, rings, sourcecodeLink, headlinePrefix, quadrantsPreDescription, ringsPreDescription } = pageHelp;
const title = `${headlinePrefix || 'How to use the'} ${radarName}`;
const {
paragraphs,
quadrants,
rings,
sourcecodeLink,
headlinePrefix,
quadrantsPreDescription,
ringsPreDescription,
} = pageHelp;
const title = `${headlinePrefix || "How to use the"} ${radarName}`;
return (
<Fadeable leaving={leaving} onLeave={onLeave}>
<SetTitle title={title} />
@@ -27,10 +36,12 @@ const PageHelp: React.FC<Props> = ({ leaving, onLeave }) => {
<h3>{headline}</h3>
{values.map((element, index) => {
return (
<p key={index} dangerouslySetInnerHTML={sanitize(element)}></p>
)
})
}
<p
key={index}
dangerouslySetInnerHTML={sanitize(element)}
></p>
);
})}
</React.Fragment>
))}
@@ -38,16 +49,25 @@ const PageHelp: React.FC<Props> = ({ leaving, onLeave }) => {
<ul>
{quadrants.map(({ name, description }) => (
<li key={name}>
<strong>{name}:</strong> <span dangerouslySetInnerHTML={sanitize(description, {})}></span>
<strong>{name}:</strong>{" "}
<span
dangerouslySetInnerHTML={sanitize(description, {})}
></span>
</li>
))}
</ul>
<p>{ringsPreDescription ?? "Each of the items is classified in one of these rings:"}</p>
<p>
{ringsPreDescription ??
"Each of the items is classified in one of these rings:"}
</p>
<ul>
{rings.map(({ name, description }) => (
<li key={name}>
<strong>{name}:</strong> <span dangerouslySetInnerHTML={sanitize(description, {})}></span>
<strong>{name}:</strong>{" "}
<span
dangerouslySetInnerHTML={sanitize(description, {})}
></span>
</li>
))}
</ul>

View File

@@ -1,13 +1,14 @@
import { MomentInput } from "moment";
import { ConfigData, radarName, radarNameShort } from "../../config";
import { useMessages } from "../../context/MessagesContext";
import { formatRelease } from "../../date";
import { featuredOnly, Item, HomepageOption } from "../../model";
import { HomepageOption, Item, featuredOnly } from "../../model";
import Fadeable from "../Fadeable/Fadeable";
import HeroHeadline from "../HeroHeadline/HeroHeadline";
import QuadrantGrid from "../QuadrantGrid/QuadrantGrid";
import RadarGrid from '../RadarGrid/RadarGrid';
import Fadeable from "../Fadeable/Fadeable";
import RadarGrid from "../RadarGrid/RadarGrid";
import SetTitle from "../SetTitle";
import { ConfigData, radarName, radarNameShort } from "../../config";
import { MomentInput } from "moment";
import { useMessages } from "../../context/MessagesContext";
type PageIndexProps = {
leaving: boolean;
@@ -25,12 +26,16 @@ export default function PageIndex({
releases,
}: PageIndexProps) {
const { pageIndex } = useMessages();
const publishedLabel = pageIndex?.publishedLabel || 'Published';
const publishedLabel = pageIndex?.publishedLabel || "Published";
const newestRelease = releases.slice(-1)[0];
const numberOfReleases = releases.length;
const showChart = config.homepageContent === HomepageOption.chart || config.homepageContent === HomepageOption.both;
const showColumns = config.homepageContent === HomepageOption.columns || config.homepageContent === HomepageOption.both;
const showChart =
config.homepageContent === HomepageOption.chart ||
config.homepageContent === HomepageOption.both;
const showColumns =
config.homepageContent === HomepageOption.columns ||
config.homepageContent === HomepageOption.both;
return (
<Fadeable leaving={leaving} onLeave={onLeave}>
<SetTitle title={radarNameShort} />
@@ -39,9 +44,7 @@ export default function PageIndex({
{radarName}
</HeroHeadline>
</div>
{showChart && (
<RadarGrid items={featuredOnly(items)} config={config} />
)}
{showChart && <RadarGrid items={featuredOnly(items)} config={config} />}
{showColumns && (
<QuadrantGrid items={featuredOnly(items)} config={config} />
)}

View File

@@ -1,19 +1,17 @@
import React from "react";
import { ConfigData, translate } from "../../config";
import { useMessages } from "../../context/MessagesContext";
import { Item, groupByQuadrants } from "../../model";
import Badge from "../Badge/Badge";
import EditButton from "../EditButton/EditButton";
import ItemList from "../ItemList/ItemList";
import Link from "../Link/Link";
import FooterEnd from "../FooterEnd/FooterEnd";
import SetTitle from "../SetTitle";
import ItemList from "../ItemList/ItemList";
import ItemRevisions from "../ItemRevisions/ItemRevisions";
import { useAnimations } from "./useAnimations";
import Link from "../Link/Link";
import SetTitle from "../SetTitle";
import "./item-page.scss";
import { ConfigData, translate } from "../../config";
import {
groupByQuadrants,
Item,
} from "../../model";
import { useMessages } from "../../context/MessagesContext";
import { useAnimations } from "./useAnimations";
const getItem = (pageName: string, items: Item[]) => {
const [quadrantName, itemName] = pageName.split("/");
@@ -35,9 +33,15 @@ type Props = {
onLeave: () => void;
};
const PageItem: React.FC<Props> = ({ pageName, items, config, leaving, onLeave }) => {
const PageItem: React.FC<Props> = ({
pageName,
items,
config,
leaving,
onLeave,
}) => {
const { pageItem } = useMessages();
const quadrantOverview = pageItem?.quadrantOverview || 'Quadrant Overview';
const quadrantOverview = pageItem?.quadrantOverview || "Quadrant Overview";
const itemsInRing = getItemsInRing(pageName, items);
@@ -48,7 +52,13 @@ const PageItem: React.FC<Props> = ({ pageName, items, config, leaving, onLeave }
});
const item = getItem(pageName, items);
const editButton = config.editLink ? <EditButton baseUrl={config.editLink.radarLink} item={item} title={config.editLink.title}/> : null
const editButton = config.editLink ? (
<EditButton
baseUrl={config.editLink.radarLink}
item={item}
title={config.editLink.title}
/>
) : null;
return (
<>
@@ -119,7 +129,10 @@ const PageItem: React.FC<Props> = ({ pageName, items, config, leaving, onLeave }
dangerouslySetInnerHTML={{ __html: item.body }}
/>
{item.revisions.length > 1 && (
<ItemRevisions revisions={item.revisions.slice(1)} dateFormat={config.dateFormat} />
<ItemRevisions
revisions={item.revisions.slice(1)}
dateFormat={config.dateFormat}
/>
)}
</div>
</div>

View File

@@ -1,4 +1,5 @@
import { useEffect, useState, useMemo } from "react";
import { useEffect, useMemo, useState } from "react";
import {
Animation,
AnimationStates,
@@ -13,18 +14,14 @@ interface Props {
onLeave: () => void;
}
export const useAnimations = ({
itemsInRing,
leaving,
onLeave,
}: Props) => {
export const useAnimations = ({ itemsInRing, leaving, onLeave }: Props) => {
type AnimationConfig = {
background: Animation;
navHeader: Animation;
text: Animation;
items: Animation[];
footer: Animation;
}
};
type AnimationNames = keyof AnimationConfig;

View File

@@ -1,13 +1,12 @@
import { ConfigData, translate } from "../../config";
import { Item, groupByQuadrants } from "../../model";
import Badge from "../Badge/Badge";
import EditButton from "../EditButton/EditButton";
import ItemList from "../ItemList/ItemList";
import Link from "../Link/Link";
import Fadeable from "../Fadeable/Fadeable";
import SetTitle from "../SetTitle";
import ItemList from "../ItemList/ItemList";
import ItemRevisions from "../ItemRevisions/ItemRevisions";
import { ConfigData, translate } from "../../config";
import { groupByQuadrants, Item } from "../../model";
import Link from "../Link/Link";
import SetTitle from "../SetTitle";
type PageItemMobileProps = {
pageName: string;
@@ -40,7 +39,13 @@ export default function PageItemMobile({
const item = getItem(pageName, items);
const itemsInRing = getItemsInRing(pageName, items);
const editButton = config.editLink ? <EditButton baseUrl={config.editLink.radarLink} item={item} title={config.editLink.title}/> : null
const editButton = config.editLink ? (
<EditButton
baseUrl={config.editLink.radarLink}
item={item}
title={config.editLink.title}
/>
) : null;
return (
<Fadeable leaving={leaving} onLeave={onLeave}>
<SetTitle title={item.title} />
@@ -50,7 +55,9 @@ export default function PageItemMobile({
<div className="mobile-item-page__header">
<div className="split">
<div className="split__left">
<h3 className="headline">{translate(config, item.quadrant)}</h3>
<h3 className="headline">
{translate(config, item.quadrant)}
</h3>
<h1 className="hero-headline hero-headline--inverse">
{item.title}
</h1>

View File

@@ -1,15 +1,16 @@
import { useState, useEffect } from "react";
import HeadlineGroup from "../HeadlineGroup/HeadlineGroup";
import HeroHeadline from "../HeroHeadline/HeroHeadline";
import Badge from "../Badge/Badge";
import Link from "../Link/Link";
import Search from "../Search/Search";
import Fadeable from "../Fadeable/Fadeable";
import SetTitle from "../SetTitle";
import Flag from "../Flag/Flag";
import { groupByFirstLetter, Item } from "../../model";
import { useEffect, useState } from "react";
import { ConfigData, translate } from "../../config";
import { useMessages } from "../../context/MessagesContext";
import { Item, groupByFirstLetter } from "../../model";
import Badge from "../Badge/Badge";
import Fadeable from "../Fadeable/Fadeable";
import Flag from "../Flag/Flag";
import HeadlineGroup from "../HeadlineGroup/HeadlineGroup";
import HeroHeadline from "../HeroHeadline/HeroHeadline";
import Link from "../Link/Link";
import Search from "../Search/Search";
import SetTitle from "../SetTitle";
const containsSearchTerm = (text = "", term = "") => {
// TODO search refinement

View File

@@ -1,12 +1,12 @@
import React from "react";
import HeroHeadline from "../HeroHeadline/HeroHeadline";
import HeadlineGroup from "../HeadlineGroup/HeadlineGroup";
import QuadrantSection from "../QuadrantSection/QuadrantSection";
import Fadeable from "../Fadeable/Fadeable";
import SetTitle from "../SetTitle";
import { ConfigData, translate } from "../../config";
import { featuredOnly, groupByQuadrants, Item } from "../../model";
import { Item, featuredOnly, groupByQuadrants } from "../../model";
import Fadeable from "../Fadeable/Fadeable";
import HeadlineGroup from "../HeadlineGroup/HeadlineGroup";
import HeroHeadline from "../HeroHeadline/HeroHeadline";
import QuadrantSection from "../QuadrantSection/QuadrantSection";
import SetTitle from "../SetTitle";
type PageQuadrantProps = {
leaving: boolean;

View File

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

View File

@@ -1,10 +1,11 @@
import { translate, ConfigData } from "../../config";
import Badge from "../Badge/Badge";
import Link from "../Link/Link";
import ItemList from "../ItemList/ItemList";
import Flag from "../Flag/Flag";
import { ConfigData, translate } from "../../config";
import { Group } from "../../model";
import Badge from "../Badge/Badge";
import Flag from "../Flag/Flag";
import ItemList from "../ItemList/ItemList";
import Link from "../Link/Link";
import "./quadrant-section.scss";
const renderList = (
ringName: string,
quadrantName: string,

View File

@@ -1,10 +1,10 @@
import React from "react";
import RadarChart from "../Chart/RadarChart";
import { ConfigData } from "../../config";
import { Item, QuadrantConfig } from "../../model";
import "./radar-grid.scss";
import RadarChart from "../Chart/RadarChart";
import Link from "../Link/Link";
import "./radar-grid.scss";
const QuadrantLabel: React.FC<{
quadrantConfig: QuadrantConfig;
@@ -19,7 +19,10 @@ const QuadrantLabel: React.FC<{
];
return (
<div className="quadrant-label" style={stylesMap[quadrantConfig.position - 1]}>
<div
className="quadrant-label"
style={stylesMap[quadrantConfig.position - 1]}
>
<div className="split">
<div className="split__left">
<small>Quadrant {quadrantConfig.position}</small>
@@ -65,7 +68,12 @@ const RadarGrid: React.FC<{ items: Item[]; config: ConfigData }> = ({
<div className="radar-grid">
<RadarChart items={items} config={config} />
{Object.entries(config.quadrantsMap).map(([name, quadrant], index) => (
<QuadrantLabel key={index} quadrantConfig={quadrant} quadrantName={name} quadrantLabel={config.quadrants[name]} />
<QuadrantLabel
key={index}
quadrantConfig={quadrant}
quadrantName={name}
quadrantLabel={config.quadrants[name]}
/>
))}
<Legend />
</div>

View File

@@ -1,13 +1,13 @@
import { useState, useEffect } from "react";
import { useEffect, useState } from "react";
import PageIndex from "./PageIndex/PageIndex";
import PageOverview from "./PageOverview/PageOverview";
import PageHelp from "./PageHelp/PageHelp";
import PageQuadrant from "./PageQuadrant/PageQuadrant";
import PageItem from "./PageItem/PageItem";
import PageItemMobile from "./PageItemMobile/PageItemMobile";
import { ConfigData, getItemPageNames, isMobileViewport } from "../config";
import { Item } from "../model";
import PageHelp from "./PageHelp/PageHelp";
import PageIndex from "./PageIndex/PageIndex";
import PageItem from "./PageItem/PageItem";
import PageItemMobile from "./PageItemMobile/PageItemMobile";
import PageOverview from "./PageOverview/PageOverview";
import PageQuadrant from "./PageQuadrant/PageQuadrant";
type RouterProps = {
pageName: string;

View File

@@ -1,6 +1,7 @@
import React, { FormEvent } from "react";
import { useMessages } from "../../context/MessagesContext";
import classNames from "classnames";
import React, { FormEvent } from "react";
import { useMessages } from "../../context/MessagesContext";
import "./search.scss";
type SearchProps = {

View File

@@ -1,4 +1,5 @@
import { useEffect } from "react";
import { radarName } from "../config";
type SetTitleProps = {

View File

@@ -1,13 +1,13 @@
import React from "react";
import {
FaExternalLinkAlt,
FaFacebookF,
FaTwitter,
FaLinkedinIn,
FaXing,
FaYoutube,
FaGithub,
FaInstagram,
FaExternalLinkAlt
FaLinkedinIn,
FaTwitter,
FaXing,
FaYoutube,
} from "react-icons/fa";
const icons = {

View File

@@ -1,4 +1,4 @@
import {Item, HomepageOption, QuadrantConfig} from './model';
import { HomepageOption, Item, QuadrantConfig } from "./model";
export interface ConfigData {
tags?: string[];
@@ -7,17 +7,17 @@ export interface ConfigData {
showEmptyRings: boolean;
quadrantsMap: { [quadrant: string]: QuadrantConfig };
chartConfig: {
size: number,
scale: number[],
blipSize: number,
ringsAttributes: {radius: number, arcWidth: number}[]
size: number;
scale: number[];
blipSize: number;
ringsAttributes: { radius: number; arcWidth: number }[];
};
homepageContent: HomepageOption;
dateFormat?: string;
editLink?: {
radarLink: string,
title?: string
}
radarLink: string;
title?: string;
};
}
export const radarName =

View File

@@ -1,4 +1,5 @@
import { createContext, FC, useContext } from "react";
import { FC, createContext, useContext } from "react";
import { Props as SocialLink } from "../../components/SocialLink/SocialLink";
interface Quadrant {

View File

@@ -1,11 +1,14 @@
import moment from "moment";
import { formatRelease } from "./date";
describe("formatRelease", () => {
it("should format a date object using default output format", () => {
expect(formatRelease(moment('2022-01-05'))).toEqual('January 2022')
expect(formatRelease(moment("2022-01-05"))).toEqual("January 2022");
});
it("should format a date object using a custom output format", () => {
expect(formatRelease(moment('2022-01-05'), 'DD.MM.YYYY')).toEqual('05.01.2022')
expect(formatRelease(moment("2022-01-05"), "DD.MM.YYYY")).toEqual(
"05.01.2022"
);
});
});

View File

@@ -3,5 +3,7 @@ import moment from "moment";
const isoDateToMoment = (isoDate: moment.MomentInput) =>
moment(isoDate, "YYYY-MM-DD");
export const formatRelease = (isoDate: moment.MomentInput, format: string = "MMMM YYYY") =>
isoDateToMoment(isoDate).format(format);
export const formatRelease = (
isoDate: moment.MomentInput,
format: string = "MMMM YYYY"
) => isoDateToMoment(isoDate).format(format);

View File

@@ -1,6 +1,7 @@
import React from "react";
import App from "./components/App";
import { createRoot } from "react-dom/client";
import App from "./components/App";
import "./index.scss";
const container = document.getElementById("root")!;

View File

@@ -1,7 +1,7 @@
export enum HomepageOption {
chart = "chart",
columns = "columns",
both = "both"
both = "both",
}
export type ItemAttributes = {
@@ -14,9 +14,9 @@ export type ItemAttributes = {
};
export enum FlagType {
new = 'new',
changed = 'changed',
default = 'default'
new = "new",
changed = "changed",
default = "default",
}
export type Item = ItemAttributes & {
@@ -28,12 +28,12 @@ export type Item = ItemAttributes & {
};
export type Blip = Item & {
quadrantPosition: number
ringPosition: number
colour: string
txtColour: string
coordinates: Point
}
quadrantPosition: number;
ringPosition: number;
colour: string;
txtColour: string;
coordinates: Point;
};
export type Revision = ItemAttributes & {
body: string;
@@ -46,11 +46,11 @@ export type Quadrant = {
};
export type QuadrantConfig = {
colour: string,
txtColour: string,
position: number,
description: string
}
colour: string;
txtColour: string;
position: number;
description: string;
};
export type Radar = {
items: Item[];
@@ -67,9 +67,9 @@ export const nonFeaturedOnly = (items: Item[]) =>
items.filter((item) => !item.featured);
export type Point = {
x: number,
y: number
}
x: number;
y: number;
};
export const groupByQuadrants = (items: Item[]): Group =>
items.reduce(

View File

@@ -2,17 +2,22 @@ import { sanitize } from "./sanitize";
describe("sanitize", () => {
it("should sanitize the string input to HTML output", () => {
let res = sanitize('foo');
let res = sanitize("foo");
expect(res.__html).toEqual("foo");
res = sanitize('<a href="https://example.org">Example.org</a>');
expect(res.__html).toEqual("<a href=\"https://example.org\">Example.org</a>");
expect(res.__html).toEqual('<a href="https://example.org">Example.org</a>');
});
it("should not sanitize not allowed tags", () => {
let res = sanitize('Before <iframe src="https://example.org"></iframe> After');
let res = sanitize(
'Before <iframe src="https://example.org"></iframe> After'
);
expect(res.__html).toEqual("Before After");
});
it("should accept options for rendering", () => {
let res = sanitize('<a href="https://example.org" target="_blank">Example.org</a>', { allowedAttributes: { a: ['href']}});
expect(res.__html).toEqual("<a href=\"https://example.org\">Example.org</a>");
let res = sanitize(
'<a href="https://example.org" target="_blank">Example.org</a>',
{ allowedAttributes: { a: ["href"] } }
);
expect(res.__html).toEqual('<a href="https://example.org">Example.org</a>');
});
});

View File

@@ -1,15 +1,15 @@
import sanitizeHtml from 'sanitize-html';
import sanitizeHtml from "sanitize-html";
const defaultSanitizeOptions = {
allowedTags: ['b', 'i', 'em', 'strong', 'a', 'ul', 'ol', 'li'],
allowedTags: ["b", "i", "em", "strong", "a", "ul", "ol", "li"],
allowedAttributes: {
'a': ['href', 'target']
}
}
a: ["href", "target"],
},
};
export const sanitize = (dirty: string, options: sanitizeHtml.IOptions = defaultSanitizeOptions) => ({
__html: sanitizeHtml(
dirty,
options
)
export const sanitize = (
dirty: string,
options: sanitizeHtml.IOptions = defaultSanitizeOptions
) => ({
__html: sanitizeHtml(dirty, options),
});