web: People: Optimization of People list rendering

Added new packages: react-window and react-virtualized-auto-sizer;
Added changes to Scrollbar, PageLayout and PageSection
This commit is contained in:
Alexey Safronov 2019-08-07 13:57:54 +03:00
parent f308c669e1
commit b0d5d1ea72
7 changed files with 183 additions and 91 deletions

View File

@ -26,6 +26,8 @@
"react-router": "5.0.1", "react-router": "5.0.1",
"react-router-dom": "5.0.1", "react-router-dom": "5.0.1",
"react-scripts": "3.0.1", "react-scripts": "3.0.1",
"react-virtualized-auto-sizer": "^1.0.2",
"react-window": "^1.8.5",
"reactstrap": "8.0.0", "reactstrap": "8.0.0",
"redux": "4.0.1", "redux": "4.0.1",
"redux-form": "^8.2.4", "redux-form": "^8.2.4",

View File

@ -1,76 +1,151 @@
import React from "react"; import React, { memo, useCallback } from "react";
import { withRouter } from "react-router"; import { withRouter } from "react-router";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { ContentRow, toastr } from "asc-web-components"; import { ContentRow, toastr, Scrollbar } from "asc-web-components";
import UserContent from "./userContent"; import UserContent from "./userContent";
//import config from "../../../../../../package.json"; //import config from "../../../../../../package.json";
import { selectUser, deselectUser, setSelection } from "../../../../../store/people/actions"; import {
import { isUserSelected, getUserStatus, getUserRole, isUserDisabled } from '../../../../../store/people/selectors'; selectUser,
import { isAdmin } from '../../../../../store/auth/selectors'; deselectUser,
setSelection
} from "../../../../../store/people/actions";
import {
isUserSelected,
getUserStatus,
getUserRole,
isUserDisabled
} from "../../../../../store/people/selectors";
import { isAdmin } from "../../../../../store/auth/selectors";
import { FixedSizeList as List, areEqual } from "react-window";
import AutoSizer from "react-virtualized-auto-sizer";
const CustomScrollbars = ({ onScroll, forwardedRef, style, children }) => {
const refSetter = useCallback(scrollbarsRef => {
if (scrollbarsRef) {
forwardedRef(scrollbarsRef.view);
} else {
forwardedRef(null);
}
}, [forwardedRef]);
return (
<Scrollbar
ref={refSetter}
style={{ ...style, overflow: "hidden" }}
onScroll={onScroll}
stype="mediumBlack"
>
{children}
</Scrollbar>
);
};
const CustomScrollbarsVirtualList = React.forwardRef((props, ref) => (
<CustomScrollbars {...props} forwardedRef={ref} />
));
const Row = memo(
({
data,
index,
style,
onContentRowSelect,
history,
settings,
selection,
getUserContextOptions
}) => {
// Data passed to List as "itemData" is available as props.data
const user = data[index];
// console.log("Row user", user);
const contextOptions = getUserContextOptions(user);
return (
<ContentRow
key={user.id}
status={getUserStatus(user)}
data={user}
avatarRole={getUserRole(user)}
avatarSource={user.avatar}
avatarName={user.displayName}
contextOptions={contextOptions}
checked={isUserSelected(selection, user.id)}
onSelect={onContentRowSelect}
style={style}
>
<UserContent user={user} history={history} settings={settings} />
</ContentRow>
);
},
areEqual
);
class SectionBodyContent extends React.PureComponent { class SectionBodyContent extends React.PureComponent {
onEmailSentClick = () => { onEmailSentClick = () => {
toastr.success("Context action: Send e-mail"); toastr.success("Context action: Send e-mail");
} };
onSendMessageClick = () => { onSendMessageClick = () => {
toastr.success("Context action: Send message"); toastr.success("Context action: Send message");
} };
onEditClick = (user) => { onEditClick = user => {
const { history, settings } = this.props; const { history, settings } = this.props;
history.push(`${settings.homepage}/edit/${user.userName}`); history.push(`${settings.homepage}/edit/${user.userName}`);
} };
onChangePasswordClick = () => { onChangePasswordClick = () => {
toastr.success("Context action: Change password"); toastr.success("Context action: Change password");
} };
onChangeEmailClick = () => { onChangeEmailClick = () => {
toastr.success("Context action: Change e-mail"); toastr.success("Context action: Change e-mail");
} };
onDisableClick = () => { onDisableClick = () => {
toastr.success("Context action: Disable"); toastr.success("Context action: Disable");
} };
getUserContextOptions = (user) => { getUserContextOptions = user => {
const options = [
const options = [{ {
key: "key1", key: "key1",
label: "Send e-mail", label: "Send e-mail",
onClick: this.onEmailSentClick onClick: this.onEmailSentClick
}, },
{ {
key: "key2", key: "key2",
label: "Send message", label: "Send message",
onClick: this.onSendMessageClick onClick: this.onSendMessageClick
}, },
{ key: "key3", isSeparator: true }, { key: "key3", isSeparator: true },
{ {
key: "key4", key: "key4",
label: "Edit", label: "Edit",
onClick: this.onEditClick.bind(this, user) onClick: this.onEditClick.bind(this, user)
}, },
{ {
key: "key5", key: "key5",
label: "Change password", label: "Change password",
onClick: this.onChangePasswordClick onClick: this.onChangePasswordClick
}, },
{ {
key: "key6", key: "key6",
label: "Change e-mail", label: "Change e-mail",
onClick: this.onChangeEmailClick onClick: this.onChangeEmailClick
}]; }
];
return [...options, return [
!isUserDisabled(user) ...options,
!isUserDisabled(user)
? { ? {
key: "key7", key: "key7",
label: "Disable", label: "Disable",
onClick: this.onDisableClick onClick: this.onDisableClick
} }
: {} : {}
]; ];
}; };
@ -79,58 +154,46 @@ class SectionBodyContent extends React.PureComponent {
console.log("ContentRow onSelect", checked, user); console.log("ContentRow onSelect", checked, user);
if (checked) { if (checked) {
this.props.selectUser(user); this.props.selectUser(user);
} } else {
else {
this.props.deselectUser(user); this.props.deselectUser(user);
} }
} };
render() { render() {
console.log("Home SectionBodyContent render()"); console.log("Home SectionBodyContent render()");
const { users, isAdmin, selection, history, settings} = this.props; const { users, isAdmin, selection, history, settings } = this.props;
return ( return (
<> <AutoSizer>
{users.map(user => { {({ height, width }) => (
const contextOptions = this.getUserContextOptions(user); <List
return isAdmin ? ( className="List"
<ContentRow height={height}
key={user.id} width={width}
status={getUserStatus(user)} itemSize={46} // ContentRow height
data={user} itemCount={users.length}
avatarRole={getUserRole(user)} itemData={users}
avatarSource={user.avatar} ref={this.refList}
avatarName={user.displayName} outerElementType={CustomScrollbarsVirtualList}
contextOptions={contextOptions} >
checked={isUserSelected(selection, user.id)} {({ data, index, style }) => (
onSelect={this.onContentRowSelect} <Row
> data={data}
<UserContent index={index}
user={user} style={style}
onContentRowSelect={this.onContentRowSelect}
history={history} history={history}
settings={settings} settings={settings}
selection={selection}
getUserContextOptions={this.getUserContextOptions}
/> />
</ContentRow> )}
) : ( </List>
<ContentRow )}
key={user.id} </AutoSizer>
status={getUserStatus(user)}
avatarRole={getUserRole(user)}
avatarSource={user.avatar}
avatarName={user.userName}
>
<UserContent
user={user}
history={history}
settings={settings}
/>
</ContentRow>
);
})}
</>
); );
} }
}; }
const mapStateToProps = state => { const mapStateToProps = state => {
return { return {

View File

@ -110,6 +110,7 @@ class Home extends React.Component {
fontColor={"#999"} fontColor={"#999"}
/> />
<PageLayout <PageLayout
withBodyScroll={false}
articleHeaderContent={<ArticleHeaderContent />} articleHeaderContent={<ArticleHeaderContent />}
articleMainButtonContent={<ArticleMainButtonContent />} articleMainButtonContent={<ArticleMainButtonContent />}
articleBodyContent={<ArticleBodyContent />} articleBodyContent={<ArticleBodyContent />}

View File

@ -6653,7 +6653,7 @@ mem@^4.0.0:
mimic-fn "^2.0.0" mimic-fn "^2.0.0"
p-is-promise "^2.0.0" p-is-promise "^2.0.0"
memoize-one@^5.0.0: "memoize-one@>=3.1.1 <6", memoize-one@^5.0.0:
version "5.0.5" version "5.0.5"
resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.0.5.tgz#8cd3809555723a07684afafcd6f756072ac75d7e" resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.0.5.tgz#8cd3809555723a07684afafcd6f756072ac75d7e"
integrity sha512-ey6EpYv0tEaIbM/nTDOpHciXUvd+ackQrJgEzBwemhZZIWZjcyodqEcrmqDy2BKRTM3a65kKBV4WtLXJDt26SQ== integrity sha512-ey6EpYv0tEaIbM/nTDOpHciXUvd+ackQrJgEzBwemhZZIWZjcyodqEcrmqDy2BKRTM3a65kKBV4WtLXJDt26SQ==
@ -8942,6 +8942,19 @@ react-transition-group@^2.3.1, react-transition-group@^2.6.1:
prop-types "^15.6.2" prop-types "^15.6.2"
react-lifecycles-compat "^3.0.4" react-lifecycles-compat "^3.0.4"
react-virtualized-auto-sizer@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/react-virtualized-auto-sizer/-/react-virtualized-auto-sizer-1.0.2.tgz#a61dd4f756458bbf63bd895a92379f9b70f803bd"
integrity sha512-MYXhTY1BZpdJFjUovvYHVBmkq79szK/k7V3MO+36gJkWGkrXKtyr4vCPtpphaTLRAdDNoYEYFZWE8LjN+PIHNg==
react-window@^1.8.5:
version "1.8.5"
resolved "https://registry.yarnpkg.com/react-window/-/react-window-1.8.5.tgz#a56b39307e79979721021f5d06a67742ecca52d1"
integrity sha512-HeTwlNa37AFa8MDZFZOKcNEkuF2YflA0hpGPiTT9vR7OawEt+GZbfM6wqkBahD3D3pUjIabQYzsnY/BSJbgq6Q==
dependencies:
"@babel/runtime" "^7.0.0"
memoize-one ">=3.1.1 <6"
react@^16.8.6: react@^16.8.6:
version "16.8.6" version "16.8.6"
resolved "https://registry.yarnpkg.com/react/-/react-16.8.6.tgz#ad6c3a9614fd3a4e9ef51117f54d888da01f2bbe" resolved "https://registry.yarnpkg.com/react/-/react-16.8.6.tgz#ad6c3a9614fd3a4e9ef51117f54d888da01f2bbe"

View File

@ -149,7 +149,7 @@ class PageLayout extends React.PureComponent {
} }
{ {
this.state.isSectionBodyAvailable && this.state.isSectionBodyAvailable &&
<SectionBody>{this.state.sectionBodyContent}</SectionBody> <SectionBody withScroll={this.props.withBodyScroll}>{this.state.sectionBodyContent}</SectionBody>
} }
{ {
this.state.isSectionPagingAvailable && this.state.isSectionPagingAvailable &&
@ -177,13 +177,16 @@ PageLayout.propTypes = {
sectionHeaderContent: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.node), PropTypes.node]), sectionHeaderContent: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.node), PropTypes.node]),
sectionFilterContent: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.node), PropTypes.node]), sectionFilterContent: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.node), PropTypes.node]),
sectionBodyContent: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.node), PropTypes.node]), sectionBodyContent: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.node), PropTypes.node]),
sectionPagingContent: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.node), PropTypes.node]) sectionPagingContent: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.node), PropTypes.node]),
withBodyScroll: PropTypes.bool
} }
PageLayout.defaultProps = { PageLayout.defaultProps = {
isBackdropVisible: false, isBackdropVisible: false,
isArticleVisible: false, isArticleVisible: false,
isArticlePinned: false isArticlePinned: false,
withBodyScroll: true
} }
export default PageLayout export default PageLayout

View File

@ -1,6 +1,7 @@
import React from 'react' import React from "react";
import styled from 'styled-components' import PropTypes from "prop-types";
import Scrollbar from '../../scrollbar' import styled from "styled-components";
import Scrollbar from "../../scrollbar";
const StyledSectionBody = styled.div` const StyledSectionBody = styled.div`
margin: 16px 0; margin: 16px 0;
@ -8,17 +9,26 @@ const StyledSectionBody = styled.div`
flex-grow: 1; flex-grow: 1;
`; `;
const SectionBody = React.memo(props => { const SectionBody = React.memo(props => {
console.log("PageLayout SectionBody render"); console.log("PageLayout SectionBody render");
const { children } = props; const { children, withScroll } = props;
return ( return (
<StyledSectionBody> <StyledSectionBody>
<Scrollbar stype="mediumBlack"> {withScroll
{children} ? <Scrollbar stype="mediumBlack">{children}</Scrollbar>
</Scrollbar> : <>{children}</>
}
</StyledSectionBody> </StyledSectionBody>
); );
}); });
export default SectionBody; SectionBody.propTypes = {
withScroll: PropTypes.bool
};
SectionBody.defaultProps = {
withScroll: true
};
export default SectionBody;

View File

@ -2,7 +2,7 @@ import React from 'react'
import { Scrollbars } from 'react-custom-scrollbars'; import { Scrollbars } from 'react-custom-scrollbars';
const Scrollbar = (props) => { const Scrollbar = React.forwardRef((props, ref) => {
//console.log("Scrollbar render"); //console.log("Scrollbar render");
const scrollbarType = { const scrollbarType = {
smallWhite: { smallWhite: {
@ -37,9 +37,9 @@ const Scrollbar = (props) => {
); );
return ( return (
<Scrollbars renderThumbVertical={renderNavThumbVertical} renderThumbHorizontal={renderNavThumbHorizontal} {...props} /> <Scrollbars renderThumbVertical={renderNavThumbVertical} renderThumbHorizontal={renderNavThumbHorizontal} {...props} ref={ref} />
); );
} });
Scrollbar.defaultProps = { Scrollbar.defaultProps = {
stype: "smallBlack" stype: "smallBlack"