mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-12 00:15:44 +01:00
refactor UI
This commit is contained in:
98
scm-ui/ui-webapp/src/repos/components/ImportFromUrlForm.tsx
Normal file
98
scm-ui/ui-webapp/src/repos/components/ImportFromUrlForm.tsx
Normal file
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* 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, useState } from "react";
|
||||
import { RepositoryUrlImport } from "@scm-manager/ui-types";
|
||||
import { InputField, validation } from "@scm-manager/ui-components";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import styled from "styled-components";
|
||||
|
||||
type Props = {
|
||||
repository: RepositoryUrlImport;
|
||||
onChange: (repository: RepositoryUrlImport) => void;
|
||||
setValid: (valid: boolean) => void;
|
||||
};
|
||||
|
||||
const Column = styled.div`
|
||||
padding: 0 0.75rem;
|
||||
`;
|
||||
|
||||
const Columns = styled.div`
|
||||
padding: 0.75rem 0 0;
|
||||
`;
|
||||
|
||||
const ImportFromUrlForm: FC<Props> = ({ repository, onChange, setValid }) => {
|
||||
const [t] = useTranslation("repos");
|
||||
const [urlValidationError, setUrlValidationError] = useState(false);
|
||||
|
||||
const handleImportUrlChange = (importUrl: string) => {
|
||||
const changedRepo = { ...repository, importUrl };
|
||||
|
||||
if (!repository.name) {
|
||||
// If the repository name is not fill we set a name suggestion
|
||||
const match = importUrl.match(/([^\/]+?)(?:.git)?$/);
|
||||
if (match && match[1]) {
|
||||
changedRepo.name = match[1];
|
||||
}
|
||||
}
|
||||
onChange(changedRepo);
|
||||
const valid = validation.isUrlValid(importUrl);
|
||||
setUrlValidationError(!valid);
|
||||
setValid(valid);
|
||||
};
|
||||
|
||||
return (
|
||||
<Columns className="columns is-multiline">
|
||||
<Column className="column is-full">
|
||||
<InputField
|
||||
label={t("import.importUrl")}
|
||||
onChange={handleImportUrlChange}
|
||||
value={repository.importUrl}
|
||||
helpText={t("help.importUrlHelpText")}
|
||||
validationError={urlValidationError}
|
||||
errorMessage={t("validation.url-invalid")}
|
||||
/>
|
||||
</Column>
|
||||
<Column className="column is-half">
|
||||
<InputField
|
||||
label={t("import.username")}
|
||||
onChange={username => onChange({ ...repository, username })}
|
||||
value={repository.username}
|
||||
helpText={t("help.usernameHelpText")}
|
||||
/>
|
||||
</Column>
|
||||
<Column className="column is-half">
|
||||
<InputField
|
||||
label={t("import.password")}
|
||||
onChange={password => onChange({ ...repository, password })}
|
||||
value={repository.password}
|
||||
type="password"
|
||||
helpText={t("help.passwordHelpText")}
|
||||
/>
|
||||
</Column>
|
||||
</Columns>
|
||||
);
|
||||
};
|
||||
|
||||
export default ImportFromUrlForm;
|
||||
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* 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, useState } from "react";
|
||||
import NamespaceAndNameFields from "./NamespaceAndNameFields";
|
||||
import { Repository, RepositoryUrlImport } from "@scm-manager/ui-types";
|
||||
import ImportFromUrlForm from "./ImportFromUrlForm";
|
||||
import RepositoryInformationForm from "./RepositoryInformationForm";
|
||||
import { apiClient, ErrorNotification, Level, SubmitButton } from "@scm-manager/ui-components";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useHistory } from "react-router-dom";
|
||||
|
||||
type Props = {
|
||||
url: string;
|
||||
setImportPending: (pending: boolean) => void;
|
||||
};
|
||||
|
||||
const ImportRepositoryFromUrl: FC<Props> = ({ url, setImportPending }) => {
|
||||
const [repo, setRepo] = useState<RepositoryUrlImport>({
|
||||
name: "",
|
||||
namespace: "",
|
||||
type: "",
|
||||
contact: "",
|
||||
description: "",
|
||||
importUrl: "",
|
||||
username: "",
|
||||
password: "",
|
||||
_links: {}
|
||||
});
|
||||
|
||||
const [valid, setValid] = useState({ namespaceAndName: false, contact: true, importUrl: false });
|
||||
const isValid = () => {
|
||||
return Object.values(valid).every(v => v);
|
||||
};
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<Error | undefined>();
|
||||
const history = useHistory();
|
||||
const [t] = useTranslation("repos");
|
||||
|
||||
const handleImportLoading = (loading: boolean) => {
|
||||
setLoading(loading);
|
||||
setImportPending(loading);
|
||||
};
|
||||
|
||||
const submit = () => {
|
||||
handleImportLoading(true);
|
||||
apiClient
|
||||
.post(url, repo, "application/vnd.scmm-repository+json;v=2")
|
||||
.then(response => {
|
||||
const location = response.headers.get("Location");
|
||||
// @ts-ignore Location is always set if the repository import was successful
|
||||
return apiClient.get(location);
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(repo => history.push(`/repo/${repo.namespace}/${repo.name}/code/sources`))
|
||||
.catch(error => {
|
||||
setError(error);
|
||||
handleImportLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<form onSubmit={submit}>
|
||||
<ErrorNotification error={error} />
|
||||
<ImportFromUrlForm
|
||||
repository={repo}
|
||||
onChange={setRepo}
|
||||
setValid={(importUrl: boolean) => setValid({ ...valid, importUrl })}
|
||||
/>
|
||||
<hr />
|
||||
<NamespaceAndNameFields
|
||||
repository={repo}
|
||||
onChange={setRepo as React.Dispatch<React.SetStateAction<Repository>>}
|
||||
setValid={(namespaceAndName: boolean) => setValid({ ...valid, namespaceAndName })}
|
||||
/>
|
||||
<RepositoryInformationForm
|
||||
repository={repo}
|
||||
onChange={setRepo as React.Dispatch<React.SetStateAction<Repository>>}
|
||||
disabled={false}
|
||||
setValid={(contact: boolean) => setValid({ ...valid, contact })}
|
||||
/>
|
||||
<Level
|
||||
right={<SubmitButton disabled={!isValid()} loading={loading} label={t("repositoryForm.submitImport")} />}
|
||||
/>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
export default ImportRepositoryFromUrl;
|
||||
@@ -24,6 +24,7 @@
|
||||
import React, { FC } from "react";
|
||||
import { RepositoryType, Link } from "@scm-manager/ui-types";
|
||||
import { Radio } from "@scm-manager/ui-components";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
type Props = {
|
||||
repositoryType: RepositoryType;
|
||||
@@ -31,7 +32,8 @@ type Props = {
|
||||
setImportType: (type: string) => void;
|
||||
};
|
||||
|
||||
const ImportTypeSelect: FC<Props> = ({repositoryType, importType, setImportType}) => {
|
||||
const ImportTypeSelect: FC<Props> = ({ repositoryType, importType, setImportType }) => {
|
||||
const [t] = useTranslation("repos");
|
||||
|
||||
const changeImportType = (checked: boolean, name?: string) => {
|
||||
if (name && checked) {
|
||||
@@ -39,16 +41,17 @@ const ImportTypeSelect: FC<Props> = ({repositoryType, importType, setImportType}
|
||||
}
|
||||
};
|
||||
|
||||
//TODO Add helptext translation
|
||||
return (
|
||||
<>
|
||||
{(repositoryType._links.import as Link[]).map(type => (
|
||||
{(repositoryType._links.import as Link[]).map((type, index) => (
|
||||
<Radio
|
||||
name={type.name}
|
||||
checked={importType === type.name}
|
||||
value={type.name}
|
||||
label={type.name}
|
||||
label={t(`import.importTypes.${type.name}.label`)}
|
||||
helpText={t(`import.importTypes.${type.name}.helpText`)}
|
||||
onChange={changeImportType}
|
||||
key={index}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
|
||||
117
scm-ui/ui-webapp/src/repos/components/NamespaceAndNameFields.tsx
Normal file
117
scm-ui/ui-webapp/src/repos/components/NamespaceAndNameFields.tsx
Normal file
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* 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 { Repository } from "@scm-manager/ui-types";
|
||||
import { CUSTOM_NAMESPACE_STRATEGY } from "../modules/repos";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { InputField } from "@scm-manager/ui-components";
|
||||
import { ExtensionPoint } from "@scm-manager/ui-extensions";
|
||||
import * as validator from "./form/repositoryValidation";
|
||||
import { getNamespaceStrategies } from "../../admin/modules/namespaceStrategies";
|
||||
import { connect } from "react-redux";
|
||||
|
||||
type Props = {
|
||||
repository: Repository;
|
||||
onChange: (repository: Repository) => void;
|
||||
namespaceStrategy: string;
|
||||
setValid: (valid: boolean) => void;
|
||||
};
|
||||
|
||||
const NamespaceAndNameFields: FC<Props> = ({ repository, onChange, namespaceStrategy, setValid }) => {
|
||||
const [t] = useTranslation("repos");
|
||||
const [namespaceValidationError, setNamespaceValidationError] = useState(false);
|
||||
const [nameValidationError, setNameValidationError] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
//TODO fix validation
|
||||
if (repository.name) {
|
||||
const valid = validator.isNameValid(repository.name);
|
||||
setNameValidationError(!valid);
|
||||
onFieldChange();
|
||||
}
|
||||
}, [repository.name]);
|
||||
|
||||
const handleNamespaceChange = (namespace: string) => {
|
||||
const valid = validator.isNamespaceValid(namespace);
|
||||
setNamespaceValidationError(!valid);
|
||||
onFieldChange(valid);
|
||||
onChange({ ...repository, namespace });
|
||||
};
|
||||
|
||||
const handleNameChange = (name: string) => {
|
||||
const valid = validator.isNameValid(name);
|
||||
setNameValidationError(!valid);
|
||||
onFieldChange();
|
||||
onChange({ ...repository, name });
|
||||
};
|
||||
|
||||
//TODO fix validation
|
||||
const onFieldChange = (namespaceValid?: boolean) => {
|
||||
if (namespaceStrategy === CUSTOM_NAMESPACE_STRATEGY && !validator.isNamespaceValid(repository.namespace)) {
|
||||
setValid(false);
|
||||
} else {
|
||||
setValid(!nameValidationError && !namespaceValidationError);
|
||||
}
|
||||
};
|
||||
|
||||
const renderNamespaceField = () => {
|
||||
const props = {
|
||||
label: t("repository.namespace"),
|
||||
helpText: t("help.namespaceHelpText"),
|
||||
value: repository ? repository.namespace : "",
|
||||
onChange: handleNamespaceChange,
|
||||
errorMessage: t("validation.namespace-invalid"),
|
||||
validationError: namespaceValidationError
|
||||
};
|
||||
|
||||
if (namespaceStrategy === CUSTOM_NAMESPACE_STRATEGY) {
|
||||
return <InputField {...props} />;
|
||||
}
|
||||
|
||||
return <ExtensionPoint name="repos.create.namespace" props={props} renderAll={false} />;
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{renderNamespaceField()}
|
||||
<InputField
|
||||
label={t("repository.name")}
|
||||
onChange={handleNameChange}
|
||||
value={repository ? repository.name : ""}
|
||||
validationError={nameValidationError}
|
||||
errorMessage={t("validation.name-invalid")}
|
||||
helpText={t("help.nameHelpText")}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const mapStateToProps = (state: any) => {
|
||||
const namespaceStrategy = getNamespaceStrategies(state).current;
|
||||
return {
|
||||
namespaceStrategy
|
||||
};
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps)(NamespaceAndNameFields);
|
||||
@@ -1,93 +0,0 @@
|
||||
/*
|
||||
* 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, useState } from "react";
|
||||
import { InputField } from "@scm-manager/ui-components";
|
||||
import styled from "styled-components";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
type Props = {
|
||||
url: string;
|
||||
};
|
||||
|
||||
const Column = styled.div`
|
||||
padding: 0 0.75rem;
|
||||
`;
|
||||
|
||||
const Columns = styled.div`
|
||||
padding: 0.75rem 0 0;
|
||||
`;
|
||||
|
||||
const RepositoryImportFromUrl: FC<Props> = ({}) => {
|
||||
const [name, setName] = useState("");
|
||||
const [importUrl, setImportUrl] = useState("");
|
||||
const [username, setUsername] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
const [t] = useTranslation("repos");
|
||||
|
||||
const handleImportUrlChange = (url: string) => {
|
||||
if (!name) {
|
||||
// If the repository name is not fill we set a name suggestion
|
||||
const match = url.match(/([^\/]+)(\.git)?/i);
|
||||
if (match && match[1]) {
|
||||
setName(match[1]);
|
||||
}
|
||||
}
|
||||
setImportUrl(url);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Columns className="columns is-multiline">
|
||||
<Column className="column is-full">
|
||||
<InputField
|
||||
label={t("import.importUrl")}
|
||||
onChange={handleImportUrlChange}
|
||||
value={importUrl}
|
||||
helpText={t("help.importUrlHelpText")}
|
||||
/>
|
||||
</Column>
|
||||
<Column className="column is-half">
|
||||
<InputField
|
||||
label={t("import.username")}
|
||||
onChange={setUsername}
|
||||
value={username}
|
||||
helpText={t("help.usernameHelpText")}
|
||||
/>
|
||||
</Column>
|
||||
<Column className="column is-half">
|
||||
<InputField
|
||||
label={t("import.password")}
|
||||
onChange={setPassword}
|
||||
value={password}
|
||||
type="password"
|
||||
helpText={t("help.passwordHelpText")}
|
||||
/>
|
||||
</Column>
|
||||
</Columns>
|
||||
<hr />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default RepositoryImportFromUrl;
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* 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, useState } from "react";
|
||||
import { InputField, Textarea } from "@scm-manager/ui-components";
|
||||
import { Repository } from "@scm-manager/ui-types";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import * as validator from "./form/repositoryValidation";
|
||||
|
||||
type Props = {
|
||||
repository: Repository;
|
||||
onChange: (repository: Repository) => void;
|
||||
disabled: boolean;
|
||||
setValid: (valid: boolean) => void;
|
||||
};
|
||||
|
||||
const RepositoryInformationForm: FC<Props> = ({ repository, onChange, disabled, setValid }) => {
|
||||
const [t] = useTranslation("repos");
|
||||
const [contactValidationError, setContactValidationError] = useState(false);
|
||||
|
||||
const handleContactChange = (contact: string) => {
|
||||
const valid = validator.isContactValid(contact);
|
||||
setContactValidationError(!valid);
|
||||
setValid(valid);
|
||||
onChange({ ...repository, contact });
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<InputField
|
||||
label={t("repository.contact")}
|
||||
onChange={handleContactChange}
|
||||
value={repository ? repository.contact : ""}
|
||||
validationError={contactValidationError}
|
||||
errorMessage={t("validation.contact-invalid")}
|
||||
helpText={t("help.contactHelpText")}
|
||||
disabled={disabled}
|
||||
/>
|
||||
<Textarea
|
||||
label={t("repository.description")}
|
||||
onChange={description => onChange({ ...repository, description })}
|
||||
value={repository ? repository.description : ""}
|
||||
helpText={t("help.descriptionHelpText")}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default RepositoryInformationForm;
|
||||
@@ -25,10 +25,11 @@ import React, { FC, useEffect, useState } from "react";
|
||||
import styled from "styled-components";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { ExtensionPoint } from "@scm-manager/ui-extensions";
|
||||
import { Repository, RepositoryImport, RepositoryType } from "@scm-manager/ui-types";
|
||||
import { Checkbox, InputField, Level, Select, SubmitButton, Textarea } from "@scm-manager/ui-components";
|
||||
import * as validator from "./repositoryValidation";
|
||||
import { Repository, RepositoryType } from "@scm-manager/ui-types";
|
||||
import { Checkbox, Level, Select, SubmitButton } from "@scm-manager/ui-components";
|
||||
import { CUSTOM_NAMESPACE_STRATEGY } from "../../modules/repos";
|
||||
import NamespaceAndNameFields from "../NamespaceAndNameFields";
|
||||
import RepositoryInformationForm from "../RepositoryInformationForm";
|
||||
|
||||
const CheckboxWrapper = styled.div`
|
||||
margin-top: 2em;
|
||||
@@ -44,17 +45,14 @@ const SpaceBetween = styled.div`
|
||||
justify-content: space-between;
|
||||
`;
|
||||
|
||||
|
||||
type Props = {
|
||||
createRepository?: (repo: RepositoryCreation, shouldInit: boolean) => void;
|
||||
importRepository?: (repo: RepositoryImport) => void;
|
||||
modifyRepository?: (repo: Repository) => void;
|
||||
repository?: Repository;
|
||||
repositoryTypes?: RepositoryType[];
|
||||
namespaceStrategy?: string;
|
||||
loading?: boolean;
|
||||
indexResources?: any;
|
||||
creationMode?: "CREATE" | "IMPORT";
|
||||
};
|
||||
|
||||
type RepositoryCreation = Repository & {
|
||||
@@ -64,13 +62,11 @@ type RepositoryCreation = Repository & {
|
||||
const RepositoryForm: FC<Props> = ({
|
||||
createRepository,
|
||||
modifyRepository,
|
||||
importRepository,
|
||||
repository,
|
||||
repositoryTypes,
|
||||
namespaceStrategy,
|
||||
loading,
|
||||
indexResources,
|
||||
creationMode
|
||||
indexResources
|
||||
}) => {
|
||||
const [repo, setRepo] = useState<Repository>({
|
||||
name: "",
|
||||
@@ -81,14 +77,9 @@ const RepositoryForm: FC<Props> = ({
|
||||
_links: {}
|
||||
});
|
||||
const [initRepository, setInitRepository] = useState(false);
|
||||
const [namespaceValidationError, setNamespaceValidationError] = useState(false);
|
||||
const [nameValidationError, setNameValidationError] = useState(false);
|
||||
const [contactValidationError, setContactValidationError] = useState(false);
|
||||
const [contextEntries, setContextEntries] = useState({});
|
||||
const [importUrl, setImportUrl] = useState("");
|
||||
const [username, setUsername] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
|
||||
//TODO fix validation
|
||||
const [valid, setValid] = useState({ namespaceAndName: false, contact: true });
|
||||
const [t] = useTranslation("repos");
|
||||
|
||||
useEffect(() => {
|
||||
@@ -97,29 +88,21 @@ const RepositoryForm: FC<Props> = ({
|
||||
}
|
||||
}, [repository]);
|
||||
|
||||
const isImportMode = () => creationMode === "IMPORT";
|
||||
const isCreateMode = () => creationMode === "CREATE";
|
||||
const isEditMode = () => !!repository;
|
||||
const isModifiable = () => !!repository && !!repository._links.update;
|
||||
const disabled = (!isModifiable() && isEditMode()) || loading;
|
||||
|
||||
const isValid = () => {
|
||||
return !(
|
||||
namespaceValidationError ||
|
||||
nameValidationError ||
|
||||
contactValidationError ||
|
||||
!repo.name ||
|
||||
(namespaceStrategy === CUSTOM_NAMESPACE_STRATEGY && !repo.namespace) ||
|
||||
(isImportMode() && !importUrl)
|
||||
return (
|
||||
!(!repo.name || (namespaceStrategy === CUSTOM_NAMESPACE_STRATEGY && !repo.namespace)) &&
|
||||
Object.values(valid).every(v => v)
|
||||
);
|
||||
};
|
||||
|
||||
const submit = (event: React.FormEvent<HTMLFormElement>) => {
|
||||
event.preventDefault();
|
||||
if (isValid()) {
|
||||
if (importRepository && isImportMode()) {
|
||||
importRepository({ ...repo, url: importUrl, username, password });
|
||||
} else if (createRepository && isCreateMode()) {
|
||||
if (createRepository) {
|
||||
createRepository({ ...repo, contextEntries }, initRepository);
|
||||
} else if (modifyRepository) {
|
||||
modifyRepository(repo);
|
||||
@@ -139,23 +122,6 @@ const RepositoryForm: FC<Props> = ({
|
||||
return [];
|
||||
};
|
||||
|
||||
const renderNamespaceField = () => {
|
||||
const props = {
|
||||
label: t("repository.namespace"),
|
||||
helpText: t("help.namespaceHelpText"),
|
||||
value: repo ? repo.namespace : "",
|
||||
onChange: handleNamespaceChange,
|
||||
errorMessage: t("validation.namespace-invalid"),
|
||||
validationError: namespaceValidationError
|
||||
};
|
||||
|
||||
if (namespaceStrategy === CUSTOM_NAMESPACE_STRATEGY) {
|
||||
return <InputField {...props} />;
|
||||
}
|
||||
|
||||
return <ExtensionPoint name="repos.create.namespace" props={props} renderAll={false} />;
|
||||
};
|
||||
|
||||
const renderCreateOnlyFields = () => {
|
||||
if (isEditMode()) {
|
||||
return null;
|
||||
@@ -167,15 +133,10 @@ const RepositoryForm: FC<Props> = ({
|
||||
};
|
||||
return (
|
||||
<>
|
||||
{renderNamespaceField()}
|
||||
<InputField
|
||||
label={t("repository.name")}
|
||||
onChange={handleNameChange}
|
||||
value={repo ? repo.name : ""}
|
||||
validationError={nameValidationError}
|
||||
errorMessage={t("validation.name-invalid")}
|
||||
helpText={t("help.nameHelpText")}
|
||||
disabled={disabled}
|
||||
<NamespaceAndNameFields
|
||||
repository={repo}
|
||||
onChange={setRepo}
|
||||
setValid={namespaceAndName => setValid({ ...valid, namespaceAndName })}
|
||||
/>
|
||||
<SpaceBetween>
|
||||
<SelectWrapper>
|
||||
@@ -188,20 +149,18 @@ const RepositoryForm: FC<Props> = ({
|
||||
disabled={disabled}
|
||||
/>
|
||||
</SelectWrapper>
|
||||
{!isImportMode() && (
|
||||
<CheckboxWrapper>
|
||||
<Checkbox
|
||||
label={t("repositoryForm.initializeRepository")}
|
||||
checked={initRepository}
|
||||
onChange={toggleInitCheckbox}
|
||||
helpText={t("help.initializeRepository")}
|
||||
disabled={disabled}
|
||||
/>
|
||||
{initRepository && (
|
||||
<ExtensionPoint name="repos.create.initialize" props={extensionProps} renderAll={true} />
|
||||
)}
|
||||
</CheckboxWrapper>
|
||||
)}
|
||||
<CheckboxWrapper>
|
||||
<Checkbox
|
||||
label={t("repositoryForm.initializeRepository")}
|
||||
checked={initRepository}
|
||||
onChange={toggleInitCheckbox}
|
||||
helpText={t("help.initializeRepository")}
|
||||
disabled={disabled}
|
||||
/>
|
||||
{initRepository && (
|
||||
<ExtensionPoint name="repos.create.initialize" props={extensionProps} renderAll={true} />
|
||||
)}
|
||||
</CheckboxWrapper>
|
||||
</SpaceBetween>
|
||||
</>
|
||||
);
|
||||
@@ -218,52 +177,28 @@ const RepositoryForm: FC<Props> = ({
|
||||
});
|
||||
};
|
||||
|
||||
const handleNamespaceChange = (namespace: string) => {
|
||||
setNamespaceValidationError(!validator.isNamespaceValid(namespace));
|
||||
setRepo({ ...repo, namespace });
|
||||
};
|
||||
|
||||
const handleNameChange = (name: string) => {
|
||||
setNameValidationError(!validator.isNameValid(name));
|
||||
setRepo({ ...repo, name });
|
||||
};
|
||||
|
||||
const handleContactChange = (contact: string) => {
|
||||
setContactValidationError(!validator.isContactValid(contact));
|
||||
setRepo({ ...repo, contact });
|
||||
};
|
||||
|
||||
const submitButton = () => {
|
||||
if (disabled) {
|
||||
return null;
|
||||
}
|
||||
const translationKey = isImportMode() ? "repositoryForm.submitImport" : "repositoryForm.submitCreate";
|
||||
return <Level right={<SubmitButton disabled={!isValid()} loading={loading} label={t(translationKey)} />} />;
|
||||
return (
|
||||
<Level
|
||||
right={<SubmitButton disabled={!isValid()} loading={loading} label={t("repositoryForm.submitCreate")} />}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<form onSubmit={submit}>
|
||||
{renderCreateOnlyFields()}
|
||||
<InputField
|
||||
label={t("repository.contact")}
|
||||
onChange={handleContactChange}
|
||||
value={repo ? repo.contact : ""}
|
||||
validationError={contactValidationError}
|
||||
errorMessage={t("validation.contact-invalid")}
|
||||
helpText={t("help.contactHelpText")}
|
||||
disabled={disabled}
|
||||
/>
|
||||
<Textarea
|
||||
label={t("repository.description")}
|
||||
onChange={description => setRepo({ ...repo, description })}
|
||||
value={repo ? repo.description : ""}
|
||||
helpText={t("help.descriptionHelpText")}
|
||||
disabled={disabled}
|
||||
/>
|
||||
{submitButton()}
|
||||
</form>
|
||||
</>
|
||||
<form onSubmit={submit}>
|
||||
{renderCreateOnlyFields()}
|
||||
<RepositoryInformationForm
|
||||
repository={repo}
|
||||
onChange={setRepo}
|
||||
disabled={!createRepository}
|
||||
setValid={contact => setValid({ ...valid, contact })}
|
||||
/>
|
||||
{submitButton()}
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user