mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-12-21 15:59:48 +01:00
Small header (#1721)
Co-authored-by: René Pfeuffer <rene.pfeuffer@cloudogu.com>
This commit is contained in:
2
gradle/changelog/redesign_header.yaml
Normal file
2
gradle/changelog/redesign_header.yaml
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
- type: changed
|
||||||
|
description: Redesign SCM-Manager header ([#1721](https://github.com/scm-manager/scm-manager/pull/1721))
|
||||||
@@ -32,8 +32,14 @@ const Wrapper = styled.div`
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
storiesOf("Logo", module).add("Default", () => (
|
storiesOf("Logo", module)
|
||||||
|
.add("Default", () => (
|
||||||
<Wrapper>
|
<Wrapper>
|
||||||
<Logo />
|
<Logo />
|
||||||
</Wrapper>
|
</Wrapper>
|
||||||
|
))
|
||||||
|
.add("WithoutText", () => (
|
||||||
|
<Wrapper>
|
||||||
|
<Logo withText={false} />
|
||||||
|
</Wrapper>
|
||||||
));
|
));
|
||||||
|
|||||||
@@ -21,15 +21,22 @@
|
|||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
* SOFTWARE.
|
* SOFTWARE.
|
||||||
*/
|
*/
|
||||||
import React from "react";
|
import React, { FC } from "react";
|
||||||
import { WithTranslation, withTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import Image from "./Image";
|
import Image from "./Image";
|
||||||
|
|
||||||
class Logo extends React.Component<WithTranslation> {
|
type Props = {
|
||||||
render() {
|
withText?: boolean;
|
||||||
const { t } = this.props;
|
className?: string;
|
||||||
return <Image src="/images/logo.png" alt={t("logo.alt")} />;
|
};
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default withTranslation("commons")(Logo);
|
const Logo: FC<Props> = ({ withText = true, className }) => {
|
||||||
|
const [t] = useTranslation("commons");
|
||||||
|
|
||||||
|
if (withText) {
|
||||||
|
return <Image src="/images/logo.png" alt={t("logo.alt")} className={className} />;
|
||||||
|
}
|
||||||
|
return <Image src="/images/scmLogo.svg" alt={t("logo.alt")} className={className} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Logo;
|
||||||
|
|||||||
@@ -53430,6 +53430,17 @@ exports[`Storyshots Logo Default 1`] = `
|
|||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`Storyshots Logo WithoutText 1`] = `
|
||||||
|
<div
|
||||||
|
className="Logostories__Wrapper-sc-14nnt4j-0 brMuIC"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
alt="logo.alt"
|
||||||
|
src="/images/scmLogo.svg"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`Storyshots MarkdownView Code without Lang 1`] = `
|
exports[`Storyshots MarkdownView Code without Lang 1`] = `
|
||||||
<div
|
<div
|
||||||
className="MarkdownViewstories__Spacing-sc-1lofakk-0 iOWSJJ"
|
className="MarkdownViewstories__Spacing-sc-1lofakk-0 iOWSJJ"
|
||||||
|
|||||||
@@ -30,20 +30,20 @@ export type Device = {
|
|||||||
|
|
||||||
export const devices = {
|
export const devices = {
|
||||||
mobile: {
|
mobile: {
|
||||||
width: 768
|
width: 768,
|
||||||
},
|
},
|
||||||
tablet: {
|
tablet: {
|
||||||
width: 769
|
width: 769,
|
||||||
},
|
},
|
||||||
desktop: {
|
desktop: {
|
||||||
width: 1024
|
width: 1024,
|
||||||
},
|
},
|
||||||
widescreen: {
|
widescreen: {
|
||||||
width: 1216
|
width: 1216,
|
||||||
},
|
},
|
||||||
fullhd: {
|
fullhd: {
|
||||||
width: 1408
|
width: 1408,
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export type DeviceType = keyof typeof devices;
|
export type DeviceType = keyof typeof devices;
|
||||||
|
|||||||
@@ -21,18 +21,23 @@
|
|||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
* SOFTWARE.
|
* SOFTWARE.
|
||||||
*/
|
*/
|
||||||
import React, { ReactNode } from "react";
|
import React, { FC, ReactNode } from "react";
|
||||||
import Logo from "./../Logo";
|
import Logo from "./../Logo";
|
||||||
|
import { Links } from "@scm-manager/ui-types";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
children?: ReactNode;
|
links: Links;
|
||||||
|
authenticated: boolean;
|
||||||
|
children: ReactNode;
|
||||||
};
|
};
|
||||||
|
|
||||||
class Header extends React.Component<Props> {
|
const SmallHeader: FC<{ children: ReactNode }> = ({ children }) => {
|
||||||
render() {
|
return <div className="has-scm-background">{children}</div>;
|
||||||
const { children } = this.props;
|
};
|
||||||
|
|
||||||
|
const LargeHeader: FC = () => {
|
||||||
return (
|
return (
|
||||||
<section className="hero is-dark is-small">
|
<section className="hero has-scm-background is-small">
|
||||||
<div className="hero-body">
|
<div className="hero-body">
|
||||||
<div className="container">
|
<div className="container">
|
||||||
<div className="columns is-vcentered">
|
<div className="columns is-vcentered">
|
||||||
@@ -42,12 +47,16 @@ class Header extends React.Component<Props> {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="hero-foot">
|
|
||||||
<div className="container">{children}</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const Header: FC<Props> = ({ authenticated, children, links }) => {
|
||||||
|
if (authenticated) {
|
||||||
|
return <SmallHeader>{children}</SmallHeader>;
|
||||||
|
} else {
|
||||||
|
return <LargeHeader />;
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
export default Header;
|
export default Header;
|
||||||
|
|||||||
@@ -21,86 +21,59 @@
|
|||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
* SOFTWARE.
|
* SOFTWARE.
|
||||||
*/
|
*/
|
||||||
import React, { ReactNode } from "react";
|
import React, { FC, ReactNode } from "react";
|
||||||
import { WithTranslation, withTranslation } from "react-i18next";
|
|
||||||
import PrimaryNavigationLink from "./PrimaryNavigationLink";
|
import PrimaryNavigationLink from "./PrimaryNavigationLink";
|
||||||
import { Links } from "@scm-manager/ui-types";
|
import { Links } from "@scm-manager/ui-types";
|
||||||
import { binder, ExtensionPoint } from "@scm-manager/ui-extensions";
|
import { binder, ExtensionPoint } from "@scm-manager/ui-extensions";
|
||||||
import { urls } from "@scm-manager/ui-api";
|
import { urls } from "@scm-manager/ui-api";
|
||||||
import { withRouter, RouteComponentProps } from "react-router-dom";
|
import { useLocation } from "react-router-dom";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
type Props = RouteComponentProps & WithTranslation & {
|
type Props = {
|
||||||
links: Links;
|
links: Links;
|
||||||
};
|
};
|
||||||
|
|
||||||
type Appender = (to: string, match: string, label: string, linkName: string) => void;
|
type Appender = (to: string, match: string, label: string, linkName: string) => void;
|
||||||
|
|
||||||
class PrimaryNavigation extends React.Component<Props> {
|
const PrimaryNavigation: FC<Props> = ({ links }) => {
|
||||||
createNavigationAppender = (navigationItems: ReactNode[]): Appender => {
|
const [t] = useTranslation("commons");
|
||||||
const { t, links } = this.props;
|
const location = useLocation();
|
||||||
|
|
||||||
|
const createNavigationAppender = (navItems: ReactNode[]): Appender => {
|
||||||
return (to: string, match: string, label: string, linkName: string) => {
|
return (to: string, match: string, label: string, linkName: string) => {
|
||||||
const link = links[linkName];
|
const link = links[linkName];
|
||||||
if (link) {
|
if (link) {
|
||||||
const navigationItem = <PrimaryNavigationLink testId={label.replace(".", "-")} to={to} match={match} label={t(label)} key={linkName} />;
|
const navigationItem = (
|
||||||
navigationItems.push(navigationItem);
|
<PrimaryNavigationLink
|
||||||
}
|
testId={label.replace(".", "-")}
|
||||||
};
|
to={to}
|
||||||
};
|
match={match}
|
||||||
|
label={t(label)}
|
||||||
appendLogout = (navigationItems: ReactNode[], append: Appender) => {
|
key={linkName}
|
||||||
const { t, links } = this.props;
|
className="navbar-item"
|
||||||
|
/>
|
||||||
const props = {
|
|
||||||
links,
|
|
||||||
label: t("primary-navigation.logout")
|
|
||||||
};
|
|
||||||
|
|
||||||
if (binder.hasExtension("primary-navigation.logout", props)) {
|
|
||||||
navigationItems.push(
|
|
||||||
<ExtensionPoint key="primary-navigation.logout" name="primary-navigation.logout" props={props} />
|
|
||||||
);
|
);
|
||||||
} else {
|
navItems.push(navigationItem);
|
||||||
append("/logout", "/logout", "primary-navigation.logout", "logout");
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
};
|
||||||
|
|
||||||
appendLogin = (navigationItems: ReactNode[], append: Appender) => {
|
const createNavigationItems = () => {
|
||||||
const { t, links, location } = this.props;
|
const navItems: ReactNode[] = [];
|
||||||
|
|
||||||
const from = location.pathname;
|
const extensionProps = {
|
||||||
const loginPath = "/login";
|
|
||||||
const to = `${loginPath}?from=${encodeURIComponent(from)}`;
|
|
||||||
|
|
||||||
const props = {
|
|
||||||
links,
|
links,
|
||||||
label: t("primary-navigation.login"),
|
label: t("primary-navigation.first-menu"),
|
||||||
loginUrl: urls.withContextPath(loginPath),
|
|
||||||
from
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (binder.hasExtension("primary-navigation.login", props)) {
|
const append = createNavigationAppender(navItems);
|
||||||
navigationItems.push(
|
if (binder.hasExtension("primary-navigation.first-menu", extensionProps)) {
|
||||||
<ExtensionPoint key="primary-navigation.login" name="primary-navigation.login" props={props} />
|
navItems.push(
|
||||||
);
|
<ExtensionPoint
|
||||||
} else {
|
key="primary-navigation.first-menu"
|
||||||
append(to, "/login", "primary-navigation.login", "login");
|
name="primary-navigation.first-menu"
|
||||||
}
|
props={extensionProps}
|
||||||
};
|
/>
|
||||||
|
|
||||||
createNavigationItems = () => {
|
|
||||||
const navigationItems: ReactNode[] = [];
|
|
||||||
const { t, links } = this.props;
|
|
||||||
|
|
||||||
const props = {
|
|
||||||
links,
|
|
||||||
label: t("primary-navigation.first-menu")
|
|
||||||
};
|
|
||||||
|
|
||||||
const append = this.createNavigationAppender(navigationItems);
|
|
||||||
if (binder.hasExtension("primary-navigation.first-menu", props)) {
|
|
||||||
navigationItems.push(
|
|
||||||
<ExtensionPoint key="primary-navigation.first-menu" name="primary-navigation.first-menu" props={props} />
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
append("/repos/", "/(repo|repos)", "primary-navigation.repositories", "repositories");
|
append("/repos/", "/(repo|repos)", "primary-navigation.repositories", "repositories");
|
||||||
@@ -108,32 +81,21 @@ class PrimaryNavigation extends React.Component<Props> {
|
|||||||
append("/groups/", "/(group|groups)", "primary-navigation.groups", "groups");
|
append("/groups/", "/(group|groups)", "primary-navigation.groups", "groups");
|
||||||
append("/admin", "/admin", "primary-navigation.admin", "config");
|
append("/admin", "/admin", "primary-navigation.admin", "config");
|
||||||
|
|
||||||
navigationItems.push(
|
navItems.push(
|
||||||
<ExtensionPoint
|
<ExtensionPoint
|
||||||
key="primary-navigation"
|
key="primary-navigation"
|
||||||
name="primary-navigation"
|
name="primary-navigation"
|
||||||
renderAll={true}
|
renderAll={true}
|
||||||
props={{
|
props={{
|
||||||
links: this.props.links
|
links,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
this.appendLogout(navigationItems, append);
|
return navItems;
|
||||||
this.appendLogin(navigationItems, append);
|
|
||||||
|
|
||||||
return navigationItems;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
return <>{createNavigationItems()}</>;
|
||||||
const navigationItems = this.createNavigationItems();
|
};
|
||||||
|
|
||||||
return (
|
export default PrimaryNavigation;
|
||||||
<nav className="tabs is-boxed mb-0">
|
|
||||||
<ul>{navigationItems}</ul>
|
|
||||||
</nav>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default withTranslation("commons")(withRouter(PrimaryNavigation));
|
|
||||||
|
|||||||
@@ -21,42 +21,31 @@
|
|||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
* SOFTWARE.
|
* SOFTWARE.
|
||||||
*/
|
*/
|
||||||
import * as React from "react";
|
import React, { FC } from "react";
|
||||||
import { Route, Link } from "react-router-dom";
|
import { useRouteMatch, Link } from "react-router-dom";
|
||||||
import { createAttributesForTesting } from "../devBuild";
|
import { createAttributesForTesting } from "../devBuild";
|
||||||
|
import classNames from "classnames";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
to: string;
|
to: string;
|
||||||
|
match: string;
|
||||||
label: string;
|
label: string;
|
||||||
match?: string;
|
|
||||||
activeOnlyWhenExact?: boolean;
|
|
||||||
testId?: string;
|
testId?: string;
|
||||||
|
className?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
class PrimaryNavigationLink extends React.Component<Props> {
|
const PrimaryNavigationLink: FC<Props> = ({ to, match, testId, label, className }) => {
|
||||||
renderLink = (route: any) => {
|
const routeMatch = useRouteMatch({ path: match });
|
||||||
const { to, label, testId } = this.props;
|
|
||||||
return (
|
return (
|
||||||
<li className={route.match ? "is-active" : ""}>
|
<Link
|
||||||
<Link to={to} {...createAttributesForTesting(testId)}>
|
to={to}
|
||||||
|
className={classNames(className, "navbar-item", { "is-active": routeMatch })}
|
||||||
|
{...createAttributesForTesting(testId)}
|
||||||
|
>
|
||||||
{label}
|
{label}
|
||||||
</Link>
|
</Link>
|
||||||
</li>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
|
||||||
const { to, match, activeOnlyWhenExact, testId } = this.props;
|
|
||||||
const path = match ? match : to;
|
|
||||||
return (
|
|
||||||
<Route
|
|
||||||
path={path}
|
|
||||||
exact={activeOnlyWhenExact}
|
|
||||||
children={this.renderLink}
|
|
||||||
{...createAttributesForTesting(testId)}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default PrimaryNavigationLink;
|
export default PrimaryNavigationLink;
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fortawesome/fontawesome-free": "^5.11.2",
|
"@fortawesome/fontawesome-free": "^5.11.2",
|
||||||
"bulma": "^0.9.0",
|
"bulma": "^0.9.3",
|
||||||
"bulma-popover": "^1.0.0",
|
"bulma-popover": "^1.0.0",
|
||||||
"bulma-tooltip": "^3.0.0",
|
"bulma-tooltip": "^3.0.0",
|
||||||
"react-diff-view": "^2.4.1"
|
"react-diff-view": "^2.4.1"
|
||||||
|
|||||||
@@ -60,10 +60,6 @@ $family-monospace: "Courier New", Monaco, Menlo, "Ubuntu Mono", "source-code-pro
|
|||||||
padding: 0 0 0 3.8em !important;
|
padding: 0 0 0 3.8em !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.main {
|
|
||||||
min-height: calc(100vh - 300px);
|
|
||||||
}
|
|
||||||
|
|
||||||
// shown in top section when pageactions set
|
// shown in top section when pageactions set
|
||||||
hr.header-with-actions {
|
hr.header-with-actions {
|
||||||
margin-top: -10px;
|
margin-top: -10px;
|
||||||
@@ -92,7 +88,6 @@ hr.header-with-actions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
footer.footer {
|
footer.footer {
|
||||||
//height: 100px;
|
|
||||||
background-color: $white-ter;
|
background-color: $white-ter;
|
||||||
padding: inherit;
|
padding: inherit;
|
||||||
|
|
||||||
@@ -716,6 +711,13 @@ form .field:not(.is-grouped) {
|
|||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.has-scm-background {
|
||||||
|
background-image: url(images/scmManagerHero.jpg) !important;
|
||||||
|
background-size: cover;
|
||||||
|
background-position: top center;
|
||||||
|
background-color: #002e4b;
|
||||||
|
}
|
||||||
|
|
||||||
// hero
|
// hero
|
||||||
.hero.is-dark {
|
.hero.is-dark {
|
||||||
background-color: #002e4b;
|
background-color: #002e4b;
|
||||||
|
|||||||
@@ -22,10 +22,11 @@
|
|||||||
* SOFTWARE.
|
* SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Links } from "./hal";
|
import { Embedded, Links } from "./hal";
|
||||||
|
|
||||||
export type IndexResources = {
|
export type IndexResources = {
|
||||||
version: string;
|
version: string;
|
||||||
initialization?: string;
|
initialization?: string;
|
||||||
_links: Links;
|
_links: Links;
|
||||||
|
_embedded?: Embedded;
|
||||||
};
|
};
|
||||||
|
|||||||
104
scm-ui/ui-webapp/public/images/scmLogo.svg
Normal file
104
scm-ui/ui-webapp/public/images/scmLogo.svg
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 25.3.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<svg version="1.1" id="Ebene_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
viewBox="0 0 213.8 181.8" style="enable-background:new 0 0 213.8 181.8;" xml:space="preserve">
|
||||||
|
<style type="text/css">
|
||||||
|
.st0{fill:#C7C7C6;}
|
||||||
|
.st1{clip-path:url(#SVGID_2_);fill:url(#SVGID_3_);}
|
||||||
|
.st2{clip-path:url(#SVGID_5_);fill:url(#SVGID_6_);}
|
||||||
|
.st3{clip-path:url(#SVGID_8_);fill:url(#SVGID_9_);}
|
||||||
|
.st4{clip-path:url(#SVGID_11_);fill:url(#SVGID_12_);}
|
||||||
|
.st5{clip-path:url(#SVGID_14_);fill:url(#SVGID_15_);}
|
||||||
|
</style>
|
||||||
|
<g>
|
||||||
|
<path class="st0" d="M0.1,0L0,0.1C25,30,61.8,97.2,41,160.3c-0.4,3.2-1.4,6.1-2.8,8.7C76.6,99.9,25.3,19.6,14.6,4.2
|
||||||
|
C9.8,2.4,4.9,1,0.1,0"/>
|
||||||
|
<path class="st0" d="M40.5,165.2C75.9,104.5,41.6,35.4,25.2,8.5c4.8,2.3,9.6,4.9,14.1,7.7C54.8,47.3,76.1,107,45.6,157
|
||||||
|
C43.9,159.7,42.2,162.5,40.5,165.2"/>
|
||||||
|
<path class="st0" d="M49.3,151c7.7-13.3,31.3-53.6-0.4-128.3C53.2,26,57.3,29.4,61,33c13.8,44.1,11.6,80-6.6,109.8L49.3,151z"/>
|
||||||
|
<path class="st0" d="M58.8,135.6c7-12,23.4-40.1,11.3-93.2c3.2,3.8,6.1,7.7,8.5,11.7c2,21.9,1.5,45.9-14.7,73.2
|
||||||
|
C62.2,130.1,60.5,132.9,58.8,135.6"/>
|
||||||
|
<path class="st0" d="M67.6,121.5c5.1-8.8,15.8-27.1,15.6-58.7c0.6,1.3,1.1,2.7,1.6,4c1,2.7,1.7,5.3,2.1,7.7
|
||||||
|
c-2.5,13.8-5.6,24.1-14.2,38.6L67.6,121.5z"/>
|
||||||
|
<g>
|
||||||
|
<defs>
|
||||||
|
<path id="SVGID_1_" d="M75.7,181.8L75.7,181.8c0.6-2.7,1.7-5.2,3.3-7.7c24.8-61.6,96.4-89.3,134.8-96l0-0.2
|
||||||
|
c-4.3-2.4-9-4.4-13.8-6.2C182,76.6,91.3,104.8,75.7,181.8"/>
|
||||||
|
</defs>
|
||||||
|
<clipPath id="SVGID_2_">
|
||||||
|
<use xlink:href="#SVGID_1_" style="overflow:visible;"/>
|
||||||
|
</clipPath>
|
||||||
|
|
||||||
|
<linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="-306.7593" y1="394.7581" x2="-304.2159" y2="394.7581" gradientTransform="matrix(54.3221 0 0 -54.3221 16739.459 21570.8125)">
|
||||||
|
<stop offset="0" style="stop-color:#45BFEA"/>
|
||||||
|
<stop offset="0.5" style="stop-color:#2FA9E0"/>
|
||||||
|
<stop offset="1" style="stop-color:#2893D1"/>
|
||||||
|
</linearGradient>
|
||||||
|
<rect x="75.7" y="71.7" class="st1" width="138.2" height="110.1"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<defs>
|
||||||
|
<path id="SVGID_4_" d="M77.6,168.5c-0.5,3.2-1,6.4-1.4,9.6c12.2-69.2,83.1-99.9,113-109.9c-5.2-1.4-10.5-2.4-15.8-3.2
|
||||||
|
C141.4,78.9,86.6,110.7,77.6,168.5"/>
|
||||||
|
</defs>
|
||||||
|
<clipPath id="SVGID_5_">
|
||||||
|
<use xlink:href="#SVGID_4_" style="overflow:visible;"/>
|
||||||
|
</clipPath>
|
||||||
|
|
||||||
|
<linearGradient id="SVGID_6_" gradientUnits="userSpaceOnUse" x1="-305.2708" y1="395.9162" x2="-302.7246" y2="395.9162" gradientTransform="matrix(44.3853 0 0 -44.3853 13625.6709 17694.4355)">
|
||||||
|
<stop offset="0" style="stop-color:#45BFEA"/>
|
||||||
|
<stop offset="0.5" style="stop-color:#2FA9E0"/>
|
||||||
|
<stop offset="1" style="stop-color:#2893D1"/>
|
||||||
|
</linearGradient>
|
||||||
|
<rect x="76.1" y="65" class="st2" width="113" height="113.1"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<defs>
|
||||||
|
<path id="SVGID_7_" d="M145.9,63.8c-39,24.8-60.6,53.7-65.9,88.2l-1.4,9.6c2.7-15.1,10.8-61.1,83.2-97.8
|
||||||
|
c-2.8-0.2-5.5-0.2-8.3-0.2C151,63.6,148.5,63.7,145.9,63.8"/>
|
||||||
|
</defs>
|
||||||
|
<clipPath id="SVGID_8_">
|
||||||
|
<use xlink:href="#SVGID_7_" style="overflow:visible;"/>
|
||||||
|
</clipPath>
|
||||||
|
|
||||||
|
<linearGradient id="SVGID_9_" gradientUnits="userSpaceOnUse" x1="-302.3176" y1="398.1338" x2="-299.7714" y2="398.1338" gradientTransform="matrix(32.6903 0 0 -32.6903 9961.4385 13127.7422)">
|
||||||
|
<stop offset="0" style="stop-color:#45BFEA"/>
|
||||||
|
<stop offset="0.5" style="stop-color:#2FA9E0"/>
|
||||||
|
<stop offset="1" style="stop-color:#2893D1"/>
|
||||||
|
</linearGradient>
|
||||||
|
<rect x="78.6" y="63.6" class="st3" width="83.2" height="98"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<defs>
|
||||||
|
<path id="SVGID_10_" d="M118.8,68.7c-15.6,15.4-30.8,34.1-36.1,65.4c-0.5,3.2-1,6.4-1.4,9.6c2.4-13.7,8-45.7,51.6-78.5
|
||||||
|
C128,66.1,123.2,67.3,118.8,68.7"/>
|
||||||
|
</defs>
|
||||||
|
<clipPath id="SVGID_11_">
|
||||||
|
<use xlink:href="#SVGID_10_" style="overflow:visible;"/>
|
||||||
|
</clipPath>
|
||||||
|
|
||||||
|
<linearGradient id="SVGID_12_" gradientUnits="userSpaceOnUse" x1="-295.4908" y1="403.173" x2="-292.9446" y2="403.173" gradientTransform="matrix(20.2781 0 0 -20.2781 6073.2446 8280.0889)">
|
||||||
|
<stop offset="0" style="stop-color:#45BFEA"/>
|
||||||
|
<stop offset="0.5" style="stop-color:#2FA9E0"/>
|
||||||
|
<stop offset="1" style="stop-color:#2893D1"/>
|
||||||
|
</linearGradient>
|
||||||
|
<rect x="81.3" y="65.3" class="st4" width="51.6" height="78.5"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<defs>
|
||||||
|
<path id="SVGID_13_" d="M105.9,74.4c-2.5,1.4-4.7,2.9-6.6,4.5c-7,12.2-11.3,22-14.1,38.7l-1.4,9.6c1.8-10,5.4-30.9,26-54.9
|
||||||
|
C108.4,73,107.1,73.7,105.9,74.4"/>
|
||||||
|
</defs>
|
||||||
|
<clipPath id="SVGID_14_">
|
||||||
|
<use xlink:href="#SVGID_13_" style="overflow:visible;"/>
|
||||||
|
</clipPath>
|
||||||
|
|
||||||
|
<linearGradient id="SVGID_15_" gradientUnits="userSpaceOnUse" x1="-277.7022" y1="416.0602" x2="-275.1562" y2="416.0602" gradientTransform="matrix(10.2218 0 0 -10.2218 2922.3333 4352.7075)">
|
||||||
|
<stop offset="0" style="stop-color:#45BFEA"/>
|
||||||
|
<stop offset="0.5" style="stop-color:#2FA9E0"/>
|
||||||
|
<stop offset="1" style="stop-color:#2893D1"/>
|
||||||
|
</linearGradient>
|
||||||
|
<rect x="83.7" y="72.4" class="st5" width="26" height="54.9"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 5.1 KiB |
@@ -24,11 +24,11 @@
|
|||||||
import React, { FC } from "react";
|
import React, { FC } from "react";
|
||||||
import Main from "./Main";
|
import Main from "./Main";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { ErrorPage, Footer, Header, Loading, PrimaryNavigation } from "@scm-manager/ui-components";
|
import { ErrorPage, Footer, Header, Loading } from "@scm-manager/ui-components";
|
||||||
import { binder } from "@scm-manager/ui-extensions";
|
import { binder } from "@scm-manager/ui-extensions";
|
||||||
import Login from "./Login";
|
import Login from "./Login";
|
||||||
import { useIndex, useSubject } from "@scm-manager/ui-api";
|
import { useIndex, useSubject } from "@scm-manager/ui-api";
|
||||||
import Notifications from "./Notifications";
|
import NavigationBar from "./NavigationBar";
|
||||||
|
|
||||||
const App: FC = () => {
|
const App: FC = () => {
|
||||||
const { data: index } = useIndex();
|
const { data: index } = useIndex();
|
||||||
@@ -46,7 +46,7 @@ const App: FC = () => {
|
|||||||
|
|
||||||
if (index?.initialization) {
|
if (index?.initialization) {
|
||||||
const Extension = binder.getExtension(`initialization.step.${index.initialization}`);
|
const Extension = binder.getExtension(`initialization.step.${index.initialization}`);
|
||||||
content = <Extension data={index._embedded[index.initialization]} />;
|
content = <Extension data={index?._embedded ? index._embedded[index.initialization] : undefined} />;
|
||||||
} else if (!authenticated && !isLoading) {
|
} else if (!authenticated && !isLoading) {
|
||||||
content = <Login />;
|
content = <Login />;
|
||||||
} else if (isLoading) {
|
} else if (isLoading) {
|
||||||
@@ -59,13 +59,8 @@ const App: FC = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="App">
|
<div className="App">
|
||||||
<Header>
|
<Header authenticated={authenticated} links={index._links}>
|
||||||
{authenticated ? (
|
<NavigationBar links={index._links} />
|
||||||
<div className="is-flex is-justify-content-space-between is-flex-wrap-nowrap ">
|
|
||||||
<PrimaryNavigation links={index._links} />
|
|
||||||
<Notifications />
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
</Header>
|
</Header>
|
||||||
{content}
|
{content}
|
||||||
{authenticated ? <Footer me={me} version={index.version} links={index._links} /> : null}
|
{authenticated ? <Footer me={me} version={index.version} links={index._links} /> : null}
|
||||||
|
|||||||
46
scm-ui/ui-webapp/src/containers/HeaderActions.tsx
Normal file
46
scm-ui/ui-webapp/src/containers/HeaderActions.tsx
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React, { FC } from "react";
|
||||||
|
import Notifications from "./Notifications";
|
||||||
|
import LogoutButton from "./LogoutButton";
|
||||||
|
import { Links } from "@scm-manager/ui-types";
|
||||||
|
import LoginButton from "./LoginButton";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
burgerMode: boolean;
|
||||||
|
links: Links;
|
||||||
|
};
|
||||||
|
|
||||||
|
const HeaderActions: FC<Props> = ({ burgerMode, links }) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{!burgerMode ? <Notifications className="navbar-item" /> : null}
|
||||||
|
<LogoutButton burgerMode={burgerMode} links={links} />
|
||||||
|
<LoginButton burgerMode={burgerMode} links={links} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default HeaderActions;
|
||||||
86
scm-ui/ui-webapp/src/containers/LoginButton.tsx
Normal file
86
scm-ui/ui-webapp/src/containers/LoginButton.tsx
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React, { FC } from "react";
|
||||||
|
import { devices, Icon } from "@scm-manager/ui-components";
|
||||||
|
import { binder, ExtensionPoint } from "@scm-manager/ui-extensions";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { Links } from "@scm-manager/ui-types";
|
||||||
|
import classNames from "classnames";
|
||||||
|
import { useHistory } from "react-router-dom";
|
||||||
|
import styled from "styled-components";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
className?: string;
|
||||||
|
links?: Links;
|
||||||
|
burgerMode: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
const StyledLogoutButton = styled.div`
|
||||||
|
@media screen and (max-width: ${devices.desktop.width}px) {
|
||||||
|
border-top: 1px solid white;
|
||||||
|
margin-top: 1rem;
|
||||||
|
padding-top: 1rem;
|
||||||
|
padding-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width: ${devices.desktop.width}px) {
|
||||||
|
margin-left: 2rem;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const LoginButton: FC<Props> = ({ burgerMode, links, className }) => {
|
||||||
|
const [t] = useTranslation("commons");
|
||||||
|
const history = useHistory();
|
||||||
|
|
||||||
|
const extensionProps = {
|
||||||
|
links,
|
||||||
|
label: t("primary-navigation.login"),
|
||||||
|
};
|
||||||
|
|
||||||
|
if (links?.login) {
|
||||||
|
if (binder.hasExtension("primary-navigation.login", extensionProps)) {
|
||||||
|
return <ExtensionPoint key="primary-navigation.login" name="primary-navigation.login" props={extensionProps} />;
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<StyledLogoutButton
|
||||||
|
data-testid="primary-navigation-login"
|
||||||
|
onClick={() => history.push({ pathname: "/login" })}
|
||||||
|
className={classNames("is-align-items-center", "navbar-item", className)}
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
title={t("primary-navigation.login")}
|
||||||
|
name="sign-in-alt"
|
||||||
|
color="white"
|
||||||
|
className={burgerMode ? "is-size-5" : "is-size-4"}
|
||||||
|
/>
|
||||||
|
{" " + t("primary-navigation.login")}
|
||||||
|
</StyledLogoutButton>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default LoginButton;
|
||||||
86
scm-ui/ui-webapp/src/containers/LogoutButton.tsx
Normal file
86
scm-ui/ui-webapp/src/containers/LogoutButton.tsx
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React, { FC } from "react";
|
||||||
|
import { devices, Icon } from "@scm-manager/ui-components";
|
||||||
|
import { binder, ExtensionPoint } from "@scm-manager/ui-extensions";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { Links } from "@scm-manager/ui-types";
|
||||||
|
import classNames from "classnames";
|
||||||
|
import { useHistory } from "react-router-dom";
|
||||||
|
import styled from "styled-components";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
className?: string;
|
||||||
|
links?: Links;
|
||||||
|
burgerMode: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
const StyledLogoutButton = styled.div`
|
||||||
|
@media screen and (max-width: ${devices.desktop.width}px) {
|
||||||
|
border-top: 1px solid white;
|
||||||
|
margin-top: 1rem;
|
||||||
|
padding-top: 1rem;
|
||||||
|
padding-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width: ${devices.desktop.width}px) {
|
||||||
|
margin-left: 2rem;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const LogoutButton: FC<Props> = ({ burgerMode, links, className }) => {
|
||||||
|
const [t] = useTranslation("commons");
|
||||||
|
const history = useHistory();
|
||||||
|
|
||||||
|
const extensionProps = {
|
||||||
|
links,
|
||||||
|
label: t("primary-navigation.logout"),
|
||||||
|
};
|
||||||
|
|
||||||
|
if (links?.logout) {
|
||||||
|
if (binder.hasExtension("primary-navigation.logout", extensionProps)) {
|
||||||
|
return <ExtensionPoint key="primary-navigation.logout" name="primary-navigation.logout" props={extensionProps} />;
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<StyledLogoutButton
|
||||||
|
data-testid="primary-navigation-logout"
|
||||||
|
onClick={() => history.push({ pathname: "/logout" })}
|
||||||
|
className={classNames("is-align-items-center", "navbar-item", className)}
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
title={t("primary-navigation.logout")}
|
||||||
|
name="sign-out-alt"
|
||||||
|
color="white"
|
||||||
|
className={burgerMode ? "is-size-5" : "is-size-4"}
|
||||||
|
/>
|
||||||
|
{" " + t("primary-navigation.logout")}
|
||||||
|
</StyledLogoutButton>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default LogoutButton;
|
||||||
@@ -48,6 +48,7 @@ import Profile from "./Profile";
|
|||||||
import NamespaceRoot from "../repos/namespaces/containers/NamespaceRoot";
|
import NamespaceRoot from "../repos/namespaces/containers/NamespaceRoot";
|
||||||
import ImportLog from "../repos/importlog/ImportLog";
|
import ImportLog from "../repos/importlog/ImportLog";
|
||||||
import CreateRepositoryRoot from "../repos/containers/CreateRepositoryRoot";
|
import CreateRepositoryRoot from "../repos/containers/CreateRepositoryRoot";
|
||||||
|
import styled from "styled-components";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
me: Me;
|
me: Me;
|
||||||
@@ -55,6 +56,14 @@ type Props = {
|
|||||||
links: Links;
|
links: Links;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type StyledMainProps = {
|
||||||
|
isSmallHeader: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
const StyledMain = styled.div.attrs((props) => ({}))<StyledMainProps>`
|
||||||
|
min-height: calc(100vh - ${(props) => (props.isSmallHeader ? 250 : 210)}px);
|
||||||
|
`;
|
||||||
|
|
||||||
class Main extends React.Component<Props> {
|
class Main extends React.Component<Props> {
|
||||||
render() {
|
render() {
|
||||||
const { authenticated, me, links } = this.props;
|
const { authenticated, me, links } = this.props;
|
||||||
@@ -71,7 +80,7 @@ class Main extends React.Component<Props> {
|
|||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<ErrorBoundary>
|
<ErrorBoundary>
|
||||||
<div className="main">
|
<StyledMain className="main" isSmallHeader={!!links.logout}>
|
||||||
<Switch>
|
<Switch>
|
||||||
<Redirect exact from="/" to={url} />
|
<Redirect exact from="/" to={url} />
|
||||||
<Route exact path="/login" component={Login} />
|
<Route exact path="/login" component={Login} />
|
||||||
@@ -103,11 +112,11 @@ class Main extends React.Component<Props> {
|
|||||||
props={{
|
props={{
|
||||||
me,
|
me,
|
||||||
links,
|
links,
|
||||||
authenticated
|
authenticated,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Switch>
|
</Switch>
|
||||||
</div>
|
</StyledMain>
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
144
scm-ui/ui-webapp/src/containers/NavigationBar.tsx
Normal file
144
scm-ui/ui-webapp/src/containers/NavigationBar.tsx
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* 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 { Links } from "@scm-manager/ui-types";
|
||||||
|
import classNames from "classnames";
|
||||||
|
import styled from "styled-components";
|
||||||
|
import { devices, Logo, PrimaryNavigation } from "@scm-manager/ui-components";
|
||||||
|
import HeaderActions from "./HeaderActions";
|
||||||
|
import Notifications from "./Notifications";
|
||||||
|
|
||||||
|
const StyledMenuBar = styled.div`
|
||||||
|
background-color: transparent !important;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const LogoItem = styled.a`
|
||||||
|
cursor: default !important;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const StyledNavBar = styled.nav`
|
||||||
|
@media screen and (min-width: ${devices.desktop.width - 1}px) {
|
||||||
|
.navbar-burger-actions {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-start .navbar-item {
|
||||||
|
border-bottom: solid 5px transparent;
|
||||||
|
&.is-active {
|
||||||
|
border-bottom: solid 5px #28b1e8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-menu.is-active .navbar-start .navbar-item {
|
||||||
|
border-bottom: none;
|
||||||
|
border-left: solid 5px transparent;
|
||||||
|
&.is-active {
|
||||||
|
border-left: solid 5px #28b1e8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-menu {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-brand {
|
||||||
|
@media screen and (max-width: ${devices.desktop.width - 1}px) {
|
||||||
|
border-bottom: 1px solid white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-menu.is-active .navbar-end .navbar-item {
|
||||||
|
border-left: solid 5px transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-burger {
|
||||||
|
color: #fff !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-item {
|
||||||
|
:hover:not(.logo) {
|
||||||
|
background-color: rgba(10, 10, 10, 0.1) !important;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
color: #fff !important;
|
||||||
|
background-color: transparent !important;
|
||||||
|
}
|
||||||
|
color: #fff;
|
||||||
|
background-color: transparent !important;
|
||||||
|
`;
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
links: Links;
|
||||||
|
};
|
||||||
|
|
||||||
|
const BurgerActionBar: FC = () => (
|
||||||
|
<div className="navbar-burger-actions">
|
||||||
|
<Notifications className="navbar-item" direction="left" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
const NavigationBar: FC<Props> = ({ links }) => {
|
||||||
|
const [burgerActive, setBurgerActive] = useState(false);
|
||||||
|
useEffect(() => {
|
||||||
|
const close = () => {
|
||||||
|
if (burgerActive) {
|
||||||
|
setBurgerActive(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
window.addEventListener("click", close);
|
||||||
|
return () => window.removeEventListener("click", close);
|
||||||
|
}, [burgerActive]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledNavBar className="navbar mb-0 container" role="navigation" aria-label="main navigation">
|
||||||
|
<div className="navbar-brand">
|
||||||
|
<LogoItem className="navbar-item logo">
|
||||||
|
<Logo withText={false} className="image is-32x32" />
|
||||||
|
</LogoItem>
|
||||||
|
<BurgerActionBar />
|
||||||
|
<button
|
||||||
|
role="button"
|
||||||
|
className={classNames("navbar-burger", { "is-active": burgerActive })}
|
||||||
|
aria-expanded="true"
|
||||||
|
onClick={() => setBurgerActive((active) => !active)}
|
||||||
|
>
|
||||||
|
<span aria-hidden="true" />
|
||||||
|
<span aria-hidden="true" />
|
||||||
|
<span aria-hidden="true" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<StyledMenuBar className={classNames("navbar-menu", { "is-active": burgerActive })}>
|
||||||
|
<div className="navbar-start">
|
||||||
|
<PrimaryNavigation links={links} />
|
||||||
|
</div>
|
||||||
|
<div className="navbar-end">
|
||||||
|
<HeaderActions burgerMode={burgerActive} links={links} />
|
||||||
|
</div>
|
||||||
|
</StyledMenuBar>
|
||||||
|
</StyledNavBar>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default NavigationBar;
|
||||||
@@ -33,14 +33,14 @@ import {
|
|||||||
ToastType,
|
ToastType,
|
||||||
Loading,
|
Loading,
|
||||||
DateFromNow,
|
DateFromNow,
|
||||||
devices
|
devices,
|
||||||
} from "@scm-manager/ui-components";
|
} from "@scm-manager/ui-components";
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
import {
|
import {
|
||||||
useClearNotifications,
|
useClearNotifications,
|
||||||
useDismissNotification,
|
useDismissNotification,
|
||||||
useNotifications,
|
useNotifications,
|
||||||
useNotificationSubscription
|
useNotificationSubscription,
|
||||||
} from "@scm-manager/ui-api";
|
} from "@scm-manager/ui-api";
|
||||||
import { Notification, NotificationCollection } from "@scm-manager/ui-types";
|
import { Notification, NotificationCollection } from "@scm-manager/ui-types";
|
||||||
import { useHistory, Link } from "react-router-dom";
|
import { useHistory, Link } from "react-router-dom";
|
||||||
@@ -54,13 +54,14 @@ const Bell = styled(Icon)`
|
|||||||
const Container = styled.div`
|
const Container = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
@media screen and (max-width: ${devices.desktop.width}px) {
|
|
||||||
padding-right: 1rem;
|
|
||||||
}
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const DropDownMenu = styled.div`
|
type DropDownProps = {
|
||||||
|
direction: "left" | "right";
|
||||||
|
};
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||||
|
const DropDownMenu = styled.div.attrs((props) => {})<DropDownProps>`
|
||||||
min-width: 35rem;
|
min-width: 35rem;
|
||||||
|
|
||||||
@media screen and (max-width: ${devices.mobile.width}px) {
|
@media screen and (max-width: ${devices.mobile.width}px) {
|
||||||
@@ -79,7 +80,7 @@ const DropDownMenu = styled.div`
|
|||||||
height: 0;
|
height: 0;
|
||||||
width: 0;
|
width: 0;
|
||||||
top: 0;
|
top: 0;
|
||||||
right: 0.9rem;
|
${(props) => props.direction}: 1.25rem;
|
||||||
border-color: transparent;
|
border-color: transparent;
|
||||||
border-bottom-color: white;
|
border-bottom-color: white;
|
||||||
border-left-color: white;
|
border-left-color: white;
|
||||||
@@ -273,6 +274,9 @@ const BellNotificationContainer = styled.div`
|
|||||||
position: relative;
|
position: relative;
|
||||||
width: 2rem;
|
width: 2rem;
|
||||||
height: 2rem;
|
height: 2rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
type NotificationCounterProps = {
|
type NotificationCounterProps = {
|
||||||
@@ -282,7 +286,7 @@ type NotificationCounterProps = {
|
|||||||
const NotificationCounter = styled.span<NotificationCounterProps>`
|
const NotificationCounter = styled.span<NotificationCounterProps>`
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: -0.5rem;
|
top: -0.5rem;
|
||||||
right: ${props => (props.count < 10 ? "0" : "-0.25")}rem;
|
right: ${(props) => (props.count < 10 ? "0" : "-0.25")}rem;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
type BellNotificationIconProps = {
|
type BellNotificationIconProps = {
|
||||||
@@ -317,7 +321,12 @@ const ErrorBox: FC<{ error: Error | null }> = ({ error }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const Notifications: FC = () => {
|
type NotificationProps = {
|
||||||
|
className?: string;
|
||||||
|
direction?: "left" | "right";
|
||||||
|
};
|
||||||
|
|
||||||
|
const Notifications: FC<NotificationProps> = ({ className, direction = "right" }) => {
|
||||||
const { data, isLoading, error, refetch } = useNotifications();
|
const { data, isLoading, error, refetch } = useNotifications();
|
||||||
const { notifications, remove, clear } = useNotificationSubscription(refetch, data);
|
const { notifications, remove, clear } = useNotificationSubscription(refetch, data);
|
||||||
|
|
||||||
@@ -332,15 +341,21 @@ const Notifications: FC = () => {
|
|||||||
<>
|
<>
|
||||||
<NotificationSubscription notifications={notifications} remove={remove} />
|
<NotificationSubscription notifications={notifications} remove={remove} />
|
||||||
<div
|
<div
|
||||||
className={classNames("is-align-self-flex-end", "dropdown", "is-right", "is-hoverable", {
|
className={classNames(
|
||||||
"is-active": open
|
"dropdown",
|
||||||
})}
|
`is-${direction}`,
|
||||||
onClick={e => e.stopPropagation()}
|
"is-hoverable",
|
||||||
|
{
|
||||||
|
"is-active": open,
|
||||||
|
},
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
>
|
>
|
||||||
<Container className="dropdown-trigger">
|
<Container className="dropdown-trigger">
|
||||||
<BellNotificationIcon data={data} onClick={() => setOpen(o => !o)} />
|
<BellNotificationIcon data={data} onClick={() => setOpen((o) => !o)} />
|
||||||
</Container>
|
</Container>
|
||||||
<DropDownMenu className="dropdown-menu" id="dropdown-menu" role="menu">
|
<DropDownMenu className="dropdown-menu" id="dropdown-menu" role="menu" direction={direction}>
|
||||||
<ErrorBox error={error} />
|
<ErrorBox error={error} />
|
||||||
{isLoading ? <LoadingBox /> : null}
|
{isLoading ? <LoadingBox /> : null}
|
||||||
{data ? <NotificationDropDown data={data} remove={remove} clear={clear} /> : null}
|
{data ? <NotificationDropDown data={data} remove={remove} clear={clear} /> : null}
|
||||||
|
|||||||
@@ -5937,10 +5937,10 @@ bulma-tooltip@^3.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/bulma-tooltip/-/bulma-tooltip-3.0.2.tgz#2cf0abab1de2eba07f9d84eb7f07a8a88819ea92"
|
resolved "https://registry.yarnpkg.com/bulma-tooltip/-/bulma-tooltip-3.0.2.tgz#2cf0abab1de2eba07f9d84eb7f07a8a88819ea92"
|
||||||
integrity sha512-CsT3APjhlZScskFg38n8HYL8oYNUHQtcu4sz6ERarxkUpBRbk9v0h/5KAvXeKapVSn2dp9l7bOGit5SECP8EWQ==
|
integrity sha512-CsT3APjhlZScskFg38n8HYL8oYNUHQtcu4sz6ERarxkUpBRbk9v0h/5KAvXeKapVSn2dp9l7bOGit5SECP8EWQ==
|
||||||
|
|
||||||
bulma@^0.9.0:
|
bulma@^0.9.3:
|
||||||
version "0.9.2"
|
version "0.9.3"
|
||||||
resolved "https://registry.yarnpkg.com/bulma/-/bulma-0.9.2.tgz#340011e119c605f19b8ca886bfea595f1deaf23c"
|
resolved "https://registry.yarnpkg.com/bulma/-/bulma-0.9.3.tgz#ddccb7436ebe3e21bf47afe01d3c43a296b70243"
|
||||||
integrity sha512-e14EF+3VSZ488yL/lJH0tR8mFWiEQVCMi/BQUMi2TGMBOk+zrDg4wryuwm/+dRSHJw0gMawp2tsW7X1JYUCE3A==
|
integrity sha512-0d7GNW1PY4ud8TWxdNcP6Cc8Bu7MxcntD/RRLGWuiw/s0a9P+XlH/6QoOIrmbj6o8WWJzJYhytiu9nFjTszk1g==
|
||||||
|
|
||||||
byline@^5.0.0:
|
byline@^5.0.0:
|
||||||
version "5.0.0"
|
version "5.0.0"
|
||||||
|
|||||||
Reference in New Issue
Block a user