diff --git a/scm-ui/ui-webapp/src/admin/containers/AdminDetails.js b/scm-ui/ui-webapp/src/admin/containers/AdminDetails.js index 803524abed..c927ce8808 100644 --- a/scm-ui/ui-webapp/src/admin/containers/AdminDetails.js +++ b/scm-ui/ui-webapp/src/admin/containers/AdminDetails.js @@ -1,83 +1,95 @@ // @flow import React from "react"; -import {connect} from "react-redux"; -import injectSheet from "react-jss"; -import {translate} from "react-i18next"; -import classNames from "classnames"; -import {Image, Loading, Subtitle, Title} from "@scm-manager/ui-components"; -import {getAppVersion} from "../../modules/indexResource"; +import { connect } from "react-redux"; +import { translate } from "react-i18next"; +import styled from "styled-components"; +import { Image, Loading, Subtitle, Title } from "@scm-manager/ui-components"; +import { getAppVersion } from "../../modules/indexResource"; type Props = { loading: boolean, error: Error, - version: string, // context props - classes: any, t: string => string }; -const styles = { - boxShadow: { - boxShadow: "0 2px 3px rgba(40, 177, 232, 0.1), 0 0 0 2px rgb(40, 177, 232, 0.2)" - }, - boxTitle: { - fontWeight: "500 !important" - }, - imagePadding: { - padding: "0.2rem 0.4rem" - } -}; +const BoxShadowBox = styled.div` + box-shadow: 0 2px 3px rgba(40, 177, 232, 0.1), + 0 0 0 2px rgba(40, 177, 232, 0.2); +`; + +const ImageWrapper = styled.div` + padding: 0.2rem 0.4rem; +`; class AdminDetails extends React.Component { render() { - const {loading, classes, t} = this.props; + const { loading, t } = this.props; if (loading) { - return ; + return ; } return ( <> - - <Subtitle subtitle={this.props.version}/> - <div className={classNames("box", classes.boxShadow)}> + <Title title={t("admin.info.currentAppVersion")} /> + <Subtitle subtitle={this.props.version} /> + <BoxShadowBox className="box"> <article className="media"> - <div className={classNames("media-left", classes.imagePadding)}> + <ImageWrapper className="media-left"> <Image src="/images/iconCommunitySupport.png" alt={t("admin.info.communityIconAlt")} /> - </div> + </ImageWrapper> <div className="media-content"> <div className="content"> - <h3 className={classes.boxTitle}>{t("admin.info.communityTitle")}</h3> + <h3 className="has-text-weight-medium"> + {t("admin.info.communityTitle")} + </h3> <p>{t("admin.info.communityInfo")}</p> - <a className="button is-info is-pulled-right" target="_blank" - href="https://scm-manager.org/support/">{t("admin.info.communityButton")}</a> + <a + className="button is-info is-pulled-right" + target="_blank" + href="https://scm-manager.org/support/" + > + {t("admin.info.communityButton")} + </a> </div> </div> </article> - </div> - <div className={classNames("box", classes.boxShadow)}> + </BoxShadowBox> + <BoxShadowBox className="box"> <article className="media"> - <div className={classNames("media-left", classes.imagePadding)}> + <ImageWrapper className="media-left"> <Image src="/images/iconEnterpriseSupport.png" alt={t("admin.info.enterpriseIconAlt")} /> - </div> + </ImageWrapper> <div className="media-content"> <div className="content"> - <h3 className={classes.boxTitle}>{t("admin.info.enterpriseTitle")}</h3> - <p>{t("admin.info.enterpriseInfo")}<br/><strong>{t("admin.info.enterprisePartner")}</strong></p> - <a className="button is-info is-pulled-right is-normal" target="_blank" - href={t("admin.info.enterpriseLink")}>{t("admin.info.enterpriseButton")}</a> + <h3 className="has-text-weight-medium"> + {t("admin.info.enterpriseTitle")} + </h3> + <p> + {t("admin.info.enterpriseInfo")} + <br /> + <strong>{t("admin.info.enterprisePartner")}</strong> + </p> + <a + className="button is-info is-pulled-right is-normal" + target="_blank" + href={t("admin.info.enterpriseLink")} + > + {t("admin.info.enterpriseButton")} + </a> </div> </div> </article> - </div> + </BoxShadowBox> </> ); } @@ -90,4 +102,4 @@ const mapStateToProps = (state: any) => { }; }; -export default connect(mapStateToProps)(injectSheet(styles)(translate("admin")(AdminDetails))); +export default connect(mapStateToProps)(translate("admin")(AdminDetails)); diff --git a/scm-ui/ui-webapp/src/admin/plugins/components/PluginBottomActions.js b/scm-ui/ui-webapp/src/admin/plugins/components/PluginBottomActions.js index 668fc0d285..4d3022c856 100644 --- a/scm-ui/ui-webapp/src/admin/plugins/components/PluginBottomActions.js +++ b/scm-ui/ui-webapp/src/admin/plugins/components/PluginBottomActions.js @@ -1,30 +1,21 @@ // @flow import * as React from "react"; -import classNames from "classnames"; -import injectSheet from "react-jss"; - -const styles = { - container: { - border: "2px solid #e9f7fd", - padding: "1em 1em", - marginTop: "2em", - display: "flex", - justifyContent: "center" - } -}; +import styled from "styled-components"; type Props = { - children?: React.Node, - - // context props - classes: any + children?: React.Node }; -class PluginBottomActions extends React.Component<Props> { +const ActionWrapper = styled.div` + justify-content: center; + margin-top: 2em; + padding: 1em 1em; + border: 2px solid #e9f7df; +`; + +export default class PluginBottomActions extends React.Component<Props> { render() { - const { children, classes } = this.props; - return <div className={classNames(classes.container)}>{children}</div>; + const { children } = this.props; + return <ActionWrapper className="is-flex">{children}</ActionWrapper>; } } - -export default injectSheet(styles)(PluginBottomActions); diff --git a/scm-ui/ui-webapp/src/admin/plugins/components/PluginEntry.js b/scm-ui/ui-webapp/src/admin/plugins/components/PluginEntry.js index fac20421bd..ee933b3895 100644 --- a/scm-ui/ui-webapp/src/admin/plugins/components/PluginEntry.js +++ b/scm-ui/ui-webapp/src/admin/plugins/components/PluginEntry.js @@ -1,11 +1,11 @@ //@flow import React from "react"; -import injectSheet from "react-jss"; import { translate } from "react-i18next"; +import classNames from "classnames"; +import styled from "styled-components"; import type { Plugin } from "@scm-manager/ui-types"; import { CardColumn, Icon } from "@scm-manager/ui-components"; import PluginAvatar from "./PluginAvatar"; -import classNames from "classnames"; import PluginModal from "./PluginModal"; export const PluginAction = { @@ -19,7 +19,6 @@ type Props = { refresh: () => void, // context props - classes: any, t: string => string }; @@ -29,25 +28,24 @@ type State = { showUninstallModal: boolean }; -const styles = { - link: { - cursor: "pointer", - pointerEvents: "all", - marginBottom: "0 !important", - padding: "0.5rem", - border: "solid 1px #cdcdcd", // $dark-25 - borderRadius: "4px", - "&:hover": { - borderColor: "#9a9a9a" // $dark-50 - } - }, - actionbar: { - display: "flex", - "& span + span": { - marginLeft: "0.5rem" - } +const ActionbarWrapper = styled.div` + & span + span { + margin-left: 0.5rem; } -}; +`; + +const IconWrapper = styled.span` + margin-bottom: 0 !important; + padding: 0.5rem; + border: 1px solid #cdcdcd; // $dark-25 + border-radius: 4px; + cursor: pointer; + pointer-events: all; + + &:hover { + border-color: #9a9a9a; // $dark-50 + } +`; class PluginEntry extends React.Component<Props, State> { constructor(props: Props) { @@ -91,34 +89,46 @@ class PluginEntry extends React.Component<Props, State> { }; createActionbar = () => { - const { classes, t } = this.props; + const { t } = this.props; return ( - <div className={classes.actionbar}> + <ActionbarWrapper className="is-flex"> {this.isInstallable() && ( - <span - className={classNames(classes.link, "level-item")} + <IconWrapper + className="level-item" onClick={() => this.toggleModal("showInstallModal")} > - <Icon title={t("plugins.modal.install")} name="download" color="info" /> - </span> + <Icon + title={t("plugins.modal.install")} + name="download" + color="info" + /> + </IconWrapper> )} {this.isUninstallable() && ( - <span - className={classNames(classes.link, "level-item")} + <IconWrapper + className="level-item" onClick={() => this.toggleModal("showUninstallModal")} > - <Icon title={t("plugins.modal.uninstall")} name="trash" color="info" /> - </span> + <Icon + title={t("plugins.modal.uninstall")} + name="trash" + color="info" + /> + </IconWrapper> )} {this.isUpdatable() && ( - <span - className={classNames(classes.link, "level-item")} + <IconWrapper + className="level-item" onClick={() => this.toggleModal("showUpdateModal")} > - <Icon title={t("plugins.modal.update")} name="sync-alt" color="info" /> - </span> + <Icon + title={t("plugins.modal.update")} + name="sync-alt" + color="info" + /> + </IconWrapper> )} - </div> + </ActionbarWrapper> ); }; @@ -157,16 +167,13 @@ class PluginEntry extends React.Component<Props, State> { }; createPendingSpinner = () => { - const { plugin, classes } = this.props; + const { plugin } = this.props; return ( - <span className={classes.topRight}> - <i - className={classNames( - "fas fa-spinner fa-lg fa-spin", - plugin.markedForUninstall ? "has-text-danger" : "has-text-info" - )} - /> - </span> + <Icon + className="fa-spin fa-lg" + name="spinner" + color={plugin.markedForUninstall ? "danger" : "info"} + /> ); }; @@ -202,4 +209,4 @@ class PluginEntry extends React.Component<Props, State> { } } -export default injectSheet(styles)(translate("admin")(PluginEntry)); +export default translate("admin")(PluginEntry); diff --git a/scm-ui/ui-webapp/src/admin/plugins/components/PluginModal.js b/scm-ui/ui-webapp/src/admin/plugins/components/PluginModal.js index b15a068fa2..cfbaed3ec3 100644 --- a/scm-ui/ui-webapp/src/admin/plugins/components/PluginModal.js +++ b/scm-ui/ui-webapp/src/admin/plugins/components/PluginModal.js @@ -1,8 +1,8 @@ //@flow import React from "react"; -import { compose } from "redux"; import { translate } from "react-i18next"; -import injectSheet from "react-jss"; +import classNames from "classnames"; +import styled from "styled-components"; import type { Plugin } from "@scm-manager/ui-types"; import { apiClient, @@ -13,7 +13,6 @@ import { Modal, Notification } from "@scm-manager/ui-components"; -import classNames from "classnames"; import waitForRestart from "./waitForRestart"; import SuccessNotification from "./SuccessNotification"; import { PluginAction } from "./PluginEntry"; @@ -25,7 +24,6 @@ type Props = { onClose: () => void, // context props - classes: any, t: (key: string, params?: Object) => string }; @@ -36,21 +34,16 @@ type State = { error?: Error }; -const styles = { - userLabelAlignment: { - textAlign: "left", - marginRight: 0 - }, - userLabelMarginSmall: { - minWidth: "5.5em" - }, - userLabelMarginLarge: { - minWidth: "10em" - }, - userFieldFlex: { - flexGrow: 4 - } -}; +const ListParent = styled.div` + margin-right: 0; + min-width: ${props => + props.pluginAction === PluginAction.INSTALL ? "5.5em" : "10em"}; + text-align: left; +`; + +const ListChild = styled.div` + flex-grow: 4; +`; class PluginModal extends React.Component<Props, State> { constructor(props: Props) { @@ -151,7 +144,7 @@ class PluginModal extends React.Component<Props, State> { }; renderDependencies() { - const { plugin, classes, t } = this.props; + const { plugin, t } = this.props; let dependencies = null; if (plugin.dependencies && plugin.dependencies.length > 0) { @@ -159,7 +152,7 @@ class PluginModal extends React.Component<Props, State> { <div className="media"> <Notification type="warning"> <strong>{t("plugins.modal.dependencyNotification")}</strong> - <ul className={classes.listSpacing}> + <ul> {plugin.dependencies.map((dependency, index) => { return <li key={index}>{dependency}</li>; })} @@ -206,7 +199,7 @@ class PluginModal extends React.Component<Props, State> { render() { const { restart } = this.state; - const { plugin, pluginAction, onClose, classes, t } = this.props; + const { plugin, pluginAction, onClose, t } = this.props; const body = ( <> @@ -218,88 +211,58 @@ class PluginModal extends React.Component<Props, State> { <div className="media"> <div className="media-content"> <div className="field is-horizontal"> - <div - className={classNames( - classes.userLabelAlignment, - pluginAction === PluginAction.INSTALL - ? classes.userLabelMarginSmall - : classes.userLabelMarginLarge, - "field-label is-inline-flex" - )} + <ListParent + className={classNames("field-label", "is-inline-flex")} + pluginAction={pluginAction} > {t("plugins.modal.author")}: - </div> - <div - className={classNames( - classes.userFieldFlex, - "field-body is-inline-flex" - )} - > + </ListParent> + <ListChild className={classNames("field-body", "is-inline-flex")}> {plugin.author} - </div> + </ListChild> </div> {pluginAction === PluginAction.INSTALL && ( <div className="field is-horizontal"> - <div - className={classNames( - classes.userLabelAlignment, - classes.userLabelMarginSmall, - "field-label is-inline-flex" - )} + <ListParent + className={classNames("field-label", "is-inline-flex")} + pluginAction={pluginAction} > {t("plugins.modal.version")}: - </div> - <div - className={classNames( - classes.userFieldFlex, - "field-body is-inline-flex" - )} + </ListParent> + <ListChild + className={classNames("field-body", "is-inline-flex")} > {plugin.version} - </div> + </ListChild> </div> )} {(pluginAction === PluginAction.UPDATE || pluginAction === PluginAction.UNINSTALL) && ( <div className="field is-horizontal"> - <div - className={classNames( - classes.userLabelAlignment, - classes.userLabelMarginLarge, - "field-label is-inline-flex" - )} + <ListParent + className={classNames("field-label", "is-inline-flex")} > {t("plugins.modal.currentVersion")}: - </div> - <div - className={classNames( - classes.userFieldFlex, - "field-body is-inline-flex" - )} + </ListParent> + <ListChild + className={classNames("field-body", "is-inline-flex")} > {plugin.version} - </div> + </ListChild> </div> )} {pluginAction === PluginAction.UPDATE && ( <div className="field is-horizontal"> - <div - className={classNames( - classes.userLabelAlignment, - classes.userLabelMarginLarge, - "field-label is-inline-flex" - )} + <ListParent + className={classNames("field-label", "is-inline-flex")} > {t("plugins.modal.newVersion")}: - </div> - <div - className={classNames( - classes.userFieldFlex, - "field-body is-inline-flex" - )} + </ListParent> + <ListChild + className={classNames("field-body", "is-inline-flex")} > {plugin.newVersion} - </div> + </ListChild> </div> )} {this.renderDependencies()} @@ -333,7 +296,4 @@ class PluginModal extends React.Component<Props, State> { } } -export default compose( - injectSheet(styles), - translate("admin") -)(PluginModal); +export default translate("admin")(PluginModal); diff --git a/scm-ui/ui-webapp/src/admin/plugins/components/PluginTopActions.js b/scm-ui/ui-webapp/src/admin/plugins/components/PluginTopActions.js index d30c755eb2..5f0bfc482e 100644 --- a/scm-ui/ui-webapp/src/admin/plugins/components/PluginTopActions.js +++ b/scm-ui/ui-webapp/src/admin/plugins/components/PluginTopActions.js @@ -1,32 +1,31 @@ // @flow import * as React from "react"; import classNames from "classnames"; -import injectSheet from "react-jss"; - -const styles = { - container: { - display: "flex", - justifyContent: "flex-end", - alignItems: "center" - } -}; +import styled from "styled-components"; type Props = { - children?: React.Node, - - // context props - classes: any + children?: React.Node }; -class PluginTopActions extends React.Component<Props> { +const ChildWrapper = styled.div` + justify-content: flex-end; + align-items: center; +`; + +export default class PluginTopActions extends React.Component<Props> { render() { - const { children, classes } = this.props; + const { children } = this.props; return ( - <div className={classNames(classes.container, "column", "is-one-fifths", "is-mobile-action-spacing")}> + <ChildWrapper + className={classNames( + "column", + "is-flex", + "is-one-fifths", + "is-mobile-action-spacing" + )} + > {children} - </div> + </ChildWrapper> ); } } - -export default injectSheet(styles)(PluginTopActions); diff --git a/scm-ui/ui-webapp/src/admin/roles/components/AvailableVerbs.js b/scm-ui/ui-webapp/src/admin/roles/components/AvailableVerbs.js index d544557644..e398b4d093 100644 --- a/scm-ui/ui-webapp/src/admin/roles/components/AvailableVerbs.js +++ b/scm-ui/ui-webapp/src/admin/roles/components/AvailableVerbs.js @@ -1,7 +1,5 @@ //@flow import React from "react"; -import { compose } from "redux"; -import injectSheet from "react-jss"; import { translate } from "react-i18next"; import type { RepositoryRole } from "@scm-manager/ui-types"; @@ -9,25 +7,18 @@ type Props = { role: RepositoryRole, // context props - classes: any, t: string => string }; -const styles = { - spacing: { - padding: "0 !important" - } -}; - class AvailableVerbs extends React.Component<Props> { render() { - const { role, t, classes } = this.props; + const { role, t } = this.props; let verbs = null; if (role.verbs.length > 0) { verbs = ( <tr> - <td className={classes.spacing}> + <td className="is-paddingless"> <ul> {role.verbs.map(verb => { return ( @@ -43,7 +34,4 @@ class AvailableVerbs extends React.Component<Props> { } } -export default compose( - injectSheet(styles), - translate("plugins") -)(AvailableVerbs); +export default translate("plugins")(AvailableVerbs); diff --git a/scm-ui/ui-webapp/src/admin/roles/components/SystemRoleTag.js b/scm-ui/ui-webapp/src/admin/roles/components/SystemRoleTag.js index 9ee4434822..1305fecae3 100644 --- a/scm-ui/ui-webapp/src/admin/roles/components/SystemRoleTag.js +++ b/scm-ui/ui-webapp/src/admin/roles/components/SystemRoleTag.js @@ -1,39 +1,30 @@ //@flow import React from "react"; -import injectSheet from "react-jss"; import { translate } from "react-i18next"; +import styled from "styled-components"; import { Tag } from "@scm-manager/ui-components"; type Props = { system?: boolean, // context props - classes: any, t: string => string }; -const styles = { - tag: { - marginLeft: "0.75rem", - verticalAlign: "inherit" - } -}; +const LeftMarginTag = styled(Tag)` + margin-left: 0.75rem; + vertical-align: inherit; +`; class SystemRoleTag extends React.Component<Props> { render() { - const { system, classes, t } = this.props; + const { system, t } = this.props; if (system) { - return ( - <Tag - className={classes.tag} - color="dark" - label={t("repositoryRole.system")} - /> - ); + return <LeftMarginTag color="dark" label={t("repositoryRole.system")} />; } return null; } } -export default injectSheet(styles)(translate("admin")(SystemRoleTag)); +export default translate("admin")(SystemRoleTag); diff --git a/scm-ui/ui-webapp/src/components/InfoBox.js b/scm-ui/ui-webapp/src/components/InfoBox.js index f804fa0e32..82a7d34248 100644 --- a/scm-ui/ui-webapp/src/components/InfoBox.js +++ b/scm-ui/ui-webapp/src/components/InfoBox.js @@ -1,84 +1,83 @@ //@flow import * as React from "react"; -import injectSheet from "react-jss"; -import classNames from "classnames"; import { translate } from "react-i18next"; +import classNames from "classnames"; +import styled from "styled-components"; import type { InfoItem } from "./InfoItem"; - -const styles = { - image: { - display: "flex", - alignItems: "center", - justifyContent: "center", - flexDirection: "column", - width: 160, - height: 160 - }, - icon: { - color: "#bff1e6" - }, - label: { - marginTop: "0.5em" - }, - content: { - marginLeft: "1.5em", - minHeight: "10.5rem" - }, - link: { - display: "block", - marginBottom: "1.5rem" - } -}; +import { Icon } from "@scm-manager/ui-components"; type Props = { type: "plugin" | "feature", item: InfoItem, // context props - classes: any, t: string => string }; -class InfoBox extends React.Component<Props> { +const BottomMarginA = styled.a` + display: blocK; + margin-bottom: 1.5rem; +`; +const FixedSizedIconWrapper = styled.div` + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + width: 160px; + height: 160px; +`; + +const LightBlueIcon = styled(Icon)` + margin-bottom: 0.5em; + color: #bff1e6; +`; + +const ContentWrapper = styled.div` + min-height: 1.5rem; + margin-left: 1.5em; +`; + +class InfoBox extends React.Component<Props> { renderBody = () => { const { item, t } = this.props; - - const bodyClasses = classNames("media-content", "content", this.props.classes.content); const title = item ? item.title : t("login.loading"); const summary = item ? item.summary : t("login.loading"); return ( - <div className={bodyClasses}> + <ContentWrapper className={classNames("media-content", "content")}> <h4 className="has-text-link">{title}</h4> <p>{summary}</p> - </div> + </ContentWrapper> ); - }; render() { - const { item, type, classes, t } = this.props; + const { item, type, t } = this.props; const icon = type === "plugin" ? "puzzle-piece" : "star"; return ( - <a href={item._links.self.href} className={classes.link}> + <BottomMarginA href={item._links.self.href}> <div className="box media"> <figure className="media-left"> - <div - className={classNames("image", "box", "has-background-info", "has-text-white", "has-text-weight-bold", classes.image)}> - <i className={classNames("fas", "fa-" + icon, "fa-2x", classes.icon)}/> - <div className={classNames("is-size-4", classes.label)}>{t("login." + type)}</div> - <div className={classNames("is-size-4")}>{t("login.tip")}</div> - </div> + <FixedSizedIconWrapper + className={classNames( + "image", + "box", + "has-text-weight-bold", + "has-text-white", + "has-background-info" + )} + > + <LightBlueIcon className="fa-2x" name={icon} color="inherit" /> + <div className="is-size-4">{t("login." + type)}</div> + <div className="is-size-4">{t("login.tip")}</div> + </FixedSizedIconWrapper> </figure> {this.renderBody()} </div> - </a> + </BottomMarginA> ); } - } -export default injectSheet(styles)(translate("commons")(InfoBox)); - - +export default translate("commons")(InfoBox); diff --git a/scm-ui/ui-webapp/src/components/LoginForm.js b/scm-ui/ui-webapp/src/components/LoginForm.js index 95d4c0b7d3..9cd4e8a617 100644 --- a/scm-ui/ui-webapp/src/components/LoginForm.js +++ b/scm-ui/ui-webapp/src/components/LoginForm.js @@ -1,27 +1,14 @@ //@flow import React from "react"; import { translate } from "react-i18next"; -import { Image, ErrorNotification, InputField, SubmitButton, UnauthorizedError } from "@scm-manager/ui-components"; -import classNames from "classnames"; -import injectSheet from "react-jss"; - -const styles = { - avatar: { - marginTop: "-70px", - paddingBottom: "20px" - }, - avatarImage: { - border: "1px solid lightgray", - padding: "5px", - background: "#fff", - borderRadius: "50%", - width: "128px", - height: "128px" - }, - avatarSpacing: { - marginTop: "5rem" - } -}; +import styled from "styled-components"; +import { + Image, + ErrorNotification, + InputField, + SubmitButton, + UnauthorizedError +} from "@scm-manager/ui-components"; type Props = { error?: Error, @@ -29,8 +16,7 @@ type Props = { loginHandler: (username: string, password: string) => void, // context props - t: string => string, - classes: any + t: string => string }; type State = { @@ -38,8 +24,25 @@ type State = { password: string }; -class LoginForm extends React.Component<Props, State> { +const TopMarginBox = styled.div` + margin-top: 5rem; +`; +const AvatarWrapper = styled.figure` + margin-top: -70px; + padding-bottom: 20px; +`; + +const AvatarImage = styled(Image)` + width: 128px; + height: 128px; + padding: 5px; + background: #fff; + border: 1px solid lightgray; + border-radius: 50%; +`; + +class LoginForm extends React.Component<Props, State> { constructor(props: Props) { super(props); this.state = { username: "", password: "" }; @@ -48,10 +51,7 @@ class LoginForm extends React.Component<Props, State> { handleSubmit = (event: Event) => { event.preventDefault(); if (this.isValid()) { - this.props.loginHandler( - this.state.username, - this.state.password - ); + this.props.loginHandler(this.state.username, this.state.password); } }; @@ -77,20 +77,16 @@ class LoginForm extends React.Component<Props, State> { } render() { - const { loading, classes, t } = this.props; + const { loading, t } = this.props; return ( <div className="column is-4 box has-text-centered has-background-white-ter"> <h3 className="title">{t("login.title")}</h3> <p className="subtitle">{t("login.subtitle")}</p> - <div className={classNames("box", classes.avatarSpacing)}> - <figure className={classes.avatar}> - <Image - className={classes.avatarImage} - src="/images/blib.jpg" - alt={t("login.logo-alt")} - /> - </figure> - <ErrorNotification error={this.areCredentialsInvalid()}/> + <TopMarginBox className="box"> + <AvatarWrapper> + <AvatarImage src="/images/blib.jpg" alt={t("login.logo-alt")} /> + </AvatarWrapper> + <ErrorNotification error={this.areCredentialsInvalid()} /> <form onSubmit={this.handleSubmit}> <InputField placeholder={t("login.username-placeholder")} @@ -108,13 +104,10 @@ class LoginForm extends React.Component<Props, State> { loading={loading} /> </form> - </div> + </TopMarginBox> </div> ); } - } -export default injectSheet(styles)(translate("commons")(LoginForm)); - - +export default translate("commons")(LoginForm); diff --git a/scm-ui/ui-webapp/src/containers/Login.js b/scm-ui/ui-webapp/src/containers/Login.js index f8246ab88b..ed231b53be 100644 --- a/scm-ui/ui-webapp/src/containers/Login.js +++ b/scm-ui/ui-webapp/src/containers/Login.js @@ -1,23 +1,18 @@ //@flow import React from "react"; +import { connect } from "react-redux"; import { Redirect, withRouter } from "react-router-dom"; +import { compose } from "redux"; +import { translate } from "react-i18next"; +import styled from "styled-components"; import { login, isAuthenticated, isLoginPending, getLoginFailure } from "../modules/auth"; -import { connect } from "react-redux"; import { getLoginLink, getLoginInfoLink } from "../modules/indexResource"; import LoginInfo from "../components/LoginInfo"; -import classNames from "classnames"; -import injectSheet from "react-jss"; - -const styles = { - section: { - paddingTop: "2em" - } -}; type Props = { authenticated: boolean, @@ -30,14 +25,16 @@ type Props = { login: (link: string, username: string, password: string) => void, // context props - classes: any, t: string => string, from: any, location: any }; -class Login extends React.Component<Props> { +const HeroSection = styled.section` + padding-top: 2em; +`; +class Login extends React.Component<Props> { handleLogin = (username: string, password: string): void => { const { link, login } = this.props; login(link, username, password); @@ -45,18 +42,18 @@ class Login extends React.Component<Props> { renderRedirect = () => { const { from } = this.props.location.state || { from: { pathname: "/" } }; - return <Redirect to={from}/>; + return <Redirect to={from} />; }; render() { - const { authenticated, classes, ...restProps } = this.props; + const { authenticated, ...restProps } = this.props; if (authenticated) { return this.renderRedirect(); } return ( - <section className={classNames("hero", classes.section )}> + <HeroSection className="hero"> <div className="hero-body"> <div className="container"> <div className="columns is-centered"> @@ -64,8 +61,8 @@ class Login extends React.Component<Props> { </div> </div> </div> - </section> - ); + </HeroSection> + ); } } @@ -91,10 +88,10 @@ const mapDispatchToProps = dispatch => { }; }; -const StyledLogin = injectSheet(styles)( +export default compose( + withRouter, connect( mapStateToProps, mapDispatchToProps - )(Login) -); -export default withRouter(StyledLogin); + ) +)(Login); diff --git a/scm-ui/ui-webapp/src/containers/ProfileInfo.js b/scm-ui/ui-webapp/src/containers/ProfileInfo.js index a861524c80..6c430f3fc4 100644 --- a/scm-ui/ui-webapp/src/containers/ProfileInfo.js +++ b/scm-ui/ui-webapp/src/containers/ProfileInfo.js @@ -1,29 +1,20 @@ // @flow import React from "react"; +import { translate } from "react-i18next"; import type { Me } from "@scm-manager/ui-types"; import { MailLink, AvatarWrapper, AvatarImage } from "@scm-manager/ui-components"; -import { compose } from "redux"; -import { translate } from "react-i18next"; -import injectSheet from "react-jss"; type Props = { me: Me, // Context props - classes: any, t: string => string }; -const styles = { - spacing: { - padding: "0 !important" - } -}; - class ProfileInfo extends React.Component<Props> { render() { const { me, t } = this.props; @@ -62,14 +53,14 @@ class ProfileInfo extends React.Component<Props> { } renderGroups() { - const { me, t, classes } = this.props; + const { me, t } = this.props; let groups = null; if (me.groups.length > 0) { groups = ( <tr> <th>{t("profile.groups")}</th> - <td className={classes.spacing}> + <td className="is-paddingless"> <ul> {me.groups.map(group => { return <li>{group}</li>; @@ -83,7 +74,4 @@ class ProfileInfo extends React.Component<Props> { } } -export default compose( - injectSheet(styles), - translate("commons") -)(ProfileInfo); +export default translate("commons")(ProfileInfo); diff --git a/scm-ui/ui-webapp/src/groups/components/table/Details.js b/scm-ui/ui-webapp/src/groups/components/table/Details.js index 4d64e3c63e..a1aa4d232e 100644 --- a/scm-ui/ui-webapp/src/groups/components/table/Details.js +++ b/scm-ui/ui-webapp/src/groups/components/table/Details.js @@ -1,79 +1,71 @@ //@flow import React from "react"; -import type { Group } from "@scm-manager/ui-types"; -import GroupMember from "./GroupMember"; -import { DateFromNow, Checkbox } from "@scm-manager/ui-components"; import { translate } from "react-i18next"; -import injectSheet from "react-jss"; +import type { Group } from "@scm-manager/ui-types"; +import { DateFromNow, Checkbox } from "@scm-manager/ui-components"; +import GroupMember from "./GroupMember"; type Props = { group: Group, // Context props - classes: any, t: string => string }; -const styles = { - spacing: { - padding: "0 !important" - } -}; - class Details extends React.Component<Props> { render() { const { group, t } = this.props; return ( <table className="table content"> <tbody> - <tr> - <th>{t("group.name")}</th> - <td>{group.name}</td> - </tr> - <tr> - <th>{t("group.description")}</th> - <td>{group.description}</td> - </tr> - <tr> - <th>{t("group.external")}</th> - <td> - <Checkbox checked={group.external} /> - </td> - </tr> - <tr> - <th>{t("group.type")}</th> - <td>{group.type}</td> - </tr> - <tr> - <th>{t("group.creationDate")}</th> - <td> - <DateFromNow date={group.creationDate} /> - </td> - </tr> - <tr> - <th>{t("group.lastModified")}</th> - <td> - <DateFromNow date={group.lastModified} /> - </td> - </tr> - {this.renderMembers()} + <tr> + <th>{t("group.name")}</th> + <td>{group.name}</td> + </tr> + <tr> + <th>{t("group.description")}</th> + <td>{group.description}</td> + </tr> + <tr> + <th>{t("group.external")}</th> + <td> + <Checkbox checked={group.external} /> + </td> + </tr> + <tr> + <th>{t("group.type")}</th> + <td>{group.type}</td> + </tr> + <tr> + <th>{t("group.creationDate")}</th> + <td> + <DateFromNow date={group.creationDate} /> + </td> + </tr> + <tr> + <th>{t("group.lastModified")}</th> + <td> + <DateFromNow date={group.lastModified} /> + </td> + </tr> + {this.renderMembers()} </tbody> </table> ); } renderMembers() { - const { group, t, classes } = this.props; + const { group, t } = this.props; let member = null; if (group.members.length > 0) { member = ( <tr> <th>{t("group.members")}</th> - <td className={classes.spacing}> + <td className="is-paddingless"> <ul> {group._embedded.members.map((member, index) => { - return <GroupMember key={index} member={member}/>; + return <GroupMember key={index} member={member} />; })} </ul> </td> @@ -84,4 +76,4 @@ class Details extends React.Component<Props> { } } -export default injectSheet(styles)(translate("groups")(Details)); +export default translate("groups")(Details); diff --git a/scm-ui/ui-webapp/src/permissions/components/SetPermissions.js b/scm-ui/ui-webapp/src/permissions/components/SetPermissions.js index 69b6f621cc..a19c01d97d 100644 --- a/scm-ui/ui-webapp/src/permissions/components/SetPermissions.js +++ b/scm-ui/ui-webapp/src/permissions/components/SetPermissions.js @@ -2,8 +2,8 @@ import React from "react"; import { connect } from "react-redux"; import { translate } from "react-i18next"; -import injectSheet from "react-jss"; import classNames from "classnames"; +import styled from "styled-components"; import type { Link } from "@scm-manager/ui-types"; import { Notification, @@ -11,10 +11,7 @@ import { SubmitButton } from "@scm-manager/ui-components"; import { getLink } from "../../modules/indexResource"; -import { - loadPermissionsForEntity, - setPermissions -} from "./handlePermissions"; +import { loadPermissionsForEntity, setPermissions } from "./handlePermissions"; import PermissionCheckbox from "./PermissionCheckbox"; type Props = { @@ -22,7 +19,6 @@ type Props = { selectedPermissionsLink: Link, // context props - classes: any, t: string => string }; @@ -35,16 +31,14 @@ type State = { overwritePermissionsLink?: Link }; -const styles = { - permissionsWrapper: { - paddingBottom: "0", +const PermissionsWrapper = styled.div` + padding-bottom: 0; - "& .field .control": { - width: "100%", - wordWrap: "break-word" - } + & .field .control { + width: 100%; + word-wrap: break-word; } -}; +`; class SetPermissions extends React.Component<Props, State> { constructor(props: Props) { @@ -151,13 +145,12 @@ class SetPermissions extends React.Component<Props, State> { } renderPermissions = () => { - const { classes } = this.props; const { overwritePermissionsLink, permissions } = this.state; const permissionArray = Object.keys(permissions); return ( <div className="columns"> - <div className={classNames("column", "is-half", classes.permissionsWrapper)}> - {permissionArray.slice(0, (permissionArray.length/2)+1).map(p => ( + <PermissionsWrapper className={classNames("column", "is-half")}> + {permissionArray.slice(0, permissionArray.length / 2 + 1).map(p => ( <PermissionCheckbox key={p} permission={p} @@ -166,18 +159,20 @@ class SetPermissions extends React.Component<Props, State> { disabled={!overwritePermissionsLink} /> ))} - </div> - <div className={classNames("column", "is-half", classes.permissionsWrapper)}> - {permissionArray.slice((permissionArray.length/2)+1, permissionArray.length).map(p => ( - <PermissionCheckbox - key={p} - permission={p} - checked={permissions[p]} - onChange={this.valueChanged} - disabled={!overwritePermissionsLink} - /> - ))} - </div> + </PermissionsWrapper> + <PermissionsWrapper className={classNames("column", "is-half")}> + {permissionArray + .slice(permissionArray.length / 2 + 1, permissionArray.length) + .map(p => ( + <PermissionCheckbox + key={p} + permission={p} + checked={permissions[p]} + onChange={this.valueChanged} + disabled={!overwritePermissionsLink} + /> + ))} + </PermissionsWrapper> </div> ); }; @@ -208,4 +203,5 @@ const mapStateToProps = state => { }; export default connect(mapStateToProps)( - injectSheet(styles)(translate("permissions")(SetPermissions))); + translate("permissions")(SetPermissions) +); diff --git a/scm-ui/ui-webapp/src/repos/branches/components/DefaultBranchTag.js b/scm-ui/ui-webapp/src/repos/branches/components/DefaultBranchTag.js index 4a549426cf..5425662306 100644 --- a/scm-ui/ui-webapp/src/repos/branches/components/DefaultBranchTag.js +++ b/scm-ui/ui-webapp/src/repos/branches/components/DefaultBranchTag.js @@ -1,39 +1,30 @@ //@flow import React from "react"; -import injectSheet from "react-jss"; import { translate } from "react-i18next"; +import styled from "styled-components"; import { Tag } from "@scm-manager/ui-components"; type Props = { defaultBranch?: boolean, // context props - classes: any, t: string => string }; -const styles = { - tag: { - marginLeft: "0.75rem", - verticalAlign: "inherit" - } -}; +const LeftMarginTag = styled(Tag)` + vertical-align: inherit; + margin-left: 0.75rem; +`; class DefaultBranchTag extends React.Component<Props> { render() { - const { defaultBranch, classes, t } = this.props; + const { defaultBranch, t } = this.props; if (defaultBranch) { - return ( - <Tag - className={classes.tag} - color="dark" - label={t("branch.defaultTag")} - /> - ); + return <LeftMarginTag color="dark" label={t("branch.defaultTag")} />; } return null; } } -export default injectSheet(styles)(translate("repos")(DefaultBranchTag)); +export default translate("repos")(DefaultBranchTag); diff --git a/scm-ui/ui-webapp/src/repos/components/changesets/ChangesetDetails.js b/scm-ui/ui-webapp/src/repos/components/changesets/ChangesetDetails.js index 33d63a30d4..39b97af86b 100644 --- a/scm-ui/ui-webapp/src/repos/components/changesets/ChangesetDetails.js +++ b/scm-ui/ui-webapp/src/repos/components/changesets/ChangesetDetails.js @@ -1,9 +1,10 @@ //@flow import React from "react"; -import type { Changeset, Repository } from "@scm-manager/ui-types"; import { Interpolate, translate } from "react-i18next"; -import injectSheet from "react-jss"; - +import classNames from "classnames"; +import styled from "styled-components"; +import type { Changeset, Repository, Tag } from "@scm-manager/ui-types"; +import { ExtensionPoint } from "@scm-manager/ui-extensions"; import { DateFromNow, ChangesetId, @@ -15,31 +16,27 @@ import { changesets } from "@scm-manager/ui-components"; -import classNames from "classnames"; -import type { Tag } from "@scm-manager/ui-types"; -import { ExtensionPoint } from "@scm-manager/ui-extensions"; - -const styles = { - spacing: { - marginRight: "1em" - }, - tags: { - "& .tag": { - marginLeft: ".25rem" - } - } -}; - type Props = { changeset: Changeset, repository: Repository, - t: string => string, - classes: any + + // context props + t: string => string }; +const RightMarginP = styled.p` + margin-right: 1em; +`; + +const TagsWrapper = styled.div` + & .tag { + margin-left: 0.25rem; + } +`; + class ChangesetDetails extends React.Component<Props> { render() { - const { changeset, repository, classes } = this.props; + const { changeset, repository } = this.props; const description = changesets.parseDescription(changeset.description); @@ -62,20 +59,16 @@ class ChangesetDetails extends React.Component<Props> { </h4> <article className="media"> <AvatarWrapper> - <p className={classNames("image", "is-64x64", classes.spacing)}> + <RightMarginP className={classNames("image", "is-64x64")}> <AvatarImage person={changeset.author} /> - </p> + </RightMarginP> </AvatarWrapper> <div className="media-content"> <p> <ChangesetAuthor changeset={changeset} /> </p> <p> - <Interpolate - i18nKey="changeset.summary" - id={id} - time={date} - /> + <Interpolate i18nKey="changeset.summary" id={id} time={date} /> </p> </div> <div className="media-right">{this.renderTags()}</div> @@ -106,24 +99,22 @@ class ChangesetDetails extends React.Component<Props> { } getTags = () => { - const { changeset } = this.props; - return changeset._embedded.tags || []; + return this.props.changeset._embedded.tags || []; }; renderTags = () => { - const { classes } = this.props; const tags = this.getTags(); if (tags.length > 0) { return ( - <div className={classNames("level-item", classes.tags)}> + <TagsWrapper className="level-item"> {tags.map((tag: Tag) => { return <ChangesetTag key={tag.name} tag={tag} />; })} - </div> + </TagsWrapper> ); } return null; }; } -export default injectSheet(styles)(translate("repos")(ChangesetDetails)); +export default translate("repos")(ChangesetDetails); diff --git a/scm-ui/ui-webapp/src/repos/components/list/RepositoryEntry.js b/scm-ui/ui-webapp/src/repos/components/list/RepositoryEntry.js index 471f900aa1..5a7af9d1bb 100644 --- a/scm-ui/ui-webapp/src/repos/components/list/RepositoryEntry.js +++ b/scm-ui/ui-webapp/src/repos/components/list/RepositoryEntry.js @@ -18,7 +18,7 @@ class RepositoryEntry extends React.Component<Props> { if (repository._links["branches"]) { return ( <RepositoryEntryLink - iconClass="fas fa-code-branch fa-lg" + icon="code-branch" to={repositoryLink + "/branches"} /> ); @@ -30,7 +30,7 @@ class RepositoryEntry extends React.Component<Props> { if (repository._links["changesets"]) { return ( <RepositoryEntryLink - iconClass="fas fa-exchange-alt fa-lg" + icon="exchange-alt" to={repositoryLink + "/changesets"} /> ); @@ -41,10 +41,7 @@ class RepositoryEntry extends React.Component<Props> { renderSourcesLink = (repository: Repository, repositoryLink: string) => { if (repository._links["sources"]) { return ( - <RepositoryEntryLink - iconClass="fa-code fa-lg" - to={repositoryLink + "/sources"} - /> + <RepositoryEntryLink icon="code" to={repositoryLink + "/sources"} /> ); } return null; @@ -54,7 +51,7 @@ class RepositoryEntry extends React.Component<Props> { if (repository._links["update"]) { return ( <RepositoryEntryLink - iconClass="fa-cog fa-lg" + icon="cog" to={repositoryLink + "/settings/general"} /> ); diff --git a/scm-ui/ui-webapp/src/repos/components/list/RepositoryEntryLink.js b/scm-ui/ui-webapp/src/repos/components/list/RepositoryEntryLink.js index e4fa2a623c..c1b61f9267 100644 --- a/scm-ui/ui-webapp/src/repos/components/list/RepositoryEntryLink.js +++ b/scm-ui/ui-webapp/src/repos/components/list/RepositoryEntryLink.js @@ -1,35 +1,27 @@ -//@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); +//@flow +import React from "react"; +import { Link } from "react-router-dom"; +import styled from "styled-components"; +import { Icon } from "@scm-manager/ui-components"; + +type Props = { + to: string, + icon: string +}; + +const PointerEventsLink = styled(Link)` + pointer-events: all; +`; + +class RepositoryEntryLink extends React.Component<Props> { + render() { + const { to, icon } = this.props; + return ( + <PointerEventsLink className="level-item" to={to}> + <Icon className="fa-lg" name={icon} color="inherit" /> + </PointerEventsLink> + ); + } +} + +export default RepositoryEntryLink; diff --git a/scm-ui/ui-webapp/src/repos/permissions/containers/SinglePermission.js b/scm-ui/ui-webapp/src/repos/permissions/containers/SinglePermission.js index 37f3436af3..9b04aae413 100644 --- a/scm-ui/ui-webapp/src/repos/permissions/containers/SinglePermission.js +++ b/scm-ui/ui-webapp/src/repos/permissions/containers/SinglePermission.js @@ -1,7 +1,11 @@ // @flow import React from "react"; -import type { RepositoryRole, Permission } from "@scm-manager/ui-types"; +import { connect } from "react-redux"; +import type { History } from "history"; import { translate } from "react-i18next"; +import styled from "styled-components"; +import type { RepositoryRole, Permission } from "@scm-manager/ui-types"; +import { Button, Icon } from "@scm-manager/ui-components"; import { modifyPermission, isModifyPermissionPending, @@ -9,14 +13,9 @@ import { isDeletePermissionPending, findVerbsForRole } from "../modules/permissions"; -import { connect } from "react-redux"; -import type { History } from "history"; -import { Button, Icon } from "@scm-manager/ui-components"; import DeletePermissionButton from "../components/buttons/DeletePermissionButton"; import RoleSelector from "../components/RoleSelector"; import AdvancedPermissionsDialog from "./AdvancedPermissionsDialog"; -import classNames from "classnames"; -import injectSheet from "react-jss"; type Props = { availableRepositoryRoles: RepositoryRole[], @@ -39,8 +38,7 @@ type Props = { namespace: string, name: string ) => void, - deleteLoading: boolean, - classes: any + deleteLoading: boolean }; type State = { @@ -48,15 +46,14 @@ type State = { showAdvancedDialog: boolean }; -const styles = { - centerMiddle: { - display: "table-cell", - verticalAlign: "middle !important" - }, - columnWidth: { - width: "100%" - } -}; +const FullWidthTr = styled.tr` + width: 100%; +`; + +const VCenteredTd = styled.td` + display: table-cell; + vertical-align: middle !important; +`; class SinglePermission extends React.Component<Props, State> { constructor(props: Props) { @@ -103,16 +100,15 @@ class SinglePermission extends React.Component<Props, State> { }; render() { - const { permission, showAdvancedDialog } = this.state; const { - t, availableRepositoryRoles, availableRepositoryVerbs, loading, namespace, repoName, - classes + t } = this.props; + const { permission, showAdvancedDialog } = this.state; const availableRoleNames = !!availableRepositoryRoles && availableRepositoryRoles.map(r => r.name); const readOnly = !this.mayChangePermissions(); @@ -151,18 +147,18 @@ class SinglePermission extends React.Component<Props, State> { ); return ( - <tr className={classes.columnWidth}> - <td className={classes.centerMiddle}> + <FullWidthTr> + <VCenteredTd> {iconType} {permission.name} - </td> + </VCenteredTd> {roleSelector} - <td className={classes.centerMiddle}> + <VCenteredTd> <Button label={t("permission.advanced-button.label")} action={this.handleDetailedPermissionsPressed} /> - </td> - <td className={classNames("is-darker", classes.centerMiddle)}> + </VCenteredTd> + <VCenteredTd className="is-darker"> <DeletePermissionButton permission={permission} namespace={namespace} @@ -171,8 +167,8 @@ class SinglePermission extends React.Component<Props, State> { loading={this.props.deleteLoading} /> {advancedDialog} - </td> - </tr> + </VCenteredTd> + </FullWidthTr> ); } @@ -274,4 +270,4 @@ const mapDispatchToProps = dispatch => { export default connect( mapStateToProps, mapDispatchToProps -)(translate("repos")(injectSheet(styles)(SinglePermission))); +)(translate("repos")(SinglePermission)); diff --git a/scm-ui/ui-webapp/src/repos/sources/components/FileTree.js b/scm-ui/ui-webapp/src/repos/sources/components/FileTree.js index d62a2b44db..fa229aded1 100644 --- a/scm-ui/ui-webapp/src/repos/sources/components/FileTree.js +++ b/scm-ui/ui-webapp/src/repos/sources/components/FileTree.js @@ -1,9 +1,11 @@ //@flow import React from "react"; -import { translate } from "react-i18next"; +import { compose } from "redux"; import { connect } from "react-redux"; -import injectSheet from "react-jss"; -import FileTreeLeaf from "./FileTreeLeaf"; +import { withRouter } from "react-router-dom"; +import { translate } from "react-i18next"; +import styled from "styled-components"; +import { binder } from "@scm-manager/ui-extensions"; import type { Repository, File } from "@scm-manager/ui-types"; import { ErrorNotification, @@ -15,15 +17,7 @@ import { isFetchSourcesPending, getSources } from "../modules/sources"; -import { withRouter } from "react-router-dom"; -import { compose } from "redux"; -import { binder } from "@scm-manager/ui-extensions"; - -const styles = { - iconColumn: { - width: "16px" - } -}; +import FileTreeLeaf from "./FileTreeLeaf"; type Props = { loading: boolean, @@ -33,12 +27,16 @@ type Props = { revision: string, path: string, baseUrl: string, + // context props - classes: any, t: string => string, match: any }; +const FixedWidthTh = styled.th` + width: 16px; +`; + export function findParent(path: string) { if (path.endsWith("/")) { path = path.substring(0, path.length - 1); @@ -70,7 +68,7 @@ class FileTree extends React.Component<Props> { } renderSourcesTable() { - const { tree, revision, path, baseUrl, classes, t } = this.props; + const { tree, revision, path, baseUrl, t } = this.props; const files = []; @@ -114,7 +112,7 @@ class FileTree extends React.Component<Props> { <table className="table table-hover table-sm is-fullwidth"> <thead> <tr> - <th className={classes.iconColumn} /> + <FixedWidthTh /> <th>{t("sources.file-tree.name")}</th> <th className="is-hidden-mobile"> {t("sources.file-tree.length")} @@ -165,4 +163,4 @@ const mapStateToProps = (state: any, ownProps: Props) => { export default compose( withRouter, connect(mapStateToProps) -)(injectSheet(styles)(translate("repos")(FileTree))); +)(translate("repos")(FileTree)); diff --git a/scm-ui/ui-webapp/src/repos/sources/components/FileTreeLeaf.js b/scm-ui/ui-webapp/src/repos/sources/components/FileTreeLeaf.js index 2e1a81c0e9..2c30a78f0f 100644 --- a/scm-ui/ui-webapp/src/repos/sources/components/FileTreeLeaf.js +++ b/scm-ui/ui-webapp/src/repos/sources/components/FileTreeLeaf.js @@ -1,30 +1,22 @@ //@flow import * as React from "react"; -import injectSheet from "react-jss"; +import { Link } from "react-router-dom"; +import classNames from "classnames"; +import styled from "styled-components"; +import { binder, ExtensionPoint } from "@scm-manager/ui-extensions"; +import type { File } from "@scm-manager/ui-types"; import { DateFromNow, FileSize } from "@scm-manager/ui-components"; import FileIcon from "./FileIcon"; -import { Link } from "react-router-dom"; -import type { File } from "@scm-manager/ui-types"; -import classNames from "classnames"; -import { binder, ExtensionPoint } from "@scm-manager/ui-extensions"; - -const styles = { - iconColumn: { - width: "16px" - }, - wordBreakMinWidth: { - minWidth: "10em" - } -}; type Props = { file: File, - baseUrl: string, - - // context props - classes: any + baseUrl: string }; +const MinWidthTd = styled.td` + min-width: 10em; +`; + export function createLink(base: string, file: File) { let link = base; if (file.path) { @@ -40,7 +32,7 @@ export function createLink(base: string, file: File) { return link; } -class FileTreeLeaf extends React.Component<Props> { +export default class FileTreeLeaf extends React.Component<Props> { createLink = (file: File) => { return createLink(this.props.baseUrl, file); }; @@ -68,29 +60,23 @@ class FileTreeLeaf extends React.Component<Props> { }; render() { - const { file, classes } = this.props; + const { file } = this.props; const fileSize = file.directory ? "" : <FileSize bytes={file.length} />; return ( <tr> - <td className={classes.iconColumn}>{this.createFileIcon(file)}</td> - <td className={classNames(classes.wordBreakMinWidth, "is-word-break")}> + <td>{this.createFileIcon(file)}</td> + <MinWidthTd className="is-word-break"> {this.createFileName(file)} - </td> + </MinWidthTd> <td className="is-hidden-mobile">{fileSize}</td> <td className="is-hidden-mobile"> <DateFromNow date={file.lastModified} /> </td> - <td - className={classNames( - classes.wordBreakMinWidth, - "is-word-break", - "is-hidden-mobile" - )} - > + <MinWidthTd className={classNames("is-word-break", "is-hidden-mobile")}> {file.description} - </td> + </MinWidthTd> {binder.hasExtension("repos.sources.tree.row.right") && ( <td className="is-hidden-mobile"> {!file.directory && ( @@ -106,5 +92,3 @@ class FileTreeLeaf extends React.Component<Props> { ); } } - -export default injectSheet(styles)(FileTreeLeaf); diff --git a/scm-ui/ui-webapp/src/repos/sources/components/content/FileButtonGroup.js b/scm-ui/ui-webapp/src/repos/sources/components/content/FileButtonAddons.js similarity index 83% rename from scm-ui/ui-webapp/src/repos/sources/components/content/FileButtonGroup.js rename to scm-ui/ui-webapp/src/repos/sources/components/content/FileButtonAddons.js index 7618abea18..552d5e29d8 100644 --- a/scm-ui/ui-webapp/src/repos/sources/components/content/FileButtonGroup.js +++ b/scm-ui/ui-webapp/src/repos/sources/components/content/FileButtonAddons.js @@ -4,12 +4,13 @@ import { translate } from "react-i18next"; import { ButtonAddons, Button } from "@scm-manager/ui-components"; type Props = { + className?: string, t: string => string, historyIsSelected: boolean, showHistory: boolean => void }; -class FileButtonGroup extends React.Component<Props> { +class FileButtonAddons extends React.Component<Props> { showHistory = () => { this.props.showHistory(true); }; @@ -23,10 +24,10 @@ class FileButtonGroup extends React.Component<Props> { }; render() { - const { t, historyIsSelected } = this.props; + const { className, t, historyIsSelected } = this.props; return ( - <ButtonAddons> + <ButtonAddons className={className}> <div title={t("sources.content.sourcesButton")}> <Button action={this.showSources} @@ -54,4 +55,4 @@ class FileButtonGroup extends React.Component<Props> { } } -export default translate("repos")(FileButtonGroup); +export default translate("repos")(FileButtonAddons); diff --git a/scm-ui/ui-webapp/src/repos/sources/containers/Content.js b/scm-ui/ui-webapp/src/repos/sources/containers/Content.js index 4d297c05bc..fea3abaee0 100644 --- a/scm-ui/ui-webapp/src/repos/sources/containers/Content.js +++ b/scm-ui/ui-webapp/src/repos/sources/containers/Content.js @@ -1,21 +1,21 @@ // @flow import React from "react"; +import { connect } from "react-redux"; import { translate } from "react-i18next"; +import classNames from "classnames"; +import styled from "styled-components"; +import { ExtensionPoint } from "@scm-manager/ui-extensions"; import type { File, Repository } from "@scm-manager/ui-types"; import { DateFromNow, - ButtonGroup, FileSize, - ErrorNotification + ErrorNotification, + Icon } from "@scm-manager/ui-components"; -import injectSheet from "react-jss"; -import classNames from "classnames"; -import FileButtonGroup from "../components/content/FileButtonGroup"; +import { getSources } from "../modules/sources"; +import FileButtonAddons from "../components/content/FileButtonAddons"; import SourcesView from "./SourcesView"; import HistoryView from "./HistoryView"; -import { getSources } from "../modules/sources"; -import { connect } from "react-redux"; -import { ExtensionPoint } from "@scm-manager/ui-extensions"; type Props = { loading: boolean, @@ -23,7 +23,8 @@ type Props = { repository: Repository, revision: string, path: string, - classes: any, + + // context props t: string => string }; @@ -33,21 +34,25 @@ type State = { errorFromExtension?: Error }; -const styles = { - pointer: { - cursor: "pointer" - }, - marginInHeader: { - marginRight: "0.5em" - }, - isVerticalCenter: { - display: "flex", - alignItems: "center" - }, - hasBackground: { - backgroundColor: "#FBFBFB" - } -}; +const VCenteredChild = styled.article` + align-items: center; +`; + +const RightMarginIcon = styled(Icon)` + margin-right: 0.5em; +`; + +const RightMarginFileButtonAddons = styled(FileButtonAddons)` + margin-right: 0.5em; +`; + +const LighterGreyBackgroundPanelBlock = styled.div` + background-color: #fbfbfb; +`; + +const LighterGreyBackgroundTable = styled.table` + background-color: #fbfbfb; +`; class Content extends React.Component<Props, State> { constructor(props: Props) { @@ -77,12 +82,12 @@ class Content extends React.Component<Props, State> { }; showHeader() { - const { file, revision, classes } = this.props; + const { file, revision } = this.props; const { showHistory, collapsed } = this.state; - const icon = collapsed ? "fa-angle-right" : "fa-angle-down"; + const icon = collapsed ? "angle-right" : "angle-down"; const selector = file._links.history ? ( - <FileButtonGroup + <RightMarginFileButtonAddons file={file} historyIsSelected={showHistory} showHistory={(changeShowHistory: boolean) => @@ -92,20 +97,14 @@ class Content extends React.Component<Props, State> { ) : null; return ( - <span className={classes.pointer}> - <article className={classNames("media", classes.isVerticalCenter)}> + <span className="has-cursor-pointer"> + <VCenteredChild className={classNames("media", "is-flex")}> <div className="media-content" onClick={this.toggleCollapse}> - <i - className={classNames( - "fa is-medium", - icon, - classes.marginInHeader - )} - /> + <RightMarginIcon name={icon} color="inherit" /> <span className="is-word-break">{file.name}</span> </div> <div className="buttons is-grouped"> - <div className={classes.marginInHeader}>{selector}</div> + {selector} <ExtensionPoint name="repos.sources.content.actionbar" props={{ @@ -116,14 +115,14 @@ class Content extends React.Component<Props, State> { renderAll={true} /> </div> - </article> + </VCenteredChild> </span> ); } showMoreInformation() { const collapsed = this.state.collapsed; - const { classes, file, revision, t, repository } = this.props; + const { file, revision, t, repository } = this.props; const date = <DateFromNow date={file.lastModified} />; const description = file.description ? ( <p> @@ -140,8 +139,8 @@ class Content extends React.Component<Props, State> { const fileSize = file.directory ? "" : <FileSize bytes={file.length} />; if (!collapsed) { return ( - <div className={classNames("panel-block", classes.hasBackground)}> - <table className={classNames("table", classes.hasBackground)}> + <LighterGreyBackgroundPanelBlock className="panel-block"> + <LighterGreyBackgroundTable className="table"> <tbody> <tr> <td>{t("sources.content.path")}</td> @@ -169,8 +168,8 @@ class Content extends React.Component<Props, State> { props={{ file, repository, revision }} /> </tbody> - </table> - </div> + </LighterGreyBackgroundTable> + </LighterGreyBackgroundPanelBlock> ); } return null; @@ -217,6 +216,4 @@ const mapStateToProps = (state: any, ownProps: Props) => { }; }; -export default injectSheet(styles)( - connect(mapStateToProps)(translate("repos")(Content)) -); +export default connect(mapStateToProps)(translate("repos")(Content)); diff --git a/scm-ui/ui-webapp/src/repos/sources/containers/Sources.js b/scm-ui/ui-webapp/src/repos/sources/containers/Sources.js index 065dfb5a51..ac1937f47f 100644 --- a/scm-ui/ui-webapp/src/repos/sources/containers/Sources.js +++ b/scm-ui/ui-webapp/src/repos/sources/containers/Sources.js @@ -116,7 +116,6 @@ class Sources extends React.Component<Props, State> { const { repository, baseUrl, - branches, loading, error, revision, @@ -124,8 +123,6 @@ class Sources extends React.Component<Props, State> { currentFileIsDirectory } = this.props; - const { selectedBranch } = this.state; - if (error) { return <ErrorNotification error={error} />; } @@ -138,17 +135,7 @@ class Sources extends React.Component<Props, State> { return ( <div className="panel"> {this.renderBranchSelector()} - <Breadcrumb - revision={encodeURIComponent(revision)} - path={path} - baseUrl={baseUrl} - branch={selectedBranch} - defaultBranch={ - branches && branches.filter(b => b.defaultBranch === true)[0] - } - branches={branches && branches} - repository={repository} - /> + {this.renderBreadcrumb()} <FileTree repository={repository} revision={revision} @@ -183,6 +170,28 @@ class Sources extends React.Component<Props, State> { } return null; }; + + renderBreadcrumb = () => { + const { revision, path, baseUrl, branches, repository } = this.props; + const { selectedBranch } = this.state; + + if (revision) { + return ( + <Breadcrumb + revision={encodeURIComponent(revision)} + path={path} + baseUrl={baseUrl} + branch={selectedBranch} + defaultBranch={ + branches && branches.filter(b => b.defaultBranch === true)[0] + } + branches={branches && branches} + repository={repository} + /> + ); + } + return null; + }; } const mapStateToProps = (state, ownProps) => {