mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-12-21 15:59:48 +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, {
|
||||
"is-outlined": outlined,
|
||||
"is-rounded": rounded,
|
||||
"has-cursor-pointer": onClick,
|
||||
})}
|
||||
<span
|
||||
className={classNames(
|
||||
"tag",
|
||||
`is-${color}`,
|
||||
`is-${size}`,
|
||||
className,
|
||||
{
|
||||
"is-outlined": outlined,
|
||||
"is-rounded": rounded,
|
||||
"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>
|
||||
</div>
|
||||
<VCenteredChildColumn className={classNames("column", "is-flex")}>
|
||||
<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