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,19 +1,19 @@
|
||||
// @flow
|
||||
import React from "react";
|
||||
import {Async, AsyncCreatable} from "react-select";
|
||||
import type {AutocompleteObject, SelectValue} from "@scm-manager/ui-types";
|
||||
import { Async, AsyncCreatable } from "react-select";
|
||||
import { SelectValue } from "@scm-manager/ui-types";
|
||||
import LabelWithHelpIcon from "./forms/LabelWithHelpIcon";
|
||||
import { ActionMeta, ValueType } from "react-select/lib/types";
|
||||
|
||||
type Props = {
|
||||
loadSuggestions: string => Promise<AutocompleteObject>,
|
||||
valueSelected: SelectValue => void,
|
||||
label: string,
|
||||
helpText?: string,
|
||||
value?: SelectValue,
|
||||
placeholder: string,
|
||||
loadingMessage: string,
|
||||
noOptionsMessage: string,
|
||||
creatable?: boolean
|
||||
loadSuggestions: (p: string) => Promise<SelectValue[]>;
|
||||
valueSelected: (p: SelectValue) => void;
|
||||
label: string;
|
||||
helpText?: string;
|
||||
value?: SelectValue;
|
||||
placeholder: string;
|
||||
loadingMessage: string;
|
||||
noOptionsMessage: string;
|
||||
creatable?: boolean;
|
||||
};
|
||||
|
||||
type State = {};
|
||||
@@ -25,19 +25,21 @@ class Autocomplete extends React.Component<Props, State> {
|
||||
noOptionsMessage: "No suggestion available"
|
||||
};
|
||||
|
||||
handleInputChange = (newValue: SelectValue) => {
|
||||
this.props.valueSelected(newValue);
|
||||
handleInputChange = (newValue: ValueType<SelectValue>, action: ActionMeta) => {
|
||||
this.selectValue(newValue as SelectValue);
|
||||
};
|
||||
|
||||
selectValue = (value: SelectValue) => {
|
||||
this.props.valueSelected(value);
|
||||
};
|
||||
|
||||
// We overwrite this to avoid running into a bug (https://github.com/JedWatson/react-select/issues/2944)
|
||||
isValidNewOption = (
|
||||
inputValue: string,
|
||||
selectValue: SelectValue,
|
||||
selectOptions: SelectValue[]
|
||||
) => {
|
||||
const isNotDuplicated = !selectOptions
|
||||
.map(option => option.label)
|
||||
.includes(inputValue);
|
||||
selectValue: ValueType<SelectValue>,
|
||||
selectOptions: readonly SelectValue[]
|
||||
): boolean => {
|
||||
const isNotDuplicated = !selectOptions.map(option => option.label).includes(inputValue);
|
||||
const isNotEmpty = inputValue !== "";
|
||||
return isNotEmpty && isNotDuplicated;
|
||||
};
|
||||
@@ -68,9 +70,12 @@ class Autocomplete extends React.Component<Props, State> {
|
||||
noOptionsMessage={() => noOptionsMessage}
|
||||
isValidNewOption={this.isValidNewOption}
|
||||
onCreateOption={value => {
|
||||
this.handleInputChange({
|
||||
this.selectValue({
|
||||
label: value,
|
||||
value: { id: value, displayName: value }
|
||||
value: {
|
||||
id: value,
|
||||
displayName: value
|
||||
}
|
||||
});
|
||||
}}
|
||||
/>
|
||||
@@ -1,11 +1,12 @@
|
||||
// @flow
|
||||
import React from "react";
|
||||
import { BackendError } from "./errors";
|
||||
import Notification from "./Notification";
|
||||
|
||||
import { translate } from "react-i18next";
|
||||
import { WithTranslation, withTranslation } from "react-i18next";
|
||||
|
||||
type Props = { error: BackendError, t: string => string };
|
||||
type Props = WithTranslation & {
|
||||
error: BackendError;
|
||||
};
|
||||
|
||||
class BackendErrorNotification extends React.Component<Props> {
|
||||
constructor(props: Props) {
|
||||
@@ -84,7 +85,7 @@ class BackendErrorNotification extends React.Component<Props> {
|
||||
};
|
||||
|
||||
renderContext = () => {
|
||||
const { error, t} = this.props;
|
||||
const { error, t } = this.props;
|
||||
if (error.context) {
|
||||
return (
|
||||
<>
|
||||
@@ -120,4 +121,4 @@ class BackendErrorNotification extends React.Component<Props> {
|
||||
};
|
||||
}
|
||||
|
||||
export default translate("plugins")(BackendErrorNotification);
|
||||
export default withTranslation("plugins")(BackendErrorNotification);
|
||||
@@ -1,19 +1,20 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import classNames from "classnames";
|
||||
import styled from "styled-components";
|
||||
import type { Branch } from "@scm-manager/ui-types";
|
||||
import { Branch } from "@scm-manager/ui-types";
|
||||
import DropDown from "./forms/DropDown";
|
||||
|
||||
type Props = {
|
||||
branches: Branch[],
|
||||
selected: (branch?: Branch) => void,
|
||||
selectedBranch?: string,
|
||||
label: string,
|
||||
disabled?: boolean
|
||||
branches: Branch[];
|
||||
selected: (branch?: Branch) => void;
|
||||
selectedBranch?: string;
|
||||
label: string;
|
||||
disabled?: boolean;
|
||||
};
|
||||
|
||||
type State = { selectedBranch?: Branch };
|
||||
type State = {
|
||||
selectedBranch?: Branch;
|
||||
};
|
||||
|
||||
const ZeroflexFieldLabel = styled.div`
|
||||
flex-basis: inherit;
|
||||
@@ -37,10 +38,10 @@ export default class BranchSelector extends React.Component<Props, State> {
|
||||
componentDidMount() {
|
||||
const { branches } = this.props;
|
||||
if (branches) {
|
||||
const selectedBranch = branches.find(
|
||||
branch => branch.name === this.props.selectedBranch
|
||||
);
|
||||
this.setState({ selectedBranch });
|
||||
const selectedBranch = branches.find(branch => branch.name === this.props.selectedBranch);
|
||||
this.setState({
|
||||
selectedBranch
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,9 +51,7 @@ export default class BranchSelector extends React.Component<Props, State> {
|
||||
if (branches) {
|
||||
return (
|
||||
<div className={classNames("field", "is-horizontal")}>
|
||||
<ZeroflexFieldLabel
|
||||
className={classNames("field-label", "is-normal")}
|
||||
>
|
||||
<ZeroflexFieldLabel className={classNames("field-label", "is-normal")}>
|
||||
<label className={classNames("label", "is-size-6")}>{label}</label>
|
||||
</ZeroflexFieldLabel>
|
||||
<div className="field-body">
|
||||
@@ -63,11 +62,7 @@ export default class BranchSelector extends React.Component<Props, State> {
|
||||
options={branches.map(b => b.name)}
|
||||
optionSelected={this.branchSelected}
|
||||
disabled={!!disabled}
|
||||
preselectedOption={
|
||||
this.state.selectedBranch
|
||||
? this.state.selectedBranch.name
|
||||
: ""
|
||||
}
|
||||
preselectedOption={this.state.selectedBranch ? this.state.selectedBranch.name : ""}
|
||||
/>
|
||||
</MinWidthControl>
|
||||
</NoBottomMarginField>
|
||||
@@ -83,13 +78,17 @@ export default class BranchSelector extends React.Component<Props, State> {
|
||||
const { branches, selected } = this.props;
|
||||
|
||||
if (!branchName) {
|
||||
this.setState({ selectedBranch: undefined });
|
||||
this.setState({
|
||||
selectedBranch: undefined
|
||||
});
|
||||
selected(undefined);
|
||||
return;
|
||||
}
|
||||
const branch = branches.find(b => b.name === branchName);
|
||||
|
||||
selected(branch);
|
||||
this.setState({ selectedBranch: branch });
|
||||
this.setState({
|
||||
selectedBranch: branch
|
||||
});
|
||||
};
|
||||
}
|
||||
99
scm-ui/ui-components/src/Breadcrumb.tsx
Normal file
99
scm-ui/ui-components/src/Breadcrumb.tsx
Normal file
@@ -0,0 +1,99 @@
|
||||
import React from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { WithTranslation, withTranslation } from "react-i18next";
|
||||
import classNames from "classnames";
|
||||
import styled from "styled-components";
|
||||
import { binder, ExtensionPoint } from "@scm-manager/ui-extensions";
|
||||
import { Branch, Repository } from "@scm-manager/ui-types";
|
||||
import Icon from "./Icon";
|
||||
|
||||
type Props = WithTranslation & {
|
||||
repository: Repository;
|
||||
branch: Branch;
|
||||
defaultBranch: Branch;
|
||||
branches: Branch[];
|
||||
revision: string;
|
||||
path: string;
|
||||
baseUrl: string;
|
||||
};
|
||||
|
||||
const FlexStartNav = styled.nav`
|
||||
flex: 1;
|
||||
`;
|
||||
|
||||
const HomeIcon = styled(Icon)`
|
||||
line-height: 1.5rem;
|
||||
`;
|
||||
|
||||
const ActionWrapper = styled.div`
|
||||
align-self: center;
|
||||
padding-right: 1rem;
|
||||
`;
|
||||
|
||||
class Breadcrumb extends React.Component<Props> {
|
||||
renderPath() {
|
||||
const { revision, path, baseUrl } = this.props;
|
||||
|
||||
if (path) {
|
||||
const paths = path.split("/");
|
||||
const map = paths.map((path, index) => {
|
||||
const currPath = paths.slice(0, index + 1).join("/");
|
||||
if (paths.length - 1 === index) {
|
||||
return (
|
||||
<li className="is-active" key={index}>
|
||||
<Link to="#" aria-current="page">
|
||||
{path}
|
||||
</Link>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<li key={index}>
|
||||
<Link to={baseUrl + "/" + revision + "/" + currPath}>{path}</Link>
|
||||
</li>
|
||||
);
|
||||
});
|
||||
return map;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
render() {
|
||||
const { baseUrl, branch, defaultBranch, branches, revision, path, repository, t } = this.props;
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="is-flex">
|
||||
<FlexStartNav className={classNames("breadcrumb", "sources-breadcrumb")} aria-label="breadcrumbs">
|
||||
<ul>
|
||||
<li>
|
||||
<Link to={baseUrl + "/" + revision + "/"}>
|
||||
<HomeIcon title={t("breadcrumb.home")} name="home" color="inherit" />
|
||||
</Link>
|
||||
</li>
|
||||
{this.renderPath()}
|
||||
</ul>
|
||||
</FlexStartNav>
|
||||
{binder.hasExtension("repos.sources.actionbar") && (
|
||||
<ActionWrapper>
|
||||
<ExtensionPoint
|
||||
name="repos.sources.actionbar"
|
||||
props={{
|
||||
baseUrl,
|
||||
branch: branch ? branch : defaultBranch,
|
||||
path,
|
||||
isBranchUrl: branches && branches.filter(b => b.name.replace("/", "%2F") === revision).length > 0,
|
||||
repository
|
||||
}}
|
||||
renderAll={true}
|
||||
/>
|
||||
</ActionWrapper>
|
||||
)}
|
||||
</div>
|
||||
<hr className="is-marginless" />
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default withTranslation("commons")(Breadcrumb);
|
||||
@@ -1,19 +1,18 @@
|
||||
//@flow
|
||||
import * as React from "react";
|
||||
import React, { ReactNode } from "react";
|
||||
import classNames from "classnames";
|
||||
import styled from "styled-components";
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
type Props = {
|
||||
title: string,
|
||||
description: string,
|
||||
avatar: React.Node,
|
||||
contentRight?: React.Node,
|
||||
footerLeft: React.Node,
|
||||
footerRight: React.Node,
|
||||
link?: string,
|
||||
action?: () => void,
|
||||
className?: string
|
||||
title: string;
|
||||
description: string;
|
||||
avatar: ReactNode;
|
||||
contentRight?: ReactNode;
|
||||
footerLeft: ReactNode;
|
||||
footerRight: ReactNode;
|
||||
link?: string;
|
||||
action?: () => void;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
const NoEventWrapper = styled.article`
|
||||
@@ -67,24 +66,14 @@ export default class CardColumn extends React.Component<Props> {
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
avatar,
|
||||
title,
|
||||
description,
|
||||
contentRight,
|
||||
footerLeft,
|
||||
footerRight,
|
||||
className
|
||||
} = this.props;
|
||||
const { avatar, title, description, contentRight, footerLeft, footerRight, className } = this.props;
|
||||
const link = this.createLink();
|
||||
return (
|
||||
<>
|
||||
{link}
|
||||
<NoEventWrapper className={classNames("media", className)}>
|
||||
<AvatarWrapper className="media-left">{avatar}</AvatarWrapper>
|
||||
<FlexFullHeight
|
||||
className={classNames("media-content", "text-box", "is-flex")}
|
||||
>
|
||||
<FlexFullHeight className={classNames("media-content", "text-box", "is-flex")}>
|
||||
<div className="is-flex">
|
||||
<ContentLeft className="content">
|
||||
<p className="shorten-text is-marginless">
|
||||
@@ -96,9 +85,7 @@ export default class CardColumn extends React.Component<Props> {
|
||||
</div>
|
||||
<FooterWrapper className={classNames("level", "is-flex")}>
|
||||
<div className="level-left is-hidden-mobile">{footerLeft}</div>
|
||||
<div className="level-right is-mobile is-marginless">
|
||||
{footerRight}
|
||||
</div>
|
||||
<div className="level-right is-mobile is-marginless">{footerRight}</div>
|
||||
</FooterWrapper>
|
||||
</FlexFullHeight>
|
||||
</NoEventWrapper>
|
||||
@@ -1,15 +1,14 @@
|
||||
//@flow
|
||||
import * as React from "react";
|
||||
import React, { ReactNode } from "react";
|
||||
import classNames from "classnames";
|
||||
import styled from "styled-components";
|
||||
|
||||
type Props = {
|
||||
name: string,
|
||||
elements: React.Node[]
|
||||
name: string;
|
||||
elements: ReactNode[];
|
||||
};
|
||||
|
||||
type State = {
|
||||
collapsed: boolean
|
||||
collapsed: boolean;
|
||||
};
|
||||
|
||||
const Container = styled.div`
|
||||
@@ -34,15 +33,15 @@ export default class CardColumnGroup extends React.Component<Props, State> {
|
||||
}));
|
||||
};
|
||||
|
||||
isLastEntry = (array: React.Node[], index: number) => {
|
||||
isLastEntry = (array: ReactNode[], index: number) => {
|
||||
return index === array.length - 1;
|
||||
};
|
||||
|
||||
isLengthOdd = (array: React.Node[]) => {
|
||||
isLengthOdd = (array: ReactNode[]) => {
|
||||
return array.length % 2 !== 0;
|
||||
};
|
||||
|
||||
isFullSize = (array: React.Node[], index: number) => {
|
||||
isFullSize = (array: ReactNode[], index: number) => {
|
||||
return this.isLastEntry(array, index) && this.isLengthOdd(array);
|
||||
};
|
||||
|
||||
@@ -57,16 +56,7 @@ export default class CardColumnGroup extends React.Component<Props, State> {
|
||||
const fullColumnWidth = this.isFullSize(elements, index);
|
||||
const sizeClass = fullColumnWidth ? "is-full" : "is-half";
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
"box",
|
||||
"box-link-shadow",
|
||||
"column",
|
||||
"is-clipped",
|
||||
sizeClass
|
||||
)}
|
||||
key={index}
|
||||
>
|
||||
<div className={classNames("box", "box-link-shadow", "column", "is-clipped", sizeClass)} key={index}>
|
||||
{entry}
|
||||
</div>
|
||||
);
|
||||
@@ -75,17 +65,12 @@ export default class CardColumnGroup extends React.Component<Props, State> {
|
||||
return (
|
||||
<Container>
|
||||
<h2>
|
||||
<span
|
||||
className={classNames("is-size-4", "has-cursor-pointer")}
|
||||
onClick={this.toggleCollapse}
|
||||
>
|
||||
<span className={classNames("is-size-4", "has-cursor-pointer")} onClick={this.toggleCollapse}>
|
||||
<i className={classNames("fa", icon)} /> {name}
|
||||
</span>
|
||||
</h2>
|
||||
<hr />
|
||||
<Wrapper className={classNames("columns", "is-multiline")}>
|
||||
{content}
|
||||
</Wrapper>
|
||||
<Wrapper className={classNames("columns", "is-multiline")}>{content}</Wrapper>
|
||||
<div className="is-clearfix" />
|
||||
</Container>
|
||||
);
|
||||
@@ -1,19 +1,24 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import { translate } from "react-i18next";
|
||||
import { formatDistance, format, parseISO } from "date-fns";
|
||||
import { withTranslation, WithTranslation } from "react-i18next";
|
||||
import { formatDistance, format, parseISO, Locale } from "date-fns";
|
||||
import { enUS, de, es } from "date-fns/locale";
|
||||
import styled from "styled-components";
|
||||
|
||||
const supportedLocales = {
|
||||
type LocaleMap = {
|
||||
[key: string]: Locale;
|
||||
};
|
||||
|
||||
type DateInput = Date | string;
|
||||
|
||||
const supportedLocales: LocaleMap = {
|
||||
enUS,
|
||||
de,
|
||||
es
|
||||
};
|
||||
|
||||
type Props = {
|
||||
date?: string,
|
||||
timeZone?: string,
|
||||
type Props = WithTranslation & {
|
||||
date?: DateInput;
|
||||
timeZone?: string;
|
||||
|
||||
/**
|
||||
* baseDate is the date from which the distance is calculated,
|
||||
@@ -21,10 +26,13 @@ type Props = {
|
||||
* is required to keep snapshots tests green over the time on
|
||||
* ci server.
|
||||
*/
|
||||
baseDate?: string,
|
||||
baseDate?: DateInput;
|
||||
};
|
||||
|
||||
// context props
|
||||
i18n: any
|
||||
type Options = {
|
||||
addSuffix: boolean;
|
||||
locale: Locale;
|
||||
timeZone?: string;
|
||||
};
|
||||
|
||||
const DateElement = styled.time`
|
||||
@@ -33,7 +41,7 @@ const DateElement = styled.time`
|
||||
`;
|
||||
|
||||
class DateFromNow extends React.Component<Props> {
|
||||
getLocale = () => {
|
||||
getLocale = (): Locale => {
|
||||
const { i18n } = this.props;
|
||||
const locale = supportedLocales[i18n.language];
|
||||
if (!locale) {
|
||||
@@ -44,9 +52,9 @@ class DateFromNow extends React.Component<Props> {
|
||||
|
||||
createOptions = () => {
|
||||
const { timeZone } = this.props;
|
||||
const options: Object = {
|
||||
const options: Options = {
|
||||
addSuffix: true,
|
||||
locate: this.getLocale(),
|
||||
locale: this.getLocale()
|
||||
};
|
||||
if (timeZone) {
|
||||
options.timeZone = timeZone;
|
||||
@@ -54,10 +62,17 @@ class DateFromNow extends React.Component<Props> {
|
||||
return options;
|
||||
};
|
||||
|
||||
toDate = (value: DateInput): Date => {
|
||||
if (value instanceof Date) {
|
||||
return value as Date;
|
||||
}
|
||||
return parseISO(value);
|
||||
};
|
||||
|
||||
getBaseDate = () => {
|
||||
const { baseDate } = this.props;
|
||||
if (baseDate) {
|
||||
return parseISO(baseDate);
|
||||
return this.toDate(baseDate);
|
||||
}
|
||||
return new Date();
|
||||
};
|
||||
@@ -65,7 +80,7 @@ class DateFromNow extends React.Component<Props> {
|
||||
render() {
|
||||
const { date } = this.props;
|
||||
if (date) {
|
||||
const isoDate = parseISO(date);
|
||||
const isoDate = this.toDate(date);
|
||||
const options = this.createOptions();
|
||||
const distance = formatDistance(isoDate, this.getBaseDate(), options);
|
||||
const formatted = format(isoDate, "yyyy-MM-dd HH:mm:ss", options);
|
||||
@@ -75,4 +90,4 @@ class DateFromNow extends React.Component<Props> {
|
||||
}
|
||||
}
|
||||
|
||||
export default translate()(DateFromNow);
|
||||
export default withTranslation()(DateFromNow);
|
||||
@@ -1,22 +1,21 @@
|
||||
// @flow
|
||||
import * as React from "react";
|
||||
import React, { ReactNode } from "react";
|
||||
import ErrorNotification from "./ErrorNotification";
|
||||
|
||||
type Props = {
|
||||
fallback?: React.ComponentType<any>,
|
||||
children: React.Node
|
||||
fallback?: React.ComponentType<any>;
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
type ErrorInfo = {
|
||||
componentStack: string
|
||||
componentStack: string;
|
||||
};
|
||||
|
||||
type State = {
|
||||
error?: Error,
|
||||
errorInfo?: ErrorInfo
|
||||
error?: Error;
|
||||
errorInfo?: ErrorInfo;
|
||||
};
|
||||
|
||||
class ErrorBoundary extends React.Component<Props,State> {
|
||||
class ErrorBoundary extends React.Component<Props, State> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
this.state = {};
|
||||
@@ -1,13 +1,11 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import {translate} from "react-i18next";
|
||||
import {BackendError, ForbiddenError, UnauthorizedError} from "./errors";
|
||||
import { WithTranslation, withTranslation } from "react-i18next";
|
||||
import { BackendError, ForbiddenError, UnauthorizedError } from "./errors";
|
||||
import Notification from "./Notification";
|
||||
import BackendErrorNotification from "./BackendErrorNotification";
|
||||
|
||||
type Props = {
|
||||
t: string => string,
|
||||
error?: Error
|
||||
type Props = WithTranslation & {
|
||||
error?: Error;
|
||||
};
|
||||
|
||||
class ErrorNotification extends React.Component<Props> {
|
||||
@@ -19,18 +17,14 @@ class ErrorNotification extends React.Component<Props> {
|
||||
} else if (error instanceof UnauthorizedError) {
|
||||
return (
|
||||
<Notification type="danger">
|
||||
<strong>{t("errorNotification.prefix")}:</strong>{" "}
|
||||
{t("errorNotification.timeout")}{" "}
|
||||
<a href="javascript:window.location.reload(true)">
|
||||
{t("errorNotification.loginLink")}
|
||||
</a>
|
||||
<strong>{t("errorNotification.prefix")}:</strong> {t("errorNotification.timeout")}{" "}
|
||||
<a href="javascript:window.location.reload(true)">{t("errorNotification.loginLink")}</a>
|
||||
</Notification>
|
||||
);
|
||||
} else if (error instanceof ForbiddenError) {
|
||||
return (
|
||||
<Notification type="danger">
|
||||
<strong>{t("errorNotification.prefix")}:</strong>{" "}
|
||||
{t("errorNotification.forbidden")}
|
||||
<strong>{t("errorNotification.prefix")}:</strong> {t("errorNotification.forbidden")}
|
||||
</Notification>
|
||||
);
|
||||
} else {
|
||||
@@ -45,4 +39,4 @@ class ErrorNotification extends React.Component<Props> {
|
||||
}
|
||||
}
|
||||
|
||||
export default translate("commons")(ErrorNotification);
|
||||
export default withTranslation("commons")(ErrorNotification);
|
||||
@@ -1,12 +1,11 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import ErrorNotification from "./ErrorNotification";
|
||||
import { BackendError, ForbiddenError } from "./errors";
|
||||
|
||||
type Props = {
|
||||
error: Error,
|
||||
title: string,
|
||||
subtitle: string
|
||||
error: Error;
|
||||
title: string;
|
||||
subtitle: string;
|
||||
};
|
||||
|
||||
class ErrorPage extends React.Component<Props> {
|
||||
@@ -29,8 +28,8 @@ class ErrorPage extends React.Component<Props> {
|
||||
if (error instanceof BackendError || error instanceof ForbiddenError) {
|
||||
return null;
|
||||
}
|
||||
return <p className="subtitle">{subtitle}</p>
|
||||
}
|
||||
return <p className="subtitle">{subtitle}</p>;
|
||||
};
|
||||
}
|
||||
|
||||
export default ErrorPage;
|
||||
@@ -1,8 +1,7 @@
|
||||
// @flow
|
||||
import React from "react";
|
||||
|
||||
type Props = {
|
||||
bytes: number
|
||||
bytes: number;
|
||||
};
|
||||
|
||||
class FileSize extends React.Component<Props> {
|
||||
@@ -1,15 +1,9 @@
|
||||
// @flow
|
||||
import React from "react";
|
||||
import {translate} from "react-i18next";
|
||||
import type AutocompleteProps from "./UserGroupAutocomplete";
|
||||
import { WithTranslation, withTranslation } from "react-i18next";
|
||||
import AutocompleteProps from "./UserGroupAutocomplete";
|
||||
import UserGroupAutocomplete from "./UserGroupAutocomplete";
|
||||
|
||||
type Props = AutocompleteProps & {
|
||||
// Context props
|
||||
t: string => string
|
||||
};
|
||||
|
||||
class GroupAutocomplete extends React.Component<Props> {
|
||||
class GroupAutocomplete extends React.Component<AutocompleteProps & WithTranslation> {
|
||||
render() {
|
||||
const { t } = this.props;
|
||||
return (
|
||||
@@ -24,4 +18,4 @@ class GroupAutocomplete extends React.Component<Props> {
|
||||
}
|
||||
}
|
||||
|
||||
export default translate("commons")(GroupAutocomplete);
|
||||
export default withTranslation("commons")(GroupAutocomplete);
|
||||
@@ -1,4 +1,3 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import classNames from "classnames";
|
||||
import styled from "styled-components";
|
||||
@@ -6,8 +5,8 @@ import Tooltip from "./Tooltip";
|
||||
import HelpIcon from "./HelpIcon";
|
||||
|
||||
type Props = {
|
||||
message: string,
|
||||
className?: string
|
||||
message: string;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
const HelpTooltip = styled(Tooltip)`
|
||||
@@ -1,16 +1,13 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import Icon from "./Icon";
|
||||
|
||||
type Props = {
|
||||
className?: string
|
||||
className?: string;
|
||||
};
|
||||
|
||||
export default class HelpIcon extends React.Component<Props> {
|
||||
render() {
|
||||
const { className } = this.props;
|
||||
return (
|
||||
<Icon name="question-circle" color="blue-light" className={className} />
|
||||
);
|
||||
return <Icon name="question-circle" color="blue-light" className={className} />;
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,11 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import classNames from "classnames";
|
||||
|
||||
type Props = {
|
||||
title?: string,
|
||||
name: string,
|
||||
color: string,
|
||||
className?: string
|
||||
title?: string;
|
||||
name: string;
|
||||
color: string;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
export default class Icon extends React.Component<Props> {
|
||||
@@ -17,12 +16,7 @@ export default class Icon extends React.Component<Props> {
|
||||
render() {
|
||||
const { title, name, color, className } = this.props;
|
||||
if (title) {
|
||||
return (
|
||||
<i
|
||||
title={title}
|
||||
className={classNames("fas", "fa-fw", "fa-" + name, `has-text-${color}`, className)}
|
||||
/>
|
||||
);
|
||||
return <i title={title} className={classNames("fas", "fa-fw", "fa-" + name, `has-text-${color}`, className)} />;
|
||||
}
|
||||
return <i className={classNames("fas", "fa-" + name, `has-text-${color}`, className)} />;
|
||||
}
|
||||
@@ -1,15 +1,13 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import {withContextPath} from "./urls";
|
||||
import { withContextPath } from "./urls";
|
||||
|
||||
type Props = {
|
||||
src: string,
|
||||
alt: string,
|
||||
className?: any
|
||||
src: string;
|
||||
alt: string;
|
||||
className?: any;
|
||||
};
|
||||
|
||||
class Image extends React.Component<Props> {
|
||||
|
||||
createImageSrc = () => {
|
||||
const { src } = this.props;
|
||||
if (src.startsWith("http")) {
|
||||
@@ -1,16 +1,12 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import { translate } from "react-i18next";
|
||||
import type { PagedCollection } from "@scm-manager/ui-types";
|
||||
import { WithTranslation, withTranslation } from "react-i18next";
|
||||
import { PagedCollection } from "@scm-manager/ui-types";
|
||||
import { Button } from "./buttons";
|
||||
|
||||
type Props = {
|
||||
collection: PagedCollection,
|
||||
page: number,
|
||||
filter?: string,
|
||||
|
||||
// context props
|
||||
t: string => string
|
||||
type Props = WithTranslation & {
|
||||
collection: PagedCollection;
|
||||
page: number;
|
||||
filter?: string;
|
||||
};
|
||||
|
||||
class LinkPaginator extends React.Component<Props> {
|
||||
@@ -23,14 +19,7 @@ class LinkPaginator extends React.Component<Props> {
|
||||
}
|
||||
|
||||
renderFirstButton() {
|
||||
return (
|
||||
<Button
|
||||
className="pagination-link"
|
||||
label={"1"}
|
||||
disabled={false}
|
||||
link={this.addFilterToLink("1")}
|
||||
/>
|
||||
);
|
||||
return <Button className="pagination-link" label={"1"} disabled={false} link={this.addFilterToLink("1")} />;
|
||||
}
|
||||
|
||||
renderPreviousButton(className: string, label?: string) {
|
||||
@@ -82,13 +71,7 @@ class LinkPaginator extends React.Component<Props> {
|
||||
}
|
||||
|
||||
currentPage(page: number) {
|
||||
return (
|
||||
<Button
|
||||
className="pagination-link is-current"
|
||||
label={page}
|
||||
disabled={true}
|
||||
/>
|
||||
);
|
||||
return <Button className="pagination-link is-current" label={"" + page} disabled={true} />;
|
||||
}
|
||||
|
||||
pageLinks() {
|
||||
@@ -112,26 +95,19 @@ class LinkPaginator extends React.Component<Props> {
|
||||
if (page + 1 < pageTotal) {
|
||||
links.push(this.renderNextButton("pagination-link"));
|
||||
}
|
||||
if (page + 2 < pageTotal)
|
||||
//if there exists pages between next and last
|
||||
links.push(this.separator());
|
||||
if (page + 2 < pageTotal) links.push(this.separator());
|
||||
//if there exists pages between next and last
|
||||
if (page < pageTotal) {
|
||||
links.push(this.renderLastButton());
|
||||
}
|
||||
|
||||
return links;
|
||||
}
|
||||
|
||||
render() {
|
||||
const { collection, t } = this.props;
|
||||
|
||||
if(collection) {
|
||||
if (collection) {
|
||||
return (
|
||||
<nav className="pagination is-centered" aria-label="pagination">
|
||||
{this.renderPreviousButton(
|
||||
"pagination-previous",
|
||||
t("paginator.previous")
|
||||
)}
|
||||
{this.renderPreviousButton("pagination-previous", t("paginator.previous"))}
|
||||
<ul className="pagination-list">
|
||||
{this.pageLinks().map((link, index) => {
|
||||
return <li key={index}>{link}</li>;
|
||||
@@ -144,5 +120,4 @@ class LinkPaginator extends React.Component<Props> {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export default translate("commons")(LinkPaginator);
|
||||
export default withTranslation("commons")(LinkPaginator);
|
||||
@@ -1,11 +0,0 @@
|
||||
// @flow
|
||||
import React from "react";
|
||||
import { storiesOf } from "@storybook/react";
|
||||
import Loading from './Loading';
|
||||
|
||||
storiesOf("Loading", module)
|
||||
.add("Default", () => (
|
||||
<div>
|
||||
<Loading />
|
||||
</div>
|
||||
));
|
||||
9
scm-ui/ui-components/src/Loading.stories.tsx
Normal file
9
scm-ui/ui-components/src/Loading.stories.tsx
Normal file
@@ -0,0 +1,9 @@
|
||||
import React from "react";
|
||||
import { storiesOf } from "@storybook/react";
|
||||
import Loading from "./Loading";
|
||||
|
||||
storiesOf("Loading", module).add("Default", () => (
|
||||
<div>
|
||||
<Loading />
|
||||
</div>
|
||||
));
|
||||
@@ -1,12 +1,10 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import { translate } from "react-i18next";
|
||||
import { WithTranslation, withTranslation } from "react-i18next";
|
||||
import styled from "styled-components";
|
||||
import Image from "./Image";
|
||||
|
||||
type Props = {
|
||||
t: string => string,
|
||||
message?: string
|
||||
type Props = WithTranslation & {
|
||||
message?: string;
|
||||
};
|
||||
|
||||
const Wrapper = styled.div`
|
||||
@@ -34,4 +32,4 @@ class Loading extends React.Component<Props> {
|
||||
}
|
||||
}
|
||||
|
||||
export default translate("commons")(Loading);
|
||||
export default withTranslation("commons")(Loading);
|
||||
@@ -1,17 +0,0 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import { translate } from "react-i18next";
|
||||
import Image from "./Image";
|
||||
|
||||
type Props = {
|
||||
t: string => string
|
||||
};
|
||||
|
||||
class Logo extends React.Component<Props> {
|
||||
render() {
|
||||
const { t } = this.props;
|
||||
return <Image src="/images/logo.png" alt={t("logo.alt")} />;
|
||||
}
|
||||
}
|
||||
|
||||
export default translate("commons")(Logo);
|
||||
@@ -1,4 +1,3 @@
|
||||
// @flow
|
||||
import React from "react";
|
||||
import { storiesOf } from "@storybook/react";
|
||||
import styled from "styled-components";
|
||||
12
scm-ui/ui-components/src/Logo.tsx
Normal file
12
scm-ui/ui-components/src/Logo.tsx
Normal file
@@ -0,0 +1,12 @@
|
||||
import React from "react";
|
||||
import { WithTranslation, withTranslation } from "react-i18next";
|
||||
import Image from "./Image";
|
||||
|
||||
class Logo extends React.Component<WithTranslation> {
|
||||
render() {
|
||||
const { t } = this.props;
|
||||
return <Image src="/images/logo.png" alt={t("logo.alt")} />;
|
||||
}
|
||||
}
|
||||
|
||||
export default withTranslation("commons")(Logo);
|
||||
@@ -1,8 +1,7 @@
|
||||
// @flow
|
||||
import React from "react";
|
||||
|
||||
type Props = {
|
||||
address?: string
|
||||
address?: string;
|
||||
};
|
||||
|
||||
class MailLink extends React.Component<Props> {
|
||||
@@ -1,9 +1,7 @@
|
||||
// @flow
|
||||
import React from "react";
|
||||
import { headingToAnchorId } from "./MarkdownHeadingRenderer";
|
||||
|
||||
describe("headingToAnchorId tests", () => {
|
||||
|
||||
it("should lower case the text", () => {
|
||||
expect(headingToAnchorId("Hello")).toBe("hello");
|
||||
expect(headingToAnchorId("HeLlO")).toBe("hello");
|
||||
@@ -14,5 +12,4 @@ describe("headingToAnchorId tests", () => {
|
||||
expect(headingToAnchorId("awesome stuff")).toBe("awesome-stuff");
|
||||
expect(headingToAnchorId("a b c d e f")).toBe("a-b-c-d-e-f");
|
||||
});
|
||||
|
||||
});
|
||||
@@ -1,6 +1,5 @@
|
||||
// @flow
|
||||
import * as React from "react";
|
||||
import { withRouter } from "react-router-dom";
|
||||
import React, { ReactNode } from "react";
|
||||
import { withRouter, RouteComponentProps } from "react-router-dom";
|
||||
import { withContextPath } from "./urls";
|
||||
|
||||
/**
|
||||
@@ -9,16 +8,13 @@ import { withContextPath } from "./urls";
|
||||
* @see <a href="https://github.com/rexxars/react-markdown/issues/69">Headings are missing anchors / ids</a>
|
||||
*/
|
||||
|
||||
type Props = {
|
||||
children: React.Node,
|
||||
level: number,
|
||||
location: any
|
||||
type Props = RouteComponentProps & {
|
||||
children: ReactNode;
|
||||
level: number;
|
||||
};
|
||||
|
||||
function flatten(text: string, child: any) {
|
||||
return typeof child === "string"
|
||||
? text + child
|
||||
: React.Children.toArray(child.props.children).reduce(flatten, text);
|
||||
function flatten(text: string, child: any): any {
|
||||
return typeof child === "string" ? text + child : React.Children.toArray(child.props.children).reduce(flatten, text);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1,22 +0,0 @@
|
||||
// @flow
|
||||
import React from "react";
|
||||
import {storiesOf} from "@storybook/react";
|
||||
import MarkdownView from "./MarkdownView";
|
||||
import styled from "styled-components";
|
||||
import {MemoryRouter} from "react-router-dom";
|
||||
|
||||
import TestPage from "./__resources__/test-page.md";
|
||||
|
||||
const Spacing = styled.div`
|
||||
padding: 2em;
|
||||
`;
|
||||
|
||||
storiesOf("MarkdownView", module)
|
||||
.addDecorator(story => (
|
||||
<MemoryRouter initialEntries={["/"]}>{story()}</MemoryRouter>
|
||||
))
|
||||
.add("Default", () => (
|
||||
<Spacing>
|
||||
<MarkdownView content={TestPage} skipHtml={false} />
|
||||
</Spacing>
|
||||
));
|
||||
19
scm-ui/ui-components/src/MarkdownView.stories.tsx
Normal file
19
scm-ui/ui-components/src/MarkdownView.stories.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import React from "react";
|
||||
import { storiesOf } from "@storybook/react";
|
||||
import MarkdownView from "./MarkdownView";
|
||||
import styled from "styled-components";
|
||||
import { MemoryRouter } from "react-router-dom";
|
||||
|
||||
import TestPage from "./__resources__/test-page.md";
|
||||
|
||||
const Spacing = styled.div`
|
||||
padding: 2em;
|
||||
`;
|
||||
|
||||
storiesOf("MarkdownView", module)
|
||||
.addDecorator(story => <MemoryRouter initialEntries={["/"]}>{story()}</MemoryRouter>)
|
||||
.add("Default", () => (
|
||||
<Spacing>
|
||||
<MarkdownView content={TestPage} skipHtml={false} />
|
||||
</Spacing>
|
||||
));
|
||||
@@ -1,26 +1,28 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import { withRouter } from "react-router-dom";
|
||||
import { withRouter, RouteComponentProps } from "react-router-dom";
|
||||
// @ts-ignore
|
||||
import Markdown from "react-markdown/with-html";
|
||||
import styled from "styled-components";
|
||||
import { binder } from "@scm-manager/ui-extensions";
|
||||
import SyntaxHighlighter from "./SyntaxHighlighter";
|
||||
import MarkdownHeadingRenderer from "./MarkdownHeadingRenderer";
|
||||
|
||||
type Props = {
|
||||
content: string,
|
||||
renderContext?: Object,
|
||||
renderers?: Object,
|
||||
skipHtml?: boolean,
|
||||
enableAnchorHeadings: boolean,
|
||||
|
||||
// context props
|
||||
location: any
|
||||
type Props = RouteComponentProps & {
|
||||
content: string;
|
||||
renderContext?: object;
|
||||
renderers?: any;
|
||||
skipHtml?: boolean;
|
||||
enableAnchorHeadings?: boolean;
|
||||
};
|
||||
|
||||
const MarkdownWrapper = styled.div`
|
||||
> .content {
|
||||
> h1, h2, h3, h4, h5, h6 {
|
||||
> h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
margin: 0.5rem 0;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
@@ -30,7 +32,10 @@ const MarkdownWrapper = styled.div`
|
||||
> h2 {
|
||||
font-weight: 600;
|
||||
}
|
||||
> h3, h4, h5, h6 {
|
||||
> h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
font-weight: 500;
|
||||
}
|
||||
& strong {
|
||||
@@ -40,12 +45,12 @@ const MarkdownWrapper = styled.div`
|
||||
`;
|
||||
|
||||
class MarkdownView extends React.Component<Props> {
|
||||
static defaultProps = {
|
||||
static defaultProps: Partial<Props> = {
|
||||
enableAnchorHeadings: false,
|
||||
skipHtml: false
|
||||
};
|
||||
|
||||
contentRef: ?HTMLDivElement;
|
||||
contentRef: HTMLDivElement | null | undefined;
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
@@ -67,13 +72,7 @@ class MarkdownView extends React.Component<Props> {
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
content,
|
||||
renderers,
|
||||
renderContext,
|
||||
enableAnchorHeadings,
|
||||
skipHtml
|
||||
} = this.props;
|
||||
const { content, renderers, renderContext, enableAnchorHeadings, skipHtml } = this.props;
|
||||
|
||||
const rendererFactory = binder.getExtension("markdown-renderer-factory");
|
||||
let rendererList = renderers;
|
||||
@@ -1,20 +1,13 @@
|
||||
//@flow
|
||||
import * as React from "react";
|
||||
import React, { ReactNode } from "react";
|
||||
import classNames from "classnames";
|
||||
|
||||
type NotificationType =
|
||||
| "primary"
|
||||
| "info"
|
||||
| "success"
|
||||
| "warning"
|
||||
| "danger"
|
||||
| "inherit";
|
||||
type NotificationType = "primary" | "info" | "success" | "warning" | "danger" | "inherit";
|
||||
|
||||
type Props = {
|
||||
type: NotificationType,
|
||||
onClose?: () => void,
|
||||
className?: string,
|
||||
children?: React.Node
|
||||
type: NotificationType;
|
||||
onClose?: () => void;
|
||||
className?: string;
|
||||
children?: ReactNode;
|
||||
};
|
||||
|
||||
class Notification extends React.Component<Props> {
|
||||
@@ -1,19 +1,18 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import type { History } from "history";
|
||||
import { withRouter } from "react-router-dom";
|
||||
import { History } from "history";
|
||||
import { withRouter, RouteComponentProps } from "react-router-dom";
|
||||
import classNames from "classnames";
|
||||
import { Button, urls } from "./index";
|
||||
import { FilterInput } from "./forms";
|
||||
|
||||
type Props = {
|
||||
showCreateButton: boolean,
|
||||
link: string,
|
||||
label?: string,
|
||||
type Props = RouteComponentProps & {
|
||||
showCreateButton: boolean;
|
||||
link: string;
|
||||
label?: string;
|
||||
|
||||
// context props
|
||||
history: History,
|
||||
location: any
|
||||
history: History;
|
||||
location: any;
|
||||
};
|
||||
|
||||
class OverviewPageActions extends React.Component<Props> {
|
||||
@@ -1,11 +1,9 @@
|
||||
// @flow
|
||||
import React from "react";
|
||||
import { mount, shallow } from "@scm-manager/ui-tests/enzyme-router";
|
||||
import "@scm-manager/ui-tests/i18n";
|
||||
import Paginator from "./Paginator";
|
||||
|
||||
xdescribe("paginator rendering tests", () => {
|
||||
|
||||
const dummyLink = {
|
||||
href: "https://dummy"
|
||||
};
|
||||
@@ -18,14 +16,13 @@ xdescribe("paginator rendering tests", () => {
|
||||
_embedded: {}
|
||||
};
|
||||
|
||||
const paginator = shallow(
|
||||
<Paginator collection={collection} />
|
||||
);
|
||||
const paginator = shallow(<Paginator collection={collection} />);
|
||||
const buttons = paginator.find("Button");
|
||||
expect(buttons.length).toBe(7);
|
||||
for (let button of buttons) {
|
||||
buttons.forEach(button => {
|
||||
// @ts-ignore ???
|
||||
expect(button.props.disabled).toBeTruthy();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it("should render buttons for first page", () => {
|
||||
@@ -40,9 +37,7 @@ xdescribe("paginator rendering tests", () => {
|
||||
_embedded: {}
|
||||
};
|
||||
|
||||
const paginator = shallow(
|
||||
<Paginator collection={collection} />
|
||||
);
|
||||
const paginator = shallow(<Paginator collection={collection} />);
|
||||
const buttons = paginator.find("Button");
|
||||
expect(buttons.length).toBe(5);
|
||||
|
||||
@@ -79,9 +74,7 @@ xdescribe("paginator rendering tests", () => {
|
||||
_embedded: {}
|
||||
};
|
||||
|
||||
const paginator = shallow(
|
||||
<Paginator collection={collection} />
|
||||
);
|
||||
const paginator = shallow(<Paginator collection={collection} />);
|
||||
const buttons = paginator.find("Button");
|
||||
expect(buttons.length).toBe(6);
|
||||
|
||||
@@ -121,9 +114,7 @@ xdescribe("paginator rendering tests", () => {
|
||||
_embedded: {}
|
||||
};
|
||||
|
||||
const paginator = shallow(
|
||||
<Paginator collection={collection} />
|
||||
);
|
||||
const paginator = shallow(<Paginator collection={collection} />);
|
||||
const buttons = paginator.find("Button");
|
||||
expect(buttons.length).toBe(5);
|
||||
|
||||
@@ -160,9 +151,7 @@ xdescribe("paginator rendering tests", () => {
|
||||
_embedded: {}
|
||||
};
|
||||
|
||||
const paginator = shallow(
|
||||
<Paginator collection={collection} />
|
||||
);
|
||||
const paginator = shallow(<Paginator collection={collection} />);
|
||||
const buttons = paginator.find("Button");
|
||||
expect(buttons.length).toBe(6);
|
||||
|
||||
@@ -204,9 +193,7 @@ xdescribe("paginator rendering tests", () => {
|
||||
_embedded: {}
|
||||
};
|
||||
|
||||
const paginator = shallow(
|
||||
<Paginator collection={collection} />
|
||||
);
|
||||
const paginator = shallow(<Paginator collection={collection} />);
|
||||
const buttons = paginator.find("Button");
|
||||
expect(buttons.length).toBe(7);
|
||||
|
||||
@@ -261,9 +248,7 @@ xdescribe("paginator rendering tests", () => {
|
||||
urlToOpen = url;
|
||||
};
|
||||
|
||||
const paginator = mount(
|
||||
<Paginator collection={collection} onPageChange={callMe} />
|
||||
);
|
||||
const paginator = mount(<Paginator collection={collection} onPageChange={callMe} />);
|
||||
paginator.find("Button.pagination-previous").simulate("click");
|
||||
|
||||
expect(urlToOpen).toBe("https://www.scm-manager.org");
|
||||
@@ -1,13 +1,11 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import { translate } from "react-i18next";
|
||||
import type { PagedCollection } from "@scm-manager/ui-types";
|
||||
import { WithTranslation, withTranslation } from "react-i18next";
|
||||
import { PagedCollection, Link } from "@scm-manager/ui-types";
|
||||
import { Button } from "./buttons";
|
||||
|
||||
type Props = {
|
||||
collection: PagedCollection,
|
||||
onPageChange?: string => void,
|
||||
t: string => string
|
||||
type Props = WithTranslation & {
|
||||
collection: PagedCollection;
|
||||
onPageChange?: (p: string) => void;
|
||||
};
|
||||
|
||||
class Paginator extends React.Component<Props> {
|
||||
@@ -18,7 +16,7 @@ class Paginator extends React.Component<Props> {
|
||||
createAction = (linkType: string) => () => {
|
||||
const { collection, onPageChange } = this.props;
|
||||
if (onPageChange) {
|
||||
const link = collection._links[linkType];
|
||||
const link = collection._links[linkType] as Link;
|
||||
if (link && link.href) {
|
||||
onPageChange(link.href);
|
||||
}
|
||||
@@ -31,11 +29,7 @@ class Paginator extends React.Component<Props> {
|
||||
|
||||
renderPreviousButton() {
|
||||
const { t } = this.props;
|
||||
return this.renderButton(
|
||||
"pagination-previous",
|
||||
t("paginator.previous"),
|
||||
"prev"
|
||||
);
|
||||
return this.renderButton("pagination-previous", t("paginator.previous"), "prev");
|
||||
}
|
||||
|
||||
renderNextButton() {
|
||||
@@ -68,13 +62,7 @@ class Paginator extends React.Component<Props> {
|
||||
}
|
||||
|
||||
currentPage(page: number) {
|
||||
return (
|
||||
<Button
|
||||
className="pagination-link is-current"
|
||||
label={page}
|
||||
disabled={true}
|
||||
/>
|
||||
);
|
||||
return <Button className="pagination-link is-current" label={"" + page} disabled={true} />;
|
||||
}
|
||||
|
||||
pageLinks() {
|
||||
@@ -98,16 +86,13 @@ class Paginator extends React.Component<Props> {
|
||||
if (page + 1 < pageTotal) {
|
||||
links.push(this.renderPageButton(page + 1, "next"));
|
||||
}
|
||||
if (page + 2 < pageTotal)
|
||||
//if there exists pages between next and last
|
||||
links.push(this.seperator());
|
||||
if (page + 2 < pageTotal) links.push(this.seperator());
|
||||
//if there exists pages between next and last
|
||||
if (page < pageTotal) {
|
||||
links.push(this.renderLastButton());
|
||||
}
|
||||
|
||||
return links;
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<nav className="pagination is-centered" aria-label="pagination">
|
||||
@@ -122,5 +107,4 @@ class Paginator extends React.Component<Props> {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default translate("commons")(Paginator);
|
||||
export default withTranslation("commons")(Paginator);
|
||||
@@ -1,13 +1,12 @@
|
||||
//@flow
|
||||
import React, { Component } from "react";
|
||||
import { Route, Redirect, withRouter } from "react-router-dom";
|
||||
import { Route, Redirect, withRouter, RouteComponentProps, RouteProps } from "react-router-dom";
|
||||
|
||||
type Props = {
|
||||
authenticated?: boolean,
|
||||
component: Component<any, any>
|
||||
};
|
||||
type Props = RouteComponentProps &
|
||||
RouteProps & {
|
||||
authenticated?: boolean;
|
||||
};
|
||||
|
||||
class ProtectedRoute extends React.Component<Props> {
|
||||
class ProtectedRoute extends Component<Props> {
|
||||
renderRoute = (Component: any, authenticated?: boolean) => {
|
||||
return (routeProps: any) => {
|
||||
if (authenticated) {
|
||||
@@ -17,7 +16,9 @@ class ProtectedRoute extends React.Component<Props> {
|
||||
<Redirect
|
||||
to={{
|
||||
pathname: "/login",
|
||||
state: { from: routeProps.location }
|
||||
state: {
|
||||
from: routeProps.location
|
||||
}
|
||||
}}
|
||||
/>
|
||||
);
|
||||
@@ -27,12 +28,7 @@ class ProtectedRoute extends React.Component<Props> {
|
||||
|
||||
render() {
|
||||
const { component, authenticated, ...routeProps } = this.props;
|
||||
return (
|
||||
<Route
|
||||
{...routeProps}
|
||||
render={this.renderRoute(component, authenticated)}
|
||||
/>
|
||||
);
|
||||
return <Route {...routeProps} render={this.renderRoute(component, authenticated)} />;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,28 +1,17 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import { translate } from "react-i18next";
|
||||
import type { PagedCollection } from "@scm-manager/ui-types";
|
||||
import { WithTranslation, withTranslation } from "react-i18next";
|
||||
import { PagedCollection } from "@scm-manager/ui-types";
|
||||
import { Button } from "./index";
|
||||
|
||||
type Props = {
|
||||
collection: PagedCollection,
|
||||
page: number,
|
||||
updatePage: number => void,
|
||||
|
||||
// context props
|
||||
t: string => string
|
||||
type Props = WithTranslation & {
|
||||
collection: PagedCollection;
|
||||
page: number;
|
||||
updatePage: (p: number) => void;
|
||||
};
|
||||
|
||||
class StatePaginator extends React.Component<Props> {
|
||||
renderFirstButton() {
|
||||
return (
|
||||
<Button
|
||||
className="pagination-link"
|
||||
label={"1"}
|
||||
disabled={false}
|
||||
action={() => this.updateCurrentPage(1)}
|
||||
/>
|
||||
);
|
||||
return <Button className="pagination-link" label={"1"} disabled={false} action={() => this.updateCurrentPage(1)} />;
|
||||
}
|
||||
|
||||
updateCurrentPage = (newPage: number) => {
|
||||
@@ -81,7 +70,7 @@ class StatePaginator extends React.Component<Props> {
|
||||
return (
|
||||
<Button
|
||||
className="pagination-link is-current"
|
||||
label={page}
|
||||
label={"" + page}
|
||||
disabled={true}
|
||||
action={() => this.updateCurrentPage(page)}
|
||||
/>
|
||||
@@ -109,16 +98,13 @@ class StatePaginator extends React.Component<Props> {
|
||||
if (page + 1 < pageTotal) {
|
||||
links.push(this.renderNextButton());
|
||||
}
|
||||
if (page + 2 < pageTotal)
|
||||
//if there exists pages between next and last
|
||||
links.push(this.separator());
|
||||
if (page + 2 < pageTotal) links.push(this.separator());
|
||||
//if there exists pages between next and last
|
||||
if (page < pageTotal) {
|
||||
links.push(this.renderLastButton());
|
||||
}
|
||||
|
||||
return links;
|
||||
}
|
||||
|
||||
render() {
|
||||
const { t } = this.props;
|
||||
return (
|
||||
@@ -134,5 +120,4 @@ class StatePaginator extends React.Component<Props> {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default translate("commons")(StatePaginator);
|
||||
export default withTranslation("commons")(StatePaginator);
|
||||
@@ -1,27 +0,0 @@
|
||||
// @flow
|
||||
import React from "react";
|
||||
|
||||
import {LightAsync as ReactSyntaxHighlighter} from "react-syntax-highlighter";
|
||||
import {arduinoLight} from "react-syntax-highlighter/dist/cjs/styles/hljs";
|
||||
|
||||
type Props = {
|
||||
language: string,
|
||||
value: string
|
||||
};
|
||||
|
||||
class SyntaxHighlighter extends React.Component<Props> {
|
||||
|
||||
render() {
|
||||
return (
|
||||
<ReactSyntaxHighlighter
|
||||
showLineNumbers="false"
|
||||
language={this.props.language}
|
||||
style={arduinoLight}
|
||||
>
|
||||
{this.props.value}
|
||||
</ReactSyntaxHighlighter>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default SyntaxHighlighter;
|
||||
@@ -1,12 +1,11 @@
|
||||
// @flow
|
||||
import React from "react";
|
||||
import {storiesOf} from "@storybook/react";
|
||||
import { storiesOf } from "@storybook/react";
|
||||
import styled from "styled-components";
|
||||
import SyntaxHighlighter from "./SyntaxHighlighter";
|
||||
|
||||
import JavaHttpServer from "./__resources__/HttpServer.java";
|
||||
import GoHttpServer from "./__resources__/HttpServer.go";
|
||||
import JsHttpServer from "./__resources__/HttpServer.js.js";
|
||||
import JsHttpServer from "./__resources__/HttpServer.js";
|
||||
import PyHttpServer from "./__resources__/HttpServer.py";
|
||||
|
||||
const Spacing = styled.div`
|
||||
22
scm-ui/ui-components/src/SyntaxHighlighter.tsx
Normal file
22
scm-ui/ui-components/src/SyntaxHighlighter.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
import React from "react";
|
||||
|
||||
import { LightAsync as ReactSyntaxHighlighter } from "react-syntax-highlighter";
|
||||
// @ts-ignore
|
||||
import { arduinoLight } from "react-syntax-highlighter/dist/cjs/styles/hljs";
|
||||
|
||||
type Props = {
|
||||
language: string;
|
||||
value: string;
|
||||
};
|
||||
|
||||
class SyntaxHighlighter extends React.Component<Props> {
|
||||
render() {
|
||||
return (
|
||||
<ReactSyntaxHighlighter showLineNumbers={false} language={this.props.language} style={arduinoLight}>
|
||||
{this.props.value}
|
||||
</ReactSyntaxHighlighter>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default SyntaxHighlighter;
|
||||
@@ -1,15 +1,14 @@
|
||||
//@flow
|
||||
import * as React from "react";
|
||||
import classNames from "classnames";
|
||||
|
||||
type Props = {
|
||||
className?: string,
|
||||
color: string,
|
||||
icon?: string,
|
||||
label: string,
|
||||
title?: string,
|
||||
onClick?: () => void,
|
||||
onRemove?: () => void
|
||||
className?: string;
|
||||
color: string;
|
||||
icon?: string;
|
||||
label: string;
|
||||
title?: string;
|
||||
onClick?: () => void;
|
||||
onRemove?: () => void;
|
||||
};
|
||||
|
||||
class Tag extends React.Component<Props> {
|
||||
@@ -18,15 +17,7 @@ class Tag extends React.Component<Props> {
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
className,
|
||||
color,
|
||||
icon,
|
||||
label,
|
||||
title,
|
||||
onClick,
|
||||
onRemove
|
||||
} = this.props;
|
||||
const { className, color, icon, label, title, onClick, onRemove } = this.props;
|
||||
let showIcon = null;
|
||||
if (icon) {
|
||||
showIcon = (
|
||||
@@ -43,11 +34,7 @@ class Tag extends React.Component<Props> {
|
||||
|
||||
return (
|
||||
<>
|
||||
<span
|
||||
className={classNames("tag", `is-${color}`, className)}
|
||||
title={title}
|
||||
onClick={onClick}
|
||||
>
|
||||
<span className={classNames("tag", `is-${color}`, className)} title={title} onClick={onClick}>
|
||||
{showIcon}
|
||||
{label}
|
||||
</span>
|
||||
@@ -1,16 +1,14 @@
|
||||
//@flow
|
||||
import * as React from "react";
|
||||
import React, { ReactNode } from "react";
|
||||
import classNames from "classnames";
|
||||
|
||||
type Props = {
|
||||
message: string,
|
||||
className?: string,
|
||||
location: string,
|
||||
children: React.Node
|
||||
message: string;
|
||||
className?: string;
|
||||
location: string;
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
class Tooltip extends React.Component<Props> {
|
||||
|
||||
static defaultProps = {
|
||||
location: "right"
|
||||
};
|
||||
@@ -19,10 +17,7 @@ class Tooltip extends React.Component<Props> {
|
||||
const { className, message, location, children } = this.props;
|
||||
const multiline = message.length > 60 ? "is-tooltip-multiline" : "";
|
||||
return (
|
||||
<span
|
||||
className={classNames("tooltip", "is-tooltip-" + location, multiline, className)}
|
||||
data-tooltip={message}
|
||||
>
|
||||
<span className={classNames("tooltip", "is-tooltip-" + location, multiline, className)} data-tooltip={message}>
|
||||
{children}
|
||||
</span>
|
||||
);
|
||||
@@ -1,15 +1,9 @@
|
||||
// @flow
|
||||
import React from "react";
|
||||
import {translate} from "react-i18next";
|
||||
import type AutocompleteProps from "./UserGroupAutocomplete";
|
||||
import { WithTranslation, withTranslation } from "react-i18next";
|
||||
import AutocompleteProps from "./UserGroupAutocomplete";
|
||||
import UserGroupAutocomplete from "./UserGroupAutocomplete";
|
||||
|
||||
type Props = AutocompleteProps & {
|
||||
// Context props
|
||||
t: string => string
|
||||
};
|
||||
|
||||
class UserAutocomplete extends React.Component<Props> {
|
||||
class UserAutocomplete extends React.Component<AutocompleteProps & WithTranslation> {
|
||||
render() {
|
||||
const { t } = this.props;
|
||||
return (
|
||||
@@ -24,4 +18,4 @@ class UserAutocomplete extends React.Component<Props> {
|
||||
}
|
||||
}
|
||||
|
||||
export default translate("commons")(UserAutocomplete);
|
||||
export default withTranslation("commons")(UserAutocomplete);
|
||||
@@ -1,32 +1,29 @@
|
||||
// @flow
|
||||
import React from "react";
|
||||
import type {SelectValue} from "@scm-manager/ui-types";
|
||||
import { SelectValue, AutocompleteObject } from "@scm-manager/ui-types";
|
||||
import Autocomplete from "./Autocomplete";
|
||||
|
||||
export type AutocompleteProps = {
|
||||
autocompleteLink: string,
|
||||
valueSelected: SelectValue => void,
|
||||
value?: SelectValue
|
||||
autocompleteLink?: string;
|
||||
valueSelected?: (p: SelectValue) => void;
|
||||
value?: SelectValue;
|
||||
};
|
||||
|
||||
type Props = AutocompleteProps & {
|
||||
label: string,
|
||||
noOptionsMessage: string,
|
||||
loadingMessage: string,
|
||||
placeholder: string
|
||||
label: string;
|
||||
noOptionsMessage: string;
|
||||
loadingMessage: string;
|
||||
placeholder: string;
|
||||
};
|
||||
|
||||
export default class UserGroupAutocomplete extends React.Component<Props> {
|
||||
loadSuggestions = (inputValue: string) => {
|
||||
loadSuggestions = (inputValue: string): Promise<SelectValue[]> => {
|
||||
const url = this.props.autocompleteLink;
|
||||
const link = url + "?q=";
|
||||
return fetch(link + inputValue)
|
||||
.then(response => response.json())
|
||||
.then(json => {
|
||||
.then((json: AutocompleteObject[]) => {
|
||||
return json.map(element => {
|
||||
const label = element.displayName
|
||||
? `${element.displayName} (${element.id})`
|
||||
: element.id;
|
||||
const label = element.displayName ? `${element.displayName} (${element.id})` : element.id;
|
||||
return {
|
||||
value: element,
|
||||
label
|
||||
@@ -36,7 +33,9 @@ export default class UserGroupAutocomplete extends React.Component<Props> {
|
||||
};
|
||||
|
||||
selectName = (selection: SelectValue) => {
|
||||
this.props.valueSelected(selection);
|
||||
if (this.props.valueSelected) {
|
||||
this.props.valueSelected(selection);
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
@@ -1,4 +1,5 @@
|
||||
export default `var http = require('http');
|
||||
|
||||
http.createServer(function (req, res) {
|
||||
res.writeHead(200, {'Content-Type': 'text/plain'});
|
||||
res.write('Hello World!');
|
||||
@@ -5,7 +5,7 @@ exports[`Storyshots Buttons|AddButton Default 1`] = `
|
||||
className="sc-htoDjs bIDNS"
|
||||
>
|
||||
<button
|
||||
className="button is-primary"
|
||||
className="button is-default"
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
@@ -243,6 +243,30 @@ exports[`Storyshots Buttons|DownloadButton Default 1`] = `
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Storyshots Buttons|DownloadButton Disabled 1`] = `
|
||||
<div
|
||||
className="sc-htoDjs bIDNS"
|
||||
>
|
||||
<a
|
||||
className="button is-link"
|
||||
disabled={true}
|
||||
href=""
|
||||
onClick={[Function]}
|
||||
>
|
||||
<span
|
||||
className="icon is-medium"
|
||||
>
|
||||
<i
|
||||
className="fas fa-arrow-circle-down"
|
||||
/>
|
||||
</span>
|
||||
<span>
|
||||
Download
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Storyshots Buttons|EditButton Default 1`] = `
|
||||
<div
|
||||
className="sc-htoDjs bIDNS"
|
||||
@@ -310,6 +334,128 @@ exports[`Storyshots DateFromNow Default 1`] = `
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Storyshots Forms|Checkbox Default 1`] = `
|
||||
<div
|
||||
className="sc-gipzik xsalO"
|
||||
>
|
||||
<div
|
||||
className="field is-grouped"
|
||||
>
|
||||
<div
|
||||
className="control"
|
||||
>
|
||||
<label
|
||||
className="checkbox"
|
||||
>
|
||||
<input
|
||||
checked={false}
|
||||
onChange={[Function]}
|
||||
type="checkbox"
|
||||
/>
|
||||
|
||||
Not checked
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="field is-grouped"
|
||||
>
|
||||
<div
|
||||
className="control"
|
||||
>
|
||||
<label
|
||||
className="checkbox"
|
||||
>
|
||||
<input
|
||||
checked={true}
|
||||
onChange={[Function]}
|
||||
type="checkbox"
|
||||
/>
|
||||
|
||||
Checked
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Storyshots Forms|Checkbox Disabled 1`] = `
|
||||
<div
|
||||
className="sc-gipzik xsalO"
|
||||
>
|
||||
<div
|
||||
className="field is-grouped"
|
||||
>
|
||||
<div
|
||||
className="control"
|
||||
>
|
||||
<label
|
||||
className="checkbox"
|
||||
disabled={true}
|
||||
>
|
||||
<input
|
||||
checked={true}
|
||||
disabled={true}
|
||||
onChange={[Function]}
|
||||
type="checkbox"
|
||||
/>
|
||||
|
||||
Checked but disabled
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Storyshots Forms|Radio Default 1`] = `
|
||||
<div
|
||||
className="sc-csuQGl fFFkRK"
|
||||
>
|
||||
<label
|
||||
className="radio"
|
||||
>
|
||||
<input
|
||||
checked={false}
|
||||
onChange={[Function]}
|
||||
type="radio"
|
||||
/>
|
||||
|
||||
Not checked
|
||||
</label>
|
||||
<label
|
||||
className="radio"
|
||||
>
|
||||
<input
|
||||
checked={true}
|
||||
onChange={[Function]}
|
||||
type="radio"
|
||||
/>
|
||||
|
||||
Checked
|
||||
</label>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Storyshots Forms|Radio Disabled 1`] = `
|
||||
<div
|
||||
className="sc-csuQGl fFFkRK"
|
||||
>
|
||||
<label
|
||||
className="radio"
|
||||
disabled={true}
|
||||
>
|
||||
<input
|
||||
checked={true}
|
||||
disabled={true}
|
||||
onChange={[Function]}
|
||||
type="radio"
|
||||
/>
|
||||
|
||||
Checked but disabled
|
||||
</label>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Storyshots Loading Default 1`] = `
|
||||
<div>
|
||||
<div
|
||||
@@ -343,7 +489,7 @@ exports[`Storyshots MarkdownView Default 1`] = `
|
||||
className="sc-EHOje lbpDzp"
|
||||
>
|
||||
<div
|
||||
className="sc-ifAKCX bndlBs"
|
||||
className="sc-ifAKCX frmxmY"
|
||||
>
|
||||
<div
|
||||
className="content"
|
||||
@@ -967,120 +1113,6 @@ Deserunt officia esse aliquip consectetur duis ut labore laborum commodo aliquip
|
||||
}
|
||||
}
|
||||
>
|
||||
<code
|
||||
style={
|
||||
Object {
|
||||
"float": "left",
|
||||
"paddingRight": "10px",
|
||||
}
|
||||
}
|
||||
>
|
||||
<span
|
||||
className="react-syntax-highlighter-line-number"
|
||||
style={Object {}}
|
||||
>
|
||||
1
|
||||
|
||||
</span>
|
||||
<span
|
||||
className="react-syntax-highlighter-line-number"
|
||||
style={Object {}}
|
||||
>
|
||||
2
|
||||
|
||||
</span>
|
||||
<span
|
||||
className="react-syntax-highlighter-line-number"
|
||||
style={Object {}}
|
||||
>
|
||||
3
|
||||
|
||||
</span>
|
||||
<span
|
||||
className="react-syntax-highlighter-line-number"
|
||||
style={Object {}}
|
||||
>
|
||||
4
|
||||
|
||||
</span>
|
||||
<span
|
||||
className="react-syntax-highlighter-line-number"
|
||||
style={Object {}}
|
||||
>
|
||||
5
|
||||
|
||||
</span>
|
||||
<span
|
||||
className="react-syntax-highlighter-line-number"
|
||||
style={Object {}}
|
||||
>
|
||||
6
|
||||
|
||||
</span>
|
||||
<span
|
||||
className="react-syntax-highlighter-line-number"
|
||||
style={Object {}}
|
||||
>
|
||||
7
|
||||
|
||||
</span>
|
||||
<span
|
||||
className="react-syntax-highlighter-line-number"
|
||||
style={Object {}}
|
||||
>
|
||||
8
|
||||
|
||||
</span>
|
||||
<span
|
||||
className="react-syntax-highlighter-line-number"
|
||||
style={Object {}}
|
||||
>
|
||||
9
|
||||
|
||||
</span>
|
||||
<span
|
||||
className="react-syntax-highlighter-line-number"
|
||||
style={Object {}}
|
||||
>
|
||||
10
|
||||
|
||||
</span>
|
||||
<span
|
||||
className="react-syntax-highlighter-line-number"
|
||||
style={Object {}}
|
||||
>
|
||||
11
|
||||
|
||||
</span>
|
||||
<span
|
||||
className="react-syntax-highlighter-line-number"
|
||||
style={Object {}}
|
||||
>
|
||||
12
|
||||
|
||||
</span>
|
||||
<span
|
||||
className="react-syntax-highlighter-line-number"
|
||||
style={Object {}}
|
||||
>
|
||||
13
|
||||
|
||||
</span>
|
||||
<span
|
||||
className="react-syntax-highlighter-line-number"
|
||||
style={Object {}}
|
||||
>
|
||||
14
|
||||
|
||||
</span>
|
||||
<span
|
||||
className="react-syntax-highlighter-line-number"
|
||||
style={Object {}}
|
||||
>
|
||||
15
|
||||
|
||||
</span>
|
||||
</code>
|
||||
<code>
|
||||
package main
|
||||
|
||||
@@ -1221,134 +1253,6 @@ exports[`Storyshots SyntaxHighlighter Go 1`] = `
|
||||
}
|
||||
}
|
||||
>
|
||||
<code
|
||||
style={
|
||||
Object {
|
||||
"float": "left",
|
||||
"paddingRight": "10px",
|
||||
}
|
||||
}
|
||||
>
|
||||
<span
|
||||
className="react-syntax-highlighter-line-number"
|
||||
style={Object {}}
|
||||
>
|
||||
1
|
||||
|
||||
</span>
|
||||
<span
|
||||
className="react-syntax-highlighter-line-number"
|
||||
style={Object {}}
|
||||
>
|
||||
2
|
||||
|
||||
</span>
|
||||
<span
|
||||
className="react-syntax-highlighter-line-number"
|
||||
style={Object {}}
|
||||
>
|
||||
3
|
||||
|
||||
</span>
|
||||
<span
|
||||
className="react-syntax-highlighter-line-number"
|
||||
style={Object {}}
|
||||
>
|
||||
4
|
||||
|
||||
</span>
|
||||
<span
|
||||
className="react-syntax-highlighter-line-number"
|
||||
style={Object {}}
|
||||
>
|
||||
5
|
||||
|
||||
</span>
|
||||
<span
|
||||
className="react-syntax-highlighter-line-number"
|
||||
style={Object {}}
|
||||
>
|
||||
6
|
||||
|
||||
</span>
|
||||
<span
|
||||
className="react-syntax-highlighter-line-number"
|
||||
style={Object {}}
|
||||
>
|
||||
7
|
||||
|
||||
</span>
|
||||
<span
|
||||
className="react-syntax-highlighter-line-number"
|
||||
style={Object {}}
|
||||
>
|
||||
8
|
||||
|
||||
</span>
|
||||
<span
|
||||
className="react-syntax-highlighter-line-number"
|
||||
style={Object {}}
|
||||
>
|
||||
9
|
||||
|
||||
</span>
|
||||
<span
|
||||
className="react-syntax-highlighter-line-number"
|
||||
style={Object {}}
|
||||
>
|
||||
10
|
||||
|
||||
</span>
|
||||
<span
|
||||
className="react-syntax-highlighter-line-number"
|
||||
style={Object {}}
|
||||
>
|
||||
11
|
||||
|
||||
</span>
|
||||
<span
|
||||
className="react-syntax-highlighter-line-number"
|
||||
style={Object {}}
|
||||
>
|
||||
12
|
||||
|
||||
</span>
|
||||
<span
|
||||
className="react-syntax-highlighter-line-number"
|
||||
style={Object {}}
|
||||
>
|
||||
13
|
||||
|
||||
</span>
|
||||
<span
|
||||
className="react-syntax-highlighter-line-number"
|
||||
style={Object {}}
|
||||
>
|
||||
14
|
||||
|
||||
</span>
|
||||
<span
|
||||
className="react-syntax-highlighter-line-number"
|
||||
style={Object {}}
|
||||
>
|
||||
15
|
||||
|
||||
</span>
|
||||
<span
|
||||
className="react-syntax-highlighter-line-number"
|
||||
style={Object {}}
|
||||
>
|
||||
16
|
||||
|
||||
</span>
|
||||
<span
|
||||
className="react-syntax-highlighter-line-number"
|
||||
style={Object {}}
|
||||
>
|
||||
17
|
||||
|
||||
</span>
|
||||
</code>
|
||||
<code>
|
||||
<span
|
||||
style={
|
||||
@@ -1557,232 +1461,6 @@ exports[`Storyshots SyntaxHighlighter Java 1`] = `
|
||||
}
|
||||
}
|
||||
>
|
||||
<code
|
||||
style={
|
||||
Object {
|
||||
"float": "left",
|
||||
"paddingRight": "10px",
|
||||
}
|
||||
}
|
||||
>
|
||||
<span
|
||||
className="react-syntax-highlighter-line-number"
|
||||
style={Object {}}
|
||||
>
|
||||
1
|
||||
|
||||
</span>
|
||||
<span
|
||||
className="react-syntax-highlighter-line-number"
|
||||
style={Object {}}
|
||||
>
|
||||
2
|
||||
|
||||
</span>
|
||||
<span
|
||||
className="react-syntax-highlighter-line-number"
|
||||
style={Object {}}
|
||||
>
|
||||
3
|
||||
|
||||
</span>
|
||||
<span
|
||||
className="react-syntax-highlighter-line-number"
|
||||
style={Object {}}
|
||||
>
|
||||
4
|
||||
|
||||
</span>
|
||||
<span
|
||||
className="react-syntax-highlighter-line-number"
|
||||
style={Object {}}
|
||||
>
|
||||
5
|
||||
|
||||
</span>
|
||||
<span
|
||||
className="react-syntax-highlighter-line-number"
|
||||
style={Object {}}
|
||||
>
|
||||
6
|
||||
|
||||
</span>
|
||||
<span
|
||||
className="react-syntax-highlighter-line-number"
|
||||
style={Object {}}
|
||||
>
|
||||
7
|
||||
|
||||
</span>
|
||||
<span
|
||||
className="react-syntax-highlighter-line-number"
|
||||
style={Object {}}
|
||||
>
|
||||
8
|
||||
|
||||
</span>
|
||||
<span
|
||||
className="react-syntax-highlighter-line-number"
|
||||
style={Object {}}
|
||||
>
|
||||
9
|
||||
|
||||
</span>
|
||||
<span
|
||||
className="react-syntax-highlighter-line-number"
|
||||
style={Object {}}
|
||||
>
|
||||
10
|
||||
|
||||
</span>
|
||||
<span
|
||||
className="react-syntax-highlighter-line-number"
|
||||
style={Object {}}
|
||||
>
|
||||
11
|
||||
|
||||
</span>
|
||||
<span
|
||||
className="react-syntax-highlighter-line-number"
|
||||
style={Object {}}
|
||||
>
|
||||
12
|
||||
|
||||
</span>
|
||||
<span
|
||||
className="react-syntax-highlighter-line-number"
|
||||
style={Object {}}
|
||||
>
|
||||
13
|
||||
|
||||
</span>
|
||||
<span
|
||||
className="react-syntax-highlighter-line-number"
|
||||
style={Object {}}
|
||||
>
|
||||
14
|
||||
|
||||
</span>
|
||||
<span
|
||||
className="react-syntax-highlighter-line-number"
|
||||
style={Object {}}
|
||||
>
|
||||
15
|
||||
|
||||
</span>
|
||||
<span
|
||||
className="react-syntax-highlighter-line-number"
|
||||
style={Object {}}
|
||||
>
|
||||
16
|
||||
|
||||
</span>
|
||||
<span
|
||||
className="react-syntax-highlighter-line-number"
|
||||
style={Object {}}
|
||||
>
|
||||
17
|
||||
|
||||
</span>
|
||||
<span
|
||||
className="react-syntax-highlighter-line-number"
|
||||
style={Object {}}
|
||||
>
|
||||
18
|
||||
|
||||
</span>
|
||||
<span
|
||||
className="react-syntax-highlighter-line-number"
|
||||
style={Object {}}
|
||||
>
|
||||
19
|
||||
|
||||
</span>
|
||||
<span
|
||||
className="react-syntax-highlighter-line-number"
|
||||
style={Object {}}
|
||||
>
|
||||
20
|
||||
|
||||
</span>
|
||||
<span
|
||||
className="react-syntax-highlighter-line-number"
|
||||
style={Object {}}
|
||||
>
|
||||
21
|
||||
|
||||
</span>
|
||||
<span
|
||||
className="react-syntax-highlighter-line-number"
|
||||
style={Object {}}
|
||||
>
|
||||
22
|
||||
|
||||
</span>
|
||||
<span
|
||||
className="react-syntax-highlighter-line-number"
|
||||
style={Object {}}
|
||||
>
|
||||
23
|
||||
|
||||
</span>
|
||||
<span
|
||||
className="react-syntax-highlighter-line-number"
|
||||
style={Object {}}
|
||||
>
|
||||
24
|
||||
|
||||
</span>
|
||||
<span
|
||||
className="react-syntax-highlighter-line-number"
|
||||
style={Object {}}
|
||||
>
|
||||
25
|
||||
|
||||
</span>
|
||||
<span
|
||||
className="react-syntax-highlighter-line-number"
|
||||
style={Object {}}
|
||||
>
|
||||
26
|
||||
|
||||
</span>
|
||||
<span
|
||||
className="react-syntax-highlighter-line-number"
|
||||
style={Object {}}
|
||||
>
|
||||
27
|
||||
|
||||
</span>
|
||||
<span
|
||||
className="react-syntax-highlighter-line-number"
|
||||
style={Object {}}
|
||||
>
|
||||
28
|
||||
|
||||
</span>
|
||||
<span
|
||||
className="react-syntax-highlighter-line-number"
|
||||
style={Object {}}
|
||||
>
|
||||
29
|
||||
|
||||
</span>
|
||||
<span
|
||||
className="react-syntax-highlighter-line-number"
|
||||
style={Object {}}
|
||||
>
|
||||
30
|
||||
|
||||
</span>
|
||||
<span
|
||||
className="react-syntax-highlighter-line-number"
|
||||
style={Object {}}
|
||||
>
|
||||
31
|
||||
|
||||
</span>
|
||||
</code>
|
||||
<code>
|
||||
<span
|
||||
style={
|
||||
@@ -2222,57 +1900,6 @@ exports[`Storyshots SyntaxHighlighter Javascript 1`] = `
|
||||
}
|
||||
}
|
||||
>
|
||||
<code
|
||||
style={
|
||||
Object {
|
||||
"float": "left",
|
||||
"paddingRight": "10px",
|
||||
}
|
||||
}
|
||||
>
|
||||
<span
|
||||
className="react-syntax-highlighter-line-number"
|
||||
style={Object {}}
|
||||
>
|
||||
1
|
||||
|
||||
</span>
|
||||
<span
|
||||
className="react-syntax-highlighter-line-number"
|
||||
style={Object {}}
|
||||
>
|
||||
2
|
||||
|
||||
</span>
|
||||
<span
|
||||
className="react-syntax-highlighter-line-number"
|
||||
style={Object {}}
|
||||
>
|
||||
3
|
||||
|
||||
</span>
|
||||
<span
|
||||
className="react-syntax-highlighter-line-number"
|
||||
style={Object {}}
|
||||
>
|
||||
4
|
||||
|
||||
</span>
|
||||
<span
|
||||
className="react-syntax-highlighter-line-number"
|
||||
style={Object {}}
|
||||
>
|
||||
5
|
||||
|
||||
</span>
|
||||
<span
|
||||
className="react-syntax-highlighter-line-number"
|
||||
style={Object {}}
|
||||
>
|
||||
6
|
||||
|
||||
</span>
|
||||
</code>
|
||||
<code>
|
||||
<span
|
||||
style={
|
||||
@@ -2304,6 +1931,7 @@ exports[`Storyshots SyntaxHighlighter Javascript 1`] = `
|
||||
'http'
|
||||
</span>
|
||||
);
|
||||
|
||||
http.createServer(
|
||||
<span
|
||||
style={
|
||||
@@ -2406,155 +2034,6 @@ exports[`Storyshots SyntaxHighlighter Python 1`] = `
|
||||
}
|
||||
}
|
||||
>
|
||||
<code
|
||||
style={
|
||||
Object {
|
||||
"float": "left",
|
||||
"paddingRight": "10px",
|
||||
}
|
||||
}
|
||||
>
|
||||
<span
|
||||
className="react-syntax-highlighter-line-number"
|
||||
style={Object {}}
|
||||
>
|
||||
1
|
||||
|
||||
</span>
|
||||
<span
|
||||
className="react-syntax-highlighter-line-number"
|
||||
style={Object {}}
|
||||
>
|
||||
2
|
||||
|
||||
</span>
|
||||
<span
|
||||
className="react-syntax-highlighter-line-number"
|
||||
style={Object {}}
|
||||
>
|
||||
3
|
||||
|
||||
</span>
|
||||
<span
|
||||
className="react-syntax-highlighter-line-number"
|
||||
style={Object {}}
|
||||
>
|
||||
4
|
||||
|
||||
</span>
|
||||
<span
|
||||
className="react-syntax-highlighter-line-number"
|
||||
style={Object {}}
|
||||
>
|
||||
5
|
||||
|
||||
</span>
|
||||
<span
|
||||
className="react-syntax-highlighter-line-number"
|
||||
style={Object {}}
|
||||
>
|
||||
6
|
||||
|
||||
</span>
|
||||
<span
|
||||
className="react-syntax-highlighter-line-number"
|
||||
style={Object {}}
|
||||
>
|
||||
7
|
||||
|
||||
</span>
|
||||
<span
|
||||
className="react-syntax-highlighter-line-number"
|
||||
style={Object {}}
|
||||
>
|
||||
8
|
||||
|
||||
</span>
|
||||
<span
|
||||
className="react-syntax-highlighter-line-number"
|
||||
style={Object {}}
|
||||
>
|
||||
9
|
||||
|
||||
</span>
|
||||
<span
|
||||
className="react-syntax-highlighter-line-number"
|
||||
style={Object {}}
|
||||
>
|
||||
10
|
||||
|
||||
</span>
|
||||
<span
|
||||
className="react-syntax-highlighter-line-number"
|
||||
style={Object {}}
|
||||
>
|
||||
11
|
||||
|
||||
</span>
|
||||
<span
|
||||
className="react-syntax-highlighter-line-number"
|
||||
style={Object {}}
|
||||
>
|
||||
12
|
||||
|
||||
</span>
|
||||
<span
|
||||
className="react-syntax-highlighter-line-number"
|
||||
style={Object {}}
|
||||
>
|
||||
13
|
||||
|
||||
</span>
|
||||
<span
|
||||
className="react-syntax-highlighter-line-number"
|
||||
style={Object {}}
|
||||
>
|
||||
14
|
||||
|
||||
</span>
|
||||
<span
|
||||
className="react-syntax-highlighter-line-number"
|
||||
style={Object {}}
|
||||
>
|
||||
15
|
||||
|
||||
</span>
|
||||
<span
|
||||
className="react-syntax-highlighter-line-number"
|
||||
style={Object {}}
|
||||
>
|
||||
16
|
||||
|
||||
</span>
|
||||
<span
|
||||
className="react-syntax-highlighter-line-number"
|
||||
style={Object {}}
|
||||
>
|
||||
17
|
||||
|
||||
</span>
|
||||
<span
|
||||
className="react-syntax-highlighter-line-number"
|
||||
style={Object {}}
|
||||
>
|
||||
18
|
||||
|
||||
</span>
|
||||
<span
|
||||
className="react-syntax-highlighter-line-number"
|
||||
style={Object {}}
|
||||
>
|
||||
19
|
||||
|
||||
</span>
|
||||
<span
|
||||
className="react-syntax-highlighter-line-number"
|
||||
style={Object {}}
|
||||
>
|
||||
20
|
||||
|
||||
</span>
|
||||
</code>
|
||||
<code>
|
||||
<span
|
||||
style={
|
||||
@@ -1,13 +1,10 @@
|
||||
// @flow
|
||||
import { apiClient, createUrl } from "./apiclient";
|
||||
import fetchMock from "fetch-mock";
|
||||
import { BackendError } from "./errors";
|
||||
|
||||
describe("create url", () => {
|
||||
it("should not change absolute urls", () => {
|
||||
expect(createUrl("https://www.scm-manager.org")).toBe(
|
||||
"https://www.scm-manager.org"
|
||||
);
|
||||
expect(createUrl("https://www.scm-manager.org")).toBe("https://www.scm-manager.org");
|
||||
});
|
||||
|
||||
it("should add prefix for api", () => {
|
||||
@@ -16,17 +13,17 @@ describe("create url", () => {
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe("error handling tests", () => {
|
||||
|
||||
const earthNotFoundError = {
|
||||
transactionId: "42t",
|
||||
errorCode: "42e",
|
||||
message: "earth not found",
|
||||
context: [{
|
||||
type: "planet",
|
||||
id: "earth"
|
||||
}]
|
||||
context: [
|
||||
{
|
||||
type: "planet",
|
||||
id: "earth"
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
afterEach(() => {
|
||||
@@ -34,21 +31,19 @@ describe("error handling tests", () => {
|
||||
fetchMock.restore();
|
||||
});
|
||||
|
||||
it("should create a normal error, if the content type is not scmm-error", (done) => {
|
||||
|
||||
it("should create a normal error, if the content type is not scmm-error", done => {
|
||||
fetchMock.getOnce("/api/v2/error", {
|
||||
status: 404
|
||||
});
|
||||
|
||||
apiClient.get("/error")
|
||||
.catch((err: Error) => {
|
||||
expect(err.name).toEqual("Error");
|
||||
expect(err.message).toContain("404");
|
||||
done();
|
||||
});
|
||||
apiClient.get("/error").catch((err: Error) => {
|
||||
expect(err.name).toEqual("Error");
|
||||
expect(err.message).toContain("404");
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("should create an backend error, if the content type is scmm-error", (done) => {
|
||||
it("should create an backend error, if the content type is scmm-error", done => {
|
||||
fetchMock.getOnce("/api/v2/error", {
|
||||
status: 404,
|
||||
headers: {
|
||||
@@ -57,22 +52,21 @@ describe("error handling tests", () => {
|
||||
body: earthNotFoundError
|
||||
});
|
||||
|
||||
apiClient.get("/error")
|
||||
.catch((err: BackendError) => {
|
||||
apiClient.get("/error").catch((err: BackendError) => {
|
||||
expect(err).toBeInstanceOf(BackendError);
|
||||
|
||||
expect(err).toBeInstanceOf(BackendError);
|
||||
expect(err.message).toEqual("earth not found");
|
||||
expect(err.statusCode).toBe(404);
|
||||
|
||||
expect(err.message).toEqual("earth not found");
|
||||
expect(err.statusCode).toBe(404);
|
||||
|
||||
expect(err.transactionId).toEqual("42t");
|
||||
expect(err.errorCode).toEqual("42e");
|
||||
expect(err.context).toEqual([{
|
||||
expect(err.transactionId).toEqual("42t");
|
||||
expect(err.errorCode).toEqual("42e");
|
||||
expect(err.context).toEqual([
|
||||
{
|
||||
type: "planet",
|
||||
id: "earth"
|
||||
}]);
|
||||
done();
|
||||
});
|
||||
}
|
||||
]);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
@@ -1,9 +1,8 @@
|
||||
// @flow
|
||||
import { contextPath } from "./urls";
|
||||
import { createBackendError, ForbiddenError, isBackendError, UnauthorizedError } from "./errors";
|
||||
import type { BackendErrorContent } from "./errors";
|
||||
import { BackendErrorContent } from "./errors";
|
||||
|
||||
const applyFetchOptions: (RequestOptions) => RequestOptions = o => {
|
||||
const applyFetchOptions: (p: RequestInit) => RequestInit = o => {
|
||||
o.credentials = "same-origin";
|
||||
o.headers = {
|
||||
Cache: "no-cache",
|
||||
@@ -16,10 +15,9 @@ const applyFetchOptions: (RequestOptions) => RequestOptions = o => {
|
||||
function handleFailure(response: Response) {
|
||||
if (!response.ok) {
|
||||
if (isBackendError(response)) {
|
||||
return response.json()
|
||||
.then((content: BackendErrorContent) => {
|
||||
throw createBackendError(content, response.status);
|
||||
});
|
||||
return response.json().then((content: BackendErrorContent) => {
|
||||
throw createBackendError(content, response.status);
|
||||
});
|
||||
} else {
|
||||
if (response.status === 401) {
|
||||
throw new UnauthorizedError("Unauthorized", 401);
|
||||
@@ -49,27 +47,27 @@ class ApiClient {
|
||||
return fetch(createUrl(url), applyFetchOptions({})).then(handleFailure);
|
||||
}
|
||||
|
||||
post(url: string, payload: any, contentType: string = "application/json") {
|
||||
post(url: string, payload: any, contentType = "application/json") {
|
||||
return this.httpRequestWithJSONBody("POST", url, contentType, payload);
|
||||
}
|
||||
|
||||
postBinary(url: string, fileAppender: FormData => void) {
|
||||
let formData = new FormData();
|
||||
postBinary(url: string, fileAppender: (p: FormData) => void) {
|
||||
const formData = new FormData();
|
||||
fileAppender(formData);
|
||||
|
||||
let options: RequestOptions = {
|
||||
const options: RequestInit = {
|
||||
method: "POST",
|
||||
body: formData
|
||||
};
|
||||
return this.httpRequestWithBinaryBody(options, url);
|
||||
}
|
||||
|
||||
put(url: string, payload: any, contentType: string = "application/json") {
|
||||
put(url: string, payload: any, contentType = "application/json") {
|
||||
return this.httpRequestWithJSONBody("PUT", url, contentType, payload);
|
||||
}
|
||||
|
||||
head(url: string) {
|
||||
let options: RequestOptions = {
|
||||
let options: RequestInit = {
|
||||
method: "HEAD"
|
||||
};
|
||||
options = applyFetchOptions(options);
|
||||
@@ -77,30 +75,28 @@ class ApiClient {
|
||||
}
|
||||
|
||||
delete(url: string): Promise<Response> {
|
||||
let options: RequestOptions = {
|
||||
let options: RequestInit = {
|
||||
method: "DELETE"
|
||||
};
|
||||
options = applyFetchOptions(options);
|
||||
return fetch(createUrl(url), options).then(handleFailure);
|
||||
}
|
||||
|
||||
httpRequestWithJSONBody(
|
||||
method: string,
|
||||
url: string,
|
||||
contentType: string,
|
||||
payload: any
|
||||
): Promise<Response> {
|
||||
let options: RequestOptions = {
|
||||
httpRequestWithJSONBody(method: string, url: string, contentType: string, payload: any): Promise<Response> {
|
||||
const options: RequestInit = {
|
||||
method: method,
|
||||
body: JSON.stringify(payload)
|
||||
};
|
||||
return this.httpRequestWithBinaryBody(options, url, contentType);
|
||||
}
|
||||
|
||||
httpRequestWithBinaryBody(options: RequestOptions, url: string, contentType?: string) {
|
||||
httpRequestWithBinaryBody(options: RequestInit, url: string, contentType?: string) {
|
||||
options = applyFetchOptions(options);
|
||||
if (contentType) {
|
||||
// $FlowFixMe
|
||||
if (!options.headers) {
|
||||
options.headers = new Headers();
|
||||
}
|
||||
// @ts-ignore
|
||||
options.headers["Content-Type"] = contentType;
|
||||
}
|
||||
|
||||
@@ -108,4 +104,4 @@ class ApiClient {
|
||||
}
|
||||
}
|
||||
|
||||
export let apiClient = new ApiClient();
|
||||
export const apiClient = new ApiClient();
|
||||
@@ -1,8 +1,6 @@
|
||||
// @flow
|
||||
|
||||
export type Person = {
|
||||
name: string,
|
||||
mail?: string
|
||||
name: string;
|
||||
mail?: string;
|
||||
};
|
||||
|
||||
export const EXTENSION_POINT = "avatar.factory";
|
||||
@@ -1,13 +1,11 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import {binder} from "@scm-manager/ui-extensions";
|
||||
import {Image} from "..";
|
||||
import type { Person } from "./Avatar";
|
||||
import { binder } from "@scm-manager/ui-extensions";
|
||||
import { Image } from "..";
|
||||
import { Person } from "./Avatar";
|
||||
import { EXTENSION_POINT } from "./Avatar";
|
||||
|
||||
|
||||
type Props = {
|
||||
person: Person
|
||||
person: Person;
|
||||
};
|
||||
|
||||
class AvatarImage extends React.Component<Props> {
|
||||
@@ -18,13 +16,7 @@ class AvatarImage extends React.Component<Props> {
|
||||
if (avatarFactory) {
|
||||
const avatar = avatarFactory(person);
|
||||
|
||||
return (
|
||||
<Image
|
||||
className="has-rounded-border"
|
||||
src={avatar}
|
||||
alt={person.name}
|
||||
/>
|
||||
);
|
||||
return <Image className="has-rounded-border" src={avatar} alt={person.name} />;
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -1,13 +1,12 @@
|
||||
//@flow
|
||||
import * as React from "react";
|
||||
import {binder} from "@scm-manager/ui-extensions";
|
||||
import React, { Component, ReactNode } from "react";
|
||||
import { binder } from "@scm-manager/ui-extensions";
|
||||
import { EXTENSION_POINT } from "./Avatar";
|
||||
|
||||
type Props = {
|
||||
children: React.Node
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
class AvatarWrapper extends React.Component<Props> {
|
||||
class AvatarWrapper extends Component<Props> {
|
||||
render() {
|
||||
if (binder.hasExtension(EXTENSION_POINT)) {
|
||||
return <>{this.props.children}</>;
|
||||
@@ -1,4 +1,2 @@
|
||||
// @flow
|
||||
|
||||
export { default as AvatarWrapper } from "./AvatarWrapper";
|
||||
export { default as AvatarImage } from "./AvatarImage";
|
||||
@@ -1,6 +1,5 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import Button, { type ButtonProps } from "./Button";
|
||||
import Button, { ButtonProps } from "./Button";
|
||||
|
||||
class AddButton extends React.Component<ButtonProps> {
|
||||
render() {
|
||||
@@ -1,37 +1,34 @@
|
||||
//@flow
|
||||
import * as React from "react";
|
||||
import React, { MouseEvent, ReactNode } from "react";
|
||||
import classNames from "classnames";
|
||||
import { withRouter } from "react-router-dom";
|
||||
import { withRouter, RouteComponentProps } from "react-router-dom";
|
||||
import Icon from "../Icon";
|
||||
|
||||
export type ButtonProps = {
|
||||
label?: string,
|
||||
loading?: boolean,
|
||||
disabled?: boolean,
|
||||
action?: (event: Event) => void,
|
||||
link?: string,
|
||||
className?: string,
|
||||
icon?: string,
|
||||
fullWidth?: boolean,
|
||||
reducedMobile?: boolean,
|
||||
children?: React.Node
|
||||
label?: string;
|
||||
loading?: boolean;
|
||||
disabled?: boolean;
|
||||
action?: (event: MouseEvent) => void;
|
||||
link?: string;
|
||||
className?: string;
|
||||
icon?: string;
|
||||
fullWidth?: boolean;
|
||||
reducedMobile?: boolean;
|
||||
children?: ReactNode;
|
||||
};
|
||||
|
||||
type Props = ButtonProps & {
|
||||
type: string,
|
||||
color: string,
|
||||
|
||||
// context prop
|
||||
history: any
|
||||
};
|
||||
type Props = ButtonProps &
|
||||
RouteComponentProps & {
|
||||
type?: "button" | "submit" | "reset";
|
||||
color?: string;
|
||||
};
|
||||
|
||||
class Button extends React.Component<Props> {
|
||||
static defaultProps = {
|
||||
static defaultProps: Partial<Props> = {
|
||||
type: "button",
|
||||
color: "default"
|
||||
};
|
||||
|
||||
onClick = (event: Event) => {
|
||||
onClick = (event: React.MouseEvent) => {
|
||||
const { action, link, history } = this.props;
|
||||
if (action) {
|
||||
action(event);
|
||||
@@ -41,18 +38,7 @@ class Button extends React.Component<Props> {
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
label,
|
||||
loading,
|
||||
disabled,
|
||||
type,
|
||||
color,
|
||||
className,
|
||||
icon,
|
||||
fullWidth,
|
||||
reducedMobile,
|
||||
children
|
||||
} = this.props;
|
||||
const { label, loading, disabled, type, color, className, icon, fullWidth, reducedMobile, children } = this.props;
|
||||
const loadingClass = loading ? "is-loading" : "";
|
||||
const fullWidthClass = fullWidth ? "is-fullwidth" : "";
|
||||
const reducedMobileClass = reducedMobile ? "is-reduced-mobile" : "";
|
||||
@@ -62,14 +48,7 @@ class Button extends React.Component<Props> {
|
||||
type={type}
|
||||
disabled={disabled}
|
||||
onClick={this.onClick}
|
||||
className={classNames(
|
||||
"button",
|
||||
"is-" + color,
|
||||
loadingClass,
|
||||
fullWidthClass,
|
||||
reducedMobileClass,
|
||||
className
|
||||
)}
|
||||
className={classNames("button", "is-" + color, loadingClass, fullWidthClass, reducedMobileClass, className)}
|
||||
>
|
||||
<span className="icon is-medium">
|
||||
<Icon name={icon} color="inherit" />
|
||||
@@ -86,13 +65,7 @@ class Button extends React.Component<Props> {
|
||||
type={type}
|
||||
disabled={disabled}
|
||||
onClick={this.onClick}
|
||||
className={classNames(
|
||||
"button",
|
||||
"is-" + color,
|
||||
loadingClass,
|
||||
fullWidthClass,
|
||||
className
|
||||
)}
|
||||
className={classNames("button", "is-" + color, loadingClass, fullWidthClass, className)}
|
||||
>
|
||||
{label} {children}
|
||||
</button>
|
||||
@@ -1,5 +1,4 @@
|
||||
// @flow
|
||||
import * as React from "react";
|
||||
import React, { ReactNode } from "react";
|
||||
import classNames from "classnames";
|
||||
import styled from "styled-components";
|
||||
|
||||
@@ -10,15 +9,15 @@ const Flex = styled.div`
|
||||
`;
|
||||
|
||||
type Props = {
|
||||
className?: string,
|
||||
children: React.Node
|
||||
className?: string;
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
class ButtonAddons extends React.Component<Props> {
|
||||
render() {
|
||||
const { className, children } = this.props;
|
||||
|
||||
const childWrapper = [];
|
||||
const childWrapper: ReactNode[] = [];
|
||||
React.Children.forEach(children, child => {
|
||||
if (child) {
|
||||
childWrapper.push(
|
||||
@@ -29,11 +28,7 @@ class ButtonAddons extends React.Component<Props> {
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<Flex className={classNames("field", "has-addons", className)}>
|
||||
{childWrapper}
|
||||
</Flex>
|
||||
);
|
||||
return <Flex className={classNames("field", "has-addons", className)}>{childWrapper}</Flex>;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
// @flow
|
||||
import * as React from "react";
|
||||
import classNames from "classnames";
|
||||
|
||||
type Props = {
|
||||
className?: string,
|
||||
children: React.Node
|
||||
};
|
||||
|
||||
class ButtonGroup extends React.Component<Props> {
|
||||
render() {
|
||||
const { className, children } = this.props;
|
||||
|
||||
const childWrapper = [];
|
||||
React.Children.forEach(children, child => {
|
||||
if (child) {
|
||||
childWrapper.push(<div className="control" key={childWrapper.length}>{child}</div>);
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<div className={classNames("field", "is-grouped", className)}>
|
||||
{childWrapper}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default ButtonGroup;
|
||||
28
scm-ui/ui-components/src/buttons/ButtonGroup.tsx
Normal file
28
scm-ui/ui-components/src/buttons/ButtonGroup.tsx
Normal file
@@ -0,0 +1,28 @@
|
||||
import React, { ReactNode } from "react";
|
||||
import classNames from "classnames";
|
||||
|
||||
type Props = {
|
||||
className?: string;
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
class ButtonGroup extends React.Component<Props> {
|
||||
render() {
|
||||
const { className, children } = this.props;
|
||||
|
||||
const childWrapper: ReactNode[] = [];
|
||||
React.Children.forEach(children, child => {
|
||||
if (child) {
|
||||
childWrapper.push(
|
||||
<div className="control" key={childWrapper.length}>
|
||||
{child}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
return <div className={classNames("field", "is-grouped", className)}>{childWrapper}</div>;
|
||||
}
|
||||
}
|
||||
|
||||
export default ButtonGroup;
|
||||
@@ -1,7 +1,6 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import styled from "styled-components";
|
||||
import Button, { type ButtonProps } from "./Button";
|
||||
import Button, { ButtonProps } from "./Button";
|
||||
|
||||
const Wrapper = styled.div`
|
||||
margin-top: 2em;
|
||||
@@ -1,6 +1,5 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import Button, { type ButtonProps } from "./Button";
|
||||
import Button, { ButtonProps } from "./Button";
|
||||
|
||||
class DeleteButton extends React.Component<ButtonProps> {
|
||||
render() {
|
||||
@@ -1,26 +0,0 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
|
||||
type Props = {
|
||||
displayName: string,
|
||||
url: string,
|
||||
disabled: boolean,
|
||||
onClick?: () => void
|
||||
};
|
||||
|
||||
class DownloadButton extends React.Component<Props> {
|
||||
render() {
|
||||
const { displayName, url, disabled, onClick } = this.props;
|
||||
const onClickOrDefault = !!onClick ? onClick : () => {};
|
||||
return (
|
||||
<a className="button is-link" href={url} disabled={disabled} onClick={onClickOrDefault}>
|
||||
<span className="icon is-medium">
|
||||
<i className="fas fa-arrow-circle-down" />
|
||||
</span>
|
||||
<span>{displayName}</span>
|
||||
</a>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default DownloadButton;
|
||||
32
scm-ui/ui-components/src/buttons/DownloadButton.tsx
Normal file
32
scm-ui/ui-components/src/buttons/DownloadButton.tsx
Normal file
@@ -0,0 +1,32 @@
|
||||
import React from "react";
|
||||
|
||||
type Props = {
|
||||
displayName: string;
|
||||
url: string;
|
||||
disabled: boolean;
|
||||
onClick?: () => void;
|
||||
};
|
||||
|
||||
class DownloadButton extends React.Component<Props> {
|
||||
render() {
|
||||
const { displayName, url, disabled, onClick } = this.props;
|
||||
const onClickOrDefault = !!onClick ? onClick : () => {};
|
||||
return (
|
||||
<>
|
||||
{/*
|
||||
we have to ignore the next line,
|
||||
because jsx a does not the custom disabled attribute
|
||||
but bulma does.
|
||||
// @ts-ignore */}
|
||||
<a className="button is-link" href={url} disabled={disabled} onClick={onClickOrDefault}>
|
||||
<span className="icon is-medium">
|
||||
<i className="fas fa-arrow-circle-down" />
|
||||
</span>
|
||||
<span>{displayName}</span>
|
||||
</a>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default DownloadButton;
|
||||
@@ -1,6 +1,5 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import Button, { type ButtonProps } from "./Button";
|
||||
import Button, { ButtonProps } from "./Button";
|
||||
|
||||
class EditButton extends React.Component<ButtonProps> {
|
||||
render() {
|
||||
@@ -1,13 +1,12 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import React, { MouseEvent } from "react";
|
||||
import { DeleteButton } from ".";
|
||||
import classNames from "classnames";
|
||||
|
||||
type Props = {
|
||||
entryname: string,
|
||||
removeEntry: string => void,
|
||||
disabled: boolean,
|
||||
label: string
|
||||
entryname: string;
|
||||
removeEntry: (p: string) => void;
|
||||
disabled: boolean;
|
||||
label: string;
|
||||
};
|
||||
|
||||
type State = {};
|
||||
@@ -19,7 +18,7 @@ class RemoveEntryOfTableButton extends React.Component<Props, State> {
|
||||
<div className={classNames("is-pulled-right")}>
|
||||
<DeleteButton
|
||||
label={label}
|
||||
action={(event: Event) => {
|
||||
action={(event: MouseEvent) => {
|
||||
event.preventDefault();
|
||||
removeEntry(entryname);
|
||||
}}
|
||||
@@ -1,10 +1,9 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import Button, { type ButtonProps } from "./Button";
|
||||
import React, { MouseEvent } from "react";
|
||||
import Button, { ButtonProps } from "./Button";
|
||||
|
||||
type SubmitButtonProps = ButtonProps & {
|
||||
scrollToTop: boolean
|
||||
}
|
||||
scrollToTop: boolean;
|
||||
};
|
||||
|
||||
class SubmitButton extends React.Component<SubmitButtonProps> {
|
||||
static defaultProps = {
|
||||
@@ -18,7 +17,7 @@ class SubmitButton extends React.Component<SubmitButtonProps> {
|
||||
type="submit"
|
||||
color="primary"
|
||||
{...this.props}
|
||||
action={(event) => {
|
||||
action={(event: MouseEvent) => {
|
||||
if (action) {
|
||||
action(event);
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
// @create-index
|
||||
|
||||
export { default as AddButton } from "./AddButton.js";
|
||||
export { default as Button } from "./Button.js";
|
||||
export { default as CreateButton } from "./CreateButton.js";
|
||||
export { default as DeleteButton } from "./DeleteButton.js";
|
||||
export { default as EditButton } from "./EditButton.js";
|
||||
export { default as SubmitButton } from "./SubmitButton.js";
|
||||
export { default as DownloadButton } from "./DownloadButton.js";
|
||||
export { default as ButtonGroup } from "./ButtonGroup.js";
|
||||
export { default as ButtonAddons } from "./ButtonAddons.js";
|
||||
export {
|
||||
default as RemoveEntryOfTableButton
|
||||
} from "./RemoveEntryOfTableButton.js";
|
||||
@@ -1,5 +1,4 @@
|
||||
// @flow
|
||||
import React, { type Node } from "react";
|
||||
import React, { ReactNode } from "react";
|
||||
import Button from "./Button";
|
||||
import { storiesOf } from "@storybook/react";
|
||||
import styled from "styled-components";
|
||||
@@ -10,30 +9,19 @@ import DeleteButton from "./DeleteButton";
|
||||
import DownloadButton from "./DownloadButton";
|
||||
import EditButton from "./EditButton";
|
||||
import SubmitButton from "./SubmitButton";
|
||||
import { ReactElement } from "react";
|
||||
|
||||
const colors = [
|
||||
"primary",
|
||||
"link",
|
||||
"info",
|
||||
"success",
|
||||
"warning",
|
||||
"danger",
|
||||
"white",
|
||||
"light",
|
||||
"dark",
|
||||
"black",
|
||||
"text"
|
||||
];
|
||||
const colors = ["primary", "link", "info", "success", "warning", "danger", "white", "light", "dark", "black", "text"];
|
||||
|
||||
const Spacing = styled.div`
|
||||
padding: 1em;
|
||||
`;
|
||||
|
||||
const RoutingDecorator = story => (
|
||||
<MemoryRouter initialEntries={["/"]}>{story()}</MemoryRouter>
|
||||
);
|
||||
type StoryFn = () => ReactNode;
|
||||
|
||||
const SpacingDecorator = story => <Spacing>{story()}</Spacing>;
|
||||
const RoutingDecorator = (story: StoryFn) => <MemoryRouter initialEntries={["/"]}>{story()}</MemoryRouter>;
|
||||
|
||||
const SpacingDecorator = (story: StoryFn) => <Spacing>{story()}</Spacing>;
|
||||
|
||||
storiesOf("Buttons|Button", module)
|
||||
.addDecorator(RoutingDecorator)
|
||||
@@ -54,22 +42,19 @@ storiesOf("Buttons|Button", module)
|
||||
</Spacing>
|
||||
));
|
||||
|
||||
const buttonStory = (name: string, storyFn: () => Node) => {
|
||||
const buttonStory = (name: string, storyFn: () => ReactElement) => {
|
||||
return storiesOf("Buttons|" + name, module)
|
||||
.addDecorator(RoutingDecorator)
|
||||
.addDecorator(SpacingDecorator)
|
||||
.add("Default", storyFn);
|
||||
};
|
||||
|
||||
buttonStory("AddButton", () => <AddButton color={"primary"}>Add</AddButton>);
|
||||
buttonStory("AddButton", () => <AddButton>Add</AddButton>);
|
||||
buttonStory("CreateButton", () => <CreateButton>Create</CreateButton>);
|
||||
buttonStory("DeleteButton", () => <DeleteButton>Delete</DeleteButton>);
|
||||
buttonStory("DownloadButton", () => (
|
||||
<DownloadButton
|
||||
displayName="Download"
|
||||
disabled={false}
|
||||
url=""
|
||||
></DownloadButton>
|
||||
));
|
||||
buttonStory("DownloadButton", () => <DownloadButton displayName="Download" disabled={false} url="" />).add(
|
||||
"Disabled",
|
||||
() => <DownloadButton displayName="Download" disabled={true} url=""></DownloadButton>
|
||||
);
|
||||
buttonStory("EditButton", () => <EditButton>Edit</EditButton>);
|
||||
buttonStory("SubmitButton", () => <SubmitButton>Submit</SubmitButton>);
|
||||
12
scm-ui/ui-components/src/buttons/index.ts
Normal file
12
scm-ui/ui-components/src/buttons/index.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
// @create-index
|
||||
|
||||
export { default as AddButton } from "./AddButton";
|
||||
export { default as Button } from "./Button";
|
||||
export { default as CreateButton } from "./CreateButton";
|
||||
export { default as DeleteButton } from "./DeleteButton";
|
||||
export { default as EditButton } from "./EditButton";
|
||||
export { default as SubmitButton } from "./SubmitButton";
|
||||
export { default as DownloadButton } from "./DownloadButton";
|
||||
export { default as ButtonGroup } from "./ButtonGroup";
|
||||
export { default as ButtonAddons } from "./ButtonAddons";
|
||||
export { default as RemoveEntryOfTableButton } from "./RemoveEntryOfTableButton";
|
||||
@@ -1,37 +1,34 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import { translate } from "react-i18next";
|
||||
import type { Links } from "@scm-manager/ui-types";
|
||||
import { WithTranslation, withTranslation } from "react-i18next";
|
||||
import { Links, Link } from "@scm-manager/ui-types";
|
||||
import { apiClient, SubmitButton, Loading, ErrorNotification } from "../";
|
||||
import { FormEvent } from "react";
|
||||
|
||||
type RenderProps = {
|
||||
readOnly: boolean,
|
||||
initialConfiguration: ConfigurationType,
|
||||
onConfigurationChange: (ConfigurationType, boolean) => void
|
||||
readOnly: boolean;
|
||||
initialConfiguration: ConfigurationType;
|
||||
onConfigurationChange: (p1: ConfigurationType, p2: boolean) => void;
|
||||
};
|
||||
|
||||
type Props = {
|
||||
link: string,
|
||||
render: (props: RenderProps) => any, // ???
|
||||
|
||||
// context props
|
||||
t: string => string
|
||||
type Props = WithTranslation & {
|
||||
link: string;
|
||||
render: (props: RenderProps) => any; // ???
|
||||
};
|
||||
|
||||
type ConfigurationType = {
|
||||
_links: Links
|
||||
} & Object;
|
||||
_links: Links;
|
||||
} & object;
|
||||
|
||||
type State = {
|
||||
error?: Error,
|
||||
fetching: boolean,
|
||||
modifying: boolean,
|
||||
contentType?: string,
|
||||
configChanged: boolean,
|
||||
error?: Error;
|
||||
fetching: boolean;
|
||||
modifying: boolean;
|
||||
contentType?: string | null;
|
||||
configChanged: boolean;
|
||||
|
||||
configuration?: ConfigurationType,
|
||||
modifiedConfiguration?: ConfigurationType,
|
||||
valid: boolean
|
||||
configuration?: ConfigurationType;
|
||||
modifiedConfiguration?: ConfigurationType;
|
||||
valid: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -89,12 +86,13 @@ class Configuration extends React.Component<Props, State> {
|
||||
});
|
||||
};
|
||||
|
||||
getModificationUrl = (): ?string => {
|
||||
getModificationUrl = (): string | undefined => {
|
||||
const { configuration } = this.state;
|
||||
if (configuration) {
|
||||
const links = configuration._links;
|
||||
if (links && links.update) {
|
||||
return links.update.href;
|
||||
const link = links.update as Link;
|
||||
return link.href;
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -111,21 +109,32 @@ class Configuration extends React.Component<Props, State> {
|
||||
});
|
||||
};
|
||||
|
||||
modifyConfiguration = (event: Event) => {
|
||||
modifyConfiguration = (event: FormEvent) => {
|
||||
event.preventDefault();
|
||||
|
||||
this.setState({ modifying: true });
|
||||
this.setState({
|
||||
modifying: true
|
||||
});
|
||||
|
||||
const { modifiedConfiguration } = this.state;
|
||||
|
||||
apiClient
|
||||
.put(
|
||||
this.getModificationUrl(),
|
||||
modifiedConfiguration,
|
||||
this.getContentType()
|
||||
)
|
||||
.then(() => this.setState({ modifying: false, configChanged: true, valid: false }))
|
||||
.catch(this.handleError);
|
||||
const modificationUrl = this.getModificationUrl();
|
||||
if (modificationUrl) {
|
||||
apiClient
|
||||
.put(modificationUrl, modifiedConfiguration, this.getContentType())
|
||||
.then(() =>
|
||||
this.setState({
|
||||
modifying: false,
|
||||
configChanged: true,
|
||||
valid: false
|
||||
})
|
||||
)
|
||||
.catch(this.handleError);
|
||||
} else {
|
||||
this.setState({
|
||||
error: new Error("no modification link available")
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
renderConfigChangedNotification = () => {
|
||||
@@ -134,7 +143,11 @@ class Configuration extends React.Component<Props, State> {
|
||||
<div className="notification is-primary">
|
||||
<button
|
||||
className="delete"
|
||||
onClick={() => this.setState({ configChanged: false })}
|
||||
onClick={() =>
|
||||
this.setState({
|
||||
configChanged: false
|
||||
})
|
||||
}
|
||||
/>
|
||||
{this.props.t("config.form.submit-success-notification")}
|
||||
</div>
|
||||
@@ -166,11 +179,7 @@ class Configuration extends React.Component<Props, State> {
|
||||
<form onSubmit={this.modifyConfiguration}>
|
||||
{this.props.render(renderProps)}
|
||||
<hr />
|
||||
<SubmitButton
|
||||
label={t("config.form.submit")}
|
||||
disabled={!valid || readOnly}
|
||||
loading={modifying}
|
||||
/>
|
||||
<SubmitButton label={t("config.form.submit")} disabled={!valid || readOnly} loading={modifying} />
|
||||
</form>
|
||||
</>
|
||||
);
|
||||
@@ -178,4 +187,4 @@ class Configuration extends React.Component<Props, State> {
|
||||
}
|
||||
}
|
||||
|
||||
export default translate("config")(Configuration);
|
||||
export default withTranslation("config")(Configuration);
|
||||
@@ -1,34 +1,42 @@
|
||||
// @flow
|
||||
import React from "react";
|
||||
import { binder } from "@scm-manager/ui-extensions";
|
||||
import { NavLink } from "../navigation";
|
||||
import { Route } from "react-router-dom";
|
||||
import { translate } from "react-i18next";
|
||||
import { WithTranslation, withTranslation } from "react-i18next";
|
||||
import { Repository, Links, Link } from "@scm-manager/ui-types";
|
||||
|
||||
type GlobalRouteProps = {
|
||||
url: string;
|
||||
links: Links;
|
||||
};
|
||||
|
||||
type RepositoryRouteProps = {
|
||||
url: string;
|
||||
repository: Repository;
|
||||
};
|
||||
|
||||
type RepositoryNavProps = WithTranslation & { url: string };
|
||||
|
||||
class ConfigurationBinder {
|
||||
i18nNamespace = "plugins";
|
||||
|
||||
i18nNamespace: string = "plugins";
|
||||
|
||||
navLink(to: string, labelI18nKey: string, t: any){
|
||||
navLink(to: string, labelI18nKey: string, t: any) {
|
||||
return <NavLink to={to} label={t(labelI18nKey)} />;
|
||||
}
|
||||
|
||||
route(path: string, Component: any){
|
||||
return <Route path={path}
|
||||
render={() => Component}
|
||||
exact/>;
|
||||
route(path: string, Component: any) {
|
||||
return <Route path={path} render={() => Component} exact />;
|
||||
}
|
||||
|
||||
bindGlobal(to: string, labelI18nKey: string, linkName: string, ConfigurationComponent: any) {
|
||||
|
||||
// create predicate based on the link name of the index resource
|
||||
// if the linkname is not available, the navigation link and the route are not bound to the extension points
|
||||
const configPredicate = (props: Object) => {
|
||||
const configPredicate = (props: any) => {
|
||||
return props.links && props.links[linkName];
|
||||
};
|
||||
|
||||
// create NavigationLink with translated label
|
||||
const ConfigNavLink = translate(this.i18nNamespace)(({t}) => {
|
||||
const ConfigNavLink = withTranslation(this.i18nNamespace)(({ t }) => {
|
||||
return this.navLink("/admin/settings" + to, labelI18nKey, t);
|
||||
});
|
||||
|
||||
@@ -36,9 +44,14 @@ class ConfigurationBinder {
|
||||
binder.bind("admin.setting", ConfigNavLink, configPredicate);
|
||||
|
||||
// route for global configuration, passes the link from the index resource to component
|
||||
const ConfigRoute = ({ url, links, ...additionalProps }) => {
|
||||
const link = links[linkName].href;
|
||||
return this.route(url + "/settings" + to, <ConfigurationComponent link={link} {...additionalProps} />);
|
||||
const ConfigRoute = ({ url, links, ...additionalProps }: GlobalRouteProps) => {
|
||||
const link = links[linkName];
|
||||
if (link) {
|
||||
return this.route(
|
||||
url + "/settings" + to,
|
||||
<ConfigurationComponent link={(link as Link).href} {...additionalProps} />
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
// bind config route to extension point
|
||||
@@ -46,26 +59,29 @@ class ConfigurationBinder {
|
||||
}
|
||||
|
||||
bindRepository(to: string, labelI18nKey: string, linkName: string, RepositoryComponent: any) {
|
||||
|
||||
// create predicate based on the link name of the current repository route
|
||||
// if the linkname is not available, the navigation link and the route are not bound to the extension points
|
||||
const repoPredicate = (props: Object) => {
|
||||
const repoPredicate = (props: any) => {
|
||||
return props.repository && props.repository._links && props.repository._links[linkName];
|
||||
};
|
||||
|
||||
// create NavigationLink with translated label
|
||||
const RepoNavLink = translate(this.i18nNamespace)(({t, url}) => {
|
||||
const RepoNavLink = withTranslation(this.i18nNamespace)(({ t, url }: RepositoryNavProps) => {
|
||||
return this.navLink(url + to, labelI18nKey, t);
|
||||
});
|
||||
|
||||
// bind navigation link to extension point
|
||||
binder.bind("repository.navigation", RepoNavLink, repoPredicate);
|
||||
|
||||
|
||||
// route for global configuration, passes the current repository to component
|
||||
const RepoRoute = ({url, repository, ...additionalProps}) => {
|
||||
const link = repository._links[linkName].href;
|
||||
return this.route(url + to, <RepositoryComponent repository={repository} link={link} {...additionalProps}/>);
|
||||
const RepoRoute = ({ url, repository, ...additionalProps }: RepositoryRouteProps) => {
|
||||
const link = repository._links[linkName];
|
||||
if (link) {
|
||||
return this.route(
|
||||
url + to,
|
||||
<RepositoryComponent repository={repository} link={(link as Link).href} {...additionalProps} />
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
// bind config route to extension point
|
||||
@@ -73,32 +89,34 @@ class ConfigurationBinder {
|
||||
}
|
||||
|
||||
bindRepositorySetting(to: string, labelI18nKey: string, linkName: string, RepositoryComponent: any) {
|
||||
|
||||
// create predicate based on the link name of the current repository route
|
||||
// if the linkname is not available, the navigation link and the route are not bound to the extension points
|
||||
const repoPredicate = (props: Object) => {
|
||||
const repoPredicate = (props: any) => {
|
||||
return props.repository && props.repository._links && props.repository._links[linkName];
|
||||
};
|
||||
|
||||
// create NavigationLink with translated label
|
||||
const RepoNavLink = translate(this.i18nNamespace)(({t, url}) => {
|
||||
const RepoNavLink = withTranslation(this.i18nNamespace)(({ t, url }: RepositoryNavProps) => {
|
||||
return this.navLink(url + "/settings" + to, labelI18nKey, t);
|
||||
});
|
||||
|
||||
// bind navigation link to extension point
|
||||
binder.bind("repository.setting", RepoNavLink, repoPredicate);
|
||||
|
||||
|
||||
// route for global configuration, passes the current repository to component
|
||||
const RepoRoute = ({url, repository, ...additionalProps}) => {
|
||||
const link = repository._links[linkName].href;
|
||||
return this.route(url + "/settings" + to, <RepositoryComponent repository={repository} link={link} {...additionalProps}/>);
|
||||
const RepoRoute = ({ url, repository, ...additionalProps }: RepositoryRouteProps) => {
|
||||
const link = repository._links[linkName];
|
||||
if (link) {
|
||||
return this.route(
|
||||
url + "/settings" + to,
|
||||
<RepositoryComponent repository={repository} link={(link as Link).href} {...additionalProps} />
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
// bind config route to extension point
|
||||
binder.bind("repository.route", RepoRoute, repoPredicate);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default new ConfigurationBinder();
|
||||
@@ -1,3 +1,2 @@
|
||||
// @flow
|
||||
export { default as ConfigurationBinder } from "./ConfigurationBinder";
|
||||
export { default as Configuration } from "./Configuration";
|
||||
@@ -1,17 +1,16 @@
|
||||
// @flow
|
||||
|
||||
import { BackendError, UnauthorizedError, createBackendError, NotFoundError } from "./errors";
|
||||
|
||||
describe("test createBackendError", () => {
|
||||
|
||||
const earthNotFoundError = {
|
||||
transactionId: "42t",
|
||||
errorCode: "42e",
|
||||
message: "earth not found",
|
||||
context: [{
|
||||
type: "planet",
|
||||
id: "earth"
|
||||
}],
|
||||
context: [
|
||||
{
|
||||
type: "planet",
|
||||
id: "earth"
|
||||
}
|
||||
],
|
||||
violations: []
|
||||
};
|
||||
|
||||
@@ -33,5 +32,4 @@ describe("test createBackendError", () => {
|
||||
expect(err).toBeInstanceOf(NotFoundError);
|
||||
expect(err.name).toBe("NotFoundError");
|
||||
});
|
||||
|
||||
});
|
||||
@@ -1,20 +1,25 @@
|
||||
// @flow
|
||||
type Context = { type: string, id: string }[];
|
||||
type Violation = { path: string, message: string };
|
||||
type Context = {
|
||||
type: string;
|
||||
id: string;
|
||||
}[];
|
||||
type Violation = {
|
||||
path: string;
|
||||
message: string;
|
||||
};
|
||||
|
||||
export type BackendErrorContent = {
|
||||
transactionId: string,
|
||||
errorCode: string,
|
||||
message: string,
|
||||
url?: string,
|
||||
context: Context,
|
||||
violations: Violation[]
|
||||
transactionId: string;
|
||||
errorCode: string;
|
||||
message: string;
|
||||
url?: string;
|
||||
context: Context;
|
||||
violations: Violation[];
|
||||
};
|
||||
|
||||
export class BackendError extends Error {
|
||||
transactionId: string;
|
||||
errorCode: string;
|
||||
url: ?string;
|
||||
url: string | null | undefined;
|
||||
context: Context = [];
|
||||
statusCode: number;
|
||||
violations: Violation[];
|
||||
@@ -59,10 +64,7 @@ export class ConflictError extends BackendError {
|
||||
}
|
||||
}
|
||||
|
||||
export function createBackendError(
|
||||
content: BackendErrorContent,
|
||||
statusCode: number
|
||||
) {
|
||||
export function createBackendError(content: BackendErrorContent, statusCode: number) {
|
||||
switch (statusCode) {
|
||||
case 404:
|
||||
return new NotFoundError(content, statusCode);
|
||||
@@ -74,8 +76,5 @@ export function createBackendError(
|
||||
}
|
||||
|
||||
export function isBackendError(response: Response) {
|
||||
return (
|
||||
response.headers.get("Content-Type") ===
|
||||
"application/vnd.scmm-error+json;v=2"
|
||||
);
|
||||
return response.headers.get("Content-Type") === "application/vnd.scmm-error+json;v=2";
|
||||
}
|
||||
@@ -1,21 +1,20 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import React, { MouseEvent } from "react";
|
||||
|
||||
import { AddButton } from "../buttons";
|
||||
import InputField from "./InputField";
|
||||
|
||||
type Props = {
|
||||
addEntry: string => void,
|
||||
disabled: boolean,
|
||||
buttonLabel: string,
|
||||
fieldLabel: string,
|
||||
errorMessage: string,
|
||||
helpText?: string,
|
||||
validateEntry?: string => boolean
|
||||
addEntry: (p: string) => void;
|
||||
disabled: boolean;
|
||||
buttonLabel: string;
|
||||
fieldLabel: string;
|
||||
errorMessage: string;
|
||||
helpText?: string;
|
||||
validateEntry?: (p: string) => boolean;
|
||||
};
|
||||
|
||||
type State = {
|
||||
entryToAdd: string
|
||||
entryToAdd: string;
|
||||
};
|
||||
|
||||
class AddEntryToTableField extends React.Component<Props, State> {
|
||||
@@ -27,7 +26,7 @@ class AddEntryToTableField extends React.Component<Props, State> {
|
||||
}
|
||||
|
||||
isValid = () => {
|
||||
const {validateEntry} = this.props;
|
||||
const { validateEntry } = this.props;
|
||||
if (!this.state.entryToAdd || this.state.entryToAdd === "" || !validateEntry) {
|
||||
return true;
|
||||
} else {
|
||||
@@ -36,13 +35,7 @@ class AddEntryToTableField extends React.Component<Props, State> {
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
disabled,
|
||||
buttonLabel,
|
||||
fieldLabel,
|
||||
errorMessage,
|
||||
helpText
|
||||
} = this.props;
|
||||
const { disabled, buttonLabel, fieldLabel, errorMessage, helpText } = this.props;
|
||||
return (
|
||||
<div className="field">
|
||||
<InputField
|
||||
@@ -58,13 +51,13 @@ class AddEntryToTableField extends React.Component<Props, State> {
|
||||
<AddButton
|
||||
label={buttonLabel}
|
||||
action={this.addButtonClicked}
|
||||
disabled={disabled || this.state.entryToAdd ==="" || !this.isValid()}
|
||||
disabled={disabled || this.state.entryToAdd === "" || !this.isValid()}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
addButtonClicked = (event: Event) => {
|
||||
addButtonClicked = (event: MouseEvent) => {
|
||||
event.preventDefault();
|
||||
this.appendEntry();
|
||||
};
|
||||
@@ -72,7 +65,10 @@ class AddEntryToTableField extends React.Component<Props, State> {
|
||||
appendEntry = () => {
|
||||
const { entryToAdd } = this.state;
|
||||
this.props.addEntry(entryToAdd);
|
||||
this.setState({ ...this.state, entryToAdd: "" });
|
||||
this.setState({
|
||||
...this.state,
|
||||
entryToAdd: ""
|
||||
});
|
||||
};
|
||||
|
||||
handleAddEntryChange = (entryname: string) => {
|
||||
@@ -1,30 +1,31 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import React, { MouseEvent } from "react";
|
||||
|
||||
import type { AutocompleteObject, SelectValue } from "@scm-manager/ui-types";
|
||||
import { AutocompleteObject, SelectValue } from "@scm-manager/ui-types";
|
||||
import Autocomplete from "../Autocomplete";
|
||||
import AddButton from "../buttons/AddButton";
|
||||
|
||||
type Props = {
|
||||
addEntry: SelectValue => void,
|
||||
disabled: boolean,
|
||||
buttonLabel: string,
|
||||
fieldLabel: string,
|
||||
helpText?: string,
|
||||
loadSuggestions: string => Promise<AutocompleteObject>,
|
||||
placeholder?: string,
|
||||
loadingMessage?: string,
|
||||
noOptionsMessage?: string
|
||||
addEntry: (p: SelectValue) => void;
|
||||
disabled: boolean;
|
||||
buttonLabel: string;
|
||||
fieldLabel: string;
|
||||
helpText?: string;
|
||||
loadSuggestions: (p: string) => Promise<SelectValue[]>;
|
||||
placeholder?: string;
|
||||
loadingMessage?: string;
|
||||
noOptionsMessage?: string;
|
||||
};
|
||||
|
||||
type State = {
|
||||
selectedValue?: SelectValue
|
||||
selectedValue?: SelectValue;
|
||||
};
|
||||
|
||||
class AutocompleteAddEntryToTableField extends React.Component<Props, State> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
this.state = { selectedValue: undefined };
|
||||
this.state = {
|
||||
selectedValue: undefined
|
||||
};
|
||||
}
|
||||
render() {
|
||||
const {
|
||||
@@ -53,16 +54,12 @@ class AutocompleteAddEntryToTableField extends React.Component<Props, State> {
|
||||
creatable={true}
|
||||
/>
|
||||
|
||||
<AddButton
|
||||
label={buttonLabel}
|
||||
action={this.addButtonClicked}
|
||||
disabled={disabled}
|
||||
/>
|
||||
<AddButton label={buttonLabel} action={this.addButtonClicked} disabled={disabled} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
addButtonClicked = (event: Event) => {
|
||||
addButtonClicked = (event: MouseEvent) => {
|
||||
event.preventDefault();
|
||||
this.appendEntry();
|
||||
};
|
||||
@@ -72,9 +69,13 @@ class AutocompleteAddEntryToTableField extends React.Component<Props, State> {
|
||||
if (!selectedValue) {
|
||||
return;
|
||||
}
|
||||
// $FlowFixMe null is needed to clear the selection; undefined does not work
|
||||
this.setState({ ...this.state, selectedValue: null }, () =>
|
||||
this.props.addEntry(selectedValue)
|
||||
this.setState(
|
||||
// @ts-ignore null is needed to clear the selection; undefined does not work
|
||||
{
|
||||
...this.state,
|
||||
selectedValue: null
|
||||
},
|
||||
() => this.props.addEntry(selectedValue)
|
||||
);
|
||||
};
|
||||
|
||||
21
scm-ui/ui-components/src/forms/Checkbox.stories.tsx
Normal file
21
scm-ui/ui-components/src/forms/Checkbox.stories.tsx
Normal file
@@ -0,0 +1,21 @@
|
||||
import React from "react";
|
||||
import { storiesOf } from "@storybook/react";
|
||||
import Checkbox from "./Checkbox";
|
||||
import styled from "styled-components";
|
||||
|
||||
const Spacing = styled.div`
|
||||
padding: 2em;
|
||||
`;
|
||||
|
||||
storiesOf("Forms|Checkbox", module)
|
||||
.add("Default", () => (
|
||||
<Spacing>
|
||||
<Checkbox label="Not checked" checked={false} />
|
||||
<Checkbox label="Checked" checked={true} />
|
||||
</Spacing>
|
||||
))
|
||||
.add("Disabled", () => (
|
||||
<Spacing>
|
||||
<Checkbox label="Checked but disabled" checked={true} disabled={true} />
|
||||
</Spacing>
|
||||
));
|
||||
@@ -1,19 +1,17 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import React, { ChangeEvent } from "react";
|
||||
import { Help } from "../index";
|
||||
|
||||
type Props = {
|
||||
label?: string,
|
||||
name?: string,
|
||||
checked: boolean,
|
||||
onChange?: (value: boolean, name?: string) => void,
|
||||
disabled?: boolean,
|
||||
helpText?: string
|
||||
label?: string;
|
||||
name?: string;
|
||||
checked: boolean;
|
||||
onChange?: (value: boolean, name?: string) => void;
|
||||
disabled?: boolean;
|
||||
helpText?: string;
|
||||
};
|
||||
|
||||
class Checkbox extends React.Component<Props> {
|
||||
|
||||
onCheckboxChange = (event: SyntheticInputEvent<HTMLInputElement>) => {
|
||||
onCheckboxChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
if (this.props.onChange) {
|
||||
this.props.onChange(event.target.checked, this.props.name);
|
||||
}
|
||||
@@ -30,14 +28,18 @@ class Checkbox extends React.Component<Props> {
|
||||
return (
|
||||
<div className="field is-grouped">
|
||||
<div className="control">
|
||||
{/*
|
||||
we have to ignore the next line,
|
||||
because jsx label does not the custom disabled attribute
|
||||
but bulma does.
|
||||
// @ts-ignore */}
|
||||
<label className="checkbox" disabled={this.props.disabled}>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={this.props.checked}
|
||||
onChange={this.onCheckboxChange}
|
||||
disabled={this.props.disabled}
|
||||
/>
|
||||
{" "}
|
||||
/>{" "}
|
||||
{this.props.label}
|
||||
{this.renderHelp()}
|
||||
</label>
|
||||
@@ -1,27 +1,21 @@
|
||||
// @flow
|
||||
|
||||
import React from "react";
|
||||
import React, { ChangeEvent } from "react";
|
||||
import classNames from "classnames";
|
||||
|
||||
type Props = {
|
||||
options: string[],
|
||||
optionValues?: string[],
|
||||
optionSelected: string => void,
|
||||
preselectedOption?: string,
|
||||
className: any,
|
||||
disabled?: boolean
|
||||
options: string[];
|
||||
optionValues?: string[];
|
||||
optionSelected: (p: string) => void;
|
||||
preselectedOption?: string;
|
||||
className: any;
|
||||
disabled?: boolean;
|
||||
};
|
||||
|
||||
class DropDown extends React.Component<Props> {
|
||||
render() {
|
||||
const { options, optionValues, preselectedOption, className, disabled } = this.props;
|
||||
return (
|
||||
<div className={classNames(className, "select", disabled ? "disabled": "")}>
|
||||
<select
|
||||
value={preselectedOption ? preselectedOption : ""}
|
||||
onChange={this.change}
|
||||
disabled={disabled}
|
||||
>
|
||||
<div className={classNames(className, "select", disabled ? "disabled" : "")}>
|
||||
<select value={preselectedOption ? preselectedOption : ""} onChange={this.change} disabled={disabled}>
|
||||
<option key="" />
|
||||
{options.map((option, index) => {
|
||||
return (
|
||||
@@ -35,7 +29,7 @@ class DropDown extends React.Component<Props> {
|
||||
);
|
||||
}
|
||||
|
||||
change = (event: SyntheticInputEvent<HTMLSelectElement>) => {
|
||||
change = (event: ChangeEvent<HTMLSelectElement>) => {
|
||||
this.props.optionSelected(event.target.value);
|
||||
};
|
||||
}
|
||||
@@ -1,18 +1,14 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import { translate } from "react-i18next";
|
||||
import React, { ChangeEvent, FormEvent } from "react";
|
||||
import { WithTranslation, withTranslation } from "react-i18next";
|
||||
import styled from "styled-components";
|
||||
|
||||
type Props = {
|
||||
filter: string => void,
|
||||
value?: string,
|
||||
|
||||
// context props
|
||||
t: string => string
|
||||
type Props = WithTranslation & {
|
||||
filter: (p: string) => void;
|
||||
value?: string;
|
||||
};
|
||||
|
||||
type State = {
|
||||
value: string
|
||||
value: string;
|
||||
};
|
||||
|
||||
const FixedHeightInput = styled.input`
|
||||
@@ -20,16 +16,20 @@ const FixedHeightInput = styled.input`
|
||||
`;
|
||||
|
||||
class FilterInput extends React.Component<Props, State> {
|
||||
constructor(props) {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
this.state = { value: this.props.value ? this.props.value : "" };
|
||||
this.state = {
|
||||
value: this.props.value ? this.props.value : ""
|
||||
};
|
||||
}
|
||||
|
||||
handleChange = event => {
|
||||
this.setState({ value: event.target.value });
|
||||
handleChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
this.setState({
|
||||
value: event.target.value
|
||||
});
|
||||
};
|
||||
|
||||
handleSubmit = event => {
|
||||
handleSubmit = (event: FormEvent) => {
|
||||
this.props.filter(this.state.value);
|
||||
event.preventDefault();
|
||||
};
|
||||
@@ -55,4 +55,4 @@ class FilterInput extends React.Component<Props, State> {
|
||||
}
|
||||
}
|
||||
|
||||
export default translate("commons")(FilterInput);
|
||||
export default withTranslation("commons")(FilterInput);
|
||||
@@ -1,21 +1,20 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import React, { ChangeEvent, KeyboardEvent } from "react";
|
||||
import classNames from "classnames";
|
||||
import LabelWithHelpIcon from "./LabelWithHelpIcon";
|
||||
|
||||
type Props = {
|
||||
label?: string,
|
||||
name?: string,
|
||||
placeholder?: string,
|
||||
value?: string,
|
||||
type?: string,
|
||||
autofocus?: boolean,
|
||||
onChange: (value: string, name?: string) => void,
|
||||
onReturnPressed?: () => void,
|
||||
validationError: boolean,
|
||||
errorMessage: string,
|
||||
disabled?: boolean,
|
||||
helpText?: string
|
||||
label?: string;
|
||||
name?: string;
|
||||
placeholder?: string;
|
||||
value?: string;
|
||||
type?: string;
|
||||
autofocus?: boolean;
|
||||
onChange: (value: string, name?: string) => void;
|
||||
onReturnPressed?: () => void;
|
||||
validationError?: boolean;
|
||||
errorMessage?: string;
|
||||
disabled?: boolean;
|
||||
helpText?: string;
|
||||
};
|
||||
|
||||
class InputField extends React.Component<Props> {
|
||||
@@ -24,7 +23,7 @@ class InputField extends React.Component<Props> {
|
||||
placeholder: ""
|
||||
};
|
||||
|
||||
field: ?HTMLInputElement;
|
||||
field: HTMLInputElement | null | undefined;
|
||||
|
||||
componentDidMount() {
|
||||
if (this.props.autofocus && this.field) {
|
||||
@@ -32,11 +31,11 @@ class InputField extends React.Component<Props> {
|
||||
}
|
||||
}
|
||||
|
||||
handleInput = (event: SyntheticInputEvent<HTMLInputElement>) => {
|
||||
handleInput = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
this.props.onChange(event.target.value, this.props.name);
|
||||
};
|
||||
|
||||
handleKeyPress = (event: SyntheticKeyboardEvent<HTMLInputElement>) => {
|
||||
handleKeyPress = (event: KeyboardEvent<HTMLInputElement>) => {
|
||||
const onReturnPressed = this.props.onReturnPressed;
|
||||
if (!onReturnPressed) {
|
||||
return;
|
||||
@@ -48,22 +47,9 @@ class InputField extends React.Component<Props> {
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
type,
|
||||
placeholder,
|
||||
value,
|
||||
validationError,
|
||||
errorMessage,
|
||||
disabled,
|
||||
label,
|
||||
helpText
|
||||
} = this.props;
|
||||
const { type, placeholder, value, validationError, errorMessage, disabled, label, helpText } = this.props;
|
||||
const errorView = validationError ? "is-danger" : "";
|
||||
const helper = validationError ? (
|
||||
<p className="help is-danger">{errorMessage}</p>
|
||||
) : (
|
||||
""
|
||||
);
|
||||
const helper = validationError ? <p className="help is-danger">{errorMessage}</p> : "";
|
||||
return (
|
||||
<div className="field">
|
||||
<LabelWithHelpIcon label={label} helpText={helpText} />
|
||||
@@ -1,31 +1,27 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import Help from "../Help.js";
|
||||
import Help from "../Help";
|
||||
|
||||
type Props = {
|
||||
label?: string,
|
||||
helpText?: string
|
||||
label?: string;
|
||||
helpText?: string;
|
||||
};
|
||||
|
||||
class LabelWithHelpIcon extends React.Component<Props> {
|
||||
|
||||
renderHelp() {
|
||||
const { helpText } = this.props;
|
||||
if (helpText) {
|
||||
return (
|
||||
<Help message={helpText} />
|
||||
);
|
||||
return <Help message={helpText} />;
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const {label } = this.props;
|
||||
const { label } = this.props;
|
||||
|
||||
if (label) {
|
||||
const help = this.renderHelp();
|
||||
return (
|
||||
<label className="label">
|
||||
{label} { help }
|
||||
{label} {help}
|
||||
</label>
|
||||
);
|
||||
}
|
||||
@@ -1,22 +1,24 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import { translate } from "react-i18next";
|
||||
import type { DisplayedUser } from "@scm-manager/ui-types";
|
||||
import { WithTranslation, withTranslation } from "react-i18next";
|
||||
import { DisplayedUser } from "@scm-manager/ui-types";
|
||||
import TagGroup from "./TagGroup";
|
||||
|
||||
type Props = {
|
||||
members: string[],
|
||||
memberListChanged: (string[]) => void,
|
||||
label?: string,
|
||||
helpText?: string,
|
||||
t: string => string
|
||||
type Props = WithTranslation & {
|
||||
members: string[];
|
||||
memberListChanged: (p: string[]) => void;
|
||||
label?: string;
|
||||
helpText?: string;
|
||||
};
|
||||
|
||||
class MemberNameTagGroup extends React.Component<Props> {
|
||||
render() {
|
||||
const { members, label, helpText, t } = this.props;
|
||||
const membersExtended = members.map(id => {
|
||||
return { id, displayName: id, mail: "" };
|
||||
return {
|
||||
id,
|
||||
displayName: id,
|
||||
mail: ""
|
||||
};
|
||||
});
|
||||
return (
|
||||
<TagGroup
|
||||
@@ -36,4 +38,4 @@ class MemberNameTagGroup extends React.Component<Props> {
|
||||
};
|
||||
}
|
||||
|
||||
export default translate("groups")(MemberNameTagGroup);
|
||||
export default withTranslation("groups")(MemberNameTagGroup);
|
||||
@@ -1,20 +1,16 @@
|
||||
// @flow
|
||||
|
||||
import React from "react";
|
||||
import { translate } from "react-i18next";
|
||||
import { WithTranslation, withTranslation } from "react-i18next";
|
||||
import InputField from "./InputField";
|
||||
|
||||
type State = {
|
||||
password: string,
|
||||
confirmedPassword: string,
|
||||
passwordValid: boolean,
|
||||
passwordConfirmationFailed: boolean
|
||||
password: string;
|
||||
confirmedPassword: string;
|
||||
passwordValid: boolean;
|
||||
passwordConfirmationFailed: boolean;
|
||||
};
|
||||
type Props = {
|
||||
passwordChanged: (string, boolean) => void,
|
||||
passwordValidator?: string => boolean,
|
||||
// Context props
|
||||
t: string => string
|
||||
type Props = WithTranslation & {
|
||||
passwordChanged: (p1: string, p2: boolean) => void;
|
||||
passwordValidator?: (p: string) => boolean;
|
||||
};
|
||||
|
||||
class PasswordConfirmation extends React.Component<Props, State> {
|
||||
@@ -65,7 +61,7 @@ class PasswordConfirmation extends React.Component<Props, State> {
|
||||
);
|
||||
}
|
||||
|
||||
validatePassword = password => {
|
||||
validatePassword = (password: string) => {
|
||||
const { passwordValidator } = this.props;
|
||||
if (passwordValidator) {
|
||||
return passwordValidator(password);
|
||||
@@ -87,8 +83,7 @@ class PasswordConfirmation extends React.Component<Props, State> {
|
||||
};
|
||||
|
||||
handlePasswordChange = (password: string) => {
|
||||
const passwordConfirmationFailed =
|
||||
password !== this.state.confirmedPassword;
|
||||
const passwordConfirmationFailed = password !== this.state.confirmedPassword;
|
||||
|
||||
this.setState(
|
||||
{
|
||||
@@ -109,4 +104,4 @@ class PasswordConfirmation extends React.Component<Props, State> {
|
||||
};
|
||||
}
|
||||
|
||||
export default translate("commons")(PasswordConfirmation);
|
||||
export default withTranslation("commons")(PasswordConfirmation);
|
||||
@@ -1,41 +0,0 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import { Help } from "../index";
|
||||
|
||||
type Props = {
|
||||
label?: string,
|
||||
name?: string,
|
||||
value?: string,
|
||||
checked: boolean,
|
||||
onChange?: (value: boolean, name?: string) => void,
|
||||
disabled?: boolean,
|
||||
helpText?: string
|
||||
};
|
||||
|
||||
class Radio extends React.Component<Props> {
|
||||
renderHelp = () => {
|
||||
const helpText = this.props.helpText;
|
||||
if (helpText) {
|
||||
return <Help message={helpText} />;
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<label className="radio" disabled={this.props.disabled}>
|
||||
<input
|
||||
type="radio"
|
||||
name={this.props.name}
|
||||
value={this.props.value}
|
||||
checked={this.props.checked}
|
||||
onChange={this.props.onChange}
|
||||
disabled={this.props.disabled}
|
||||
/>{" "}
|
||||
{this.props.label}
|
||||
{this.renderHelp()}
|
||||
</label>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Radio;
|
||||
21
scm-ui/ui-components/src/forms/Radio.stories.tsx
Normal file
21
scm-ui/ui-components/src/forms/Radio.stories.tsx
Normal file
@@ -0,0 +1,21 @@
|
||||
import React from "react";
|
||||
import { storiesOf } from "@storybook/react";
|
||||
import Radio from "./Radio";
|
||||
import styled from "styled-components";
|
||||
|
||||
const Spacing = styled.div`
|
||||
padding: 2em;
|
||||
`;
|
||||
|
||||
storiesOf("Forms|Radio", module)
|
||||
.add("Default", () => (
|
||||
<Spacing>
|
||||
<Radio label="Not checked" checked={false} />
|
||||
<Radio label="Checked" checked={true} />
|
||||
</Spacing>
|
||||
))
|
||||
.add("Disabled", () => (
|
||||
<Spacing>
|
||||
<Radio label="Checked but disabled" checked={true} disabled={true} />
|
||||
</Spacing>
|
||||
));
|
||||
53
scm-ui/ui-components/src/forms/Radio.tsx
Normal file
53
scm-ui/ui-components/src/forms/Radio.tsx
Normal file
@@ -0,0 +1,53 @@
|
||||
import React, { ChangeEvent } from "react";
|
||||
import { Help } from "../index";
|
||||
|
||||
type Props = {
|
||||
label?: string;
|
||||
name?: string;
|
||||
value?: string;
|
||||
checked: boolean;
|
||||
onChange?: (value: boolean, name?: string) => void;
|
||||
disabled?: boolean;
|
||||
helpText?: string;
|
||||
};
|
||||
|
||||
class Radio extends React.Component<Props> {
|
||||
renderHelp = () => {
|
||||
const helpText = this.props.helpText;
|
||||
if (helpText) {
|
||||
return <Help message={helpText} />;
|
||||
}
|
||||
};
|
||||
|
||||
onValueChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
if (this.props.onChange) {
|
||||
this.props.onChange(event.target.checked, this.props.name);
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<>
|
||||
{/*
|
||||
we have to ignore the next line,
|
||||
because jsx label does not the custom disabled attribute
|
||||
but bulma does.
|
||||
// @ts-ignore */}
|
||||
<label className="radio" disabled={this.props.disabled}>
|
||||
<input
|
||||
type="radio"
|
||||
name={this.props.name}
|
||||
value={this.props.value}
|
||||
checked={this.props.checked}
|
||||
onChange={this.onValueChange}
|
||||
disabled={this.props.disabled}
|
||||
/>{" "}
|
||||
{this.props.label}
|
||||
{this.renderHelp()}
|
||||
</label>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Radio;
|
||||
@@ -1,26 +1,25 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import React, { ChangeEvent } from "react";
|
||||
import classNames from "classnames";
|
||||
import LabelWithHelpIcon from "./LabelWithHelpIcon";
|
||||
|
||||
export type SelectItem = {
|
||||
value: string,
|
||||
label: string
|
||||
value: string;
|
||||
label: string;
|
||||
};
|
||||
|
||||
type Props = {
|
||||
name?: string,
|
||||
label?: string,
|
||||
options: SelectItem[],
|
||||
value?: string,
|
||||
onChange: (value: string, name?: string) => void,
|
||||
loading?: boolean,
|
||||
helpText?: string,
|
||||
disabled?: boolean
|
||||
name?: string;
|
||||
label?: string;
|
||||
options: SelectItem[];
|
||||
value?: string;
|
||||
onChange: (value: string, name?: string) => void;
|
||||
loading?: boolean;
|
||||
helpText?: string;
|
||||
disabled?: boolean;
|
||||
};
|
||||
|
||||
class Select extends React.Component<Props> {
|
||||
field: ?HTMLSelectElement;
|
||||
field: HTMLSelectElement | null | undefined;
|
||||
|
||||
componentDidMount() {
|
||||
// trigger change after render, if value is null to set it to the first value
|
||||
@@ -30,7 +29,7 @@ class Select extends React.Component<Props> {
|
||||
}
|
||||
}
|
||||
|
||||
handleInput = (event: SyntheticInputEvent<HTMLSelectElement>) => {
|
||||
handleInput = (event: ChangeEvent<HTMLSelectElement>) => {
|
||||
this.props.onChange(event.target.value, this.props.name);
|
||||
};
|
||||
|
||||
@@ -38,14 +37,10 @@ class Select extends React.Component<Props> {
|
||||
const { options, value, label, helpText, loading, disabled } = this.props;
|
||||
const loadingClass = loading ? "is-loading" : "";
|
||||
|
||||
|
||||
return (
|
||||
<div className="field">
|
||||
<LabelWithHelpIcon label={label} helpText={helpText} />
|
||||
<div className={classNames(
|
||||
"control select",
|
||||
loadingClass
|
||||
)}>
|
||||
<div className={classNames("control select", loadingClass)}>
|
||||
<select
|
||||
ref={input => {
|
||||
this.field = input;
|
||||
@@ -1,13 +1,12 @@
|
||||
//@flow
|
||||
import * as React from "react";
|
||||
import type { DisplayedUser } from "@scm-manager/ui-types";
|
||||
import { DisplayedUser } from "@scm-manager/ui-types";
|
||||
import { Help, Tag } from "../index";
|
||||
|
||||
type Props = {
|
||||
items: DisplayedUser[],
|
||||
label: string,
|
||||
helpText?: string,
|
||||
onRemove: (DisplayedUser[]) => void
|
||||
items: DisplayedUser[];
|
||||
label: string;
|
||||
helpText?: string;
|
||||
onRemove: (p: DisplayedUser[]) => void;
|
||||
};
|
||||
|
||||
export default class TagGroup extends React.Component<Props> {
|
||||
@@ -34,11 +33,7 @@ export default class TagGroup extends React.Component<Props> {
|
||||
return (
|
||||
<div className="control" key={key}>
|
||||
<div className="tags has-addons">
|
||||
<Tag
|
||||
color="info is-outlined"
|
||||
label={item.displayName}
|
||||
onRemove={() => this.removeEntry(item)}
|
||||
/>
|
||||
<Tag color="info is-outlined" label={item.displayName} onRemove={() => this.removeEntry(item)} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -47,7 +42,7 @@ export default class TagGroup extends React.Component<Props> {
|
||||
);
|
||||
}
|
||||
|
||||
removeEntry = item => {
|
||||
removeEntry = (item: DisplayedUser) => {
|
||||
const newItems = this.props.items.filter(name => name !== item);
|
||||
this.props.onRemove(newItems);
|
||||
};
|
||||
@@ -1,25 +1,19 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import React, { ChangeEvent } from "react";
|
||||
import LabelWithHelpIcon from "./LabelWithHelpIcon";
|
||||
|
||||
export type SelectItem = {
|
||||
value: string,
|
||||
label: string
|
||||
};
|
||||
|
||||
type Props = {
|
||||
name?: string,
|
||||
label?: string,
|
||||
placeholder?: SelectItem[],
|
||||
value?: string,
|
||||
autofocus?: boolean,
|
||||
onChange: (value: string, name?: string) => void,
|
||||
helpText?: string,
|
||||
disabled?: boolean
|
||||
name?: string;
|
||||
label?: string;
|
||||
placeholder?: string;
|
||||
value?: string;
|
||||
autofocus?: boolean;
|
||||
onChange: (value: string, name?: string) => void;
|
||||
helpText?: string;
|
||||
disabled?: boolean;
|
||||
};
|
||||
|
||||
class Textarea extends React.Component<Props> {
|
||||
field: ?HTMLTextAreaElement;
|
||||
field: HTMLTextAreaElement | null | undefined;
|
||||
|
||||
componentDidMount() {
|
||||
if (this.props.autofocus && this.field) {
|
||||
@@ -27,7 +21,7 @@ class Textarea extends React.Component<Props> {
|
||||
}
|
||||
}
|
||||
|
||||
handleInput = (event: SyntheticInputEvent<HTMLTextAreaElement>) => {
|
||||
handleInput = (event: ChangeEvent<HTMLTextAreaElement>) => {
|
||||
this.props.onChange(event.target.value, this.props.name);
|
||||
};
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
// @create-index
|
||||
|
||||
export { default as AddEntryToTableField } from "./AddEntryToTableField.js";
|
||||
export { default as AutocompleteAddEntryToTableField } from "./AutocompleteAddEntryToTableField.js";
|
||||
export { default as TagGroup } from "./TagGroup.js";
|
||||
export { default as MemberNameTagGroup } from "./MemberNameTagGroup.js";
|
||||
export { default as Checkbox } from "./Checkbox.js";
|
||||
export { default as Radio } from "./Radio.js";
|
||||
export { default as FilterInput } from "./FilterInput.js";
|
||||
export { default as InputField } from "./InputField.js";
|
||||
export { default as Select } from "./Select.js";
|
||||
export { default as Textarea } from "./Textarea.js";
|
||||
export { default as PasswordConfirmation } from "./PasswordConfirmation.js";
|
||||
export { default as LabelWithHelpIcon } from "./LabelWithHelpIcon.js";
|
||||
export { default as DropDown } from "./DropDown.js";
|
||||
|
||||
15
scm-ui/ui-components/src/forms/index.ts
Normal file
15
scm-ui/ui-components/src/forms/index.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
// @create-index
|
||||
|
||||
export { default as AddEntryToTableField } from "./AddEntryToTableField";
|
||||
export { default as AutocompleteAddEntryToTableField } from "./AutocompleteAddEntryToTableField";
|
||||
export { default as TagGroup } from "./TagGroup";
|
||||
export { default as MemberNameTagGroup } from "./MemberNameTagGroup";
|
||||
export { default as Checkbox } from "./Checkbox";
|
||||
export { default as Radio } from "./Radio";
|
||||
export { default as FilterInput } from "./FilterInput";
|
||||
export { default as InputField } from "./InputField";
|
||||
export { default as Select } from "./Select";
|
||||
export { default as Textarea } from "./Textarea";
|
||||
export { default as PasswordConfirmation } from "./PasswordConfirmation";
|
||||
export { default as LabelWithHelpIcon } from "./LabelWithHelpIcon";
|
||||
export { default as DropDown } from "./DropDown";
|
||||
@@ -1,45 +1,58 @@
|
||||
// @create-index
|
||||
|
||||
import * as validation from "./validation.js";
|
||||
import * as validation from "./validation";
|
||||
import * as urls from "./urls";
|
||||
import * as repositories from "./repositories.js";
|
||||
import * as repositories from "./repositories";
|
||||
|
||||
// not sure if it is required
|
||||
import {
|
||||
File,
|
||||
FileChangeType,
|
||||
Hunk,
|
||||
Change,
|
||||
BaseContext,
|
||||
AnnotationFactory,
|
||||
AnnotationFactoryContext,
|
||||
DiffEventHandler,
|
||||
DiffEventContext
|
||||
} from "./repos";
|
||||
|
||||
export { validation, urls, repositories };
|
||||
|
||||
export { default as DateFromNow } from "./DateFromNow.js";
|
||||
export { default as ErrorNotification } from "./ErrorNotification.js";
|
||||
export { default as ErrorPage } from "./ErrorPage.js";
|
||||
export { default as Icon } from "./Icon.js";
|
||||
export { default as Image } from "./Image.js";
|
||||
export { default as Loading } from "./Loading.js";
|
||||
export { default as Logo } from "./Logo.js";
|
||||
export { default as MailLink } from "./MailLink.js";
|
||||
export { default as Notification } from "./Notification.js";
|
||||
export { default as Paginator } from "./Paginator.js";
|
||||
export { default as LinkPaginator } from "./LinkPaginator.js";
|
||||
export { default as StatePaginator } from "./StatePaginator.js";
|
||||
export { default as DateFromNow } from "./DateFromNow";
|
||||
export { default as ErrorNotification } from "./ErrorNotification";
|
||||
export { default as ErrorPage } from "./ErrorPage";
|
||||
export { default as Icon } from "./Icon";
|
||||
export { default as Image } from "./Image";
|
||||
export { default as Loading } from "./Loading";
|
||||
export { default as Logo } from "./Logo";
|
||||
export { default as MailLink } from "./MailLink";
|
||||
export { default as Notification } from "./Notification";
|
||||
export { default as Paginator } from "./Paginator";
|
||||
export { default as LinkPaginator } from "./LinkPaginator";
|
||||
export { default as StatePaginator } from "./StatePaginator";
|
||||
|
||||
export { default as FileSize } from "./FileSize.js";
|
||||
export { default as ProtectedRoute } from "./ProtectedRoute.js";
|
||||
export { default as FileSize } from "./FileSize";
|
||||
export { default as ProtectedRoute } from "./ProtectedRoute";
|
||||
export { default as Help } from "./Help";
|
||||
export { default as HelpIcon } from "./HelpIcon";
|
||||
export { default as Tag } from "./Tag";
|
||||
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 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";
|
||||
export { default as SyntaxHighlighter } from "./SyntaxHighlighter";
|
||||
export { default as ErrorBoundary } from "./ErrorBoundary";
|
||||
export { default as OverviewPageActions } from "./OverviewPageActions.js";
|
||||
export { default as OverviewPageActions } from "./OverviewPageActions";
|
||||
export { default as CardColumnGroup } from "./CardColumnGroup";
|
||||
export { default as CardColumn } from "./CardColumn";
|
||||
|
||||
export { apiClient } from "./apiclient.js";
|
||||
export { apiClient } from "./apiclient";
|
||||
export * from "./errors";
|
||||
|
||||
export * from "./avatar";
|
||||
@@ -51,8 +64,7 @@ export * from "./modals";
|
||||
export * from "./navigation";
|
||||
export * from "./repos";
|
||||
|
||||
// not sure if it is required
|
||||
export type {
|
||||
export {
|
||||
File,
|
||||
FileChangeType,
|
||||
Hunk,
|
||||
@@ -62,4 +74,4 @@ export type {
|
||||
AnnotationFactoryContext,
|
||||
DiffEventHandler,
|
||||
DiffEventContext
|
||||
} from "./repos";
|
||||
};
|
||||
@@ -1,10 +1,9 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import type { Me } from "@scm-manager/ui-types";
|
||||
import {Link} from "react-router-dom";
|
||||
import { Me } from "@scm-manager/ui-types";
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
type Props = {
|
||||
me?: Me
|
||||
me?: Me;
|
||||
};
|
||||
|
||||
class Footer extends React.Component<Props> {
|
||||
@@ -16,7 +15,9 @@ class Footer extends React.Component<Props> {
|
||||
return (
|
||||
<footer className="footer">
|
||||
<div className="container is-centered">
|
||||
<p className="has-text-centered"><Link to={"/me"}>{me.displayName}</Link></p>
|
||||
<p className="has-text-centered">
|
||||
<Link to={"/me"}>{me.displayName}</Link>
|
||||
</p>
|
||||
</div>
|
||||
</footer>
|
||||
);
|
||||
@@ -1,31 +1,30 @@
|
||||
//@flow
|
||||
import * as React from "react";
|
||||
import Logo from "./../Logo";
|
||||
|
||||
type Props = {
|
||||
children?: React.Node
|
||||
};
|
||||
|
||||
class Header extends React.Component<Props> {
|
||||
render() {
|
||||
const { children } = this.props;
|
||||
return (
|
||||
<section className="hero is-dark is-small">
|
||||
<div className="hero-body">
|
||||
<div className="container">
|
||||
<div className="columns is-vcentered">
|
||||
<div className="column">
|
||||
<Logo />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="hero-foot">
|
||||
<div className="container">{children}</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Header;
|
||||
import React, { ReactNode } from "react";
|
||||
import Logo from "./../Logo";
|
||||
|
||||
type Props = {
|
||||
children?: ReactNode;
|
||||
};
|
||||
|
||||
class Header extends React.Component<Props> {
|
||||
render() {
|
||||
const { children } = this.props;
|
||||
return (
|
||||
<section className="hero is-dark is-small">
|
||||
<div className="hero-body">
|
||||
<div className="container">
|
||||
<div className="columns is-vcentered">
|
||||
<div className="column">
|
||||
<Logo />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="hero-foot">
|
||||
<div className="container">{children}</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Header;
|
||||
@@ -1,11 +1,10 @@
|
||||
//@flow
|
||||
import * as React from "react";
|
||||
import React, { ReactNode } from "react";
|
||||
import classNames from "classnames";
|
||||
|
||||
type Props = {
|
||||
className?: string,
|
||||
left?: React.Node,
|
||||
right?: React.Node
|
||||
className?: string;
|
||||
left?: ReactNode;
|
||||
right?: ReactNode;
|
||||
};
|
||||
|
||||
export default class Level extends React.Component<Props> {
|
||||
@@ -1,5 +1,4 @@
|
||||
//@flow
|
||||
import * as React from "react";
|
||||
import React, { ReactNode } from "react";
|
||||
import classNames from "classnames";
|
||||
import styled from "styled-components";
|
||||
import Loading from "./../Loading";
|
||||
@@ -10,12 +9,12 @@ import PageActions from "./PageActions";
|
||||
import ErrorBoundary from "../ErrorBoundary";
|
||||
|
||||
type Props = {
|
||||
title?: string,
|
||||
subtitle?: string,
|
||||
loading?: boolean,
|
||||
error?: Error,
|
||||
showContentOnError?: boolean,
|
||||
children: React.Node
|
||||
title?: string;
|
||||
subtitle?: string;
|
||||
loading?: boolean;
|
||||
error?: Error;
|
||||
showContentOnError?: boolean;
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
const PageActionContainer = styled.div`
|
||||
@@ -46,8 +45,7 @@ export default class Page extends React.Component<Props> {
|
||||
|
||||
isPageAction(node: any) {
|
||||
return (
|
||||
node.displayName === PageActions.displayName ||
|
||||
(node.type && node.type.displayName === PageActions.displayName)
|
||||
node.displayName === PageActions.displayName || (node.type && node.type.displayName === PageActions.displayName)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -61,12 +59,7 @@ export default class Page extends React.Component<Props> {
|
||||
if (this.isPageAction(child)) {
|
||||
pageActions = (
|
||||
<PageActionContainer
|
||||
className={classNames(
|
||||
"column",
|
||||
"is-three-fifths",
|
||||
"is-mobile-action-spacing",
|
||||
"is-flex"
|
||||
)}
|
||||
className={classNames("column", "is-three-fifths", "is-mobile-action-spacing", "is-flex")}
|
||||
>
|
||||
{child}
|
||||
</PageActionContainer>
|
||||
@@ -75,9 +68,7 @@ export default class Page extends React.Component<Props> {
|
||||
}
|
||||
}
|
||||
});
|
||||
let underline = pageActionsExists ? (
|
||||
<hr className="header-with-actions" />
|
||||
) : null;
|
||||
const underline = pageActionsExists ? <hr className="header-with-actions" /> : null;
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -103,7 +94,7 @@ export default class Page extends React.Component<Props> {
|
||||
return <Loading />;
|
||||
}
|
||||
|
||||
let content = [];
|
||||
const content: ReactNode[] = [];
|
||||
React.Children.forEach(children, child => {
|
||||
if (child) {
|
||||
if (!this.isPageAction(child)) {
|
||||
@@ -1,11 +1,10 @@
|
||||
//@flow
|
||||
import * as React from "react";
|
||||
import React, { ReactNode } from "react";
|
||||
import Loading from "./../Loading";
|
||||
|
||||
type Props = {
|
||||
loading?: boolean,
|
||||
error?: Error,
|
||||
children: React.Node
|
||||
loading?: boolean;
|
||||
error?: Error;
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
export default class PageActions extends React.Component<Props> {
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user