mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-14 01:15:44 +01:00
Show source code controls even if a file is not present (#1680)
This fix preserves the context and shows the error in the component where normally the content of the file is displayed, so that you can still change the branch and the path.
This commit is contained in:
2
gradle/changelog/file_not_found_controls.yaml
Normal file
2
gradle/changelog/file_not_found_controls.yaml
Normal file
@@ -0,0 +1,2 @@
|
||||
- type: fixed
|
||||
description: Show source code controls even if a file is not present ([#1680](https://github.com/scm-manager/scm-manager/pull/1680))
|
||||
@@ -21,8 +21,8 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
import React, { ReactNode } from "react";
|
||||
import { WithTranslation, withTranslation } from "react-i18next";
|
||||
import React, { FC, ReactNode, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import classNames from "classnames";
|
||||
import styled from "styled-components";
|
||||
import { ExtensionPoint } from "@scm-manager/ui-extensions";
|
||||
@@ -33,21 +33,16 @@ import SourcesView from "./SourcesView";
|
||||
import HistoryView from "./HistoryView";
|
||||
import AnnotateView from "./AnnotateView";
|
||||
|
||||
type Props = WithTranslation & {
|
||||
type Props = {
|
||||
file: File;
|
||||
repository: Repository;
|
||||
revision: string;
|
||||
path: string;
|
||||
breadcrumb: React.ReactNode;
|
||||
error?: Error;
|
||||
};
|
||||
|
||||
type State = {
|
||||
collapsed: boolean;
|
||||
selected: SourceViewSelection;
|
||||
errorFromExtension?: Error;
|
||||
};
|
||||
|
||||
const Header = styled.div`
|
||||
const HeaderWrapper = styled.div`
|
||||
border-bottom: solid 1px #dbdbdb;
|
||||
font-size: 1.25em;
|
||||
font-weight: 300;
|
||||
@@ -81,50 +76,49 @@ const BorderLessDiv = styled.div`
|
||||
box-shadow: none;
|
||||
`;
|
||||
|
||||
export type SourceViewSelection = "source" | "history" | "annotations";
|
||||
export type SourceViewSelection = "source" | "annotations" | "history";
|
||||
|
||||
class Content extends React.Component<Props, State> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
const Content: FC<Props> = ({ file, repository, revision, path, breadcrumb, error }) => {
|
||||
const [t] = useTranslation("repos");
|
||||
const [collapsed, setCollapsed] = useState(true);
|
||||
const [selected, setSelected] = useState<SourceViewSelection>("source");
|
||||
const [errorFromExtension, setErrorFromExtension] = useState<Error>();
|
||||
|
||||
this.state = {
|
||||
collapsed: true,
|
||||
selected: "source"
|
||||
};
|
||||
}
|
||||
|
||||
toggleCollapse = () => {
|
||||
this.setState(prevState => ({
|
||||
collapsed: !prevState.collapsed
|
||||
}));
|
||||
const wrapContent = (content: ReactNode) => {
|
||||
return (
|
||||
<>
|
||||
<div className="panel">
|
||||
{breadcrumb}
|
||||
{content}
|
||||
</div>
|
||||
<ErrorNotification error={errorFromExtension} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
handleExtensionError = (error: Error) => {
|
||||
this.setState({
|
||||
errorFromExtension: error
|
||||
});
|
||||
const toggleCollapse = () => {
|
||||
setCollapsed(!collapsed);
|
||||
};
|
||||
|
||||
showHeader(content: ReactNode) {
|
||||
const { repository, file, revision } = this.props;
|
||||
const { selected, collapsed } = this.state;
|
||||
const showHeader = (content: ReactNode) => {
|
||||
const icon = collapsed ? "angle-right" : "angle-down";
|
||||
|
||||
const selector = file._links.history ? (
|
||||
<FileButtonAddons
|
||||
className="mr-2"
|
||||
selected={selected}
|
||||
showSources={() => this.setState({ selected: "source" })}
|
||||
showHistory={() => this.setState({ selected: "history" })}
|
||||
showAnnotations={() => this.setState({ selected: "annotations" })}
|
||||
showSources={() => setSelected("source")}
|
||||
showAnnotations={() => setSelected("annotations")}
|
||||
showHistory={() => setSelected("history")}
|
||||
/>
|
||||
) : null;
|
||||
|
||||
return (
|
||||
<HeaderWrapper>
|
||||
<div className={classNames("level", "is-flex-wrap-wrap")}>
|
||||
<FullWidthTitleHeader
|
||||
className={classNames("level-left", "is-flex", "has-cursor-pointer", "is-word-break", "mr-2")}
|
||||
onClick={this.toggleCollapse}
|
||||
onClick={toggleCollapse}
|
||||
>
|
||||
<Icon className={classNames("is-inline", "mr-2")} name={`${icon} fa-fw`} color="inherit" />
|
||||
{file.name}
|
||||
@@ -142,19 +136,18 @@ class Content extends React.Component<Props, State> {
|
||||
repository,
|
||||
file,
|
||||
revision: revision ? encodeURIComponent(revision) : "",
|
||||
handleExtensionError: this.handleExtensionError
|
||||
handleExtensionError: setErrorFromExtension,
|
||||
}}
|
||||
renderAll={true}
|
||||
/>
|
||||
</AlignRight>
|
||||
</div>
|
||||
</HeaderWrapper>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
showMoreInformation() {
|
||||
const collapsed = this.state.collapsed;
|
||||
const { file, revision, t, repository } = this.props;
|
||||
const date = <DateFromNow date={file.commitDate} />;
|
||||
const showMoreInformation = () => {
|
||||
const fileSize = file.directory ? null : <FileSize bytes={file?.length ? file.length : 0} />;
|
||||
const description = file.description ? (
|
||||
<p>
|
||||
{file.description.split("\n").map((item, key) => {
|
||||
@@ -167,7 +160,7 @@ class Content extends React.Component<Props, State> {
|
||||
})}
|
||||
</p>
|
||||
) : null;
|
||||
const fileSize = file.directory ? "" : <FileSize bytes={file?.length ? file.length : 0} />;
|
||||
|
||||
if (!collapsed) {
|
||||
return (
|
||||
<>
|
||||
@@ -188,7 +181,9 @@ class Content extends React.Component<Props, State> {
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{t("sources.content.commitDate")}</td>
|
||||
<td>{date}</td>
|
||||
<td>
|
||||
<DateFromNow date={file.commitDate} />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{t("sources.content.description")}</td>
|
||||
@@ -200,7 +195,7 @@ class Content extends React.Component<Props, State> {
|
||||
props={{
|
||||
file,
|
||||
repository,
|
||||
revision
|
||||
revision,
|
||||
}}
|
||||
/>
|
||||
</tbody>
|
||||
@@ -211,38 +206,33 @@ class Content extends React.Component<Props, State> {
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
if (!file || error) {
|
||||
return wrapContent(<ErrorNotification error={error} />);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { file, revision, repository, path, breadcrumb } = this.props;
|
||||
const { selected, errorFromExtension } = this.state;
|
||||
|
||||
let content;
|
||||
let body;
|
||||
switch (selected) {
|
||||
case "source":
|
||||
content = <SourcesView revision={revision} file={file} repository={repository} path={path} />;
|
||||
break;
|
||||
case "history":
|
||||
content = <HistoryView file={file} repository={repository} />;
|
||||
body = <SourcesView revision={revision} file={file} repository={repository} path={path} />;
|
||||
break;
|
||||
case "annotations":
|
||||
content = <AnnotateView file={file} repository={repository} />;
|
||||
body = <AnnotateView file={file} repository={repository} />;
|
||||
break;
|
||||
case "history":
|
||||
body = <HistoryView file={file} repository={repository} />;
|
||||
}
|
||||
const header = this.showHeader(content);
|
||||
const moreInformation = this.showMoreInformation();
|
||||
const header = showHeader(body);
|
||||
const moreInformation = showMoreInformation();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="panel">
|
||||
{breadcrumb}
|
||||
<Header>{header}</Header>
|
||||
return wrapContent(
|
||||
<>
|
||||
{header}
|
||||
{moreInformation}
|
||||
{content}
|
||||
</div>
|
||||
{errorFromExtension && <ErrorNotification error={errorFromExtension} />}
|
||||
</div>
|
||||
{body}
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default withTranslation("repos")(Content);
|
||||
export default Content;
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
*/
|
||||
import React, { FC, useEffect } from "react";
|
||||
import { Branch, Repository } from "@scm-manager/ui-types";
|
||||
import { Breadcrumb, ErrorNotification, Loading, Notification } from "@scm-manager/ui-components";
|
||||
import { Breadcrumb, Loading, Notification } from "@scm-manager/ui-components";
|
||||
import FileTree from "../components/FileTree";
|
||||
import Content from "./Content";
|
||||
import CodeActionBar from "../../codeSection/components/CodeActionBar";
|
||||
@@ -50,7 +50,7 @@ const useUrlParams = () => {
|
||||
const { revision, path } = useParams<Params>();
|
||||
return {
|
||||
revision: revision ? decodeURIComponent(revision) : undefined,
|
||||
path: path || ""
|
||||
path: path || "",
|
||||
};
|
||||
};
|
||||
|
||||
@@ -62,23 +62,25 @@ const Sources: FC<Props> = ({ repository, branches, selectedBranch, baseUrl }) =
|
||||
// redirect to default branch is non branch selected
|
||||
useEffect(() => {
|
||||
if (branches && branches.length > 0 && !selectedBranch) {
|
||||
const defaultBranch = branches?.filter(b => b.defaultBranch === true)[0];
|
||||
const defaultBranch = branches?.filter((b) => b.defaultBranch === true)[0];
|
||||
history.replace(`${baseUrl}/sources/${encodeURIComponent(defaultBranch.name)}/`);
|
||||
}
|
||||
}, [branches, selectedBranch]);
|
||||
const { isLoading, error, data: file, isFetchingNextPage, fetchNextPage } = useSources(repository, {
|
||||
const {
|
||||
isLoading,
|
||||
error,
|
||||
data: file,
|
||||
isFetchingNextPage,
|
||||
fetchNextPage,
|
||||
} = useSources(repository, {
|
||||
revision,
|
||||
path,
|
||||
// we have to wait until a branch is selected,
|
||||
// expect if we have no branches (svn)
|
||||
enabled: !branches || !!selectedBranch
|
||||
enabled: !branches || !!selectedBranch,
|
||||
});
|
||||
|
||||
if (error) {
|
||||
return <ErrorNotification error={error} />;
|
||||
}
|
||||
|
||||
if (isLoading || !file) {
|
||||
if (isLoading || (!error && !file)) {
|
||||
return <Loading />;
|
||||
}
|
||||
|
||||
@@ -98,7 +100,7 @@ const Sources: FC<Props> = ({ repository, branches, selectedBranch, baseUrl }) =
|
||||
};
|
||||
|
||||
const evaluateSwitchViewLink = () => {
|
||||
if (branches && selectedBranch && branches?.filter(b => b.name === selectedBranch).length !== 0) {
|
||||
if (branches && selectedBranch && branches?.filter((b) => b.name === selectedBranch).length !== 0) {
|
||||
return `${baseUrl}/branch/${encodeURIComponent(selectedBranch)}/changesets/`;
|
||||
}
|
||||
return `${baseUrl}/changesets/`;
|
||||
@@ -119,15 +121,15 @@ const Sources: FC<Props> = ({ repository, branches, selectedBranch, baseUrl }) =
|
||||
revision={revision || file.revision}
|
||||
path={path || ""}
|
||||
baseUrl={baseUrl + "/sources"}
|
||||
branch={branches?.filter(b => b.name === selectedBranch)[0]}
|
||||
defaultBranch={branches?.filter(b => b.defaultBranch === true)[0]}
|
||||
branch={branches?.filter((b) => b.name === selectedBranch)[0]}
|
||||
defaultBranch={branches?.filter((b) => b.defaultBranch === true)[0]}
|
||||
sources={file}
|
||||
permalink={permalink}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
if (file.directory) {
|
||||
if (file && file.directory) {
|
||||
let body;
|
||||
if (isRootFile(file) && isEmptyDirectory(file)) {
|
||||
body = (
|
||||
@@ -176,6 +178,7 @@ const Sources: FC<Props> = ({ repository, branches, selectedBranch, baseUrl }) =
|
||||
revision={revision || file.revision}
|
||||
path={path}
|
||||
breadcrumb={renderBreadcrumb()}
|
||||
error={error}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user