implemented ui for available plugins page

This commit is contained in:
Florian Scholdei
2019-07-31 15:40:35 +02:00
parent 8a8942cbc4
commit 508e5bbf3b
5 changed files with 165 additions and 51 deletions

View File

@@ -25,12 +25,17 @@ const styles = {
}, },
content: { content: {
display: "flex", display: "flex",
flexGrow: 1 flexGrow: 1,
alignItems: "center",
justifyContent: "space-between"
}, },
footer: { footer: {
display: "flex", display: "flex",
marginTop: "auto", marginTop: "auto",
paddingBottom: "1.5rem" paddingBottom: "1.5rem"
},
noBottomMargin: {
marginBottom: "0 !important"
} }
}; };
@@ -38,9 +43,11 @@ type Props = {
title: string, title: string,
description: string, description: string,
avatar: React.Node, avatar: React.Node,
contentRight?: React.Node,
footerLeft: React.Node, footerLeft: React.Node,
footerRight: React.Node, footerRight: React.Node,
link: string, link: string,
// context props // context props
classes: any classes: any
}; };
@@ -55,7 +62,15 @@ class CardColumn extends React.Component<Props> {
}; };
render() { render() {
const { avatar, title, description, footerLeft, footerRight, classes } = this.props; const {
avatar,
title,
description,
contentRight,
footerLeft,
footerRight,
classes
} = this.props;
const link = this.createLink(); const link = this.createLink();
return ( return (
<> <>
@@ -64,16 +79,29 @@ class CardColumn extends React.Component<Props> {
<figure className={classNames(classes.centerImage, "media-left")}> <figure className={classNames(classes.centerImage, "media-left")}>
{avatar} {avatar}
</figure> </figure>
<div className={classNames("media-content", "text-box", classes.flexFullHeight)}> <div
className={classNames(
"media-content",
"text-box",
classes.flexFullHeight
)}
>
<div className={classes.content}> <div className={classes.content}>
<div className="content shorten-text"> <div
className={classNames(
"content",
"shorten-text",
classes.noBottomMargin
)}
>
<p className="is-marginless"> <p className="is-marginless">
<strong>{title}</strong> <strong>{title}</strong>
</p> </p>
<p className="shorten-text">{description}</p> <p className="shorten-text">{description}</p>
</div> </div>
{contentRight && contentRight}
</div> </div>
<div className={classNames(classes.footer, "level")}> <div className={classNames("level", classes.footer)}>
<div className="level-left is-hidden-mobile">{footerLeft}</div> <div className="level-left is-hidden-mobile">{footerLeft}</div>
<div className="level-right is-mobile">{footerRight}</div> <div className="level-right is-mobile">{footerRight}</div>
</div> </div>

View File

@@ -7,8 +7,18 @@ import type { History } from "history";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { compose } from "redux"; import { compose } from "redux";
import type { Links } from "@scm-manager/ui-types"; import type { Links } from "@scm-manager/ui-types";
import { Page, Navigation, NavLink, Section, SubNavigation } from "@scm-manager/ui-components"; import {
import { getLinks } from "../../modules/indexResource"; Page,
Navigation,
NavLink,
Section,
SubNavigation
} from "@scm-manager/ui-components";
import {
getLinks,
getAvailablePluginsLink,
getInstalledPluginsLink
} from "../../modules/indexResource";
import AdminDetails from "./AdminDetails"; import AdminDetails from "./AdminDetails";
import PluginsOverview from "../plugins/containers/PluginsOverview"; import PluginsOverview from "../plugins/containers/PluginsOverview";
import GlobalConfig from "./GlobalConfig"; import GlobalConfig from "./GlobalConfig";
@@ -18,6 +28,8 @@ import CreateRepositoryRole from "../roles/containers/CreateRepositoryRole";
type Props = { type Props = {
links: Links, links: Links,
availablePluginsLink: string,
installedPluginsLink: string,
// context objects // context objects
t: string => string, t: string => string,
@@ -47,7 +59,7 @@ class Admin extends React.Component<Props> {
}; };
render() { render() {
const { links, t } = this.props; const { links, availablePluginsLink, installedPluginsLink, t } = this.props;
const url = this.matchedUrl(); const url = this.matchedUrl();
const extensionProps = { const extensionProps = {
@@ -62,34 +74,54 @@ class Admin extends React.Component<Props> {
<Switch> <Switch>
<Redirect exact from={url} to={`${url}/info`} /> <Redirect exact from={url} to={`${url}/info`} />
<Route path={`${url}/info`} exact component={AdminDetails} /> <Route path={`${url}/info`} exact component={AdminDetails} />
<Route path={`${url}/settings/general`} exact component={GlobalConfig} /> <Route
<Redirect exact from={`${url}/plugins`} to={`${url}/plugins/installed/`} /> path={`${url}/settings/general`}
exact
component={GlobalConfig}
/>
<Redirect
exact
from={`${url}/plugins`}
to={`${url}/plugins/installed/`}
/>
<Route <Route
path={`${url}/plugins/installed`} path={`${url}/plugins/installed`}
exact exact
render={() => ( render={() => (
<PluginsOverview baseUrl={`${url}/plugins/installed`} installed={true} /> <PluginsOverview
baseUrl={`${url}/plugins/installed`}
installed={true}
/>
)} )}
/> />
<Route <Route
path={`${url}/plugins/installed/:page`} path={`${url}/plugins/installed/:page`}
exact exact
render={() => ( render={() => (
<PluginsOverview baseUrl={`${url}/plugins/installed`} installed={true} /> <PluginsOverview
baseUrl={`${url}/plugins/installed`}
installed={true}
/>
)} )}
/> />
<Route <Route
path={`${url}/plugins/available`} path={`${url}/plugins/available`}
exact exact
render={() => ( render={() => (
<PluginsOverview baseUrl={`${url}/plugins/available`} installed={false} /> <PluginsOverview
baseUrl={`${url}/plugins/available`}
installed={false}
/>
)} )}
/> />
<Route <Route
path={`${url}/plugins/available/:page`} path={`${url}/plugins/available/:page`}
exact exact
render={() => ( render={() => (
<PluginsOverview baseUrl={`${url}/plugins/available`} installed={false} /> <PluginsOverview
baseUrl={`${url}/plugins/available`}
installed={false}
/>
)} )}
/> />
<Route <Route
@@ -109,17 +141,13 @@ class Admin extends React.Component<Props> {
<Route <Route
path={`${url}/roles/create`} path={`${url}/roles/create`}
render={() => ( render={() => (
<CreateRepositoryRole <CreateRepositoryRole history={this.props.history} />
history={this.props.history}
/>
)} )}
/> />
<Route <Route
path={`${url}/roles/:page`} path={`${url}/roles/:page`}
exact exact
render={() => ( render={() => <RepositoryRoles baseUrl={`${url}/roles`} />}
<RepositoryRoles baseUrl={`${url}/roles`} />
)}
/> />
<ExtensionPoint <ExtensionPoint
name="admin.route" name="admin.route"
@@ -136,24 +164,26 @@ class Admin extends React.Component<Props> {
icon="fas fa-info-circle" icon="fas fa-info-circle"
label={t("admin.menu.informationNavLink")} label={t("admin.menu.informationNavLink")}
/> />
{ {(availablePluginsLink || installedPluginsLink) && (
links.plugins &&
<SubNavigation <SubNavigation
to={`${url}/plugins/`} to={`${url}/plugins/`}
icon="fas fa-puzzle-piece" icon="fas fa-puzzle-piece"
label={t("plugins.menu.pluginsNavLink")} label={t("plugins.menu.pluginsNavLink")}
> >
{installedPluginsLink && (
<NavLink <NavLink
to={`${url}/plugins/installed/`} to={`${url}/plugins/installed/`}
label={t("plugins.menu.installedNavLink")} label={t("plugins.menu.installedNavLink")}
/> />
{/* Activate this again after available plugins page is created */} )}
{/*<NavLink*/} {availablePluginsLink && (
{/* to={`${url}/plugins/available/`}*/} <NavLink
{/* label={t("plugins.menu.availableNavLink")}*/} to={`${url}/plugins/available/`}
{/*/>*/} label={t("plugins.menu.availableNavLink")}
/>
)}
</SubNavigation> </SubNavigation>
} )}
<NavLink <NavLink
to={`${url}/roles/`} to={`${url}/roles/`}
icon="fas fa-user-shield" icon="fas fa-user-shield"
@@ -191,8 +221,12 @@ class Admin extends React.Component<Props> {
const mapStateToProps = (state: any) => { const mapStateToProps = (state: any) => {
const links = getLinks(state); const links = getLinks(state);
const availablePluginsLink = getAvailablePluginsLink(state);
const installedPluginsLink = getInstalledPluginsLink(state);
return { return {
links links,
availablePluginsLink,
installedPluginsLink
}; };
}; };

View File

@@ -1,11 +1,21 @@
//@flow //@flow
import React from "react"; import React from "react";
import injectSheet from "react-jss";
import type { Plugin } from "@scm-manager/ui-types"; import type { Plugin } from "@scm-manager/ui-types";
import { CardColumn } from "@scm-manager/ui-components"; import { CardColumn } from "@scm-manager/ui-components";
import PluginAvatar from "./PluginAvatar"; import PluginAvatar from "./PluginAvatar";
type Props = { type Props = {
plugin: Plugin plugin: Plugin,
// context props
classes: any
};
const styles = {
link: {
pointerEvents: "all"
}
}; };
class PluginEntry extends React.Component<Props> { class PluginEntry extends React.Component<Props> {
@@ -13,6 +23,17 @@ class PluginEntry extends React.Component<Props> {
return <PluginAvatar plugin={plugin} />; return <PluginAvatar plugin={plugin} />;
}; };
createContentRight = (plugin: Plugin) => {
const { classes } = this.props;
if (plugin._links && plugin._links.install && plugin._links.install.href) {
return (
<a className={classes.link} href={plugin._links.install.href}>
<i className="fas fa-cloud-download-alt fa-2x" />
</a>
);
}
};
createFooterLeft = (plugin: Plugin) => { createFooterLeft = (plugin: Plugin) => {
return <small className="level-item">{plugin.author}</small>; return <small className="level-item">{plugin.author}</small>;
}; };
@@ -24,6 +45,7 @@ class PluginEntry extends React.Component<Props> {
render() { render() {
const { plugin } = this.props; const { plugin } = this.props;
const avatar = this.createAvatar(plugin); const avatar = this.createAvatar(plugin);
const contentRight = this.createContentRight(plugin);
const footerLeft = this.createFooterLeft(plugin); const footerLeft = this.createFooterLeft(plugin);
const footerRight = this.createFooterRight(plugin); const footerRight = this.createFooterRight(plugin);
@@ -34,6 +56,7 @@ class PluginEntry extends React.Component<Props> {
avatar={avatar} avatar={avatar}
title={plugin.name} title={plugin.name}
description={plugin.description} description={plugin.description}
contentRight={contentRight}
footerLeft={footerLeft} footerLeft={footerLeft}
footerRight={footerRight} footerRight={footerRight}
/> />
@@ -41,4 +64,4 @@ class PluginEntry extends React.Component<Props> {
} }
} }
export default PluginEntry; export default injectSheet(styles)(PluginEntry);

View File

@@ -18,7 +18,10 @@ import {
isFetchPluginsPending isFetchPluginsPending
} from "../modules/plugins"; } from "../modules/plugins";
import PluginsList from "../components/PluginsList"; import PluginsList from "../components/PluginsList";
import { getPluginsLink } from "../../../modules/indexResource"; import {
getAvailablePluginsLink,
getInstalledPluginsLink
} from "../../../modules/indexResource";
type Props = { type Props = {
loading: boolean, loading: boolean,
@@ -26,7 +29,8 @@ type Props = {
collection: PluginCollection, collection: PluginCollection,
baseUrl: string, baseUrl: string,
installed: boolean, installed: boolean,
pluginsLink: string, availablePluginsLink: string,
installedPluginsLink: string,
// context objects // context objects
t: string => string, t: string => string,
@@ -37,8 +41,27 @@ type Props = {
class PluginsOverview extends React.Component<Props> { class PluginsOverview extends React.Component<Props> {
componentDidMount() { componentDidMount() {
const { fetchPluginsByLink, pluginsLink } = this.props; const {
fetchPluginsByLink(pluginsLink); installed,
fetchPluginsByLink,
availablePluginsLink,
installedPluginsLink
} = this.props;
fetchPluginsByLink(installed ? installedPluginsLink : availablePluginsLink);
}
componentDidUpdate(prevProps) {
const {
installed,
fetchPluginsByLink,
availablePluginsLink,
installedPluginsLink
} = this.props;
if (prevProps.installed !== installed) {
fetchPluginsByLink(
installed ? installedPluginsLink : availablePluginsLink
);
}
} }
render() { render() {
@@ -81,13 +104,15 @@ const mapStateToProps = state => {
const collection = getPluginCollection(state); const collection = getPluginCollection(state);
const loading = isFetchPluginsPending(state); const loading = isFetchPluginsPending(state);
const error = getFetchPluginsFailure(state); const error = getFetchPluginsFailure(state);
const pluginsLink = getPluginsLink(state); const availablePluginsLink = getAvailablePluginsLink(state);
const installedPluginsLink = getInstalledPluginsLink(state);
return { return {
collection, collection,
loading, loading,
error, error,
pluginsLink availablePluginsLink,
installedPluginsLink
}; };
}; };

View File

@@ -116,8 +116,12 @@ export function getUiPluginsLink(state: Object) {
return getLink(state, "uiPlugins"); return getLink(state, "uiPlugins");
} }
export function getPluginsLink(state: Object) { export function getAvailablePluginsLink(state: Object) {
return getLink(state, "plugins"); return getLink(state, "availablePlugins");
}
export function getInstalledPluginsLink(state: Object) {
return getLink(state, "installedPlugins");
} }
export function getMeLink(state: Object) { export function getMeLink(state: Object) {