mirror of
https://github.com/scm-manager/scm-manager.git
synced 2026-05-07 17:55:59 +02:00
Use different icons in file tree to show status (#2246)
--------- Co-authored-by: Florian Scholdei <florian.scholdei@cloudogu.com>
This commit is contained in:
Binary file not shown.
|
Before Width: | Height: | Size: 104 KiB After Width: | Height: | Size: 867 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 106 KiB After Width: | Height: | Size: 855 KiB |
2
gradle/changelog/add-status-icon-in-file-tree.yaml
Normal file
2
gradle/changelog/add-status-icon-in-file-tree.yaml
Normal file
@@ -0,0 +1,2 @@
|
||||
- type: added
|
||||
description: Colored status icons in file tree
|
||||
@@ -28,10 +28,10 @@ const LayoutRadioButtons = ({ setLayout, layout }: LayoutProps) => {
|
||||
const [t] = useTranslation("repos");
|
||||
|
||||
return (
|
||||
<Field as="fieldset" className={"pl-4"}>
|
||||
<Field as="fieldset">
|
||||
<RadioGroup
|
||||
aria-label={t("changesets.diffTree.layout")}
|
||||
className="mt-2 columns is is-multiline is-align-items-center"
|
||||
className="is-flex is-flex-wrap-wrap is-align-items-center mt-2"
|
||||
value={layout}
|
||||
onValueChange={(value) => setLayout(value as unknown as LayoutMode)}
|
||||
>
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
import React, { FC, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import classNames from "classnames";
|
||||
import { NotFoundError, useDiff } from "@scm-manager/ui-api";
|
||||
import ErrorNotification from "../ErrorNotification";
|
||||
import Loading from "../Loading";
|
||||
@@ -26,8 +27,8 @@ import { DiffObjectProps } from "./DiffTypes";
|
||||
import DiffStatistics from "./DiffStatistics";
|
||||
import { DiffDropDown } from "../index";
|
||||
import DiffFileTree from "./diff/DiffFileTree";
|
||||
import { DiffContent, Divider, FileTreeContent, StickyFileDiffContainer } from "./diff/styledElements";
|
||||
import { useHistory, useLocation } from "react-router-dom";
|
||||
import { DiffContent, DiffTreeTitle, FileTreeContent, StickyFileDiffContainer } from "./diff/styledElements";
|
||||
import { useLocation } from "react-router-dom";
|
||||
import { getFileNameFromHash } from "./diffs";
|
||||
import LayoutRadioButtons from "./LayoutRadioButtons";
|
||||
import { useAriaId } from "@scm-manager/ui-core";
|
||||
@@ -66,7 +67,6 @@ const LoadingDiff: FC<Props> = ({ url, limit, refetchOnWindowFocus, ...props })
|
||||
const [prevHash, setPrevHash] = useState("");
|
||||
const diffContentId = useAriaId();
|
||||
const location = useLocation();
|
||||
const history = useHistory();
|
||||
|
||||
const { error, isLoading, data, fetchNextPage, isFetchingNextPage } = useDiff(url, {
|
||||
limit,
|
||||
@@ -79,10 +79,9 @@ const LoadingDiff: FC<Props> = ({ url, limit, refetchOnWindowFocus, ...props })
|
||||
setCollapsed(!collapsed);
|
||||
};
|
||||
|
||||
const setFilePath = (path: string) => {
|
||||
const setFilePath = () => {
|
||||
setPrevHash("");
|
||||
setLayout("Both");
|
||||
history.push(`#diff-${encodeURIComponent(path)}`);
|
||||
};
|
||||
|
||||
if (error) {
|
||||
@@ -97,7 +96,7 @@ const LoadingDiff: FC<Props> = ({ url, limit, refetchOnWindowFocus, ...props })
|
||||
} else {
|
||||
return (
|
||||
<>
|
||||
<Divider />
|
||||
<hr />
|
||||
<div className="is-flex is-justify-content-space-between">
|
||||
<DiffStatistics data={data.statistics} />
|
||||
<DiffDropDown
|
||||
@@ -110,22 +109,23 @@ const LoadingDiff: FC<Props> = ({ url, limit, refetchOnWindowFocus, ...props })
|
||||
/>
|
||||
</div>
|
||||
<LayoutRadioButtons layout={layout} setLayout={setLayout} />
|
||||
<div className="is-flex mb-4 mt-1 columns is-multiline">
|
||||
<div className="is-flex columns is-multiline mb-4 mt-1 ">
|
||||
<StickyFileDiffContainer
|
||||
className={
|
||||
(layout === "Both" ? "column pl-3 is-one-quarter" : "column pl-3 is-full") +
|
||||
(layout !== "Diff" ? "" : " is-hidden")
|
||||
}
|
||||
className={classNames(
|
||||
"column",
|
||||
"pt-0",
|
||||
layout === "Both" ? "is-one-quarter" : "is-full",
|
||||
layout !== "Diff" ? "" : " is-hidden"
|
||||
)}
|
||||
>
|
||||
<FileTreeContent className={"p-3"} isBorder={layout !== "Diff"}>
|
||||
<h3 className={"title is-6 pt-4"}>{t("changesets.diffTree.title")}</h3>
|
||||
<Divider />
|
||||
<FileTreeContent className="p-0" isBorder={layout !== "Diff"}>
|
||||
<DiffTreeTitle className="title is-6 m-0 p-4">{t("changesets.diffTree.title")}</DiffTreeTitle>
|
||||
{data?.tree && (
|
||||
<DiffFileTree
|
||||
tree={data.tree}
|
||||
currentFile={decodeURIComponent(getFileNameFromHash(location.hash) ?? "")}
|
||||
setCurrentFile={setFilePath}
|
||||
gap={12}
|
||||
gap={10}
|
||||
/>
|
||||
)}
|
||||
</FileTreeContent>
|
||||
|
||||
@@ -14,39 +14,35 @@
|
||||
* along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
import { FileTree } from "@scm-manager/ui-types";
|
||||
import React, { FC } from "react";
|
||||
import { FileDiffContainer, FileDiffContent } from "./styledElements";
|
||||
import { Icon } from "@scm-manager/ui-core";
|
||||
import { Link } from "react-router-dom";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import styled from "styled-components";
|
||||
import classNames from "classnames";
|
||||
import { FileTree } from "@scm-manager/ui-types";
|
||||
import { FileDiffContent, StackedSpan, StyledIcon } from "./styledElements";
|
||||
|
||||
type Props = { tree: FileTree; currentFile: string; setCurrentFile: (path: string) => void; gap?: number };
|
||||
|
||||
const StyledIcon = styled(Icon)`
|
||||
min-width: 1.5rem;
|
||||
`;
|
||||
|
||||
const DiffFileTree: FC<Props> = ({ tree, currentFile, setCurrentFile, gap = 15 }) => {
|
||||
return (
|
||||
<FileDiffContainer className={"mt-4 py-3 pr-2"}>
|
||||
<FileDiffContent gap={gap}>
|
||||
{Object.keys(tree.children).map((key) => (
|
||||
<TreeNode
|
||||
key={key}
|
||||
node={tree.children[key]}
|
||||
parentPath={""}
|
||||
currentFile={currentFile}
|
||||
setCurrentFile={setCurrentFile}
|
||||
/>
|
||||
))}
|
||||
</FileDiffContent>
|
||||
</FileDiffContainer>
|
||||
<FileDiffContent gap={gap}>
|
||||
{Object.keys(tree.children).map((key) => (
|
||||
<TreeNode
|
||||
key={key}
|
||||
node={tree.children[key]}
|
||||
parentPath=""
|
||||
currentFile={currentFile}
|
||||
setCurrentFile={setCurrentFile}
|
||||
/>
|
||||
))}
|
||||
</FileDiffContent>
|
||||
);
|
||||
};
|
||||
|
||||
export default DiffFileTree;
|
||||
|
||||
type ChangeType = "add" | "modify" | "delete" | "rename" | "copy";
|
||||
|
||||
type NodeProps = { node: FileTree; parentPath: string; currentFile: string; setCurrentFile: (path: string) => void };
|
||||
|
||||
const addPath = (parentPath: string, path: string) => {
|
||||
@@ -62,10 +58,10 @@ const TreeNode: FC<NodeProps> = ({ node, parentPath, currentFile, setCurrentFile
|
||||
return (
|
||||
<li>
|
||||
{Object.keys(node.children).length > 0 ? (
|
||||
<ul className={"py-1 pr-1 pl-3"}>
|
||||
<li className={"is-flex has-text-grey"}>
|
||||
<ul className="py-1 pl-3">
|
||||
<li className="is-flex has-text-grey">
|
||||
<StyledIcon alt={t("diff.showContent")}>folder</StyledIcon>
|
||||
<div className={"ml-1"}>{node.nodeName}</div>
|
||||
<div className="ml-1">{node.nodeName}</div>
|
||||
</li>
|
||||
{Object.keys(node.children).map((key) => (
|
||||
<TreeNode
|
||||
@@ -79,6 +75,7 @@ const TreeNode: FC<NodeProps> = ({ node, parentPath, currentFile, setCurrentFile
|
||||
</ul>
|
||||
) : (
|
||||
<TreeFile
|
||||
changeType={node.changeType.toLowerCase() as ChangeType}
|
||||
path={node.nodeName}
|
||||
parentPath={parentPath}
|
||||
currentFile={currentFile}
|
||||
@@ -89,13 +86,41 @@ const TreeNode: FC<NodeProps> = ({ node, parentPath, currentFile, setCurrentFile
|
||||
);
|
||||
};
|
||||
|
||||
type FileProps = { path: string; parentPath: string; currentFile: string; setCurrentFile: (path: string) => void };
|
||||
const getColor = (changeType: ChangeType) => {
|
||||
switch (changeType) {
|
||||
case "add":
|
||||
return "success";
|
||||
case "modify":
|
||||
case "rename":
|
||||
case "copy":
|
||||
return "info";
|
||||
case "delete":
|
||||
return "danger";
|
||||
}
|
||||
};
|
||||
|
||||
export const TreeFileContent = styled.div`
|
||||
cursor: pointer;
|
||||
`;
|
||||
const getIcon = (changeType: ChangeType) => {
|
||||
switch (changeType) {
|
||||
case "add":
|
||||
case "copy":
|
||||
return "plus";
|
||||
case "modify":
|
||||
case "rename":
|
||||
return "circle";
|
||||
case "delete":
|
||||
return "minus";
|
||||
}
|
||||
};
|
||||
|
||||
const TreeFile: FC<FileProps> = ({ path, parentPath, currentFile, setCurrentFile }) => {
|
||||
type FileProps = {
|
||||
changeType: ChangeType;
|
||||
path: string;
|
||||
parentPath: string;
|
||||
currentFile: string;
|
||||
setCurrentFile: (path: string) => void;
|
||||
};
|
||||
|
||||
const TreeFile: FC<FileProps> = ({ changeType, path, parentPath, currentFile, setCurrentFile }) => {
|
||||
const [t] = useTranslation("repos");
|
||||
const completePath = addPath(parentPath, path);
|
||||
|
||||
@@ -104,17 +129,34 @@ const TreeFile: FC<FileProps> = ({ path, parentPath, currentFile, setCurrentFile
|
||||
};
|
||||
|
||||
return (
|
||||
<TreeFileContent className={"is-flex py-1 pl-3"} onClick={() => setCurrentFile(completePath)}>
|
||||
{isCurrentFile() ? (
|
||||
<StyledIcon style={{ minWidth: "1.5rem" }} key={completePath + "file"} alt={t("diff.showContent")}>
|
||||
<Link
|
||||
className="is-flex py-1 pl-3 has-cursor-pointer"
|
||||
onClick={() => setCurrentFile(completePath)}
|
||||
to={`#diff-${encodeURIComponent(completePath)}`}
|
||||
>
|
||||
<StackedSpan className="fa-stack">
|
||||
<StyledIcon
|
||||
className={classNames("fa-stack-2x", `has-text-${getColor(changeType)}`)}
|
||||
key={completePath + "file"}
|
||||
type={isCurrentFile() ? "fas" : "far"}
|
||||
alt={t("diff.showContent")}
|
||||
>
|
||||
file
|
||||
</StyledIcon>
|
||||
) : (
|
||||
<StyledIcon style={{ minWidth: "1.5rem" }} key={completePath + "file"} type="far" alt={t("diff.showContent")}>
|
||||
file
|
||||
<StyledIcon
|
||||
className={classNames(
|
||||
"fa-stack-1x",
|
||||
"is-relative",
|
||||
isCurrentFile() ? "has-text-secondary-least" : `has-text-${getColor(changeType)}`
|
||||
)}
|
||||
isSmaller={getIcon(changeType) === "circle"}
|
||||
key={changeType}
|
||||
alt={t(`diff.changes.${changeType}`)}
|
||||
>
|
||||
{getIcon(changeType)}
|
||||
</StyledIcon>
|
||||
)}
|
||||
<div className={"ml-1"}>{path}</div>
|
||||
</TreeFileContent>
|
||||
</StackedSpan>
|
||||
<div className="ml-1">{path}</div>
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
import styled from "styled-components";
|
||||
// @ts-ignore react-diff-view does not provide types
|
||||
import { Hunk } from "react-diff-view";
|
||||
import { Icon } from "@scm-manager/ui-core";
|
||||
import { devices } from "../../devices";
|
||||
|
||||
export type Collapsible = {
|
||||
collapsed?: boolean;
|
||||
@@ -83,27 +85,37 @@ export const PanelHeading = styled.div<{ sticky?: boolean | number }>`
|
||||
}}
|
||||
`;
|
||||
|
||||
export const StickyFileDiffContainer = styled.div`
|
||||
top: 5rem;
|
||||
position: sticky;
|
||||
height: 100%;
|
||||
|
||||
@media screen and (max-width: ${devices.mobile.width}px) {
|
||||
top: 0;
|
||||
position: relative;
|
||||
flex: 0 0 100%;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
`;
|
||||
|
||||
export const FileTreeContent = styled.div<{ isBorder: boolean }>`
|
||||
${({ isBorder }) =>
|
||||
isBorder &&
|
||||
`
|
||||
border: 1px solid var(--scm-border-color);
|
||||
border-radius: 1rem;
|
||||
border-radius: 0.25rem;
|
||||
overflow: hidden;
|
||||
`}
|
||||
`;
|
||||
|
||||
export const DiffTreeTitle = styled.h3`
|
||||
border-bottom: 1px solid var(--scm-border-color);
|
||||
box-shadow: 0 24px 3px -24px var(--scm-border-color);
|
||||
`;
|
||||
|
||||
export const DiffContent = styled.div`
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
export const StickyFileDiffContainer = styled.div`
|
||||
top: 3rem;
|
||||
position: sticky;
|
||||
height: 100%;
|
||||
`;
|
||||
|
||||
export const FileDiffContainer = styled.div`
|
||||
top: 5rem;
|
||||
padding-top: 0;
|
||||
`;
|
||||
|
||||
export const FileDiffContent = styled.ul<{ gap?: number }>`
|
||||
@@ -120,8 +132,18 @@ export const FileDiffContent = styled.ul<{ gap?: number }>`
|
||||
}};
|
||||
`;
|
||||
|
||||
export const Divider = styled.div`
|
||||
margin-bottom: 16px;
|
||||
border-bottom: 1px solid var(--scm-border-color);
|
||||
box-shadow: 0 24px 3px -24px var(--scm-border-color);
|
||||
export const StackedSpan = styled.span`
|
||||
width: 3em;
|
||||
height: 3em;
|
||||
font-size: 0.5em;
|
||||
`;
|
||||
|
||||
export const StyledIcon = styled(Icon)<{ isSmaller?: boolean }>`
|
||||
${({ isSmaller }) =>
|
||||
isSmaller &&
|
||||
`
|
||||
font-size: 0.5em;
|
||||
margin-top: 0.05rem;
|
||||
`}
|
||||
min-width: 1.5rem;
|
||||
`;
|
||||
|
||||
Reference in New Issue
Block a user