merge 2.0.0-m3

This commit is contained in:
Eduard Heimbuch
2019-08-15 10:51:36 +02:00
172 changed files with 3320 additions and 1792 deletions

View File

@@ -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;

View File

@@ -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);

View File

@@ -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);

View File

@@ -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>

View File

@@ -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);

View File

@@ -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}
/>
);
}
}

View File

@@ -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";

View File

@@ -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> {

View File

@@ -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
)}
>

View File

@@ -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 = {

View File

@@ -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

View File

@@ -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" />

View File

@@ -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>

View File

@@ -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,