mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-09 06:55:47 +01:00
merge 2.0.0-m3
This commit is contained in:
@@ -1,10 +1,9 @@
|
||||
// @flow
|
||||
import React from "react";
|
||||
import { AsyncCreatable, Async } from "react-select";
|
||||
import type { AutocompleteObject, SelectValue } from "@scm-manager/ui-types";
|
||||
import {Async, AsyncCreatable} from "react-select";
|
||||
import type {AutocompleteObject, SelectValue} from "@scm-manager/ui-types";
|
||||
import LabelWithHelpIcon from "./forms/LabelWithHelpIcon";
|
||||
|
||||
|
||||
type Props = {
|
||||
loadSuggestions: string => Promise<AutocompleteObject>,
|
||||
valueSelected: SelectValue => void,
|
||||
@@ -17,12 +16,9 @@ type Props = {
|
||||
creatable?: boolean
|
||||
};
|
||||
|
||||
|
||||
type State = {};
|
||||
|
||||
class Autocomplete extends React.Component<Props, State> {
|
||||
|
||||
|
||||
static defaultProps = {
|
||||
placeholder: "Type here",
|
||||
loadingMessage: "Loading...",
|
||||
@@ -34,7 +30,11 @@ class Autocomplete extends React.Component<Props, State> {
|
||||
};
|
||||
|
||||
// We overwrite this to avoid running into a bug (https://github.com/JedWatson/react-select/issues/2944)
|
||||
isValidNewOption = (inputValue: string, selectValue: SelectValue, selectOptions: SelectValue[]) => {
|
||||
isValidNewOption = (
|
||||
inputValue: string,
|
||||
selectValue: SelectValue,
|
||||
selectOptions: SelectValue[]
|
||||
) => {
|
||||
const isNotDuplicated = !selectOptions
|
||||
.map(option => option.label)
|
||||
.includes(inputValue);
|
||||
@@ -43,12 +43,21 @@ class Autocomplete extends React.Component<Props, State> {
|
||||
};
|
||||
|
||||
render() {
|
||||
const { label, helpText, value, placeholder, loadingMessage, noOptionsMessage, loadSuggestions, creatable } = this.props;
|
||||
const {
|
||||
label,
|
||||
helpText,
|
||||
value,
|
||||
placeholder,
|
||||
loadingMessage,
|
||||
noOptionsMessage,
|
||||
loadSuggestions,
|
||||
creatable
|
||||
} = this.props;
|
||||
return (
|
||||
<div className="field">
|
||||
<LabelWithHelpIcon label={label} helpText={helpText} />
|
||||
<div className="control">
|
||||
{creatable?
|
||||
{creatable ? (
|
||||
<AsyncCreatable
|
||||
cacheOptions
|
||||
loadOptions={loadSuggestions}
|
||||
@@ -65,7 +74,7 @@ class Autocomplete extends React.Component<Props, State> {
|
||||
});
|
||||
}}
|
||||
/>
|
||||
:
|
||||
) : (
|
||||
<Async
|
||||
cacheOptions
|
||||
loadOptions={loadSuggestions}
|
||||
@@ -75,13 +84,11 @@ class Autocomplete extends React.Component<Props, State> {
|
||||
loadingMessage={() => loadingMessage}
|
||||
noOptionsMessage={() => noOptionsMessage}
|
||||
/>
|
||||
|
||||
}
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export default Autocomplete;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import { translate } from "react-i18next";
|
||||
import { BackendError, ForbiddenError, UnauthorizedError } from "./errors";
|
||||
import {translate} from "react-i18next";
|
||||
import {BackendError, ForbiddenError, UnauthorizedError} from "./errors";
|
||||
import Notification from "./Notification";
|
||||
import BackendErrorNotification from "./BackendErrorNotification";
|
||||
|
||||
@@ -10,35 +10,33 @@ type Props = {
|
||||
error?: Error
|
||||
};
|
||||
|
||||
|
||||
class ErrorNotification extends React.Component<Props> {
|
||||
render() {
|
||||
const { t, error } = this.props;
|
||||
if (error) {
|
||||
if (error instanceof BackendError) {
|
||||
return <BackendErrorNotification error={error} />
|
||||
return <BackendErrorNotification error={error} />;
|
||||
} else if (error instanceof UnauthorizedError) {
|
||||
return (
|
||||
<Notification type="danger">
|
||||
<strong>{t("error-notification.prefix")}:</strong>{" "}
|
||||
{t("error-notification.timeout")}{" "}
|
||||
<strong>{t("errorNotification.prefix")}:</strong>{" "}
|
||||
{t("errorNotification.timeout")}{" "}
|
||||
<a href="javascript:window.location.reload(true)">
|
||||
{t("error-notification.loginLink")}
|
||||
{t("errorNotification.loginLink")}
|
||||
</a>
|
||||
</Notification>
|
||||
);
|
||||
} else if (error instanceof ForbiddenError) {
|
||||
return (
|
||||
<Notification type="danger">
|
||||
<strong>{t("error-notification.prefix")}:</strong>{" "}
|
||||
{t("error-notification.forbidden")}
|
||||
<strong>{t("errorNotification.prefix")}:</strong>{" "}
|
||||
{t("errorNotification.forbidden")}
|
||||
</Notification>
|
||||
)
|
||||
} else
|
||||
{
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<Notification type="danger">
|
||||
<strong>{t("error-notification.prefix")}:</strong> {error.message}
|
||||
<strong>{t("errorNotification.prefix")}:</strong> {error.message}
|
||||
</Notification>
|
||||
);
|
||||
}
|
||||
@@ -47,4 +45,4 @@ class ErrorNotification extends React.Component<Props> {
|
||||
}
|
||||
}
|
||||
|
||||
export default translate("commons")(ErrorNotification);
|
||||
export default translate("commons")(ErrorNotification);
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
// @flow
|
||||
import React from "react";
|
||||
import {translate} from "react-i18next";
|
||||
import type AutocompleteProps from "./UserGroupAutocomplete";
|
||||
import UserGroupAutocomplete from "./UserGroupAutocomplete";
|
||||
|
||||
type Props = AutocompleteProps & {
|
||||
// Context props
|
||||
t: string => string
|
||||
};
|
||||
|
||||
class GroupAutocomplete extends React.Component<Props> {
|
||||
render() {
|
||||
const { t } = this.props;
|
||||
return (
|
||||
<UserGroupAutocomplete
|
||||
label={t("autocomplete.group")}
|
||||
noOptionsMessage={t("autocomplete.noGroupOptions")}
|
||||
loadingMessage={t("autocomplete.loading")}
|
||||
placeholder={t("autocomplete.groupPlaceholder")}
|
||||
{...this.props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default translate("commons")(GroupAutocomplete);
|
||||
@@ -2,11 +2,18 @@
|
||||
import * as React from "react";
|
||||
import classNames from "classnames";
|
||||
|
||||
type NotificationType = "primary" | "info" | "success" | "warning" | "danger";
|
||||
type NotificationType =
|
||||
| "primary"
|
||||
| "info"
|
||||
| "success"
|
||||
| "warning"
|
||||
| "danger"
|
||||
| "inherit";
|
||||
|
||||
type Props = {
|
||||
type: NotificationType,
|
||||
onClose?: () => void,
|
||||
className?: string,
|
||||
children?: React.Node
|
||||
};
|
||||
|
||||
@@ -24,9 +31,12 @@ class Notification extends React.Component<Props> {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { type, children } = this.props;
|
||||
const { type, className, children } = this.props;
|
||||
|
||||
const color = type !== "inherit" ? "is-" + type : "";
|
||||
|
||||
return (
|
||||
<div className={classNames("notification", "is-" + type)}>
|
||||
<div className={classNames("notification", color, className)}>
|
||||
{this.renderCloseButton()}
|
||||
{children}
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
// @flow
|
||||
import React from "react";
|
||||
import {translate} from "react-i18next";
|
||||
import type AutocompleteProps from "./UserGroupAutocomplete";
|
||||
import UserGroupAutocomplete from "./UserGroupAutocomplete";
|
||||
|
||||
type Props = AutocompleteProps & {
|
||||
// Context props
|
||||
t: string => string
|
||||
};
|
||||
|
||||
class UserAutocomplete extends React.Component<Props> {
|
||||
render() {
|
||||
const { t } = this.props;
|
||||
return (
|
||||
<UserGroupAutocomplete
|
||||
label={t("autocomplete.user")}
|
||||
noOptionsMessage={t("autocomplete.noUserOptions")}
|
||||
loadingMessage={t("autocomplete.loading")}
|
||||
placeholder={t("autocomplete.userPlaceholder")}
|
||||
{...this.props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default translate("commons")(UserAutocomplete);
|
||||
@@ -0,0 +1,52 @@
|
||||
// @flow
|
||||
import React from "react";
|
||||
import type {SelectValue} from "@scm-manager/ui-types";
|
||||
import Autocomplete from "./Autocomplete";
|
||||
|
||||
export type AutocompleteProps = {
|
||||
autocompleteLink: string,
|
||||
valueSelected: SelectValue => void,
|
||||
value?: SelectValue
|
||||
};
|
||||
|
||||
type Props = AutocompleteProps & {
|
||||
label: string,
|
||||
noOptionsMessage: string,
|
||||
loadingMessage: string,
|
||||
placeholder: string
|
||||
};
|
||||
|
||||
export default class UserGroupAutocomplete extends React.Component<Props> {
|
||||
loadSuggestions = (inputValue: string) => {
|
||||
const url = this.props.autocompleteLink;
|
||||
const link = url + "?q=";
|
||||
return fetch(link + inputValue)
|
||||
.then(response => response.json())
|
||||
.then(json => {
|
||||
return json.map(element => {
|
||||
const label = element.displayName
|
||||
? `${element.displayName} (${element.id})`
|
||||
: element.id;
|
||||
return {
|
||||
value: element,
|
||||
label
|
||||
};
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
selectName = (selection: SelectValue) => {
|
||||
this.props.valueSelected(selection);
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Autocomplete
|
||||
loadSuggestions={this.loadSuggestions}
|
||||
valueSelected={this.selectName}
|
||||
creatable={true}
|
||||
{...this.props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -26,6 +26,8 @@ export { default as Tooltip } from "./Tooltip";
|
||||
// TODO do we need this? getPageFromMatch is already exported by urls
|
||||
export { getPageFromMatch } from "./urls";
|
||||
export { default as Autocomplete} from "./Autocomplete";
|
||||
export { default as GroupAutocomplete} from "./GroupAutocomplete";
|
||||
export { default as UserAutocomplete} from "./UserAutocomplete";
|
||||
export { default as BranchSelector } from "./BranchSelector";
|
||||
export { default as Breadcrumb } from "./Breadcrumb";
|
||||
export { default as MarkdownView } from "./MarkdownView";
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import DiffFile from "./DiffFile";
|
||||
import type { DiffObjectProps } from "./DiffTypes";
|
||||
import type {DiffObjectProps, File} from "./DiffTypes";
|
||||
|
||||
type Props = DiffObjectProps & {
|
||||
diff: any
|
||||
diff: File[]
|
||||
};
|
||||
|
||||
class Diff extends React.Component<Props> {
|
||||
|
||||
@@ -1,17 +1,10 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import {
|
||||
Hunk,
|
||||
Diff as DiffComponent,
|
||||
getChangeKey,
|
||||
Change,
|
||||
DiffObjectProps,
|
||||
File
|
||||
} from "react-diff-view";
|
||||
import {Change, Diff as DiffComponent, DiffObjectProps, File, getChangeKey, Hunk} from "react-diff-view";
|
||||
import injectSheets from "react-jss";
|
||||
import classNames from "classnames";
|
||||
import { translate } from "react-i18next";
|
||||
import { ButtonGroup, Button } from "../buttons";
|
||||
import {translate} from "react-i18next";
|
||||
import {Button, ButtonGroup} from "../buttons";
|
||||
|
||||
const styles = {
|
||||
panel: {
|
||||
@@ -178,12 +171,20 @@ class DiffFile extends React.Component<Props, State> {
|
||||
if (key === value) {
|
||||
value = file.type;
|
||||
}
|
||||
const color =
|
||||
value === "added"
|
||||
? "is-success"
|
||||
: value === "deleted"
|
||||
? "is-danger"
|
||||
: "is-info";
|
||||
|
||||
return (
|
||||
<span
|
||||
className={classNames(
|
||||
"tag",
|
||||
"is-info",
|
||||
"is-rounded",
|
||||
"has-text-weight-normal",
|
||||
color,
|
||||
classes.changeType
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -27,12 +27,17 @@ export type Hunk = {
|
||||
content: string
|
||||
};
|
||||
|
||||
export type ChangeType = "insert" | "delete" | "normal";
|
||||
|
||||
export type Change = {
|
||||
content: string,
|
||||
isNormal: boolean,
|
||||
newLineNumber: number,
|
||||
oldLineNumber: number,
|
||||
type: string
|
||||
isNormal?: boolean,
|
||||
isInsert?: boolean,
|
||||
isDelete?: boolean,
|
||||
lineNumber?: number,
|
||||
newLineNumber?: number,
|
||||
oldLineNumber?: number,
|
||||
type: ChangeType
|
||||
};
|
||||
|
||||
export type BaseContext = {
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import { apiClient } from "../apiclient";
|
||||
import {apiClient} from "../apiclient";
|
||||
import ErrorNotification from "../ErrorNotification";
|
||||
import parser from "gitdiff-parser";
|
||||
|
||||
import Loading from "../Loading";
|
||||
import Diff from "./Diff";
|
||||
import type {DiffObjectProps} from "./DiffTypes";
|
||||
import type {DiffObjectProps, File} from "./DiffTypes";
|
||||
|
||||
type Props = DiffObjectProps & {
|
||||
url: string
|
||||
};
|
||||
|
||||
type State = {
|
||||
diff?: any,
|
||||
diff?: File[],
|
||||
loading: boolean,
|
||||
error?: Error
|
||||
};
|
||||
@@ -47,7 +47,8 @@ class LoadingDiff extends React.Component<Props, State> {
|
||||
.get(url)
|
||||
.then(response => response.text())
|
||||
.then(parser.parse)
|
||||
.then(diff => {
|
||||
// $FlowFixMe
|
||||
.then((diff: File[]) => {
|
||||
this.setState({
|
||||
loading: false,
|
||||
diff: diff
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import type { Changeset, Repository } from "@scm-manager/ui-types";
|
||||
import { ButtonAddons, Button } from "../../buttons";
|
||||
import { createChangesetLink, createSourcesLink } from "./changesets";
|
||||
import { translate } from "react-i18next";
|
||||
import type {Changeset, Repository} from "@scm-manager/ui-types";
|
||||
import {Button, ButtonAddons} from "../../buttons";
|
||||
import {createChangesetLink, createSourcesLink} from "./changesets";
|
||||
import {translate} from "react-i18next";
|
||||
|
||||
type Props = {
|
||||
repository: Repository,
|
||||
@@ -21,7 +21,7 @@ class ChangesetButtonGroup extends React.Component<Props> {
|
||||
const sourcesLink = createSourcesLink(repository, changeset);
|
||||
|
||||
return (
|
||||
<ButtonAddons className="is-pulled-right">
|
||||
<ButtonAddons className="level-item">
|
||||
<Button link={changesetLink} className="reduced-mobile">
|
||||
<span className="icon">
|
||||
<i className="fas fa-exchange-alt" />
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import type { Changeset, Repository, Tag } from "@scm-manager/ui-types";
|
||||
import type {Changeset, Repository} from "@scm-manager/ui-types";
|
||||
|
||||
import classNames from "classnames";
|
||||
import { Interpolate, translate } from "react-i18next";
|
||||
import {Interpolate, translate} from "react-i18next";
|
||||
import ChangesetId from "./ChangesetId";
|
||||
import injectSheet from "react-jss";
|
||||
import { DateFromNow } from "../..";
|
||||
import {DateFromNow} from "../..";
|
||||
import ChangesetAuthor from "./ChangesetAuthor";
|
||||
import { parseDescription } from "./changesets";
|
||||
import { AvatarWrapper, AvatarImage } from "../../avatar";
|
||||
import { ExtensionPoint } from "@scm-manager/ui-extensions";
|
||||
import {parseDescription} from "./changesets";
|
||||
import {AvatarImage, AvatarWrapper} from "../../avatar";
|
||||
import {ExtensionPoint} from "@scm-manager/ui-extensions";
|
||||
import ChangesetTags from "./ChangesetTags";
|
||||
import ChangesetButtonGroup from "./ChangesetButtonGroup";
|
||||
|
||||
@@ -110,7 +110,16 @@ class ChangesetRow extends React.Component<Props> {
|
||||
</div>
|
||||
<div className={classNames("column", classes.isVcentered)}>
|
||||
<ChangesetTags changeset={changeset} />
|
||||
<ChangesetButtonGroup repository={repository} changeset={changeset} />
|
||||
<div className="is-pulled-right level">
|
||||
<ChangesetButtonGroup repository={repository} changeset={changeset} />
|
||||
<div className={classes.isVcentered}>
|
||||
<ExtensionPoint
|
||||
name="changeset.right"
|
||||
props={{ repository, changeset }}
|
||||
renderAll={true}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// @flow
|
||||
import * as diffs from "./diffs";
|
||||
|
||||
export { diffs };
|
||||
|
||||
export * from "./changesets";
|
||||
@@ -12,6 +13,7 @@ export type {
|
||||
FileChangeType,
|
||||
Hunk,
|
||||
Change,
|
||||
ChangeType,
|
||||
BaseContext,
|
||||
AnnotationFactory,
|
||||
AnnotationFactoryContext,
|
||||
|
||||
Reference in New Issue
Block a user