chore(codestyle): cleanup and reformat
This commit is contained in:
@@ -1,3 +1,3 @@
|
||||
{
|
||||
"extends": ["react-app", "react-app/jest"]
|
||||
"extends": ["react-app", "react-app/jest", "prettier"]
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
389
package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
module.exports = {
|
||||
importOrder: ["^[./]"],
|
||||
importOrderSeparation: true,
|
||||
importOrderSortSpecifiers: true,
|
||||
};
|
||||
|
||||
@@ -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.`);
|
||||
});
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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,31 +68,29 @@ 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);
|
||||
let html = marked(fm.body.replace(/\]\(\//g, `](${publicUrl}/`));
|
||||
html = html.replace(
|
||||
/a href="http/g,
|
||||
'a target="_blank" rel="noopener noreferrer" href="http'
|
||||
);
|
||||
const attributes = checkAttributes(fileName, fm.attributes);
|
||||
if (attributes) {
|
||||
return {
|
||||
...itemInfoFromFilename(fileName),
|
||||
...attributes,
|
||||
fileName,
|
||||
body: html,
|
||||
} as Revision;
|
||||
}
|
||||
})
|
||||
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,
|
||||
'a target="_blank" rel="noopener noreferrer" href="http'
|
||||
);
|
||||
const attributes = checkAttributes(fileName, fm.attributes);
|
||||
if (attributes) {
|
||||
return {
|
||||
...itemInfoFromFilename(fileName),
|
||||
...attributes,
|
||||
fileName,
|
||||
body: html,
|
||||
} as Revision;
|
||||
}
|
||||
})
|
||||
)
|
||||
);
|
||||
};
|
||||
@@ -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[]) =>
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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 = ({
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React, { MouseEventHandler } from "react";
|
||||
import classNames from "classnames";
|
||||
import React, { MouseEventHandler } from "react";
|
||||
|
||||
import "./badge.scss";
|
||||
|
||||
type BadgeProps = {
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -1,46 +1,44 @@
|
||||
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);
|
||||
|
||||
const ref = useRef<SVGSVGElement>(null);
|
||||
useLayoutEffect(() => {
|
||||
if (ref.current == null) {
|
||||
return;
|
||||
}
|
||||
const axisGenerator = d3.axisLeft(scale).ticks(6);
|
||||
d3.select(ref.current)
|
||||
.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());
|
||||
}, [scale]);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (ref.current == null) {
|
||||
return
|
||||
}
|
||||
const axisGenerator = d3.axisLeft(scale).ticks(6);
|
||||
d3.select(ref.current)
|
||||
.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());
|
||||
}, [scale]);
|
||||
|
||||
return <g ref={ref} />;
|
||||
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);
|
||||
|
||||
const ref = useRef<SVGSVGElement>(null);
|
||||
useLayoutEffect(() => {
|
||||
if (ref.current == null) {
|
||||
return;
|
||||
}
|
||||
const axisGenerator = d3.axisBottom(scale).ticks(6);
|
||||
d3.select(ref.current)
|
||||
.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());
|
||||
}, [scale]);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (ref.current == null) {
|
||||
return
|
||||
}
|
||||
const axisGenerator = d3.axisBottom(scale).ticks(6);
|
||||
d3.select(ref.current)
|
||||
.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());
|
||||
}, [scale]);
|
||||
|
||||
return <g ref={ref} />;
|
||||
return <g ref={ref} />;
|
||||
};
|
||||
|
||||
@@ -1,126 +1,147 @@
|
||||
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 {
|
||||
const pi = Math.PI,
|
||||
ringRadius = config.chartConfig.ringsAttributes[blip.ringPosition].radius,
|
||||
previousRingRadius = blip.ringPosition === 0 ? 0 : config.chartConfig.ringsAttributes[blip.ringPosition - 1].radius,
|
||||
ringPadding = 0.7;
|
||||
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,
|
||||
ringPadding = 0.7;
|
||||
|
||||
// radian between 0 and 90 degrees
|
||||
const randomDegree = ((Math.random() * 90) * pi) / 180;
|
||||
// random distance from the centre of the radar, but within given ring. Also, with some "padding" so the points don't touch ring borders.
|
||||
const radius = randomBetween(previousRingRadius + ringPadding, ringRadius - ringPadding);
|
||||
/*
|
||||
// radian between 0 and 90 degrees
|
||||
const randomDegree = (Math.random() * 90 * pi) / 180;
|
||||
// random distance from the centre of the radar, but within given ring. Also, with some "padding" so the points don't touch ring borders.
|
||||
const radius = randomBetween(
|
||||
previousRingRadius + ringPadding,
|
||||
ringRadius - ringPadding
|
||||
);
|
||||
/*
|
||||
Multiples of PI/2. To apply the calculated position to the specific quadrant.
|
||||
Order here is counter-clockwise, 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)
|
||||
};
|
||||
};
|
||||
return {
|
||||
x: xScale(Math.cos(randomDegree + shift) * radius),
|
||||
y: yScale(Math.sin(randomDegree + shift) * radius),
|
||||
};
|
||||
}
|
||||
|
||||
function randomBetween (min: number, max: number): number {
|
||||
return Math.random() * (max - min) + min;
|
||||
};
|
||||
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));
|
||||
};
|
||||
const a = point2.x - point1.x;
|
||||
const b = point2.y - point1.y;
|
||||
return Math.sqrt(a * a + b * b);
|
||||
}
|
||||
|
||||
function renderBlip(blip: Blip, index: number, config: ConfigData): JSX.Element {
|
||||
const props = {
|
||||
blip,
|
||||
className: 'blip',
|
||||
fill: blip.colour,
|
||||
'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} />;
|
||||
case FlagType.changed:
|
||||
return <ChangedBlip {...props} config={config} />;
|
||||
default:
|
||||
return <DefaultBlip {...props} config={config} />;
|
||||
}
|
||||
};
|
||||
function renderBlip(
|
||||
blip: Blip,
|
||||
index: number,
|
||||
config: ConfigData
|
||||
): JSX.Element {
|
||||
const props = {
|
||||
blip,
|
||||
className: "blip",
|
||||
fill: blip.colour,
|
||||
"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} />;
|
||||
case FlagType.changed:
|
||||
return <ChangedBlip {...props} config={config} />;
|
||||
default:
|
||||
return <DefaultBlip {...props} config={config} />;
|
||||
}
|
||||
}
|
||||
|
||||
const BlipPoints: React.FC<{
|
||||
items: Item[]
|
||||
xScale:ScaleLinear<number, number>
|
||||
yScale:ScaleLinear<number, number>
|
||||
config:ConfigData
|
||||
}> = ({items, xScale, yScale, config}) => {
|
||||
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
|
||||
return list;
|
||||
}
|
||||
const quadrantConfig = config.quadrantsMap[item.quadrant];
|
||||
if (!quadrantConfig) {
|
||||
return list;
|
||||
}
|
||||
|
||||
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
|
||||
return list;
|
||||
}
|
||||
const quadrantConfig = config.quadrantsMap[item.quadrant];
|
||||
if (!quadrantConfig) {
|
||||
return list;
|
||||
}
|
||||
let blip: Blip = {
|
||||
...item,
|
||||
quadrantPosition: quadrantConfig.position,
|
||||
ringPosition: config.rings.findIndex((r) => r === item.ring),
|
||||
colour: quadrantConfig.colour,
|
||||
txtColour: quadrantConfig.txtColour,
|
||||
coordinates: { x: 0, y: 0 },
|
||||
};
|
||||
|
||||
let blip: Blip = { ...item,
|
||||
quadrantPosition: quadrantConfig.position,
|
||||
ringPosition: config.rings.findIndex(r => r === item.ring),
|
||||
colour: quadrantConfig.colour,
|
||||
txtColour: quadrantConfig.txtColour,
|
||||
coordinates: {x: 0, y: 0},
|
||||
};
|
||||
|
||||
let point: Point;
|
||||
let counter = 1;
|
||||
let distanceBetweenCheck: boolean;
|
||||
do {
|
||||
const localpoint = generateCoordinates(blip, xScale, yScale, config);
|
||||
point = localpoint
|
||||
counter++;
|
||||
/*
|
||||
let point: Point;
|
||||
let counter = 1;
|
||||
let distanceBetweenCheck: boolean;
|
||||
do {
|
||||
const localpoint = generateCoordinates(blip, xScale, yScale, config);
|
||||
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
|
||||
));
|
||||
|
||||
blip.coordinates = point;
|
||||
|
||||
list.push(blip);
|
||||
return list;
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<g className="blips">
|
||||
{blips.map((blip, index) => (
|
||||
<Link pageName={`${blip.quadrant}/${blip.name}`} key={index}>
|
||||
{renderBlip(blip, index, config)}
|
||||
</Link>
|
||||
))}
|
||||
</g>
|
||||
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;
|
||||
|
||||
list.push(blip);
|
||||
return list;
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<g className="blips">
|
||||
{blips.map((blip, index) => (
|
||||
<Link pageName={`${blip.quadrant}/${blip.name}`} key={index}>
|
||||
{renderBlip(blip, index, config)}
|
||||
</Link>
|
||||
))}
|
||||
</g>
|
||||
);
|
||||
};
|
||||
|
||||
export default BlipPoints;
|
||||
@@ -1,64 +1,65 @@
|
||||
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, config, ...props}) => {
|
||||
const centeredX = blip.coordinates.x - config.chartConfig.blipSize/2,
|
||||
centeredY = blip.coordinates.y - config.chartConfig.blipSize/2;
|
||||
{ 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;
|
||||
|
||||
return (
|
||||
<rect
|
||||
transform={`rotate(-45 ${centeredX} ${centeredY})`}
|
||||
x={centeredX}
|
||||
y={centeredY}
|
||||
width={config.chartConfig.blipSize}
|
||||
height={config.chartConfig.blipSize}
|
||||
rx="3"
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
return (
|
||||
<rect
|
||||
transform={`rotate(-45 ${centeredX} ${centeredY})`}
|
||||
x={centeredX}
|
||||
y={centeredY}
|
||||
width={config.chartConfig.blipSize}
|
||||
height={config.chartConfig.blipSize}
|
||||
rx="3"
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const NewBlip: React.FC<
|
||||
{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;
|
||||
{ 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;
|
||||
|
||||
/*
|
||||
/*
|
||||
The below is a predefined path of a triangle with rounded corners.
|
||||
I didn't find any more human friendly way of doing this as all examples I found have tons of lines of code
|
||||
e.g. https://observablehq.com/@perlmonger42/interactive-rounded-corners-on-svg-polygons-using-d3-js
|
||||
*/
|
||||
return (
|
||||
<path
|
||||
transform={`translate(${centeredX}, ${centeredY})`}
|
||||
d="M.247 10.212l5.02-8.697a2 2 0 013.465 0l5.021 8.697a2 2 0 01-1.732 3H1.98a2 2 0 01-1.732-3z"
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
return (
|
||||
<path
|
||||
transform={`translate(${centeredX}, ${centeredY})`}
|
||||
d="M.247 10.212l5.02-8.697a2 2 0 013.465 0l5.021 8.697a2 2 0 01-1.732 3H1.98a2 2 0 01-1.732-3z"
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const DefaultBlip: React.FC<
|
||||
{blip: Blip, config: ConfigData} & VisualBlipProps
|
||||
> = ({blip, config, ...props}) => {
|
||||
return (
|
||||
<circle
|
||||
r={config.chartConfig.blipSize / 2}
|
||||
cx={blip.coordinates.x}
|
||||
cy={blip.coordinates.y}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
{ blip: Blip; config: ConfigData } & VisualBlipProps
|
||||
> = ({ blip, config, ...props }) => {
|
||||
return (
|
||||
<circle
|
||||
r={config.chartConfig.blipSize / 2}
|
||||
cx={blip.coordinates.x}
|
||||
cy={blip.coordinates.y}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -1,74 +1,92 @@
|
||||
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
|
||||
const arcAttrs = config.chartConfig.ringsAttributes[ringPosition],
|
||||
ringRadiusPx = xScale(arcAttrs.radius) - xScale(0),
|
||||
arc = d3.arc();
|
||||
import { ConfigData } from "../../config";
|
||||
import { QuadrantConfig } from "../../model";
|
||||
|
||||
return arc({
|
||||
innerRadius: ringRadiusPx - arcAttrs.arcWidth,
|
||||
outerRadius: ringRadiusPx,
|
||||
startAngle,
|
||||
endAngle
|
||||
}) || undefined;
|
||||
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({
|
||||
innerRadius: ringRadiusPx - arcAttrs.arcWidth,
|
||||
outerRadius: ringRadiusPx,
|
||||
startAngle,
|
||||
endAngle,
|
||||
}) || undefined
|
||||
);
|
||||
}
|
||||
|
||||
const QuadrantRings: React.FC<{
|
||||
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}
|
||||
];
|
||||
const gradientId = `${quadrant.position}-radial-gradient`,
|
||||
quadrantSize = config.chartConfig.size / 2;
|
||||
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 },
|
||||
];
|
||||
const gradientId = `${quadrant.position}-radial-gradient`,
|
||||
quadrantSize = config.chartConfig.size / 2;
|
||||
|
||||
return (
|
||||
<g className="quadrant-ring">
|
||||
{/* Definition of the quadrant gradient */}
|
||||
<defs>
|
||||
<radialGradient id={gradientId} {...gradientAttributes[quadrant.position - 1]}>
|
||||
<stop offset="0%" stopColor={quadrant.colour}></stop>
|
||||
<stop offset="100%" stopColor={quadrant.colour} stopOpacity="0"></stop>
|
||||
</radialGradient>
|
||||
</defs>
|
||||
return (
|
||||
<g className="quadrant-ring">
|
||||
{/* Definition of the quadrant gradient */}
|
||||
<defs>
|
||||
<radialGradient
|
||||
id={gradientId}
|
||||
{...gradientAttributes[quadrant.position - 1]}
|
||||
>
|
||||
<stop offset="0%" stopColor={quadrant.colour}></stop>
|
||||
<stop
|
||||
offset="100%"
|
||||
stopColor={quadrant.colour}
|
||||
stopOpacity="0"
|
||||
></stop>
|
||||
</radialGradient>
|
||||
</defs>
|
||||
|
||||
{/* Gradient background area */}
|
||||
<rect
|
||||
width={quadrantSize}
|
||||
height={quadrantSize}
|
||||
x={gradientAttributes[quadrant.position - 1].x}
|
||||
y={gradientAttributes[quadrant.position - 1].y}
|
||||
fill={`url(#${gradientId})`}
|
||||
style={{opacity: 0.5}}
|
||||
{/* Gradient background area */}
|
||||
<rect
|
||||
width={quadrantSize}
|
||||
height={quadrantSize}
|
||||
x={gradientAttributes[quadrant.position - 1].x}
|
||||
y={gradientAttributes[quadrant.position - 1].y}
|
||||
fill={`url(#${gradientId})`}
|
||||
style={{ opacity: 0.5 }}
|
||||
/>
|
||||
|
||||
{/* Rings' arcs */}
|
||||
{Array.from(config.rings).map((ringPosition, index) => (
|
||||
<path
|
||||
key={index}
|
||||
fill={quadrant.colour}
|
||||
d={arcPath(quadrant.position, index, xScale, config)}
|
||||
style={{
|
||||
transform: `translate(${quadrantSize}px, ${quadrantSize}px)`,
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</g>
|
||||
);
|
||||
};
|
||||
|
||||
{/* Rings' arcs */}
|
||||
{Array.from(config.rings).map((ringPosition, index) => (
|
||||
<path
|
||||
key={index}
|
||||
fill={quadrant.colour}
|
||||
d={arcPath(quadrant.position, index, xScale, config)}
|
||||
style={{transform: `translate(${quadrantSize}px, ${quadrantSize}px)`}}
|
||||
/>
|
||||
))}
|
||||
|
||||
</g>
|
||||
);
|
||||
}
|
||||
|
||||
export default QuadrantRings;
|
||||
export default QuadrantRings;
|
||||
|
||||
@@ -1,77 +1,109 @@
|
||||
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, xScale, yScale, config}) => {
|
||||
const ringIndex = config.rings.findIndex(r => r === ring)
|
||||
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 ringRadius = config.chartConfig.ringsAttributes[ringIndex].radius,
|
||||
previousRingRadius = ringIndex === 0 ? 0 : config.chartConfig.ringsAttributes[ringIndex - 1].radius,
|
||||
const ringRadius = config.chartConfig.ringsAttributes[ringIndex].radius,
|
||||
previousRingRadius =
|
||||
ringIndex === 0
|
||||
? 0
|
||||
: config.chartConfig.ringsAttributes[ringIndex - 1].radius,
|
||||
// middle point in between two ring arcs
|
||||
distanceFromCentre =
|
||||
previousRingRadius + (ringRadius - previousRingRadius) / 2;
|
||||
|
||||
// middle point in between two ring arcs
|
||||
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">
|
||||
{ring}
|
||||
</text>
|
||||
{/* Left hand-side label */}
|
||||
<text x={xScale(-distanceFromCentre)} y={yScale(0)} textAnchor="middle" dy=".35em">
|
||||
{ring}
|
||||
</text>
|
||||
</g>
|
||||
);
|
||||
return (
|
||||
<g className="ring-label">
|
||||
{/* Right hand-side label */}
|
||||
<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"
|
||||
>
|
||||
{ring}
|
||||
</text>
|
||||
</g>
|
||||
);
|
||||
};
|
||||
|
||||
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}`}>
|
||||
<g transform={`translate(${xScale(0)}, 0)`}>
|
||||
<YAxis scale={yScale}/>
|
||||
</g>
|
||||
<g transform={`translate(0, ${yScale(0)})`}>
|
||||
<XAxis scale={xScale}/>
|
||||
</g>
|
||||
<div className="chart" style={{ maxWidth: `${config.chartConfig.size}px` }}>
|
||||
<svg
|
||||
viewBox={`0 0 ${config.chartConfig.size} ${config.chartConfig.size}`}
|
||||
>
|
||||
<g transform={`translate(${xScale(0)}, 0)`}>
|
||||
<YAxis scale={yScale} />
|
||||
</g>
|
||||
<g transform={`translate(0, ${yScale(0)})`}>
|
||||
<XAxis scale={xScale} />
|
||||
</g>
|
||||
|
||||
{Object.values(config.quadrantsMap).map((value, index) => (
|
||||
<QuadrantRings key={index} quadrant={value} xScale={xScale} config={config} />
|
||||
))}
|
||||
{Object.values(config.quadrantsMap).map((value, index) => (
|
||||
<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} />
|
||||
))}
|
||||
{Array.from(config.rings).map((ring: string, index) => (
|
||||
<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}}/>
|
||||
<ReactTooltip className="tooltip" offset={{ top: -5 }} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export default RadarChart;
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React from "react";
|
||||
import classNames from "classnames";
|
||||
import React from "react";
|
||||
|
||||
import "./headline-group.scss";
|
||||
|
||||
interface Props {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React from "react";
|
||||
|
||||
import "./hero-headline.scss";
|
||||
|
||||
interface Props {
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React from "react";
|
||||
import { Link as RLink } from "react-router-dom";
|
||||
|
||||
import "./link.scss";
|
||||
|
||||
type LinkProps = {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,39 +16,58 @@ 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}/>
|
||||
<SetTitle title={title} />
|
||||
<HeroHeadline>{title}</HeroHeadline>
|
||||
<div className="fullpage-content">
|
||||
{paragraphs.map(({ headline, values }) => (
|
||||
<React.Fragment key={headline}>
|
||||
<h3>{headline}</h3>
|
||||
{values.map((element, index) => {
|
||||
return (
|
||||
<p key={index} dangerouslySetInnerHTML={sanitize(element)}></p>
|
||||
)
|
||||
})
|
||||
}
|
||||
</React.Fragment>
|
||||
<React.Fragment key={headline}>
|
||||
<h3>{headline}</h3>
|
||||
{values.map((element, index) => {
|
||||
return (
|
||||
<p
|
||||
key={index}
|
||||
dangerouslySetInnerHTML={sanitize(element)}
|
||||
></p>
|
||||
);
|
||||
})}
|
||||
</React.Fragment>
|
||||
))}
|
||||
|
||||
<p>{quadrantsPreDescription ?? "The quadrants are:"}</p>
|
||||
<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>
|
||||
|
||||
@@ -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} />
|
||||
)}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 = {
|
||||
@@ -52,7 +53,7 @@ function Search(
|
||||
<span className={classNames("search__button", { "is-open": open })}>
|
||||
<button type="submit" className="button">
|
||||
<span className="icon icon--search button__icon" />
|
||||
{ searchLabel }
|
||||
{searchLabel}
|
||||
</button>
|
||||
</span>
|
||||
{closable && (
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useEffect } from "react";
|
||||
|
||||
import { radarName } from "../config";
|
||||
|
||||
type SetTitleProps = {
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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")!;
|
||||
|
||||
36
src/model.ts
36
src/model.ts
@@ -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(
|
||||
|
||||
@@ -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>');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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),
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user