mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-18 11:11:07 +01:00
scm-ui: new repository layout
This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import { ExtensionPoint } from "@scm-manager/ui-extensions";
|
||||
import type { Repository } from "@scm-manager/ui-types";
|
||||
import { Image } from "@scm-manager/ui-components";
|
||||
|
||||
type Props = {
|
||||
repository: Repository
|
||||
};
|
||||
|
||||
class RepositoryAvatar extends React.Component<Props> {
|
||||
render() {
|
||||
const { repository } = this.props;
|
||||
return (
|
||||
<p className="image is-64x64">
|
||||
<ExtensionPoint name="repos.repository-avatar" props={{ repository }}>
|
||||
<Image src="/images/blib.jpg" alt="Logo" />
|
||||
</ExtensionPoint>
|
||||
</p>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default RepositoryAvatar;
|
||||
102
scm-ui/ui-webapp/src/repos/components/list/RepositoryEntry.js
Normal file
102
scm-ui/ui-webapp/src/repos/components/list/RepositoryEntry.js
Normal file
@@ -0,0 +1,102 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import type { Repository } from "@scm-manager/ui-types";
|
||||
import { CardColumn, DateFromNow } from "@scm-manager/ui-components";
|
||||
import RepositoryEntryLink from "./RepositoryEntryLink";
|
||||
import RepositoryAvatar from "./RepositoryAvatar";
|
||||
|
||||
type Props = {
|
||||
repository: Repository
|
||||
};
|
||||
|
||||
class RepositoryEntry extends React.Component<Props> {
|
||||
createLink = (repository: Repository) => {
|
||||
return `/repo/${repository.namespace}/${repository.name}`;
|
||||
};
|
||||
|
||||
renderBranchesLink = (repository: Repository, repositoryLink: string) => {
|
||||
if (repository._links["branches"]) {
|
||||
return (
|
||||
<RepositoryEntryLink
|
||||
iconClass="fas fa-code-branch fa-lg"
|
||||
to={repositoryLink + "/branches"}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
renderChangesetsLink = (repository: Repository, repositoryLink: string) => {
|
||||
if (repository._links["changesets"]) {
|
||||
return (
|
||||
<RepositoryEntryLink
|
||||
iconClass="fas fa-exchange-alt fa-lg"
|
||||
to={repositoryLink + "/changesets"}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
renderSourcesLink = (repository: Repository, repositoryLink: string) => {
|
||||
if (repository._links["sources"]) {
|
||||
return (
|
||||
<RepositoryEntryLink
|
||||
iconClass="fa-code fa-lg"
|
||||
to={repositoryLink + "/sources"}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
renderModifyLink = (repository: Repository, repositoryLink: string) => {
|
||||
if (repository._links["update"]) {
|
||||
return (
|
||||
<RepositoryEntryLink
|
||||
iconClass="fa-cog fa-lg"
|
||||
to={repositoryLink + "/settings/general"}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
createFooterLeft = (repository: Repository, repositoryLink: string) => {
|
||||
return (
|
||||
<>
|
||||
{this.renderBranchesLink(repository, repositoryLink)}
|
||||
{this.renderChangesetsLink(repository, repositoryLink)}
|
||||
{this.renderSourcesLink(repository, repositoryLink)}
|
||||
{this.renderModifyLink(repository, repositoryLink)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
createFooterRight = (repository: Repository) => {
|
||||
return (
|
||||
<small className="level-item">
|
||||
<DateFromNow date={repository.creationDate} />
|
||||
</small>
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { repository } = this.props;
|
||||
const repositoryLink = this.createLink(repository);
|
||||
const footerLeft = this.createFooterLeft(repository, repositoryLink);
|
||||
const footerRight = this.createFooterRight(repository);
|
||||
return (
|
||||
<CardColumn
|
||||
avatar={<RepositoryAvatar repository={repository} />}
|
||||
title={repository.name}
|
||||
description={repository.description}
|
||||
link={repositoryLink}
|
||||
footerLeft={footerLeft}
|
||||
footerRight={footerRight}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default RepositoryEntry;
|
||||
@@ -0,0 +1,35 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import injectSheet from "react-jss";
|
||||
import classNames from "classnames";
|
||||
|
||||
const styles = {
|
||||
link: {
|
||||
pointerEvents: "all",
|
||||
marginRight: "1.25rem !important"
|
||||
}
|
||||
};
|
||||
|
||||
type Props = {
|
||||
to: string,
|
||||
iconClass: string,
|
||||
|
||||
// context props
|
||||
classes: any
|
||||
};
|
||||
|
||||
class RepositoryEntryLink extends React.Component<Props> {
|
||||
render() {
|
||||
const { to, iconClass, classes } = this.props;
|
||||
return (
|
||||
<Link className={classNames("level-item", classes.link)} to={to}>
|
||||
<span className="icon is-small">
|
||||
<i className={classNames("fa", iconClass)} />
|
||||
</span>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default injectSheet(styles)(RepositoryEntryLink);
|
||||
@@ -0,0 +1,21 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import { CardColumnGroup } from "@scm-manager/ui-components";
|
||||
import type { RepositoryGroup } from "@scm-manager/ui-types";
|
||||
import RepositoryEntry from "./RepositoryEntry";
|
||||
|
||||
type Props = {
|
||||
group: RepositoryGroup
|
||||
};
|
||||
|
||||
class RepositoryGroupEntry extends React.Component<Props> {
|
||||
render() {
|
||||
const { group } = this.props;
|
||||
const entries = group.repositories.map((repository, index) => {
|
||||
return <RepositoryEntry repository={repository} key={index} />;
|
||||
});
|
||||
return <CardColumnGroup name={group.name} elements={entries} />;
|
||||
}
|
||||
}
|
||||
|
||||
export default RepositoryGroupEntry;
|
||||
28
scm-ui/ui-webapp/src/repos/components/list/RepositoryList.js
Normal file
28
scm-ui/ui-webapp/src/repos/components/list/RepositoryList.js
Normal file
@@ -0,0 +1,28 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
|
||||
import type { Repository } from "@scm-manager/ui-types";
|
||||
|
||||
import groupByNamespace from "./groupByNamespace";
|
||||
import RepositoryGroupEntry from "./RepositoryGroupEntry";
|
||||
|
||||
type Props = {
|
||||
repositories: Repository[]
|
||||
};
|
||||
|
||||
class RepositoryList extends React.Component<Props> {
|
||||
render() {
|
||||
const { repositories } = this.props;
|
||||
|
||||
const groups = groupByNamespace(repositories);
|
||||
return (
|
||||
<div className="content">
|
||||
{groups.map(group => {
|
||||
return <RepositoryGroupEntry group={group} key={group.name} />;
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default RepositoryList;
|
||||
@@ -0,0 +1,39 @@
|
||||
// @flow
|
||||
import type { Repository, RepositoryGroup } from "@scm-manager/ui-types";
|
||||
|
||||
export default function groupByNamespace(
|
||||
repositories: Repository[]
|
||||
): RepositoryGroup[] {
|
||||
let groups = {};
|
||||
for (let repository of repositories) {
|
||||
const groupName = repository.namespace;
|
||||
|
||||
let group = groups[groupName];
|
||||
if (!group) {
|
||||
group = {
|
||||
name: groupName,
|
||||
repositories: []
|
||||
};
|
||||
groups[groupName] = group;
|
||||
}
|
||||
group.repositories.push(repository);
|
||||
}
|
||||
|
||||
let groupArray = [];
|
||||
for (let groupName in groups) {
|
||||
const group = groups[groupName];
|
||||
group.repositories.sort(sortByName);
|
||||
groupArray.push(groups[groupName]);
|
||||
}
|
||||
groupArray.sort(sortByName);
|
||||
return groupArray;
|
||||
}
|
||||
|
||||
function sortByName(a, b) {
|
||||
if (a.name < b.name) {
|
||||
return -1;
|
||||
} else if (a.name > b.name) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
// @flow
|
||||
import groupByNamespace from "./groupByNamespace";
|
||||
|
||||
const base = {
|
||||
type: "git",
|
||||
_links: {}
|
||||
};
|
||||
|
||||
const slartiBlueprintsFjords = {
|
||||
...base,
|
||||
namespace: "slarti",
|
||||
name: "fjords-blueprints"
|
||||
};
|
||||
|
||||
const slartiFjords = {
|
||||
...base,
|
||||
namespace: "slarti",
|
||||
name: "fjords"
|
||||
};
|
||||
|
||||
const hitchhikerRestand = {
|
||||
...base,
|
||||
namespace: "hitchhiker",
|
||||
name: "restand"
|
||||
};
|
||||
const hitchhikerPuzzle42 = {
|
||||
...base,
|
||||
namespace: "hitchhiker",
|
||||
name: "puzzle42"
|
||||
};
|
||||
|
||||
const hitchhikerHeartOfGold = {
|
||||
...base,
|
||||
namespace: "hitchhiker",
|
||||
name: "heartOfGold"
|
||||
};
|
||||
|
||||
const zaphodMarvinFirmware = {
|
||||
...base,
|
||||
namespace: "zaphod",
|
||||
name: "marvin-firmware"
|
||||
};
|
||||
|
||||
it("should group the repositories by their namespace", () => {
|
||||
const repositories = [
|
||||
zaphodMarvinFirmware,
|
||||
slartiBlueprintsFjords,
|
||||
hitchhikerRestand,
|
||||
slartiFjords,
|
||||
hitchhikerHeartOfGold,
|
||||
hitchhikerPuzzle42
|
||||
];
|
||||
|
||||
const expected = [
|
||||
{
|
||||
name: "hitchhiker",
|
||||
repositories: [
|
||||
hitchhikerHeartOfGold,
|
||||
hitchhikerPuzzle42,
|
||||
hitchhikerRestand
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "slarti",
|
||||
repositories: [slartiFjords, slartiBlueprintsFjords]
|
||||
},
|
||||
{
|
||||
name: "zaphod",
|
||||
repositories: [zaphodMarvinFirmware]
|
||||
}
|
||||
];
|
||||
|
||||
expect(groupByNamespace(repositories)).toEqual(expected);
|
||||
});
|
||||
2
scm-ui/ui-webapp/src/repos/components/list/index.js
Normal file
2
scm-ui/ui-webapp/src/repos/components/list/index.js
Normal file
@@ -0,0 +1,2 @@
|
||||
import RepositoryList from "./RepositoryList";
|
||||
export default RepositoryList;
|
||||
Reference in New Issue
Block a user