diff --git a/gradle/changelog/file_not_found_controls.yaml b/gradle/changelog/file_not_found_controls.yaml new file mode 100644 index 0000000000..34128197d0 --- /dev/null +++ b/gradle/changelog/file_not_found_controls.yaml @@ -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)) diff --git a/scm-ui/ui-webapp/src/repos/sources/containers/Content.tsx b/scm-ui/ui-webapp/src/repos/sources/containers/Content.tsx index 560bb58379..499e536e1a 100644 --- a/scm-ui/ui-webapp/src/repos/sources/containers/Content.tsx +++ b/scm-ui/ui-webapp/src/repos/sources/containers/Content.tsx @@ -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,80 +76,78 @@ const BorderLessDiv = styled.div` box-shadow: none; `; -export type SourceViewSelection = "source" | "history" | "annotations"; +export type SourceViewSelection = "source" | "annotations" | "history"; -class Content extends React.Component { - constructor(props: Props) { - super(props); +const Content: FC = ({ file, repository, revision, path, breadcrumb, error }) => { + const [t] = useTranslation("repos"); + const [collapsed, setCollapsed] = useState(true); + const [selected, setSelected] = useState("source"); + const [errorFromExtension, setErrorFromExtension] = useState(); - this.state = { - collapsed: true, - selected: "source" - }; - } - - toggleCollapse = () => { - this.setState(prevState => ({ - collapsed: !prevState.collapsed - })); + const wrapContent = (content: ReactNode) => { + return ( + <> +
+ {breadcrumb} + {content} +
+ + + ); }; - 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 ? ( 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 ( -
- - - {file.name} - - - {selector} - {content}} - tooltipStyle="htmlTitle" - /> - - -
+ +
+ + + {file.name} + + + {selector} + {content}} + tooltipStyle="htmlTitle" + /> + + +
+
); - } + }; - showMoreInformation() { - const collapsed = this.state.collapsed; - const { file, revision, t, repository } = this.props; - const date = ; + const showMoreInformation = () => { + const fileSize = file.directory ? null : ; const description = file.description ? (

{file.description.split("\n").map((item, key) => { @@ -167,7 +160,7 @@ class Content extends React.Component { })}

) : null; - const fileSize = file.directory ? "" : ; + if (!collapsed) { return ( <> @@ -188,7 +181,9 @@ class Content extends React.Component { {t("sources.content.commitDate")} - {date} + + + {t("sources.content.description")} @@ -200,7 +195,7 @@ class Content extends React.Component { props={{ file, repository, - revision + revision, }} /> @@ -211,38 +206,33 @@ class Content extends React.Component { ); } return null; + }; + + if (!file || error) { + return wrapContent(); } - render() { - const { file, revision, repository, path, breadcrumb } = this.props; - const { selected, errorFromExtension } = this.state; - - let content; - switch (selected) { - case "source": - content = ; - break; - case "history": - content = ; - break; - case "annotations": - content = ; - } - const header = this.showHeader(content); - const moreInformation = this.showMoreInformation(); - - return ( -
-
- {breadcrumb} -
{header}
- {moreInformation} - {content} -
- {errorFromExtension && } -
- ); + let body; + switch (selected) { + case "source": + body = ; + break; + case "annotations": + body = ; + break; + case "history": + body = ; } -} + const header = showHeader(body); + const moreInformation = showMoreInformation(); -export default withTranslation("repos")(Content); + return wrapContent( + <> + {header} + {moreInformation} + {body} + + ); +}; + +export default Content; diff --git a/scm-ui/ui-webapp/src/repos/sources/containers/Sources.tsx b/scm-ui/ui-webapp/src/repos/sources/containers/Sources.tsx index a4330d7cea..84323e6af3 100644 --- a/scm-ui/ui-webapp/src/repos/sources/containers/Sources.tsx +++ b/scm-ui/ui-webapp/src/repos/sources/containers/Sources.tsx @@ -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(); return { revision: revision ? decodeURIComponent(revision) : undefined, - path: path || "" + path: path || "", }; }; @@ -62,23 +62,25 @@ const Sources: FC = ({ 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 ; - } - - if (isLoading || !file) { + if (isLoading || (!error && !file)) { return ; } @@ -98,7 +100,7 @@ const Sources: FC = ({ 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 = ({ 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 = ({ repository, branches, selectedBranch, baseUrl }) = revision={revision || file.revision} path={path} breadcrumb={renderBreadcrumb()} + error={error} /> );