diff --git a/products/ASC.People/Client/src/components/pages/GroupAction/index.js b/products/ASC.People/Client/src/components/pages/GroupAction/index.js index f13c7252e5..efcb924864 100644 --- a/products/ASC.People/Client/src/components/pages/GroupAction/index.js +++ b/products/ASC.People/Client/src/components/pages/GroupAction/index.js @@ -39,7 +39,7 @@ class GroupAction extends React.Component { {group || !match.params.groupId ? } articleMainButtonContent={} articleBodyContent={} diff --git a/products/ASC.People/Client/src/components/pages/Home/Section/Body/index.js b/products/ASC.People/Client/src/components/pages/Home/Section/Body/index.js index 7e73448d3d..a81b234394 100644 --- a/products/ASC.People/Client/src/components/pages/Home/Section/Body/index.js +++ b/products/ASC.People/Client/src/components/pages/Home/Section/Body/index.js @@ -39,6 +39,7 @@ import { deleteUser } from "../../../../../store/services/api"; import { isMobileOnly } from "react-device-detect"; +import isEqual from "lodash/isEqual"; class SectionBodyContent extends React.PureComponent { constructor(props) { @@ -461,6 +462,19 @@ class SectionBodyContent extends React.PureComponent { }); }; + needForUpdate = (currentProps, nextProps) => { + if (currentProps.checked !== nextProps.checked) { + return true; + } + if (currentProps.status !== nextProps.status) { + return true; + } + if (!isEqual(currentProps.data, nextProps.data)) { + return true; + } + return false; + }; + render() { console.log("Home SectionBodyContent render()"); const { users, viewer, selection, history, settings, t } = this.props; @@ -468,7 +482,7 @@ class SectionBodyContent extends React.PureComponent { return users.length > 0 ? ( <> - + {users.map(user => { const contextOptions = this.getUserContextOptions(user, viewer); const contextOptionsProps = !contextOptions.length @@ -494,6 +508,7 @@ class SectionBodyContent extends React.PureComponent { onSelect={this.onContentRowSelect} {...checkedProps} {...contextOptionsProps} + needForUpdate={this.needForUpdate} > + ) : ( } articleMainButtonContent={} articleBodyContent={} diff --git a/products/ASC.People/Client/src/store/people/actions.js b/products/ASC.People/Client/src/store/people/actions.js index ee70c18d83..fb23deaf06 100644 --- a/products/ASC.People/Client/src/store/people/actions.js +++ b/products/ASC.People/Client/src/store/people/actions.js @@ -14,6 +14,7 @@ import { PAGE_COUNT, EmployeeStatus } from "../../helpers/constants"; +import unionBy from 'lodash/unionBy'; export const SET_GROUPS = "SET_GROUPS"; export const SET_USERS = "SET_USERS"; @@ -190,11 +191,14 @@ function fetchPeopleByFilter(dispatch, filter) { } export function updateUserStatus(status, userIds) { - return dispatch => { + return (dispatch, getState) => { return api.updateUserStatus(status, userIds).then(users => { - users.forEach(user => { - dispatch(setUser(user)); - }); + const { people } = getState(); + const { users: currentUsers } = people; + + const newUsers = unionBy(users, currentUsers, "id"); + + dispatch(setUsers(newUsers)); }); }; } diff --git a/web/ASC.Web.Components/package.json b/web/ASC.Web.Components/package.json index 771aa202f7..16835801f4 100644 --- a/web/ASC.Web.Components/package.json +++ b/web/ASC.Web.Components/package.json @@ -1,6 +1,6 @@ { "name": "asc-web-components", - "version": "1.0.177", + "version": "1.0.178", "description": "Ascensio System SIA component library", "license": "AGPL-3.0", "main": "dist/asc-web-components.js", diff --git a/web/ASC.Web.Components/src/components/page-layout/index.js b/web/ASC.Web.Components/src/components/page-layout/index.js index 17bebb2b2d..01692fd786 100644 --- a/web/ASC.Web.Components/src/components/page-layout/index.js +++ b/web/ASC.Web.Components/src/components/page-layout/index.js @@ -45,8 +45,8 @@ class PageLayout extends React.PureComponent { isArticleAvailable = isArticleHeaderAvailable || isArticleMainButtonAvailable || isArticleBodyAvailable, isSectionHeaderAvailable = !!props.sectionHeaderContent, isSectionFilterAvailable = !!props.sectionFilterContent, - isSectionBodyAvailable = !!props.sectionBodyContent, isSectionPagingAvailable = !!props.sectionPagingContent, + isSectionBodyAvailable = !!props.sectionBodyContent || isSectionFilterAvailable || isSectionPagingAvailable, isSectionAvailable = isSectionHeaderAvailable || isSectionFilterAvailable || isSectionBodyAvailable || isSectionPagingAvailable || isArticleAvailable, isBackdropAvailable = isArticleAvailable; @@ -154,17 +154,19 @@ class PageLayout extends React.PureComponent { {this.state.isSectionHeaderAvailable && ( {this.state.sectionHeaderContent} )} - {this.state.isSectionFilterAvailable && ( - {this.state.sectionFilterContent} - )} + {this.state.isSectionBodyAvailable && ( - + + {this.state.isSectionFilterAvailable && ( + {this.state.sectionFilterContent} + )} {this.state.sectionBodyContent} + {this.state.isSectionPagingAvailable && ( + {this.state.sectionPagingContent} + )} )} - {this.state.isSectionPagingAvailable && ( - {this.state.sectionPagingContent} - )} + {this.state.isArticleAvailable && ( (props.pinned ? "none" : "block")}; + } +`; + const SectionBody = React.memo(props => { //console.log("PageLayout SectionBody render"); - const { children, withScroll } = props; + const { children, withScroll, pinned } = props; return ( {withScroll ? ( - {children} + + {children} + + ) : ( - <>{children} + <> + {children} + + )} ); @@ -29,6 +45,7 @@ SectionBody.displayName = "SectionBody"; SectionBody.propTypes = { withScroll: PropTypes.bool, + pinned: PropTypes.bool, children: PropTypes.oneOfType([ PropTypes.arrayOf(PropTypes.node), PropTypes.node, @@ -37,7 +54,8 @@ SectionBody.propTypes = { }; SectionBody.defaultProps = { - withScroll: true + withScroll: true, + pinned: false }; export default SectionBody; diff --git a/web/ASC.Web.Components/src/components/page-layout/sub-components/section-filter.js b/web/ASC.Web.Components/src/components/page-layout/sub-components/section-filter.js index e64205ac59..742b9b9348 100644 --- a/web/ASC.Web.Components/src/components/page-layout/sub-components/section-filter.js +++ b/web/ASC.Web.Components/src/components/page-layout/sub-components/section-filter.js @@ -2,7 +2,7 @@ import React from "react"; import styled from "styled-components"; const StyledSectionFilter = styled.div` - margin: 16px 0 0; + margin: 0 0 16px; `; const SectionFilter = React.memo(props => { diff --git a/web/ASC.Web.Components/src/components/page-layout/sub-components/section-header.js b/web/ASC.Web.Components/src/components/page-layout/sub-components/section-header.js index b9d6497a1b..86fcce6657 100644 --- a/web/ASC.Web.Components/src/components/page-layout/sub-components/section-header.js +++ b/web/ASC.Web.Components/src/components/page-layout/sub-components/section-header.js @@ -4,6 +4,7 @@ import styled from "styled-components"; const StyledSectionHeader = styled.div` border-bottom: 1px solid #eceef1; height: 56px; + margin-right: 16px; `; const SectionHeader = React.memo(props => { diff --git a/web/ASC.Web.Components/src/components/page-layout/sub-components/section-paging.js b/web/ASC.Web.Components/src/components/page-layout/sub-components/section-paging.js index ec583cb7cb..b7bd5f98fb 100644 --- a/web/ASC.Web.Components/src/components/page-layout/sub-components/section-paging.js +++ b/web/ASC.Web.Components/src/components/page-layout/sub-components/section-paging.js @@ -2,7 +2,7 @@ import React from "react"; import styled from "styled-components"; const StyledSectionPaging = styled.div` - margin: 0 0 16px; + margin: 16px 0 0; `; const SectionPaging = React.memo(props => { diff --git a/web/ASC.Web.Components/src/components/page-layout/sub-components/section-toggler.js b/web/ASC.Web.Components/src/components/page-layout/sub-components/section-toggler.js index 0dfd1d3a4c..277e47592c 100644 --- a/web/ASC.Web.Components/src/components/page-layout/sub-components/section-toggler.js +++ b/web/ASC.Web.Components/src/components/page-layout/sub-components/section-toggler.js @@ -6,6 +6,8 @@ import { Icons } from "../../icons"; const StyledSectionToggler = styled.div` height: 64px; + position: fixed; + bottom: 0; display: none; @media ${tablet} { @@ -19,6 +21,7 @@ const StyledSectionToggler = styled.div` box-shadow: 0px 5px 20px rgba(0, 0, 0, 0.13); border-radius: 48px; cursor: pointer; + background: #fff; } `; diff --git a/web/ASC.Web.Components/src/components/page-layout/sub-components/section.js b/web/ASC.Web.Components/src/components/page-layout/sub-components/section.js index a3364e2b98..829004541f 100644 --- a/web/ASC.Web.Components/src/components/page-layout/sub-components/section.js +++ b/web/ASC.Web.Components/src/components/page-layout/sub-components/section.js @@ -2,7 +2,7 @@ import React from "react"; import styled from "styled-components"; const StyledSection = styled.section` - padding: 0 16px; + padding: 0 0 0 16px; flex-grow: 1; display: flex; flex-direction: column; diff --git a/web/ASC.Web.Components/src/components/row-container/README.md b/web/ASC.Web.Components/src/components/row-container/README.md index 36bf011b2b..2420f29ef0 100644 --- a/web/ASC.Web.Components/src/components/row-container/README.md +++ b/web/ASC.Web.Components/src/components/row-container/README.md @@ -18,7 +18,8 @@ Container for rows #### Properties -| Props | Type | Required | Values | Default | Description | -| -------------- | -------- | :------: | ------ | ------- | --------------------------------------------------------------- | -| `manualHeight` | `string` | - | | - | Allows you to set fixed block height for Row | -| `itemHeight` | `number` | - | | 50 | Height of one Row element. Required for scroll to work properly | +| Props | Type | Required | Values | Default | Description | +| ---------------- | -------- | :------: | ------ | ------- | --------------------------------------------------------------- | +| `manualHeight` | `string` | - | | - | Allows you to set fixed block height for Row | +| `itemHeight` | `number` | - | | 50 | Height of one Row element. Required for scroll to work properly | +| `useReactWindow` | `bool` | - | | true | Use react-window for efficiently rendering large lists | diff --git a/web/ASC.Web.Components/src/components/row-container/index.js b/web/ASC.Web.Components/src/components/row-container/index.js index d5ca8b2b49..df4dbaaa36 100644 --- a/web/ASC.Web.Components/src/components/row-container/index.js +++ b/web/ASC.Web.Components/src/components/row-container/index.js @@ -8,7 +8,9 @@ import AutoSizer from 'react-virtualized-auto-sizer'; import ContextMenu from '../context-menu'; const StyledRowContainer = styled.div` - height: ${props => props.manualHeight ? props.manualHeight : '100%'}; + height: ${props => props.useReactWindow ? props.manualHeight ? props.manualHeight : '100%' : 'auto'}; + margin: 16px 0; + position: relative; `; class RowContainer extends React.PureComponent { @@ -49,7 +51,7 @@ class RowContainer extends React.PureComponent { }, areEqual); render() { - const { manualHeight, itemHeight, children } = this.props; + const { manualHeight, itemHeight, children, useReactWindow } = this.props; const renderList = ({ height, width }) => ( - - {renderList} - - + + { useReactWindow ? ( + {renderList} + ) : ( + children.map((item, index) => ( +
+ {item} +
+ )) + )} +
); } @@ -79,11 +87,13 @@ class RowContainer extends React.PureComponent { RowContainer.propTypes = { itemHeight: PropTypes.number, manualHeight: PropTypes.string, - children: PropTypes.any.isRequired + children: PropTypes.any.isRequired, + useReactWindow: PropTypes.bool }; RowContainer.defaultProps = { itemHeight: 50, + useReactWindow: true }; export default RowContainer; \ No newline at end of file diff --git a/web/ASC.Web.Components/src/components/row-container/row-container.stories.js b/web/ASC.Web.Components/src/components/row-container/row-container.stories.js index c3fc9d03d2..10564ea6d9 100644 --- a/web/ASC.Web.Components/src/components/row-container/row-container.stories.js +++ b/web/ASC.Web.Components/src/components/row-container/row-container.stories.js @@ -1,5 +1,7 @@ import React from 'react'; import { storiesOf } from '@storybook/react'; +import withReadme from 'storybook-readme/with-readme'; +import Readme from './README.md'; import Section from '../../../.storybook/decorators/section'; import RowContainer from '.'; import Row from '../row'; @@ -45,6 +47,7 @@ const fillFakeData = (n) => { const fakeData = fillFakeData(20); storiesOf('Components|RowContainer', module) + .addDecorator(withReadme(Readme)) .add('base', () => { return (
@@ -71,7 +74,7 @@ storiesOf('Components|RowContainer', module) {user.isHead ? Head of department - :
+ :
} {user.department} {user.mobilePhone} diff --git a/web/ASC.Web.Components/src/components/row/README.md b/web/ASC.Web.Components/src/components/row/README.md index d6fe587143..4e6037716a 100644 --- a/web/ASC.Web.Components/src/components/row/README.md +++ b/web/ASC.Web.Components/src/components/row/README.md @@ -29,4 +29,5 @@ Displays content as row. | `element` | `element` | - | | ` ` | Required to host some component. It has a fixed order of location, if the Checkbox component is specified, then it follows, otherwise it occupies the first position. If there is no value, the occupied space is distributed among the other child elements. | | `contextOptions` | `array` | - | | ` ` | Required to host the ContextMenuButton component. It is always located near the right border of the container, regardless of the contents of the child elements. If there is no value, the occupied space is distributed among the other child elements. | | `data` | `object` | - | | ` ` | Current row item information. | -| `onSelect` | `function` | - | | ` ` | Event when selecting row element. Returns data value. | \ No newline at end of file +| `onSelect` | `function` | - | | ` ` | Event when selecting row element. Returns data value. | +| `needForUpdate` | `function` | - | | ` ` | Custom shouldComponentUpdate function | \ No newline at end of file diff --git a/web/ASC.Web.Components/src/components/row/index.js b/web/ASC.Web.Components/src/components/row/index.js index 339c199a2d..963064a6ce 100644 --- a/web/ASC.Web.Components/src/components/row/index.js +++ b/web/ASC.Web.Components/src/components/row/index.js @@ -1,17 +1,17 @@ -import React from 'react' -import styled from 'styled-components' -import PropTypes from 'prop-types' - -import Checkbox from '../checkbox' -import ContextMenuButton from '../context-menu-button' -import { tablet } from '../../utils/device'; +import React from "react"; +import styled from "styled-components"; +import PropTypes from "prop-types"; +import isEqual from "lodash/isEqual"; +import Checkbox from "../checkbox"; +import ContextMenuButton from "../context-menu-button"; +import { tablet } from "../../utils/device"; const StyledRow = styled.div` cursor: default; - + min-height: 50px; width: 100%; - border-bottom: 1px solid #ECEEF1; + border-bottom: 1px solid #eceef1; display: flex; flex-direction: row; @@ -55,38 +55,64 @@ const StyledOptionButton = styled.div` `; // eslint-disable-next-line react/display-name -const Row = props => { - const changeCheckbox = (e) => { - props.onSelect && props.onSelect(e.target.checked, props.data); - }; - const getOptions = () => props.contextOptions; - //console.log("Row render"); - const { checked, element, children, contextOptions } = props; +class Row extends React.Component { + shouldComponentUpdate(nextProps) { + if (this.props.needForUpdate) { + return this.props.needForUpdate(this.props, nextProps); + } + return !isEqual(this.props, nextProps); + } - return ( - - {Object.prototype.hasOwnProperty.call(props, 'checked') && - - - - } - {Object.prototype.hasOwnProperty.call(props, 'element') && - - {element} - - } - - {children} - - - {Object.prototype.hasOwnProperty.call(props, 'contextOptions') && contextOptions.length > 0 && - - } - - - ); -}; + render() { + //console.log("Row render"); + const { + checked, + element, + children, + data, + contextOptions, + onSelect + } = this.props; + + const renderCheckbox = Object.prototype.hasOwnProperty.call( + this.props, + "checked" + ); + + const renderElement = Object.prototype.hasOwnProperty.call( + this.props, + "element" + ); + + const renderContext = + Object.prototype.hasOwnProperty.call(this.props, "contextOptions") && + contextOptions.length > 0; + + const changeCheckbox = e => { + onSelect && onSelect(e.target.checked, data); + }; + + const getOptions = () => contextOptions; + + return ( + + {renderCheckbox && ( + + + + )} + {renderElement && {element}} + {children} + + {renderContext && ( + + )} + + + ); + } +} Row.propTypes = { checked: PropTypes.bool, @@ -94,7 +120,8 @@ Row.propTypes = { children: PropTypes.element, data: PropTypes.object, contextOptions: PropTypes.array, - onSelect: PropTypes.func + onSelect: PropTypes.func, + needForUpdate: PropTypes.func }; -export default Row; \ No newline at end of file +export default Row; diff --git a/web/ASC.Web.Components/src/components/scrollbar/index.js b/web/ASC.Web.Components/src/components/scrollbar/index.js index d1752b0bba..76f7585e29 100644 --- a/web/ASC.Web.Components/src/components/scrollbar/index.js +++ b/web/ASC.Web.Components/src/components/scrollbar/index.js @@ -7,19 +7,23 @@ const Scrollbar = React.forwardRef((props, ref) => { const scrollbarType = { smallWhite: { thumbV: { backgroundColor: 'rgba(256, 256, 256, 0.2)', width: '2px', marginLeft: '2px', borderRadius: 'inherit' }, - thumbH: { backgroundColor: 'rgba(256, 256, 256, 0.2)', height: '2px', marginTop: '2px', borderRadius: 'inherit' } + thumbH: { backgroundColor: 'rgba(256, 256, 256, 0.2)', height: '2px', marginTop: '2px', borderRadius: 'inherit' }, + view: {} }, smallBlack: { thumbV: { backgroundColor: 'rgba(0, 0, 0, 0.1)', width: '2px', marginLeft: '2px', borderRadius: 'inherit' }, - thumbH: { backgroundColor: 'rgba(0, 0, 0, 0.1)', height: '2px', marginTop: '2px', borderRadius: 'inherit' } + thumbH: { backgroundColor: 'rgba(0, 0, 0, 0.1)', height: '2px', marginTop: '2px', borderRadius: 'inherit' }, + view: {} }, mediumBlack: { thumbV: { backgroundColor: 'rgba(0, 0, 0, 0.1)', width: '8px', borderRadius: 'inherit' }, - thumbH: { backgroundColor: 'rgba(0, 0, 0, 0.1)', height: '8px', borderRadius: 'inherit' } + thumbH: { backgroundColor: 'rgba(0, 0, 0, 0.1)', height: '8px', borderRadius: 'inherit' }, + view: {paddingRight: '16px'} }, preMediumBlack: { thumbV: { backgroundColor: 'rgba(0, 0, 0, 0.1)', width: '5px', borderRadius: 'inherit', cursor: 'default' }, - thumbH: { backgroundColor: 'rgba(0, 0, 0, 0.1)', height: '5px', borderRadius: 'inherit', cursor: 'default' } + thumbH: { backgroundColor: 'rgba(0, 0, 0, 0.1)', height: '5px', borderRadius: 'inherit', cursor: 'default' }, + view: {} }, }; @@ -27,6 +31,7 @@ const Scrollbar = React.forwardRef((props, ref) => { const thumbV = stype ? stype.thumbV : {}; const thumbH = stype ? stype.thumbH : {}; + const view = stype ? stype.view : {}; const renderNavThumbVertical = ({ style, ...props }) => (
@@ -36,8 +41,12 @@ const Scrollbar = React.forwardRef((props, ref) => {
); + const renderView = ({ style, ...props }) => ( +
+ ); + return ( - + ); });