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 type { Changeset } from "@scm-manager/ui-types";
import { ExtensionPoint } from "@scm-manager/ui-extensions";
import {translate} from "react-i18next";
type Props = {
changeset: Changeset
changeset: Changeset,
// context props
t: (string) => string
};
class ChangesetAuthor extends React.Component<Props> {
@@ -14,39 +18,35 @@ class ChangesetAuthor extends React.Component<Props> {
return null;
}
const { name } = changeset.author;
const { name, mail } = changeset.author;
if (mail) {
return this.withExtensionPoint(this.renderWithMail(name, mail));
}
return this.withExtensionPoint(<>{name}</>);
}
renderWithMail(name: string, mail: string) {
const { t } = this.props;
return (
<>
{name} {this.renderMail()} {this.renderAuthorMetadataExtensionPoint()}
</>
<a href={"mailto: " + mail} title={t("changesets.author.mailto") + " " + mail}>
{name}
</a>
);
}
renderAuthorMetadataExtensionPoint = () => {
const { changeset } = this.props;
withExtensionPoint(child: any) {
const { t } = this.props;
return (
<ExtensionPoint
name="changesets.changeset.author.metadata"
props={{ changeset }}
renderAll={true}
>
asas
</ExtensionPoint>
<>
{t("changesets.author.prefix")} {child}
<ExtensionPoint
name="changesets.author.suffix"
props={{ changeset: this.props.changeset }}
renderAll={true}
/>
</>
);
};
renderMail() {
const { mail } = this.props.changeset.author;
if (mail) {
return (
<a className="is-hidden-mobile" href={"mailto:" + mail}>
&lt;
{mail}
&gt;
</a>
);
}
}
}
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 React from "react";
import type {Changeset, Repository} from "@scm-manager/ui-types";
import { createChangesetLink } from "./changesets";
type Props = {
repository: Repository,
@@ -20,13 +21,11 @@ export default class ChangesetId extends React.Component<Props> {
};
renderLink = () => {
const { changeset, repository } = this.props;
const { repository, changeset } = this.props;
const link = createChangesetLink(repository, changeset);
return (
<Link
to={`/repo/${repository.namespace}/${repository.name}/changeset/${
changeset.id
}`}
>
<Link to={link}>
{this.shortId(changeset)}
</Link>
);

View File

@@ -8,21 +8,39 @@ import ChangesetId from "./ChangesetId";
import injectSheet from "react-jss";
import { DateFromNow } from "../..";
import ChangesetAuthor from "./ChangesetAuthor";
import ChangesetTag from "./ChangesetTag";
import { parseDescription } from "./changesets";
import { AvatarWrapper, AvatarImage } from "../../avatar";
import { ExtensionPoint } from "@scm-manager/ui-extensions";
import ChangesetTags from "./ChangesetTags";
import ChangesetButtonGroup from "./ChangesetButtonGroup";
const styles = {
pointer: {
cursor: "pointer"
changeset: {
// & 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: {
marginBottom: "1em"
avatarFigure: {
marginTop: ".25rem",
marginRight: ".5rem",
},
withOverflow: {
overflow: "auto"
avatarImage: {
height: "35px",
width: "35px"
},
isVcentered: {
marginTop: "auto",
marginBottom: "auto"
},
metadata: {
marginLeft: 0
},
tag: {
marginTop: ".5rem"
}
};
@@ -34,74 +52,70 @@ type Props = {
};
class ChangesetRow extends React.Component<Props> {
createLink = (changeset: Changeset) => {
createChangesetId = (changeset: Changeset) => {
const { repository } = this.props;
return <ChangesetId changeset={changeset} repository={repository} />;
};
getTags = () => {
const { changeset } = this.props;
return changeset._embedded.tags || [];
};
render() {
const { changeset, classes } = this.props;
const changesetLink = this.createLink(changeset);
const dateFromNow = <DateFromNow date={changeset.date} />;
const authorLine = <ChangesetAuthor changeset={changeset} />;
const { repository, changeset, classes } = this.props;
const description = parseDescription(changeset.description);
const changesetId = this.createChangesetId(changeset);
const dateFromNow = <DateFromNow date={changeset.date} />;
return (
<article className={classNames("media", classes.inner)}>
<AvatarWrapper>
<div>
<figure className="media-left">
<p className="image is-64x64">
<AvatarImage person={changeset.author} />
</p>
</figure>
<div className={classes.changeset}>
<div className="columns">
<div className="column is-three-fifths">
<h4 className="has-text-weight-bold is-ellipsis-overflow">
<ExtensionPoint
name="changesets.changeset.description"
props={{ changeset, value: description.title }}
renderAll={false}
>
{description.title}
</ExtensionPoint>
</h4>
<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
i18nKey="changesets.changeset.summary"
id={changesetId}
time={dateFromNow}
/>
</p>
<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>
</AvatarWrapper>
<div className={classNames("media-content", classes.withOverflow)}>
<div className="content">
<p className="is-ellipsis-overflow">
<strong>
<ExtensionPoint
name="changesets.changeset.description"
props={{ changeset, value: description.title }}
renderAll={false}
>
{description.title}
</ExtensionPoint>
</strong>
<br />
<Interpolate
i18nKey="changesets.changeset.summary"
id={changesetLink}
time={dateFromNow}
/>
</p>{" "}
<div className="is-size-7">{authorLine}</div>
<div className={classNames("column", classes.isVcentered)}>
<ChangesetTags changeset={changeset} />
<ChangesetButtonGroup repository={repository} changeset={changeset} />
</div>
</div>
{this.renderTags()}
</article>
</div>
);
}
renderTags = () => {
const tags = this.getTags();
if (tags.length > 0) {
return (
<div className="media-right">
{tags.map((tag: Tag) => {
return <ChangesetTag key={tag.name} tag={tag} />;
})}
</div>
);
}
return null;
};
}
export default injectSheet(styles)(translate("repos")(ChangesetRow));

View File

@@ -1,32 +1,17 @@
//@flow
import React from "react";
import type { Tag } from "@scm-manager/ui-types";
import injectSheet from "react-jss";
import classNames from "classnames";
const styles = {
spacing: {
marginRight: "4px"
}
};
import ChangesetTagBase from "./ChangesetTagBase";
type Props = {
tag: Tag,
// context props
classes: Object
tag: Tag
};
class ChangesetTag extends React.Component<Props> {
render() {
const { tag, classes } = this.props;
return (
<span className="tag is-info">
<span className={classNames("fa", "fa-tag", classes.spacing)} />{" "}
{tag.name}
</span>
);
const { tag } = this.props;
return <ChangesetTagBase icon={"fa-tag"} label={tag.name} />;
}
}
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
import type { Changeset, Repository } from "@scm-manager/ui-types";
export type Description = {
title: 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 {
const desc = description ? description : "";
const lineBreak = desc.indexOf("\n");

View File

@@ -3,8 +3,12 @@ import * as changesets from "./changesets";
export { changesets };
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 ChangesetList } from "./ChangesetList";
export { default as ChangesetRow } from "./ChangesetRow";
export { default as ChangesetTag } from "./ChangesetTag";
export { default as ChangesetTags } from "./ChangesetTags";
export { default as ChangesetTagsCollapsed } from "./ChangesetTagsCollapsed";
export { default as ChangesetDiff } from "./ChangesetDiff";