From 7fe8b58e7d2aa147c6479efa26d2f4e6a98a8843 Mon Sep 17 00:00:00 2001 From: Eduard Heimbuch Date: Tue, 25 Feb 2020 09:49:23 +0100 Subject: [PATCH 01/24] 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)); +} From eee6cad1d350f3d5e760bf0fda9fb1973429503c Mon Sep 17 00:00:00 2001 From: Eduard Heimbuch Date: Tue, 25 Feb 2020 17:15:23 +0100 Subject: [PATCH 02/24] make repository navigation fixed // add title for collapsed navigation items --- .../ui-components/src/navigation/NavLink.tsx | 5 ++-- .../ui-components/src/navigation/Section.tsx | 30 ++++++++++++++----- .../src/navigation/SubNavigation.tsx | 5 ++-- .../repos/components/RepositoryNavLink.tsx | 1 + .../src/repos/containers/RepositoryRoot.tsx | 4 +++ scm-ui/ui-webapp/src/repos/modules/repos.ts | 8 ++--- 6 files changed, 37 insertions(+), 16 deletions(-) diff --git a/scm-ui/ui-components/src/navigation/NavLink.tsx b/scm-ui/ui-components/src/navigation/NavLink.tsx index 9a858dc5af..bff7e71e1c 100644 --- a/scm-ui/ui-components/src/navigation/NavLink.tsx +++ b/scm-ui/ui-components/src/navigation/NavLink.tsx @@ -11,6 +11,7 @@ type Props = { activeOnlyWhenExact?: boolean; activeWhenMatch?: (route: any) => boolean; collapsed: boolean; + title?: string; }; class NavLink extends React.Component { @@ -24,7 +25,7 @@ class NavLink extends React.Component { } renderLink = (route: any) => { - const { to, icon, label, collapsed } = this.props; + const { to, icon, label, collapsed, title } = this.props; let showIcon = null; if (icon) { @@ -36,7 +37,7 @@ class NavLink extends React.Component { } return ( -
  • +
  • void; }; +const SectionContainer = styled.div` + position: ${props => (props.scrollPositionY > 210 ? "fixed" : "absolute")}; + top: ${props => props.scrollPositionY > 210 && "4.5rem"}; + width: ${props => (props.collapsed ? "5.5rem" : "20.5rem")}; +`; + const SmallButton = styled(Button)` height: 1.5rem; width: 1rem; position: absolute; right: 1.5rem; - > { - outline: none; - } `; const MenuLabel = styled.p` @@ -25,19 +27,31 @@ const MenuLabel = styled.p` `; const Section: FC = ({ label, children, collapsed, onCollapse }) => { + const [scrollPositionY, setScrollPositionY] = useState(0); + + useEffect(() => { + window.addEventListener("scroll", () => setScrollPositionY(window.pageYOffset)); + + return () => { + window.removeEventListener("scroll", () => setScrollPositionY(window.pageYOffset)); + }; + }, []); + const childrenWithProps = React.Children.map(children, child => React.cloneElement(child, { collapsed: collapsed })); + const arrowIcon = collapsed ? : ; + return ( -
    + {collapsed ? "" : label} {onCollapse && ( - + {arrowIcon} )}
      {childrenWithProps}
    -
    + ); }; diff --git a/scm-ui/ui-components/src/navigation/SubNavigation.tsx b/scm-ui/ui-components/src/navigation/SubNavigation.tsx index 4cb9d2b306..e254975e0c 100644 --- a/scm-ui/ui-components/src/navigation/SubNavigation.tsx +++ b/scm-ui/ui-components/src/navigation/SubNavigation.tsx @@ -11,6 +11,7 @@ type Props = { children?: ReactNode; collapsed?: boolean; onCollapsed?: (newStatus: boolean) => void; + title?: string }; class SubNavigation extends React.Component { @@ -24,7 +25,7 @@ class SubNavigation extends React.Component { } renderLink = (route: any) => { - const { to, icon, label, collapsed } = this.props; + const { to, icon, label, collapsed, title } = this.props; let defaultIcon = "fas fa-cog"; if (icon) { @@ -37,7 +38,7 @@ class SubNavigation extends React.Component { } return ( -
  • +
  • boolean; activeOnlyWhenExact: boolean; icon?: string; + title?: string; }; /** diff --git a/scm-ui/ui-webapp/src/repos/containers/RepositoryRoot.tsx b/scm-ui/ui-webapp/src/repos/containers/RepositoryRoot.tsx index 62b482cbaa..96f941c21a 100644 --- a/scm-ui/ui-webapp/src/repos/containers/RepositoryRoot.tsx +++ b/scm-ui/ui-webapp/src/repos/containers/RepositoryRoot.tsx @@ -209,6 +209,7 @@ class RepositoryRoot extends React.Component { to={`${url}/info`} icon="fas fa-info-circle" label={t("repositoryRoot.menu.informationNavLink")} + title={t("repositoryRoot.menu.informationNavLink")} /> { label={t("repositoryRoot.menu.branchesNavLink")} activeWhenMatch={this.matchesBranches} activeOnlyWhenExact={false} + title={t("repositoryRoot.menu.branchesNavLink")} /> { label={t("repositoryRoot.menu.sourcesNavLink")} activeWhenMatch={this.matchesCode} activeOnlyWhenExact={false} + title={t("repositoryRoot.menu.sourcesNavLink")} /> this.onCollapse(false)} + title={t("repositoryRoot.menu.settingsNavLink")} > diff --git a/scm-ui/ui-webapp/src/repos/modules/repos.ts b/scm-ui/ui-webapp/src/repos/modules/repos.ts index ac9e8fd81b..5dde4e4104 100644 --- a/scm-ui/ui-webapp/src/repos/modules/repos.ts +++ b/scm-ui/ui-webapp/src/repos/modules/repos.ts @@ -442,11 +442,11 @@ export function getPermissionsLink(state: object, namespace: string, name: strin return repo && repo._links ? repo._links.permissions.href : undefined; } -const REPOSITORY_NAVIGATION_COLLAPSED = "repository-menu-collapsed"; +const REPOSITORY_MENU_COLLAPSED = "repository-menu-collapsed"; export function isRepositoryMenuCollapsed() { - return localStorage.getItem(REPOSITORY_NAVIGATION_COLLAPSED) === "true"; + return localStorage.getItem(REPOSITORY_MENU_COLLAPSED) === "true"; } -export function switchRepositoryMenuCollapsed(newStatus: boolean) { - localStorage.setItem(REPOSITORY_NAVIGATION_COLLAPSED, String(newStatus)); +export function switchRepositoryMenuCollapsed(status: boolean) { + localStorage.setItem(REPOSITORY_MENU_COLLAPSED, String(status)); } From 72328159000d4cee7d91aaec7da68bce5294a381 Mon Sep 17 00:00:00 2001 From: Eduard Heimbuch Date: Wed, 26 Feb 2020 10:41:39 +0100 Subject: [PATCH 03/24] use react context to toggle collapsable repository menu --- .../src/__snapshots__/storyshots.test.ts.snap | 48 ++-- .../ui-components/src/navigation/NavLink.tsx | 2 +- .../ui-components/src/navigation/Section.tsx | 16 +- .../src/navigation/SubNavigation.tsx | 3 +- .../src/repos/containers/RepositoryRoot.tsx | 222 +++++++++--------- scm-ui/ui-webapp/src/repos/modules/repos.ts | 6 + 6 files changed, 160 insertions(+), 137 deletions(-) diff --git a/scm-ui/ui-components/src/__snapshots__/storyshots.test.ts.snap b/scm-ui/ui-components/src/__snapshots__/storyshots.test.ts.snap index 0ec9bb0c76..4fefa33757 100644 --- a/scm-ui/ui-components/src/__snapshots__/storyshots.test.ts.snap +++ b/scm-ui/ui-components/src/__snapshots__/storyshots.test.ts.snap @@ -484,7 +484,7 @@ exports[`Storyshots DateFromNow Default 1`] = ` exports[`Storyshots Diff Binaries 1`] = `
  • + +
    -
    - + + ); } } diff --git a/scm-ui/ui-webapp/src/repos/modules/repos.ts b/scm-ui/ui-webapp/src/repos/modules/repos.ts index 5dde4e4104..b2c1150fc3 100644 --- a/scm-ui/ui-webapp/src/repos/modules/repos.ts +++ b/scm-ui/ui-webapp/src/repos/modules/repos.ts @@ -3,6 +3,7 @@ import * as types from "../../modules/types"; import { Action, Repository, RepositoryCollection } from "@scm-manager/ui-types"; import { isPending } from "../../modules/pending"; import { getFailure } from "../../modules/failure"; +import React from "react"; export const FETCH_REPOS = "scm/repos/FETCH_REPOS"; export const FETCH_REPOS_PENDING = `${FETCH_REPOS}_${types.PENDING_SUFFIX}`; @@ -450,3 +451,8 @@ export function isRepositoryMenuCollapsed() { export function switchRepositoryMenuCollapsed(status: boolean) { localStorage.setItem(REPOSITORY_MENU_COLLAPSED, String(status)); } + +export const RepositoryContext = React.createContext({ + menuCollapsed: isRepositoryMenuCollapsed(), + toggleMenuCollapsed: () => {} +}); From 119236a2276e48eeca737455fa79c74968aac931 Mon Sep 17 00:00:00 2001 From: Eduard Heimbuch Date: Wed, 26 Feb 2020 15:10:56 +0100 Subject: [PATCH 04/24] move MenuContext to ui-components --- scm-ui/ui-components/src/contexts/MenuContext.ts | 15 +++++++++++++++ scm-ui/ui-components/src/contexts/index.ts | 3 +++ scm-ui/ui-components/src/index.ts | 1 + scm-ui/ui-webapp/src/repos/modules/repos.ts | 14 -------------- 4 files changed, 19 insertions(+), 14 deletions(-) create mode 100644 scm-ui/ui-components/src/contexts/MenuContext.ts create mode 100644 scm-ui/ui-components/src/contexts/index.ts diff --git a/scm-ui/ui-components/src/contexts/MenuContext.ts b/scm-ui/ui-components/src/contexts/MenuContext.ts new file mode 100644 index 0000000000..fcd83ce801 --- /dev/null +++ b/scm-ui/ui-components/src/contexts/MenuContext.ts @@ -0,0 +1,15 @@ +import React from "react"; + +const MENU_COLLAPSED = "secondary-menu-collapsed"; + +export const MenuContext = React.createContext({ + menuCollapsed: localStorage.getItem(MENU_COLLAPSED) === "true", + setMenuCollapsed: (collapsed: boolean) => {} +}); + +export function isMenuCollapsed() { + return localStorage.getItem(MENU_COLLAPSED) === "true"; +} +export function storeMenuCollapsed(status: boolean) { + localStorage.setItem(MENU_COLLAPSED, String(status)); +} diff --git a/scm-ui/ui-components/src/contexts/index.ts b/scm-ui/ui-components/src/contexts/index.ts new file mode 100644 index 0000000000..56f9c0126a --- /dev/null +++ b/scm-ui/ui-components/src/contexts/index.ts @@ -0,0 +1,3 @@ +// @create-index + +export { MenuContext, storeMenuCollapsed, isMenuCollapsed } from "./MenuContext"; diff --git a/scm-ui/ui-components/src/index.ts b/scm-ui/ui-components/src/index.ts index 46ccbb48ec..566bcf38aa 100644 --- a/scm-ui/ui-components/src/index.ts +++ b/scm-ui/ui-components/src/index.ts @@ -67,6 +67,7 @@ export * from "./navigation"; export * from "./repos"; export * from "./table"; export * from "./toast"; +export * from "./contexts"; export { File, diff --git a/scm-ui/ui-webapp/src/repos/modules/repos.ts b/scm-ui/ui-webapp/src/repos/modules/repos.ts index b2c1150fc3..38da52611f 100644 --- a/scm-ui/ui-webapp/src/repos/modules/repos.ts +++ b/scm-ui/ui-webapp/src/repos/modules/repos.ts @@ -442,17 +442,3 @@ 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_MENU_COLLAPSED = "repository-menu-collapsed"; - -export function isRepositoryMenuCollapsed() { - return localStorage.getItem(REPOSITORY_MENU_COLLAPSED) === "true"; -} -export function switchRepositoryMenuCollapsed(status: boolean) { - localStorage.setItem(REPOSITORY_MENU_COLLAPSED, String(status)); -} - -export const RepositoryContext = React.createContext({ - menuCollapsed: isRepositoryMenuCollapsed(), - toggleMenuCollapsed: () => {} -}); From ed53745d9f083868b80c3a348b583708a6aa035a Mon Sep 17 00:00:00 2001 From: Eduard Heimbuch Date: Wed, 26 Feb 2020 15:45:24 +0100 Subject: [PATCH 05/24] make secondary navigation also for user, group and administration collapsable --- .../ui-webapp/src/admin/containers/Admin.tsx | 221 +++++++++++------- .../src/groups/containers/SingleGroup.tsx | 133 +++++++---- .../src/repos/containers/RepositoryRoot.tsx | 160 ++++++------- .../src/users/containers/SingleUser.tsx | 132 +++++++---- 4 files changed, 400 insertions(+), 246 deletions(-) diff --git a/scm-ui/ui-webapp/src/admin/containers/Admin.tsx b/scm-ui/ui-webapp/src/admin/containers/Admin.tsx index 23b1a58c65..82b564a607 100644 --- a/scm-ui/ui-webapp/src/admin/containers/Admin.tsx +++ b/scm-ui/ui-webapp/src/admin/containers/Admin.tsx @@ -2,11 +2,18 @@ import React from "react"; import { connect } from "react-redux"; import { compose } from "redux"; import { WithTranslation, withTranslation } from "react-i18next"; -import { Redirect, Route, Switch } from "react-router-dom"; -import { History } from "history"; +import { Redirect, Route, RouteComponentProps, Switch } from "react-router-dom"; import { ExtensionPoint } from "@scm-manager/ui-extensions"; import { Links } from "@scm-manager/ui-types"; -import { Navigation, NavLink, Page, Section, SubNavigation } from "@scm-manager/ui-components"; +import { + Navigation, + NavLink, + Page, + Section, + SubNavigation, + isMenuCollapsed, + MenuContext +} from "@scm-manager/ui-components"; import { getAvailablePluginsLink, getInstalledPluginsLink, getLinks } from "../../modules/indexResource"; import AdminDetails from "./AdminDetails"; import PluginsOverview from "../plugins/containers/PluginsOverview"; @@ -14,18 +21,44 @@ import GlobalConfig from "./GlobalConfig"; import RepositoryRoles from "../roles/containers/RepositoryRoles"; import SingleRepositoryRole from "../roles/containers/SingleRepositoryRole"; import CreateRepositoryRole from "../roles/containers/CreateRepositoryRole"; +import { storeMenuCollapsed } from "@scm-manager/ui-components/src"; -type Props = WithTranslation & { - links: Links; - availablePluginsLink: string; - installedPluginsLink: string; +type Props = RouteComponentProps & + WithTranslation & { + links: Links; + availablePluginsLink: string; + installedPluginsLink: string; + }; - // context objects - match: any; - history: History; +type State = { + menuCollapsed: boolean; + setMenuCollapsed: (collapsed: boolean) => void; }; -class Admin extends React.Component { +class Admin extends React.Component { + constructor(props: Props) { + super(props); + + this.state = { + menuCollapsed: isMenuCollapsed(), + setMenuCollapsed: (collapsed: boolean) => this.setState({ menuCollapsed: collapsed }) + }; + } + + componentDidUpdate() { + if (this.state.menuCollapsed && this.isCollapseForbidden()) { + this.setState({ menuCollapsed: false }); + } + } + + isCollapseForbidden = () => { + return this.props.location.pathname.includes("/settings/") || this.props.location.pathname.includes("/plugins/"); + }; + + onCollapseAdminMenu = (collapsed: boolean) => { + this.setState({ menuCollapsed: collapsed }, () => storeMenuCollapsed(collapsed)); + }; + stripEndingSlash = (url: string) => { if (url.endsWith("/")) { if (url.includes("role")) { @@ -48,6 +81,7 @@ class Admin extends React.Component { render() { const { links, availablePluginsLink, installedPluginsLink, t } = this.props; + const { menuCollapsed } = this.state; const url = this.matchedUrl(); const extensionProps = { @@ -56,82 +90,99 @@ class Admin extends React.Component { }; return ( - -
    -
    - - - - - - } - /> - } - /> - } - /> - } - /> - } - /> - } /> - } - /> - } /> - - -
    -
    - -
    - - {(availablePluginsLink || installedPluginsLink) && ( - - {installedPluginsLink && ( - - )} - {availablePluginsLink && ( - - )} - - )} - + +
    +
    + + + + + + } /> - - - - - -
    -
    + } + /> + } + /> + } + /> + } + /> + } /> + } + /> + } /> + + +
    +
    + +
    this.onCollapseAdminMenu(!menuCollapsed)} + collapsed={menuCollapsed} + > + + {(availablePluginsLink || installedPluginsLink) && ( + + {installedPluginsLink && ( + + )} + {availablePluginsLink && ( + + )} + + )} + + + + + + +
    +
    +
    -
    -
    + + ); } } diff --git a/scm-ui/ui-webapp/src/groups/containers/SingleGroup.tsx b/scm-ui/ui-webapp/src/groups/containers/SingleGroup.tsx index 90e15b5e43..c80a3225c9 100644 --- a/scm-ui/ui-webapp/src/groups/containers/SingleGroup.tsx +++ b/scm-ui/ui-webapp/src/groups/containers/SingleGroup.tsx @@ -1,38 +1,73 @@ import React from "react"; import { connect } from "react-redux"; -import { Route } from "react-router-dom"; +import { Route, RouteComponentProps } from "react-router-dom"; import { WithTranslation, withTranslation } from "react-i18next"; -import { History } from "history"; import { ExtensionPoint } from "@scm-manager/ui-extensions"; import { Group } from "@scm-manager/ui-types"; -import { ErrorPage, Loading, Navigation, NavLink, Page, Section, SubNavigation } from "@scm-manager/ui-components"; +import { + ErrorPage, + Loading, + Navigation, + NavLink, + Page, + Section, + SubNavigation, + isMenuCollapsed, + MenuContext +} from "@scm-manager/ui-components"; import { getGroupsLink } from "../../modules/indexResource"; import { fetchGroupByName, getFetchGroupFailure, getGroupByName, isFetchGroupPending } from "../modules/groups"; import { Details } from "./../components/table"; import { EditGroupNavLink, SetPermissionsNavLink } from "./../components/navLinks"; import EditGroup from "./EditGroup"; import SetPermissions from "../../permissions/components/SetPermissions"; +import { storeMenuCollapsed } from "@scm-manager/ui-components/src"; -type Props = WithTranslation & { - name: string; - group: Group; - loading: boolean; - error: Error; - groupLink: string; +type Props = RouteComponentProps & + WithTranslation & { + name: string; + group: Group; + loading: boolean; + error: Error; + groupLink: string; - // dispatcher functions - fetchGroupByName: (p1: string, p2: string) => void; + // dispatcher functions + fetchGroupByName: (p1: string, p2: string) => void; + }; - // context objects - match: any; - history: History; +type State = { + menuCollapsed: boolean; + setMenuCollapsed: (collapsed: boolean) => void; }; -class SingleGroup extends React.Component { +class SingleGroup extends React.Component { + constructor(props: Props) { + super(props); + + this.state = { + menuCollapsed: isMenuCollapsed(), + setMenuCollapsed: (collapsed: boolean) => this.setState({ menuCollapsed: collapsed }) + }; + } + componentDidMount() { this.props.fetchGroupByName(this.props.groupLink, this.props.name); } + componentDidUpdate() { + if (this.state.menuCollapsed && this.isCollapseForbidden()) { + this.setState({ menuCollapsed: false }); + } + } + + isCollapseForbidden = () => { + return this.props.location.pathname.includes("/settings/"); + }; + + onCollapseGroupMenu = (collapsed: boolean) => { + this.setState({ menuCollapsed: collapsed }, () => storeMenuCollapsed(collapsed)); + }; + stripEndingSlash = (url: string) => { if (url.endsWith("/")) { return url.substring(0, url.length - 2); @@ -46,6 +81,7 @@ class SingleGroup extends React.Component { render() { const { t, loading, error, group } = this.props; + const { menuCollapsed } = this.state; if (error) { return ; @@ -63,33 +99,48 @@ class SingleGroup extends React.Component { }; return ( - -
    -
    -
    } /> - } /> - } - /> - + + +
    +
    +
    } /> + } /> + } + /> + +
    +
    + +
    this.onCollapseGroupMenu(!menuCollapsed)} + collapsed={menuCollapsed} + > + + + + + + + +
    +
    +
    -
    - -
    - - - - - - - -
    -
    -
    -
    - + + ); } } diff --git a/scm-ui/ui-webapp/src/repos/containers/RepositoryRoot.tsx b/scm-ui/ui-webapp/src/repos/containers/RepositoryRoot.tsx index 06184842ff..c436219e8b 100644 --- a/scm-ui/ui-webapp/src/repos/containers/RepositoryRoot.tsx +++ b/scm-ui/ui-webapp/src/repos/containers/RepositoryRoot.tsx @@ -4,16 +4,19 @@ import { Redirect, Route, Switch, RouteComponentProps } from "react-router-dom"; import { WithTranslation, withTranslation } from "react-i18next"; 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, - isRepositoryMenuCollapsed, - switchRepositoryMenuCollapsed, - RepositoryContext -} from "../modules/repos"; + ErrorPage, + Loading, + Navigation, + NavLink, + Page, + Section, + SubNavigation, + MenuContext, + storeMenuCollapsed, + isMenuCollapsed +} from "@scm-manager/ui-components"; +import { fetchRepoByName, getFetchRepoFailure, getRepository, isFetchRepoPending } from "../modules/repos"; import RepositoryDetails from "../components/RepositoryDetails"; import EditRepo from "./EditRepo"; import BranchesOverview from "../branches/containers/BranchesOverview"; @@ -43,24 +46,28 @@ type Props = RouteComponentProps & }; type State = { - collapsedRepositoryMenu: boolean; + menuCollapsed: boolean; + setMenuCollapsed: (collapsed: boolean) => void; }; class RepositoryRoot extends React.Component { constructor(props: Props) { super(props); + this.state = { - collapsedRepositoryMenu: isRepositoryMenuCollapsed() + menuCollapsed: isMenuCollapsed(), + setMenuCollapsed: (collapsed: boolean) => this.setState({ menuCollapsed: collapsed }) }; } + componentDidMount() { const { fetchRepoByName, namespace, name, repoLink } = this.props; fetchRepoByName(repoLink, namespace, name); } componentDidUpdate() { - if (this.state.collapsedRepositoryMenu && this.isCollapseForbidden()) { - this.onCollapseRepositoryMenu(false); + if (this.state.menuCollapsed && this.isCollapseForbidden()) { + this.setState({ menuCollapsed: false }); } } @@ -111,13 +118,13 @@ class RepositoryRoot extends React.Component { return `${url}/changesets`; }; - onCollapseRepositoryMenu = (status: boolean) => { - this.setState({ collapsedRepositoryMenu: status }, () => switchRepositoryMenuCollapsed(status)); + onCollapseRepositoryMenu = (collapsed: boolean) => { + this.setState({ menuCollapsed: collapsed }, () => storeMenuCollapsed(collapsed)); }; render() { const { loading, error, indexLinks, repository, t } = this.props; - const { collapsedRepositoryMenu } = this.state; + const { menuCollapsed } = this.state; if (error) { return ( @@ -135,7 +142,7 @@ class RepositoryRoot extends React.Component { repository, url, indexLinks, - collapsedRepositoryMenu + collapsedRepositoryMenu: menuCollapsed }; const redirectUrlFactory = binder.getExtension("repository.redirect", this.props); @@ -147,15 +154,10 @@ class RepositoryRoot extends React.Component { } return ( - this.setState({ collapsedRepositoryMenu: !this.state.collapsedRepositoryMenu }) - }} - > - -
    -
    + +
    +
    + @@ -204,61 +206,59 @@ class RepositoryRoot extends React.Component { } /> -
    -
    - -
    this.onCollapseRepositoryMenu(!collapsedRepositoryMenu) - } - collapsed={collapsedRepositoryMenu} - > - - - - - - - - - - -
    -
    -
    +
    -
    - +
    + +
    this.onCollapseRepositoryMenu(!menuCollapsed) + } + collapsed={menuCollapsed} + > + + + + + + + + + + +
    +
    +
    +
    + ); } } diff --git a/scm-ui/ui-webapp/src/users/containers/SingleUser.tsx b/scm-ui/ui-webapp/src/users/containers/SingleUser.tsx index 8b0826ee6b..9d0dd2b9c5 100644 --- a/scm-ui/ui-webapp/src/users/containers/SingleUser.tsx +++ b/scm-ui/ui-webapp/src/users/containers/SingleUser.tsx @@ -1,10 +1,20 @@ import React from "react"; import { connect } from "react-redux"; -import { Route } from "react-router-dom"; +import { Route, RouteComponentProps } from "react-router-dom"; import { History } from "history"; import { ExtensionPoint } from "@scm-manager/ui-extensions"; import { User } from "@scm-manager/ui-types"; -import { ErrorPage, Loading, Navigation, NavLink, Page, Section, SubNavigation } from "@scm-manager/ui-components"; +import { + ErrorPage, + Loading, + Navigation, + NavLink, + Page, + Section, + SubNavigation, + MenuContext, + isMenuCollapsed +} from "@scm-manager/ui-components"; import { Details } from "./../components/table"; import EditUser from "./EditUser"; import { fetchUserByName, getFetchUserFailure, getUserByName, isFetchUserPending } from "../modules/users"; @@ -13,27 +23,45 @@ import { WithTranslation, withTranslation } from "react-i18next"; import { getUsersLink } from "../../modules/indexResource"; import SetUserPassword from "../components/SetUserPassword"; import SetPermissions from "../../permissions/components/SetPermissions"; +import { storeMenuCollapsed } from "@scm-manager/ui-components/src"; -type Props = WithTranslation & { - name: string; - user: User; - loading: boolean; - error: Error; - usersLink: string; +type Props = RouteComponentProps & + WithTranslation & { + name: string; + user: User; + loading: boolean; + error: Error; + usersLink: string; - // dispatcher function - fetchUserByName: (p1: string, p2: string) => void; + // dispatcher function + fetchUserByName: (p1: string, p2: string) => void; + }; - // context objects - match: any; - history: History; +type State = { + menuCollapsed: boolean; + setMenuCollapsed: (collapsed: boolean) => void; }; -class SingleUser extends React.Component { +class SingleUser extends React.Component { + constructor(props: Props) { + super(props); + + this.state = { + menuCollapsed: isMenuCollapsed(), + setMenuCollapsed: (collapsed: boolean) => this.setState({ menuCollapsed: collapsed }) + }; + } + componentDidMount() { this.props.fetchUserByName(this.props.usersLink, this.props.name); } + componentDidUpdate() { + if (this.state.menuCollapsed && this.isCollapseForbidden()) { + this.setState({ menuCollapsed: false }); + } + } + stripEndingSlash = (url: string) => { if (url.endsWith("/")) { return url.substring(0, url.length - 2); @@ -41,12 +69,21 @@ class SingleUser extends React.Component { return url; }; + isCollapseForbidden = () => { + return this.props.location.pathname.includes("/settings/"); + }; + + onCollapseUserMenu = (collapsed: boolean) => { + this.setState({ menuCollapsed: collapsed }, () => storeMenuCollapsed(collapsed)); + }; + matchedUrl = () => { return this.stripEndingSlash(this.props.match.url); }; render() { const { t, loading, error, user } = this.props; + const { menuCollapsed } = this.state; if (error) { return ; @@ -64,33 +101,48 @@ class SingleUser extends React.Component { }; return ( - -
    -
    -
    } /> - } /> - } /> - } - /> - + + +
    +
    +
    } /> + } /> + } /> + } + /> + +
    +
    + +
    this.onCollapseUserMenu(!menuCollapsed)} + collapsed={menuCollapsed} + > + + + + + + + +
    +
    +
    -
    - -
    - - - - - - - -
    -
    -
    -
    - + + ); } } From f8720087a14e6f81cff9c207d0af33d0831c32ef Mon Sep 17 00:00:00 2001 From: Eduard Heimbuch Date: Thu, 27 Feb 2020 09:27:02 +0100 Subject: [PATCH 06/24] refactor --- scm-ui/ui-webapp/src/containers/Profile.tsx | 106 +++++++++++++----- .../src/groups/containers/SingleGroup.tsx | 6 +- .../src/users/containers/SingleUser.tsx | 7 +- 3 files changed, 86 insertions(+), 33 deletions(-) diff --git a/scm-ui/ui-webapp/src/containers/Profile.tsx b/scm-ui/ui-webapp/src/containers/Profile.tsx index 4852993afd..68350deca5 100644 --- a/scm-ui/ui-webapp/src/containers/Profile.tsx +++ b/scm-ui/ui-webapp/src/containers/Profile.tsx @@ -1,24 +1,62 @@ import React from "react"; -import { Route, withRouter } from "react-router-dom"; +import { Route, RouteComponentProps, withRouter } from "react-router-dom"; import { getMe } from "../modules/auth"; import { compose } from "redux"; import { connect } from "react-redux"; import { WithTranslation, withTranslation } from "react-i18next"; import { Me } from "@scm-manager/ui-types"; -import { ErrorPage, Navigation, NavLink, Page, Section, SubNavigation } from "@scm-manager/ui-components"; +import { + ErrorPage, + isMenuCollapsed, + MenuContext, + Navigation, + NavLink, + Page, + Section, + SubNavigation +} from "@scm-manager/ui-components"; import ChangeUserPassword from "./ChangeUserPassword"; import ProfileInfo from "./ProfileInfo"; import { ExtensionPoint } from "@scm-manager/ui-extensions"; +import { storeMenuCollapsed } from "@scm-manager/ui-components/src"; -type Props = WithTranslation & { - me: Me; +type Props = RouteComponentProps & + WithTranslation & { + me: Me; - // Context props - match: any; + // Context props + match: any; + }; + +type State = { + menuCollapsed: boolean; + setMenuCollapsed: (collapsed: boolean) => void; }; -type State = {}; class Profile extends React.Component { + constructor(props: Props) { + super(props); + + this.state = { + menuCollapsed: isMenuCollapsed(), + setMenuCollapsed: (collapsed: boolean) => this.setState({ menuCollapsed: collapsed }) + }; + } + + componentDidUpdate() { + if (this.state.menuCollapsed && this.isCollapseForbidden()) { + this.setState({ menuCollapsed: false }); + } + } + + isCollapseForbidden = () => { + return this.props.location.pathname.includes("/settings/"); + }; + + onCollapseProfileMenu = (collapsed: boolean) => { + this.setState({ menuCollapsed: collapsed }, () => storeMenuCollapsed(collapsed)); + }; + stripEndingSlash = (url: string) => { if (url.endsWith("/")) { return url.substring(0, url.length - 2); @@ -34,6 +72,7 @@ class Profile extends React.Component { const url = this.matchedUrl(); const { me, t } = this.props; + const { menuCollapsed } = this.state; if (!me) { return ( @@ -54,26 +93,41 @@ class Profile extends React.Component { }; return ( - -
    -
    - } /> - } /> - + + +
    +
    + } /> + } /> + +
    +
    + +
    this.onCollapseProfileMenu(!menuCollapsed)} + collapsed={menuCollapsed} + > + + + + + +
    +
    +
    -
    - -
    - - - - - -
    -
    -
    -
    - + + ); } } diff --git a/scm-ui/ui-webapp/src/groups/containers/SingleGroup.tsx b/scm-ui/ui-webapp/src/groups/containers/SingleGroup.tsx index c80a3225c9..ddcb387ca7 100644 --- a/scm-ui/ui-webapp/src/groups/containers/SingleGroup.tsx +++ b/scm-ui/ui-webapp/src/groups/containers/SingleGroup.tsx @@ -6,14 +6,14 @@ import { ExtensionPoint } from "@scm-manager/ui-extensions"; import { Group } from "@scm-manager/ui-types"; import { ErrorPage, + isMenuCollapsed, Loading, + MenuContext, Navigation, NavLink, Page, Section, - SubNavigation, - isMenuCollapsed, - MenuContext + SubNavigation } from "@scm-manager/ui-components"; import { getGroupsLink } from "../../modules/indexResource"; import { fetchGroupByName, getFetchGroupFailure, getGroupByName, isFetchGroupPending } from "../modules/groups"; diff --git a/scm-ui/ui-webapp/src/users/containers/SingleUser.tsx b/scm-ui/ui-webapp/src/users/containers/SingleUser.tsx index 9d0dd2b9c5..d2718650bd 100644 --- a/scm-ui/ui-webapp/src/users/containers/SingleUser.tsx +++ b/scm-ui/ui-webapp/src/users/containers/SingleUser.tsx @@ -1,19 +1,18 @@ import React from "react"; import { connect } from "react-redux"; import { Route, RouteComponentProps } from "react-router-dom"; -import { History } from "history"; import { ExtensionPoint } from "@scm-manager/ui-extensions"; import { User } from "@scm-manager/ui-types"; import { ErrorPage, + isMenuCollapsed, Loading, + MenuContext, Navigation, NavLink, Page, Section, - SubNavigation, - MenuContext, - isMenuCollapsed + SubNavigation } from "@scm-manager/ui-components"; import { Details } from "./../components/table"; import EditUser from "./EditUser"; From f9b680f548ece6959ca7fe1fad4f12067d5a919a Mon Sep 17 00:00:00 2001 From: Eduard Heimbuch Date: Thu, 27 Feb 2020 10:00:29 +0100 Subject: [PATCH 07/24] fix responsiveness for section --- .../ui-components/src/navigation/Section.tsx | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/scm-ui/ui-components/src/navigation/Section.tsx b/scm-ui/ui-components/src/navigation/Section.tsx index 2214b974c5..c4af45e203 100644 --- a/scm-ui/ui-components/src/navigation/Section.tsx +++ b/scm-ui/ui-components/src/navigation/Section.tsx @@ -1,33 +1,33 @@ -import React, {FC, ReactChild, useEffect, useState} from "react"; +import React, { FC, ReactElement, useEffect, useState } from "react"; import { Button } from "../buttons"; import styled from "styled-components"; type Props = { label: string; - children?: ReactChild; + children: ReactElement[]; collapsed?: boolean; onCollapse?: (newStatus: boolean) => void; }; +type StylingProps = { + scrollPositionY: number; + collapsed: boolean; +}; const SectionContainer = styled.div` -// @ts-ignore - position: ${props => (props.scrollPositionY > 210 ? "fixed" : "absolute")}; - // @ts-ignore - top: ${props => props.scrollPositionY > 210 && "4.5rem"}; - // @ts-ignore - width: ${props => (props.collapsed ? "5.5rem" : "20.5rem")}; + position: ${(props: StylingProps) => (props.scrollPositionY > 210 && window.innerWidth > 770 ? "fixed" : "inherit")}; + top: ${(props: StylingProps) => props.scrollPositionY > 210 && window.innerWidth > 770 && "4.5rem"}; + width: ${(props: StylingProps) => (props.collapsed ? "5.5rem" : "20.5rem")}; `; const SmallButton = styled(Button)` height: 1.5rem; - width: 1rem; - position: absolute; - right: 1.5rem; `; const MenuLabel = styled.p` min-height: 2.5rem; + display: flex; + justify-content: ${(props: StylingProps) => (props.collapsed ? "center" : "space-between")}; `; const Section: FC = ({ label, children, collapsed, onCollapse }) => { @@ -41,14 +41,14 @@ const Section: FC = ({ label, children, collapsed, onCollapse }) => { }; }, []); - // @ts-ignore - const childrenWithProps = React.Children.map(children, (child: ReactChild) => React.cloneElement(child, { collapsed: collapsed })); + const childrenWithProps = React.Children.map(children, (child: ReactElement) => + React.cloneElement(child, { collapsed: collapsed }) + ); const arrowIcon = collapsed ? : ; return ( - // @ts-ignore - - + + {collapsed ? "" : label} {onCollapse && ( onCollapse(!collapsed)}> From d59778c3bd24e2bd41f715ef878a876643f007b7 Mon Sep 17 00:00:00 2001 From: Eduard Heimbuch Date: Thu, 27 Feb 2020 16:41:19 +0100 Subject: [PATCH 08/24] fix secondary navigation responsiveness --- scm-ui/ui-components/src/navigation/Section.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scm-ui/ui-components/src/navigation/Section.tsx b/scm-ui/ui-components/src/navigation/Section.tsx index c4af45e203..951ac71a3a 100644 --- a/scm-ui/ui-components/src/navigation/Section.tsx +++ b/scm-ui/ui-components/src/navigation/Section.tsx @@ -27,7 +27,7 @@ const SmallButton = styled(Button)` const MenuLabel = styled.p` min-height: 2.5rem; display: flex; - justify-content: ${(props: StylingProps) => (props.collapsed ? "center" : "space-between")}; + justify-content: ${(props: { collapsed: boolean }) => (props.collapsed ? "center" : "space-between")}; `; const Section: FC = ({ label, children, collapsed, onCollapse }) => { @@ -48,10 +48,10 @@ const Section: FC = ({ label, children, collapsed, onCollapse }) => { return ( - + {collapsed ? "" : label} {onCollapse && ( - onCollapse(!collapsed)}> + onCollapse(!collapsed)}> {arrowIcon} )} From 2d038327d0ceb7ba899c18c8c92419619b921b75 Mon Sep 17 00:00:00 2001 From: Eduard Heimbuch Date: Fri, 28 Feb 2020 13:15:27 +0100 Subject: [PATCH 09/24] fix re-render bug for changesets --- scm-ui/ui-components/src/repos/DiffFile.tsx | 26 +++++++++----- .../src/repos/containers/Changesets.tsx | 34 ++++++++++--------- .../src/repos/containers/RepositoryRoot.tsx | 14 +++++--- .../src/repos/modules/changesets.test.ts | 34 +++++++++++++++++++ .../ui-webapp/src/repos/modules/changesets.ts | 14 ++++++-- 5 files changed, 89 insertions(+), 33 deletions(-) diff --git a/scm-ui/ui-components/src/repos/DiffFile.tsx b/scm-ui/ui-components/src/repos/DiffFile.tsx index edf1d8c79a..90a4239971 100644 --- a/scm-ui/ui-components/src/repos/DiffFile.tsx +++ b/scm-ui/ui-components/src/repos/DiffFile.tsx @@ -10,6 +10,7 @@ import Icon from "../Icon"; import { Change, ChangeEvent, DiffObjectProps, File, Hunk as HunkType } from "./DiffTypes"; import TokenizedDiffView from "./TokenizedDiffView"; import DiffButton from "./DiffButton"; +import { MenuContext } from "@scm-manager/ui-components"; const EMPTY_ANNOTATION_FACTORY = {}; @@ -100,10 +101,13 @@ class DiffFile extends React.Component { } }; - toggleSideBySide = () => { - this.setState(state => ({ - sideBySide: !state.sideBySide - })); + toggleSideBySide = (callback: (collapsed: boolean) => void) => { + this.setState( + state => ({ + sideBySide: !state.sideBySide + }), + () => callback(this.state.sideBySide ? this.state.sideBySide : false) + ); }; setCollapse = (collapsed: boolean) => { @@ -259,11 +263,15 @@ class DiffFile extends React.Component { file.hunks && file.hunks.length > 0 ? ( - + + {({ setMenuCollapsed }) => ( + this.toggleSideBySide(() => setMenuCollapsed(sideBySide ? true : false))} + /> + )} + {fileControls} diff --git a/scm-ui/ui-webapp/src/repos/containers/Changesets.tsx b/scm-ui/ui-webapp/src/repos/containers/Changesets.tsx index a5ca949cee..9b58662805 100644 --- a/scm-ui/ui-webapp/src/repos/containers/Changesets.tsx +++ b/scm-ui/ui-webapp/src/repos/containers/Changesets.tsx @@ -1,7 +1,7 @@ import React from "react"; import { connect } from "react-redux"; import { compose } from "redux"; -import { withRouter } from "react-router-dom"; +import { withRouter, RouteComponentProps } from "react-router-dom"; import { WithTranslation, withTranslation } from "react-i18next"; import { Branch, Changeset, PagedCollection, Repository } from "@scm-manager/ui-types"; import { @@ -20,23 +20,21 @@ import { selectListAsCollection } from "../modules/changesets"; -type Props = WithTranslation & { - repository: Repository; - branch: Branch; - page: number; +type Props = RouteComponentProps & + WithTranslation & { + repository: Repository; + branch: Branch; + page: number; - // State props - changesets: Changeset[]; - list: PagedCollection; - loading: boolean; - error: Error; + // State props + changesets: Changeset[]; + list: PagedCollection; + loading: boolean; + error: Error; - // Dispatch props - fetchChangesets: (p1: Repository, p2: Branch, p3: number) => void; - - // context props - match: any; -}; + // Dispatch props + fetchChangesets: (p1: Repository, p2: Branch, p3: number) => void; + }; class Changesets extends React.Component { componentDidMount() { @@ -44,6 +42,10 @@ class Changesets extends React.Component { fetchChangesets(repository, branch, page); } + shouldComponentUpdate(nextProps: Readonly, nextState: Readonly<{}>, nextContext: any): boolean { + return this.props.changesets !== nextProps.changesets; + } + render() { const { changesets, loading, error, t } = this.props; diff --git a/scm-ui/ui-webapp/src/repos/containers/RepositoryRoot.tsx b/scm-ui/ui-webapp/src/repos/containers/RepositoryRoot.tsx index c436219e8b..3d65d16cb2 100644 --- a/scm-ui/ui-webapp/src/repos/containers/RepositoryRoot.tsx +++ b/scm-ui/ui-webapp/src/repos/containers/RepositoryRoot.tsx @@ -138,10 +138,14 @@ class RepositoryRoot extends React.Component { const url = this.matchedUrl(); - const extensionProps = { + const extensionProps: any = { repository, url, - indexLinks, + indexLinks + }; + + const navExtensionProps = { + ...extensionProps, collapsedRepositoryMenu: menuCollapsed }; @@ -217,7 +221,7 @@ class RepositoryRoot extends React.Component { } collapsed={menuCollapsed} > - + { activeOnlyWhenExact={false} title={t("repositoryRoot.menu.sourcesNavLink")} /> - + { > - + diff --git a/scm-ui/ui-webapp/src/repos/modules/changesets.test.ts b/scm-ui/ui-webapp/src/repos/modules/changesets.test.ts index b0d9675c40..45191b1e69 100644 --- a/scm-ui/ui-webapp/src/repos/modules/changesets.test.ts +++ b/scm-ui/ui-webapp/src/repos/modules/changesets.test.ts @@ -581,6 +581,31 @@ describe("changesets", () => { ]); }); + it("should return always the same changeset array for the given parameters", () => { + const state = { + changesets: { + "foo/bar": { + byId: { + id2: { + id: "id2" + }, + id1: { + id: "id1" + } + }, + byBranch: { + "": { + entries: ["id1", "id2"] + } + } + } + } + }; + const one = getChangesets(state, repository); + const two = getChangesets(state, repository); + expect(one).toBe(two); + }); + it("should return true, when fetching changesets is pending", () => { const state = { pending: { @@ -639,5 +664,14 @@ describe("changesets", () => { expect(collection.page).toBe(1); expect(collection.pageTotal).toBe(10); }); + + it("should return always the same empty object", () => { + const state = { + changesets: {} + }; + const one = selectListAsCollection(state, repository); + const two = selectListAsCollection(state, repository); + expect(one).toBe(two); + }); }); }); diff --git a/scm-ui/ui-webapp/src/repos/modules/changesets.ts b/scm-ui/ui-webapp/src/repos/modules/changesets.ts index 5be98cf4df..3e75a1668d 100644 --- a/scm-ui/ui-webapp/src/repos/modules/changesets.ts +++ b/scm-ui/ui-webapp/src/repos/modules/changesets.ts @@ -3,6 +3,7 @@ import { apiClient, urls } from "@scm-manager/ui-components"; import { isPending } from "../../modules/pending"; import { getFailure } from "../../modules/failure"; import { Action, Branch, PagedCollection, Repository } from "@scm-manager/ui-types"; +import memoizeOne from "memoize-one"; export const FETCH_CHANGESETS = "scm/repos/FETCH_CHANGESETS"; export const FETCH_CHANGESETS_PENDING = `${FETCH_CHANGESETS}_${PENDING_SUFFIX}`; @@ -254,10 +255,15 @@ export function getChangesets(state: object, repository: Repository, branch?: Br return null; } + return collectChangesets(stateRoot, changesets); +} +const mapChangesets = (stateRoot, changesets) => { return changesets.entries.map((id: string) => { return stateRoot.byId[id]; }); -} +}; + +const collectChangesets = memoizeOne(mapChangesets); export function getChangeset(state: object, repository: Repository, id: string) { const key = createItemId(repository); @@ -291,6 +297,8 @@ export function getFetchChangesetsFailure(state: object, repository: Repository, return getFailure(state, FETCH_CHANGESETS, createItemId(repository, branch)); } +const EMPTY = {}; + const selectList = (state: object, repository: Repository, branch?: Branch) => { const repoId = createItemId(repository); @@ -302,7 +310,7 @@ const selectList = (state: object, repository: Repository, branch?: Branch) => { return repoState.byBranch[branchName]; } } - return {}; + return EMPTY; }; const selectListEntry = (state: object, repository: Repository, branch?: Branch): object => { @@ -310,7 +318,7 @@ const selectListEntry = (state: object, repository: Repository, branch?: Branch) if (list.entry) { return list.entry; } - return {}; + return EMPTY; }; export const selectListAsCollection = (state: object, repository: Repository, branch?: Branch): PagedCollection => { From 4df33fcc7396a924ce890c259670280daae27792 Mon Sep 17 00:00:00 2001 From: Eduard Heimbuch Date: Fri, 28 Feb 2020 14:58:56 +0100 Subject: [PATCH 10/24] fix styling --- scm-ui/ui-components/src/navigation/Section.tsx | 2 ++ scm-ui/ui-components/src/repos/DiffFile.tsx | 2 +- .../src/repos/containers/RepositoryRoot.tsx | 12 ++++-------- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/scm-ui/ui-components/src/navigation/Section.tsx b/scm-ui/ui-components/src/navigation/Section.tsx index 951ac71a3a..5b1ba58aa3 100644 --- a/scm-ui/ui-components/src/navigation/Section.tsx +++ b/scm-ui/ui-components/src/navigation/Section.tsx @@ -21,6 +21,8 @@ const SectionContainer = styled.div` `; const SmallButton = styled(Button)` + padding-left: 1rem; + padding-right: 1rem; height: 1.5rem; `; diff --git a/scm-ui/ui-components/src/repos/DiffFile.tsx b/scm-ui/ui-components/src/repos/DiffFile.tsx index 90a4239971..09abcc6e5a 100644 --- a/scm-ui/ui-components/src/repos/DiffFile.tsx +++ b/scm-ui/ui-components/src/repos/DiffFile.tsx @@ -268,7 +268,7 @@ class DiffFile extends React.Component { this.toggleSideBySide(() => setMenuCollapsed(sideBySide ? true : false))} + onClick={() => this.toggleSideBySide(() => setMenuCollapsed(true))} /> )} diff --git a/scm-ui/ui-webapp/src/repos/containers/RepositoryRoot.tsx b/scm-ui/ui-webapp/src/repos/containers/RepositoryRoot.tsx index 3d65d16cb2..58eb18391d 100644 --- a/scm-ui/ui-webapp/src/repos/containers/RepositoryRoot.tsx +++ b/scm-ui/ui-webapp/src/repos/containers/RepositoryRoot.tsx @@ -141,11 +141,7 @@ class RepositoryRoot extends React.Component { const extensionProps: any = { repository, url, - indexLinks - }; - - const navExtensionProps = { - ...extensionProps, + indexLinks, collapsedRepositoryMenu: menuCollapsed }; @@ -221,7 +217,7 @@ class RepositoryRoot extends React.Component { } collapsed={menuCollapsed} > - + { activeOnlyWhenExact={false} title={t("repositoryRoot.menu.sourcesNavLink")} /> - + { > - + From 9705347c1fa97f97d023630e2cd70c94c3ae2c64 Mon Sep 17 00:00:00 2001 From: Eduard Heimbuch Date: Fri, 28 Feb 2020 15:14:13 +0100 Subject: [PATCH 11/24] update storyshots after merge --- .../src/__snapshots__/storyshots.test.ts.snap | 104 +++++++++--------- 1 file changed, 52 insertions(+), 52 deletions(-) diff --git a/scm-ui/ui-components/src/__snapshots__/storyshots.test.ts.snap b/scm-ui/ui-components/src/__snapshots__/storyshots.test.ts.snap index 790c2cdc94..cb24897d56 100644 --- a/scm-ui/ui-components/src/__snapshots__/storyshots.test.ts.snap +++ b/scm-ui/ui-components/src/__snapshots__/storyshots.test.ts.snap @@ -484,7 +484,7 @@ exports[`Storyshots DateFromNow Default 1`] = ` exports[`Storyshots Diff Binaries 1`] = `