Merge with upstream

This commit is contained in:
Florian Scholdei
2020-02-25 16:22:54 +01:00
78 changed files with 3192 additions and 1518 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@@ -484,7 +484,7 @@ exports[`Storyshots DateFromNow Default 1`] = `
exports[`Storyshots Diff Binaries 1`] = `
<div
className="sc-TOsTZ flmUBf"
className="sc-hmzhuo TypKC"
>
<div
className="sc-gZMcBi iABzaT panel is-size-6"
@@ -753,7 +753,7 @@ exports[`Storyshots Diff Binaries 1`] = `
exports[`Storyshots Diff Collapsed 1`] = `
<div
className="sc-TOsTZ flmUBf"
className="sc-hmzhuo TypKC"
>
<div
className="sc-gZMcBi ckdmuY panel is-size-6"
@@ -1102,7 +1102,7 @@ exports[`Storyshots Diff Collapsed 1`] = `
exports[`Storyshots Diff CollapsingWithFunction 1`] = `
<div
className="sc-TOsTZ flmUBf"
className="sc-hmzhuo TypKC"
>
<div
className="sc-gZMcBi ckdmuY panel is-size-6"
@@ -3028,7 +3028,7 @@ exports[`Storyshots Diff CollapsingWithFunction 1`] = `
exports[`Storyshots Diff Default 1`] = `
<div
className="sc-TOsTZ flmUBf"
className="sc-hmzhuo TypKC"
>
<div
className="sc-gZMcBi iABzaT panel is-size-6"
@@ -6936,7 +6936,7 @@ exports[`Storyshots Diff Default 1`] = `
exports[`Storyshots Diff File Annotation 1`] = `
<div
className="sc-TOsTZ flmUBf"
className="sc-hmzhuo TypKC"
>
<div
className="sc-gZMcBi iABzaT panel is-size-6"
@@ -10868,7 +10868,7 @@ exports[`Storyshots Diff File Annotation 1`] = `
exports[`Storyshots Diff File Controls 1`] = `
<div
className="sc-TOsTZ flmUBf"
className="sc-hmzhuo TypKC"
>
<div
className="sc-gZMcBi iABzaT panel is-size-6"
@@ -14884,7 +14884,7 @@ exports[`Storyshots Diff File Controls 1`] = `
exports[`Storyshots Diff Hunks 1`] = `
<div
className="sc-TOsTZ flmUBf"
className="sc-hmzhuo TypKC"
>
<div
className="sc-gZMcBi iABzaT panel is-size-6"
@@ -15721,7 +15721,7 @@ exports[`Storyshots Diff Hunks 1`] = `
exports[`Storyshots Diff Line Annotation 1`] = `
<div
className="sc-TOsTZ flmUBf"
className="sc-hmzhuo TypKC"
>
<div
className="sc-gZMcBi iABzaT panel is-size-6"
@@ -19665,7 +19665,7 @@ exports[`Storyshots Diff Line Annotation 1`] = `
exports[`Storyshots Diff OnClick 1`] = `
<div
className="sc-TOsTZ flmUBf"
className="sc-hmzhuo TypKC"
>
<div
className="sc-gZMcBi iABzaT panel is-size-6"
@@ -23847,7 +23847,7 @@ exports[`Storyshots Diff OnClick 1`] = `
exports[`Storyshots Diff Side-By-Side 1`] = `
<div
className="sc-TOsTZ flmUBf"
className="sc-hmzhuo TypKC"
>
<div
className="sc-gZMcBi iABzaT panel is-size-6"
@@ -28284,7 +28284,7 @@ exports[`Storyshots Diff Side-By-Side 1`] = `
exports[`Storyshots Diff SyntaxHighlighting 1`] = `
<div
className="sc-TOsTZ flmUBf"
className="sc-hmzhuo TypKC"
>
<div
className="sc-gZMcBi iABzaT panel is-size-6"
@@ -32192,7 +32192,7 @@ exports[`Storyshots Diff SyntaxHighlighting 1`] = `
exports[`Storyshots Forms|Checkbox Default 1`] = `
<div
className="sc-gisBJw jHakbY"
className="sc-kgAjT khfRmZ"
>
<div
className="field"
@@ -32237,7 +32237,7 @@ exports[`Storyshots Forms|Checkbox Default 1`] = `
exports[`Storyshots Forms|Checkbox Disabled 1`] = `
<div
className="sc-gisBJw jHakbY"
className="sc-kgAjT khfRmZ"
>
<div
className="field"
@@ -32265,7 +32265,7 @@ exports[`Storyshots Forms|Checkbox Disabled 1`] = `
exports[`Storyshots Forms|Radio Default 1`] = `
<div
className="sc-kjoXOD hVPZau"
className="sc-cJSrbW hLoADP"
>
<label
className="sc-cMljjf kOqpHe radio"
@@ -32294,7 +32294,7 @@ exports[`Storyshots Forms|Radio Default 1`] = `
exports[`Storyshots Forms|Radio Disabled 1`] = `
<div
className="sc-kjoXOD hVPZau"
className="sc-cJSrbW hLoADP"
>
<label
className="sc-cMljjf kOqpHe radio"
@@ -32314,7 +32314,7 @@ exports[`Storyshots Forms|Radio Disabled 1`] = `
exports[`Storyshots Forms|Textarea OnCancel 1`] = `
<div
className="sc-cHGsZl klfJMr"
className="sc-ksYbfQ ePXdiL"
>
<div
className="field"
@@ -32337,7 +32337,7 @@ exports[`Storyshots Forms|Textarea OnCancel 1`] = `
exports[`Storyshots Forms|Textarea OnChange 1`] = `
<div
className="sc-cHGsZl klfJMr"
className="sc-ksYbfQ ePXdiL"
>
<div
className="field"
@@ -32364,7 +32364,7 @@ exports[`Storyshots Forms|Textarea OnChange 1`] = `
exports[`Storyshots Forms|Textarea OnSubmit 1`] = `
<div
className="sc-cHGsZl klfJMr"
className="sc-ksYbfQ ePXdiL"
>
<div
className="field"
@@ -32389,6 +32389,514 @@ exports[`Storyshots Forms|Textarea OnSubmit 1`] = `
</div>
`;
exports[`Storyshots Layout|Footer Default 1`] = `
<footer
className="footer"
>
<section
className="section container"
>
<div
className="columns is-size-7"
>
<section
className="column is-one-third"
>
<div
className="sc-hzDkRC jeksqW"
>
<i
className="fas fa-user-circle fa-fw"
/>
Trillian McMillian
</div>
<ul
className="sc-jhAzac eoWThz"
>
<li>
<a
className=""
href="/me"
onClick={[Function]}
>
footer.user.profile
</a>
</li>
<li>
<a
className=""
href="/me/settings/password"
onClick={[Function]}
>
profile.changePasswordNavLink
</a>
</li>
</ul>
</section>
<section
className="column is-one-third"
>
<div
className="sc-hzDkRC jeksqW"
>
<i
className="fas fa-info-circle fa-fw"
/>
footer.information.title
</div>
<ul
className="sc-jhAzac eoWThz"
>
<li>
<a
href="https://www.scm-manager.org/"
target="_blank"
>
SCM-Manager 2.0.0
</a>
</li>
</ul>
</section>
<section
className="column is-one-third"
>
<div
className="sc-hzDkRC jeksqW"
>
<i
className="fas fa-life-ring fa-fw"
/>
footer.support.title
</div>
<ul
className="sc-jhAzac eoWThz"
>
<li>
<a
href="https://www.scm-manager.org/support/"
target="_blank"
>
footer.support.community
</a>
</li>
<li>
<a
href="https://cloudogu.com/en/scm-manager-enterprise/"
target="_blank"
>
footer.support.enterprise
</a>
</li>
</ul>
</section>
</div>
</section>
</footer>
`;
exports[`Storyshots Layout|Footer Full 1`] = `
<footer
className="footer"
>
<section
className="section container"
>
<div
className="columns is-size-7"
>
<section
className="column is-one-third"
>
<div
className="sc-hzDkRC jeksqW"
>
<span
className="sc-fMiknA fyPpQQ image is-rounded"
>
<img
alt="trillian"
className="is-rounded sc-fBuWsC djJrAv"
src="test-file-stub"
/>
</span>
Trillian McMillian
</div>
<ul
className="sc-jhAzac eoWThz"
>
<li>
<a
className=""
href="/me"
onClick={[Function]}
>
footer.user.profile
</a>
</li>
<li>
<a
className=""
href="/me/settings/password"
onClick={[Function]}
>
profile.changePasswordNavLink
</a>
</li>
<li>
<a
className=""
href="/"
onClick={[Function]}
>
Authorized Keys
</a>
</li>
</ul>
</section>
<section
className="column is-one-third"
>
<div
className="sc-hzDkRC jeksqW"
>
<i
className="fas fa-info-circle fa-fw"
/>
footer.information.title
</div>
<ul
className="sc-jhAzac eoWThz"
>
<li>
<a
href="https://www.scm-manager.org/"
target="_blank"
>
SCM-Manager 2.0.0
</a>
</li>
<li>
<a
href="#"
target="_blank"
>
REST API
</a>
</li>
<li>
<a
href="#"
target="_blank"
>
CLI
</a>
</li>
</ul>
</section>
<section
className="column is-one-third"
>
<div
className="sc-hzDkRC jeksqW"
>
<i
className="fas fa-life-ring fa-fw"
/>
footer.support.title
</div>
<ul
className="sc-jhAzac eoWThz"
>
<li>
<a
href="https://www.scm-manager.org/support/"
target="_blank"
>
footer.support.community
</a>
</li>
<li>
<a
href="https://cloudogu.com/en/scm-manager-enterprise/"
target="_blank"
>
footer.support.enterprise
</a>
</li>
<li>
<a
href="#"
target="_blank"
>
FAQ
</a>
</li>
</ul>
</section>
</div>
</section>
</footer>
`;
exports[`Storyshots Layout|Footer With Avatar 1`] = `
<footer
className="footer"
>
<section
className="section container"
>
<div
className="columns is-size-7"
>
<section
className="column is-one-third"
>
<div
className="sc-hzDkRC jeksqW"
>
<span
className="sc-fMiknA fyPpQQ image is-rounded"
>
<img
alt="trillian"
className="is-rounded sc-fBuWsC djJrAv"
src="test-file-stub"
/>
</span>
Trillian McMillian
</div>
<ul
className="sc-jhAzac eoWThz"
>
<li>
<a
className=""
href="/me"
onClick={[Function]}
>
footer.user.profile
</a>
</li>
<li>
<a
className=""
href="/me/settings/password"
onClick={[Function]}
>
profile.changePasswordNavLink
</a>
</li>
</ul>
</section>
<section
className="column is-one-third"
>
<div
className="sc-hzDkRC jeksqW"
>
<i
className="fas fa-info-circle fa-fw"
/>
footer.information.title
</div>
<ul
className="sc-jhAzac eoWThz"
>
<li>
<a
href="https://www.scm-manager.org/"
target="_blank"
>
SCM-Manager 2.0.0
</a>
</li>
</ul>
</section>
<section
className="column is-one-third"
>
<div
className="sc-hzDkRC jeksqW"
>
<i
className="fas fa-life-ring fa-fw"
/>
footer.support.title
</div>
<ul
className="sc-jhAzac eoWThz"
>
<li>
<a
href="https://www.scm-manager.org/support/"
target="_blank"
>
footer.support.community
</a>
</li>
<li>
<a
href="https://cloudogu.com/en/scm-manager-enterprise/"
target="_blank"
>
footer.support.enterprise
</a>
</li>
</ul>
</section>
</div>
</section>
</footer>
`;
exports[`Storyshots Layout|Footer With Plugin Links 1`] = `
<footer
className="footer"
>
<section
className="section container"
>
<div
className="columns is-size-7"
>
<section
className="column is-one-third"
>
<div
className="sc-hzDkRC jeksqW"
>
<i
className="fas fa-user-circle fa-fw"
/>
Trillian McMillian
</div>
<ul
className="sc-jhAzac eoWThz"
>
<li>
<a
className=""
href="/me"
onClick={[Function]}
>
footer.user.profile
</a>
</li>
<li>
<a
className=""
href="/me/settings/password"
onClick={[Function]}
>
profile.changePasswordNavLink
</a>
</li>
<li>
<a
className=""
href="/"
onClick={[Function]}
>
Authorized Keys
</a>
</li>
</ul>
</section>
<section
className="column is-one-third"
>
<div
className="sc-hzDkRC jeksqW"
>
<i
className="fas fa-info-circle fa-fw"
/>
footer.information.title
</div>
<ul
className="sc-jhAzac eoWThz"
>
<li>
<a
href="https://www.scm-manager.org/"
target="_blank"
>
SCM-Manager 2.0.0
</a>
</li>
<li>
<a
href="#"
target="_blank"
>
REST API
</a>
</li>
<li>
<a
href="#"
target="_blank"
>
CLI
</a>
</li>
</ul>
</section>
<section
className="column is-one-third"
>
<div
className="sc-hzDkRC jeksqW"
>
<i
className="fas fa-life-ring fa-fw"
/>
footer.support.title
</div>
<ul
className="sc-jhAzac eoWThz"
>
<li>
<a
href="https://www.scm-manager.org/support/"
target="_blank"
>
footer.support.community
</a>
</li>
<li>
<a
href="https://cloudogu.com/en/scm-manager-enterprise/"
target="_blank"
>
footer.support.enterprise
</a>
</li>
<li>
<a
href="#"
target="_blank"
>
FAQ
</a>
</li>
</ul>
</section>
</div>
</section>
</footer>
`;
exports[`Storyshots Loading Default 1`] = `
<div>
<div
@@ -34243,7 +34751,7 @@ PORT_NUMBER =
exports[`Storyshots Table|Table Default 1`] = `
<table
className="sc-fBuWsC eeihxG table content is-hoverable"
className="sc-fAjcbJ byigni table content is-hoverable"
>
<thead>
<tr>
@@ -34261,7 +34769,7 @@ exports[`Storyshots Table|Table Default 1`] = `
>
Last Name
<i
className="fas fa-sort-amount-down has-text-grey-light sc-jhAzac gDbcZp"
className="fas fa-sort-amount-down has-text-grey-light sc-eqIVtm jxAoDg"
/>
</th>
<th
@@ -34334,7 +34842,7 @@ exports[`Storyshots Table|Table Empty 1`] = `
exports[`Storyshots Table|Table TextColumn 1`] = `
<table
className="sc-fBuWsC eeihxG table content is-hoverable"
className="sc-fAjcbJ byigni table content is-hoverable"
>
<thead>
<tr>
@@ -34346,7 +34854,7 @@ exports[`Storyshots Table|Table TextColumn 1`] = `
>
Id
<i
className="fas fa-sort-alpha-down has-text-grey-light sc-jhAzac gDbcZp"
className="fas fa-sort-alpha-down has-text-grey-light sc-eqIVtm jxAoDg"
/>
</th>
<th
@@ -34357,7 +34865,7 @@ exports[`Storyshots Table|Table TextColumn 1`] = `
>
Name
<i
className="fas fa-sort-alpha-down has-text-grey-light sc-jhAzac gDbcZp"
className="fas fa-sort-alpha-down has-text-grey-light sc-eqIVtm jxAoDg"
/>
</th>
<th
@@ -34368,7 +34876,7 @@ exports[`Storyshots Table|Table TextColumn 1`] = `
>
Description
<i
className="fas fa-sort-alpha-down has-text-grey-light sc-jhAzac gDbcZp"
className="fas fa-sort-alpha-down has-text-grey-light sc-eqIVtm jxAoDg"
/>
</th>
</tr>

View File

@@ -1,26 +1,30 @@
import React from "react";
import { binder } from "@scm-manager/ui-extensions";
import React, { FC } from "react";
import { Image } from "..";
import { Person } from "./Avatar";
import { EXTENSION_POINT } from "./Avatar";
import { useBinder } from "@scm-manager/ui-extensions";
type Props = {
person: Person;
representation?: "rounded" | "rounded-border";
className?: string;
};
class AvatarImage extends React.Component<Props> {
render() {
const { person } = this.props;
const AvatarImage: FC<Props> = ({ person, representation = "rounded-border", className }) => {
const binder = useBinder();
const avatarFactory = binder.getExtension(EXTENSION_POINT);
if (avatarFactory) {
const avatar = avatarFactory(person);
const avatarFactory = binder.getExtension(EXTENSION_POINT);
if (avatarFactory) {
const avatar = avatarFactory(person);
return <Image className="has-rounded-border" src={avatar} alt={person.name} />;
let classes = representation === "rounded" ? "is-rounded" : "has-rounded-border";
if (className) {
classes += " " + className;
}
return null;
return <Image className={classes} src={avatar} alt={person.name} />;
}
}
return null;
};
export default AvatarImage;

View File

@@ -1,18 +1,13 @@
import React, { Component, ReactNode } from "react";
import { binder } from "@scm-manager/ui-extensions";
import React, { FC } from "react";
import { useBinder } from "@scm-manager/ui-extensions";
import { EXTENSION_POINT } from "./Avatar";
type Props = {
children: ReactNode;
const AvatarWrapper: FC = ({ children }) => {
const binder = useBinder();
if (binder.hasExtension(EXTENSION_POINT)) {
return <>{children}</>;
}
return null;
};
class AvatarWrapper extends Component<Props> {
render() {
if (binder.hasExtension(EXTENSION_POINT)) {
return <>{this.props.children}</>;
}
return null;
}
}
export default AvatarWrapper;

View File

@@ -0,0 +1,62 @@
import React from "react";
import { storiesOf } from "@storybook/react";
import Footer from "./Footer";
import { Binder, BinderContext } from "@scm-manager/ui-extensions";
import { Me } from "@scm-manager/ui-types";
import { EXTENSION_POINT } from "../avatar/Avatar";
// @ts-ignore ignore unknown png
import hitchhiker from "../__resources__/hitchhiker.png";
// @ts-ignore ignore unknown jpg
import marvin from "../__resources__/marvin.jpg";
import NavLink from "../navigation/NavLink";
import ExternalLink from "../navigation/ExternalLink";
const trillian: Me = {
name: "trillian",
displayName: "Trillian McMillian",
mail: "tricia@hitchhiker.com",
groups: ["crew"],
_links: {}
};
const bindAvatar = (binder: Binder, avatar: string) => {
binder.bind(EXTENSION_POINT, () => {
return avatar;
});
};
const bindLinks = (binder: Binder) => {
binder.bind("footer.information", () => <ExternalLink to="#" label="REST API" />);
binder.bind("footer.information", () => <ExternalLink to="#" label="CLI" />);
binder.bind("footer.support", () => <ExternalLink to="#" label="FAQ" />);
binder.bind("profile.setting", () => <NavLink label="Authorized Keys" to="#" />);
};
const withBinder = (binder: Binder) => {
return (
<BinderContext.Provider value={binder}>
<Footer me={trillian} version="2.0.0" links={{}} />
</BinderContext.Provider>
);
};
storiesOf("Layout|Footer", module)
.add("Default", () => {
return <Footer me={trillian} version="2.0.0" links={{}} />;
})
.add("With Avatar", () => {
const binder = new Binder("avatar-story");
bindAvatar(binder, hitchhiker);
return withBinder(binder);
})
.add("With Plugin Links", () => {
const binder = new Binder("link-story");
bindLinks(binder);
return withBinder(binder);
})
.add("Full", () => {
const binder = new Binder("link-story");
bindAvatar(binder, marvin);
bindLinks(binder);
return withBinder(binder);
});

View File

@@ -1,27 +1,93 @@
import React from "react";
import { Me } from "@scm-manager/ui-types";
import { Link } from "react-router-dom";
import React, { FC } from "react";
import { Me, Links } from "@scm-manager/ui-types";
import { useBinder, ExtensionPoint } from "@scm-manager/ui-extensions";
import { AvatarImage } from "../avatar";
import NavLink from "../navigation/NavLink";
import FooterSection from "./FooterSection";
import styled from "styled-components";
import { EXTENSION_POINT } from "../avatar/Avatar";
import ExternalLink from "../navigation/ExternalLink";
import { useTranslation } from "react-i18next";
type Props = {
me?: Me;
version: string;
links: Links;
};
class Footer extends React.Component<Props> {
render() {
const { me } = this.props;
if (!me) {
return "";
}
return (
<footer className="footer">
<div className="container is-centered">
<p className="has-text-centered">
<Link to={"/me"}>{me.displayName}</Link>
</p>
</div>
</footer>
);
type TitleWithIconsProps = {
title: string;
icon: string;
};
const TitleWithIcon: FC<TitleWithIconsProps> = ({ icon, title }) => (
<>
<i className={`fas fa-${icon} fa-fw`} /> {title}
</>
);
type TitleWithAvatarProps = {
me: Me;
};
const VCenteredAvatar = styled(AvatarImage)`
vertical-align: middle;
`;
const AvatarContainer = styled.span`
float: left;
margin-right: 0.3em;
padding-top: 0.2em;
width: 1em;
height: 1em;
`;
const TitleWithAvatar: FC<TitleWithAvatarProps> = ({ me }) => (
<>
<AvatarContainer className="image is-rounded">
<VCenteredAvatar person={me} representation="rounded" />
</AvatarContainer>
{me.displayName}
</>
);
const Footer: FC<Props> = ({ me, version, links }) => {
const [t] = useTranslation("commons");
const binder = useBinder();
if (!me) {
return null;
}
}
const extensionProps = { me, url: "/me", links };
let meSectionTile;
if (binder.hasExtension(EXTENSION_POINT)) {
meSectionTile = <TitleWithAvatar me={me} />;
} else {
meSectionTile = <TitleWithIcon title={me.displayName} icon="user-circle" />;
}
return (
<footer className="footer">
<section className="section container">
<div className="columns is-size-7">
<FooterSection title={meSectionTile}>
<NavLink to="/me" label={t("footer.user.profile")} />
<NavLink to="/me/settings/password" label={t("profile.changePasswordNavLink")} />
<ExtensionPoint name="profile.setting" props={extensionProps} renderAll={true} />
</FooterSection>
<FooterSection title={<TitleWithIcon title={t("footer.information.title")} icon="info-circle" />}>
<ExternalLink to="https://www.scm-manager.org/" label={`SCM-Manager ${version}`} />
<ExtensionPoint name="footer.information" props={extensionProps} renderAll={true} />
</FooterSection>
<FooterSection title={<TitleWithIcon title={t("footer.support.title")} icon="life-ring" />}>
<ExternalLink to="https://www.scm-manager.org/support/" label={t("footer.support.community")} />
<ExternalLink to="https://cloudogu.com/en/scm-manager-enterprise/" label={t("footer.support.enterprise")} />
<ExtensionPoint name="footer.support" props={extensionProps} renderAll={true} />
</FooterSection>
</div>
</section>
</footer>
);
};
export default Footer;

View File

@@ -0,0 +1,26 @@
import React, { FC, ReactNode } from "react";
import styled from "styled-components";
type Props = {
title: ReactNode;
};
const Title = styled.div`
font-weight: bold;
margin-bottom: 0.5rem;
`;
const Menu = styled.ul`
padding-left: 1.1rem;
`;
const FooterSection: FC<Props> = ({ title, children }) => {
return (
<section className="column is-one-third">
<Title>{title}</Title>
<Menu>{children}</Menu>
</section>
);
};
export default FooterSection;

View File

@@ -0,0 +1,30 @@
import React, { FC } from "react";
import classNames from "classnames";
type Props = {
to: string;
icon?: string;
label: string;
};
const ExternalLink: FC<Props> = ({ to, icon, label }) => {
let showIcon;
if (icon) {
showIcon = (
<>
<i className={classNames(icon, "fa-fw")} />{" "}
</>
);
}
return (
<li>
<a target="_blank" href={to}>
{showIcon}
{label}
</a>
</li>
);
};
export default ExternalLink;