From 0d0f9995feced505bbcd5e27328e520413a62b27 Mon Sep 17 00:00:00 2001 From: Florian Scholdei Date: Tue, 13 Jul 2021 11:40:49 +0200 Subject: [PATCH] Add files to empty repository (#1717) It should also be possible to create new files in empty non-initiated repositories with the help of scm-manager/scm-editor-plugin/pull/39. So that the plugin can mount itself, a new endpoint was provided hereby. Co-authored-by: Eduard Heimbuch --- gradle/changelog/add_files_to_empty_repo.yaml | 2 + scm-ui/ui-components/src/Breadcrumb.tsx | 112 +++++++++++------- .../src/__snapshots__/storyshots.test.ts.snap | 40 ++++--- scm-ui/ui-extensions/src/extensionPoints.ts | 25 +++- .../codeSection/containers/CodeOverview.tsx | 4 +- .../src/repos/sources/containers/Sources.tsx | 86 ++++++++------ .../src/repos/sources/utils/files.ts | 4 +- 7 files changed, 168 insertions(+), 105 deletions(-) create mode 100644 gradle/changelog/add_files_to_empty_repo.yaml diff --git a/gradle/changelog/add_files_to_empty_repo.yaml b/gradle/changelog/add_files_to_empty_repo.yaml new file mode 100644 index 0000000000..85e2599a89 --- /dev/null +++ b/gradle/changelog/add_files_to_empty_repo.yaml @@ -0,0 +1,2 @@ +- type: Added + description: Create files in empty non-initiated repositories ([#1717](https://github.com/scm-manager/scm-manager/pull/1717)) diff --git a/scm-ui/ui-components/src/Breadcrumb.tsx b/scm-ui/ui-components/src/Breadcrumb.tsx index 259c79248e..3d6ca53cc8 100644 --- a/scm-ui/ui-components/src/Breadcrumb.tsx +++ b/scm-ui/ui-components/src/Breadcrumb.tsx @@ -26,12 +26,12 @@ import { useTranslation } from "react-i18next"; import { useHistory, useLocation, Link } from "react-router-dom"; import classNames from "classnames"; import styled from "styled-components"; -import { binder, ExtensionPoint } from "@scm-manager/ui-extensions"; +import { urls } from "@scm-manager/ui-api"; import { Branch, Repository, File } from "@scm-manager/ui-types"; +import { binder, ExtensionPoint, extensionPoints } from "@scm-manager/ui-extensions"; import Icon from "./Icon"; import Tooltip from "./Tooltip"; import copyToClipboard from "./CopyToClipboard"; -import { urls } from "@scm-manager/ui-api"; type Props = { repository: Repository; @@ -65,8 +65,6 @@ const BreadcrumbNav = styled.nav` flex: 1; display: flex; align-items: center; - margin: 1rem 1rem !important; - width: 100%; /* move slash to end */ @@ -127,7 +125,7 @@ const Breadcrumb: FC = ({ baseUrl, sources, permalink, - preButtons + preButtons, }) => { const location = useLocation(); const history = useHistory(); @@ -138,7 +136,10 @@ const Breadcrumb: FC = ({ if (path) { const paths = path.split("/"); return paths.map((pathFragment, index) => { - const currPath = paths.slice(0, index + 1).join("/"); + let currPath = paths.slice(0, index + 1).join("/"); + if (!currPath.endsWith("/")) { + currPath = currPath + "/"; + } if (paths.length - 1 === index) { return (
  • @@ -172,15 +173,45 @@ const Breadcrumb: FC = ({ ).finally(() => setCopying(false)); }; - let homeUrl = baseUrl + "/"; - if (revision) { - homeUrl += encodeURIComponent(revision) + "/"; - } + const renderBreadcrumbNav = () => { + let prefixButtons = null; + if (preButtons) { + prefixButtons = {preButtons}; + } - let prefixButtons = null; - if (preButtons) { - prefixButtons = {preButtons}; - } + let homeUrl = baseUrl + "/"; + if (revision) { + homeUrl += encodeURIComponent(revision) + "/"; + } else { + homeUrl = `/repo/${repository.namespace}/${repository.name}/code/sources/`; + } + + return ( + + {prefixButtons} +
      +
    • + + + +
    • + {pathSection()} +
    + + {copying ? ( + + ) : ( + + copySource()} /> + + )} + +
    + ); + }; const extProps = { baseUrl, @@ -188,40 +219,33 @@ const Breadcrumb: FC = ({ branch: branch ? branch : defaultBranch, path, sources, - repository + repository, + }; + + const renderExtensionPoints = () => { + if ( + binder.hasExtension("repos.sources.empty.actionbar") && + sources?._embedded?.children?.length === 0 + ) { + return ( + + ); + } + if (binder.hasExtension("repos.sources.actionbar")) { + return ; + } + return null; }; return ( <> -
    - - {prefixButtons} -
      -
    • - - - -
    • - {pathSection()} -
    - - {copying ? ( - - ) : ( - - copySource()} /> - - )} - -
    - {binder.hasExtension("repos.sources.actionbar") && ( - - - - )} +
    + {renderBreadcrumbNav()} + {{renderExtensionPoints()}}

    diff --git a/scm-ui/ui-components/src/__snapshots__/storyshots.test.ts.snap b/scm-ui/ui-components/src/__snapshots__/storyshots.test.ts.snap index a282bb69f0..3536bda8b8 100644 --- a/scm-ui/ui-components/src/__snapshots__/storyshots.test.ts.snap +++ b/scm-ui/ui-components/src/__snapshots__/storyshots.test.ts.snap @@ -1663,11 +1663,11 @@ exports[`Storyshots BreadCrumb Default 1`] = ` className="Breadcrumbstories__Wrapper-sc-1eq8sgz-0 exRctT" >
    +

    +

    ; export type RepositoryFlags = ExtensionPointDefinition<"repository.flags", { repository: Repository }>; + +export type ReposSourcesActionbarExtensionProps = { + baseUrl: string; + revision: string; + branch: Branch | undefined; + path: string; + sources: File; + repository: Repository; +}; +export type ReposSourcesActionbarExtension = React.ComponentType; +export type ReposSourcesActionbar = ExtensionPointDefinition<"repos.sources.actionbar", ReposSourcesActionbarExtension>; + +export type ReposSourcesEmptyActionbarExtensionProps = { + sources: File; + repository: Repository; +}; +export type ReposSourcesEmptyActionbarExtension = ReposSourcesActionbarExtension; +export type ReposSourcesEmptyActionbar = ExtensionPointDefinition< + "repos.sources.empty.actionbar", + ReposSourcesEmptyActionbarExtension +>; diff --git a/scm-ui/ui-webapp/src/repos/codeSection/containers/CodeOverview.tsx b/scm-ui/ui-webapp/src/repos/codeSection/containers/CodeOverview.tsx index ec1af04964..f5eafe3b38 100644 --- a/scm-ui/ui-webapp/src/repos/codeSection/containers/CodeOverview.tsx +++ b/scm-ui/ui-webapp/src/repos/codeSection/containers/CodeOverview.tsx @@ -26,7 +26,7 @@ import { Route, useLocation } from "react-router-dom"; import Sources from "../../sources/containers/Sources"; import ChangesetsRoot from "../../containers/ChangesetsRoot"; import { Branch, Repository } from "@scm-manager/ui-types"; -import { ErrorPage, Loading, Notification } from "@scm-manager/ui-components"; +import { ErrorPage, Loading } from "@scm-manager/ui-components"; import { useTranslation } from "react-i18next"; import { useBranches } from "@scm-manager/ui-api"; import FileSearch from "./FileSearch"; @@ -71,7 +71,7 @@ const CodeOverviewWithBranches: FC = ({ repository, baseUrl }) => { } if (branches.length === 0) { - return {t("code.noBranches")}; + return ; } return ; 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 6375436bec..4a8fdfc0f3 100644 --- a/scm-ui/ui-webapp/src/repos/sources/containers/Sources.tsx +++ b/scm-ui/ui-webapp/src/repos/sources/containers/Sources.tsx @@ -22,17 +22,17 @@ * SOFTWARE. */ import React, { FC, useEffect } from "react"; +import { useTranslation } from "react-i18next"; +import { useHistory, useLocation, useParams } from "react-router-dom"; +import { useSources } from "@scm-manager/ui-api"; import { Branch, Repository } from "@scm-manager/ui-types"; import { Breadcrumb, Loading, Notification } from "@scm-manager/ui-components"; import FileTree from "../components/FileTree"; import Content from "./Content"; import CodeActionBar from "../../codeSection/components/CodeActionBar"; import replaceBranchWithRevision from "../ReplaceBranchWithRevision"; -import { useSources } from "@scm-manager/ui-api"; -import { useHistory, useLocation, useParams } from "react-router-dom"; import FileSearchButton from "../../codeSection/components/FileSearchButton"; import { isEmptyDirectory, isRootFile } from "../utils/files"; -import { useTranslation } from "react-i18next"; type Props = { repository: Repository; @@ -84,6 +84,10 @@ const Sources: FC = ({ repository, branches, selectedBranch, baseUrl }) = return ; } + if (!file) { + return null; + } + const onSelectBranch = (branch?: Branch) => { let url; if (branch) { @@ -129,18 +133,17 @@ const Sources: FC = ({ repository, branches, selectedBranch, baseUrl }) = ); }; - if (file && file.directory) { - let body; - if (isRootFile(file) && isEmptyDirectory(file)) { - body = ( -
    - {t("sources.noSources")} -
    - ); - } else { - body = ( - <> - {renderBreadcrumb()} + const renderPanelContent = () => { + if (file.directory) { + let body; + if (isRootFile(file) && isEmptyDirectory(file)) { + body = ( +
    + {t("sources.noSources")} +
    + ); + } else { + body = ( = ({ repository, branches, selectedBranch, baseUrl }) = isFetchingNextPage={isFetchingNextPage} fetchNextPage={fetchNextPage} /> - + ); + } + + return ( +
    + {renderBreadcrumb()} + {body} +
    ); } return ( - <> + + ); + }; + + const hasBranchesWhenSupporting = (repository: Repository) => { + return !repository._links.branches || (branches && branches.length !== 0); + }; + + return ( + <> + {hasBranchesWhenSupporting(repository) && ( -
    {body}
    - - ); - } else { - return ( - <> - - - - ); - } + )} + {renderPanelContent()} + + ); }; export default Sources; diff --git a/scm-ui/ui-webapp/src/repos/sources/utils/files.ts b/scm-ui/ui-webapp/src/repos/sources/utils/files.ts index 2029a90ab7..eb3ca0f2d8 100644 --- a/scm-ui/ui-webapp/src/repos/sources/utils/files.ts +++ b/scm-ui/ui-webapp/src/repos/sources/utils/files.ts @@ -28,14 +28,14 @@ export const isRootPath = (path: string) => { }; export const isRootFile = (file: File) => { - if (!file.directory) { + if (!file?.directory) { return false; } return isRootPath(file.path); }; export const isEmptyDirectory = (file: File) => { - if (!file.directory) { + if (!file?.directory) { return false; } return (file._embedded?.children?.length || 0) === 0;