diff --git a/scm-ui-components/packages/ui-components/src/CardColumn.js b/scm-ui-components/packages/ui-components/src/CardColumn.js new file mode 100644 index 0000000000..e1eb65255a --- /dev/null +++ b/scm-ui-components/packages/ui-components/src/CardColumn.js @@ -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 { + createLink = () => { + const { link } = this.props; + if (link) { + return ; + } + return null; + }; + + render() { + const { avatar, title, description, footerLeft, footerRight, classes } = this.props; + const link = this.createLink(); + return ( + <> + {link} +
+
+ {avatar} +
+
+
+
+

+ {title} +

+

{description}

+
+
+
+
{footerLeft}
+
{footerRight}
+
+
+
+ + ); + } +} + +export default injectSheet(styles)(CardColumn); diff --git a/scm-ui-components/packages/ui-components/src/CardColumnGroup.js b/scm-ui-components/packages/ui-components/src/CardColumnGroup.js new file mode 100644 index 0000000000..b72bd97dd7 --- /dev/null +++ b/scm-ui-components/packages/ui-components/src/CardColumnGroup.js @@ -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 { + 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 ( +
+ {entry} +
+ ); + }); + } + return ( +
+

+ + {name} + +

+
+
+ {content} +
+
+
+ ); + } +} + +export default injectSheet(styles)(CardColumnGroup); diff --git a/scm-ui-components/packages/ui-components/src/index.js b/scm-ui-components/packages/ui-components/src/index.js index ae6fd6f875..f6d83d6d08 100644 --- a/scm-ui-components/packages/ui-components/src/index.js +++ b/scm-ui-components/packages/ui-components/src/index.js @@ -32,6 +32,8 @@ export { default as MarkdownView } from "./MarkdownView"; export { default as SyntaxHighlighter } from "./SyntaxHighlighter"; export { default as ErrorBoundary } from "./ErrorBoundary"; 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 * from "./errors"; diff --git a/scm-ui/src/admin/plugins/components/PluginEntry.js b/scm-ui/src/admin/plugins/components/PluginEntry.js index b12c8dd1e1..a8cdaad915 100644 --- a/scm-ui/src/admin/plugins/components/PluginEntry.js +++ b/scm-ui/src/admin/plugins/components/PluginEntry.js @@ -1,86 +1,44 @@ //@flow 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 { CardColumn } from "@scm-manager/ui-components"; 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 = { - plugin: Plugin, - fullColumnWidth?: boolean, - - // context props - classes: any + plugin: Plugin }; class PluginEntry extends React.Component { + createAvatar = (plugin: Plugin) => { + return ; + }; + + createFooterLeft = (plugin: Plugin) => { + return {plugin.author}; + }; + + createFooterRight = (plugin: Plugin) => { + return

{plugin.version}

; + }; + render() { - const { plugin, classes, fullColumnWidth } = this.props; - const halfColumn = fullColumnWidth ? "is-full" : "is-half"; - const overlayLinkClass = fullColumnWidth - ? "overlay-full-column" - : "overlay-half-column"; + const { plugin } = this.props; + const avatar = this.createAvatar(plugin); + const footerLeft = this.createFooterLeft(plugin); + const footerRight = this.createFooterRight(plugin); + // TODO: Add link to plugin page below return ( -
- -
-
- -
-
-
- -

{plugin.description}

-

- {plugin.author} -

-
-
-
-
+ ); } } -export default injectSheet(styles)(PluginEntry); +export default PluginEntry; diff --git a/scm-ui/src/admin/plugins/components/PluginGroupEntry.js b/scm-ui/src/admin/plugins/components/PluginGroupEntry.js index 87076f6eaf..44046eb6ab 100644 --- a/scm-ui/src/admin/plugins/components/PluginGroupEntry.js +++ b/scm-ui/src/admin/plugins/components/PluginGroupEntry.js @@ -1,96 +1,21 @@ //@flow import React from "react"; -import injectSheet from "react-jss"; -import classNames from "classnames"; -import type { PluginGroup, Plugin } from "@scm-manager/ui-types"; +import { CardColumnGroup } from "@scm-manager/ui-components"; +import type { PluginGroup } from "@scm-manager/ui-types"; 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 = { - group: PluginGroup, - - // context props - classes: any + group: PluginGroup }; -type State = { - collapsed: boolean -}; - -class PluginGroupEntry extends React.Component { - 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 { render() { - const { group, classes } = this.props; - const { collapsed } = this.state; - - 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 ( - - ); - }); - } - return ( -
-

- - {group.name} - -

-
-
- {content} -
-
-
- ); + const { group } = this.props; + const entries = group.plugins.map((plugin, index) => { + return ; + }); + return ; } } -export default injectSheet(styles)(PluginGroupEntry); +export default PluginGroupEntry; diff --git a/scm-ui/src/admin/plugins/components/PluginsList.js b/scm-ui/src/admin/plugins/components/PluginsList.js index 01f64afe78..e04d78d46e 100644 --- a/scm-ui/src/admin/plugins/components/PluginsList.js +++ b/scm-ui/src/admin/plugins/components/PluginsList.js @@ -14,7 +14,7 @@ class PluginList extends React.Component { const groups = groupByCategory(plugins); return ( -
+
{groups.map(group => { return ; })} diff --git a/scm-ui/src/admin/plugins/modules/plugins.js b/scm-ui/src/admin/plugins/modules/plugins.js index 6e94648319..3f475c68b6 100644 --- a/scm-ui/src/admin/plugins/modules/plugins.js +++ b/scm-ui/src/admin/plugins/modules/plugins.js @@ -144,8 +144,7 @@ export default function reducer( switch (action.type) { case FETCH_PLUGINS_SUCCESS: - const t = normalizeByName(action.payload); - return t; + return normalizeByName(action.payload); case FETCH_PLUGIN_SUCCESS: return reducerByNames(state, action.payload); default: @@ -190,8 +189,3 @@ export function isFetchPluginPending(state: Object, name: string) { export function getFetchPluginFailure(state: Object, name: string) { 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; -} diff --git a/scm-ui/src/admin/plugins/modules/plugins.test.js b/scm-ui/src/admin/plugins/modules/plugins.test.js index 20b7c10590..d577b20996 100644 --- a/scm-ui/src/admin/plugins/modules/plugins.test.js +++ b/scm-ui/src/admin/plugins/modules/plugins.test.js @@ -23,10 +23,7 @@ import reducer, { isFetchPluginPending, getFetchPluginFailure } from "./plugins"; -import type { - Plugin, - PluginCollection -} from "@scm-manager/ui-types"; +import type { Plugin, PluginCollection } from "@scm-manager/ui-types"; const groupManagerPlugin: Plugin = { name: "scm-groupmanager-plugin", @@ -37,8 +34,7 @@ const groupManagerPlugin: Plugin = { description: "Notify a remote webserver whenever a plugin is pushed to.", _links: { self: { - href: - "http://localhost:8081/api/v2/ui/plugins/scm-groupmanager-plugin" + href: "http://localhost:8081/api/v2/ui/plugins/scm-groupmanager-plugin" } } }; @@ -52,8 +48,7 @@ const scriptPlugin: Plugin = { description: "Script support for scm-manager.", _links: { self: { - href: - "http://localhost:8081/api/v2/ui/plugins/scm-script-plugin" + href: "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.", _links: { self: { - href: - "http://localhost:8081/api/v2/ui/plugins/scm-branchwp-plugin" + href: "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", () => { - fetchMock.getOnce( - PLUGINS_URL + "/scm-groupmanager-plugin", - { - status: 500 - } - ); + fetchMock.getOnce(PLUGINS_URL + "/scm-groupmanager-plugin", { + status: 500 + }); const store = mockStore({}); return store @@ -331,8 +322,7 @@ describe("plugins selectors", () => { it("should return true, when fetch plugin is pending", () => { const state = { pending: { - [FETCH_PLUGIN + - "/scm-groupmanager-plugin"]: true + [FETCH_PLUGIN + "/scm-groupmanager-plugin"]: true } }; expect(isFetchPluginPending(state, "scm-groupmanager-plugin")).toEqual( @@ -347,8 +337,7 @@ describe("plugins selectors", () => { it("should return error when fetch plugin did fail", () => { const state = { failure: { - [FETCH_PLUGIN + - "/scm-groupmanager-plugin"]: error + [FETCH_PLUGIN + "/scm-groupmanager-plugin"]: error } }; expect(getFetchPluginFailure(state, "scm-groupmanager-plugin")).toEqual( diff --git a/scm-ui/src/repos/components/list/RepositoryEntry.js b/scm-ui/src/repos/components/list/RepositoryEntry.js index eb3b3d95f6..471f900aa1 100644 --- a/scm-ui/src/repos/components/list/RepositoryEntry.js +++ b/scm-ui/src/repos/components/list/RepositoryEntry.js @@ -1,33 +1,12 @@ //@flow import React from "react"; -import { Link } from "react-router-dom"; -import injectSheet from "react-jss"; 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 classNames from "classnames"; 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 = { - repository: Repository, - fullColumnWidth?: boolean, - // context props - classes: any + repository: Repository }; class RepositoryEntry extends React.Component { @@ -83,53 +62,41 @@ class RepositoryEntry extends React.Component { return null; }; - render() { - 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"; + createFooterLeft = (repository: Repository, repositoryLink: string) => { return ( -
- -
-
- -
-
-
-

- {repository.name} -

-

{repository.description}

-
- -
-
-
+ <> + {this.renderBranchesLink(repository, repositoryLink)} + {this.renderChangesetsLink(repository, repositoryLink)} + {this.renderSourcesLink(repository, repositoryLink)} + {this.renderModifyLink(repository, repositoryLink)} + + ); + }; + + createFooterRight = (repository: Repository) => { + return ( + + + + ); + }; + + render() { + const { repository } = this.props; + const repositoryLink = this.createLink(repository); + const footerLeft = this.createFooterLeft(repository, repositoryLink); + const footerRight = this.createFooterRight(repository); + return ( + } + title={repository.name} + description={repository.description} + link={repositoryLink} + footerLeft={footerLeft} + footerRight={footerRight} + /> ); } } -export default injectSheet(styles)(RepositoryEntry); +export default RepositoryEntry; diff --git a/scm-ui/src/repos/components/list/RepositoryGroupEntry.js b/scm-ui/src/repos/components/list/RepositoryGroupEntry.js index 98e7925150..8f7e25dcbe 100644 --- a/scm-ui/src/repos/components/list/RepositoryGroupEntry.js +++ b/scm-ui/src/repos/components/list/RepositoryGroupEntry.js @@ -1,92 +1,21 @@ //@flow import React from "react"; -import type { RepositoryGroup, Repository } from "@scm-manager/ui-types"; -import injectSheet from "react-jss"; -import classNames from "classnames"; +import { CardColumnGroup } from "@scm-manager/ui-components"; +import type { RepositoryGroup } from "@scm-manager/ui-types"; 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 = { - group: RepositoryGroup, - - // context props - classes: any + group: RepositoryGroup }; -type State = { - collapsed: boolean -}; - -class RepositoryGroupEntry extends React.Component { - 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 { render() { - const { group, classes } = this.props; - const { collapsed } = this.state; - - 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 ( - - ); - }); - } - return ( -
-

- - {group.name} - -

-
-
- {content} -
-
-
- ); + const { group } = this.props; + const entries = group.repositories.map((repository, index) => { + return ; + }); + return ; } } -export default injectSheet(styles)(RepositoryGroupEntry); +export default RepositoryGroupEntry; diff --git a/scm-ui/styles/scm.scss b/scm-ui/styles/scm.scss index c9d423a9e7..8378bb23e9 100644 --- a/scm-ui/styles/scm.scss +++ b/scm-ui/styles/scm.scss @@ -159,33 +159,31 @@ ul.is-separated { // multiline Columns .columns.is-multiline { + .column { + height: 120px; + + .overlay-column { + position: absolute; + height: calc(120px - 1.5rem); + } + } + .column.is-half { width: calc(50% - 0.75rem); - max-height: 120px; &:nth-child(odd) { margin-right: 1.5rem; } - .overlay-half-column { - position: absolute; - height: calc(120px - 1.5rem); + .overlay-column { width: calc(50% - 3rem); } - .overlay-half-column.is-plugin-page { - width: calc(37.5% - 1.5rem); - } } - .column.is-full { - .overlay-full-column { - position: absolute; - height: calc(120px - 0.5rem); - width: calc(100% - 1.5rem); - } - .overlay-full-column.is-plugin-page { - width: calc(75% - 1.5rem); - } + + .column.is-full .overlay-column { + width: calc(100% - 1.5rem); } + @media screen and (max-width: 768px) { .column.is-half { width: 100%; @@ -194,13 +192,27 @@ ul.is-separated { margin-right: 0; } - .overlay-half-column, - .overlay-half-column.is-plugin-page { + .overlay-column { width: calc(100% - 1.5rem); } } - .column.is-full .overlay-full-column.is-plugin-page { - width: calc(100% - 1.5rem); + } +} +.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); + } } } } diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UIPluginDtoMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UIPluginDtoMapper.java index 46e4a8b4ac..a586fbcbff 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UIPluginDtoMapper.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UIPluginDtoMapper.java @@ -29,7 +29,7 @@ public class UIPluginDtoMapper { UIPluginDto dto = new UIPluginDto(); dto.setName(plugin.getPlugin().getInformation().getName()); 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.setAuthor(plugin.getPlugin().getInformation().getAuthor()); dto.setDescription(plugin.getPlugin().getInformation().getDescription());