mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-07 05:55:44 +01:00
improve changeset styling
This commit is contained in:
@@ -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} />;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@
|
||||
"description": "Description",
|
||||
"contact": "Contact",
|
||||
"date": "Date",
|
||||
"summary": "Changeset {{id}} committed {{time}}"
|
||||
"summary": "Changeset {{id}} was committed {{time}}"
|
||||
},
|
||||
"author": {
|
||||
"name": "Author",
|
||||
|
||||
@@ -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;
|
||||
@@ -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));
|
||||
32
scm-ui/src/repos/components/changesets/AvatarImage.js
Normal file
32
scm-ui/src/repos/components/changesets/AvatarImage.js
Normal 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;
|
||||
18
scm-ui/src/repos/components/changesets/AvatarWrapper.js
Normal file
18
scm-ui/src/repos/components/changesets/AvatarWrapper.js
Normal 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;
|
||||
@@ -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;
|
||||
96
scm-ui/src/repos/components/changesets/ChangesetDetails.js
Normal file
96
scm-ui/src/repos/components/changesets/ChangesetDetails.js
Normal 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));
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
24
scm-ui/src/repos/components/changesets/changesets.js
Normal file
24
scm-ui/src/repos/components/changesets/changesets.js
Normal 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
|
||||
};
|
||||
}
|
||||
16
scm-ui/src/repos/components/changesets/changesets.test.js
Normal file
16
scm-ui/src/repos/components/changesets/changesets.test.js
Normal 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");
|
||||
});
|
||||
});
|
||||
@@ -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";
|
||||
|
||||
|
||||
Reference in New Issue
Block a user