mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-14 09:25:43 +01:00
merged
This commit is contained in:
87
scm-ui-components/packages/ui-components/src/CardColumn.js
Normal file
87
scm-ui-components/packages/ui-components/src/CardColumn.js
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
//@flow
|
||||||
|
import * as React from "react";
|
||||||
|
import injectSheet from "react-jss";
|
||||||
|
import classNames from "classnames";
|
||||||
|
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
|
|
||||||
|
const styles = {
|
||||||
|
inner: {
|
||||||
|
position: "relative",
|
||||||
|
pointerEvents: "none",
|
||||||
|
zIndex: 1
|
||||||
|
},
|
||||||
|
innerLink: {
|
||||||
|
pointerEvents: "all"
|
||||||
|
},
|
||||||
|
centerImage: {
|
||||||
|
marginTop: "0.8em",
|
||||||
|
marginLeft: "1em !important"
|
||||||
|
},
|
||||||
|
flexFullHeight: {
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
alignSelf: "stretch"
|
||||||
|
},
|
||||||
|
content: {
|
||||||
|
display: "flex",
|
||||||
|
flexGrow: 1
|
||||||
|
},
|
||||||
|
footer: {
|
||||||
|
display: "flex",
|
||||||
|
marginTop: "auto",
|
||||||
|
paddingBottom: "1.5rem"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
title: string,
|
||||||
|
description: string,
|
||||||
|
avatar: React.Node,
|
||||||
|
footerLeft: React.Node,
|
||||||
|
footerRight: React.Node,
|
||||||
|
link: string,
|
||||||
|
// context props
|
||||||
|
classes: any
|
||||||
|
};
|
||||||
|
|
||||||
|
class CardColumn extends React.Component<Props> {
|
||||||
|
createLink = () => {
|
||||||
|
const { link } = this.props;
|
||||||
|
if (link) {
|
||||||
|
return <Link className="overlay-column" to={link} />;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { avatar, title, description, footerLeft, footerRight, classes } = this.props;
|
||||||
|
const link = this.createLink();
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{link}
|
||||||
|
<article className={classNames("media", classes.inner)}>
|
||||||
|
<figure className={classNames(classes.centerImage, "media-left")}>
|
||||||
|
{avatar}
|
||||||
|
</figure>
|
||||||
|
<div className={classNames("media-content", "text-box", classes.flexFullHeight)}>
|
||||||
|
<div className={classes.content}>
|
||||||
|
<div className="content shorten-text">
|
||||||
|
<p className="is-marginless">
|
||||||
|
<strong>{title}</strong>
|
||||||
|
</p>
|
||||||
|
<p className="shorten-text">{description}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className={classNames(classes.footer, "level")}>
|
||||||
|
<div className="level-left is-hidden-mobile">{footerLeft}</div>
|
||||||
|
<div className="level-right is-mobile">{footerRight}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default injectSheet(styles)(CardColumn);
|
||||||
103
scm-ui-components/packages/ui-components/src/CardColumnGroup.js
Normal file
103
scm-ui-components/packages/ui-components/src/CardColumnGroup.js
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
//@flow
|
||||||
|
import * as React from "react";
|
||||||
|
import injectSheet from "react-jss";
|
||||||
|
import classNames from "classnames";
|
||||||
|
|
||||||
|
const styles = {
|
||||||
|
pointer: {
|
||||||
|
cursor: "pointer",
|
||||||
|
fontSize: "1.5rem"
|
||||||
|
},
|
||||||
|
repoGroup: {
|
||||||
|
marginBottom: "1em"
|
||||||
|
},
|
||||||
|
wrapper: {
|
||||||
|
padding: "0 0.75rem"
|
||||||
|
},
|
||||||
|
clearfix: {
|
||||||
|
clear: "both"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
name: string,
|
||||||
|
elements: React.Node[],
|
||||||
|
|
||||||
|
// context props
|
||||||
|
classes: any
|
||||||
|
};
|
||||||
|
|
||||||
|
type State = {
|
||||||
|
collapsed: boolean
|
||||||
|
};
|
||||||
|
|
||||||
|
class CardColumnGroup extends React.Component<Props, State> {
|
||||||
|
constructor(props: Props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
collapsed: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleCollapse = () => {
|
||||||
|
this.setState(prevState => ({
|
||||||
|
collapsed: !prevState.collapsed
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
isLastEntry = (array: React.Node[], index: number) => {
|
||||||
|
return index === array.length - 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
isLengthOdd = (array: React.Node[]) => {
|
||||||
|
return array.length % 2 !== 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
isFullSize = (array: React.Node[], index: number) => {
|
||||||
|
return this.isLastEntry(array, index) && this.isLengthOdd(array);
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { name, elements, classes } = this.props;
|
||||||
|
const { collapsed } = this.state;
|
||||||
|
|
||||||
|
const icon = collapsed ? "fa-angle-right" : "fa-angle-down";
|
||||||
|
let content = null;
|
||||||
|
if (!collapsed) {
|
||||||
|
content = elements.map((entry, index) => {
|
||||||
|
const fullColumnWidth = this.isFullSize(elements, index);
|
||||||
|
const sizeClass = fullColumnWidth ? "is-full" : "is-half";
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={classNames(
|
||||||
|
"box",
|
||||||
|
"box-link-shadow",
|
||||||
|
"column",
|
||||||
|
"is-clipped",
|
||||||
|
sizeClass
|
||||||
|
)}
|
||||||
|
key={index}
|
||||||
|
>
|
||||||
|
{entry}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div className={classes.repoGroup}>
|
||||||
|
<h2>
|
||||||
|
<span className={classes.pointer} onClick={this.toggleCollapse}>
|
||||||
|
<i className={classNames("fa", icon)} /> {name}
|
||||||
|
</span>
|
||||||
|
</h2>
|
||||||
|
<hr />
|
||||||
|
<div className={classNames("columns", "is-multiline", classes.wrapper)}>
|
||||||
|
{content}
|
||||||
|
</div>
|
||||||
|
<div className={classes.clearfix} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default injectSheet(styles)(CardColumnGroup);
|
||||||
@@ -32,6 +32,8 @@ export { default as MarkdownView } from "./MarkdownView";
|
|||||||
export { default as SyntaxHighlighter } from "./SyntaxHighlighter";
|
export { default as SyntaxHighlighter } from "./SyntaxHighlighter";
|
||||||
export { default as ErrorBoundary } from "./ErrorBoundary";
|
export { default as ErrorBoundary } from "./ErrorBoundary";
|
||||||
export { default as OverviewPageActions } from "./OverviewPageActions.js";
|
export { default as OverviewPageActions } from "./OverviewPageActions.js";
|
||||||
|
export { default as CardColumnGroup } from "./CardColumnGroup";
|
||||||
|
export { default as CardColumn } from "./CardColumn";
|
||||||
|
|
||||||
export { apiClient } from "./apiclient.js";
|
export { apiClient } from "./apiclient.js";
|
||||||
export * from "./errors";
|
export * from "./errors";
|
||||||
|
|||||||
@@ -1,86 +1,44 @@
|
|||||||
//@flow
|
//@flow
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { Link } from "react-router-dom";
|
|
||||||
import injectSheet from "react-jss";
|
|
||||||
import classNames from "classnames";
|
|
||||||
import type { Plugin } from "@scm-manager/ui-types";
|
import type { Plugin } from "@scm-manager/ui-types";
|
||||||
|
import { CardColumn } from "@scm-manager/ui-components";
|
||||||
import PluginAvatar from "./PluginAvatar";
|
import PluginAvatar from "./PluginAvatar";
|
||||||
|
|
||||||
const styles = {
|
|
||||||
inner: {
|
|
||||||
position: "relative",
|
|
||||||
pointerEvents: "none",
|
|
||||||
zIndex: 1
|
|
||||||
},
|
|
||||||
centerImage: {
|
|
||||||
marginTop: "0.8em",
|
|
||||||
marginLeft: "1em !important"
|
|
||||||
},
|
|
||||||
marginBottom: {
|
|
||||||
marginBottom: "0.75rem !important"
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
plugin: Plugin,
|
plugin: Plugin
|
||||||
fullColumnWidth?: boolean,
|
|
||||||
|
|
||||||
// context props
|
|
||||||
classes: any
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class PluginEntry extends React.Component<Props> {
|
class PluginEntry extends React.Component<Props> {
|
||||||
|
createAvatar = (plugin: Plugin) => {
|
||||||
|
return <PluginAvatar plugin={plugin} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
createFooterLeft = (plugin: Plugin) => {
|
||||||
|
return <small className="level-item">{plugin.author}</small>;
|
||||||
|
};
|
||||||
|
|
||||||
|
createFooterRight = (plugin: Plugin) => {
|
||||||
|
return <p className="level-item">{plugin.version}</p>;
|
||||||
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { plugin, classes, fullColumnWidth } = this.props;
|
const { plugin } = this.props;
|
||||||
const halfColumn = fullColumnWidth ? "is-full" : "is-half";
|
const avatar = this.createAvatar(plugin);
|
||||||
const overlayLinkClass = fullColumnWidth
|
const footerLeft = this.createFooterLeft(plugin);
|
||||||
? "overlay-full-column"
|
const footerRight = this.createFooterRight(plugin);
|
||||||
: "overlay-half-column";
|
|
||||||
// TODO: Add link to plugin page below
|
// TODO: Add link to plugin page below
|
||||||
return (
|
return (
|
||||||
<div
|
<CardColumn
|
||||||
className={classNames(
|
link="#"
|
||||||
"box",
|
avatar={avatar}
|
||||||
"box-link-shadow",
|
title={plugin.name}
|
||||||
"column",
|
description={plugin.description}
|
||||||
"is-clipped",
|
footerLeft={footerLeft}
|
||||||
halfColumn
|
footerRight={footerRight}
|
||||||
)}
|
|
||||||
>
|
|
||||||
<Link
|
|
||||||
className={classNames(overlayLinkClass, "is-plugin-page")}
|
|
||||||
to="#"
|
|
||||||
/>
|
/>
|
||||||
<article className={classNames("media", classes.inner)}>
|
|
||||||
<figure className={classNames(classes.centerImage, "media-left")}>
|
|
||||||
<PluginAvatar plugin={plugin} />
|
|
||||||
</figure>
|
|
||||||
<div className={classNames("media-content", "text-box")}>
|
|
||||||
<div className="content">
|
|
||||||
<nav
|
|
||||||
className={classNames(
|
|
||||||
"level",
|
|
||||||
"is-mobile",
|
|
||||||
classes.marginBottom
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<div className="level-left">
|
|
||||||
<strong>{plugin.name}</strong>
|
|
||||||
</div>
|
|
||||||
<div className="level-right is-hidden-mobile">
|
|
||||||
{plugin.version}
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
<p className="shorten-text is-marginless">{plugin.description}</p>
|
|
||||||
<p>
|
|
||||||
<small>{plugin.author}</small>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</article>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default injectSheet(styles)(PluginEntry);
|
export default PluginEntry;
|
||||||
|
|||||||
@@ -1,96 +1,21 @@
|
|||||||
//@flow
|
//@flow
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import injectSheet from "react-jss";
|
import { CardColumnGroup } from "@scm-manager/ui-components";
|
||||||
import classNames from "classnames";
|
import type { PluginGroup } from "@scm-manager/ui-types";
|
||||||
import type { PluginGroup, Plugin } from "@scm-manager/ui-types";
|
|
||||||
import PluginEntry from "./PluginEntry";
|
import PluginEntry from "./PluginEntry";
|
||||||
|
|
||||||
const styles = {
|
|
||||||
pointer: {
|
|
||||||
cursor: "pointer",
|
|
||||||
fontSize: "1.5rem"
|
|
||||||
},
|
|
||||||
pluginGroup: {
|
|
||||||
marginBottom: "1em"
|
|
||||||
},
|
|
||||||
wrapper: {
|
|
||||||
padding: "0 0.75rem"
|
|
||||||
},
|
|
||||||
clearfix: {
|
|
||||||
clear: "both"
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
group: PluginGroup,
|
group: PluginGroup
|
||||||
|
|
||||||
// context props
|
|
||||||
classes: any
|
|
||||||
};
|
|
||||||
|
|
||||||
type State = {
|
|
||||||
collapsed: boolean
|
|
||||||
};
|
|
||||||
|
|
||||||
class PluginGroupEntry extends React.Component<Props, State> {
|
|
||||||
constructor(props: Props) {
|
|
||||||
super(props);
|
|
||||||
this.state = {
|
|
||||||
collapsed: false
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
toggleCollapse = () => {
|
|
||||||
this.setState(prevState => ({
|
|
||||||
collapsed: !prevState.collapsed
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
|
|
||||||
isLastEntry = (array: Plugin[], index: number) => {
|
|
||||||
return index === array.length - 1;
|
|
||||||
};
|
|
||||||
|
|
||||||
isLengthOdd = (array: Plugin[]) => {
|
|
||||||
return array.length % 2 !== 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
isFullSize = (array: Plugin[], index: number) => {
|
|
||||||
return this.isLastEntry(array, index) && this.isLengthOdd(array);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class PluginGroupEntry extends React.Component<Props> {
|
||||||
render() {
|
render() {
|
||||||
const { group, classes } = this.props;
|
const { group } = this.props;
|
||||||
const { collapsed } = this.state;
|
const entries = group.plugins.map((plugin, index) => {
|
||||||
|
return <PluginEntry plugin={plugin} key={index} />;
|
||||||
const icon = collapsed ? "fa-angle-right" : "fa-angle-down";
|
|
||||||
let content = null;
|
|
||||||
if (!collapsed) {
|
|
||||||
content = group.plugins.map((plugin, index) => {
|
|
||||||
const fullColumnWidth = this.isFullSize(group.plugins, index);
|
|
||||||
return (
|
|
||||||
<PluginEntry
|
|
||||||
plugin={plugin}
|
|
||||||
fullColumnWidth={fullColumnWidth}
|
|
||||||
key={index}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
}
|
return <CardColumnGroup name={group.name} elements={entries} />;
|
||||||
return (
|
|
||||||
<div className={classes.pluginGroup}>
|
|
||||||
<h2>
|
|
||||||
<span className={classes.pointer} onClick={this.toggleCollapse}>
|
|
||||||
<i className={classNames("fa", icon)} /> {group.name}
|
|
||||||
</span>
|
|
||||||
</h2>
|
|
||||||
<hr />
|
|
||||||
<div className={classNames("columns", "is-multiline", classes.wrapper)}>
|
|
||||||
{content}
|
|
||||||
</div>
|
|
||||||
<div className={classes.clearfix} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default injectSheet(styles)(PluginGroupEntry);
|
export default PluginGroupEntry;
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ class PluginList extends React.Component<Props> {
|
|||||||
|
|
||||||
const groups = groupByCategory(plugins);
|
const groups = groupByCategory(plugins);
|
||||||
return (
|
return (
|
||||||
<div className="content">
|
<div className="content is-plugin-page">
|
||||||
{groups.map(group => {
|
{groups.map(group => {
|
||||||
return <PluginGroupEntry group={group} key={group.name} />;
|
return <PluginGroupEntry group={group} key={group.name} />;
|
||||||
})}
|
})}
|
||||||
|
|||||||
@@ -144,8 +144,7 @@ export default function reducer(
|
|||||||
|
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case FETCH_PLUGINS_SUCCESS:
|
case FETCH_PLUGINS_SUCCESS:
|
||||||
const t = normalizeByName(action.payload);
|
return normalizeByName(action.payload);
|
||||||
return t;
|
|
||||||
case FETCH_PLUGIN_SUCCESS:
|
case FETCH_PLUGIN_SUCCESS:
|
||||||
return reducerByNames(state, action.payload);
|
return reducerByNames(state, action.payload);
|
||||||
default:
|
default:
|
||||||
@@ -190,8 +189,3 @@ export function isFetchPluginPending(state: Object, name: string) {
|
|||||||
export function getFetchPluginFailure(state: Object, name: string) {
|
export function getFetchPluginFailure(state: Object, name: string) {
|
||||||
return getFailure(state, FETCH_PLUGIN, name);
|
return getFailure(state, FETCH_PLUGIN, name);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getPermissionsLink(state: Object, name: string) {
|
|
||||||
const plugin = getPlugin(state, name);
|
|
||||||
return plugin && plugin._links ? plugin._links.permissions.href : undefined;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -23,10 +23,7 @@ import reducer, {
|
|||||||
isFetchPluginPending,
|
isFetchPluginPending,
|
||||||
getFetchPluginFailure
|
getFetchPluginFailure
|
||||||
} from "./plugins";
|
} from "./plugins";
|
||||||
import type {
|
import type { Plugin, PluginCollection } from "@scm-manager/ui-types";
|
||||||
Plugin,
|
|
||||||
PluginCollection
|
|
||||||
} from "@scm-manager/ui-types";
|
|
||||||
|
|
||||||
const groupManagerPlugin: Plugin = {
|
const groupManagerPlugin: Plugin = {
|
||||||
name: "scm-groupmanager-plugin",
|
name: "scm-groupmanager-plugin",
|
||||||
@@ -37,8 +34,7 @@ const groupManagerPlugin: Plugin = {
|
|||||||
description: "Notify a remote webserver whenever a plugin is pushed to.",
|
description: "Notify a remote webserver whenever a plugin is pushed to.",
|
||||||
_links: {
|
_links: {
|
||||||
self: {
|
self: {
|
||||||
href:
|
href: "http://localhost:8081/api/v2/ui/plugins/scm-groupmanager-plugin"
|
||||||
"http://localhost:8081/api/v2/ui/plugins/scm-groupmanager-plugin"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -52,8 +48,7 @@ const scriptPlugin: Plugin = {
|
|||||||
description: "Script support for scm-manager.",
|
description: "Script support for scm-manager.",
|
||||||
_links: {
|
_links: {
|
||||||
self: {
|
self: {
|
||||||
href:
|
href: "http://localhost:8081/api/v2/ui/plugins/scm-script-plugin"
|
||||||
"http://localhost:8081/api/v2/ui/plugins/scm-script-plugin"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -67,8 +62,7 @@ const branchwpPlugin: Plugin = {
|
|||||||
description: "This plugin adds branch write protection for plugins.",
|
description: "This plugin adds branch write protection for plugins.",
|
||||||
_links: {
|
_links: {
|
||||||
self: {
|
self: {
|
||||||
href:
|
href: "http://localhost:8081/api/v2/ui/plugins/scm-branchwp-plugin"
|
||||||
"http://localhost:8081/api/v2/ui/plugins/scm-branchwp-plugin"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -166,12 +160,9 @@ describe("plugins fetch", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should dispatch FETCH_PLUGIN_FAILURE, if the request for scm-groupmanager-plugin by name fails", () => {
|
it("should dispatch FETCH_PLUGIN_FAILURE, if the request for scm-groupmanager-plugin by name fails", () => {
|
||||||
fetchMock.getOnce(
|
fetchMock.getOnce(PLUGINS_URL + "/scm-groupmanager-plugin", {
|
||||||
PLUGINS_URL + "/scm-groupmanager-plugin",
|
|
||||||
{
|
|
||||||
status: 500
|
status: 500
|
||||||
}
|
});
|
||||||
);
|
|
||||||
|
|
||||||
const store = mockStore({});
|
const store = mockStore({});
|
||||||
return store
|
return store
|
||||||
@@ -331,8 +322,7 @@ describe("plugins selectors", () => {
|
|||||||
it("should return true, when fetch plugin is pending", () => {
|
it("should return true, when fetch plugin is pending", () => {
|
||||||
const state = {
|
const state = {
|
||||||
pending: {
|
pending: {
|
||||||
[FETCH_PLUGIN +
|
[FETCH_PLUGIN + "/scm-groupmanager-plugin"]: true
|
||||||
"/scm-groupmanager-plugin"]: true
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
expect(isFetchPluginPending(state, "scm-groupmanager-plugin")).toEqual(
|
expect(isFetchPluginPending(state, "scm-groupmanager-plugin")).toEqual(
|
||||||
@@ -347,8 +337,7 @@ describe("plugins selectors", () => {
|
|||||||
it("should return error when fetch plugin did fail", () => {
|
it("should return error when fetch plugin did fail", () => {
|
||||||
const state = {
|
const state = {
|
||||||
failure: {
|
failure: {
|
||||||
[FETCH_PLUGIN +
|
[FETCH_PLUGIN + "/scm-groupmanager-plugin"]: error
|
||||||
"/scm-groupmanager-plugin"]: error
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
expect(getFetchPluginFailure(state, "scm-groupmanager-plugin")).toEqual(
|
expect(getFetchPluginFailure(state, "scm-groupmanager-plugin")).toEqual(
|
||||||
|
|||||||
@@ -1,33 +1,12 @@
|
|||||||
//@flow
|
//@flow
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { Link } from "react-router-dom";
|
|
||||||
import injectSheet from "react-jss";
|
|
||||||
import type { Repository } from "@scm-manager/ui-types";
|
import type { Repository } from "@scm-manager/ui-types";
|
||||||
import { DateFromNow } from "@scm-manager/ui-components";
|
import { CardColumn, DateFromNow } from "@scm-manager/ui-components";
|
||||||
import RepositoryEntryLink from "./RepositoryEntryLink";
|
import RepositoryEntryLink from "./RepositoryEntryLink";
|
||||||
import classNames from "classnames";
|
|
||||||
import RepositoryAvatar from "./RepositoryAvatar";
|
import RepositoryAvatar from "./RepositoryAvatar";
|
||||||
|
|
||||||
const styles = {
|
|
||||||
inner: {
|
|
||||||
position: "relative",
|
|
||||||
pointerEvents: "none",
|
|
||||||
zIndex: 1
|
|
||||||
},
|
|
||||||
innerLink: {
|
|
||||||
pointerEvents: "all"
|
|
||||||
},
|
|
||||||
centerImage: {
|
|
||||||
marginTop: "0.8em",
|
|
||||||
marginLeft: "1em !important"
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
repository: Repository,
|
repository: Repository
|
||||||
fullColumnWidth?: boolean,
|
|
||||||
// context props
|
|
||||||
classes: any
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class RepositoryEntry extends React.Component<Props> {
|
class RepositoryEntry extends React.Component<Props> {
|
||||||
@@ -83,53 +62,41 @@ class RepositoryEntry extends React.Component<Props> {
|
|||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
createFooterLeft = (repository: Repository, repositoryLink: string) => {
|
||||||
const { repository, classes, fullColumnWidth } = this.props;
|
|
||||||
const repositoryLink = this.createLink(repository);
|
|
||||||
const halfColumn = fullColumnWidth ? "is-full" : "is-half";
|
|
||||||
const overlayLinkClass = fullColumnWidth
|
|
||||||
? "overlay-full-column"
|
|
||||||
: "overlay-half-column";
|
|
||||||
return (
|
return (
|
||||||
<div
|
<>
|
||||||
className={classNames(
|
|
||||||
"box",
|
|
||||||
"box-link-shadow",
|
|
||||||
"column",
|
|
||||||
"is-clipped",
|
|
||||||
halfColumn
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<Link className={classNames(overlayLinkClass)} to={repositoryLink} />
|
|
||||||
<article className={classNames("media", classes.inner)}>
|
|
||||||
<figure className={classNames(classes.centerImage, "media-left")}>
|
|
||||||
<RepositoryAvatar repository={repository} />
|
|
||||||
</figure>
|
|
||||||
<div className={classNames("media-content", "text-box")}>
|
|
||||||
<div className="content">
|
|
||||||
<p className="is-marginless">
|
|
||||||
<strong>{repository.name}</strong>
|
|
||||||
</p>
|
|
||||||
<p className="shorten-text">{repository.description}</p>
|
|
||||||
</div>
|
|
||||||
<nav className="level is-mobile">
|
|
||||||
<div className="level-left">
|
|
||||||
{this.renderBranchesLink(repository, repositoryLink)}
|
{this.renderBranchesLink(repository, repositoryLink)}
|
||||||
{this.renderChangesetsLink(repository, repositoryLink)}
|
{this.renderChangesetsLink(repository, repositoryLink)}
|
||||||
{this.renderSourcesLink(repository, repositoryLink)}
|
{this.renderSourcesLink(repository, repositoryLink)}
|
||||||
{this.renderModifyLink(repository, repositoryLink)}
|
{this.renderModifyLink(repository, repositoryLink)}
|
||||||
</div>
|
</>
|
||||||
<div className="level-right is-hidden-mobile">
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
createFooterRight = (repository: Repository) => {
|
||||||
|
return (
|
||||||
<small className="level-item">
|
<small className="level-item">
|
||||||
<DateFromNow date={repository.creationDate} />
|
<DateFromNow date={repository.creationDate} />
|
||||||
</small>
|
</small>
|
||||||
</div>
|
);
|
||||||
</nav>
|
};
|
||||||
</div>
|
|
||||||
</article>
|
render() {
|
||||||
</div>
|
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 injectSheet(styles)(RepositoryEntry);
|
export default RepositoryEntry;
|
||||||
|
|||||||
@@ -1,92 +1,21 @@
|
|||||||
//@flow
|
//@flow
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import type { RepositoryGroup, Repository } from "@scm-manager/ui-types";
|
import { CardColumnGroup } from "@scm-manager/ui-components";
|
||||||
import injectSheet from "react-jss";
|
import type { RepositoryGroup } from "@scm-manager/ui-types";
|
||||||
import classNames from "classnames";
|
|
||||||
import RepositoryEntry from "./RepositoryEntry";
|
import RepositoryEntry from "./RepositoryEntry";
|
||||||
|
|
||||||
const styles = {
|
|
||||||
pointer: {
|
|
||||||
cursor: "pointer",
|
|
||||||
fontSize: "1.5rem"
|
|
||||||
},
|
|
||||||
repoGroup: {
|
|
||||||
marginBottom: "1em"
|
|
||||||
},
|
|
||||||
wrapper: {
|
|
||||||
padding: "0 0.75rem"
|
|
||||||
},
|
|
||||||
clearfix: {
|
|
||||||
clear: "both"
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
group: RepositoryGroup,
|
group: RepositoryGroup
|
||||||
|
|
||||||
// context props
|
|
||||||
classes: any
|
|
||||||
};
|
|
||||||
|
|
||||||
type State = {
|
|
||||||
collapsed: boolean
|
|
||||||
};
|
|
||||||
|
|
||||||
class RepositoryGroupEntry extends React.Component<Props, State> {
|
|
||||||
constructor(props: Props) {
|
|
||||||
super(props);
|
|
||||||
this.state = {
|
|
||||||
collapsed: false
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
toggleCollapse = () => {
|
|
||||||
this.setState(prevState => ({
|
|
||||||
collapsed: !prevState.collapsed
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
|
|
||||||
isLastEntry = (array: Repository[], index: number) => {
|
|
||||||
return index === array.length - 1;
|
|
||||||
};
|
|
||||||
|
|
||||||
isLengthOdd = (array: Repository[]) => {
|
|
||||||
return array.length % 2 !== 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
isFullSize = (array: Repository[], index: number) => {
|
|
||||||
return this.isLastEntry(array, index) && this.isLengthOdd(array);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class RepositoryGroupEntry extends React.Component<Props> {
|
||||||
render() {
|
render() {
|
||||||
const { group, classes } = this.props;
|
const { group } = this.props;
|
||||||
const { collapsed } = this.state;
|
const entries = group.repositories.map((repository, index) => {
|
||||||
|
return <RepositoryEntry repository={repository} key={index} />;
|
||||||
const icon = collapsed ? "fa-angle-right" : "fa-angle-down";
|
|
||||||
let content = null;
|
|
||||||
if (!collapsed) {
|
|
||||||
content = group.repositories.map((repository, index) => {
|
|
||||||
const fullColumnWidth = this.isFullSize(group.repositories, index);
|
|
||||||
return (
|
|
||||||
<RepositoryEntry repository={repository} fullColumnWidth={fullColumnWidth} key={index} />
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
}
|
return <CardColumnGroup name={group.name} elements={entries} />;
|
||||||
return (
|
|
||||||
<div className={classes.repoGroup}>
|
|
||||||
<h2>
|
|
||||||
<span className={classes.pointer} onClick={this.toggleCollapse}>
|
|
||||||
<i className={classNames("fa", icon)} /> {group.name}
|
|
||||||
</span>
|
|
||||||
</h2>
|
|
||||||
<hr />
|
|
||||||
<div className={classNames("columns", "is-multiline", classes.wrapper)}>
|
|
||||||
{content}
|
|
||||||
</div>
|
|
||||||
<div className={classes.clearfix} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default injectSheet(styles)(RepositoryGroupEntry);
|
export default RepositoryGroupEntry;
|
||||||
|
|||||||
@@ -159,33 +159,31 @@ ul.is-separated {
|
|||||||
|
|
||||||
// multiline Columns
|
// multiline Columns
|
||||||
.columns.is-multiline {
|
.columns.is-multiline {
|
||||||
|
.column {
|
||||||
|
height: 120px;
|
||||||
|
|
||||||
|
.overlay-column {
|
||||||
|
position: absolute;
|
||||||
|
height: calc(120px - 1.5rem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.column.is-half {
|
.column.is-half {
|
||||||
width: calc(50% - 0.75rem);
|
width: calc(50% - 0.75rem);
|
||||||
max-height: 120px;
|
|
||||||
|
|
||||||
&:nth-child(odd) {
|
&:nth-child(odd) {
|
||||||
margin-right: 1.5rem;
|
margin-right: 1.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.overlay-half-column {
|
.overlay-column {
|
||||||
position: absolute;
|
|
||||||
height: calc(120px - 1.5rem);
|
|
||||||
width: calc(50% - 3rem);
|
width: calc(50% - 3rem);
|
||||||
}
|
}
|
||||||
.overlay-half-column.is-plugin-page {
|
|
||||||
width: calc(37.5% - 1.5rem);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
.column.is-full {
|
.column.is-full .overlay-column {
|
||||||
.overlay-full-column {
|
|
||||||
position: absolute;
|
|
||||||
height: calc(120px - 0.5rem);
|
|
||||||
width: calc(100% - 1.5rem);
|
width: calc(100% - 1.5rem);
|
||||||
}
|
}
|
||||||
.overlay-full-column.is-plugin-page {
|
|
||||||
width: calc(75% - 1.5rem);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@media screen and (max-width: 768px) {
|
@media screen and (max-width: 768px) {
|
||||||
.column.is-half {
|
.column.is-half {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@@ -194,16 +192,30 @@ ul.is-separated {
|
|||||||
margin-right: 0;
|
margin-right: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.overlay-half-column,
|
.overlay-column {
|
||||||
.overlay-half-column.is-plugin-page {
|
|
||||||
width: calc(100% - 1.5rem);
|
width: calc(100% - 1.5rem);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.column.is-full .overlay-full-column.is-plugin-page {
|
}
|
||||||
|
}
|
||||||
|
.content.is-plugin-page {
|
||||||
|
.columns.is-multiline {
|
||||||
|
.column.is-half .overlay-column {
|
||||||
|
width: calc(37.5% - 1.5rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
.column.is-full .overlay-column {
|
||||||
|
width: calc(75% - 1.5rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 768px) {
|
||||||
|
.column.is-half .overlay-column,
|
||||||
|
.column.is-full .overlay-column {
|
||||||
width: calc(100% - 1.5rem);
|
width: calc(100% - 1.5rem);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.text-box {
|
.text-box {
|
||||||
width: calc(50% - 0.75rem);
|
width: calc(50% - 0.75rem);
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ public class UIPluginDtoMapper {
|
|||||||
UIPluginDto dto = new UIPluginDto();
|
UIPluginDto dto = new UIPluginDto();
|
||||||
dto.setName(plugin.getPlugin().getInformation().getName());
|
dto.setName(plugin.getPlugin().getInformation().getName());
|
||||||
dto.setBundles(getScriptResources(plugin));
|
dto.setBundles(getScriptResources(plugin));
|
||||||
dto.setType(plugin.getPlugin().getInformation().getCategory() != null ? plugin.getPlugin().getInformation().getCategory() : "Sonstige/Miscellaneous");
|
dto.setType(plugin.getPlugin().getInformation().getCategory() != null ? plugin.getPlugin().getInformation().getCategory() : "Miscellaneous");
|
||||||
dto.setVersion(plugin.getPlugin().getInformation().getVersion());
|
dto.setVersion(plugin.getPlugin().getInformation().getVersion());
|
||||||
dto.setAuthor(plugin.getPlugin().getInformation().getAuthor());
|
dto.setAuthor(plugin.getPlugin().getInformation().getAuthor());
|
||||||
dto.setDescription(plugin.getPlugin().getInformation().getDescription());
|
dto.setDescription(plugin.getPlugin().getInformation().getDescription());
|
||||||
|
|||||||
Reference in New Issue
Block a user