diff --git a/README.md b/README.md index 852e9fa..559f8d0 100644 --- a/README.md +++ b/README.md @@ -80,6 +80,13 @@ If unspecified, no artifact will be created. Default: `''` (no artifact) +### `file_colors` + +You can customize the colors for specific file extensions. Key/value pairs will extend the [default colors](https://github.com/githubocto/repo-visualizer/pull/src/language-colors.json). + +For example: '{"js": "red","ts": "green"}' +default: '{}' + ## Outputs ### `svg` diff --git a/action.yml b/action.yml index 356b29e..ac2beb9 100644 --- a/action.yml +++ b/action.yml @@ -31,6 +31,10 @@ inputs: description: "If given, the name of an artifact to be created containing the diagram. Default: don't create an artifact." required: false default: '' + file_colors: + description: "You can customize the colors for specific file extensions. Key/value pairs will extend the [default colors](https://github.com/githubocto/repo-visualizer/pull/src/language-colors.json)." + required: false + default: "{}" outputs: svg: description: "The diagram contents as text" diff --git a/diagram.svg b/diagram.svg index 237fe1c..bbcf31c 100644 --- a/diagram.svg +++ b/diagram.svg @@ -1 +1 @@ -srcsrc.husky.husky__language-colors.jsonlanguage-colors.jsonlanguage-colors.jsonTree.tsxTree.tsxTree.tsxindex.jsxindex.jsxindex.jsxprocess-dir.jsprocess-dir.jsprocess-dir.jsutils.tsutils.tsutils.tsCircleText.tsxCircleText.tsxCircleText.tsxtypes.tstypes.tstypes.tsindex.jsindex.jsindex.jsREADME.mdREADME.mdREADME.mdLICENSELICENSELICENSEaction.ymlaction.ymlaction.ymlpackage.jsonpackage.jsonpackage.jsonhusky.shhusky.shhusky.sh.gitignore.js.json.jsx.md.sh.svg.ts.tsx.ymleach dot sized by file size \ No newline at end of file +srcsrc.husky.husky__language-colors.jsonlanguage-colors.jsonlanguage-colors.jsonTree.tsxTree.tsxTree.tsxindex.jsxindex.jsxindex.jsxshould-exclude-...should-exclude-...should-exclude-...process-dir.jsprocess-dir.jsprocess-dir.jsutils.tsutils.tsutils.tsCircleText.tsxCircleText.tsxCircleText.tsxshould-exc...should-exc...should-exc...types.tstypes.tstypes.tsindex.jsindex.jsindex.jsREADME.mdREADME.mdREADME.mdaction.ymlaction.ymlaction.ymlLICENSELICENSELICENSEpackage.jsonpackage.jsonpackage.jsonhusky.shhusky.shhusky.sh.cjs.gitignore.js.json.jsx.md.sh.svg.ts.tsx.yaml.ymleach dot sized by file size \ No newline at end of file diff --git a/index.js b/index.js index 97eb710..e45dc7b 100644 --- a/index.js +++ b/index.js @@ -25814,7 +25814,8 @@ var numberOfCommitsAccessor = (d2) => { var _a; return ((_a = d2 == null ? void 0 : d2.commits) == null ? void 0 : _a.length) || 0; }; -var Tree = ({ data, filesChanged = [], maxDepth = 9, colorEncoding = "type" }) => { +var Tree = ({ data, filesChanged = [], maxDepth = 9, colorEncoding = "type", customFileColors }) => { + const fileColors = { ...language_colors_default, ...customFileColors }; const [selectedNodeId, setSelectedNodeId] = (0, import_react2.useState)(null); const cachedPositions = (0, import_react2.useRef)({}); const cachedOrders = (0, import_react2.useRef)({}); @@ -25845,9 +25846,9 @@ var Tree = ({ data, filesChanged = [], maxDepth = 9, colorEncoding = "type" }) = if (isParent) { const extensions = (0, import_countBy.default)(d2.children, (c3) => c3.extension); const mainExtension = (_a = (0, import_maxBy.default)((0, import_entries.default)(extensions), ([k, v2]) => v2)) == null ? void 0 : _a[0]; - return language_colors_default[mainExtension] || "#CED6E0"; + return fileColors[mainExtension] || "#CED6E0"; } - return language_colors_default[d2.extension] || "#CED6E0"; + return fileColors[d2.extension] || "#CED6E0"; } else if (colorEncoding === "number-of-changes") { return colorScale(numberOfCommitsAccessor(d2)) || "#f4f4f4"; } else if (colorEncoding === "last-change") { @@ -25857,7 +25858,7 @@ var Tree = ({ data, filesChanged = [], maxDepth = 9, colorEncoding = "type" }) = const packedData = (0, import_react2.useMemo)(() => { if (!data) return []; - const hierarchicalData = hierarchy(processChild(data, getColor, cachedOrders.current)).sum((d2) => d2.value).sort((a2, b) => { + const hierarchicalData = hierarchy(processChild(data, getColor, cachedOrders.current, 0, fileColors)).sum((d2) => d2.value).sort((a2, b) => { if (b.data.path.startsWith("src/fonts")) { } return b.data.sortOrder - a2.data.sortOrder || (b.data.name > a2.data.name ? 1 : -1); @@ -25888,9 +25889,9 @@ var Tree = ({ data, filesChanged = [], maxDepth = 9, colorEncoding = "type" }) = cachedPositions.current[d2.data.path] = [d2.x, d2.y]; }); return children2.slice(0, maxChildren); - }, [data]); + }, [data, fileColors]); const selectedNode = selectedNodeId && packedData.find((d2) => d2.data.path === selectedNodeId); - const fileTypes = (0, import_uniqBy.default)(packedData.map((d2) => language_colors_default[d2.data.extension] && d2.data.extension)).sort().filter(Boolean); + const fileTypes = (0, import_uniqBy.default)(packedData.map((d2) => fileColors[d2.data.extension] && d2.data.extension)).sort().filter(Boolean); return /* @__PURE__ */ import_react2.default.createElement("svg", { width, height, @@ -26047,7 +26048,8 @@ var Tree = ({ data, filesChanged = [], maxDepth = 9, colorEncoding = "type" }) = dominantBaseline: "middle" }, label)); }), !filesChanged.length && colorEncoding === "type" && /* @__PURE__ */ import_react2.default.createElement(Legend, { - fileTypes + fileTypes, + fileColors }), !filesChanged.length && colorEncoding !== "type" && /* @__PURE__ */ import_react2.default.createElement(ColorLegend, { scale: colorScale, extent: colorExtent, @@ -26088,7 +26090,7 @@ var ColorLegend = ({ scale, extent, colorEncoding }) => { textAnchor: i ? "end" : "start" }, formatD(d2)))); }; -var Legend = ({ fileTypes = [] }) => { +var Legend = ({ fileTypes = [], fileColors }) => { return /* @__PURE__ */ import_react2.default.createElement("g", { transform: `translate(${width - 60}, ${height - fileTypes.length * 15 - 20})` }, fileTypes.map((extension, i) => /* @__PURE__ */ import_react2.default.createElement("g", { @@ -26096,7 +26098,7 @@ var Legend = ({ fileTypes = [] }) => { transform: `translate(0, ${i * 15})` }, /* @__PURE__ */ import_react2.default.createElement("circle", { r: "5", - fill: language_colors_default[extension] + fill: fileColors[extension] }), /* @__PURE__ */ import_react2.default.createElement("text", { x: "10", style: { fontSize: "14px", fontWeight: 300 }, @@ -26110,14 +26112,14 @@ var Legend = ({ fileTypes = [] }) => { } }, "each dot sized by file size")); }; -var processChild = (child, getColor, cachedOrders, i = 0) => { +var processChild = (child, getColor, cachedOrders, i = 0, fileColors) => { var _a; if (!child) return; const isRoot = !child.path; let name = child.name; let path = child.path; - let children2 = (_a = child == null ? void 0 : child.children) == null ? void 0 : _a.map((c3, i2) => processChild(c3, getColor, cachedOrders, i2)); + let children2 = (_a = child == null ? void 0 : child.children) == null ? void 0 : _a.map((c3, i2) => processChild(c3, getColor, cachedOrders, i2, fileColors)); if ((children2 == null ? void 0 : children2.length) === 1) { name = `${name}/${children2[0].name}`; path = children2[0].path; @@ -26125,7 +26127,7 @@ var processChild = (child, getColor, cachedOrders, i = 0) => { } const pathWithoutExtension = path == null ? void 0 : path.split(".").slice(0, -1).join("."); const extension = name == null ? void 0 : name.split(".").slice(-1)[0]; - const hasExtension = !!language_colors_default[extension]; + const hasExtension = !!fileColors[extension]; if (isRoot && children2) { const looseChildren = children2 == null ? void 0 : children2.filter((d2) => { var _a2; @@ -26271,6 +26273,7 @@ var main = async () => { core.endGroup(); const rootPath = core.getInput("root_path") || ""; const maxDepth = core.getInput("max_depth") || 9; + const customFileColors = JSON.parse(core.getInput("file_colors") || "{}"); const colorEncoding = core.getInput("color_encoding") || "type"; const commitMessage = core.getInput("commit_message") || "Repo visualizer: updated diagram"; const excludedPathsString = core.getInput("excluded_paths") || "node_modules,bower_components,dist,out,build,eject,.next,.netlify,.yarn,.git,.vscode,package-lock.json,yarn.lock"; @@ -26282,7 +26285,8 @@ var main = async () => { const componentCodeString = import_server.default.renderToStaticMarkup(/* @__PURE__ */ import_react3.default.createElement(Tree, { data, maxDepth: +maxDepth, - colorEncoding + colorEncoding, + customFileColors })); const outputFile = core.getInput("output_file") || "./diagram.svg"; core.setOutput("svg", componentCodeString); diff --git a/src/Tree.tsx b/src/Tree.tsx index 5b13c27..4e22a79 100644 --- a/src/Tree.tsx +++ b/src/Tree.tsx @@ -19,7 +19,7 @@ import entries from "lodash/entries"; import uniqBy from "lodash/uniqBy"; import flatten from "lodash/flatten"; // file colors are from the github/linguist repo -import fileColors from "./language-colors.json"; +import defaultFileColors from "./language-colors.json"; import { CircleText } from "./CircleText"; import { keepBetween, @@ -32,6 +32,7 @@ type Props = { filesChanged: string[]; maxDepth: number; colorEncoding: "type" | "number-of-changes" | "last-change" + customFileColors?: { [key: string]: string }; }; type ExtendedFileType = { extension?: string; @@ -40,6 +41,7 @@ type ExtendedFileType = { color?: string; value?: number; sortOrder?: number; + fileColors?: { [key: string]: string }; } & FileType; type ProcessedDataItem = { data: ExtendedFileType; @@ -58,9 +60,10 @@ const maxChildren = 9000; const lastCommitAccessor = (d) => new Date(d.commits?.[0]?.date + "0"); const numberOfCommitsAccessor = (d) => d?.commits?.length || 0; export const Tree = ( - { data, filesChanged = [], maxDepth = 9, colorEncoding = "type" }: + { data, filesChanged = [], maxDepth = 9, colorEncoding = "type", customFileColors}: Props, ) => { + const fileColors = { ...defaultFileColors, ...customFileColors }; const [selectedNodeId, setSelectedNodeId] = useState(null); const cachedPositions = useRef<{ [key: string]: [number, number] }>({}); const cachedOrders = useRef<{ [key: string]: string[] }>({}); @@ -121,7 +124,7 @@ export const Tree = ( const packedData = useMemo(() => { if (!data) return []; const hierarchicalData = hierarchy( - processChild(data, getColor, cachedOrders.current), + processChild(data, getColor, cachedOrders.current, 0, fileColors), ).sum((d) => d.value) .sort((a, b) => { if (b.data.path.startsWith("src/fonts")) { @@ -171,7 +174,7 @@ export const Tree = ( }); return children.slice(0, maxChildren); - }, [data]); + }, [data, fileColors]); const selectedNode = selectedNodeId && packedData.find((d) => d.data.path === selectedNodeId); @@ -379,7 +382,7 @@ export const Tree = ( })} {!filesChanged.length && colorEncoding === "type" && - } + } {!filesChanged.length && colorEncoding !== "type" && } @@ -429,7 +432,7 @@ const ColorLegend = ({ scale, extent, colorEncoding }) => { ); }; -const Legend = ({ fileTypes = [] }) => { +const Legend = ({ fileTypes = [], fileColors}) => { return ( { if (!child) return; const isRoot = !child.path; let name = child.name; let path = child.path; let children = child?.children?.map((c, i) => - processChild(c, getColor, cachedOrders, i) + processChild(c, getColor, cachedOrders, i, fileColors) ); if (children?.length === 1) { name = `${name}/${children[0].name}`; diff --git a/src/index.jsx b/src/index.jsx index bc07573..6782a3c 100644 --- a/src/index.jsx +++ b/src/index.jsx @@ -25,6 +25,7 @@ const main = async () => { const rootPath = core.getInput("root_path") || ""; // Micro and minimatch do not support paths starting with ./ const maxDepth = core.getInput("max_depth") || 9 + const customFileColors = JSON.parse(core.getInput("file_colors") || '{}'); const colorEncoding = core.getInput("color_encoding") || "type" const commitMessage = core.getInput("commit_message") || "Repo visualizer: updated diagram" const excludedPathsString = core.getInput("excluded_paths") || "node_modules,bower_components,dist,out,build,eject,.next,.netlify,.yarn,.git,.vscode,package-lock.json,yarn.lock" @@ -38,7 +39,7 @@ const main = async () => { const data = await processDir(rootPath, excludedPaths, excludedGlobs); const componentCodeString = ReactDOMServer.renderToStaticMarkup( - + ); const outputFile = core.getInput("output_file") || "./diagram.svg"