diff --git a/scm-ui/ui-webapp/public/locales/de/repos.json b/scm-ui/ui-webapp/public/locales/de/repos.json index b8510069c0..7647a67d60 100644 --- a/scm-ui/ui-webapp/public/locales/de/repos.json +++ b/scm-ui/ui-webapp/public/locales/de/repos.json @@ -95,7 +95,8 @@ "form": { "field": { "name": { - "label": "Name" + "label": "Name", + "error": "Dieser Tag existiert bereits." } } }, diff --git a/scm-ui/ui-webapp/public/locales/en/repos.json b/scm-ui/ui-webapp/public/locales/en/repos.json index 6cf48d8503..fde688516e 100644 --- a/scm-ui/ui-webapp/public/locales/en/repos.json +++ b/scm-ui/ui-webapp/public/locales/en/repos.json @@ -95,7 +95,8 @@ "form": { "field": { "name": { - "label": "Name" + "label": "Name", + "error": "This tag already exists." } } }, diff --git a/scm-ui/ui-webapp/src/repos/components/changesets/ChangesetDetails.tsx b/scm-ui/ui-webapp/src/repos/components/changesets/ChangesetDetails.tsx index 2b5284235d..b730b74101 100644 --- a/scm-ui/ui-webapp/src/repos/components/changesets/ChangesetDetails.tsx +++ b/scm-ui/ui-webapp/src/repos/components/changesets/ChangesetDetails.tsx @@ -26,7 +26,7 @@ import { Trans, useTranslation, WithTranslation, withTranslation } from "react-i import classNames from "classnames"; import styled from "styled-components"; import { ExtensionPoint } from "@scm-manager/ui-extensions"; -import { Changeset, Link, ParentChangeset, Repository } from "@scm-manager/ui-types"; +import {Changeset, Link, ParentChangeset, Repository, Tag} from "@scm-manager/ui-types"; import { AvatarImage, AvatarWrapper, @@ -42,22 +42,18 @@ import { Icon, Level, SignatureIcon, - Modal, - InputField, - apiClient + Tooltip, + ErrorNotification } from "@scm-manager/ui-components"; import ContributorTable from "./ContributorTable"; import { Link as ReactLink } from "react-router-dom"; +import CreateTagModal from "./CreateTagModal"; type Props = WithTranslation & { changeset: Changeset; repository: Repository; fileControlFactory?: FileControlFactory; - refetchChangeset?: (re) => void; -}; - -type State = { - collapsed: boolean; + refetchChangeset?: () => void; }; const RightMarginP = styled.p` @@ -91,7 +87,6 @@ const ContributorColumn = styled.p` text-overflow: ellipsis; white-space: nowrap; min-width: 0; - margin-right: 16px; `; const CountColumn = styled.p` @@ -113,7 +108,6 @@ const ContributorToggleLine = styled.p` const ChangesetSummary = styled.div` display: flex; - justify-content: space-between; `; const SeparatedParents = styled.div` @@ -152,7 +146,7 @@ const Contributors: FC<{ changeset: Changeset }> = ({ changeset }) => { {signatureIcon} - + ( {t("changeset.contributors.count", { count: countContributors(changeset) })} @@ -164,10 +158,10 @@ const Contributors: FC<{ changeset: Changeset }> = ({ changeset }) => { ); }; -const ChangesetDetails: FC = ({ changeset, repository, fileControlFactory, t , refetchChangeset}) => { +const ChangesetDetails: FC = ({ changeset, repository, fileControlFactory, t, refetchChangeset }) => { const [collapsed, setCollapsed] = useState(false); const [isTagCreationModalVisible, setTagCreationModalVisible] = useState(false); - const [newTagName, setNewTagName] = useState(""); + const [error, setError] = useState(); const description = changesets.parseDescription(changeset.description); const id = ; @@ -183,22 +177,9 @@ const ChangesetDetails: FC = ({ changeset, repository, fileControlFactory setCollapsed(!collapsed); }; - const createTag = () => { - apiClient - .post((changeset._links["tag"] as Link).href, { - revision: changeset.id, - name: newTagName - }) - .then(() => { - refetchChangeset?.(); - closeTagCreationModal(); - }); - }; - - const closeTagCreationModal = () => { - setNewTagName(""); - setTagCreationModalVisible(false); - }; + if (error) { + return ; + } return ( <> @@ -238,43 +219,33 @@ const ChangesetDetails: FC = ({ changeset, repository, fileControlFactory
-
- {showCreateButton && ( - - - - } - closeFunction={() => closeTagCreationModal()} - /> - )} -
+ + {showCreateButton && ( +
+ +
+ )} + {isTagCreationModalVisible && ( + tag.name)} + onClose={() => setTagCreationModalVisible(false)} + onCreated={() => { + refetchChangeset?.(); + setTagCreationModalVisible(false); + }} + onError={setError} + tagCreationLink={(changeset._links["tag"] as Link).href} + /> + )}

{description.message.split("\n").map((item, key) => { diff --git a/scm-ui/ui-webapp/src/repos/components/changesets/CreateTagModal.tsx b/scm-ui/ui-webapp/src/repos/components/changesets/CreateTagModal.tsx new file mode 100644 index 0000000000..1e83d784a0 --- /dev/null +++ b/scm-ui/ui-webapp/src/repos/components/changesets/CreateTagModal.tsx @@ -0,0 +1,88 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import React, {FC, useEffect, useState} from "react"; +import { Modal, InputField, Button, apiClient } from "@scm-manager/ui-components"; +import { WithTranslation, withTranslation } from "react-i18next"; + +type Props = WithTranslation & { + tagCreationLink: string; + tagNames: string[]; + onClose: () => void; + onCreated: () => void; + onError: (error: Error) => void; + revision: string; +}; + +const CreateTagModal: FC = ({ t, onClose, tagCreationLink, onCreated, onError, revision, tagNames }) => { + const [newTagName, setNewTagName] = useState(""); + const [loading, setLoading] = useState(false); + + const createTag = () => { + setLoading(true); + apiClient + .post(tagCreationLink, { + revision, + name: newTagName + }) + .then(onCreated) + .catch(onError) + .finally(() => setLoading(false)); + }; + + const isInvalid = () => { + return tagNames.includes(newTagName); + }; + + return ( + + setNewTagName(val)} + value={newTagName} + validationError={isInvalid()} + errorMessage={t("tags.create.form.field.name.error")} + /> +

{t("tags.create.hint")}
+ + } + footer={ + <> + + + + } + closeFunction={onClose} + /> + ); +}; + +export default withTranslation("repos")(CreateTagModal);