From 7fe8b58e7d2aa147c6479efa26d2f4e6a98a8843 Mon Sep 17 00:00:00 2001 From: Eduard Heimbuch Date: Tue, 25 Feb 2020 09:49:23 +0100 Subject: [PATCH] make secondary navigation collapsable // save collapse status in local storage --- .../ui-components/src/navigation/NavLink.tsx | 10 ++- .../ui-components/src/navigation/Section.tsx | 46 ++++++++--- .../src/navigation/SubNavigation.tsx | 11 ++- .../src/repos/containers/RepositoryRoot.tsx | 78 ++++++++++++++----- scm-ui/ui-webapp/src/repos/modules/repos.ts | 16 +++- 5 files changed, 122 insertions(+), 39 deletions(-) diff --git a/scm-ui/ui-components/src/navigation/NavLink.tsx b/scm-ui/ui-components/src/navigation/NavLink.tsx index f87351d496..9a858dc5af 100644 --- a/scm-ui/ui-components/src/navigation/NavLink.tsx +++ b/scm-ui/ui-components/src/navigation/NavLink.tsx @@ -10,6 +10,7 @@ type Props = { label: string; activeOnlyWhenExact?: boolean; activeWhenMatch?: (route: any) => boolean; + collapsed: boolean; }; class NavLink extends React.Component { @@ -23,7 +24,7 @@ class NavLink extends React.Component { } renderLink = (route: any) => { - const { to, icon, label } = this.props; + const { to, icon, label, collapsed } = this.props; let showIcon = null; if (icon) { @@ -36,9 +37,12 @@ class NavLink extends React.Component { return (
  • - + {showIcon} - {label} + {collapsed ? null : label}
  • ); diff --git a/scm-ui/ui-components/src/navigation/Section.tsx b/scm-ui/ui-components/src/navigation/Section.tsx index b6f0542506..7890afe442 100644 --- a/scm-ui/ui-components/src/navigation/Section.tsx +++ b/scm-ui/ui-components/src/navigation/Section.tsx @@ -1,20 +1,44 @@ -import React, { ReactNode } from "react"; +import React, { FC, ReactNode } from "react"; +import Icon from "../Icon"; +import { Button } from "../buttons"; +import styled from "styled-components"; type Props = { label: string; children?: ReactNode; + collapsed?: boolean; + onCollapse?: (newStatus: boolean) => void; }; -class Section extends React.Component { - render() { - const { label, children } = this.props; - return ( -
    -

    {label}

    -
      {children}
    -
    - ); +const SmallButton = styled(Button)` + height: 1.5rem; + width: 1rem; + position: absolute; + right: 1.5rem; + > { + outline: none; } -} +`; + +const MenuLabel = styled.p` + min-height: 2.5rem; +`; + +const Section: FC = ({ label, children, collapsed, onCollapse }) => { + const childrenWithProps = React.Children.map(children, child => React.cloneElement(child, { collapsed: collapsed })); + return ( +
    + + {collapsed ? "" : label} + {onCollapse && ( + + + + )} + +
      {childrenWithProps}
    +
    + ); +}; export default Section; diff --git a/scm-ui/ui-components/src/navigation/SubNavigation.tsx b/scm-ui/ui-components/src/navigation/SubNavigation.tsx index 258658561a..4cb9d2b306 100644 --- a/scm-ui/ui-components/src/navigation/SubNavigation.tsx +++ b/scm-ui/ui-components/src/navigation/SubNavigation.tsx @@ -9,6 +9,8 @@ type Props = { activeOnlyWhenExact?: boolean; activeWhenMatch?: (route: any) => boolean; children?: ReactNode; + collapsed?: boolean; + onCollapsed?: (newStatus: boolean) => void; }; class SubNavigation extends React.Component { @@ -22,7 +24,7 @@ class SubNavigation extends React.Component { } renderLink = (route: any) => { - const { to, icon, label } = this.props; + const { to, icon, label, collapsed } = this.props; let defaultIcon = "fas fa-cog"; if (icon) { @@ -36,8 +38,11 @@ class SubNavigation extends React.Component { return (
  • - - {label} + + {collapsed ? "" : label} {children}
  • diff --git a/scm-ui/ui-webapp/src/repos/containers/RepositoryRoot.tsx b/scm-ui/ui-webapp/src/repos/containers/RepositoryRoot.tsx index e0452416f7..62b482cbaa 100644 --- a/scm-ui/ui-webapp/src/repos/containers/RepositoryRoot.tsx +++ b/scm-ui/ui-webapp/src/repos/containers/RepositoryRoot.tsx @@ -1,12 +1,18 @@ import React from "react"; import { connect } from "react-redux"; -import { Redirect, Route, Switch } from "react-router-dom"; +import { Redirect, Route, Switch, RouteComponentProps } from "react-router-dom"; import { WithTranslation, withTranslation } from "react-i18next"; -import { History } from "history"; import { binder, ExtensionPoint } from "@scm-manager/ui-extensions"; import { Repository } from "@scm-manager/ui-types"; import { ErrorPage, Loading, Navigation, NavLink, Page, Section, SubNavigation } from "@scm-manager/ui-components"; -import { fetchRepoByName, getFetchRepoFailure, getRepository, isFetchRepoPending } from "../modules/repos"; +import { + fetchRepoByName, + getFetchRepoFailure, + getRepository, + isFetchRepoPending, + isRepositoryMenuCollapsed, + switchRepositoryMenuCollapsed +} from "../modules/repos"; import RepositoryDetails from "../components/RepositoryDetails"; import EditRepo from "./EditRepo"; import BranchesOverview from "../branches/containers/BranchesOverview"; @@ -21,29 +27,46 @@ import CodeOverview from "../codeSection/containers/CodeOverview"; import ChangesetView from "./ChangesetView"; import SourceExtensions from "../sources/containers/SourceExtensions"; -type Props = WithTranslation & { - namespace: string; - name: string; - repository: Repository; - loading: boolean; - error: Error; - repoLink: string; - indexLinks: object; +type Props = RouteComponentProps & + WithTranslation & { + namespace: string; + name: string; + repository: Repository; + loading: boolean; + error: Error; + repoLink: string; + indexLinks: object; - // dispatch functions - fetchRepoByName: (link: string, namespace: string, name: string) => void; + // dispatch functions + fetchRepoByName: (link: string, namespace: string, name: string) => void; + }; - // context props - history: History; - match: any; +type State = { + collapsedMenu: boolean; }; -class RepositoryRoot extends React.Component { +class RepositoryRoot extends React.Component { + constructor(props: Props) { + super(props); + this.state = { + collapsedMenu: isRepositoryMenuCollapsed() + }; + } componentDidMount() { const { fetchRepoByName, namespace, name, repoLink } = this.props; fetchRepoByName(repoLink, namespace, name); } + componentDidUpdate() { + if (this.state.collapsedMenu && this.isCollapseForbidden()) { + this.onCollapse(false); + } + } + + isCollapseForbidden= () => { + return this.props.location.pathname.includes("/settings/"); + }; + stripEndingSlash = (url: string) => { if (url.endsWith("/")) { return url.substring(0, url.length - 1); @@ -87,8 +110,13 @@ class RepositoryRoot extends React.Component { return `${url}/changesets`; }; + onCollapse = (newStatus: boolean) => { + this.setState({ collapsedMenu: newStatus }, () => switchRepositoryMenuCollapsed(newStatus)); + }; + render() { const { loading, error, indexLinks, repository, t } = this.props; + const { collapsedMenu } = this.state; if (error) { return ( @@ -119,7 +147,7 @@ class RepositoryRoot extends React.Component { return (
    -
    +
    @@ -169,9 +197,13 @@ class RepositoryRoot extends React.Component {
    -
    +
    -
    +
    this.onCollapse(!collapsedMenu) : undefined} + collapsed={collapsedMenu} + > { activeOnlyWhenExact={false} /> - + this.onCollapse(false)} + > diff --git a/scm-ui/ui-webapp/src/repos/modules/repos.ts b/scm-ui/ui-webapp/src/repos/modules/repos.ts index dbea9c24fd..ac9e8fd81b 100644 --- a/scm-ui/ui-webapp/src/repos/modules/repos.ts +++ b/scm-ui/ui-webapp/src/repos/modules/repos.ts @@ -155,7 +155,12 @@ export function fetchRepoFailure(namespace: string, name: string, error: Error): // create repo -export function createRepo(link: string, repository: Repository, initRepository: boolean, callback?: (repo: Repository) => void) { +export function createRepo( + link: string, + repository: Repository, + initRepository: boolean, + callback?: (repo: Repository) => void +) { return function(dispatch: any) { dispatch(createRepoPending()); const repoLink = initRepository ? link + "?initialize=true" : link; @@ -436,3 +441,12 @@ export function getPermissionsLink(state: object, namespace: string, name: strin const repo = getRepository(state, namespace, name); return repo && repo._links ? repo._links.permissions.href : undefined; } + +const REPOSITORY_NAVIGATION_COLLAPSED = "repository-menu-collapsed"; + +export function isRepositoryMenuCollapsed() { + return localStorage.getItem(REPOSITORY_NAVIGATION_COLLAPSED) === "true"; +} +export function switchRepositoryMenuCollapsed(newStatus: boolean) { + localStorage.setItem(REPOSITORY_NAVIGATION_COLLAPSED, String(newStatus)); +}