mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-10 23:45:44 +01:00
scm-ui: new repository layout
This commit is contained in:
55
scm-ui/ui-components/src/repos/changesets/ChangesetAuthor.js
Normal file
55
scm-ui/ui-components/src/repos/changesets/ChangesetAuthor.js
Normal file
@@ -0,0 +1,55 @@
|
||||
//@flow
|
||||
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,
|
||||
|
||||
// context props
|
||||
t: string => string
|
||||
};
|
||||
|
||||
class ChangesetAuthor extends React.Component<Props> {
|
||||
render() {
|
||||
const { changeset } = this.props;
|
||||
if (!changeset.author) {
|
||||
return null;
|
||||
}
|
||||
|
||||
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 (
|
||||
<a
|
||||
href={"mailto:" + mail}
|
||||
title={t("changeset.author.mailto") + " " + mail}
|
||||
>
|
||||
{name}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
withExtensionPoint(child: any) {
|
||||
const { t } = this.props;
|
||||
return (
|
||||
<>
|
||||
{t("changeset.author.prefix")} {child}
|
||||
<ExtensionPoint
|
||||
name="changesets.author.suffix"
|
||||
props={{ changeset: this.props.changeset }}
|
||||
renderAll={true}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default translate("repos")(ChangesetAuthor);
|
||||
@@ -0,0 +1,40 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import type { Changeset, Repository } from "@scm-manager/ui-types";
|
||||
import { ButtonAddons, Button } from "../../buttons";
|
||||
import { createChangesetLink, createSourcesLink } from "./changesets";
|
||||
import { translate } from "react-i18next";
|
||||
|
||||
type Props = {
|
||||
repository: Repository,
|
||||
changeset: Changeset,
|
||||
|
||||
// context props
|
||||
t: string => string
|
||||
};
|
||||
|
||||
class ChangesetButtonGroup extends React.Component<Props> {
|
||||
render() {
|
||||
const { repository, changeset, t } = this.props;
|
||||
const changesetLink = createChangesetLink(repository, changeset);
|
||||
const sourcesLink = createSourcesLink(repository, changeset);
|
||||
return (
|
||||
<ButtonAddons className="is-marginless">
|
||||
<Button
|
||||
link={changesetLink}
|
||||
icon="exchange-alt"
|
||||
label={t("changeset.buttons.details")}
|
||||
reducedMobile={true}
|
||||
/>
|
||||
<Button
|
||||
link={sourcesLink}
|
||||
icon="code"
|
||||
label={t("changeset.buttons.sources")}
|
||||
reducedMobile={true}
|
||||
/>
|
||||
</ButtonAddons>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default translate("repos")(ChangesetButtonGroup);
|
||||
37
scm-ui/ui-components/src/repos/changesets/ChangesetDiff.js
Normal file
37
scm-ui/ui-components/src/repos/changesets/ChangesetDiff.js
Normal file
@@ -0,0 +1,37 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import type { Changeset } from "@scm-manager/ui-types";
|
||||
import LoadingDiff from "../LoadingDiff";
|
||||
import Notification from "../../Notification";
|
||||
import {translate} from "react-i18next";
|
||||
|
||||
type Props = {
|
||||
changeset: Changeset,
|
||||
|
||||
// context props
|
||||
t: string => string
|
||||
};
|
||||
|
||||
class ChangesetDiff extends React.Component<Props> {
|
||||
|
||||
isDiffSupported(changeset: Changeset) {
|
||||
return !!changeset._links.diff;
|
||||
}
|
||||
|
||||
createUrl(changeset: Changeset) {
|
||||
return changeset._links.diff.href + "?format=GIT";
|
||||
}
|
||||
|
||||
render() {
|
||||
const { changeset, t } = this.props;
|
||||
if (!this.isDiffSupported(changeset)) {
|
||||
return <Notification type="danger">{t("changeset.diffNotSupported")}</Notification>;
|
||||
} else {
|
||||
const url = this.createUrl(changeset);
|
||||
return <LoadingDiff url={url} />;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default translate("repos")(ChangesetDiff);
|
||||
46
scm-ui/ui-components/src/repos/changesets/ChangesetId.js
Normal file
46
scm-ui/ui-components/src/repos/changesets/ChangesetId.js
Normal file
@@ -0,0 +1,46 @@
|
||||
//@flow
|
||||
|
||||
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,
|
||||
changeset: Changeset,
|
||||
link: boolean
|
||||
};
|
||||
|
||||
export default class ChangesetId extends React.Component<Props> {
|
||||
static defaultProps = {
|
||||
link: true
|
||||
};
|
||||
|
||||
shortId = (changeset: Changeset) => {
|
||||
return changeset.id.substr(0, 7);
|
||||
};
|
||||
|
||||
renderLink = () => {
|
||||
const { repository, changeset } = this.props;
|
||||
const link = createChangesetLink(repository, changeset);
|
||||
|
||||
return (
|
||||
<Link to={link}>
|
||||
{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();
|
||||
}
|
||||
}
|
||||
28
scm-ui/ui-components/src/repos/changesets/ChangesetList.js
Normal file
28
scm-ui/ui-components/src/repos/changesets/ChangesetList.js
Normal file
@@ -0,0 +1,28 @@
|
||||
// @flow
|
||||
import ChangesetRow from "./ChangesetRow";
|
||||
import React from "react";
|
||||
|
||||
import type { Changeset, Repository } from "@scm-manager/ui-types";
|
||||
|
||||
type Props = {
|
||||
repository: Repository,
|
||||
changesets: Changeset[]
|
||||
};
|
||||
|
||||
class ChangesetList extends React.Component<Props> {
|
||||
render() {
|
||||
const { repository, changesets } = this.props;
|
||||
const content = changesets.map(changeset => {
|
||||
return (
|
||||
<ChangesetRow
|
||||
key={changeset.id}
|
||||
repository={repository}
|
||||
changeset={changeset}
|
||||
/>
|
||||
);
|
||||
});
|
||||
return <>{content}</>;
|
||||
}
|
||||
}
|
||||
|
||||
export default ChangesetList;
|
||||
138
scm-ui/ui-components/src/repos/changesets/ChangesetRow.js
Normal file
138
scm-ui/ui-components/src/repos/changesets/ChangesetRow.js
Normal file
@@ -0,0 +1,138 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import type { Changeset, Repository } from "@scm-manager/ui-types";
|
||||
|
||||
import classNames from "classnames";
|
||||
import { Interpolate, translate } from "react-i18next";
|
||||
import ChangesetId from "./ChangesetId";
|
||||
import injectSheet from "react-jss";
|
||||
import { DateFromNow } from "../..";
|
||||
import ChangesetAuthor from "./ChangesetAuthor";
|
||||
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 = {
|
||||
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"
|
||||
}
|
||||
},
|
||||
avatarFigure: {
|
||||
marginTop: ".5rem",
|
||||
marginRight: ".5rem"
|
||||
},
|
||||
avatarImage: {
|
||||
height: "35px",
|
||||
width: "35px"
|
||||
},
|
||||
metadata: {
|
||||
marginLeft: 0
|
||||
},
|
||||
authorMargin: {
|
||||
marginTop: "0.5rem"
|
||||
},
|
||||
isVcentered: {
|
||||
alignSelf: "center"
|
||||
},
|
||||
flexVcenter: {
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "flex-end"
|
||||
}
|
||||
};
|
||||
|
||||
type Props = {
|
||||
repository: Repository,
|
||||
changeset: Changeset,
|
||||
t: any,
|
||||
classes: any
|
||||
};
|
||||
|
||||
class ChangesetRow extends React.Component<Props> {
|
||||
createChangesetId = (changeset: Changeset) => {
|
||||
const { repository } = this.props;
|
||||
return <ChangesetId changeset={changeset} repository={repository} />;
|
||||
};
|
||||
|
||||
render() {
|
||||
const { repository, changeset, classes } = this.props;
|
||||
const description = parseDescription(changeset.description);
|
||||
const changesetId = this.createChangesetId(changeset);
|
||||
const dateFromNow = <DateFromNow date={changeset.date} />;
|
||||
|
||||
return (
|
||||
<div className={classes.changeset}>
|
||||
<div className="columns is-gapless is-mobile">
|
||||
<div className="column is-three-fifths">
|
||||
<div className="columns is-gapless">
|
||||
<div className="column is-four-fifths">
|
||||
<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")}>
|
||||
<h4 className="has-text-weight-bold is-ellipsis-overflow">
|
||||
<ExtensionPoint
|
||||
name="changeset.description"
|
||||
props={{ changeset, value: description.title }}
|
||||
renderAll={false}
|
||||
>
|
||||
{description.title}
|
||||
</ExtensionPoint>
|
||||
</h4>
|
||||
<p className="is-hidden-touch">
|
||||
<Interpolate
|
||||
i18nKey="changeset.summary"
|
||||
id={changesetId}
|
||||
time={dateFromNow}
|
||||
/>
|
||||
</p>
|
||||
<p className="is-hidden-desktop">
|
||||
<Interpolate
|
||||
i18nKey="changeset.shortSummary"
|
||||
id={changesetId}
|
||||
time={dateFromNow}
|
||||
/>
|
||||
</p>
|
||||
<p className={classNames("is-size-7", classes.authorMargin)}>
|
||||
<ChangesetAuthor changeset={changeset} />
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={classNames("column", classes.isVcentered)}>
|
||||
<ChangesetTags changeset={changeset} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={classNames("column", classes.flexVcenter)}>
|
||||
<ChangesetButtonGroup
|
||||
repository={repository}
|
||||
changeset={changeset}
|
||||
/>
|
||||
<ExtensionPoint
|
||||
name="changeset.right"
|
||||
props={{ repository, changeset }}
|
||||
renderAll={true}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default injectSheet(styles)(translate("repos")(ChangesetRow));
|
||||
17
scm-ui/ui-components/src/repos/changesets/ChangesetTag.js
Normal file
17
scm-ui/ui-components/src/repos/changesets/ChangesetTag.js
Normal file
@@ -0,0 +1,17 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import type { Tag } from "@scm-manager/ui-types";
|
||||
import ChangesetTagBase from "./ChangesetTagBase";
|
||||
|
||||
type Props = {
|
||||
tag: Tag
|
||||
};
|
||||
|
||||
class ChangesetTag extends React.Component<Props> {
|
||||
render() {
|
||||
const { tag } = this.props;
|
||||
return <ChangesetTagBase icon="tag" label={tag.name} />;
|
||||
}
|
||||
}
|
||||
|
||||
export default ChangesetTag;
|
||||
@@ -0,0 +1,19 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import Tag from "../../Tag";
|
||||
|
||||
type Props = {
|
||||
icon: string,
|
||||
label: string
|
||||
};
|
||||
|
||||
class ChangesetTagBase extends React.Component<Props> {
|
||||
render() {
|
||||
const { icon, label } = this.props;
|
||||
return (
|
||||
<Tag color="info" icon={icon} label={label} />
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default ChangesetTagBase;
|
||||
31
scm-ui/ui-components/src/repos/changesets/ChangesetTags.js
Normal file
31
scm-ui/ui-components/src/repos/changesets/ChangesetTags.js
Normal 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;
|
||||
@@ -0,0 +1,30 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import { translate } from "react-i18next";
|
||||
import type { Tag } from "@scm-manager/ui-types";
|
||||
import Tooltip from "../../Tooltip";
|
||||
import ChangesetTagBase from "./ChangesetTagBase";
|
||||
|
||||
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="tags"
|
||||
label={tags.length + " " + t("changeset.tags")}
|
||||
/>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default translate("repos")(ChangesetTagsCollapsed);
|
||||
35
scm-ui/ui-components/src/repos/changesets/changesets.js
Normal file
35
scm-ui/ui-components/src/repos/changesets/changesets.js
Normal file
@@ -0,0 +1,35 @@
|
||||
// @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");
|
||||
|
||||
let title;
|
||||
let message = "";
|
||||
|
||||
if (lineBreak > 0) {
|
||||
title = desc.substring(0, lineBreak);
|
||||
message = desc.substring(lineBreak + 1);
|
||||
} else {
|
||||
title = desc;
|
||||
}
|
||||
|
||||
return {
|
||||
title,
|
||||
message
|
||||
};
|
||||
}
|
||||
22
scm-ui/ui-components/src/repos/changesets/changesets.test.js
Normal file
22
scm-ui/ui-components/src/repos/changesets/changesets.test.js
Normal file
@@ -0,0 +1,22 @@
|
||||
// @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");
|
||||
});
|
||||
|
||||
it("should return an empty description for undefined", () => {
|
||||
const desc = parseDescription();
|
||||
expect(desc.title).toBe("");
|
||||
expect(desc.message).toBe("");
|
||||
});
|
||||
});
|
||||
13
scm-ui/ui-components/src/repos/changesets/index.js
Normal file
13
scm-ui/ui-components/src/repos/changesets/index.js
Normal file
@@ -0,0 +1,13 @@
|
||||
// @flow
|
||||
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";
|
||||
Reference in New Issue
Block a user