From b9746d863368a17166ae109a134584bce79b9eac Mon Sep 17 00:00:00 2001 From: Rene Pfeuffer Date: Tue, 2 Sep 2025 13:13:29 +0200 Subject: [PATCH] Add possibility to change redering of diff tree items Squash commits of branch feature/diff_tree_extension: - Add optional extension for diff file tree - Make it a wrapper - Make extension more complex - Log change --- gradle/changelog/diff_tree_extension.yaml | 2 + scm-ui/ui-components/src/repos/DiffTypes.ts | 16 ++- .../src/repos/diff/DiffFileTree.tsx | 126 +++++++++++++----- 3 files changed, 105 insertions(+), 39 deletions(-) create mode 100644 gradle/changelog/diff_tree_extension.yaml diff --git a/gradle/changelog/diff_tree_extension.yaml b/gradle/changelog/diff_tree_extension.yaml new file mode 100644 index 0000000000..6a6e67d617 --- /dev/null +++ b/gradle/changelog/diff_tree_extension.yaml @@ -0,0 +1,2 @@ +- type: added + description: API to change diff tree diff --git a/scm-ui/ui-components/src/repos/DiffTypes.ts b/scm-ui/ui-components/src/repos/DiffTypes.ts index 53f6166661..ade20d6dfa 100644 --- a/scm-ui/ui-components/src/repos/DiffTypes.ts +++ b/scm-ui/ui-components/src/repos/DiffTypes.ts @@ -14,9 +14,9 @@ * along with this program. If not, see https://www.gnu.org/licenses/. */ -import { ReactNode } from "react"; +import { FC, ReactNode } from "react"; import { DefaultCollapsed } from "./defaultCollapsed"; -import { Change, Hunk, FileDiff as File } from "@scm-manager/ui-types"; +import { Change, Hunk, FileDiff as File, FileChangeType } from "@scm-manager/ui-types"; export type ChangeEvent = { change: Change; @@ -31,6 +31,18 @@ export type AnnotationFactoryContext = BaseContext; export type FileAnnotationFactory = (file: File) => ReactNode[]; +export type FileTreeNodeWrapper = FC<{ + name: string; + path: string; + changeType?: FileChangeType; + iconName: string; + iconColor: string; + isFile: boolean; + originalIcon: ReactNode; + originalLabel: ReactNode; + isCurrentFile: boolean; +}>; + // key = change id, value = react component export type AnnotationFactory = (context: AnnotationFactoryContext) => { [key: string]: any; diff --git a/scm-ui/ui-components/src/repos/diff/DiffFileTree.tsx b/scm-ui/ui-components/src/repos/diff/DiffFileTree.tsx index dc291a62c4..07df032820 100644 --- a/scm-ui/ui-components/src/repos/diff/DiffFileTree.tsx +++ b/scm-ui/ui-components/src/repos/diff/DiffFileTree.tsx @@ -18,12 +18,19 @@ import React, { FC } from "react"; import { Link } from "react-router-dom"; import { useTranslation } from "react-i18next"; import classNames from "classnames"; -import { FileTree } from "@scm-manager/ui-types"; +import { FileChangeType, FileTree } from "@scm-manager/ui-types"; import { FileDiffContent, StackedSpan, StyledIcon } from "./styledElements"; +import { FileTreeNodeWrapper } from "../DiffTypes"; -type Props = { tree: FileTree; currentFile: string; setCurrentFile: (path: string) => void; gap?: number }; +type Props = { + tree: FileTree; + currentFile: string; + setCurrentFile: (path: string) => void; + gap?: number; + FileTreeNodeWrapper?: FileTreeNodeWrapper; +}; -const DiffFileTree: FC = ({ tree, currentFile, setCurrentFile, gap = 15 }) => { +const DiffFileTree: FC = ({ tree, currentFile, setCurrentFile, gap = 15, FileTreeNodeWrapper }) => { return ( {Object.keys(tree.children).map((key) => ( @@ -33,6 +40,7 @@ const DiffFileTree: FC = ({ tree, currentFile, setCurrentFile, gap = 15 } parentPath="" currentFile={currentFile} setCurrentFile={setCurrentFile} + FileTreeNodeWrapper={FileTreeNodeWrapper} /> ))} @@ -41,9 +49,13 @@ const DiffFileTree: FC = ({ tree, currentFile, setCurrentFile, gap = 15 } export default DiffFileTree; -type ChangeType = "add" | "modify" | "delete" | "rename" | "copy"; - -type NodeProps = { node: FileTree; parentPath: string; currentFile: string; setCurrentFile: (path: string) => void }; +type NodeProps = { + node: FileTree; + parentPath: string; + currentFile: string; + setCurrentFile: (path: string) => void; + FileTreeNodeWrapper?: FileTreeNodeWrapper; +}; const addPath = (parentPath: string, path: string) => { if ("" === parentPath) { @@ -52,16 +64,31 @@ const addPath = (parentPath: string, path: string) => { return parentPath + "/" + path; }; -const TreeNode: FC = ({ node, parentPath, currentFile, setCurrentFile }) => { +const TreeNode: FC = ({ node, parentPath, currentFile, setCurrentFile, FileTreeNodeWrapper }) => { const [t] = useTranslation("repos"); + FileTreeNodeWrapper = FileTreeNodeWrapper || (({ children }) => <>{children}); + + const label =
{node.nodeName}
; + const icon = folder; return (
  • {Object.keys(node.children).length > 0 ? (
    • - folder -
      {node.nodeName}
      + + {icon} + {label} +
    • {Object.keys(node.children).map((key) => ( = ({ node, parentPath, currentFile, setCurrentFile parentPath={addPath(parentPath, node.nodeName)} currentFile={currentFile} setCurrentFile={setCurrentFile} + FileTreeNodeWrapper={FileTreeNodeWrapper} /> ))}
    ) : ( )}
  • ); }; -const getColor = (changeType: ChangeType) => { +const getColor = (changeType: FileChangeType) => { switch (changeType) { case "add": return "success"; @@ -99,7 +128,7 @@ const getColor = (changeType: ChangeType) => { } }; -const getIcon = (changeType: ChangeType) => { +const getIcon = (changeType: FileChangeType) => { switch (changeType) { case "add": case "copy": @@ -113,14 +142,22 @@ const getIcon = (changeType: ChangeType) => { }; type FileProps = { - changeType: ChangeType; + changeType: FileChangeType; path: string; parentPath: string; currentFile: string; setCurrentFile: (path: string) => void; + FileTreeNodeWrapper: FileTreeNodeWrapper; }; -const TreeFile: FC = ({ changeType, path, parentPath, currentFile, setCurrentFile }) => { +const TreeFile: FC = ({ + changeType, + path, + parentPath, + currentFile, + setCurrentFile, + FileTreeNodeWrapper, +}) => { const [t] = useTranslation("repos"); const completePath = addPath(parentPath, path); @@ -128,35 +165,50 @@ const TreeFile: FC = ({ changeType, path, parentPath, currentFile, se return currentFile === completePath; }; + const iconName = getIcon(changeType); + + const icon = ( + + + file + + + {iconName} + + + ); + const label =
    {path}
    ; + return ( setCurrentFile(completePath)} to={`#diff-${encodeURIComponent(completePath)}`} > - - - file - - - {getIcon(changeType)} - - -
    {path}
    + + {icon} + {label} + ); };