improve changeset styling

This commit is contained in:
Sebastian Sdorra
2018-10-18 14:40:35 +02:00
parent 167e8dbdca
commit 9ac5b3fe37
13 changed files with 239 additions and 136 deletions

View File

@@ -9,9 +9,18 @@ type Props = {
};
class Image extends React.Component<Props> {
createImageSrc = () => {
const { src } = this.props;
if (src.startsWith("http")) {
return src;
}
return withContextPath(src);
};
render() {
const { src, alt, className } = this.props;
return <img className={className} src={withContextPath(src)} alt={alt} />;
const { alt, className } = this.props;
return <img className={className} src={this.createImageSrc()} alt={alt} />;
}
}

View File

@@ -53,7 +53,7 @@
"description": "Description",
"contact": "Contact",
"date": "Date",
"summary": "Changeset {{id}} committed {{time}}"
"summary": "Changeset {{id}} was committed {{time}}"
},
"author": {
"name": "Author",

View File

@@ -1,26 +0,0 @@
//@flow
import React from "react";
import { ExtensionPoint } from "@scm-manager/ui-extensions";
import type { Changeset, Repository } from "@scm-manager/ui-types";
import { Image } from "@scm-manager/ui-components";
type Props = {
changeset: Changeset,
repository: Repository
};
class ChangesetAvatar extends React.Component<Props> {
render() {
const { changeset, repository } = this.props;
return (
<p className="image is-64x64">
<ExtensionPoint
name="repos.changeset.gravatar"
props={{ changeset, repository }}
/>
</p>
);
}
}
export default ChangesetAvatar;

View File

@@ -1,69 +0,0 @@
//@flow
import React from "react";
import type { Changeset, Repository } from "@scm-manager/ui-types";
import { translate } from "react-i18next";
import { MailLink, DateFromNow } from "@scm-manager/ui-components";
import ChangesetAvatar from "./ChangesetAvatar";
import classNames from "classnames";
import injectSheet from "react-jss";
const styles = {
floatLeft: {
float: "left"
}
};
type Props = {
changeset: Changeset,
repository: Repository,
t: string => string,
classes: any
};
class ChangesetDetails extends React.Component<Props> {
render() {
const { changeset, repository, t, classes } = this.props;
const mailadress = changeset.author.mail ? (
<tr>
<td>{t("author.mail")}</td>
<td>
<MailLink address={changeset.author.mail} />
</td>
</tr>
) : null;
return (
<div>
<table className={classNames("table", classes.floatLeft)}>
<tbody>
<tr>
<td>{t("changeset.id")}</td>
<td>{changeset.id}</td>
</tr>
<tr>
<td>{t("author.name")}</td>
<td>{changeset.author.name}</td>
</tr>
{mailadress}
<tr>
<td>{t("changeset.description")}</td>
<td>{changeset.description}</td>
</tr>
<tr>
<td>{t("changeset.date")}</td>
<td>
<DateFromNow date={changeset.date} />
</td>
</tr>
</tbody>
</table>
<figure className={classNames(classes.floatLeft)}>
<ChangesetAvatar changeset={changeset} repository={repository} />
</figure>
</div>
);
}
}
export default injectSheet(styles)(translate("changesets")(ChangesetDetails));

View File

@@ -0,0 +1,32 @@
//@flow
import React from "react";
import { binder } from "@scm-manager/ui-extensions";
import type { Changeset } from "@scm-manager/ui-types";
import { Image } from "@scm-manager/ui-components";
type Props = {
changeset: Changeset
};
class AvatarImage extends React.Component<Props> {
render() {
const { changeset } = this.props;
const avatarFactory = binder.getExtension("changeset.avatar-factory");
if (avatarFactory) {
const avatar = avatarFactory(changeset);
return (
<Image
className="has-rounded-border"
src={avatar}
alt={changeset.author.name}
/>
);
}
return null;
}
}
export default AvatarImage;

View File

@@ -0,0 +1,18 @@
//@flow
import React from "react";
import { binder } from "@scm-manager/ui-extensions";
type Props = {
children: React.Node
};
class AvatarWrapper extends React.Component<Props> {
render() {
if (binder.hasExtension("changeset.avatar-factory")) {
return <>{this.props.children}</>;
}
return null;
}
}
export default AvatarWrapper;

View File

@@ -1,30 +0,0 @@
//@flow
import React from "react";
import { ExtensionPoint } from "@scm-manager/ui-extensions";
import type { Changeset } from "@scm-manager/ui-types";
type Props = {
changeset: Changeset
};
class ChangesetAvatar extends React.Component<Props> {
render() {
const { changeset } = this.props;
return (
<ExtensionPoint
name="repos.changeset-table.information"
renderAll={true}
props={{ changeset }}
>
{/* extension should render something like this: */}
{/* <div className="image is-64x64"> */}
{/* <figure className="media-left"> */}
{/* <Image src="/some/image.jpg" alt="Logo" /> */}
{/* </figure> */}
{/* </div> */}
</ExtensionPoint>
);
}
}
export default ChangesetAvatar;

View File

@@ -0,0 +1,96 @@
//@flow
import React from "react";
import type { Changeset, Repository } from "../../../../../scm-ui-components/packages/ui-types/src/index";
import { Interpolate, translate } from "react-i18next";
import injectSheet from "react-jss";
import ChangesetTag from "./ChangesetTag";
import ChangesetAuthor from "./ChangesetAuthor";
import { parseDescription } from "./changesets";
import { DateFromNow } from "../../../../../scm-ui-components/packages/ui-components/src/index";
import AvatarWrapper from "./AvatarWrapper";
import AvatarImage from "./AvatarImage";
import classNames from "classnames";
import ChangesetId from "./ChangesetId";
const styles = {
spacing: {
marginRight: "1em"
}
};
type Props = {
changeset: Changeset,
repository: Repository,
t: string => string,
classes: any
};
class ChangesetDetails extends React.Component<Props> {
render() {
const { changeset, repository, t, classes } = this.props;
const description = parseDescription(changeset.description);
const id = (
<ChangesetId repository={repository} changeset={changeset} link={false} />
);
const date = <DateFromNow date={changeset.date} />;
return (
<div className="content">
<h4>{description.title}</h4>
<article className="media">
<AvatarWrapper>
<p className={classNames("image", "is-64x64", classes.spacing)}>
<AvatarImage changeset={changeset} />
</p>
</AvatarWrapper>
<div className="media-content">
<p>
<ChangesetAuthor changeset={changeset} />
</p>
<p>
<Interpolate
i18nKey="changesets.changeset.summary"
id={id}
time={date}
/>
</p>
</div>
<div className="media-right">{this.renderTags()}</div>
</article>
<p>
{description.message.split("\n").map((item, key) => {
return (
<span key={key}>
{item}
<br />
</span>
);
})}
</p>
</div>
);
}
getTags = () => {
const { changeset } = this.props;
return changeset._embedded.tags || [];
};
renderTags = () => {
const tags = this.getTags();
if (tags.length > 0) {
return (
<div className="level-item">
{tags.map((tag: Tag) => {
return <ChangesetTag key={tag.name} tag={tag} />;
})}
</div>
);
}
return null;
};
}
export default injectSheet(styles)(translate("repos")(ChangesetDetails));

View File

@@ -6,20 +6,42 @@ import type { Repository, Changeset } from "@scm-manager/ui-types";
type Props = {
repository: Repository,
changeset: Changeset
changeset: Changeset,
link: boolean
};
export default class ChangesetId extends React.Component<Props> {
render() {
const { repository, changeset } = this.props;
static defaultProps = {
link: true
};
shortId = (changeset: Changeset) => {
return changeset.id.substr(0, 7);
};
renderLink = () => {
const { changeset, repository } = this.props;
return (
<Link
to={`/repo/${repository.namespace}/${repository.name}/changeset/${
changeset.id
}`}
>
{changeset.id.substr(0, 7)}
{this.shortId(changeset)}
</Link>
);
};
renderText = () => {
const { changeset } = this.props;
return this.shortId(changeset);
};
render() {
const { link } = this.props;
if (link) {
return this.renderLink();
}
return this.renderText();
}
}

View File

@@ -3,13 +3,15 @@ import React from "react";
import type { Changeset, Repository, Tag } from "@scm-manager/ui-types";
import classNames from "classnames";
import { translate, Interpolate } from "react-i18next";
import ChangesetAvatar from "./ChangesetAvatar";
import ChangesetId from "./ChangesetId";
import injectSheet from "react-jss";
import { DateFromNow } from "@scm-manager/ui-components";
import ChangesetAuthor from "./ChangesetAuthor";
import ChangesetTag from "./ChangesetTag";
import { compose } from "redux";
import { parseDescription } from "./changesets";
import AvatarWrapper from "./AvatarWrapper";
import AvatarImage from "./AvatarImage";
const styles = {
pointer: {
@@ -46,14 +48,23 @@ class ChangesetRow extends React.Component<Props> {
const changesetLink = this.createLink(changeset);
const dateFromNow = <DateFromNow date={changeset.date} />;
const authorLine = <ChangesetAuthor changeset={changeset} />;
const description = parseDescription(changeset.description);
return (
<article className={classNames("media", classes.inner)}>
<ChangesetAvatar changeset={changeset} />
<AvatarWrapper>
<div>
<figure className="media-left">
<p className="image is-64x64">
<AvatarImage changeset={changeset} />
</p>
</figure>
</div>
</AvatarWrapper>
<div className={classNames("media-content", classes.withOverflow)}>
<div className="content">
<p className="is-ellipsis-overflow">
{changeset.description}
<strong>{description.title}</strong>
<br />
<Interpolate
i18nKey="changesets.changeset.summary"

View File

@@ -0,0 +1,24 @@
// @flow
export type Description = {
title: string,
message: string
};
export function parseDescription(description: string): Description {
const lineBreak = description.indexOf("\n");
let title;
let message = "";
if (lineBreak > 0) {
title = description.substring(0, lineBreak);
message = description.substring(lineBreak + 1);
} else {
title = description;
}
return {
title,
message
};
}

View File

@@ -0,0 +1,16 @@
// @flow
import { parseDescription } from "./changesets";
describe("parseDescription tests", () => {
it("should return a description with title and message", () => {
const desc = parseDescription("Hello\nTrillian");
expect(desc.title).toBe("Hello");
expect(desc.message).toBe("Trillian");
});
it("should return a description with title and without message", () => {
const desc = parseDescription("Hello Trillian");
expect(desc.title).toBe("Hello Trillian");
});
});

View File

@@ -9,7 +9,7 @@ import {
getFetchChangesetFailure,
isFetchChangesetPending
} from "../modules/changesets";
import ChangesetDetails from "../components/ChangesetDetails";
import ChangesetDetails from "../components/changesets/ChangesetDetails";
import { translate } from "react-i18next";
import { Loading, ErrorPage } from "@scm-manager/ui-components";