redesign changeset list

This commit is contained in:
Sebastian Sdorra
2019-02-07 10:27:11 +01:00
parent 6ba90fb8da
commit 0bf7a6f168
12 changed files with 278 additions and 121 deletions

View File

@@ -2,9 +2,13 @@
import React from "react"; import React from "react";
import type { Changeset } from "@scm-manager/ui-types"; import type { Changeset } from "@scm-manager/ui-types";
import { ExtensionPoint } from "@scm-manager/ui-extensions"; import { ExtensionPoint } from "@scm-manager/ui-extensions";
import {translate} from "react-i18next";
type Props = { type Props = {
changeset: Changeset changeset: Changeset,
// context props
t: (string) => string
}; };
class ChangesetAuthor extends React.Component<Props> { class ChangesetAuthor extends React.Component<Props> {
@@ -14,39 +18,35 @@ class ChangesetAuthor extends React.Component<Props> {
return null; return null;
} }
const { name } = changeset.author; const { name, mail } = changeset.author;
return ( if (mail) {
<> return this.withExtensionPoint(this.renderWithMail(name, mail));
{name} {this.renderMail()} {this.renderAuthorMetadataExtensionPoint()} }
</> return this.withExtensionPoint(<>{name}</>);
);
} }
renderAuthorMetadataExtensionPoint = () => { renderWithMail(name: string, mail: string) {
const { changeset } = this.props; const { t } = this.props;
return ( return (
<ExtensionPoint <a href={"mailto: " + mail} title={t("changesets.author.mailto") + " " + mail}>
name="changesets.changeset.author.metadata" {name}
props={{ changeset }}
renderAll={true}
>
asas
</ExtensionPoint>
);
};
renderMail() {
const { mail } = this.props.changeset.author;
if (mail) {
return (
<a className="is-hidden-mobile" href={"mailto:" + mail}>
&lt;
{mail}
&gt;
</a> </a>
); );
} }
withExtensionPoint(child: any) {
const { t } = this.props;
return (
<>
{t("changesets.author.prefix")} {child}
<ExtensionPoint
name="changesets.author.suffix"
props={{ changeset: this.props.changeset }}
renderAll={true}
/>
</>
);
} }
} }
export default ChangesetAuthor; export default translate("repos")(ChangesetAuthor);

View File

@@ -0,0 +1,45 @@
//@flow
import React from "react";
import type { Changeset, Repository } from "@scm-manager/ui-types";
import ButtonGroup from "../../buttons/ButtonGroup";
import Button from "../../buttons/Button";
import { createChangesetLink, createSourcesLink } from "./changesets";
type Props = {
repository: Repository,
changeset: Changeset
}
class ChangesetButtonGroup extends React.Component<Props> {
render() {
const { repository, changeset } = this.props;
const changesetLink = createChangesetLink(repository, changeset);
const sourcesLink = createSourcesLink(repository, changeset);
return (
<ButtonGroup className="is-pulled-right">
<Button link={changesetLink}>
<span className="icon">
<i className="fas fa-code"></i>
</span>
<span className="is-hidden-mobile is-hidden-tablet-only">
Details
</span>
</Button>
<Button link={sourcesLink}>
<span className="icon">
<i className="fas fa-history"></i>
</span>
<span className="is-hidden-mobile is-hidden-tablet-only">
Sources
</span>
</Button>
</ButtonGroup>
);
}
}
export default ChangesetButtonGroup;

View File

@@ -3,6 +3,7 @@
import {Link} from "react-router-dom"; import {Link} from "react-router-dom";
import React from "react"; import React from "react";
import type {Changeset, Repository} from "@scm-manager/ui-types"; import type {Changeset, Repository} from "@scm-manager/ui-types";
import { createChangesetLink } from "./changesets";
type Props = { type Props = {
repository: Repository, repository: Repository,
@@ -20,13 +21,11 @@ export default class ChangesetId extends React.Component<Props> {
}; };
renderLink = () => { renderLink = () => {
const { changeset, repository } = this.props; const { repository, changeset } = this.props;
const link = createChangesetLink(repository, changeset);
return ( return (
<Link <Link to={link}>
to={`/repo/${repository.namespace}/${repository.name}/changeset/${
changeset.id
}`}
>
{this.shortId(changeset)} {this.shortId(changeset)}
</Link> </Link>
); );

View File

@@ -8,21 +8,39 @@ import ChangesetId from "./ChangesetId";
import injectSheet from "react-jss"; import injectSheet from "react-jss";
import { DateFromNow } from "../.."; import { DateFromNow } from "../..";
import ChangesetAuthor from "./ChangesetAuthor"; import ChangesetAuthor from "./ChangesetAuthor";
import ChangesetTag from "./ChangesetTag";
import { parseDescription } from "./changesets"; import { parseDescription } from "./changesets";
import { AvatarWrapper, AvatarImage } from "../../avatar"; import { AvatarWrapper, AvatarImage } from "../../avatar";
import { ExtensionPoint } from "@scm-manager/ui-extensions"; import { ExtensionPoint } from "@scm-manager/ui-extensions";
import ChangesetTags from "./ChangesetTags";
import ChangesetButtonGroup from "./ChangesetButtonGroup";
const styles = { const styles = {
pointer: { changeset: {
cursor: "pointer" // & references parent rule
// have a look at https://cssinjs.org/jss-plugin-nested?v=v10.0.0-alpha.9
"& + &": {
borderTop: "1px solid rgba(219, 219, 219, 0.5)",
marginTop: "1rem",
paddingTop: "1rem"
}
}, },
changesetGroup: { avatarFigure: {
marginBottom: "1em" marginTop: ".25rem",
marginRight: ".5rem",
}, },
withOverflow: { avatarImage: {
overflow: "auto" height: "35px",
width: "35px"
},
isVcentered: {
marginTop: "auto",
marginBottom: "auto"
},
metadata: {
marginLeft: 0
},
tag: {
marginTop: ".5rem"
} }
}; };
@@ -34,38 +52,23 @@ type Props = {
}; };
class ChangesetRow extends React.Component<Props> { class ChangesetRow extends React.Component<Props> {
createLink = (changeset: Changeset) => { createChangesetId = (changeset: Changeset) => {
const { repository } = this.props; const { repository } = this.props;
return <ChangesetId changeset={changeset} repository={repository} />; return <ChangesetId changeset={changeset} repository={repository} />;
}; };
getTags = () => {
const { changeset } = this.props;
return changeset._embedded.tags || [];
};
render() { render() {
const { changeset, classes } = this.props; const { repository, changeset, classes } = this.props;
const changesetLink = this.createLink(changeset);
const dateFromNow = <DateFromNow date={changeset.date} />;
const authorLine = <ChangesetAuthor changeset={changeset} />;
const description = parseDescription(changeset.description); const description = parseDescription(changeset.description);
const changesetId = this.createChangesetId(changeset);
const dateFromNow = <DateFromNow date={changeset.date} />;
return ( return (
<article className={classNames("media", classes.inner)}> <div className={classes.changeset}>
<AvatarWrapper> <div className="columns">
<div> <div className="column is-three-fifths">
<figure className="media-left">
<p className="image is-64x64"> <h4 className="has-text-weight-bold is-ellipsis-overflow">
<AvatarImage person={changeset.author} />
</p>
</figure>
</div>
</AvatarWrapper>
<div className={classNames("media-content", classes.withOverflow)}>
<div className="content">
<p className="is-ellipsis-overflow">
<strong>
<ExtensionPoint <ExtensionPoint
name="changesets.changeset.description" name="changesets.changeset.description"
props={{ changeset, value: description.title }} props={{ changeset, value: description.title }}
@@ -73,35 +76,46 @@ class ChangesetRow extends React.Component<Props> {
> >
{description.title} {description.title}
</ExtensionPoint> </ExtensionPoint>
</strong> </h4>
<br />
<div className="media">
<AvatarWrapper>
<figure className={classNames(classes.avatarFigure, "media-left")}>
<div className={classNames("image", classes.avatarImage)}>
<AvatarImage person={changeset.author} />
</div>
</figure>
</AvatarWrapper>
<div className={classNames(classes.metadata, "media-right")}>
<p className="is-hidden-mobile is-hidden-tablet-only">
<Interpolate <Interpolate
i18nKey="changesets.changeset.summary" i18nKey="changesets.changeset.summary"
id={changesetLink} id={changesetId}
time={dateFromNow} time={dateFromNow}
/> />
</p>{" "} </p>
<div className="is-size-7">{authorLine}</div> <p className="is-hidden-desktop">
<Interpolate
i18nKey="changesets.changeset.short-summary"
id={changesetId}
time={dateFromNow}
/>
</p>
<p className="is-size-7">
<ChangesetAuthor changeset={changeset} />
</p>
</div> </div>
</div> </div>
{this.renderTags()}
</article>
);
}
renderTags = () => { </div>
const tags = this.getTags(); <div className={classNames("column", classes.isVcentered)}>
if (tags.length > 0) { <ChangesetTags changeset={changeset} />
return ( <ChangesetButtonGroup repository={repository} changeset={changeset} />
<div className="media-right"> </div>
{tags.map((tag: Tag) => { </div>
return <ChangesetTag key={tag.name} tag={tag} />;
})}
</div> </div>
); );
} }
return null;
};
} }
export default injectSheet(styles)(translate("repos")(ChangesetRow)); export default injectSheet(styles)(translate("repos")(ChangesetRow));

View File

@@ -1,32 +1,17 @@
//@flow //@flow
import React from "react"; import React from "react";
import type { Tag } from "@scm-manager/ui-types"; import type { Tag } from "@scm-manager/ui-types";
import injectSheet from "react-jss"; import ChangesetTagBase from "./ChangesetTagBase";
import classNames from "classnames";
const styles = {
spacing: {
marginRight: "4px"
}
};
type Props = { type Props = {
tag: Tag, tag: Tag
// context props
classes: Object
}; };
class ChangesetTag extends React.Component<Props> { class ChangesetTag extends React.Component<Props> {
render() { render() {
const { tag, classes } = this.props; const { tag } = this.props;
return ( return <ChangesetTagBase icon={"fa-tag"} label={tag.name} />;
<span className="tag is-info">
<span className={classNames("fa", "fa-tag", classes.spacing)} />{" "}
{tag.name}
</span>
);
} }
} }
export default injectSheet(styles)(ChangesetTag); export default ChangesetTag;

View File

@@ -0,0 +1,34 @@
//@flow
import React from "react";
import injectSheet from "react-jss";
import classNames from "classnames";
const styles = {
tag: {
marginTop: ".5rem"
},
spacing: {
marginRight: ".25rem"
}
};
type Props = {
icon: string,
label: string,
// context props
classes: Object
};
class ChangesetTagBase extends React.Component<Props> {
render() {
const { icon, label, classes } = this.props;
return (
<span className={classNames(classes.tag, "tag", "is-info")}>
<span className={classNames("fa", icon, classes.spacing)} /> {label}
</span>
);
}
}
export default injectSheet(styles)(ChangesetTagBase);

View File

@@ -0,0 +1,31 @@
//@flow
import React from "react";
import type { Changeset} from "@scm-manager/ui-types";
import ChangesetTag from "./ChangesetTag";
import ChangesetTagsCollapsed from "./ChangesetTagsCollapsed";
type Props = {
changeset: Changeset
};
class ChangesetTags extends React.Component<Props> {
getTags = () => {
const { changeset } = this.props;
return changeset._embedded.tags || [];
};
render() {
const tags = this.getTags();
if (tags.length === 1) {
return <ChangesetTag tag={tags[0]} />;
} else if (tags.length > 1) {
return <ChangesetTagsCollapsed tags={tags} />;
} else {
return null;
}
}
}
export default ChangesetTags;

View File

@@ -0,0 +1,27 @@
//@flow
import React from "react";
import type { Tag } from "@scm-manager/ui-types";
import ChangesetTagBase from "./ChangesetTagBase";
import { translate } from "react-i18next";
import Tooltip from "../../Tooltip";
type Props = {
tags: Tag[],
// context props
t: (string) => string
};
class ChangesetTagsCollapsed extends React.Component<Props> {
render() {
const { tags, t } = this.props;
const message = tags.map((tag) => tag.name).join(", ");
return (
<Tooltip location="top" message={message}>
<ChangesetTagBase icon={"fa-tags"} label={ tags.length + " " + t("changesets.tags") } />
</Tooltip>
);
}
}
export default translate("repos")(ChangesetTagsCollapsed);

View File

@@ -1,9 +1,19 @@
// @flow // @flow
import type { Changeset, Repository } from "@scm-manager/ui-types";
export type Description = { export type Description = {
title: string, title: string,
message: string message: string
}; };
export function createChangesetLink(repository: Repository, changeset: Changeset) {
return `/repo/${repository.namespace}/${repository.name}/changeset/${changeset.id}`;
}
export function createSourcesLink(repository: Repository, changeset: Changeset) {
return `/repo/${repository.namespace}/${repository.name}/sources/${changeset.id}`;
}
export function parseDescription(description?: string): Description { export function parseDescription(description?: string): Description {
const desc = description ? description : ""; const desc = description ? description : "";
const lineBreak = desc.indexOf("\n"); const lineBreak = desc.indexOf("\n");

View File

@@ -3,8 +3,12 @@ import * as changesets from "./changesets";
export { changesets }; export { changesets };
export { default as ChangesetAuthor } from "./ChangesetAuthor"; export { default as ChangesetAuthor } from "./ChangesetAuthor";
export { default as ChangesetButtonGroup } from "./ChangesetButtonGroup";
export { default as ChangesetDiff } from "./ChangesetDiff";
export { default as ChangesetId } from "./ChangesetId"; export { default as ChangesetId } from "./ChangesetId";
export { default as ChangesetList } from "./ChangesetList"; export { default as ChangesetList } from "./ChangesetList";
export { default as ChangesetRow } from "./ChangesetRow"; export { default as ChangesetRow } from "./ChangesetRow";
export { default as ChangesetTag } from "./ChangesetTag"; export { default as ChangesetTag } from "./ChangesetTag";
export { default as ChangesetTags } from "./ChangesetTags";
export { default as ChangesetTagsCollapsed } from "./ChangesetTagsCollapsed";
export { default as ChangesetDiff } from "./ChangesetDiff"; export { default as ChangesetDiff } from "./ChangesetDiff";

View File

@@ -76,11 +76,15 @@
"description": "Beschreibung", "description": "Beschreibung",
"contact": "Kontakt", "contact": "Kontakt",
"date": "Datum", "date": "Datum",
"summary": "Changeset {{id}} wurde committet {{time}}" "summary": "Changeset {{id}} wurde committet {{time}}",
"short-summary": "Committet {{id}} {{time}}"
}, },
"tags": "Tags",
"author": { "author": {
"name": "Autor", "name": "Autor",
"mail": "Mail" "mail": "Mail",
"prefix": "Verfasst von",
"mailto": "E-Mail senden an"
} }
}, },
"branch-selector": { "branch-selector": {

View File

@@ -76,11 +76,15 @@
"description": "Description", "description": "Description",
"contact": "Contact", "contact": "Contact",
"date": "Date", "date": "Date",
"summary": "Changeset {{id}} was committed {{time}}" "summary": "Changeset {{id}} was committed {{time}}",
"short-summary": "Committed {{id}} {{time}}"
}, },
"tags": "Tags",
"author": { "author": {
"name": "Author", "name": "Author",
"mail": "Mail" "mail": "Mail",
"prefix": "Authored by",
"mailto": "Send mail to"
} }
}, },
"branch-selector": { "branch-selector": {