mirror of
				https://github.com/scm-manager/scm-manager.git
				synced 2025-10-31 10:35:56 +01:00 
			
		
		
		
	Replace styled-components with bulma helpers (#1783)
Use Bulma helpers whenever possible instead of custom styled components. This pull request replaces primarily color definitions, spacing and flex instructions.
This commit is contained in:
		
							
								
								
									
										2
									
								
								gradle/changelog/simplify_styling.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								gradle/changelog/simplify_styling.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | ||||
| - type: Changed | ||||
|   description: Replace styled-components with bulma helpers ([#1783](https://github.com/scm-manager/scm-manager/pull/1783)) | ||||
| @@ -27,18 +27,14 @@ import { Repository, Link } from "@scm-manager/ui-types"; | ||||
| import { ButtonAddons, Button } from "@scm-manager/ui-components"; | ||||
| import CloneInformation from "./CloneInformation"; | ||||
|  | ||||
| const Wrapper = styled.div` | ||||
|   position: relative; | ||||
| `; | ||||
|  | ||||
| const Switcher = styled(ButtonAddons)` | ||||
|   position: absolute; | ||||
|   top: 0; | ||||
|   right: 0; | ||||
| `; | ||||
|  | ||||
| const SmallButton = styled(Button).attrs(props => ({ | ||||
|   className: "is-small" | ||||
| const SmallButton = styled(Button).attrs((props) => ({ | ||||
|   className: "is-small", | ||||
| }))` | ||||
|   height: inherit; | ||||
| `; | ||||
| @@ -70,13 +66,13 @@ export default class ProtocolInformation extends React.Component<Props, State> { | ||||
|   constructor(props: Props) { | ||||
|     super(props); | ||||
|     this.state = { | ||||
|       selected: selectHttpOrFirst(props.repository) | ||||
|       selected: selectHttpOrFirst(props.repository), | ||||
|     }; | ||||
|   } | ||||
|  | ||||
|   selectProtocol = (protocol: Link) => { | ||||
|     this.setState({ | ||||
|       selected: protocol | ||||
|       selected: protocol, | ||||
|     }); | ||||
|   }; | ||||
|  | ||||
| @@ -116,10 +112,10 @@ export default class ProtocolInformation extends React.Component<Props, State> { | ||||
|     } | ||||
|  | ||||
|     return ( | ||||
|       <Wrapper> | ||||
|       <div className="is-relative"> | ||||
|         <Switcher>{protocols.map(this.renderProtocolButton)}</Switcher> | ||||
|         {cloneInformation} | ||||
|       </Wrapper> | ||||
|       </div> | ||||
|     ); | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -44,10 +44,6 @@ const MinWidthControl = styled.div` | ||||
|   min-width: 10rem; | ||||
| `; | ||||
|  | ||||
| const NoBottomMarginField = styled.div` | ||||
|   margin-bottom: 0 !important; | ||||
| `; | ||||
|  | ||||
| const BranchSelector: FC<Props> = ({ branches, onSelectBranch, selectedBranch, label, disabled }) => { | ||||
|   if (branches) { | ||||
|     return ( | ||||
| @@ -56,7 +52,7 @@ const BranchSelector: FC<Props> = ({ branches, onSelectBranch, selectedBranch, l | ||||
|           <label className={classNames("label", "is-size-6")}>{label}</label> | ||||
|         </ZeroflexFieldLabel> | ||||
|         <div className="field-body"> | ||||
|           <NoBottomMarginField className={classNames("field", "is-narrow")}> | ||||
|           <div className={classNames("field", "is-narrow", "mb-0")}> | ||||
|             <MinWidthControl className="control"> | ||||
|               <Select | ||||
|                 className="is-fullwidth" | ||||
| @@ -67,7 +63,7 @@ const BranchSelector: FC<Props> = ({ branches, onSelectBranch, selectedBranch, l | ||||
|                 addValueToOptions={true} | ||||
|               /> | ||||
|             </MinWidthControl> | ||||
|           </NoBottomMarginField> | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
|     ); | ||||
|   | ||||
| @@ -21,7 +21,6 @@ | ||||
|  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||||
|  * SOFTWARE. | ||||
|  */ | ||||
|  | ||||
| import { storiesOf } from "@storybook/react"; | ||||
| import * as React from "react"; | ||||
| import styled from "styled-components"; | ||||
| @@ -30,6 +29,7 @@ import repository from "./__resources__/repository"; | ||||
| // @ts-ignore ignore unknown png | ||||
| import Git from "./__resources__/git-logo.png"; | ||||
| import { MemoryRouter } from "react-router-dom"; | ||||
| import Icon from "./Icon"; | ||||
|  | ||||
| const Wrapper = styled.div` | ||||
|   margin: 2rem; | ||||
| @@ -42,10 +42,15 @@ const longPath = | ||||
|   "dream-path/src/main/scm-plugins/javaUtilityHomeHousingLinkReferrer/sonia/scm/repositoryUndergroundSupportManager/spi/SvnRepositoryServiceResolver.java"; | ||||
| const baseUrl = "scm-manager.org/scm/repo/hitchhiker/heartOfGold/sources"; | ||||
| const sources = Git; | ||||
| const prefix = ( | ||||
|   <a href="#link"> | ||||
|     <Icon name="heart" color="danger" /> | ||||
|   </a> | ||||
| ); | ||||
|  | ||||
| storiesOf("BreadCrumb", module) | ||||
|   .addDecorator(story => <MemoryRouter initialEntries={["/"]}>{story()}</MemoryRouter>) | ||||
|   .addDecorator(storyFn => <Wrapper>{storyFn()}</Wrapper>) | ||||
|   .addDecorator((story) => <MemoryRouter initialEntries={["/"]}>{story()}</MemoryRouter>) | ||||
|   .addDecorator((storyFn) => <Wrapper>{storyFn()}</Wrapper>) | ||||
|   .add("Default", () => ( | ||||
|     <Breadcrumb | ||||
|       repository={repository} | ||||
| @@ -69,4 +74,17 @@ storiesOf("BreadCrumb", module) | ||||
|       revision={"1"} | ||||
|       permalink={"/" + longPath} | ||||
|     /> | ||||
|   )) | ||||
|   .add("With prefix button", () => ( | ||||
|     <Breadcrumb | ||||
|       repository={repository} | ||||
|       defaultBranch={master} | ||||
|       branch={master} | ||||
|       path={path} | ||||
|       baseUrl={baseUrl} | ||||
|       sources={sources} | ||||
|       revision={"1"} | ||||
|       permalink={"/" + path} | ||||
|       preButtons={prefix} | ||||
|     /> | ||||
|   )); | ||||
|   | ||||
| @@ -93,14 +93,7 @@ const HomeIcon = styled(Icon)` | ||||
| `; | ||||
|  | ||||
| const ActionBar = styled.div` | ||||
|   align-self: center; | ||||
|  | ||||
|   /* order actionbar items horizontal */ | ||||
|   display: flex; | ||||
|   justify-content: flex-start; | ||||
|  | ||||
|   /* ensure space between action bar items */ | ||||
|  | ||||
|   & > * { | ||||
|     /*  | ||||
|      * We have to use important, because plugins could use field or control classes like the editor-plugin does. | ||||
| @@ -113,7 +106,6 @@ const ActionBar = styled.div` | ||||
|  | ||||
| const PrefixButton = styled.div` | ||||
|   border-right: 1px solid lightgray; | ||||
|   margin-right: 0.5rem; | ||||
| `; | ||||
|  | ||||
| const Breadcrumb: FC<Props> = ({ | ||||
| @@ -176,7 +168,7 @@ const Breadcrumb: FC<Props> = ({ | ||||
|   const renderBreadcrumbNav = () => { | ||||
|     let prefixButtons = null; | ||||
|     if (preButtons) { | ||||
|       prefixButtons = <PrefixButton>{preButtons}</PrefixButton>; | ||||
|       prefixButtons = <PrefixButton className="mr-2">{preButtons}</PrefixButton>; | ||||
|     } | ||||
|  | ||||
|     let homeUrl = baseUrl + "/"; | ||||
| @@ -227,13 +219,7 @@ const Breadcrumb: FC<Props> = ({ | ||||
|       binder.hasExtension<extensionPoints.ReposSourcesEmptyActionbar>("repos.sources.empty.actionbar") && | ||||
|       sources?._embedded?.children?.length === 0 | ||||
|     ) { | ||||
|       return ( | ||||
|         <ExtensionPoint | ||||
|           name="repos.sources.empty.actionbar" | ||||
|           props={{ repository, sources }} | ||||
|           renderAll={true} | ||||
|         /> | ||||
|       ); | ||||
|       return <ExtensionPoint name="repos.sources.empty.actionbar" props={{ repository, sources }} renderAll={true} />; | ||||
|     } | ||||
|     if (binder.hasExtension<extensionPoints.ReposSourcesActionbar>("repos.sources.actionbar")) { | ||||
|       return <ExtensionPoint name="repos.sources.actionbar" props={extProps} renderAll={true} />; | ||||
| @@ -243,11 +229,15 @@ const Breadcrumb: FC<Props> = ({ | ||||
|  | ||||
|   return ( | ||||
|     <> | ||||
|       <div className="is-flex is-align-items-center is-justify-content-flex-end"> | ||||
|       <div className={classNames("is-flex", "is-justify-content-flex-end", "is-align-items-center")}> | ||||
|         {renderBreadcrumbNav()} | ||||
|         {<ActionBar className="my-2">{renderExtensionPoints()}</ActionBar>} | ||||
|         { | ||||
|           <ActionBar className={classNames("is-flex", "is-justify-content-flex-start", "is-align-self-center", "my-2")}> | ||||
|             {renderExtensionPoints()} | ||||
|           </ActionBar> | ||||
|         } | ||||
|       </div> | ||||
|       <hr className="is-marginless" /> | ||||
|       <hr className="m-0" /> | ||||
|     </> | ||||
|   ); | ||||
| }; | ||||
|   | ||||
| @@ -44,29 +44,6 @@ const NoEventWrapper = styled.article` | ||||
|   z-index: 1; | ||||
| `; | ||||
|  | ||||
| const AvatarWrapper = styled.figure` | ||||
|   margin-top: 0.8em; | ||||
|   margin-left: 1em !important; | ||||
| `; | ||||
|  | ||||
| const FlexFullHeight = styled.div` | ||||
|   flex-direction: column; | ||||
|   justify-content: space-around; | ||||
|   align-self: stretch; | ||||
| `; | ||||
|  | ||||
| const FooterWrapper = styled.div` | ||||
|   padding-bottom: 1rem; | ||||
| `; | ||||
|  | ||||
| const ContentRight = styled.div` | ||||
|   margin-left: auto; | ||||
| `; | ||||
|  | ||||
| const RightMarginDiv = styled.div` | ||||
|   margin-right: 0.5rem; | ||||
| `; | ||||
|  | ||||
| const InheritFlexShrinkDiv = styled.div` | ||||
|   flex-shrink: inherit; | ||||
|   pointer-events: all; | ||||
| @@ -81,11 +58,11 @@ const CardColumn: FC<Props> = ({ | ||||
|   footerLeft, | ||||
|   footerRight, | ||||
|   action, | ||||
|   className | ||||
|   className, | ||||
| }) => { | ||||
|   const renderAvatar = avatar ? <AvatarWrapper className="media-left">{avatar}</AvatarWrapper> : null; | ||||
|   const renderAvatar = avatar ? <figure className="media-left mt-3 ml-4">{avatar}</figure> : null; | ||||
|   const renderDescription = description ? <p className="shorten-text">{description}</p> : null; | ||||
|   const renderContentRight = contentRight ? <ContentRight>{contentRight}</ContentRight> : null; | ||||
|   const renderContentRight = contentRight ? <div className="ml-auto">{contentRight}</div> : null; | ||||
|  | ||||
|   let createLink = null; | ||||
|   if (link) { | ||||
| @@ -94,7 +71,7 @@ const CardColumn: FC<Props> = ({ | ||||
|     createLink = ( | ||||
|       <a | ||||
|         className="overlay-column" | ||||
|         onClick={e => { | ||||
|         onClick={(e) => { | ||||
|           e.preventDefault(); | ||||
|           action(); | ||||
|         }} | ||||
| @@ -108,7 +85,16 @@ const CardColumn: FC<Props> = ({ | ||||
|       {createLink} | ||||
|       <NoEventWrapper className={classNames("media", className)}> | ||||
|         {renderAvatar} | ||||
|         <FlexFullHeight className={classNames("media-content", "text-box", "is-flex")}> | ||||
|         <div | ||||
|           className={classNames( | ||||
|             "media-content", | ||||
|             "text-box", | ||||
|             "is-flex", | ||||
|             "is-flex-direction-column", | ||||
|             "is-justify-content-space-around", | ||||
|             "is-align-self-stretch" | ||||
|           )} | ||||
|         > | ||||
|           <div className="is-flex"> | ||||
|             <div className="is-clipped mb-0"> | ||||
|               <p className="shorten-text m-0">{title}</p> | ||||
| @@ -116,13 +102,13 @@ const CardColumn: FC<Props> = ({ | ||||
|             </div> | ||||
|             {renderContentRight} | ||||
|           </div> | ||||
|           <FooterWrapper className={classNames("level", "is-flex")}> | ||||
|             <RightMarginDiv className="level-left is-hidden-mobile">{footerLeft}</RightMarginDiv> | ||||
|           <div className={classNames("level", "is-flex", "pb-4")}> | ||||
|             <div className={classNames("level-left", "is-hidden-mobile", "mr-2")}>{footerLeft}</div> | ||||
|             <InheritFlexShrinkDiv className="level-right is-block is-mobile m-0 shorten-text"> | ||||
|               {footerRight} | ||||
|             </InheritFlexShrinkDiv> | ||||
|           </FooterWrapper> | ||||
|         </FlexFullHeight> | ||||
|           </div> | ||||
|         </div> | ||||
|       </NoEventWrapper> | ||||
|     </> | ||||
|   ); | ||||
|   | ||||
| @@ -24,7 +24,6 @@ | ||||
| import React, { ReactNode } from "react"; | ||||
| import { Link } from "react-router-dom"; | ||||
| import classNames from "classnames"; | ||||
| import styled from "styled-components"; | ||||
|  | ||||
| type Props = { | ||||
|   name: ReactNode; | ||||
| @@ -36,25 +35,17 @@ type State = { | ||||
|   collapsed: boolean; | ||||
| }; | ||||
|  | ||||
| const Container = styled.div` | ||||
|   margin-bottom: 1em; | ||||
| `; | ||||
|  | ||||
| const Wrapper = styled.div` | ||||
|   padding: 0 0.75rem; | ||||
| `; | ||||
|  | ||||
| export default class CardColumnGroup extends React.Component<Props, State> { | ||||
|   constructor(props: Props) { | ||||
|     super(props); | ||||
|     this.state = { | ||||
|       collapsed: false | ||||
|       collapsed: false, | ||||
|     }; | ||||
|   } | ||||
|  | ||||
|   toggleCollapse = () => { | ||||
|     this.setState(prevState => ({ | ||||
|       collapsed: !prevState.collapsed | ||||
|     this.setState((prevState) => ({ | ||||
|       collapsed: !prevState.collapsed, | ||||
|     })); | ||||
|   }; | ||||
|  | ||||
| @@ -89,13 +80,13 @@ export default class CardColumnGroup extends React.Component<Props, State> { | ||||
|     } | ||||
|  | ||||
|     return ( | ||||
|       <Container> | ||||
|       <div className="mb-4"> | ||||
|         <h2> | ||||
|           <span className={classNames("is-size-4", "has-cursor-pointer")} onClick={this.toggleCollapse}> | ||||
|           <span className={classNames("is-size-4", "is-clickable")} onClick={this.toggleCollapse}> | ||||
|             <i className={classNames("fa", icon)} /> | ||||
|           </span>{" "} | ||||
|           {url ? ( | ||||
|             <Link to={url} className={"has-text-dark"}> | ||||
|             <Link to={url} className="has-text-dark"> | ||||
|               {name} | ||||
|             </Link> | ||||
|           ) : ( | ||||
| @@ -103,9 +94,9 @@ export default class CardColumnGroup extends React.Component<Props, State> { | ||||
|           )} | ||||
|         </h2> | ||||
|         <hr /> | ||||
|         <Wrapper className={classNames("columns", "card-columns", "is-multiline")}>{content}</Wrapper> | ||||
|         <div className={classNames("columns", "card-columns", "is-multiline", "mx-3", "my-0")}>{content}</div> | ||||
|         <div className="is-clearfix" /> | ||||
|       </Container> | ||||
|       </div> | ||||
|     ); | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -35,7 +35,7 @@ const Wrapper = styled.div` | ||||
|  | ||||
| const link = "/foo/bar"; | ||||
| const icon = <Icon name="icons fa-2x fa-fw" />; | ||||
| const contentLeft = <strong className="is-marginless">main content</strong>; | ||||
| const contentLeft = <strong className="m-0">main content</strong>; | ||||
| const contentRight = <small>more text</small>; | ||||
|  | ||||
| storiesOf("CardColumnSmall", module) | ||||
|   | ||||
| @@ -35,22 +35,6 @@ type Props = { | ||||
|   footer?: ReactNode; | ||||
| }; | ||||
|  | ||||
| const FlexFullHeight = styled.div` | ||||
|   flex-direction: column; | ||||
|   justify-content: space-around; | ||||
|   align-self: stretch; | ||||
| `; | ||||
|  | ||||
| const ContentLeft = styled.div` | ||||
|   margin-bottom: 0 !important; | ||||
|   overflow: hidden; | ||||
| `; | ||||
|  | ||||
| const ContentRight = styled.div` | ||||
|   margin-left: auto; | ||||
|   align-items: start; | ||||
| `; | ||||
|  | ||||
| const StyledLink = styled(Link)` | ||||
|   color: inherit; | ||||
|   :hover { | ||||
| @@ -58,25 +42,30 @@ const StyledLink = styled(Link)` | ||||
|   } | ||||
| `; | ||||
|  | ||||
| const AvatarWrapper = styled.figure` | ||||
|   margin-right: 0.5rem; | ||||
| `; | ||||
|  | ||||
| const CardColumnSmall: FC<Props> = ({ link, avatar, contentLeft, contentRight, footer }) => { | ||||
|   const renderAvatar = avatar ? <AvatarWrapper className="media-left">{avatar}</AvatarWrapper> : null; | ||||
|   const renderAvatar = avatar ? <figure className={classNames("media-left", "mr-2")}>{avatar}</figure> : null; | ||||
|   const renderFooter = footer ? <small>{footer}</small> : null; | ||||
|  | ||||
|   return ( | ||||
|     <StyledLink to={link}> | ||||
|       <div className="media"> | ||||
|         {renderAvatar} | ||||
|         <FlexFullHeight className={classNames("media-content", "text-box", "is-flex")}> | ||||
|           <div className="is-flex is-align-items-center"> | ||||
|             <ContentLeft>{contentLeft}</ContentLeft> | ||||
|             <ContentRight>{contentRight}</ContentRight> | ||||
|         <div | ||||
|           className={classNames( | ||||
|             "media-content", | ||||
|             "text-box", | ||||
|             "is-flex", | ||||
|             "is-flex-direction-column", | ||||
|             "is-justify-content-space-around", | ||||
|             "is-align-self-stretch" | ||||
|           )} | ||||
|         > | ||||
|           <div className={classNames("is-flex", "is-align-items-center")}> | ||||
|             <div className={classNames("is-clipped", "mb-0")}>{contentLeft}</div> | ||||
|             <div className={classNames("is-align-items-start", "ml-auto")}>{contentRight}</div> | ||||
|           </div> | ||||
|           {renderFooter} | ||||
|         </FlexFullHeight> | ||||
|         </div> | ||||
|       </div> | ||||
|     </StyledLink> | ||||
|   ); | ||||
|   | ||||
| @@ -22,43 +22,38 @@ | ||||
|  * SOFTWARE. | ||||
|  */ | ||||
| import React from "react"; | ||||
| import DateFromNow from "./DateFromNow"; | ||||
| import { storiesOf } from "@storybook/react"; | ||||
| import DateFromNow from "./DateFromNow"; | ||||
| import DateShort from "./DateShort"; | ||||
| import styled from "styled-components"; | ||||
|  | ||||
| const baseProps = { | ||||
|   timeZone: "Europe/Berlin", | ||||
|   baseDate: "2019-10-12T13:56:42+02:00" | ||||
|   baseDate: "2019-10-12T13:56:42+02:00", | ||||
| }; | ||||
|  | ||||
| const dates = [ | ||||
|   "2009-06-30T18:30:00+02:00", | ||||
|   "2019-06-30T18:30:00+02:00", | ||||
|   "2019-10-12T13:56:40+02:00", | ||||
|   "2019-10-11T13:56:40+02:00" | ||||
|   "2019-10-11T13:56:40+02:00", | ||||
| ]; | ||||
|  | ||||
| const Wrapper = styled.div` | ||||
|   padding: 2rem; | ||||
| `; | ||||
|  | ||||
| storiesOf("Date", module) | ||||
|   .add("Date from now", () => ( | ||||
|     <Wrapper> | ||||
|       {dates.map(d => ( | ||||
|     <div className="p-5"> | ||||
|       {dates.map((d) => ( | ||||
|         <p> | ||||
|           <DateFromNow date={d} {...baseProps} /> | ||||
|         </p> | ||||
|       ))} | ||||
|     </Wrapper> | ||||
|     </div> | ||||
|   )) | ||||
|   .add("Short", () => ( | ||||
|     <Wrapper> | ||||
|       {dates.map(d => ( | ||||
|     <div className="p-5"> | ||||
|       {dates.map((d) => ( | ||||
|         <p> | ||||
|           <DateShort date={d} {...baseProps} /> | ||||
|         </p> | ||||
|       ))} | ||||
|     </Wrapper> | ||||
|     </div> | ||||
|   )); | ||||
|   | ||||
| @@ -22,14 +22,15 @@ | ||||
|  * SOFTWARE. | ||||
|  */ | ||||
| import React, { FC, ReactNode, useEffect } from "react"; | ||||
| import ErrorNotification from "./ErrorNotification"; | ||||
| import { MissingLinkError, urls, useIndexLink } from "@scm-manager/ui-api"; | ||||
| import { RouteComponentProps, useLocation, withRouter } from "react-router-dom"; | ||||
| import ErrorPage from "./ErrorPage"; | ||||
| import { useTranslation } from "react-i18next"; | ||||
| import classNames from "classnames"; | ||||
| import styled from "styled-components"; | ||||
| import { MissingLinkError, urls, useIndexLink } from "@scm-manager/ui-api"; | ||||
| import ErrorNotification from "./ErrorNotification"; | ||||
| import ErrorPage from "./ErrorPage"; | ||||
| import { Subtitle, Title } from "./layout"; | ||||
| import Icon from "./Icon"; | ||||
| import styled from "styled-components"; | ||||
|  | ||||
| type State = { | ||||
|   error?: Error; | ||||
| @@ -54,9 +55,6 @@ type ErrorDisplayProps = { | ||||
| }; | ||||
|  | ||||
| const RedirectIconContainer = styled.div` | ||||
|   flex-direction: column; | ||||
|   align-items: center; | ||||
|   justify-content: center; | ||||
|   min-height: 256px; | ||||
| `; | ||||
|  | ||||
| @@ -69,7 +67,14 @@ const RedirectPage = () => { | ||||
|       <div className="container"> | ||||
|         <Title>{t("errorBoundary.redirect.title")}</Title> | ||||
|         <Subtitle>{t("errorBoundary.redirect.subtitle")}</Subtitle> | ||||
|         <RedirectIconContainer className="is-flex"> | ||||
|         <RedirectIconContainer | ||||
|           className={classNames( | ||||
|             "is-flex", | ||||
|             "is-flex-direction-column", | ||||
|             "is-justify-content-center", | ||||
|             "is-align-items-center" | ||||
|           )} | ||||
|         > | ||||
|           <Icon name="directions" className="fa-7x" /> | ||||
|         </RedirectIconContainer> | ||||
|       </div> | ||||
| @@ -108,7 +113,7 @@ const ErrorDisplay: FC<ErrorDisplayProps> = ({ error, errorInfo, fallback: Fallb | ||||
|  | ||||
|   const fallbackProps = { | ||||
|     error, | ||||
|     errorInfo | ||||
|     errorInfo, | ||||
|   }; | ||||
|  | ||||
|   return <FallbackComponent {...fallbackProps} />; | ||||
| @@ -130,7 +135,7 @@ class ErrorBoundary extends React.Component<Props, State> { | ||||
|   componentDidCatch(error: Error, errorInfo: ErrorInfo) { | ||||
|     this.setState({ | ||||
|       error, | ||||
|       errorInfo | ||||
|       errorInfo, | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   | ||||
| @@ -21,20 +21,10 @@ | ||||
|  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||||
|  * SOFTWARE. | ||||
|  */ | ||||
|  | ||||
| import styled from "styled-components"; | ||||
| import * as React from "react"; | ||||
| import { storiesOf } from "@storybook/react"; | ||||
| import Help from "./Help"; | ||||
|  | ||||
| const Wrapper = styled.div` | ||||
|   margin: 5rem; | ||||
| `; | ||||
|  | ||||
| const Spacing = styled.div` | ||||
|   margin-top: 1rem; | ||||
| `; | ||||
|  | ||||
| const longContent = | ||||
|   "Cleverness nuclear genuine static irresponsibility invited President Zaphod\n" + | ||||
|   "Beeblebrox hyperspace ship. Another custard through computer-generated universe\n" + | ||||
| @@ -42,17 +32,17 @@ const longContent = | ||||
|   "imaginative generator sweep."; | ||||
|  | ||||
| storiesOf("Help", module) | ||||
|   .addDecorator(storyFn => <Wrapper>{storyFn()}</Wrapper>) | ||||
|   .addDecorator((storyFn) => <div className="m-6">{storyFn()}</div>) | ||||
|   .add("Default", () => <Help message="This is a help message" />) | ||||
|   .add("Multiline", () => ( | ||||
|     <> | ||||
|       <Spacing> | ||||
|       <div className="mt-4"> | ||||
|         <label>With multiline (default):</label> | ||||
|         <Help message={longContent} /> | ||||
|       </Spacing> | ||||
|       <Spacing> | ||||
|       </div> | ||||
|       <div className="mt-4"> | ||||
|         <label>Without multiline:</label> | ||||
|         <Help message={longContent} multiline={false} /> | ||||
|       </Spacing> | ||||
|       </div> | ||||
|     </> | ||||
|   )); | ||||
|   | ||||
| @@ -33,22 +33,21 @@ type Props = { | ||||
|   className?: string; | ||||
| }; | ||||
|  | ||||
| const HelpTooltip = styled(Tooltip)` | ||||
| const AbsolutePositionTooltip = styled(Tooltip)` | ||||
|   position: absolute; | ||||
|   padding-left: 3px; | ||||
| `; | ||||
|  | ||||
| const Help: FC<Props> = ({ message, multiline, className }) => ( | ||||
|   <HelpTooltip | ||||
|     className={classNames("is-inline-block", multiline ? "has-tooltip-multiline" : undefined, className)} | ||||
|   <AbsolutePositionTooltip | ||||
|     className={classNames("is-inline-block", "pl-1", multiline ? "has-tooltip-multiline" : undefined, className)} | ||||
|     message={message} | ||||
|   > | ||||
|     <HelpIcon /> | ||||
|   </HelpTooltip> | ||||
|   </AbsolutePositionTooltip> | ||||
| ); | ||||
|  | ||||
| Help.defaultProps = { | ||||
|   multiline: true | ||||
|   multiline: true, | ||||
| }; | ||||
|  | ||||
| export default Help; | ||||
|   | ||||
| @@ -23,6 +23,7 @@ | ||||
|  */ | ||||
| import React from "react"; | ||||
| import { WithTranslation, withTranslation } from "react-i18next"; | ||||
| import classNames from "classnames"; | ||||
| import styled from "styled-components"; | ||||
| import Image from "./Image"; | ||||
|  | ||||
| @@ -31,14 +32,10 @@ type Props = WithTranslation & { | ||||
| }; | ||||
|  | ||||
| const Wrapper = styled.div` | ||||
|   flex-direction: column; | ||||
|   align-items: center; | ||||
|   justify-content: center; | ||||
|   min-height: 256px; | ||||
| `; | ||||
|  | ||||
| const FixedSizedImage = styled(Image)` | ||||
|   margin-bottom: 0.75rem; | ||||
|   width: 128px; | ||||
|   height: 128px; | ||||
| `; | ||||
| @@ -47,8 +44,15 @@ class Loading extends React.Component<Props> { | ||||
|   render() { | ||||
|     const { message, t } = this.props; | ||||
|     return ( | ||||
|       <Wrapper className="is-flex"> | ||||
|         <FixedSizedImage src="/images/loading.svg" alt={t("loading.alt")} /> | ||||
|       <Wrapper | ||||
|         className={classNames( | ||||
|           "is-flex", | ||||
|           "is-flex-direction-column", | ||||
|           "is-justify-content-center", | ||||
|           "is-align-items-center" | ||||
|         )} | ||||
|       > | ||||
|         <FixedSizedImage className="mb-3" src="/images/loading.svg" alt={t("loading.alt")} /> | ||||
|         <p className="has-text-centered">{message}</p> | ||||
|       </Wrapper> | ||||
|     ); | ||||
|   | ||||
| @@ -60,9 +60,9 @@ const OverviewPageActions: FC<Props> = ({ | ||||
|   const link = createAbsoluteLink(inputLink); | ||||
|  | ||||
|   const groupSelector = groups && ( | ||||
|     <div className={"column is-flex"}> | ||||
|     <div className="column is-flex"> | ||||
|       <Select | ||||
|         className={"is-fullwidth"} | ||||
|         className="is-fullwidth" | ||||
|         options={groups.map((g) => ({ value: g, label: g }))} | ||||
|         value={currentGroup} | ||||
|         onChange={groupSelected} | ||||
| @@ -89,9 +89,9 @@ const OverviewPageActions: FC<Props> = ({ | ||||
|   }; | ||||
|  | ||||
|   return ( | ||||
|     <div className={"columns is-tablet"}> | ||||
|     <div className="columns is-tablet"> | ||||
|       {groupSelector} | ||||
|       <div className={"column"}> | ||||
|       <div className="column"> | ||||
|         <FilterInput | ||||
|           placeholder={searchPlaceholder} | ||||
|           value={urls.getQueryStringFromLocation(location)} | ||||
|   | ||||
| @@ -21,37 +21,33 @@ | ||||
|  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||||
|  * SOFTWARE. | ||||
|  */ | ||||
|  | ||||
| import React from "react"; | ||||
| import { Icon } from "@scm-manager/ui-components"; | ||||
| import { storiesOf } from "@storybook/react"; | ||||
| import SplitAndReplace from "./SplitAndReplace"; | ||||
| import { Icon } from "@scm-manager/ui-components"; | ||||
| import styled from "styled-components"; | ||||
|  | ||||
| const Wrapper = styled.div` | ||||
|   margin: 2rem; | ||||
| `; | ||||
|  | ||||
| storiesOf("SplitAndReplace", module).add("Simple replacement", () => { | ||||
|   const replacements = [ | ||||
|     { | ||||
|       textToReplace: "'", | ||||
|       replacement: <Icon name={"quote-left"} />, | ||||
|       replaceAll: true | ||||
|       replaceAll: true, | ||||
|     }, | ||||
|     { | ||||
|       textToReplace: "`", | ||||
|       replacement: <Icon name={"quote-right"} />, | ||||
|       replaceAll: true | ||||
|     } | ||||
|       replaceAll: true, | ||||
|     }, | ||||
|   ]; | ||||
|   return ( | ||||
|     <> | ||||
|       <Wrapper> | ||||
|       <div className="m-6"> | ||||
|         <SplitAndReplace text={"'So this is it,` said Arthur, 'We are going to die.`"} replacements={replacements} /> | ||||
|       </Wrapper> | ||||
|       <Wrapper> | ||||
|       </div> | ||||
|       <div className="m-6"> | ||||
|         <SplitAndReplace text={"'Yes,` said Ford, 'except... no! Wait a minute!`"} replacements={replacements} /> | ||||
|       </Wrapper> | ||||
|       </div> | ||||
|     </> | ||||
|   ); | ||||
| }); | ||||
|   | ||||
| @@ -21,10 +21,9 @@ | ||||
|  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||||
|  * SOFTWARE. | ||||
|  */ | ||||
| import React, { FC, HTMLAttributes } from "react"; | ||||
| import React, { FC } from "react"; | ||||
| import classNames from "classnames"; | ||||
| import { Color, Size } from "./styleConstants"; | ||||
| import styled, { css } from "styled-components"; | ||||
|  | ||||
| type Props = { | ||||
|   className?: string; | ||||
| @@ -39,19 +38,7 @@ type Props = { | ||||
|   onRemove?: () => void; | ||||
| }; | ||||
|  | ||||
| type InnerTagProps = HTMLAttributes<HTMLSpanElement> & { | ||||
|   small: boolean; | ||||
| }; | ||||
|  | ||||
| const smallMixin = css` | ||||
|   font-size: 0.7rem !important; | ||||
|   padding: 0.25rem !important; | ||||
|   font-weight: bold; | ||||
| `; | ||||
|  | ||||
| const InnerTag = styled.span<InnerTagProps>` | ||||
|   ${(props) => props.small && smallMixin}; | ||||
| `; | ||||
| const smallClassNames = classNames("p-1", "is-size-7", "has-text-weight-bold"); | ||||
|  | ||||
| const Tag: FC<Props> = ({ | ||||
|   className, | ||||
| @@ -82,20 +69,26 @@ const Tag: FC<Props> = ({ | ||||
|  | ||||
|   return ( | ||||
|     <> | ||||
|       <InnerTag | ||||
|         className={classNames("tag", `is-${color}`, `is-${size}`, className, { | ||||
|       <span | ||||
|         className={classNames( | ||||
|           "tag", | ||||
|           `is-${color}`, | ||||
|           `is-${size}`, | ||||
|           className, | ||||
|           { | ||||
|             "is-outlined": outlined, | ||||
|             "is-rounded": rounded, | ||||
|           "has-cursor-pointer": onClick, | ||||
|         })} | ||||
|             "is-clickable": onClick, | ||||
|           }, | ||||
|           size === "small" && smallClassNames | ||||
|         )} | ||||
|         title={title} | ||||
|         onClick={onClick} | ||||
|         small={size === "small"} | ||||
|       > | ||||
|         {showIcon} | ||||
|         {label} | ||||
|         {children} | ||||
|       </InnerTag> | ||||
|       </span> | ||||
|       {showDelete} | ||||
|     </> | ||||
|   ); | ||||
|   | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -24,17 +24,16 @@ | ||||
| import React from "react"; | ||||
| import styled from "styled-components"; | ||||
| import Button, { ButtonProps } from "./Button"; | ||||
| import classNames from "classnames"; | ||||
|  | ||||
| const Wrapper = styled.div` | ||||
|   margin-top: 2em; | ||||
|   padding: 1em 1em; | ||||
|   border: 2px solid #e9f7fd; | ||||
| `; | ||||
|  | ||||
| export default class CreateButton extends React.Component<ButtonProps> { | ||||
|   render() { | ||||
|     return ( | ||||
|       <Wrapper className="has-text-centered"> | ||||
|       <Wrapper className={classNames("has-text-centered", "mt-5", "p-4")}> | ||||
|         <Button color="primary" {...this.props} /> | ||||
|       </Wrapper> | ||||
|     ); | ||||
|   | ||||
| @@ -37,20 +37,8 @@ type Props = { | ||||
|   errorMessage: string; | ||||
| }; | ||||
|  | ||||
| const StyledLevel = styled(Level)` | ||||
|   align-items: stretch; | ||||
|   margin-bottom: 1rem !important; // same margin as field | ||||
| `; | ||||
|  | ||||
| const FullWidthInputField = styled(InputField)` | ||||
|   width: 100%; | ||||
|   margin-right: 1.5rem; | ||||
| `; | ||||
|  | ||||
| const StyledField = styled.div.attrs(() => ({ | ||||
|   className: "field" | ||||
| }))` | ||||
|   align-self: flex-end; | ||||
| `; | ||||
|  | ||||
| const AddEntryToTableField: FC<Props> = ({ | ||||
| @@ -60,7 +48,7 @@ const AddEntryToTableField: FC<Props> = ({ | ||||
|   fieldLabel, | ||||
|   helpText, | ||||
|   validateEntry, | ||||
|   errorMessage | ||||
|   errorMessage, | ||||
| }) => { | ||||
|   const [entryToAdd, setEntryToAdd] = useState(""); | ||||
|  | ||||
| @@ -89,9 +77,11 @@ const AddEntryToTableField: FC<Props> = ({ | ||||
|   }; | ||||
|  | ||||
|   return ( | ||||
|     <StyledLevel | ||||
|     <Level | ||||
|       className="is-align-items-stretch mb-4" | ||||
|       children={ | ||||
|         <FullWidthInputField | ||||
|           className="mr-5" | ||||
|           label={fieldLabel} | ||||
|           errorMessage={errorMessage} | ||||
|           onChange={handleAddEntryChange} | ||||
| @@ -103,13 +93,13 @@ const AddEntryToTableField: FC<Props> = ({ | ||||
|         /> | ||||
|       } | ||||
|       right={ | ||||
|         <StyledField> | ||||
|         <div className="field is-align-self-flex-end"> | ||||
|           <AddButton | ||||
|             label={buttonLabel} | ||||
|             action={addButtonClicked} | ||||
|             disabled={disabled || entryToAdd === "" || !isValid()} | ||||
|           /> | ||||
|         </StyledField> | ||||
|         </div> | ||||
|       } | ||||
|     /> | ||||
|   ); | ||||
|   | ||||
| @@ -21,37 +21,31 @@ | ||||
|  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||||
|  * SOFTWARE. | ||||
|  */ | ||||
|  | ||||
| import styled from "styled-components"; | ||||
| import { storiesOf } from "@storybook/react"; | ||||
| import React from "react"; | ||||
| import AddKeyValueEntryToTableField from "./AddKeyValueEntryToTableField"; | ||||
| import { MemoryRouter } from "react-router-dom"; | ||||
|  | ||||
| const Spacing = styled.div` | ||||
|   padding: 2em; | ||||
| `; | ||||
| import { storiesOf } from "@storybook/react"; | ||||
| import AddKeyValueEntryToTableField from "./AddKeyValueEntryToTableField"; | ||||
|  | ||||
| storiesOf("Forms|AddKeyValueEntryToTableField", module) | ||||
|   .addDecorator(story => <MemoryRouter initialEntries={["/"]}>{story()}</MemoryRouter>) | ||||
|   .addDecorator((story) => <MemoryRouter initialEntries={["/"]}>{story()}</MemoryRouter>) | ||||
|   .add("Default", () => ( | ||||
|     <Spacing> | ||||
|     <div className="m-6"> | ||||
|       <AddKeyValueEntryToTableField | ||||
|         keyFieldLabel="Key" | ||||
|         valueFieldLabel="Value" | ||||
|         buttonLabel="Add to table" | ||||
|         addEntry={(key, value) => {console.log(key, value)}} | ||||
|         buttonLabel="Add to Table" | ||||
|         addEntry={() => null} | ||||
|       /> | ||||
|     </Spacing> | ||||
|     </div> | ||||
|   )) | ||||
|   .add("Disabled", () => ( | ||||
|     <Spacing> | ||||
|     <div className="m-6"> | ||||
|       <AddKeyValueEntryToTableField | ||||
|         keyFieldLabel="Key" | ||||
|         valueFieldLabel="Value" | ||||
|         buttonLabel="Add to table" | ||||
|         addEntry={() => {}} | ||||
|         buttonLabel="Add to Table" | ||||
|         addEntry={() => null} | ||||
|         disabled={true} | ||||
|       /> | ||||
|     </Spacing> | ||||
|     </div> | ||||
|   )); | ||||
|   | ||||
| @@ -21,7 +21,6 @@ | ||||
|  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||||
|  * SOFTWARE. | ||||
|  */ | ||||
|  | ||||
| import React, { FC, useState } from "react"; | ||||
| import styled from "styled-components"; | ||||
| import InputField from "./InputField"; | ||||
| @@ -39,13 +38,7 @@ type Props = { | ||||
|   validateEntry?: (p: string) => boolean; | ||||
| }; | ||||
|  | ||||
| const InputLevel = styled.div` | ||||
|   display: flex; | ||||
|   justify-content: space-between; | ||||
| `; | ||||
|  | ||||
| const StyledInputField = styled(InputField)` | ||||
|   margin-right: 1.5rem; | ||||
| const FullWidthInputField = styled(InputField)` | ||||
|   width: 100%; | ||||
| `; | ||||
|  | ||||
| @@ -62,7 +55,7 @@ const AddKeyValueEntryToTableField: FC<Props> = ({ | ||||
|   valueHelpText, | ||||
|   validateEntry, | ||||
|   errorMessage, | ||||
|   addEntry | ||||
|   addEntry, | ||||
| }) => { | ||||
|   const [key, setKey] = useState(""); | ||||
|   const [value, setValue] = useState(""); | ||||
| @@ -77,13 +70,14 @@ const AddKeyValueEntryToTableField: FC<Props> = ({ | ||||
|  | ||||
|   const add = () => { | ||||
|     addEntry(key, value); | ||||
|     setKey("") | ||||
|     setKey(""); | ||||
|     setValue(""); | ||||
|   } | ||||
|   }; | ||||
|  | ||||
|   return ( | ||||
|     <InputLevel> | ||||
|       <StyledInputField | ||||
|     <div className="is-flex is-justify-content-space-between"> | ||||
|       <FullWidthInputField | ||||
|         className="mr-5" | ||||
|         label={keyFieldLabel} | ||||
|         errorMessage={errorMessage} | ||||
|         onChange={setKey} | ||||
| @@ -93,7 +87,8 @@ const AddKeyValueEntryToTableField: FC<Props> = ({ | ||||
|         disabled={disabled} | ||||
|         helpText={keyHelpText} | ||||
|       /> | ||||
|       <StyledInputField | ||||
|       <FullWidthInputField | ||||
|         className="mr-5" | ||||
|         label={valueFieldLabel} | ||||
|         errorMessage={errorMessage} | ||||
|         onChange={setValue} | ||||
| @@ -108,7 +103,7 @@ const AddKeyValueEntryToTableField: FC<Props> = ({ | ||||
|         action={add} | ||||
|         disabled={disabled || !key || !value || !isValid(key) || !isValid(value)} | ||||
|       /> | ||||
|     </InputLevel> | ||||
|     </div> | ||||
|   ); | ||||
| }; | ||||
|  | ||||
|   | ||||
| @@ -42,7 +42,6 @@ type Props = { | ||||
|  | ||||
| const FullWidthAutocomplete = styled(Autocomplete)` | ||||
|   width: 100%; | ||||
|   margin-right: 1.5rem; | ||||
| `; | ||||
|  | ||||
| const AutocompleteAddEntryToTableField: FC<Props> = ({ | ||||
| @@ -79,6 +78,7 @@ const AutocompleteAddEntryToTableField: FC<Props> = ({ | ||||
|     <Level | ||||
|       children={ | ||||
|         <FullWidthAutocomplete | ||||
|           className="mr-5" | ||||
|           label={fieldLabel} | ||||
|           loadSuggestions={loadSuggestions} | ||||
|           valueSelected={handleAddEntryChange} | ||||
|   | ||||
| @@ -22,18 +22,9 @@ | ||||
|  * SOFTWARE. | ||||
|  */ | ||||
| import React, { ChangeEvent, FC, FocusEvent } from "react"; | ||||
| import { Help } from "../index"; | ||||
| import styled from "styled-components"; | ||||
| import { createFormFieldWrapper, FieldProps, FieldType, isLegacy, isUsingRef } from "./FormFieldTypes"; | ||||
| import classNames from "classnames"; | ||||
|  | ||||
| const StyledRadio = styled.label` | ||||
|   margin-right: 0.5em; | ||||
| `; | ||||
|  | ||||
| const InlineFieldset = styled.fieldset` | ||||
|   display: inline-block; | ||||
| `; | ||||
| import { Help } from "../index"; | ||||
| import { createFormFieldWrapper, FieldProps, FieldType, isLegacy, isUsingRef } from "./FormFieldTypes"; | ||||
|  | ||||
| type BaseProps = { | ||||
|   label?: string; | ||||
| @@ -47,7 +38,12 @@ type BaseProps = { | ||||
|   readOnly?: boolean; | ||||
| }; | ||||
|  | ||||
| const InnerRadio: FC<FieldProps<BaseProps, HTMLInputElement, boolean>> = ({ name, defaultChecked, readOnly, ...props }) => { | ||||
| const InnerRadio: FC<FieldProps<BaseProps, HTMLInputElement, boolean>> = ({ | ||||
|   name, | ||||
|   defaultChecked, | ||||
|   readOnly, | ||||
|   ...props | ||||
| }) => { | ||||
|   const renderHelp = () => { | ||||
|     const helpText = props.helpText; | ||||
|     if (helpText) { | ||||
| @@ -76,13 +72,13 @@ const InnerRadio: FC<FieldProps<BaseProps, HTMLInputElement, boolean>> = ({ name | ||||
|   }; | ||||
|  | ||||
|   return ( | ||||
|     <InlineFieldset disabled={readOnly}> | ||||
|     <fieldset className="is-inline-block" disabled={readOnly}> | ||||
|       {/* | ||||
|         we have to ignore the next line, | ||||
|         because jsx label does not the custom disabled attribute | ||||
|         but bulma does. | ||||
|         // @ts-ignore */} | ||||
|       <StyledRadio className={classNames("radio", props.className)} disabled={props.disabled}> | ||||
|       <label className={classNames("radio", "mr-2", props.className)} disabled={props.disabled}> | ||||
|         <input | ||||
|           type="radio" | ||||
|           name={name} | ||||
| @@ -96,8 +92,8 @@ const InnerRadio: FC<FieldProps<BaseProps, HTMLInputElement, boolean>> = ({ name | ||||
|         />{" "} | ||||
|         {props.label} | ||||
|         {renderHelp()} | ||||
|       </StyledRadio> | ||||
|     </InlineFieldset> | ||||
|       </label> | ||||
|     </fieldset> | ||||
|   ); | ||||
| }; | ||||
|  | ||||
|   | ||||
| @@ -22,25 +22,21 @@ | ||||
|  * SOFTWARE. | ||||
|  */ | ||||
| import React, { FC, ReactNode } from "react"; | ||||
| import classNames from "classnames"; | ||||
| import styled from "styled-components"; | ||||
|  | ||||
| type Props = { | ||||
|   title: ReactNode; | ||||
| }; | ||||
|  | ||||
| const Title = styled.div` | ||||
|   font-weight: bold; | ||||
|   margin-bottom: 0.5rem; | ||||
| `; | ||||
|  | ||||
| const Menu = styled.ul` | ||||
|   padding-left: 1.1rem; | ||||
| `; | ||||
|  | ||||
| const FooterSection: FC<Props> = ({ title, children }) => { | ||||
|   return ( | ||||
|     <section className="column is-one-third"> | ||||
|       <Title>{title}</Title> | ||||
|     <section className={classNames("column", "is-one-third")}> | ||||
|       <div className={classNames("has-text-weight-bold", "mb-2")}>{title}</div> | ||||
|       <Menu>{children}</Menu> | ||||
|     </section> | ||||
|   ); | ||||
|   | ||||
| @@ -21,25 +21,12 @@ | ||||
|  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||||
|  * SOFTWARE. | ||||
|  */ | ||||
|  | ||||
| import React, { FC, ReactNode } from "react"; | ||||
| import classNames from "classnames"; | ||||
| import styled from "styled-components"; | ||||
|  | ||||
| const TitleWrapper = styled.div` | ||||
|   display: flex; | ||||
|   align-items: center; | ||||
|   padding: 0.75rem; | ||||
|   font-size: 1rem; | ||||
|   font-weight: bold; | ||||
| `; | ||||
|  | ||||
| const Separator = styled.div` | ||||
|   border-bottom: 1px solid rgb(219, 219, 219, 0.5); | ||||
|   margin: 0 1rem; | ||||
| `; | ||||
|  | ||||
| const Box = styled.div` | ||||
|   padding: 0.5rem; | ||||
| `; | ||||
|  | ||||
| type Props = { | ||||
| @@ -51,14 +38,16 @@ const GroupEntries: FC<Props> = ({ namespaceHeader, elements }) => { | ||||
|   const content = elements.map((entry, index) => ( | ||||
|     <React.Fragment key={index}> | ||||
|       <div>{entry}</div> | ||||
|       {index + 1 !== elements.length ? <Separator /> : null} | ||||
|       {index + 1 !== elements.length ? <Separator className="mx-4" /> : null} | ||||
|     </React.Fragment> | ||||
|   )); | ||||
|  | ||||
|   return ( | ||||
|     <> | ||||
|       <TitleWrapper>{namespaceHeader}</TitleWrapper> | ||||
|       <Box className="box">{content}</Box> | ||||
|       <div className={classNames("is-flex", "is-align-items-center", "is-size-6", "has-text-weight-bold", "p-3")}> | ||||
|         {namespaceHeader} | ||||
|       </div> | ||||
|       <div className={classNames("box", "p-2")}>{content}</div> | ||||
|       <div className="is-clearfix" /> | ||||
|     </> | ||||
|   ); | ||||
|   | ||||
| @@ -22,7 +22,6 @@ | ||||
|  * SOFTWARE. | ||||
|  */ | ||||
|  | ||||
| import styled from "styled-components"; | ||||
| import Icon from "../Icon"; | ||||
| import { storiesOf } from "@storybook/react"; | ||||
| import { MemoryRouter } from "react-router-dom"; | ||||
| @@ -31,16 +30,12 @@ import GroupEntry from "./GroupEntry"; | ||||
| import { Button, ButtonGroup } from "../buttons"; | ||||
| import copyToClipboard from "../CopyToClipboard"; | ||||
|  | ||||
| const Wrapper = styled.div` | ||||
|   margin: 2rem; | ||||
| `; | ||||
|  | ||||
| const link = "/foo/bar"; | ||||
| const icon = <Icon name="icons fa-2x fa-fw" />; | ||||
| const name = <strong className="is-marginless">main content</strong>; | ||||
| const name = <strong className="m-0">main content</strong>; | ||||
| const description = <small>more text</small>; | ||||
| const longName = ( | ||||
|   <strong className="is-marginless"> | ||||
|   <strong className="m-0"> | ||||
|     Very-important-repository-with-a-particular-long-but-easily-rememberable-name-which-also-is-written-in-kebab-case | ||||
|   </strong> | ||||
| ); | ||||
| @@ -56,7 +51,7 @@ const contentRight = ( | ||||
|  | ||||
| storiesOf("GroupEntry", module) | ||||
|   .addDecorator((story) => <MemoryRouter initialEntries={["/"]}>{story()}</MemoryRouter>) | ||||
|   .addDecorator((storyFn) => <Wrapper>{storyFn()}</Wrapper>) | ||||
|   .addDecorator((storyFn) => <div className="m-5">{storyFn()}</div>) | ||||
|   .add("Default", () => ( | ||||
|     <GroupEntry link={link} avatar={icon} name={name} description={description} contentRight={contentRight} /> | ||||
|   )) | ||||
|   | ||||
| @@ -21,18 +21,14 @@ | ||||
|  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||||
|  * SOFTWARE. | ||||
|  */ | ||||
|  | ||||
| import React, { FC, ReactNode } from "react"; | ||||
| import { Link } from "react-router-dom"; | ||||
| import classNames from "classnames"; | ||||
| import styled from "styled-components"; | ||||
|  | ||||
| const StyledGroupEntry = styled.div` | ||||
|   max-height: calc(90px - 1.5rem); | ||||
|   width: 100%; | ||||
|   display: flex; | ||||
|   justify-content: space-between; | ||||
|   padding: 0.5rem; | ||||
|   align-items: center; | ||||
|   pointer-events: all; | ||||
| `; | ||||
|  | ||||
| @@ -49,7 +45,6 @@ const OverlayLink = styled(Link)` | ||||
| `; | ||||
|  | ||||
| const Avatar = styled.div` | ||||
|   padding-right: 1rem; | ||||
|   .predefined-avatar { | ||||
|     height: 48px; | ||||
|     width: 48px; | ||||
| @@ -57,12 +52,7 @@ const Avatar = styled.div` | ||||
|   } | ||||
| `; | ||||
|  | ||||
| const Name = styled.div` | ||||
|   padding: 0 0.25rem; | ||||
| `; | ||||
|  | ||||
| const Description = styled.p` | ||||
|   padding: 0 0.25rem; | ||||
|   height: 1.5rem; | ||||
|   text-overflow: ellipsis; | ||||
|   overflow-x: hidden; | ||||
| @@ -72,30 +62,14 @@ const Description = styled.p` | ||||
| `; | ||||
|  | ||||
| const ContentLeft = styled.div` | ||||
|   display: flex; | ||||
|   flex: 1 1 auto; | ||||
|   align-items: center; | ||||
|   min-width: 0; | ||||
| `; | ||||
|  | ||||
| const ContentRight = styled.div` | ||||
|   display: flex; | ||||
|   flex: 0 0 auto; | ||||
|   justify-content: flex-end; | ||||
|   pointer-events: all; | ||||
|   padding-left: 2rem; | ||||
|   margin-bottom: -10px; | ||||
| `; | ||||
|  | ||||
| const NameDescriptionWrapper = styled.div` | ||||
|   overflow: hidden; | ||||
|   flex: 1 1 auto; | ||||
| `; | ||||
|  | ||||
| const Wrapper = styled.div` | ||||
|   position: relative; | ||||
| `; | ||||
|  | ||||
| type Props = { | ||||
|   title?: string; | ||||
|   avatar: string | ReactNode; | ||||
| @@ -107,19 +81,32 @@ type Props = { | ||||
|  | ||||
| const GroupEntry: FC<Props> = ({ link, avatar, title, name, description, contentRight }) => { | ||||
|   return ( | ||||
|     <Wrapper> | ||||
|     <div className="is-relative"> | ||||
|       <OverlayLink to={link} /> | ||||
|       <StyledGroupEntry title={title}> | ||||
|         <ContentLeft> | ||||
|           <Avatar>{avatar}</Avatar> | ||||
|           <NameDescriptionWrapper> | ||||
|             <Name>{name}</Name> | ||||
|             <Description>{description}</Description> | ||||
|           </NameDescriptionWrapper> | ||||
|       <StyledGroupEntry | ||||
|         className={classNames("is-flex", "is-justify-content-space-between", "is-align-items-center", "p-2")} | ||||
|         title={title} | ||||
|       > | ||||
|         <ContentLeft className={classNames("is-flex", "is-flex-grow-1", "is-align-items-center")}> | ||||
|           <Avatar className="mr-4">{avatar}</Avatar> | ||||
|           <div className={classNames("is-flex-grow-1", "is-clipped")}> | ||||
|             <div className="mx-1">{name}</div> | ||||
|             <Description className="mx-1">{description}</Description> | ||||
|           </div> | ||||
|         </ContentLeft> | ||||
|         <ContentRight className="is-hidden-touch">{contentRight}</ContentRight> | ||||
|         <ContentRight | ||||
|           className={classNames( | ||||
|             "is-hidden-touch", | ||||
|             "is-flex", | ||||
|             "is-flex-shrink-0", | ||||
|             "is-justify-content-flex-end", | ||||
|             "pl-5" | ||||
|           )} | ||||
|         > | ||||
|           {contentRight} | ||||
|         </ContentRight> | ||||
|       </StyledGroupEntry> | ||||
|     </Wrapper> | ||||
|     </div> | ||||
|   ); | ||||
| }; | ||||
|  | ||||
|   | ||||
| @@ -45,22 +45,14 @@ type Props = { | ||||
| }; | ||||
|  | ||||
| const PageActionContainer = styled.div` | ||||
|   justify-content: flex-end; | ||||
|   align-items: center; | ||||
|  | ||||
|   // every child except first | ||||
|   > * ~ * { | ||||
|     margin-left: 1.25rem; | ||||
|   } | ||||
| `; | ||||
|  | ||||
| const MarginLeft = styled.div` | ||||
|   margin-left: 0.5rem; | ||||
| `; | ||||
|  | ||||
| const FlexContainer = styled.div` | ||||
|   display: flex; | ||||
|   flex-direction: row; | ||||
| const MaxTitleHeight = styled.div` | ||||
|   // remove blank space in repo create form | ||||
|   height: 2.25rem; | ||||
| `; | ||||
|  | ||||
| @@ -98,12 +90,19 @@ export default class Page extends React.Component<Props> { | ||||
|  | ||||
|     let pageActions = null; | ||||
|     let pageActionsExists = false; | ||||
|     React.Children.forEach(children, child => { | ||||
|     React.Children.forEach(children, (child) => { | ||||
|       if (child && !error) { | ||||
|         if (this.isPageAction(child)) { | ||||
|           pageActions = ( | ||||
|             <PageActionContainer | ||||
|               className={classNames("column", "is-three-fifths", "is-mobile-action-spacing", "is-flex-tablet")} | ||||
|               className={classNames( | ||||
|                 "column", | ||||
|                 "is-three-fifths", | ||||
|                 "is-mobile-action-spacing", | ||||
|                 "is-flex-tablet", | ||||
|                 "is-justify-content-flex-end", | ||||
|                 "is-align-items-center" | ||||
|               )} | ||||
|             > | ||||
|               {child} | ||||
|             </PageActionContainer> | ||||
| @@ -119,10 +118,10 @@ export default class Page extends React.Component<Props> { | ||||
|         <> | ||||
|           <div className="columns"> | ||||
|             <div className="column"> | ||||
|               <FlexContainer> | ||||
|               <MaxTitleHeight className="is-flex"> | ||||
|                 <Title title={this.getTextualTitle()}>{this.getTitleComponent()}</Title> | ||||
|                 {afterTitle && <MarginLeft>{afterTitle}</MarginLeft>} | ||||
|               </FlexContainer> | ||||
|                 {afterTitle && <div className="ml-2">{afterTitle}</div>} | ||||
|               </MaxTitleHeight> | ||||
|               {subtitle ? <Subtitle>{subtitle}</Subtitle> : null} | ||||
|             </div> | ||||
|             {pageActions} | ||||
| @@ -145,7 +144,7 @@ export default class Page extends React.Component<Props> { | ||||
|     } | ||||
|  | ||||
|     const content: ReactNode[] = []; | ||||
|     React.Children.forEach(children, child => { | ||||
|     React.Children.forEach(children, (child) => { | ||||
|       if (child) { | ||||
|         if (!this.isPageAction(child)) { | ||||
|           content.push(child); | ||||
|   | ||||
| @@ -76,7 +76,7 @@ export const Modal: FC<Props> = ({ | ||||
|       <div className="modal-background" onClick={closeFunction} /> | ||||
|       <SizedModal className="modal-card" size={size}> | ||||
|         <header className={classNames("modal-card-head", `has-background-${headColor}`)}> | ||||
|           <p className={`modal-card-title is-marginless has-text-${headTextColor}`}>{title}</p> | ||||
|           <p className={`modal-card-title m-0 has-text-${headTextColor}`}>{title}</p> | ||||
|           <button className="delete" aria-label="close" onClick={closeFunction} /> | ||||
|         </header> | ||||
|         <section className="modal-card-body">{body}</section> | ||||
|   | ||||
| @@ -82,7 +82,7 @@ const SecondaryNavigation: FC<Props> = ({ label, children, collapsible = true }) | ||||
|     <SectionContainer className="menu"> | ||||
|       <div> | ||||
|         <MenuLabel | ||||
|           className={classNames("menu-label", { "has-cursor-pointer": collapsible })} | ||||
|           className={classNames("menu-label", { "is-clickable": collapsible })} | ||||
|           collapsed={isCollapsed} | ||||
|           onClick={toggleCollapseState} | ||||
|         > | ||||
|   | ||||
| @@ -43,7 +43,7 @@ type ContainerProps = { | ||||
| const PopoverContainer = styled.div<ContainerProps>` | ||||
|   position: absolute; | ||||
|   z-index: 100; | ||||
|   width: ${props => props.width}px; | ||||
|   width: ${(props) => props.width}px; | ||||
|   display: block; | ||||
|  | ||||
|   &:before { | ||||
| @@ -54,7 +54,7 @@ const PopoverContainer = styled.div<ContainerProps>` | ||||
|     height: 0; | ||||
|     width: 0; | ||||
|     top: 100%; | ||||
|     left: ${props => props.width / 2}px; | ||||
|     left: ${(props) => props.width / 2}px; | ||||
|     border-color: transparent; | ||||
|     border-bottom-color: white; | ||||
|     border-left-color: white; | ||||
| @@ -67,15 +67,11 @@ const PopoverContainer = styled.div<ContainerProps>` | ||||
|   } | ||||
| `; | ||||
|  | ||||
| const SmallHr = styled.hr` | ||||
|   margin: 0.5em 0; | ||||
| `; | ||||
|  | ||||
| const PopoverHeading = styled.div` | ||||
|   height: 1.5em; | ||||
| `; | ||||
|  | ||||
| const Popover: FC<Props> = props => { | ||||
| const Popover: FC<Props> = (props) => { | ||||
|   if (!props.show) { | ||||
|     return null; | ||||
|   } | ||||
| @@ -93,13 +89,13 @@ const InnerPopover: FC<Props> = ({ title, show, width, offsetTop, offsetLeft, di | ||||
|  | ||||
|   const onMouseEnter = () => { | ||||
|     dispatch({ | ||||
|       type: "enter-popover" | ||||
|       type: "enter-popover", | ||||
|     }); | ||||
|   }; | ||||
|  | ||||
|   const onMouseLeave = () => { | ||||
|     dispatch({ | ||||
|       type: "leave-popover" | ||||
|       type: "leave-popover", | ||||
|     }); | ||||
|   }; | ||||
|  | ||||
| @@ -109,20 +105,20 @@ const InnerPopover: FC<Props> = ({ title, show, width, offsetTop, offsetLeft, di | ||||
|     <PopoverContainer | ||||
|       onMouseEnter={onMouseEnter} | ||||
|       onMouseLeave={onMouseLeave} | ||||
|       className={"box"} | ||||
|       className="box" | ||||
|       style={{ top: `${top}px`, left: `${left}px` }} | ||||
|       width={width!} | ||||
|       ref={ref} | ||||
|     > | ||||
|       <PopoverHeading>{title}</PopoverHeading> | ||||
|       <SmallHr /> | ||||
|       <hr className="my-2" /> | ||||
|       {children} | ||||
|     </PopoverContainer> | ||||
|   ); | ||||
| }; | ||||
|  | ||||
| Popover.defaultProps = { | ||||
|   width: 120 | ||||
|   width: 120, | ||||
| }; | ||||
|  | ||||
| export default Popover; | ||||
|   | ||||
| @@ -27,7 +27,6 @@ import Tooltip from "../Tooltip"; | ||||
|  | ||||
| const Button = styled.a` | ||||
|   width: 50px; | ||||
|   cursor: pointer; | ||||
|   &:hover { | ||||
|     color: #33b2e8; | ||||
|   } | ||||
| @@ -47,7 +46,7 @@ const DiffButton: FC<Props> = ({ icon, tooltip, onClick }) => { | ||||
|  | ||||
|   return ( | ||||
|     <Tooltip message={tooltip} location="top"> | ||||
|       <Button aria-label={tooltip} className="button" onClick={handleClick}> | ||||
|       <Button aria-label={tooltip} className="button is-clickable" onClick={handleClick}> | ||||
|         <i className={`fas fa-${icon}`} /> | ||||
|       </Button> | ||||
|     </Tooltip> | ||||
|   | ||||
| @@ -69,22 +69,6 @@ const FullWidthTitleHeader = styled.div` | ||||
|   max-width: 100%; | ||||
| `; | ||||
|  | ||||
| const TitleWrapper = styled.span` | ||||
|   margin-left: 0.25rem; | ||||
| `; | ||||
|  | ||||
| const AlignRight = styled.div` | ||||
|   margin-left: auto; | ||||
| `; | ||||
|  | ||||
| const HunkDivider = styled.hr` | ||||
|   margin: 0.5rem 0; | ||||
| `; | ||||
|  | ||||
| const ChangeTypeTag = styled(Tag)` | ||||
|   margin-left: 0.75rem; | ||||
| `; | ||||
|  | ||||
| const MarginlessModalContent = styled.div` | ||||
|   margin: -1.25rem; | ||||
|  | ||||
| @@ -319,7 +303,7 @@ class DiffFile extends React.Component<Props, State> { | ||||
|     } else if (i > 0) { | ||||
|       items.push( | ||||
|         <Decoration> | ||||
|           <HunkDivider /> | ||||
|           <hr className="my-2" /> | ||||
|         </Decoration> | ||||
|       ); | ||||
|     } | ||||
| @@ -403,8 +387,8 @@ class DiffFile extends React.Component<Props, State> { | ||||
|  | ||||
|     const color = value === "added" ? "success" : value === "deleted" ? "danger" : "info"; | ||||
|     return ( | ||||
|       <ChangeTypeTag | ||||
|         className={classNames("has-text-weight-normal")} | ||||
|       <Tag | ||||
|         className={classNames("has-text-weight-normal", "ml-3")} | ||||
|         rounded={true} | ||||
|         outlined={true} | ||||
|         color={color} | ||||
| @@ -431,7 +415,7 @@ class DiffFile extends React.Component<Props, State> { | ||||
|  | ||||
|     const fileAnnotations = fileAnnotationFactory ? fileAnnotationFactory(file) : null; | ||||
|     const innerContent = ( | ||||
|       <div className="panel-block is-paddingless"> | ||||
|       <div className="panel-block p-0"> | ||||
|         {fileAnnotations} | ||||
|         <TokenizedDiffView className={viewType} viewType={viewType} file={file}> | ||||
|           {(hunks: HunkType[]) => | ||||
| @@ -475,13 +459,13 @@ class DiffFile extends React.Component<Props, State> { | ||||
|       </MenuContext.Consumer> | ||||
|     ); | ||||
|     const headerButtons = ( | ||||
|       <AlignRight className={classNames("level-right", "is-flex")}> | ||||
|       <div className={classNames("level-right", "is-flex", "ml-auto")}> | ||||
|         <ButtonGroup> | ||||
|           {sideBySideToggle} | ||||
|           {openInFullscreen} | ||||
|           {fileControls} | ||||
|         </ButtonGroup> | ||||
|       </AlignRight> | ||||
|       </div> | ||||
|     ); | ||||
|  | ||||
|     let errorModal; | ||||
| @@ -506,14 +490,14 @@ class DiffFile extends React.Component<Props, State> { | ||||
|         <div className="panel-heading"> | ||||
|           <div className={classNames("level", "is-flex-wrap-wrap")}> | ||||
|             <FullWidthTitleHeader | ||||
|               className={classNames("level-left", "is-flex", "has-cursor-pointer")} | ||||
|               className={classNames("level-left", "is-flex", "is-clickable")} | ||||
|               onClick={this.toggleCollapse} | ||||
|               title={this.hoverFileTitle(file)} | ||||
|             > | ||||
|               {collapseIcon} | ||||
|               <TitleWrapper className={classNames("is-ellipsis-overflow", "is-size-6")}> | ||||
|               <span className={classNames("is-ellipsis-overflow", "is-size-6", "ml-1")}> | ||||
|                 {this.renderFileTitle(file)} | ||||
|               </TitleWrapper> | ||||
|               </span> | ||||
|               {this.renderChangeTag(file)} | ||||
|             </FullWidthTitleHeader> | ||||
|             {headerButtons} | ||||
|   | ||||
| @@ -43,7 +43,7 @@ const HealthCheckFailureDetail: FC<Props> = ({ active, closeFunction, failures } | ||||
|   return ( | ||||
|     <Modal | ||||
|       body={ | ||||
|         <div className={"content"}> | ||||
|         <div className="content"> | ||||
|           <HealthCheckFailureList failures={failures} /> | ||||
|         </div> | ||||
|       } | ||||
|   | ||||
| @@ -28,14 +28,12 @@ import styled from "styled-components"; | ||||
|  | ||||
| const HunkDivider = styled.div` | ||||
|   background: #98d8f3; | ||||
|   font-size: 0.7rem; | ||||
|   padding-left: 1.78em; | ||||
| `; | ||||
|  | ||||
| const HunkExpandDivider: FC = ({ children }) => { | ||||
|   return ( | ||||
|     <Decoration> | ||||
|       <HunkDivider>{children}</HunkDivider> | ||||
|       <HunkDivider className="is-size-7 pl-5">{children}</HunkDivider> | ||||
|     </Decoration> | ||||
|   ); | ||||
| }; | ||||
|   | ||||
| @@ -24,7 +24,6 @@ | ||||
| import React, { FC, useState } from "react"; | ||||
| import { useTranslation } from "react-i18next"; | ||||
| import classNames from "classnames"; | ||||
| import styled from "styled-components"; | ||||
|  | ||||
| type Props = { | ||||
|   icon: string; | ||||
| @@ -32,10 +31,6 @@ type Props = { | ||||
|   onClick: () => Promise<any>; | ||||
| }; | ||||
|  | ||||
| const ExpandLink = styled.span` | ||||
|   cursor: pointer; | ||||
| `; | ||||
|  | ||||
| const HunkExpandLink: FC<Props> = ({ icon, text, onClick }) => { | ||||
|   const [t] = useTranslation("repos"); | ||||
|   const [loading, setLoading] = useState(false); | ||||
| @@ -49,9 +44,9 @@ const HunkExpandLink: FC<Props> = ({ icon, text, onClick }) => { | ||||
|   }; | ||||
|  | ||||
|   return ( | ||||
|     <ExpandLink onClick={onClickWithLoadingMarker}> | ||||
|     <span className="is-clickable" onClick={onClickWithLoadingMarker}> | ||||
|       <i className={classNames("fa", icon)} /> {loading ? t("diff.expanding") : text} | ||||
|     </ExpandLink> | ||||
|     </span> | ||||
|   ); | ||||
| }; | ||||
|  | ||||
|   | ||||
| @@ -24,13 +24,11 @@ | ||||
| import React, { FC } from "react"; | ||||
| import styled from "styled-components"; | ||||
| import { Link } from "react-router-dom"; | ||||
| import { useTranslation } from "react-i18next"; | ||||
| import Tooltip from "../Tooltip"; | ||||
| import Icon from "../Icon"; | ||||
|  | ||||
| const Button = styled(Link)` | ||||
|   width: 50px; | ||||
|   cursor: pointer; | ||||
|   &:hover { | ||||
|     color: #33b2e8; | ||||
|   } | ||||
| @@ -44,7 +42,7 @@ type Props = { | ||||
| const JumpToFileButton: FC<Props> = ({ link, tooltip }) => { | ||||
|   return ( | ||||
|     <Tooltip message={tooltip} location="top"> | ||||
|       <Button aria-label={tooltip} className="button" to={link}> | ||||
|       <Button aria-label={tooltip} className="button is-clickable" to={link}> | ||||
|         <Icon name="file-code" color="inherit" /> | ||||
|       </Button> | ||||
|     </Tooltip> | ||||
|   | ||||
| @@ -22,16 +22,14 @@ | ||||
|  * SOFTWARE. | ||||
|  */ | ||||
| import React, { FC } from "react"; | ||||
| import { useTranslation } from "react-i18next"; | ||||
| import { NotFoundError, useDiff } from "@scm-manager/ui-api"; | ||||
| import ErrorNotification from "../ErrorNotification"; | ||||
| import Notification from "../Notification"; | ||||
|  | ||||
| import Loading from "../Loading"; | ||||
| import Notification from "../Notification"; | ||||
| import Button from "../buttons/Button"; | ||||
| import Diff from "./Diff"; | ||||
| import { DiffObjectProps } from "./DiffTypes"; | ||||
| import { useTranslation } from "react-i18next"; | ||||
| import Button from "../buttons/Button"; | ||||
| import styled from "styled-components"; | ||||
|  | ||||
| type Props = DiffObjectProps & { | ||||
|   url: string; | ||||
| @@ -44,19 +42,15 @@ type NotificationProps = { | ||||
|   isFetchingNextPage: boolean; | ||||
| }; | ||||
|  | ||||
| const StyledNotification = styled(Notification)` | ||||
|   margin-top: 1.5rem; | ||||
| `; | ||||
|  | ||||
| const PartialNotification: FC<NotificationProps> = ({ fetchNextPage, isFetchingNextPage }) => { | ||||
|   const [t] = useTranslation("repos"); | ||||
|   return ( | ||||
|     <StyledNotification type="info"> | ||||
|     <Notification className="mt-5" type="info"> | ||||
|       <div className="columns is-centered"> | ||||
|         <div className="column">{t("changesets.moreDiffsAvailable")}</div> | ||||
|         <Button label={t("changesets.loadMore")} action={fetchNextPage} loading={isFetchingNextPage} /> | ||||
|       </div> | ||||
|     </StyledNotification> | ||||
|     </Notification> | ||||
|   ); | ||||
| }; | ||||
|  | ||||
|   | ||||
| @@ -31,6 +31,7 @@ import RepositoryFlags from "./RepositoryFlags"; | ||||
| import styled from "styled-components"; | ||||
| import Icon from "../Icon"; | ||||
| import { useTranslation } from "react-i18next"; | ||||
| import classNames from "classnames"; | ||||
|  | ||||
| type DateProp = Date | string; | ||||
|  | ||||
| @@ -43,26 +44,9 @@ type Props = { | ||||
|  | ||||
| const ContentRightContainer = styled.div` | ||||
|   height: calc(80px - 1.5rem); | ||||
|   margin-right: 1rem; | ||||
|   position: relative; | ||||
|   display: flex; | ||||
|   flex-direction: column; | ||||
|   justify-content: space-between; | ||||
| `; | ||||
|  | ||||
| const DateWrapper = styled.small` | ||||
|   padding-bottom: 0.25rem; | ||||
| `; | ||||
|  | ||||
| const QuickActionbar = styled.span` | ||||
|   display: flex; | ||||
|   justify-content: flex-end; | ||||
|   align-items: flex-end; | ||||
| `; | ||||
|  | ||||
| const QuickAction = styled(Icon)` | ||||
|   font-size: 1.25rem; | ||||
|  | ||||
|   :hover { | ||||
|     color: #363636 !important; | ||||
|   } | ||||
| @@ -80,7 +64,15 @@ const RepositoryEntry: FC<Props> = ({ repository, baseDate }) => { | ||||
|   const [openCloneModal, setOpenCloneModal] = useState(false); | ||||
|  | ||||
|   const createContentRight = () => ( | ||||
|     <ContentRightContainer> | ||||
|     <ContentRightContainer | ||||
|       className={classNames( | ||||
|         "is-flex", | ||||
|         "is-flex-direction-column", | ||||
|         "is-justify-content-space-between", | ||||
|         "is-relative", | ||||
|         "mr-4" | ||||
|       )} | ||||
|     > | ||||
|       {openCloneModal && ( | ||||
|         <Modal | ||||
|           size="L" | ||||
| @@ -98,18 +90,18 @@ const RepositoryEntry: FC<Props> = ({ repository, baseDate }) => { | ||||
|           closeFunction={() => setOpenCloneModal(false)} | ||||
|         /> | ||||
|       )} | ||||
|       <QuickActionbar> | ||||
|       <span className={classNames("is-flex", "is-justify-content-flex-end", "is-align-items-flex-end")}> | ||||
|         <QuickAction | ||||
|           className={classNames("is-clickable", "is-size-5")} | ||||
|           name="download" | ||||
|           color="info" | ||||
|           className="has-cursor-pointer" | ||||
|           onClick={() => setOpenCloneModal(true)} | ||||
|           title={t("overview.clone")} | ||||
|         /> | ||||
|       </QuickActionbar> | ||||
|       <DateWrapper> | ||||
|       </span> | ||||
|       <small className="pb-1"> | ||||
|         <DateFromNow baseDate={baseDate} date={repository.lastModified || repository.creationDate} /> | ||||
|       </DateWrapper> | ||||
|       </small> | ||||
|     </ContentRightContainer> | ||||
|   ); | ||||
|  | ||||
|   | ||||
| @@ -23,7 +23,6 @@ | ||||
|  */ | ||||
| import React from "react"; | ||||
| import { Link } from "react-router-dom"; | ||||
| import styled from "styled-components"; | ||||
| import { Icon } from "@scm-manager/ui-components"; | ||||
| import Tooltip from "../Tooltip"; | ||||
|  | ||||
| @@ -33,10 +32,6 @@ type Props = { | ||||
|   tooltip?: string; | ||||
| }; | ||||
|  | ||||
| const PointerEventsLink = styled(Link)` | ||||
|   pointer-events: all; | ||||
| `; | ||||
|  | ||||
| class RepositoryEntryLink extends React.Component<Props> { | ||||
|   render() { | ||||
|     const { to, icon, tooltip } = this.props; | ||||
| @@ -51,9 +46,9 @@ class RepositoryEntryLink extends React.Component<Props> { | ||||
|     } | ||||
|  | ||||
|     return ( | ||||
|       <PointerEventsLink className="level-item" to={to}> | ||||
|       <Link className="level-item is-clickable" to={to}> | ||||
|         {content} | ||||
|       </PointerEventsLink> | ||||
|       </Link> | ||||
|     ); | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -21,15 +21,15 @@ | ||||
|  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||||
|  * SOFTWARE. | ||||
|  */ | ||||
|  | ||||
| import React, { FC, useState } from "react"; | ||||
| import RepositoryFlag from "./RepositoryFlag"; | ||||
| import { ExtensionPoint } from "@scm-manager/ui-extensions"; | ||||
| import { Repository } from "@scm-manager/ui-types"; | ||||
| import { useTranslation } from "react-i18next"; | ||||
| import classNames from "classnames"; | ||||
| import styled from "styled-components"; | ||||
| import HealthCheckFailureDetail from "./HealthCheckFailureDetail"; | ||||
| import { Repository } from "@scm-manager/ui-types"; | ||||
| import { ExtensionPoint } from "@scm-manager/ui-extensions"; | ||||
| import { TooltipLocation } from "../Tooltip"; | ||||
| import RepositoryFlag from "./RepositoryFlag"; | ||||
| import HealthCheckFailureDetail from "./HealthCheckFailureDetail"; | ||||
|  | ||||
| type Props = { | ||||
|   repository: Repository; | ||||
| @@ -37,11 +37,6 @@ type Props = { | ||||
|   tooltipLocation?: TooltipLocation; | ||||
| }; | ||||
|  | ||||
| const Wrapper = styled.span` | ||||
|   display: flex; | ||||
|   align-items: center; | ||||
| `; | ||||
|  | ||||
| const RepositoryFlagContainer = styled.div` | ||||
|   .tag { | ||||
|     margin-left: 0.25rem; | ||||
| @@ -91,13 +86,13 @@ const RepositoryFlags: FC<Props> = ({ repository, className, tooltipLocation = " | ||||
|   ); | ||||
|  | ||||
|   return ( | ||||
|     <Wrapper> | ||||
|     <span className={classNames("is-flex", "is-align-items-center", className)}> | ||||
|       {modal} | ||||
|       <RepositoryFlagContainer> | ||||
|         {repositoryFlags} | ||||
|         <ExtensionPoint name="repository.flags" props={{ repository, tooltipLocation }} renderAll={true} /> | ||||
|       </RepositoryFlagContainer> | ||||
|     </Wrapper> | ||||
|     </span> | ||||
|   ); | ||||
| }; | ||||
|  | ||||
|   | ||||
| @@ -36,7 +36,6 @@ import Popover from "./Popover"; | ||||
| import AnnotateLine from "./AnnotateLine"; | ||||
| import { Action } from "./actions"; | ||||
| import { determineLanguage } from "../../languages"; | ||||
| import styled from "styled-components"; | ||||
|  | ||||
| type Props = { | ||||
|   source: AnnotatedSource; | ||||
| @@ -57,11 +56,6 @@ const initialState = { | ||||
|   onLine: false, | ||||
| }; | ||||
|  | ||||
| const NoSpacingReactSyntaxHighlighter = styled(ReactSyntaxHighlighter)` | ||||
|   margin: 0 !important; | ||||
|   padding: 0 !important; | ||||
| `; | ||||
|  | ||||
| const reducer = (state: State, action: Action): State => { | ||||
|   switch (action.type) { | ||||
|     case "enter-line": { | ||||
| @@ -152,14 +146,15 @@ const Annotate: FC<Props> = ({ source, repository, baseDate }) => { | ||||
|   return ( | ||||
|     <div className="panel-block"> | ||||
|       {popover} | ||||
|       <NoSpacingReactSyntaxHighlighter | ||||
|       <ReactSyntaxHighlighter | ||||
|         className="m-0 p-0" | ||||
|         showLineNumbers={false} | ||||
|         language={determineLanguage(source.language)} | ||||
|         style={highlightingTheme} | ||||
|         renderer={defaultRenderer} | ||||
|       > | ||||
|         {code} | ||||
|       </NoSpacingReactSyntaxHighlighter> | ||||
|       </ReactSyntaxHighlighter> | ||||
|     </div> | ||||
|   ); | ||||
| }; | ||||
|   | ||||
| @@ -44,8 +44,6 @@ const Author = styled(LineElement)` | ||||
| `; | ||||
|  | ||||
| const When = styled(LineElement)` | ||||
|   display: inline-block; | ||||
|  | ||||
|   width: 6.5em; | ||||
|   overflow: hidden; | ||||
|   text-overflow: ellipsis; | ||||
|   | ||||
| @@ -31,7 +31,7 @@ import { DateInput } from "../../useDateFormatter"; | ||||
| import { Repository, AnnotatedLine } from "@scm-manager/ui-types"; | ||||
| import AuthorImage from "./AuthorImage"; | ||||
| import { Action } from "./actions"; | ||||
| import {useTranslation} from "react-i18next"; | ||||
| import { useTranslation } from "react-i18next"; | ||||
|  | ||||
| const PopoverContainer = styled.div` | ||||
|   position: absolute; | ||||
| @@ -62,16 +62,11 @@ const PopoverContainer = styled.div` | ||||
|   } | ||||
| `; | ||||
|  | ||||
| const SmallHr = styled.hr` | ||||
|   margin: 0.5em 0; | ||||
| `; | ||||
|  | ||||
| const PopoverHeading = styled.div` | ||||
|   height: 1.5em; | ||||
| `; | ||||
|  | ||||
| const PopoverDescription = styled.p` | ||||
|   margin-top: 0.5em; | ||||
|   overflow-wrap: break-word; | ||||
| `; | ||||
|  | ||||
| @@ -102,13 +97,13 @@ const Popover: FC<PopoverProps> = ({ annotation, offsetTop, repository, baseDate | ||||
|  | ||||
|   const onMouseEnter = () => { | ||||
|     dispatch({ | ||||
|       type: "enter-popover" | ||||
|       type: "enter-popover", | ||||
|     }); | ||||
|   }; | ||||
|  | ||||
|   const OnMouseLeave = () => { | ||||
|     dispatch({ | ||||
|       type: "leave-popover" | ||||
|       type: "leave-popover", | ||||
|     }); | ||||
|   }; | ||||
|  | ||||
| @@ -128,14 +123,14 @@ const Popover: FC<PopoverProps> = ({ annotation, offsetTop, repository, baseDate | ||||
|         </span> | ||||
|         <DateFromNow className="is-pulled-right" date={annotation.when} baseDate={baseDate} /> | ||||
|       </PopoverHeading> | ||||
|       <SmallHr /> | ||||
|       <hr className="my-2" /> | ||||
|       <p> | ||||
|         {t("changeset.label") + " "} | ||||
|         <Link to={`/repo/${repository.namespace}/${repository.name}/code/changeset/${annotation.revision}`}> | ||||
|           {shortRevision(annotation.revision)} | ||||
|         </Link> | ||||
|       </p> | ||||
|       <PopoverDescription className="content">{annotation.description}</PopoverDescription> | ||||
|       <PopoverDescription className="content mt-2">{annotation.description}</PopoverDescription> | ||||
|     </PopoverContainer> | ||||
|   ); | ||||
| }; | ||||
|   | ||||
| @@ -38,7 +38,7 @@ class ChangesetButtonGroup extends React.Component<Props> { | ||||
|     const changesetLink = createChangesetLink(repository, changeset); | ||||
|     const sourcesLink = createSourcesLink(repository, changeset); | ||||
|     return ( | ||||
|       <ButtonAddons className="is-marginless"> | ||||
|       <ButtonAddons className="m-0"> | ||||
|         <Button link={changesetLink} icon="exchange-alt" label={t("changeset.buttons.details")} reducedMobile={true} /> | ||||
|         <Button link={sourcesLink} icon="code" label={t("changeset.buttons.sources")} reducedMobile={true} /> | ||||
|       </ButtonAddons> | ||||
|   | ||||
| @@ -52,39 +52,15 @@ const Wrapper = styled.div` | ||||
|   } | ||||
| `; | ||||
|  | ||||
| const AvatarFigure = styled.figure` | ||||
|   margin-top: 0.5rem; | ||||
|   margin-right: 0.5rem; | ||||
| `; | ||||
|  | ||||
| const FixedSizedAvatar = styled.div` | ||||
|   width: 35px; | ||||
|   height: 35px; | ||||
| `; | ||||
|  | ||||
| const Metadata = styled.div` | ||||
|   margin-left: 0; | ||||
| const FullWidthDiv = styled.div` | ||||
|   width: 100%; | ||||
| `; | ||||
|  | ||||
| const AuthorWrapper = styled.p` | ||||
|   margin-top: 0.5rem; | ||||
| `; | ||||
|  | ||||
| const VCenteredColumn = styled.div` | ||||
|   align-self: center; | ||||
| `; | ||||
|  | ||||
| const VCenteredChildColumn = styled.div` | ||||
|   align-items: center; | ||||
|   justify-content: flex-end; | ||||
| `; | ||||
|  | ||||
| const FlexRow = styled.div` | ||||
|   display: flex; | ||||
|   flex-direction: row; | ||||
| `; | ||||
|  | ||||
| class ChangesetRow extends React.Component<Props> { | ||||
|   createChangesetId = (changeset: Changeset) => { | ||||
|     const { repository } = this.props; | ||||
| @@ -99,25 +75,25 @@ class ChangesetRow extends React.Component<Props> { | ||||
|  | ||||
|     return ( | ||||
|       <Wrapper> | ||||
|         <div className="columns is-gapless is-mobile"> | ||||
|           <div className="column is-three-fifths"> | ||||
|             <div className="columns is-gapless"> | ||||
|               <div className="column is-four-fifths"> | ||||
|         <div className={classNames("columns", "is-gapless", "is-mobile")}> | ||||
|           <div className={classNames("column", "is-three-fifths")}> | ||||
|             <div className={classNames("columns", "is-gapless")}> | ||||
|               <div className={classNames("column", "is-four-fifths")}> | ||||
|                 <div className="media"> | ||||
|                   <AvatarWrapper> | ||||
|                     <AvatarFigure className="media-left"> | ||||
|                     <figure className={classNames("media-left", "mt-2", "mr-2")}> | ||||
|                       <FixedSizedAvatar className="image"> | ||||
|                         <AvatarImage person={changeset.author} /> | ||||
|                       </FixedSizedAvatar> | ||||
|                     </AvatarFigure> | ||||
|                     </figure> | ||||
|                   </AvatarWrapper> | ||||
|                   <Metadata className="media-right"> | ||||
|                     <h4 className="has-text-weight-bold is-ellipsis-overflow"> | ||||
|                   <FullWidthDiv className={classNames("media-right", "ml-0")}> | ||||
|                     <h4 className={classNames("has-text-weight-bold", "is-ellipsis-overflow")}> | ||||
|                       <ExtensionPoint | ||||
|                         name="changeset.description" | ||||
|                         props={{ | ||||
|                           changeset, | ||||
|                           value: description.title | ||||
|                           value: description.title, | ||||
|                         }} | ||||
|                         renderAll={false} | ||||
|                       > | ||||
| @@ -130,36 +106,33 @@ class ChangesetRow extends React.Component<Props> { | ||||
|                     <p className="is-hidden-desktop"> | ||||
|                       <Trans i18nKey="repos:changeset.shortSummary" components={[changesetId, dateFromNow]} /> | ||||
|                     </p> | ||||
|                     <FlexRow> | ||||
|                       <AuthorWrapper className="is-size-7 is-ellipsis-overflow"> | ||||
|                     <div className="is-flex"> | ||||
|                       <p className={classNames("is-size-7", "is-ellipsis-overflow", "mt-2")}> | ||||
|                         <ChangesetAuthor changeset={changeset} /> | ||||
|                       </AuthorWrapper> | ||||
|                       </p> | ||||
|                       {changeset?.signatures && changeset.signatures.length > 0 && ( | ||||
|                         <SignatureIcon | ||||
|                           className="mx-2 pt-1" | ||||
|                           signatures={changeset.signatures} | ||||
|                         /> | ||||
|                         <SignatureIcon className={classNames("mx-2", "pt-1")} signatures={changeset.signatures} /> | ||||
|                       )} | ||||
|                     </FlexRow> | ||||
|                   </Metadata> | ||||
|                     </div> | ||||
|                   </FullWidthDiv> | ||||
|                 </div> | ||||
|               </div> | ||||
|               <VCenteredColumn className="column"> | ||||
|               <div className={classNames("column", "is-align-self-center")}> | ||||
|                 <ChangesetTags changeset={changeset} /> | ||||
|               </VCenteredColumn> | ||||
|               </div> | ||||
|             </div> | ||||
|           <VCenteredChildColumn className={classNames("column", "is-flex")}> | ||||
|           </div> | ||||
|           <div className={classNames("column", "is-flex", "is-justify-content-flex-end", "is-align-items-center")}> | ||||
|             <ChangesetButtonGroup repository={repository} changeset={changeset} /> | ||||
|             <ExtensionPoint | ||||
|               name="changeset.right" | ||||
|               props={{ | ||||
|                 repository, | ||||
|                 changeset | ||||
|                 changeset, | ||||
|               }} | ||||
|               renderAll={true} | ||||
|             /> | ||||
|           </VCenteredChildColumn> | ||||
|           </div> | ||||
|         </div> | ||||
|       </Wrapper> | ||||
|     ); | ||||
|   | ||||
| @@ -22,7 +22,7 @@ | ||||
|  * SOFTWARE. | ||||
|  */ | ||||
| import React, { FC } from "react"; | ||||
| import styled from "styled-components"; | ||||
| import classNames from "classnames"; | ||||
| import Icon from "../Icon"; | ||||
|  | ||||
| type Props = { | ||||
| @@ -30,13 +30,8 @@ type Props = { | ||||
|   isVisible: boolean; | ||||
| }; | ||||
|  | ||||
| const IconWithMarginLeft = styled(Icon)` | ||||
|   visibility: ${(props: Props) => (props.isVisible ? "visible" : "hidden")}; | ||||
|   margin-left: 0.25em; | ||||
| `; | ||||
|  | ||||
| const SortIcon: FC<Props> = (props: Props) => { | ||||
|   return <IconWithMarginLeft name={props.name} isVisible={props.isVisible} />; | ||||
|   return <Icon className={classNames("ml-1", { "is-invisible": !props.isVisible })} name={props.name} />; | ||||
| }; | ||||
|  | ||||
| export default SortIcon; | ||||
|   | ||||
| @@ -109,7 +109,7 @@ const Table: FC<Props> = ({ data, sortable, children, emptyMessage, className }) | ||||
|         <tr> | ||||
|           {React.Children.map(children, (child, index) => ( | ||||
|             <th | ||||
|               className={isSortable(child) && "has-cursor-pointer"} | ||||
|               className={isSortable(child) && "is-clickable"} | ||||
|               onClick={isSortable(child) ? () => tableSort(index) : undefined} | ||||
|               onMouseEnter={() => setHoveredColumnIndex(index)} | ||||
|               onMouseLeave={() => setHoveredColumnIndex(undefined)} | ||||
|   | ||||
| @@ -59,11 +59,6 @@ const Container = styled.div<Themeable>` | ||||
|   } | ||||
| `; | ||||
|  | ||||
| const Title = styled.h1<Themeable>` | ||||
|   margin-bottom: 0.25rem; | ||||
|   font-weight: bold; | ||||
| `; | ||||
|  | ||||
| const Toast: FC<Props> = ({ children, title, type }) => { | ||||
|   const rootElement = usePortalRootElement("toastRoot"); | ||||
|   if (!rootElement) { | ||||
| @@ -74,7 +69,7 @@ const Toast: FC<Props> = ({ children, title, type }) => { | ||||
|   const theme = getTheme(type); | ||||
|   const content = ( | ||||
|     <Container theme={theme}> | ||||
|       <Title theme={theme}>{title}</Title> | ||||
|       <h1 className="mb-1 has-text-weight-bold">{title}</h1> | ||||
|       <ToastThemeContext.Provider value={theme}>{children}</ToastThemeContext.Provider> | ||||
|     </Container> | ||||
|   ); | ||||
|   | ||||
| @@ -23,6 +23,7 @@ | ||||
|  */ | ||||
| import React, { FC, useContext, MouseEvent } from "react"; | ||||
| import { ToastThemeContext, Themeable } from "./themes"; | ||||
| import classNames from "classnames"; | ||||
| import styled from "styled-components"; | ||||
|  | ||||
| type Props = { | ||||
| @@ -30,25 +31,21 @@ type Props = { | ||||
|   onClick?: () => void; | ||||
| }; | ||||
|  | ||||
| const ThemedButton = styled.button.attrs(props => ({ | ||||
|   className: "button" | ||||
| const ThemedButton = styled.button.attrs((props) => ({ | ||||
|   className: "button", | ||||
| }))<Themeable>` | ||||
|   color: ${props => props.theme.primary}; | ||||
|   border-color: ${props => props.theme.primary}; | ||||
|   background-color: ${props => props.theme.secondary}; | ||||
|   color: ${(props) => props.theme.primary}; | ||||
|   border-color: ${(props) => props.theme.primary}; | ||||
|   background-color: ${(props) => props.theme.secondary}; | ||||
|   font-size: 0.75rem; | ||||
|  | ||||
|   &:hover { | ||||
|     color: ${props => props.theme.primary}; | ||||
|     border-color: ${props => props.theme.tertiary}; | ||||
|     background-color: ${props => props.theme.tertiary}; | ||||
|     color: ${(props) => props.theme.primary}; | ||||
|     border-color: ${(props) => props.theme.tertiary}; | ||||
|     background-color: ${(props) => props.theme.tertiary}; | ||||
|   } | ||||
| `; | ||||
|  | ||||
| const ToastButtonIcon = styled.i` | ||||
|   margin-right: 0.25rem; | ||||
| `; | ||||
|  | ||||
| const ToastButton: FC<Props> = ({ icon, onClick, children }) => { | ||||
|   const theme = useContext(ToastThemeContext); | ||||
|  | ||||
| @@ -61,7 +58,7 @@ const ToastButton: FC<Props> = ({ icon, onClick, children }) => { | ||||
|  | ||||
|   return ( | ||||
|     <ThemedButton theme={theme} onClick={handleClick}> | ||||
|       {icon && <ToastButtonIcon className={`fas fa-fw fa-${icon}`} />} {children} | ||||
|       {icon && <i className={classNames("fas", "fa-fw", `fa-${icon}`, "mr-1")} />} {children} | ||||
|     </ThemedButton> | ||||
|   ); | ||||
| }; | ||||
|   | ||||
| @@ -24,9 +24,7 @@ | ||||
| import React, { FC } from "react"; | ||||
| import styled from "styled-components"; | ||||
|  | ||||
| const Buttons = styled.div` | ||||
|   display: flex; | ||||
|   padding-top: 0.5rem; | ||||
| const FullWidthDiv = styled.div` | ||||
|   width: 100%; | ||||
|  | ||||
|   & > * { | ||||
| @@ -38,6 +36,6 @@ const Buttons = styled.div` | ||||
|   } | ||||
| `; | ||||
|  | ||||
| const ToastButtons: FC = ({ children }) => <Buttons>{children}</Buttons>; | ||||
| const ToastButtons: FC = ({ children }) => <FullWidthDiv className="is-flex pt-2">{children}</FullWidthDiv>; | ||||
|  | ||||
| export default ToastButtons; | ||||
|   | ||||
| @@ -21,10 +21,10 @@ | ||||
|  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||||
|  * SOFTWARE. | ||||
|  */ | ||||
|  | ||||
| import { getTheme, Themeable, ToastThemeContext, Type } from "./themes"; | ||||
| import styled from "styled-components"; | ||||
| import React, { FC } from "react"; | ||||
| import classNames from "classnames"; | ||||
| import styled from "styled-components"; | ||||
| import { getTheme, Themeable, ToastThemeContext, Type } from "./themes"; | ||||
|  | ||||
| type Props = { | ||||
|   type: Type; | ||||
| @@ -33,31 +33,22 @@ type Props = { | ||||
| }; | ||||
|  | ||||
| const Container = styled.div<Themeable>` | ||||
|   color: ${props => props.theme.primary}; | ||||
|   background-color: ${props => props.theme.secondary}; | ||||
|   color: ${(props) => props.theme.primary}; | ||||
|   background-color: ${(props) => props.theme.secondary}; | ||||
|   max-width: 18rem; | ||||
|   font-size: 0.75rem; | ||||
|   border-radius: 5px; | ||||
|   padding: 1.5rem; | ||||
|   margin-top: 0.5rem; | ||||
|   margin-bottom: 0 !important; | ||||
|  | ||||
|   & > p { | ||||
|     margin-bottom: 0.5rem; | ||||
|   } | ||||
| `; | ||||
|  | ||||
| const Title = styled.h1<Themeable>` | ||||
|   margin-bottom: 0.25rem; | ||||
|   font-weight: bold; | ||||
| `; | ||||
|  | ||||
| const ToastNotification: FC<Props> = ({ children, title, type, close }) => { | ||||
|   const theme = getTheme(type); | ||||
|   return ( | ||||
|     <Container className="notification" theme={theme}> | ||||
|       { close ? <button className="delete" onClick={close} /> : null } | ||||
|       <Title theme={theme}>{title}</Title> | ||||
|     <Container className={classNames("notification", "mt-2", "mb-0", "p-5", "is-size-7")} theme={theme}> | ||||
|       {close ? <button className="delete" onClick={close} /> : null} | ||||
|       <h1 className={classNames("mb-1", "has-text-weight-bold")}>{title}</h1> | ||||
|       <ToastThemeContext.Provider value={theme}>{children}</ToastThemeContext.Provider> | ||||
|     </Container> | ||||
|   ); | ||||
|   | ||||
| @@ -110,6 +110,7 @@ footer.footer { | ||||
| // 6. Import the rest of Bulma | ||||
| @import "bulma/bulma"; | ||||
| @import "bulma-tooltip/dist/css/bulma-tooltip.min"; | ||||
| @import "bulma-popover/css/bulma-popover"; | ||||
|  | ||||
| $dark-75: scale-color($dark, $lightness: 25%); | ||||
| $dark-50: scale-color($dark, $lightness: 50%); | ||||
| @@ -194,7 +195,10 @@ $light-25: darken($high-contrast-light-gray, 45%); | ||||
| } | ||||
|  | ||||
| .has-text-blue-light { | ||||
|   color: $blue-light !important; | ||||
|   color: $blue-light; | ||||
| } | ||||
| .has-background-blue-light { | ||||
|   background-color: $blue-light; | ||||
| } | ||||
|  | ||||
| .has-text-high-contrast-warning { | ||||
| @@ -286,10 +290,6 @@ $light-25: darken($high-contrast-light-gray, 45%); | ||||
|   background-color: $high-contrast-danger-25; | ||||
| } | ||||
|  | ||||
| .has-background-blue-light { | ||||
|   background-color: $blue-light; | ||||
| } | ||||
|  | ||||
| .has-background-high-contrast-light-gray { | ||||
|   background-color: $high-contrast-light-gray; | ||||
| } | ||||
| @@ -312,7 +312,7 @@ $light-25: darken($high-contrast-light-gray, 45%); | ||||
|     background-color: $light; | ||||
|   } | ||||
|  | ||||
|   +.is-delete { | ||||
|   + .is-delete { | ||||
|     border-color: #aaa; | ||||
|     border-width: 1px 1px 1px 0; | ||||
|   } | ||||
| @@ -520,7 +520,7 @@ $fa-font-path: "~@fortawesome/fontawesome-free/webfonts"; | ||||
|   color: #666; | ||||
| } | ||||
| .has-border-white { | ||||
|   border-color: #fff !important; | ||||
|   border-color: $white !important; | ||||
| } | ||||
| ul.is-separated { | ||||
|   > li:after { | ||||
| @@ -603,7 +603,7 @@ ul.is-separated { | ||||
|   width: 100%; | ||||
|   td, | ||||
|   th { | ||||
|     border-color: #eee; | ||||
|     border-color: $white-ter; | ||||
|     padding: 1rem; | ||||
|   } | ||||
| } | ||||
| @@ -805,7 +805,6 @@ form .field:not(.is-grouped) { | ||||
| .menu { | ||||
|   div { | ||||
|     height: 100%; | ||||
|     /*border: 1px solid #eee;*/ | ||||
|     margin-bottom: 1rem; | ||||
|   } | ||||
| } | ||||
| @@ -816,7 +815,7 @@ form .field:not(.is-grouped) { | ||||
|   color: #fff; | ||||
|   font-size: 1em; | ||||
|   font-weight: 600; | ||||
|   background-color: #bbb; | ||||
|   background-color: $blue; | ||||
|   border-radius: 5px 5px 0 0; | ||||
|   padding: 0.5rem 1rem; | ||||
|   text-transform: none; | ||||
| @@ -826,9 +825,6 @@ form .field:not(.is-grouped) { | ||||
|     margin-bottom: 0; | ||||
|   } | ||||
| } | ||||
| .menu div:first-child .menu-label { | ||||
|   background-color: $blue; | ||||
| } | ||||
| .menu-list { | ||||
|   a { | ||||
|     color: #333; | ||||
| @@ -843,7 +839,7 @@ form .field:not(.is-grouped) { | ||||
|   > li { | ||||
|     ul { | ||||
|       margin: 0; | ||||
|       border-top: 1px solid #eee; | ||||
|       border-top: 1px solid $white-ter; | ||||
|  | ||||
|       li { | ||||
|         border-right: none; | ||||
| @@ -866,15 +862,15 @@ form .field:not(.is-grouped) { | ||||
|     } | ||||
|  | ||||
|     border-radius: 0; | ||||
|     border-top: 1px solid #eee; | ||||
|     border-left: 1px solid #eee; | ||||
|     border-right: 1px solid #eee; | ||||
|     border-top: 1px solid $white-ter; | ||||
|     border-left: 1px solid $white-ter; | ||||
|     border-right: 1px solid $white-ter; | ||||
|   } | ||||
|   > li:first-child { | ||||
|     border-top: none; | ||||
|   } | ||||
|   li:last-child { | ||||
|     border-bottom: 1px solid #eee; | ||||
|     border-bottom: 1px solid $white-ter; | ||||
|   } | ||||
|   div { | ||||
|     margin-bottom: 0; | ||||
| @@ -921,7 +917,7 @@ form .field:not(.is-grouped) { | ||||
|   margin-left: 0; | ||||
| } | ||||
|  | ||||
| // cursor | ||||
| // NOTE: use .is-clickable instead! | ||||
| .has-cursor-pointer { | ||||
|   cursor: pointer; | ||||
| } | ||||
| @@ -952,5 +948,3 @@ form .field:not(.is-grouped) { | ||||
|     @include loader; | ||||
|   } | ||||
| } | ||||
|  | ||||
| @import "bulma-popover/css/bulma-popover"; | ||||
|   | ||||
| @@ -27,18 +27,10 @@ import styled from "styled-components"; | ||||
| import { ErrorNotification, Image, Loading, Subtitle, Title } from "@scm-manager/ui-components"; | ||||
| import { useUpdateInfo, useVersion } from "@scm-manager/ui-api"; | ||||
|  | ||||
| const BottomMarginDiv = styled.div` | ||||
|   margin-bottom: 1.5rem; | ||||
| `; | ||||
|  | ||||
| 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 NoBottomMarginSubtitle = styled(Subtitle)` | ||||
|   margin-bottom: 0.25rem !important; | ||||
| `; | ||||
|  | ||||
| const ImageWrapper = styled.div` | ||||
|   padding: 0.2rem 0.4rem; | ||||
| `; | ||||
| @@ -68,7 +60,7 @@ const AdminDetails: FC = () => { | ||||
|               <h3 className="has-text-weight-medium">{t("admin.info.newRelease.title")}</h3> | ||||
|               <p> | ||||
|                 {t("admin.info.newRelease.description", { | ||||
|                   version: updateInfo?.latestVersion | ||||
|                   version: updateInfo?.latestVersion, | ||||
|                 })} | ||||
|               </p> | ||||
|               <a className="button is-warning is-pulled-right" target="_blank" href={updateInfo?.link}> | ||||
| @@ -85,8 +77,8 @@ const AdminDetails: FC = () => { | ||||
|   return ( | ||||
|     <> | ||||
|       <Title title={t("admin.info.title")} /> | ||||
|       <NoBottomMarginSubtitle subtitle={t("admin.info.currentAppVersion")} /> | ||||
|       <BottomMarginDiv>{version}</BottomMarginDiv> | ||||
|       <Subtitle className="mb-1" subtitle={t("admin.info.currentAppVersion")} /> | ||||
|       <div className="mb-5">{version}</div> | ||||
|       {updateInfo ? renderUpdateInfo() : null} | ||||
|       <BoxShadowBox className="box"> | ||||
|         <article className="media"> | ||||
|   | ||||
| @@ -22,6 +22,7 @@ | ||||
|  * SOFTWARE. | ||||
|  */ | ||||
| import * as React from "react"; | ||||
| import classNames from "classnames"; | ||||
| import styled from "styled-components"; | ||||
|  | ||||
| type Props = { | ||||
| @@ -29,15 +30,16 @@ type Props = { | ||||
| }; | ||||
|  | ||||
| const ActionWrapper = styled.div` | ||||
|   justify-content: center; | ||||
|   margin-top: 2em; | ||||
|   padding: 1em 1em; | ||||
|   border: 2px solid #e9f7df; | ||||
|   border: 2px solid #e9f7fd; | ||||
| `; | ||||
|  | ||||
| export default class PluginBottomActions extends React.Component<Props> { | ||||
|   render() { | ||||
|     const { children } = this.props; | ||||
|     return <ActionWrapper className="is-flex">{children}</ActionWrapper>; | ||||
|     return ( | ||||
|       <ActionWrapper className={classNames("is-flex", "is-justify-content-center", "mt-5", "p-4")}> | ||||
|         {children} | ||||
|       </ActionWrapper> | ||||
|     ); | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -40,13 +40,11 @@ const ActionbarWrapper = styled.div` | ||||
|   } | ||||
| `; | ||||
|  | ||||
| const IconWrapper = styled.span` | ||||
|   margin-bottom: 0 !important; | ||||
|   padding: 0.5rem; | ||||
| const IconWrapper = styled.span.attrs((props) => ({ | ||||
|   className: "level-item mb-0 p-2 is-clickable", | ||||
| }))` | ||||
|   border: 1px solid #cdcdcd; // $dark-25 | ||||
|   border-radius: 4px; | ||||
|   cursor: pointer; | ||||
|   pointer-events: all; | ||||
|  | ||||
|   &:hover { | ||||
|     border-color: #9a9a9a; // $dark-50 | ||||
| @@ -78,22 +76,22 @@ const PluginEntry: FC<Props> = ({ plugin, openModal }) => { | ||||
|   const actionBar = () => ( | ||||
|     <ActionbarWrapper className="is-flex"> | ||||
|       {isCloudoguPlugin && ( | ||||
|         <IconWrapper className="level-item" onClick={() => openModal({ plugin, action: PluginAction.CLOUDOGU })}> | ||||
|         <IconWrapper onClick={() => openModal({ plugin, action: PluginAction.CLOUDOGU })}> | ||||
|           <Icon title={t("plugins.modal.cloudoguInstall")} name="link" color="success-dark" /> | ||||
|         </IconWrapper> | ||||
|       )} | ||||
|       {isInstallable && ( | ||||
|         <IconWrapper className="level-item" onClick={() => openModal({ plugin, action: PluginAction.INSTALL })}> | ||||
|         <IconWrapper onClick={() => openModal({ plugin, action: PluginAction.INSTALL })}> | ||||
|           <Icon title={t("plugins.modal.install")} name="download" color="info" /> | ||||
|         </IconWrapper> | ||||
|       )} | ||||
|       {isUninstallable && ( | ||||
|         <IconWrapper className="level-item" onClick={() => openModal({ plugin, action: PluginAction.UNINSTALL })}> | ||||
|         <IconWrapper onClick={() => openModal({ plugin, action: PluginAction.UNINSTALL })}> | ||||
|           <Icon title={t("plugins.modal.uninstall")} name="trash" color="info" /> | ||||
|         </IconWrapper> | ||||
|       )} | ||||
|       {isUpdatable && ( | ||||
|         <IconWrapper className="level-item" onClick={() => openModal({ plugin, action: PluginAction.UPDATE })}> | ||||
|         <IconWrapper onClick={() => openModal({ plugin, action: PluginAction.UPDATE })}> | ||||
|           <Icon title={t("plugins.modal.update")} name="sync-alt" color="info" /> | ||||
|         </IconWrapper> | ||||
|       )} | ||||
|   | ||||
| @@ -41,10 +41,10 @@ type ParentWithPluginAction = { | ||||
|   pluginAction?: PluginAction; | ||||
| }; | ||||
|  | ||||
| const ListParent = styled.div.attrs((props) => ({}))<ParentWithPluginAction>` | ||||
|   margin-right: 0; | ||||
| const ListParent = styled.div.attrs((props) => ({ | ||||
|   className: "field-label is-inline-flex mr-0 has-text-left", | ||||
| }))<ParentWithPluginAction>` | ||||
|   min-width: ${(props) => (props.pluginAction === PluginAction.INSTALL ? "5.5em" : "10em")}; | ||||
|   text-align: left; | ||||
| `; | ||||
|  | ||||
| const ListChild = styled.div` | ||||
| @@ -194,9 +194,7 @@ const PluginModal: FC<Props> = ({ onClose, pluginAction, plugin }) => { | ||||
|       <div className="media"> | ||||
|         <div className="media-content"> | ||||
|           <div className="field is-horizontal"> | ||||
|             <ListParent className={classNames("field-label", "is-inline-flex")} pluginAction={pluginAction}> | ||||
|               {t("plugins.modal.author")}: | ||||
|             </ListParent> | ||||
|             <ListParent pluginAction={pluginAction}>{t("plugins.modal.author")}:</ListParent> | ||||
|             <ListChild className={classNames("field-body", "is-inline-flex")}>{plugin.author}</ListChild> | ||||
|           </div> | ||||
|           {pluginAction === PluginAction.CLOUDOGU && ( | ||||
| @@ -208,25 +206,19 @@ const PluginModal: FC<Props> = ({ onClose, pluginAction, plugin }) => { | ||||
|           )} | ||||
|           {pluginAction === PluginAction.INSTALL && ( | ||||
|             <div className="field is-horizontal"> | ||||
|               <ListParent className={classNames("field-label", "is-inline-flex")} pluginAction={pluginAction}> | ||||
|                 {t("plugins.modal.version")}: | ||||
|               </ListParent> | ||||
|               <ListParent pluginAction={pluginAction}>{t("plugins.modal.version")}:</ListParent> | ||||
|               <ListChild className={classNames("field-body", "is-inline-flex")}>{plugin.version}</ListChild> | ||||
|             </div> | ||||
|           )} | ||||
|           {(pluginAction === PluginAction.UPDATE || pluginAction === PluginAction.UNINSTALL) && ( | ||||
|             <div className="field is-horizontal"> | ||||
|               <ListParent className={classNames("field-label", "is-inline-flex")}> | ||||
|                 {t("plugins.modal.currentVersion")}: | ||||
|               </ListParent> | ||||
|               <ListParent>{t("plugins.modal.currentVersion")}:</ListParent> | ||||
|               <ListChild className={classNames("field-body", "is-inline-flex")}>{plugin.version}</ListChild> | ||||
|             </div> | ||||
|           )} | ||||
|           {pluginAction === PluginAction.UPDATE && ( | ||||
|             <div className="field is-horizontal"> | ||||
|               <ListParent className={classNames("field-label", "is-inline-flex")}> | ||||
|                 {t("plugins.modal.newVersion")}: | ||||
|               </ListParent> | ||||
|               <ListParent>{t("plugins.modal.newVersion")}:</ListParent> | ||||
|               <ListChild className={classNames("field-body", "is-inline-flex")}>{plugin.newVersion}</ListChild> | ||||
|             </div> | ||||
|           )} | ||||
|   | ||||
| @@ -23,24 +23,27 @@ | ||||
|  */ | ||||
| import * as React from "react"; | ||||
| import classNames from "classnames"; | ||||
| import styled from "styled-components"; | ||||
|  | ||||
| type Props = { | ||||
|   children?: React.Node; | ||||
| }; | ||||
|  | ||||
| const ChildWrapper = styled.div` | ||||
|   justify-content: flex-end; | ||||
|   align-items: center; | ||||
| `; | ||||
|  | ||||
| export default class PluginTopActions extends React.Component<Props> { | ||||
|   render() { | ||||
|     const { children } = this.props; | ||||
|     return ( | ||||
|       <ChildWrapper className={classNames("column", "is-flex", "is-one-fifths", "is-mobile-action-spacing")}> | ||||
|       <div | ||||
|         className={classNames( | ||||
|           "column", | ||||
|           "is-one-fifths", | ||||
|           "is-mobile-action-spacing", | ||||
|           "is-flex", | ||||
|           "is-justify-content-flex-end", | ||||
|           "is-align-items-center" | ||||
|         )} | ||||
|       > | ||||
|         {children} | ||||
|       </ChildWrapper> | ||||
|       </div> | ||||
|     ); | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -36,7 +36,7 @@ class AvailableVerbs extends React.Component<Props> { | ||||
|     let verbs = null; | ||||
|     if (role.verbs.length > 0) { | ||||
|       verbs = ( | ||||
|         <td className="is-paddingless"> | ||||
|         <td className="p-0"> | ||||
|           <ul> | ||||
|             {role.verbs.map((verb, key) => { | ||||
|               return <li key={key}>{t("verbs.repository." + verb + ".displayName")}</li>; | ||||
|   | ||||
| @@ -23,24 +23,18 @@ | ||||
|  */ | ||||
| import React from "react"; | ||||
| import { WithTranslation, withTranslation } from "react-i18next"; | ||||
| import styled from "styled-components"; | ||||
| import { Tag } from "@scm-manager/ui-components"; | ||||
|  | ||||
| type Props = WithTranslation & { | ||||
|   system?: boolean; | ||||
| }; | ||||
|  | ||||
| const LeftMarginTag = styled(Tag)` | ||||
|   margin-left: 0.75rem; | ||||
|   vertical-align: inherit; | ||||
| `; | ||||
|  | ||||
| class SystemRoleTag extends React.Component<Props> { | ||||
|   render() { | ||||
|     const { system, t } = this.props; | ||||
|  | ||||
|     if (system) { | ||||
|       return <LeftMarginTag color="dark" label={t("repositoryRole.system")} />; | ||||
|       return <Tag className="ml-3" color="dark" label={t("repositoryRole.system")} />; | ||||
|     } | ||||
|     return null; | ||||
|   } | ||||
|   | ||||
| @@ -33,28 +33,13 @@ type Props = WithTranslation & { | ||||
|   item?: InfoItem; | ||||
| }; | ||||
|  | ||||
| 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: 10.5rem; | ||||
|   margin-left: 1.5em; | ||||
| `; | ||||
|  | ||||
| class InfoBox extends React.Component<Props> { | ||||
| @@ -64,7 +49,7 @@ class InfoBox extends React.Component<Props> { | ||||
|     const summary = item ? item.summary : t("login.loading"); | ||||
|  | ||||
|     return ( | ||||
|       <ContentWrapper className={classNames("media-content", "content")}> | ||||
|       <ContentWrapper className={classNames("media-content", "content", "ml-5")}> | ||||
|         <h4 className="has-text-link">{title}</h4> | ||||
|         <p>{summary}</p> | ||||
|       </ContentWrapper> | ||||
| @@ -75,20 +60,30 @@ class InfoBox extends React.Component<Props> { | ||||
|     const { item, type, t } = this.props; | ||||
|     const icon = type === "plugin" ? "puzzle-piece" : "star"; | ||||
|     return ( | ||||
|       <BottomMarginA href={item._links.self.href}> | ||||
|       <a className="is-block mb-5" href={item._links.self.href}> | ||||
|         <div className="box media"> | ||||
|           <figure className="media-left"> | ||||
|             <FixedSizedIconWrapper | ||||
|               className={classNames("image", "box", "has-text-weight-bold", "has-text-white", "has-background-info")} | ||||
|               className={classNames( | ||||
|                 "image", | ||||
|                 "box", | ||||
|                 "has-text-weight-bold", | ||||
|                 "has-text-white", | ||||
|                 "has-background-info", | ||||
|                 "is-flex", | ||||
|                 "is-flex-direction-column", | ||||
|                 "is-justify-content-center", | ||||
|                 "is-align-items-center" | ||||
|               )} | ||||
|             > | ||||
|               <LightBlueIcon className="fa-2x" name={icon} color="inherit" /> | ||||
|               <Icon className="has-text-blue-light mb-2 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> | ||||
|       </BottomMarginA> | ||||
|       </a> | ||||
|     ); | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -21,7 +21,7 @@ | ||||
|  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||||
|  * SOFTWARE. | ||||
|  */ | ||||
| import React, {FormEvent} from "react"; | ||||
| import React, { FormEvent } from "react"; | ||||
| import { WithTranslation, withTranslation } from "react-i18next"; | ||||
| import styled from "styled-components"; | ||||
| import { ErrorNotification, Image, InputField, SubmitButton, UnauthorizedError } from "@scm-manager/ui-components"; | ||||
| @@ -50,7 +50,7 @@ const AvatarImage = styled(Image)` | ||||
|   width: 128px; | ||||
|   height: 128px; | ||||
|   padding: 5px; | ||||
|   background: #fff; | ||||
|   background: white; | ||||
|   border: 1px solid lightgray; | ||||
|   border-radius: 50%; | ||||
| `; | ||||
| @@ -60,7 +60,7 @@ class LoginForm extends React.Component<Props, State> { | ||||
|     super(props); | ||||
|     this.state = { | ||||
|       username: "", | ||||
|       password: "" | ||||
|       password: "", | ||||
|     }; | ||||
|   } | ||||
|  | ||||
| @@ -73,13 +73,13 @@ class LoginForm extends React.Component<Props, State> { | ||||
|  | ||||
|   handleUsernameChange = (value: string) => { | ||||
|     this.setState({ | ||||
|       username: value | ||||
|       username: value, | ||||
|     }); | ||||
|   }; | ||||
|  | ||||
|   handlePasswordChange = (value: string) => { | ||||
|     this.setState({ | ||||
|       password: value | ||||
|       password: value, | ||||
|     }); | ||||
|   }; | ||||
|  | ||||
|   | ||||
| @@ -114,7 +114,7 @@ class LoginInfo extends React.Component<Props, State> { | ||||
|  | ||||
|   createInfoPanel = (info: LoginInfoResponse) => ( | ||||
|     <NoOpErrorBoundary> | ||||
|       <div className="column is-7 is-offset-1 is-paddingless"> | ||||
|       <div className="column is-7 is-offset-1 p-0"> | ||||
|         <InfoBox item={info.feature} type="feature" /> | ||||
|         <InfoBox item={info.plugin} type="plugin" /> | ||||
|       </div> | ||||
|   | ||||
| @@ -131,12 +131,12 @@ const InitializationAdminAccountStep: FC<Props> = ({ data }) => { | ||||
|         <h3 className="title">{t("title")}</h3> | ||||
|         <h4 className="subtitle">{t("adminStep.title")}</h4> | ||||
|         <p>{t("adminStep.description")}</p> | ||||
|         <div className={"columns"}> | ||||
|         <div className="columns"> | ||||
|           <div className="column is-full-width"> | ||||
|             <InputField placeholder={t("adminStep.startupToken")} autofocus={true} {...register("startupToken")} /> | ||||
|           </div> | ||||
|         </div> | ||||
|         <div className={"columns"}> | ||||
|         <div className="columns"> | ||||
|           <div className="column is-half"> | ||||
|             <InputField | ||||
|               testId="username-input" | ||||
| @@ -154,7 +154,7 @@ const InitializationAdminAccountStep: FC<Props> = ({ data }) => { | ||||
|             /> | ||||
|           </div> | ||||
|         </div> | ||||
|         <div className={"columns"}> | ||||
|         <div className="columns"> | ||||
|           <div className="column is-full-width"> | ||||
|             <InputField | ||||
|               label={t("adminStep.email")} | ||||
| @@ -163,7 +163,7 @@ const InitializationAdminAccountStep: FC<Props> = ({ data }) => { | ||||
|             /> | ||||
|           </div> | ||||
|         </div> | ||||
|         <div className={"columns"}> | ||||
|         <div className="columns"> | ||||
|           <div className="column is-half"> | ||||
|             <InputField | ||||
|               testId="password-input" | ||||
| @@ -184,7 +184,7 @@ const InitializationAdminAccountStep: FC<Props> = ({ data }) => { | ||||
|           </div> | ||||
|         </div> | ||||
|         {errorComponent} | ||||
|         <div className={"columns"}> | ||||
|         <div className="columns"> | ||||
|           <div className="column is-full-width"> | ||||
|             <SubmitButton | ||||
|               label={t("adminStep.submit")} | ||||
|   | ||||
| @@ -23,15 +23,10 @@ | ||||
|  */ | ||||
| import React, { FC } from "react"; | ||||
| import { Redirect, useLocation } from "react-router-dom"; | ||||
| import styled from "styled-components"; | ||||
| import LoginInfo from "../components/LoginInfo"; | ||||
| import { parse } from "query-string"; | ||||
| import { useIndexLink, useLogin } from "@scm-manager/ui-api"; | ||||
|  | ||||
| const HeroSection = styled.section` | ||||
|   padding-top: 2em; | ||||
| `; | ||||
|  | ||||
| interface FromObject { | ||||
|   from?: string; | ||||
| } | ||||
| @@ -62,7 +57,7 @@ const Login: FC = () => { | ||||
|   } | ||||
|  | ||||
|   return ( | ||||
|     <HeroSection className="hero"> | ||||
|     <section className="hero pt-6"> | ||||
|       <div className="hero-body"> | ||||
|         <div className="container"> | ||||
|           <div className="columns is-centered"> | ||||
| @@ -70,7 +65,7 @@ const Login: FC = () => { | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
|     </HeroSection> | ||||
|     </section> | ||||
|   ); | ||||
| }; | ||||
|  | ||||
|   | ||||
| @@ -21,8 +21,18 @@ | ||||
|  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||||
|  * SOFTWARE. | ||||
|  */ | ||||
|  | ||||
| import React, { FC, useEffect, useState } from "react"; | ||||
| import { useHistory, Link } from "react-router-dom"; | ||||
| import { useTranslation } from "react-i18next"; | ||||
| import classNames from "classnames"; | ||||
| import styled from "styled-components"; | ||||
| import { | ||||
|   useClearNotifications, | ||||
|   useDismissNotification, | ||||
|   useNotifications, | ||||
|   useNotificationSubscription, | ||||
| } from "@scm-manager/ui-api"; | ||||
| import { Notification, NotificationCollection } from "@scm-manager/ui-types"; | ||||
| import { | ||||
|   Button, | ||||
|   Notification as InfoNotification, | ||||
| @@ -35,26 +45,6 @@ import { | ||||
|   DateFromNow, | ||||
|   devices, | ||||
| } from "@scm-manager/ui-components"; | ||||
| import styled from "styled-components"; | ||||
| import { | ||||
|   useClearNotifications, | ||||
|   useDismissNotification, | ||||
|   useNotifications, | ||||
|   useNotificationSubscription, | ||||
| } from "@scm-manager/ui-api"; | ||||
| import { Notification, NotificationCollection } from "@scm-manager/ui-types"; | ||||
| import { useHistory, Link } from "react-router-dom"; | ||||
| import classNames from "classnames"; | ||||
| import { useTranslation } from "react-i18next"; | ||||
|  | ||||
| const Bell = styled(Icon)` | ||||
|   font-size: 1.5rem; | ||||
| `; | ||||
|  | ||||
| const Container = styled.div` | ||||
|   display: flex; | ||||
|   cursor: pointer; | ||||
| `; | ||||
|  | ||||
| const DropDownMenu = styled.div` | ||||
|   min-width: 35rem; | ||||
| @@ -130,7 +120,7 @@ const NotificationEntry: FC<EntryProps> = ({ notification, removeToast }) => { | ||||
|   } | ||||
|   return ( | ||||
|     <tr className={`is-${color(notification)}`}> | ||||
|       <VerticalCenteredTd onClick={() => history.push(notification.link)} className="has-cursor-pointer"> | ||||
|       <VerticalCenteredTd onClick={() => history.push(notification.link)} className="is-clickable"> | ||||
|         <NotificationMessage message={notification.message} /> | ||||
|       </VerticalCenteredTd> | ||||
|       <DateColumn className="has-text-right"> | ||||
| @@ -143,7 +133,7 @@ const NotificationEntry: FC<EntryProps> = ({ notification, removeToast }) => { | ||||
|           <Icon | ||||
|             name="trash" | ||||
|             color="black" | ||||
|             className="has-cursor-pointer" | ||||
|             className="is-clickable" | ||||
|             title={t("notifications.dismiss")} | ||||
|             onClick={remove} | ||||
|           /> | ||||
| @@ -172,7 +162,7 @@ const ClearEntry: FC<ClearEntryProps> = ({ notifications, clearToasts }) => { | ||||
|     clearStore(); | ||||
|   }; | ||||
|   return ( | ||||
|     <div className="dropdown-item has-text-centered"> | ||||
|     <div className={classNames("dropdown-item", "has-text-centered")}> | ||||
|       <ErrorNotification error={error} /> | ||||
|       <DismissAllButton className="is-outlined" color="link" loading={isLoading} action={clear}> | ||||
|         <Icon color="link" name="trash" className="mr-1" /> {t("notifications.dismissAll")} | ||||
| @@ -189,8 +179,8 @@ const NotificationList: FC<Props> = ({ data, clear, remove }) => { | ||||
|   const top = all.slice(0, 6); | ||||
|  | ||||
|   return ( | ||||
|     <div className="dropdown-content p-0"> | ||||
|       <table className="table mb-0 card-table"> | ||||
|     <div className={classNames("dropdown-content", "p-0")}> | ||||
|       <table className={classNames("table", "card-table", "mb-0")}> | ||||
|         <tbody> | ||||
|           {top.map((n, i) => ( | ||||
|             <NotificationEntry key={i} notification={n} removeToast={remove} /> | ||||
| @@ -198,14 +188,18 @@ const NotificationList: FC<Props> = ({ data, clear, remove }) => { | ||||
|         </tbody> | ||||
|       </table> | ||||
|       {all.length > 6 ? ( | ||||
|         <p className="has-text-centered has-text-grey">{t("notifications.xMore", { count: all.length - 6 })}</p> | ||||
|         <p className={classNames("has-text-centered", "has-text-grey")}> | ||||
|           {t("notifications.xMore", { count: all.length - 6 })} | ||||
|         </p> | ||||
|       ) : null} | ||||
|       {clearLink ? <ClearEntry notifications={data} clearToasts={clear} /> : null} | ||||
|     </div> | ||||
|   ); | ||||
| }; | ||||
|  | ||||
| const DropdownMenuContainer: FC = ({ children }) => <div className="dropdown-content p-4">{children}</div>; | ||||
| const DropdownMenuContainer: FC = ({ children }) => ( | ||||
|   <div className={classNames("dropdown-content", "p-4")}>{children}</div> | ||||
| ); | ||||
|  | ||||
| const NoNotifications: FC = () => { | ||||
|   const [t] = useTranslation("commons"); | ||||
| @@ -278,12 +272,8 @@ const NotificationSubscription: FC<SubscriptionProps> = ({ notifications, remove | ||||
| }; | ||||
|  | ||||
| const BellNotificationContainer = styled.div` | ||||
|   position: relative; | ||||
|   width: 2rem; | ||||
|   height: 2rem; | ||||
|   display: flex; | ||||
|   align-items: center; | ||||
|   justify-content: center; | ||||
| `; | ||||
|  | ||||
| type NotificationCounterProps = { | ||||
| @@ -304,8 +294,11 @@ type BellNotificationIconProps = { | ||||
| const BellNotificationIcon: FC<BellNotificationIconProps> = ({ data, onClick }) => { | ||||
|   const counter = data?._embedded.notifications.length || 0; | ||||
|   return ( | ||||
|     <BellNotificationContainer onClick={onClick}> | ||||
|       <Bell iconStyle={counter === 0 ? "far" : "fas"} name="bell" color="white" /> | ||||
|     <BellNotificationContainer | ||||
|       className={classNames("is-relative", "is-flex", "is-justify-content-center", "is-align-items-center")} | ||||
|       onClick={onClick} | ||||
|     > | ||||
|       <Icon className="is-size-4" iconStyle={counter === 0 ? "far" : "fas"} name="bell" color="white" /> | ||||
|       {counter > 0 ? <NotificationCounter count={counter}>{counter < 100 ? counter : "∞"}</NotificationCounter> : null} | ||||
|     </BellNotificationContainer> | ||||
|   ); | ||||
| @@ -357,9 +350,9 @@ const Notifications: FC<NotificationProps> = ({ className }) => { | ||||
|         )} | ||||
|         onClick={(e) => e.stopPropagation()} | ||||
|       > | ||||
|         <Container className="dropdown-trigger"> | ||||
|         <div className={classNames("is-flex", "dropdown-trigger", "is-clickable")}> | ||||
|           <BellNotificationIcon data={data} onClick={() => setOpen((o) => !o)} /> | ||||
|         </Container> | ||||
|         </div> | ||||
|         <DropDownMenu className="dropdown-menu" id="dropdown-menu" role="menu"> | ||||
|           <ErrorBox error={error} /> | ||||
|           {isLoading ? <LoadingBox /> : null} | ||||
|   | ||||
| @@ -34,10 +34,6 @@ import SyntaxModal from "../search/SyntaxModal"; | ||||
| import SearchErrorNotification from "../search/SearchErrorNotification"; | ||||
| import queryString from "query-string"; | ||||
|  | ||||
| const Field = styled.div` | ||||
|   margin-bottom: 0 !important; | ||||
| `; | ||||
|  | ||||
| const Input = styled.input` | ||||
|   border-radius: 4px !important; | ||||
| `; | ||||
| @@ -77,12 +73,6 @@ const EmptyHits: FC = () => { | ||||
|  | ||||
| const ResultHeading = styled.h3` | ||||
|   border-bottom: 1px solid lightgray; | ||||
|   margin: 0 0.5rem; | ||||
|   padding: 0.375rem 0.5rem; | ||||
|   font-weight: bold; | ||||
|   display: flex; | ||||
|   align-items: center; | ||||
|   justify-content: space-between; | ||||
| `; | ||||
|  | ||||
| const DropdownMenu = styled.div` | ||||
| @@ -91,8 +81,6 @@ const DropdownMenu = styled.div` | ||||
|  | ||||
| const ResultFooter = styled.div` | ||||
|   border-top: 1px solid lightgray; | ||||
|   margin: 0 0.5rem; | ||||
|   padding: 0.375rem 0.5rem; | ||||
| `; | ||||
|  | ||||
| const AvatarSection: FC<HitProps> = ({ hit }) => { | ||||
| @@ -115,7 +103,7 @@ const AvatarSection: FC<HitProps> = ({ hit }) => { | ||||
| const MoreResults: FC<GotoProps> = ({ gotoDetailSearch }) => { | ||||
|   const [t] = useTranslation("commons"); | ||||
|   return ( | ||||
|     <ResultFooter className="dropdown-item has-text-centered"> | ||||
|     <ResultFooter className={classNames("dropdown-item", "has-text-centered", "mx-2", "px-2", "py-1")}> | ||||
|       <Button action={gotoDetailSearch} color="primary" data-omnisearch="true"> | ||||
|         {t("search.quickSearch.moreResults")} | ||||
|       </Button> | ||||
| @@ -156,7 +144,18 @@ const Hits: FC<HitsProps> = ({ showHelp, gotoDetailSearch, ...rest }) => { | ||||
|   return ( | ||||
|     <> | ||||
|       <div aria-expanded="true" role="listbox" className="dropdown-content"> | ||||
|         <ResultHeading className="dropdown-item"> | ||||
|         <ResultHeading | ||||
|           className={classNames( | ||||
|             "dropdown-item", | ||||
|             "is-flex", | ||||
|             "is-justify-content-space-between", | ||||
|             "is-align-items-center", | ||||
|             "mx-2", | ||||
|             "px-2", | ||||
|             "py-1", | ||||
|             "has-text-weight-bold" | ||||
|           )} | ||||
|         > | ||||
|           <span>{t("search.quickSearch.resultHeading")}</span> | ||||
|           <SyntaxHelp onClick={showHelp} /> | ||||
|         </ResultHeading> | ||||
| @@ -331,7 +330,7 @@ const OmniSearch: FC = () => { | ||||
|   const { onKeyDown, index } = useKeyBoardNavigation(gotoDetailSearch, clearQuery, data?._embedded.hits); | ||||
|  | ||||
|   return ( | ||||
|     <Field className="navbar-item field"> | ||||
|     <div className={classNames("navbar-item", "field", "mb-0")}> | ||||
|       {showHelp ? <SyntaxModal close={closeHelp} /> : null} | ||||
|       <div | ||||
|         className={classNames("control", "has-icons-right", { | ||||
| @@ -376,7 +375,7 @@ const OmniSearch: FC = () => { | ||||
|           </DropdownMenu> | ||||
|         </div> | ||||
|       </div> | ||||
|     </Field> | ||||
|     </div> | ||||
|   ); | ||||
| }; | ||||
|  | ||||
|   | ||||
| @@ -22,9 +22,10 @@ | ||||
|  * SOFTWARE. | ||||
|  */ | ||||
| import React, { ReactNode } from "react"; | ||||
| import classNames from "classnames"; | ||||
| import styled from "styled-components"; | ||||
| import { apiClient, Loading, ErrorNotification, ErrorBoundary, Icon } from "@scm-manager/ui-components"; | ||||
| import loadBundle from "./loadBundle"; | ||||
| import styled from "styled-components"; | ||||
|  | ||||
| type Props = { | ||||
|   loaded: boolean; | ||||
| @@ -48,23 +49,11 @@ const BigIcon = styled(Icon)` | ||||
|   font-size: 10rem; | ||||
| `; | ||||
|  | ||||
| const Centered = styled.div` | ||||
|   display: flex; | ||||
|   flex-direction: column; | ||||
|   align-items: center; | ||||
|   justify-content: space-between; | ||||
| `; | ||||
|  | ||||
| const ErrorMessage = styled.span` | ||||
|   font-size: 20px; | ||||
|   margin: 1.5rem 0; | ||||
| `; | ||||
|  | ||||
| class PluginLoader extends React.Component<Props, State> { | ||||
|   constructor(props: Props) { | ||||
|     super(props); | ||||
|     this.state = { | ||||
|       message: "booting" | ||||
|       message: "booting", | ||||
|     }; | ||||
|   } | ||||
|  | ||||
| @@ -72,7 +61,7 @@ class PluginLoader extends React.Component<Props, State> { | ||||
|     const { loaded } = this.props; | ||||
|     if (!loaded) { | ||||
|       this.setState({ | ||||
|         message: "loading plugin information" | ||||
|         message: "loading plugin information", | ||||
|       }); | ||||
|  | ||||
|       this.getPlugins(this.props.link); | ||||
| @@ -82,16 +71,16 @@ class PluginLoader extends React.Component<Props, State> { | ||||
|   getPlugins = (link: string): Promise<any> => { | ||||
|     return apiClient | ||||
|       .get(link) | ||||
|       .then(response => response.text()) | ||||
|       .then((response) => response.text()) | ||||
|       .then(JSON.parse) | ||||
|       .then(pluginCollection => pluginCollection._embedded.plugins) | ||||
|       .then((pluginCollection) => pluginCollection._embedded.plugins) | ||||
|       .then(this.loadPlugins) | ||||
|       .then(this.props.callback); | ||||
|   }; | ||||
|  | ||||
|   loadPlugins = (plugins: Plugin[]) => { | ||||
|     this.setState({ | ||||
|       message: "loading plugins" | ||||
|       message: "loading plugins", | ||||
|     }); | ||||
|  | ||||
|     const promises = []; | ||||
| @@ -100,21 +89,21 @@ class PluginLoader extends React.Component<Props, State> { | ||||
|       promises.push(this.loadPlugin(plugin)); | ||||
|     } | ||||
|     return promises.reduce((chain, current) => { | ||||
|       return chain.then(chainResults => { | ||||
|         return current.then(currentResult => [...chainResults, currentResult]); | ||||
|       return chain.then((chainResults) => { | ||||
|         return current.then((currentResult) => [...chainResults, currentResult]); | ||||
|       }); | ||||
|     }, Promise.resolve([])); | ||||
|   }; | ||||
|  | ||||
|   loadPlugin = (plugin: Plugin) => { | ||||
|     this.setState({ | ||||
|       message: `loading ${plugin.name}` | ||||
|       message: `loading ${plugin.name}`, | ||||
|     }); | ||||
|  | ||||
|     const promises = []; | ||||
|     for (const bundle of plugin.bundles) { | ||||
|       promises.push( | ||||
|         loadBundle(bundle).catch(error => this.setState({ error, errorMessage: `loading ${plugin.name} failed` })) | ||||
|         loadBundle(bundle).catch((error) => this.setState({ error, errorMessage: `loading ${plugin.name} failed` })) | ||||
|       ); | ||||
|     } | ||||
|     return Promise.all(promises); | ||||
| @@ -129,10 +118,17 @@ class PluginLoader extends React.Component<Props, State> { | ||||
|         <section className="section"> | ||||
|           <div className="container"> | ||||
|             <ErrorBoundary> | ||||
|               <Centered> | ||||
|               <div | ||||
|                 className={classNames( | ||||
|                   "is-flex", | ||||
|                   "is-flex-direction-column", | ||||
|                   "is-justify-content-space-between", | ||||
|                   "is-align-items-center" | ||||
|                 )} | ||||
|               > | ||||
|                 <BigIcon name="exclamation-triangle" color="danger" /> | ||||
|                 <ErrorMessage>{errorMessage}</ErrorMessage> | ||||
|               </Centered> | ||||
|                 <div className={classNames("my-5", "is-size-5")}>{errorMessage}</div> | ||||
|               </div> | ||||
|               <ErrorNotification error={error} /> | ||||
|             </ErrorBoundary> | ||||
|           </div> | ||||
|   | ||||
| @@ -80,7 +80,7 @@ class ProfileInfo extends React.Component<Props> { | ||||
|       groups = ( | ||||
|         <tr> | ||||
|           <th>{t("profile.groups")}</th> | ||||
|           <td className="is-paddingless"> | ||||
|           <td className="p-0"> | ||||
|             <ul> | ||||
|               {me.groups.map(group => { | ||||
|                 return <li>{group}</li>; | ||||
|   | ||||
| @@ -23,7 +23,6 @@ | ||||
|  */ | ||||
| import React from "react"; | ||||
| import { WithTranslation, withTranslation } from "react-i18next"; | ||||
| import styled from "styled-components"; | ||||
| import { Group } from "@scm-manager/ui-types"; | ||||
| import { Checkbox, DateFromNow } from "@scm-manager/ui-components"; | ||||
| import GroupMember from "./GroupMember"; | ||||
| @@ -32,10 +31,6 @@ type Props = WithTranslation & { | ||||
|   group: Group; | ||||
| }; | ||||
|  | ||||
| const StyledMemberList = styled.ul` | ||||
|   margin-left: 1em !important; | ||||
| `; | ||||
|  | ||||
| class Details extends React.Component<Props> { | ||||
|   render() { | ||||
|     const { group, t } = this.props; | ||||
| @@ -86,12 +81,12 @@ class Details extends React.Component<Props> { | ||||
|       member = ( | ||||
|         <tr> | ||||
|           <th>{t("group.members")}</th> | ||||
|           <td className="is-paddingless"> | ||||
|             <StyledMemberList> | ||||
|           <td className="p-0"> | ||||
|             <ul className="ml-4"> | ||||
|               {group._embedded.members.map((member, index) => { | ||||
|                 return <GroupMember key={index} member={member} />; | ||||
|               })} | ||||
|             </StyledMemberList> | ||||
|             </ul> | ||||
|           </td> | ||||
|         </tr> | ||||
|       ); | ||||
|   | ||||
| @@ -23,6 +23,7 @@ | ||||
|  */ | ||||
| import React from "react"; | ||||
| import { Link } from "react-router-dom"; | ||||
| import classNames from "classnames"; | ||||
| import styled from "styled-components"; | ||||
| import { Member } from "@scm-manager/ui-types"; | ||||
| import { Icon } from "@scm-manager/ui-components"; | ||||
| @@ -32,9 +33,6 @@ type Props = { | ||||
| }; | ||||
|  | ||||
| const StyledMember = styled.li` | ||||
|   display: inline-block; | ||||
|   margin-right: 0.25rem; | ||||
|   padding: 0.25rem 0.75rem; | ||||
|   border: 1px solid #eee; | ||||
|   border-radius: 4px; | ||||
| `; | ||||
| @@ -59,6 +57,10 @@ export default class GroupMember extends React.Component<Props> { | ||||
|   render() { | ||||
|     const { member } = this.props; | ||||
|     const to = `/user/${member.name}`; | ||||
|     return <StyledMember>{this.showName(to, member)}</StyledMember>; | ||||
|     return ( | ||||
|       <StyledMember className={classNames("is-inline-block", "mr-1", "px-3", "py-1")}> | ||||
|         {this.showName(to, member)} | ||||
|       </StyledMember> | ||||
|     ); | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -24,8 +24,8 @@ | ||||
| import React from "react"; | ||||
| import classNames from "classnames"; | ||||
| import styled from "styled-components"; | ||||
| import PermissionCheckbox from "./PermissionCheckbox"; | ||||
| import { Loading } from "@scm-manager/ui-components"; | ||||
| import PermissionCheckbox from "./PermissionCheckbox"; | ||||
|  | ||||
| type Props = { | ||||
|   permissions: { | ||||
| @@ -37,8 +37,6 @@ type Props = { | ||||
| }; | ||||
|  | ||||
| const StyledWrapper = styled.div` | ||||
|   padding-bottom: 0; | ||||
|  | ||||
|   & .field .control { | ||||
|     width: 100%; | ||||
|     word-wrap: break-word; | ||||
| @@ -56,7 +54,7 @@ export default class PermissionsWrapper extends React.Component<Props> { | ||||
|     const permissionArray = Object.keys(permissions); | ||||
|     return ( | ||||
|       <div className="columns"> | ||||
|         <StyledWrapper className={classNames("column", "is-half")}> | ||||
|         <StyledWrapper className={classNames("column", "is-half", "pb-0")}> | ||||
|           {permissionArray.slice(0, permissionArray.length / 2 + 1).map(p => ( | ||||
|             <PermissionCheckbox | ||||
|               key={p} | ||||
|   | ||||
| @@ -22,49 +22,34 @@ | ||||
|  * SOFTWARE. | ||||
|  */ | ||||
| import React from "react"; | ||||
| import { Branch, Repository } from "@scm-manager/ui-types"; | ||||
| import { WithTranslation, withTranslation } from "react-i18next"; | ||||
| import classNames from "classnames"; | ||||
| import { Branch, Repository } from "@scm-manager/ui-types"; | ||||
| import { DateFromNow } from "@scm-manager/ui-components"; | ||||
| import BranchButtonGroup from "./BranchButtonGroup"; | ||||
| import DefaultBranchTag from "./DefaultBranchTag"; | ||||
| import { DateFromNow } from "@scm-manager/ui-components"; | ||||
| import styled from "styled-components"; | ||||
|  | ||||
| type Props = WithTranslation & { | ||||
|   repository: Repository; | ||||
|   branch: Branch; | ||||
| }; | ||||
|  | ||||
| const FlexRow = styled.div` | ||||
|   display: flex; | ||||
|   align-items: center; | ||||
|   flex-wrap: wrap; | ||||
| `; | ||||
|  | ||||
| const Created = styled.div` | ||||
|   margin-left: 0.5rem; | ||||
|   font-size: 0.8rem; | ||||
| `; | ||||
|  | ||||
| const Label = styled.strong` | ||||
|   margin-right: 0.3rem; | ||||
| `; | ||||
|  | ||||
| const Date = styled(DateFromNow)` | ||||
|   font-size: 0.8rem; | ||||
| `; | ||||
|  | ||||
| class BranchDetail extends React.Component<Props> { | ||||
|   render() { | ||||
|     const { repository, branch, t } = this.props; | ||||
|  | ||||
|     return ( | ||||
|       <div className="media"> | ||||
|         <FlexRow className="media-content subtitle"> | ||||
|           <Label>{t("branch.name")}</Label> {branch.name} <DefaultBranchTag defaultBranch={branch.defaultBranch} /> | ||||
|           <Created className="is-ellipsis-overflow"> | ||||
|             {t("branches.overview.lastCommit")} <Date date={branch.lastCommitDate} className="has-text-grey" /> | ||||
|           </Created> | ||||
|         </FlexRow> | ||||
|         <div | ||||
|           className={classNames("media-content", "subtitle", "is-flex", "is-align-items-center", "is-flex-wrap-wrap")} | ||||
|         > | ||||
|           <strong className="mr-1">{t("branch.name")}</strong> {branch.name}{" "} | ||||
|           <DefaultBranchTag defaultBranch={branch.defaultBranch} /> | ||||
|           <div className={classNames("is-ellipsis-overflow", "is-size-7", "ml-2")}> | ||||
|             {t("branches.overview.lastCommit")}{" "} | ||||
|             <DateFromNow className={classNames("is-size-7", "has-text-grey")} date={branch.lastCommitDate} /> | ||||
|           </div> | ||||
|         </div> | ||||
|         <div className="media-right"> | ||||
|           <BranchButtonGroup repository={repository} branch={branch} /> | ||||
|         </div> | ||||
|   | ||||
| @@ -23,11 +23,11 @@ | ||||
|  */ | ||||
| import React, { FC } from "react"; | ||||
| import { Link as ReactLink } from "react-router-dom"; | ||||
| import { Branch, Link } from "@scm-manager/ui-types"; | ||||
| import DefaultBranchTag from "./DefaultBranchTag"; | ||||
| import { DateFromNow, Icon } from "@scm-manager/ui-components"; | ||||
| import { useTranslation } from "react-i18next"; | ||||
| import styled from "styled-components"; | ||||
| import classNames from "classnames"; | ||||
| import { Branch, Link } from "@scm-manager/ui-types"; | ||||
| import { DateFromNow, Icon } from "@scm-manager/ui-components"; | ||||
| import DefaultBranchTag from "./DefaultBranchTag"; | ||||
|  | ||||
| type Props = { | ||||
|   baseUrl: string; | ||||
| @@ -35,11 +35,6 @@ type Props = { | ||||
|   onDelete: (branch: Branch) => void; | ||||
| }; | ||||
|  | ||||
| const Created = styled.span` | ||||
|   margin-left: 1rem; | ||||
|   font-size: 0.8rem; | ||||
| `; | ||||
|  | ||||
| const BranchRow: FC<Props> = ({ baseUrl, branch, onDelete }) => { | ||||
|   const to = `${baseUrl}/${encodeURIComponent(branch.name)}/info`; | ||||
|   const [t] = useTranslation("repos"); | ||||
| @@ -63,9 +58,9 @@ const BranchRow: FC<Props> = ({ baseUrl, branch, onDelete }) => { | ||||
|           <DefaultBranchTag defaultBranch={branch.defaultBranch} /> | ||||
|         </ReactLink> | ||||
|         {branch.lastCommitDate && ( | ||||
|           <Created className="has-text-grey is-ellipsis-overflow"> | ||||
|           <span className={classNames("has-text-grey", "is-ellipsis-overflow", "is-size-7", "ml-4")}> | ||||
|             {t("branches.table.lastCommit")} <DateFromNow date={branch.lastCommitDate} /> | ||||
|           </Created> | ||||
|           </span> | ||||
|         )} | ||||
|       </td> | ||||
|       <td className="is-darker">{deleteButton}</td> | ||||
|   | ||||
| @@ -23,24 +23,18 @@ | ||||
|  */ | ||||
| import React from "react"; | ||||
| import { WithTranslation, withTranslation } from "react-i18next"; | ||||
| import styled from "styled-components"; | ||||
| import { Tag } from "@scm-manager/ui-components"; | ||||
|  | ||||
| type Props = WithTranslation & { | ||||
|   defaultBranch?: boolean; | ||||
| }; | ||||
|  | ||||
| const LeftMarginTag = styled(Tag)` | ||||
|   vertical-align: inherit; | ||||
|   margin-left: 0.75rem; | ||||
| `; | ||||
|  | ||||
| class DefaultBranchTag extends React.Component<Props> { | ||||
|   render() { | ||||
|     const { defaultBranch, t } = this.props; | ||||
|  | ||||
|     if (defaultBranch) { | ||||
|       return <LeftMarginTag color="dark" label={t("branch.defaultTag")} />; | ||||
|       return <Tag className="ml-3" color="dark" label={t("branch.defaultTag")} />; | ||||
|     } | ||||
|     return null; | ||||
|   } | ||||
|   | ||||
| @@ -46,10 +46,6 @@ const LeftOverflowTd = styled.td` | ||||
|   text-align: left !important; | ||||
| `; | ||||
|  | ||||
| const ResultNotification = styled(Notification)` | ||||
|   margin: 1rem; | ||||
| `; | ||||
|  | ||||
| type PathResultRowProps = { | ||||
|   contentBaseUrl: string; | ||||
|   path: string; | ||||
| @@ -81,7 +77,7 @@ type ResultTableProps = { | ||||
| const ResultTable: FC<ResultTableProps> = ({ contentBaseUrl, paths }) => ( | ||||
|   <table className="table table-hover table-sm is-fullwidth"> | ||||
|     <tbody> | ||||
|       {paths.map(path => ( | ||||
|       {paths.map((path) => ( | ||||
|         <PathResultRow contentBaseUrl={contentBaseUrl} path={path} /> | ||||
|       ))} | ||||
|     </tbody> | ||||
| @@ -92,13 +88,17 @@ const FileSearchResults: FC<Props> = ({ query, contentBaseUrl, paths = [] }) => | ||||
|   const [t] = useTranslation("repos"); | ||||
|   let body; | ||||
|   if (query.length <= 1) { | ||||
|     body = <ResultNotification type="info">{t("filesearch.notifications.queryToShort")}</ResultNotification>; | ||||
|     body = ( | ||||
|       <Notification className="m-4" type="info"> | ||||
|         {t("filesearch.notifications.queryToShort")} | ||||
|       </Notification> | ||||
|     ); | ||||
|   } else if (paths.length === 0) { | ||||
|     const queryCmp = <strong>{query}</strong>; | ||||
|     body = ( | ||||
|       <ResultNotification type="info"> | ||||
|       <Notification className="m-4" type="info"> | ||||
|         <Trans i18nKey="repos:filesearch.notifications.emptyResult" values={{ query }} components={[queryCmp]} /> | ||||
|       </ResultNotification> | ||||
|       </Notification> | ||||
|     ); | ||||
|   } else { | ||||
|     body = <ResultTable contentBaseUrl={contentBaseUrl} paths={paths} />; | ||||
|   | ||||
| @@ -22,14 +22,15 @@ | ||||
|  * SOFTWARE. | ||||
|  */ | ||||
| import React, { FC, useEffect, useState } from "react"; | ||||
| import { Branch, Repository } from "@scm-manager/ui-types"; | ||||
| import { Link, useHistory, useLocation, useParams } from "react-router-dom"; | ||||
| import { useTranslation } from "react-i18next"; | ||||
| import classNames from "classnames"; | ||||
| import styled from "styled-components"; | ||||
| import { Branch, Repository } from "@scm-manager/ui-types"; | ||||
| import { urls, usePaths } from "@scm-manager/ui-api"; | ||||
| import { ErrorNotification, FilterInput, Help, Icon, Loading } from "@scm-manager/ui-components"; | ||||
| import CodeActionBar from "../components/CodeActionBar"; | ||||
| import styled from "styled-components"; | ||||
| import FileSearchResults from "../components/FileSearchResults"; | ||||
| import { useTranslation } from "react-i18next"; | ||||
| import { filepathSearch } from "../utils/filepathSearch"; | ||||
|  | ||||
| type Props = { | ||||
| @@ -43,27 +44,14 @@ type Params = { | ||||
|   revision: string; | ||||
| }; | ||||
|  | ||||
| const InputContainer = styled.div` | ||||
|   padding: 1rem 1.75rem 0 1.75rem; | ||||
|   display: flex; | ||||
|   align-items: center; | ||||
|   justify-content: flex-start; | ||||
| `; | ||||
|  | ||||
| const HomeLink = styled(Link)` | ||||
|   border-right: 1px solid lightgray; | ||||
|   margin-right: 0.75rem; | ||||
|   padding-right: 0.75em; | ||||
| `; | ||||
|  | ||||
| const HomeIcon = styled(Icon)` | ||||
|   line-height: 1.5rem; | ||||
| `; | ||||
|  | ||||
| const SearchHelp = styled(Help)` | ||||
|   margin-left: 0.75rem; | ||||
| `; | ||||
|  | ||||
| const useRevision = () => { | ||||
|   const { revision } = useParams<Params>(); | ||||
|   return revision; | ||||
| @@ -113,8 +101,18 @@ const FileSearch: FC<Props> = ({ repository, baseUrl, branches, selectedBranch } | ||||
|         switchViewLink={evaluateSwitchViewLink} | ||||
|       /> | ||||
|       <div className="panel"> | ||||
|         <InputContainer> | ||||
|           <HomeLink to={contentBaseUrl}> | ||||
|         <div | ||||
|           className={classNames( | ||||
|             "is-flex", | ||||
|             "is-justify-content-flex-start", | ||||
|             "is-align-items-center", | ||||
|             "pt-4", | ||||
|             "mx-3", | ||||
|             "px-4", | ||||
|             "pb-0" | ||||
|           )} | ||||
|         > | ||||
|           <HomeLink className={classNames("mr-3", "pr-3")} to={contentBaseUrl}> | ||||
|             <HomeIcon title={t("filesearch.home")} name="home" color="inherit" /> | ||||
|           </HomeLink> | ||||
|           <FilterInput | ||||
| @@ -124,8 +122,8 @@ const FileSearch: FC<Props> = ({ repository, baseUrl, branches, selectedBranch } | ||||
|             filter={search} | ||||
|             autoFocus={true} | ||||
|           /> | ||||
|           <SearchHelp message={t("filesearch.input.help")} /> | ||||
|         </InputContainer> | ||||
|           <Help className="ml-3" message={t("filesearch.input.help")} /> | ||||
|         </div> | ||||
|         <ErrorNotification error={error} /> | ||||
|         {isLoading ? <Loading /> : <FileSearchResults contentBaseUrl={contentBaseUrl} query={query} paths={result} />} | ||||
|       </div> | ||||
|   | ||||
| @@ -21,12 +21,10 @@ | ||||
|  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||||
|  * SOFTWARE. | ||||
|  */ | ||||
|  | ||||
| import React, { FC, useState } from "react"; | ||||
| import { useTranslation } from "react-i18next"; | ||||
| import { RepositoryUrlImport } from "@scm-manager/ui-types"; | ||||
| import { InputField, validation } from "@scm-manager/ui-components"; | ||||
| import { useTranslation } from "react-i18next"; | ||||
| import styled from "styled-components"; | ||||
|  | ||||
| type Props = { | ||||
|   repository: RepositoryUrlImport; | ||||
| @@ -35,14 +33,6 @@ type Props = { | ||||
|   disabled?: boolean; | ||||
| }; | ||||
|  | ||||
| const Column = styled.div` | ||||
|   padding: 0 0.75rem; | ||||
| `; | ||||
|  | ||||
| const Columns = styled.div` | ||||
|   padding: 0.75rem 0 0; | ||||
| `; | ||||
|  | ||||
| const ImportFromUrlForm: FC<Props> = ({ repository, onChange, setValid, disabled }) => { | ||||
|   const [t] = useTranslation("repos"); | ||||
|   const [urlValidationError, setUrlValidationError] = useState(false); | ||||
| @@ -65,8 +55,8 @@ const ImportFromUrlForm: FC<Props> = ({ repository, onChange, setValid, disabled | ||||
|   }; | ||||
|  | ||||
|   return ( | ||||
|     <Columns className="columns is-multiline"> | ||||
|       <Column className="column is-full"> | ||||
|     <div className="columns is-multiline pt-3"> | ||||
|       <div className="column is-full px-3"> | ||||
|         <InputField | ||||
|           label={t("import.importUrl")} | ||||
|           onChange={handleImportUrlChange} | ||||
| @@ -77,27 +67,27 @@ const ImportFromUrlForm: FC<Props> = ({ repository, onChange, setValid, disabled | ||||
|           disabled={disabled} | ||||
|           onBlur={handleImportUrlBlur} | ||||
|         /> | ||||
|       </Column> | ||||
|       <Column className="column is-half"> | ||||
|       </div> | ||||
|       <div className="column is-half px-3"> | ||||
|         <InputField | ||||
|           label={t("import.username")} | ||||
|           onChange={username => onChange({ ...repository, username })} | ||||
|           onChange={(username) => onChange({ ...repository, username })} | ||||
|           value={repository.username} | ||||
|           helpText={t("help.usernameHelpText")} | ||||
|           disabled={disabled} | ||||
|         /> | ||||
|       </Column> | ||||
|       <Column className="column is-half"> | ||||
|       </div> | ||||
|       <div className="column is-half px-3"> | ||||
|         <InputField | ||||
|           label={t("import.password")} | ||||
|           onChange={password => onChange({ ...repository, password })} | ||||
|           onChange={(password) => onChange({ ...repository, password })} | ||||
|           value={repository.password} | ||||
|           type="password" | ||||
|           helpText={t("help.passwordHelpText")} | ||||
|           disabled={disabled} | ||||
|         /> | ||||
|       </Column> | ||||
|     </Columns> | ||||
|       </div> | ||||
|     </div> | ||||
|   ); | ||||
| }; | ||||
|  | ||||
|   | ||||
| @@ -42,7 +42,7 @@ import { | ||||
|   Icon, | ||||
|   Level, | ||||
|   SignatureIcon, | ||||
|   Tooltip | ||||
|   Tooltip, | ||||
| } from "@scm-manager/ui-components"; | ||||
| import ContributorTable from "./ContributorTable"; | ||||
| import { Link as ReactLink } from "react-router-dom"; | ||||
| @@ -54,14 +54,6 @@ type Props = { | ||||
|   fileControlFactory?: FileControlFactory; | ||||
| }; | ||||
|  | ||||
| const RightMarginP = styled.p` | ||||
|   margin-right: 1em; | ||||
| `; | ||||
|  | ||||
| const BottomMarginLevel = styled(Level)` | ||||
|   margin-bottom: 1rem !important; | ||||
| `; | ||||
|  | ||||
| const countContributors = (changeset: Changeset) => { | ||||
|   if (changeset.contributors) { | ||||
|     return changeset.contributors.length + 1; | ||||
| @@ -69,16 +61,6 @@ const countContributors = (changeset: Changeset) => { | ||||
|   return 1; | ||||
| }; | ||||
|  | ||||
| const FlexRow = styled.div` | ||||
|   display: flex; | ||||
|   flex-direction: row; | ||||
| `; | ||||
|  | ||||
| const ContributorLine = styled.div` | ||||
|   display: flex; | ||||
|   cursor: pointer; | ||||
| `; | ||||
|  | ||||
| const ContributorColumn = styled.p` | ||||
|   flex-grow: 0; | ||||
|   overflow: hidden; | ||||
| @@ -92,25 +74,7 @@ const CountColumn = styled.p` | ||||
|   white-space: nowrap; | ||||
| `; | ||||
|  | ||||
| const ContributorDetails = styled.div` | ||||
|   display: flex; | ||||
|   flex-direction: column; | ||||
|   margin-bottom: 1rem; | ||||
| `; | ||||
|  | ||||
| const ContributorToggleLine = styled.p` | ||||
|   cursor: pointer; | ||||
|   /** margin-bottom is inherit from content p **/ | ||||
|   margin-bottom: 0.5rem !important; | ||||
| `; | ||||
|  | ||||
| const ChangesetSummary = styled.div` | ||||
|   display: flex; | ||||
| `; | ||||
|  | ||||
| const SeparatedParents = styled.div` | ||||
|   margin-left: 1em; | ||||
|  | ||||
|   a + a:before { | ||||
|     content: ",\\00A0"; | ||||
|     color: #4a4a4a; | ||||
| @@ -126,33 +90,33 @@ const Contributors: FC<{ changeset: Changeset }> = ({ changeset }) => { | ||||
|  | ||||
|   if (open) { | ||||
|     return ( | ||||
|       <ContributorDetails> | ||||
|         <FlexRow> | ||||
|           <ContributorToggleLine onClick={e => setOpen(!open)} className="is-ellipsis-overflow"> | ||||
|       <div className="is-flex is-flex-direction-column mb-4"> | ||||
|         <div className="is-flex"> | ||||
|           <p className="is-ellipsis-overflow is-clickable mb-2" onClick={(e) => setOpen(!open)}> | ||||
|             <Icon name="angle-down" /> {t("changeset.contributors.list")} | ||||
|           </ContributorToggleLine> | ||||
|           </p> | ||||
|           {signatureIcon} | ||||
|         </FlexRow> | ||||
|         </div> | ||||
|         <ContributorTable changeset={changeset} /> | ||||
|       </ContributorDetails> | ||||
|       </div> | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   return ( | ||||
|     <> | ||||
|       <ContributorLine onClick={e => setOpen(!open)}> | ||||
|       <div className="is-flex is-clickable" onClick={(e) => setOpen(!open)}> | ||||
|         <ContributorColumn className="is-ellipsis-overflow"> | ||||
|           <Icon name="angle-right" /> <ChangesetAuthor changeset={changeset} /> | ||||
|         </ContributorColumn> | ||||
|         {signatureIcon} | ||||
|         <CountColumn className={"is-hidden-mobile is-hidden-tablet-only is-hidden-desktop-only"}> | ||||
|         <CountColumn className="is-hidden-mobile is-hidden-tablet-only is-hidden-desktop-only"> | ||||
|           ( | ||||
|           <span className="has-text-link"> | ||||
|             {t("changeset.contributors.count", { count: countContributors(changeset) })} | ||||
|           </span> | ||||
|           ) | ||||
|         </CountColumn> | ||||
|       </ContributorLine> | ||||
|       </div> | ||||
|     </> | ||||
|   ); | ||||
| }; | ||||
| @@ -178,13 +142,13 @@ const ChangesetDetails: FC<Props> = ({ changeset, repository, fileControlFactory | ||||
|  | ||||
|   return ( | ||||
|     <> | ||||
|       <div className={classNames("content", "is-marginless")}> | ||||
|       <div className={classNames("content", "m-0")}> | ||||
|         <h4> | ||||
|           <ExtensionPoint | ||||
|             name="changeset.description" | ||||
|             props={{ | ||||
|               changeset, | ||||
|               value: description.title | ||||
|               value: description.title, | ||||
|             }} | ||||
|             renderAll={false} | ||||
|           > | ||||
| @@ -193,23 +157,23 @@ const ChangesetDetails: FC<Props> = ({ changeset, repository, fileControlFactory | ||||
|         </h4> | ||||
|         <article className="media"> | ||||
|           <AvatarWrapper> | ||||
|             <RightMarginP className={classNames("image", "is-64x64")}> | ||||
|             <p className={classNames("image", "is-64x64", "mr-4")}> | ||||
|               <AvatarImage person={changeset.author} /> | ||||
|             </RightMarginP> | ||||
|             </p> | ||||
|           </AvatarWrapper> | ||||
|           <div className="media-content"> | ||||
|             <Contributors changeset={changeset} /> | ||||
|             <ChangesetSummary className="is-ellipsis-overflow"> | ||||
|             <div className="is-flex is-ellipsis-overflow"> | ||||
|               <p> | ||||
|                 <Trans i18nKey="repos:changeset.summary" components={[id, date]} /> | ||||
|               </p> | ||||
|               {parents && parents?.length > 0 ? ( | ||||
|                 <SeparatedParents> | ||||
|                 <SeparatedParents className="ml-4"> | ||||
|                   {t("changeset.parents.label", { count: parents?.length }) + ": "} | ||||
|                   {parents} | ||||
|                 </SeparatedParents> | ||||
|               ) : null} | ||||
|             </ChangesetSummary> | ||||
|             </div> | ||||
|           </div> | ||||
|           <div className="media-right"> | ||||
|             <ChangesetTags changeset={changeset} /> | ||||
| @@ -244,7 +208,7 @@ const ChangesetDetails: FC<Props> = ({ changeset, repository, fileControlFactory | ||||
|                   name="changeset.description" | ||||
|                   props={{ | ||||
|                     changeset, | ||||
|                     value: item | ||||
|                     value: item, | ||||
|                   }} | ||||
|                   renderAll={false} | ||||
|                 > | ||||
| @@ -257,7 +221,8 @@ const ChangesetDetails: FC<Props> = ({ changeset, repository, fileControlFactory | ||||
|         </p> | ||||
|       </div> | ||||
|       <div> | ||||
|         <BottomMarginLevel | ||||
|         <Level | ||||
|           className="mb-4" | ||||
|           right={ | ||||
|             <Button | ||||
|               action={collapseDiffs} | ||||
|   | ||||
| @@ -97,7 +97,7 @@ const ContributorTable: FC<Props> = ({ changeset }) => { | ||||
|       {getContributorsByType().map(contributor => ( | ||||
|         <tr key={contributor.type}> | ||||
|           <SizedTd>{t("changeset.contributor.type." + contributor.type)}:</SizedTd> | ||||
|           <td className="is-ellipsis-overflow is-marginless"> | ||||
|           <td className="is-ellipsis-overflow m-0"> | ||||
|             <CommaSeparatedList> | ||||
|               {contributor.persons!.map(person => ( | ||||
|                 <Contributor key={person.name} person={person} /> | ||||
|   | ||||
| @@ -22,28 +22,14 @@ | ||||
|  * SOFTWARE. | ||||
|  */ | ||||
| import React, { FC, useCallback, useEffect, useState } from "react"; | ||||
| import styled from "styled-components"; | ||||
| import { useTranslation } from "react-i18next"; | ||||
| import classNames from "classnames"; | ||||
| import { ExtensionPoint } from "@scm-manager/ui-extensions"; | ||||
| import { IndexResources, Repository, RepositoryType, CUSTOM_NAMESPACE_STRATEGY } from "@scm-manager/ui-types"; | ||||
| import { Checkbox, Level, Select, SubmitButton } from "@scm-manager/ui-components"; | ||||
| import NamespaceAndNameFields from "../NamespaceAndNameFields"; | ||||
| import RepositoryInformationForm from "../RepositoryInformationForm"; | ||||
|  | ||||
| const CheckboxWrapper = styled.div` | ||||
|   margin-top: 2em; | ||||
|   flex: 1; | ||||
| `; | ||||
|  | ||||
| const SelectWrapper = styled.div` | ||||
|   flex: 1; | ||||
| `; | ||||
|  | ||||
| const SpaceBetween = styled.div` | ||||
|   display: flex; | ||||
|   justify-content: space-between; | ||||
| `; | ||||
|  | ||||
| type Props = { | ||||
|   createRepository?: (repo: RepositoryCreation, shouldInit: boolean) => void; | ||||
|   modifyRepository?: (repo: Repository) => void; | ||||
| @@ -65,7 +51,7 @@ const RepositoryForm: FC<Props> = ({ | ||||
|   repositoryTypes, | ||||
|   namespaceStrategy, | ||||
|   loading, | ||||
|   indexResources | ||||
|   indexResources, | ||||
| }) => { | ||||
|   const [repo, setRepo] = useState<Repository>({ | ||||
|     name: "", | ||||
| @@ -73,15 +59,15 @@ const RepositoryForm: FC<Props> = ({ | ||||
|     type: "", | ||||
|     contact: "", | ||||
|     description: "", | ||||
|     _links: {} | ||||
|     _links: {}, | ||||
|   }); | ||||
|   const [initRepository, setInitRepository] = useState(false); | ||||
|   const [contextEntries, setContextEntries] = useState({}); | ||||
|   const setCreationContextEntry = useCallback( | ||||
|     (key: string, value: any) => { | ||||
|       setContextEntries(entries => ({ | ||||
|       setContextEntries((entries) => ({ | ||||
|         ...entries, | ||||
|         [key]: value | ||||
|         [key]: value, | ||||
|       })); | ||||
|     }, | ||||
|     [setContextEntries] | ||||
| @@ -102,7 +88,7 @@ const RepositoryForm: FC<Props> = ({ | ||||
|   const isValid = () => { | ||||
|     return ( | ||||
|       !(!repo.name || (namespaceStrategy === CUSTOM_NAMESPACE_STRATEGY && !repo.namespace)) && | ||||
|       Object.values(valid).every(v => v) | ||||
|       Object.values(valid).every((v) => v) | ||||
|     ); | ||||
|   }; | ||||
|  | ||||
| @@ -119,10 +105,10 @@ const RepositoryForm: FC<Props> = ({ | ||||
|  | ||||
|   const createSelectOptions = (repositoryTypes?: RepositoryType[]) => { | ||||
|     if (repositoryTypes) { | ||||
|       return repositoryTypes.map(repositoryType => { | ||||
|       return repositoryTypes.map((repositoryType) => { | ||||
|         return { | ||||
|           label: repositoryType.displayName, | ||||
|           value: repositoryType.name | ||||
|           value: repositoryType.name, | ||||
|         }; | ||||
|       }); | ||||
|     } | ||||
| @@ -137,28 +123,28 @@ const RepositoryForm: FC<Props> = ({ | ||||
|     const extensionProps = { | ||||
|       repository: repo, | ||||
|       setCreationContextEntry: setCreationContextEntry, | ||||
|       indexResources: indexResourcesWithLinks | ||||
|       indexResources: indexResourcesWithLinks, | ||||
|     }; | ||||
|     return ( | ||||
|       <> | ||||
|         <NamespaceAndNameFields | ||||
|           repository={repo} | ||||
|           onChange={setRepo} | ||||
|           setValid={namespaceAndName => setValid({ ...valid, namespaceAndName })} | ||||
|           setValid={(namespaceAndName) => setValid({ ...valid, namespaceAndName })} | ||||
|           disabled={disabled} | ||||
|         /> | ||||
|         <SpaceBetween> | ||||
|           <SelectWrapper> | ||||
|         <div className="columns"> | ||||
|           <div className={classNames("column", "is-half")}> | ||||
|             <Select | ||||
|               label={t("repository.type")} | ||||
|               onChange={type => setRepo({ ...repo, type })} | ||||
|               onChange={(type) => setRepo({ ...repo, type })} | ||||
|               value={repo ? repo.type : ""} | ||||
|               options={createSelectOptions(repositoryTypes)} | ||||
|               helpText={t("help.typeHelpText")} | ||||
|               disabled={disabled} | ||||
|             /> | ||||
|           </SelectWrapper> | ||||
|           <CheckboxWrapper> | ||||
|           </div> | ||||
|           <div className={classNames("column", "is-half", "is-align-self-flex-end")}> | ||||
|             <Checkbox | ||||
|               label={t("repositoryForm.initializeRepository")} | ||||
|               checked={initRepository} | ||||
| @@ -169,8 +155,8 @@ const RepositoryForm: FC<Props> = ({ | ||||
|             {initRepository && ( | ||||
|               <ExtensionPoint name="repos.create.initialize" props={extensionProps} renderAll={true} /> | ||||
|             )} | ||||
|           </CheckboxWrapper> | ||||
|         </SpaceBetween> | ||||
|           </div> | ||||
|         </div> | ||||
|       </> | ||||
|     ); | ||||
|   }; | ||||
| @@ -199,7 +185,7 @@ const RepositoryForm: FC<Props> = ({ | ||||
|         repository={repo} | ||||
|         onChange={setRepo} | ||||
|         disabled={disabled} | ||||
|         setValid={contact => setValid({ ...valid, contact })} | ||||
|         setValid={(contact) => setValid({ ...valid, contact })} | ||||
|       /> | ||||
|       {submitButton()} | ||||
|     </form> | ||||
|   | ||||
| @@ -21,22 +21,12 @@ | ||||
|  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||||
|  * SOFTWARE. | ||||
|  */ | ||||
|  | ||||
| import React, { FC } from "react"; | ||||
| import styled from "styled-components"; | ||||
| import { Button, ButtonAddons, Icon, Level, urls } from "@scm-manager/ui-components"; | ||||
| import { useLocation } from "react-router-dom"; | ||||
| import { useTranslation } from "react-i18next"; | ||||
|  | ||||
| const MarginIcon = styled(Icon)` | ||||
|   padding-right: 0.5rem; | ||||
| `; | ||||
|  | ||||
| const SmallButton = styled(Button)` | ||||
|   border-radius: 4px; | ||||
|   font-size: 1rem; | ||||
|   font-weight: 600; | ||||
| `; | ||||
| import classNames from "classnames"; | ||||
| import styled from "styled-components"; | ||||
| import { Button, ButtonAddons, Icon, Level, urls } from "@scm-manager/ui-components"; | ||||
|  | ||||
| const TopLevel = styled(Level)` | ||||
|   margin-top: 1.5rem; | ||||
| @@ -62,10 +52,14 @@ const RepositoryFormButton: FC<RepositoryForm> = ({ path, icon, label }) => { | ||||
|   const [t] = useTranslation(["repos", "plugins"]); | ||||
|  | ||||
|   return ( | ||||
|     <SmallButton color={isSelected ? "link is-selected" : undefined} link={!isSelected ? href : undefined}> | ||||
|       <MarginIcon name={icon} color={isSelected ? "white" : "default"} /> | ||||
|       <p className="is-hidden-mobile is-hidden-tablet-only">{t(`plugins:${label}`, label)}</p> | ||||
|     </SmallButton> | ||||
|     <Button | ||||
|       className="is-size-6" | ||||
|       color={isSelected ? "link is-selected" : undefined} | ||||
|       link={!isSelected ? href : undefined} | ||||
|     > | ||||
|       <Icon className="pr-2" name={icon} color={isSelected ? "white" : "default"} /> | ||||
|       <p className={classNames("is-hidden-mobile", "is-hidden-tablet-only")}>{t(`plugins:${label}`, label)}</p> | ||||
|     </Button> | ||||
|   ); | ||||
| }; | ||||
|  | ||||
|   | ||||
| @@ -37,12 +37,12 @@ const RepositoryGroupEntry: FC<Props> = ({ group }) => { | ||||
|  | ||||
|   const settingsLink = group.namespace?._links?.permissions && ( | ||||
|     <Link to={`/namespace/${group.name}/settings`}> | ||||
|       <Icon color="is-link" name="cog" title={t("repositoryOverview.settings.tooltip")} className={"is-size-6 ml-2"} /> | ||||
|       <Icon color="is-link" name="cog" title={t("repositoryOverview.settings.tooltip")} className="is-size-6 ml-2" /> | ||||
|     </Link> | ||||
|   ); | ||||
|   const namespaceHeader = ( | ||||
|     <> | ||||
|       <Link to={`/repos/${group.name}/`} className={"has-text-dark"}> | ||||
|       <Link to={`/repos/${group.name}/`} className="has-text-dark"> | ||||
|         {group.name} | ||||
|       </Link>{" "} | ||||
|       {settingsLink} | ||||
|   | ||||
| @@ -31,18 +31,17 @@ import { | ||||
|   InputField, | ||||
|   Level, | ||||
|   Notification, | ||||
|   Subtitle | ||||
|   Subtitle, | ||||
| } from "@scm-manager/ui-components"; | ||||
| import { useTranslation } from "react-i18next"; | ||||
| import { ExportInfo, Link, Repository } from "@scm-manager/ui-types"; | ||||
| import { useExportInfo, useExportRepository } from "@scm-manager/ui-api"; | ||||
| import styled from "styled-components"; | ||||
| import classNames from "classnames"; | ||||
|  | ||||
| const InfoBox = styled.div` | ||||
|   white-space: pre-line; | ||||
|   background-color: #ccecf9; | ||||
|   margin: 1rem 0; | ||||
|   padding: 1rem; | ||||
|   border-radius: 2px; | ||||
|   border-left: 0.2rem solid; | ||||
|   border-color: #33b2e8; | ||||
| @@ -60,7 +59,7 @@ const ExportInterruptedNotification = () => { | ||||
| const ExportInfoBox: FC<{ exportInfo: ExportInfo }> = ({ exportInfo }) => { | ||||
|   const [t] = useTranslation("repos"); | ||||
|   return ( | ||||
|     <InfoBox> | ||||
|     <InfoBox className={classNames("my-4", "p-4")}> | ||||
|       <strong>{t("export.exportInfo.infoBoxTitle")}</strong> | ||||
|       <p>{t("export.exportInfo.exporter", { username: exportInfo.exporterName })}</p> | ||||
|       <p> | ||||
| @@ -90,7 +89,7 @@ const ExportRepository: FC<Props> = ({ repository }) => { | ||||
|     isLoading: isLoadingExport, | ||||
|     error: errorExport, | ||||
|     data: exportedInfo, | ||||
|     exportRepository | ||||
|     exportRepository, | ||||
|   } = useExportRepository(); | ||||
|  | ||||
|   useEffect(() => { | ||||
| @@ -144,7 +143,7 @@ const ExportRepository: FC<Props> = ({ repository }) => { | ||||
|         helpText={t("export.encrypt.helpText")} | ||||
|       /> | ||||
|       {encrypt && ( | ||||
|         <div className="columns column is-half"> | ||||
|         <div className={classNames("columns", "column", "is-half")}> | ||||
|           <InputField | ||||
|             label={t("export.password.label")} | ||||
|             helpText={t("export.password.helpText")} | ||||
| @@ -172,7 +171,7 @@ const ExportRepository: FC<Props> = ({ repository }) => { | ||||
|                 exportRepository(repository, { | ||||
|                   compressed, | ||||
|                   password: encrypt ? password : "", | ||||
|                   withMetadata: fullExport | ||||
|                   withMetadata: fullExport, | ||||
|                 }) | ||||
|               } | ||||
|               loading={isLoadingInfo || isLoadingExport} | ||||
|   | ||||
| @@ -51,9 +51,9 @@ const HealthCheckWarning: FC<Props> = ({ repository }) => { | ||||
|   return ( | ||||
|     <Notification type="danger"> | ||||
|       {modal} | ||||
|       <div className={"has-cursor-pointer"} onClick={() => setShowHealthCheck(true)}> | ||||
|       <div className="is-clickable" onClick={() => setShowHealthCheck(true)}> | ||||
|         <div>{t("repositoryForm.healthCheckWarning.title")}</div> | ||||
|         <div className={"is-small"}>{t("repositoryForm.healthCheckWarning.subtitle")}</div> | ||||
|         <div className="is-small">{t("repositoryForm.healthCheckWarning.subtitle")}</div> | ||||
|       </div> | ||||
|     </Notification> | ||||
|   ); | ||||
|   | ||||
| @@ -21,14 +21,13 @@ | ||||
|  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||||
|  * SOFTWARE. | ||||
|  */ | ||||
|  | ||||
| import React, { FC } from "react"; | ||||
| import { useTranslation } from "react-i18next"; | ||||
| import styled from "styled-components"; | ||||
| import { Repository } from "@scm-manager/ui-types"; | ||||
| import { Subtitle } from "@scm-manager/ui-components"; | ||||
| import RenameRepository from "./RenameRepository"; | ||||
| import DeleteRepo from "./DeleteRepo"; | ||||
| import styled from "styled-components"; | ||||
| import { Subtitle } from "@scm-manager/ui-components"; | ||||
| import { useTranslation } from "react-i18next"; | ||||
| import ArchiveRepo from "./ArchiveRepo"; | ||||
| import UnarchiveRepo from "./UnarchiveRepo"; | ||||
|  | ||||
| @@ -37,7 +36,6 @@ type Props = { | ||||
| }; | ||||
|  | ||||
| export const DangerZoneContainer = styled.div` | ||||
|   padding: 1.5rem 1rem; | ||||
|   border: 1px solid #ff6a88; | ||||
|   border-radius: 5px; | ||||
|  | ||||
| @@ -83,7 +81,7 @@ const RepositoryDangerZone: FC<Props> = ({ repository }) => { | ||||
|     <> | ||||
|       <hr /> | ||||
|       <Subtitle subtitle={t("repositoryForm.dangerZone")} /> | ||||
|       <DangerZoneContainer>{dangerZone}</DangerZoneContainer> | ||||
|       <DangerZoneContainer className="px-4 py-5">{dangerZone}</DangerZoneContainer> | ||||
|     </> | ||||
|   ); | ||||
| }; | ||||
|   | ||||
| @@ -22,7 +22,7 @@ | ||||
|  * SOFTWARE. | ||||
|  */ | ||||
| import React, { useState } from "react"; | ||||
| import { Link as RouteLink, match, Redirect, Route, Switch, useRouteMatch } from "react-router-dom"; | ||||
| import { Link as RouteLink, Redirect, Route, Switch, useRouteMatch } from "react-router-dom"; | ||||
| import { useTranslation } from "react-i18next"; | ||||
| import { binder, ExtensionPoint } from "@scm-manager/ui-extensions"; | ||||
| import { Changeset, Link } from "@scm-manager/ui-types"; | ||||
| @@ -61,7 +61,6 @@ import { useIndexLinks, useRepository } from "@scm-manager/ui-api"; | ||||
| import styled from "styled-components"; | ||||
|  | ||||
| const TagGroup = styled.span` | ||||
|   font-weight: bold; | ||||
|   & > * { | ||||
|     margin-right: 0.25rem; | ||||
|   } | ||||
| @@ -159,7 +158,7 @@ const RepositoryRoot = () => { | ||||
|  | ||||
|   const titleComponent = ( | ||||
|     <> | ||||
|       <RouteLink to={`/repos/${repository.namespace}/`} className={"has-text-dark"}> | ||||
|       <RouteLink to={`/repos/${repository.namespace}/`} className="has-text-dark"> | ||||
|         {repository.namespace} | ||||
|       </RouteLink> | ||||
|       /{repository.name} | ||||
| @@ -220,7 +219,7 @@ const RepositoryRoot = () => { | ||||
|         afterTitle={ | ||||
|           <div className="is-flex"> | ||||
|             <ExtensionPoint name={"repository.afterTitle"} props={{ repository }} /> | ||||
|             <TagGroup> | ||||
|             <TagGroup className="has-text-weight-bold"> | ||||
|               <RepositoryFlags repository={repository} tooltipLocation="bottom" /> | ||||
|             </TagGroup> | ||||
|           </div> | ||||
|   | ||||
| @@ -26,16 +26,11 @@ import { useTranslation } from "react-i18next"; | ||||
| import { Repository } from "@scm-manager/ui-types"; | ||||
| import { Button, ErrorNotification, Level, Subtitle } from "@scm-manager/ui-components"; | ||||
| import { useRunHealthCheck } from "@scm-manager/ui-api"; | ||||
| import styled from "styled-components"; | ||||
|  | ||||
| type Props = { | ||||
|   repository: Repository; | ||||
| }; | ||||
|  | ||||
| const MarginTopButton = styled(Button)` | ||||
|   margin-top: 1rem; | ||||
| `; | ||||
|  | ||||
| const RunHealthCheck: FC<Props> = ({ repository }) => { | ||||
|   const { isLoading, error, runHealthCheck } = useRunHealthCheck(); | ||||
|   const [t] = useTranslation("repos"); | ||||
| @@ -56,7 +51,8 @@ const RunHealthCheck: FC<Props> = ({ repository }) => { | ||||
|       </p> | ||||
|       <Level | ||||
|         right={ | ||||
|           <MarginTopButton | ||||
|           <Button | ||||
|             className="mt-4" | ||||
|             color="warning" | ||||
|             icon="heartbeat" | ||||
|             label={t("runHealthCheck.button")} | ||||
|   | ||||
| @@ -24,7 +24,6 @@ | ||||
| import React, { FC } from "react"; | ||||
| import { File } from "@scm-manager/ui-types"; | ||||
| import { ErrorNotification, Loading, MarkdownView } from "@scm-manager/ui-components"; | ||||
| import styled from "styled-components"; | ||||
| import replaceBranchWithRevision from "../../ReplaceBranchWithRevision"; | ||||
| import { useLocation } from "react-router-dom"; | ||||
| import { useFileContent } from "@scm-manager/ui-api"; | ||||
| @@ -34,10 +33,6 @@ type Props = { | ||||
|   basePath: string; | ||||
| }; | ||||
|  | ||||
| const MarkdownContent = styled.div` | ||||
|   padding: 0.5rem; | ||||
| `; | ||||
|  | ||||
| const MarkdownViewer: FC<Props> = ({ file, basePath }) => { | ||||
|   const { isLoading, error, data: content } = useFileContent(file); | ||||
|   const location = useLocation(); | ||||
| @@ -53,9 +48,9 @@ const MarkdownViewer: FC<Props> = ({ file, basePath }) => { | ||||
|   const permalink = replaceBranchWithRevision(location.pathname, file.revision); | ||||
|  | ||||
|   return ( | ||||
|     <MarkdownContent> | ||||
|     <div className="p-2"> | ||||
|       <MarkdownView content={content} basePath={basePath} permalink={permalink} enableAnchorHeadings={true} /> | ||||
|     </MarkdownContent> | ||||
|     </div> | ||||
|   ); | ||||
| }; | ||||
|  | ||||
|   | ||||
| @@ -21,13 +21,13 @@ | ||||
|  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||||
|  * SOFTWARE. | ||||
|  */ | ||||
| import React, {FC, useState} from "react"; | ||||
| import React, { FC, useState } from "react"; | ||||
| import styled from "styled-components"; | ||||
| import MarkdownViewer from "./MarkdownViewer"; | ||||
| import SourcecodeViewer from "./SourcecodeViewer"; | ||||
| import {File} from "@scm-manager/ui-types"; | ||||
| import {Button} from "@scm-manager/ui-components"; | ||||
| import {useTranslation} from "react-i18next"; | ||||
| import { File } from "@scm-manager/ui-types"; | ||||
| import { Button } from "@scm-manager/ui-components"; | ||||
| import { useTranslation } from "react-i18next"; | ||||
|  | ||||
| const ToggleButton = styled(Button)` | ||||
|   max-width: 1rem; | ||||
| @@ -37,17 +37,13 @@ const ToggleButton = styled(Button)` | ||||
|   z-index: 30; | ||||
| `; | ||||
|  | ||||
| const Container = styled.div` | ||||
|   position: relative; | ||||
| `; | ||||
|  | ||||
| type Props = { | ||||
|   file: File; | ||||
|   basePath: string; | ||||
| }; | ||||
|  | ||||
| const SwitchableMarkdownViewer: FC<Props> = ({file, basePath}) => { | ||||
|   const {t} = useTranslation("repos"); | ||||
| const SwitchableMarkdownViewer: FC<Props> = ({ file, basePath }) => { | ||||
|   const { t } = useTranslation("repos"); | ||||
|   const [renderMarkdown, setRenderMarkdown] = useState(true); | ||||
|  | ||||
|   const toggleMarkdown = () => { | ||||
| @@ -55,7 +51,7 @@ const SwitchableMarkdownViewer: FC<Props> = ({file, basePath}) => { | ||||
|   }; | ||||
|  | ||||
|   return ( | ||||
|     <Container> | ||||
|     <div className="is-relative"> | ||||
|       <ToggleButton | ||||
|         color={renderMarkdown ? "link" : ""} | ||||
|         action={toggleMarkdown} | ||||
| @@ -65,11 +61,14 @@ const SwitchableMarkdownViewer: FC<Props> = ({file, basePath}) => { | ||||
|             : t("sources.content.toggleButton.showMarkdown") | ||||
|         } | ||||
|       > | ||||
|         <i className="fab fa-markdown"/> | ||||
|         <i className="fab fa-markdown" /> | ||||
|       </ToggleButton> | ||||
|       {renderMarkdown ? <MarkdownViewer file={file} basePath={basePath}/> : | ||||
|         <SourcecodeViewer file={file} language={"MARKDOWN"}/>} | ||||
|     </Container> | ||||
|       {renderMarkdown ? ( | ||||
|         <MarkdownViewer file={file} basePath={basePath} /> | ||||
|       ) : ( | ||||
|         <SourcecodeViewer file={file} language={"MARKDOWN"} /> | ||||
|       )} | ||||
|     </div> | ||||
|   ); | ||||
| }; | ||||
|  | ||||
|   | ||||
| @@ -49,14 +49,6 @@ const HeaderWrapper = styled.div` | ||||
|   padding: 0.5em 0.75em; | ||||
| `; | ||||
|  | ||||
| const LighterGreyBackgroundPanelBlock = styled.div` | ||||
|   background-color: #fbfbfb; | ||||
| `; | ||||
|  | ||||
| const LighterGreyBackgroundTable = styled.table` | ||||
|   background-color: #fbfbfb; | ||||
| `; | ||||
|  | ||||
| const BorderBottom = styled.div` | ||||
|   border-bottom: solid 1px #dbdbdb; | ||||
| `; | ||||
| @@ -65,10 +57,6 @@ const FullWidthTitleHeader = styled.div` | ||||
|   max-width: 100%; | ||||
| `; | ||||
|  | ||||
| const AlignRight = styled.div` | ||||
|   margin-left: auto; | ||||
| `; | ||||
|  | ||||
| const BorderLessDiv = styled.div` | ||||
|   margin: -1.25rem; | ||||
|   border: none; | ||||
| @@ -116,13 +104,13 @@ const Content: FC<Props> = ({ file, repository, revision, breadcrumb, error }) = | ||||
|       <HeaderWrapper> | ||||
|         <div className={classNames("level", "is-flex-wrap-wrap")}> | ||||
|           <FullWidthTitleHeader | ||||
|             className={classNames("level-left", "is-flex", "has-cursor-pointer", "is-word-break", "mr-2")} | ||||
|             className={classNames("level-left", "is-flex", "is-clickable", "is-word-break", "mr-2")} | ||||
|             onClick={toggleCollapse} | ||||
|           > | ||||
|             <Icon className={classNames("is-inline", "mr-2")} name={`${icon} fa-fw`} color="inherit" /> | ||||
|             {file.name} | ||||
|           </FullWidthTitleHeader> | ||||
|           <AlignRight className={classNames("level-right", "buttons")}> | ||||
|           <div className={classNames("level-right", "buttons", "ml-auto")}> | ||||
|             {selector} | ||||
|             <OpenInFullscreenButton | ||||
|               modalTitle={file?.name} | ||||
| @@ -139,7 +127,7 @@ const Content: FC<Props> = ({ file, repository, revision, breadcrumb, error }) = | ||||
|               }} | ||||
|               renderAll={true} | ||||
|             /> | ||||
|           </AlignRight> | ||||
|           </div> | ||||
|         </div> | ||||
|       </HeaderWrapper> | ||||
|     ); | ||||
| @@ -163,8 +151,8 @@ const Content: FC<Props> = ({ file, repository, revision, breadcrumb, error }) = | ||||
|     if (!collapsed) { | ||||
|       return ( | ||||
|         <> | ||||
|           <LighterGreyBackgroundPanelBlock className="panel-block"> | ||||
|             <LighterGreyBackgroundTable className="table"> | ||||
|           <div className="panel-block has-background-white-bis"> | ||||
|             <table className="table has-background-white-bis"> | ||||
|               <tbody> | ||||
|                 <tr> | ||||
|                   <td>{t("sources.content.path")}</td> | ||||
| @@ -198,8 +186,8 @@ const Content: FC<Props> = ({ file, repository, revision, breadcrumb, error }) = | ||||
|                   }} | ||||
|                 /> | ||||
|               </tbody> | ||||
|             </LighterGreyBackgroundTable> | ||||
|           </LighterGreyBackgroundPanelBlock> | ||||
|             </table> | ||||
|           </div> | ||||
|           <BorderBottom /> | ||||
|         </> | ||||
|       ); | ||||
|   | ||||
| @@ -21,12 +21,11 @@ | ||||
|  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||||
|  * SOFTWARE. | ||||
|  */ | ||||
|  | ||||
| import React, { FC } from "react"; | ||||
| import { useTranslation } from "react-i18next"; | ||||
| import classNames from "classnames"; | ||||
| import { Repository, Tag } from "@scm-manager/ui-types"; | ||||
| import { DateFromNow, SignatureIcon } from "@scm-manager/ui-components"; | ||||
| import styled from "styled-components"; | ||||
| import TagButtonGroup from "./TagButtonGroup"; | ||||
|  | ||||
| type Props = { | ||||
| @@ -34,37 +33,22 @@ type Props = { | ||||
|   tag: Tag; | ||||
| }; | ||||
|  | ||||
| const FlexRow = styled.div` | ||||
|   display: flex; | ||||
|   align-items: center; | ||||
|   flex-wrap: wrap; | ||||
| `; | ||||
|  | ||||
| const Created = styled.div` | ||||
|   margin-left: 0.5rem; | ||||
|   font-size: 0.8rem; | ||||
| `; | ||||
|  | ||||
| const Label = styled.strong` | ||||
|   margin-right: 0.3rem; | ||||
| `; | ||||
|  | ||||
| const Date = styled(DateFromNow)` | ||||
|   font-size: 0.8rem; | ||||
| `; | ||||
|  | ||||
| const TagDetail: FC<Props> = ({ tag, repository }) => { | ||||
|   const [t] = useTranslation("repos"); | ||||
|  | ||||
|   return ( | ||||
|     <div className="media"> | ||||
|       <FlexRow className="media-content"> | ||||
|         <Label className="subtitle has-text-weight-bold has-text-black">{t("tag.name") + ": "} </Label> <span className="subtitle">{tag.name}</span> | ||||
|       <div className={classNames("media-content", "is-flex", "is-flex-wrap-wrap", "is-align-items-center")}> | ||||
|         <strong className={classNames("subtitle", "has-text-weight-bold", "has-text-black", "mr-1")}> | ||||
|           {t("tag.name") + ": "}{" "} | ||||
|         </strong>{" "} | ||||
|         <span className="subtitle">{tag.name}</span> | ||||
|         <SignatureIcon signatures={tag.signatures} className="ml-2 mb-5" /> | ||||
|         <Created className="is-ellipsis-overflow mb-5"> | ||||
|           {t("tags.overview.created")} <Date date={tag.date} className="has-text-grey" /> | ||||
|         </Created> | ||||
|       </FlexRow> | ||||
|         <div className={classNames("is-ellipsis-overflow", "mb-5", "ml-2", "is-size-7")}> | ||||
|           {t("tags.overview.created")}{" "} | ||||
|           <DateFromNow className={classNames("has-text-grey", "is-size-7")} date={tag.date} /> | ||||
|         </div> | ||||
|       </div> | ||||
|       <div className="media-right"> | ||||
|         <TagButtonGroup repository={repository} tag={tag} /> | ||||
|       </div> | ||||
|   | ||||
| @@ -21,12 +21,11 @@ | ||||
|  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||||
|  * SOFTWARE. | ||||
|  */ | ||||
|  | ||||
| import React, { FC } from "react"; | ||||
| import { useTranslation } from "react-i18next"; | ||||
| import { Link as RouterLink } from "react-router-dom"; | ||||
| import { useTranslation } from "react-i18next"; | ||||
| import classNames from "classnames"; | ||||
| import { Tag, Link } from "@scm-manager/ui-types"; | ||||
| import styled from "styled-components"; | ||||
| import { DateFromNow, Icon } from "@scm-manager/ui-components"; | ||||
|  | ||||
| type Props = { | ||||
| @@ -36,11 +35,6 @@ type Props = { | ||||
|   // deleting: boolean; | ||||
| }; | ||||
|  | ||||
| const Created = styled.span` | ||||
|   margin-left: 1rem; | ||||
|   font-size: 0.8rem; | ||||
| `; | ||||
|  | ||||
| const TagRow: FC<Props> = ({ tag, baseUrl, onDelete }) => { | ||||
|   const [t] = useTranslation("repos"); | ||||
|  | ||||
| @@ -61,9 +55,9 @@ const TagRow: FC<Props> = ({ tag, baseUrl, onDelete }) => { | ||||
|       <td> | ||||
|         <RouterLink to={to} title={tag.name}> | ||||
|           {tag.name} | ||||
|           <Created className="has-text-grey is-ellipsis-overflow"> | ||||
|           <span className={classNames("has-text-grey", "is-ellipsis-overflow", "ml-2", "is-size-7")}> | ||||
|             {t("tags.overview.created")} <DateFromNow date={tag.date} /> | ||||
|           </Created> | ||||
|           </span> | ||||
|         </RouterLink> | ||||
|       </td> | ||||
|       <td className="is-darker">{deleteButton}</td> | ||||
|   | ||||
| @@ -53,7 +53,7 @@ type CountProps = { | ||||
|  | ||||
| const Count: FC<CountProps> = ({ isLoading, isSelected, count }) => { | ||||
|   if (isLoading) { | ||||
|     return <span className={"small-loading-spinner"} />; | ||||
|     return <span className="small-loading-spinner" />; | ||||
|   } | ||||
|   return ( | ||||
|     <Tag rounded={true} color={isSelected ? "info" : "light"}> | ||||
|   | ||||
| @@ -22,24 +22,18 @@ | ||||
|  * SOFTWARE. | ||||
|  */ | ||||
| import React, { FC, useEffect, useState } from "react"; | ||||
| import { useTranslation } from "react-i18next"; | ||||
| import classNames from "classnames"; | ||||
| import { User } from "@scm-manager/ui-types"; | ||||
| import { useConvertToExternal, useConvertToInternal } from "@scm-manager/ui-api"; | ||||
| import { | ||||
|   Button, | ||||
|   ErrorNotification, | ||||
|   Level, | ||||
|   Modal, | ||||
|   PasswordConfirmation, | ||||
|   SubmitButton | ||||
|   SubmitButton, | ||||
| } from "@scm-manager/ui-components"; | ||||
| import { useTranslation } from "react-i18next"; | ||||
| import { User } from "@scm-manager/ui-types"; | ||||
| import styled from "styled-components"; | ||||
| import { useConvertToExternal, useConvertToInternal } from "@scm-manager/ui-api"; | ||||
|  | ||||
| const ExternalDescription = styled.div` | ||||
|   display: flex; | ||||
|   align-items: center; | ||||
|   font-weight: 400; | ||||
| `; | ||||
|  | ||||
| type Props = { | ||||
|   user: User; | ||||
| @@ -53,12 +47,12 @@ const UserConverter: FC<Props> = ({ user }) => { | ||||
|   const { | ||||
|     isLoading: isConvertingToInternal, | ||||
|     error: convertingToInternalError, | ||||
|     convertToInternal | ||||
|     convertToInternal, | ||||
|   } = useConvertToInternal(); | ||||
|   const { | ||||
|     isLoading: isConvertingToExternal, | ||||
|     error: convertingToExternalError, | ||||
|     convertToExternal | ||||
|     convertToExternal, | ||||
|   } = useConvertToExternal(); | ||||
|   const error = convertingToExternalError || convertingToInternalError || undefined; | ||||
|   const isLoading = isConvertingToExternal || isConvertingToInternal; | ||||
| @@ -135,7 +129,9 @@ const UserConverter: FC<Props> = ({ user }) => { | ||||
|       {showPasswordModal && passwordModal} | ||||
|       {error && <ErrorNotification error={error} />} | ||||
|       <div className="columns is-multiline"> | ||||
|         <ExternalDescription className="column is-half">{getUserExternalDescription()}</ExternalDescription> | ||||
|         <div className={classNames("column", "is-half", "is-flex", "is-align-items-center")}> | ||||
|           {getUserExternalDescription()} | ||||
|         </div> | ||||
|         <div className="column is-half"> | ||||
|           <Level right={getConvertButton()} /> | ||||
|         </div> | ||||
|   | ||||
| @@ -56,19 +56,19 @@ const ApiKeyCreatedModal: FC<Props> = ({ addedKey, close }) => { | ||||
|   }; | ||||
|  | ||||
|   const newPassphraseModalContent = ( | ||||
|     <div className={"media-content"}> | ||||
|     <div className="media-content"> | ||||
|       <p>{t("apiKey.modal.text1")}</p> | ||||
|       <p> | ||||
|         <b>{t("apiKey.modal.text2")}</b> | ||||
|       </p> | ||||
|       <hr /> | ||||
|       <div className={"columns"}> | ||||
|         <div className={"column is-11"}> | ||||
|           <KeyArea wrap={"soft"} ref={keyRef} className={"input"} value={addedKey.token} /> | ||||
|       <div className="columns"> | ||||
|         <div className="column is-11"> | ||||
|           <KeyArea wrap={"soft"} ref={keyRef} className="input" value={addedKey.token} /> | ||||
|         </div> | ||||
|         <NoLeftMargin className={"column is-1"}> | ||||
|         <NoLeftMargin className="column is-1"> | ||||
|           <Icon | ||||
|             className={"is-hidden-mobile fa-2x"} | ||||
|             className="is-hidden-mobile fa-2x" | ||||
|             name={copied ? "clipboard-check" : "clipboard"} | ||||
|             title={t("apiKey.modal.clipboard")} | ||||
|             onClick={copy} | ||||
|   | ||||
		Reference in New Issue
	
	Block a user