From b445e25701329a03d7b26a936528d2c4cb65a899 Mon Sep 17 00:00:00 2001 From: Alexey Safronov Date: Fri, 1 Nov 2019 17:55:23 +0300 Subject: [PATCH] web: Components: Attempt to re-write selector and fix check options --- .../advanced-selector2.stories.js | 220 +++++ .../sub-components/options/body.js | 62 +- .../advanced-selector/sub-components/row.js | 4 +- .../sub-components/selector.js | 838 +++++++++--------- .../sub-components/selector_old.js | 537 +++++++++++ 5 files changed, 1209 insertions(+), 452 deletions(-) create mode 100644 web/ASC.Web.Components/src/components/advanced-selector/advanced-selector2.stories.js create mode 100644 web/ASC.Web.Components/src/components/advanced-selector/sub-components/selector_old.js diff --git a/web/ASC.Web.Components/src/components/advanced-selector/advanced-selector2.stories.js b/web/ASC.Web.Components/src/components/advanced-selector/advanced-selector2.stories.js new file mode 100644 index 0000000000..f647d88f17 --- /dev/null +++ b/web/ASC.Web.Components/src/components/advanced-selector/advanced-selector2.stories.js @@ -0,0 +1,220 @@ +/* eslint-disable react/prop-types */ +import React, { useRef, useEffect, useState } from "react"; +import { storiesOf } from "@storybook/react"; +import { + withKnobs +} from "@storybook/addon-knobs/react"; +import withReadme from "storybook-readme/with-readme"; +import Readme from "./README.md"; +import { FixedSizeList as List } from "react-window"; +import InfiniteLoader from "react-window-infinite-loader"; +import faker, { name } from "faker"; +import uniqueId from "lodash/uniqueId"; +import Section from "../../../.storybook/decorators/section"; +//import ADSelectorRow from "./sub-components/row"; +import Checkbox from "../checkbox"; +import CustomScrollbarsVirtualList from "../scrollbar/custom-scrollbars-virtual-list"; +import DropDown from "../drop-down"; + +const ExampleWrapper = ({ + // Are there more items to load? + // (This information comes from the most recent API request.) + hasNextPage, + + // Are we currently loading a page of items? + // (This may be an in-flight flag in your Redux store for example.) + isNextPageLoading, + + // Array of items loaded so far. + items, + + // Callback function responsible for loading the next page of items. + loadNextPage, + + sortOrder +}) => { + // We create a reference for the InfiniteLoader + const listRef = useRef(null); + const hasMountedRef = useRef(false); + const [selected, setSelected] = useState([]); + + // Each time the sort prop changed we called the method resetloadMoreItemsCache to clear the cache + useEffect(() => { + if (listRef.current && hasMountedRef.current) { + listRef.current.resetloadMoreItemsCache(); + } + hasMountedRef.current = true; + }, [sortOrder]); + + // If there are more items to be loaded then add an extra row to hold a loading indicator. + const itemCount = hasNextPage ? items.length + 1 : items.length; + + // Only load 1 page of items at a time. + // Pass an empty callback to InfiniteLoader in case it asks us to load more than once. + const loadMoreItems = isNextPageLoading ? () => {} : loadNextPage; + + // Every row is loaded except for our loading indicator row. + const isItemLoaded = index => !hasNextPage || index < items.length; + + const onChange = (e, item) => { + const newSelected = e.target.checked + ? [item, ...selected] + : selected.filter(el => el.name !== item.name); + //console.log("OnChange", newSelected); + setSelected(newSelected); + }; + + const onSelect = (e, item) => { + console.log("onSelect", item); + }; + + // Render an item or a loading indicator. + const Item = ({ index, style }) => { + let content; + if (!isItemLoaded(index)) { + content = "Loading..."; + } else { + const item = items[index]; + const checked = selected.findIndex(el => el.id === item.id) > -1; + const newStyle = Object.assign({}, style, { + padding: "0 0.5rem", + lineHeight: "30px" + }); + //console.log("Item render", item, checked, selected); + content = ( + onChange(e, item)} + /> + /*<> + onChange(e, item)} + checked={checked} + /> + + */ + /* onChange(e, item)} + onSelect={e => onSelect(e, item)} + />*/ + ); + } + + return
{content}
; + }; + + // We passed down the ref to the InfiniteLoader component + return ( + + {({ onItemsRendered, ref }) => ( + + {Item} + + )} + + ); +} + +class ADSelectorExample extends React.PureComponent { + state = { + hasNextPage: true, + isNextPageLoading: false, + sortOrder: "asc", + items: [] + }; + + constructor(props) { + super(props); + faker.seed(123); + this.persons = new Array(1000) + .fill(true) + .map(() => ({ name: name.findName(), id: uniqueId() })); + this.persons.sort((a, b) => a.name.localeCompare(b.name)); + } + + _loadNextPage = (...args) => { + this.setState({ isNextPageLoading: true }, () => { + setTimeout(() => { + this.setState(state => ({ + hasNextPage: state.items.length < 100, + isNextPageLoading: false, + items: [...state.items].concat( + this.persons.slice(args[0], args[0] + 10) + ) + })); + }, 2500); + }); + }; + + _handleSortOrderChange = e => { + this.persons.sort((a, b) => { + if (e.target.value === "asc") { + return a.name.localeCompare(b.name); + } + return b.name.localeCompare(a.name); + }); + this.setState({ + sortOrder: e.target.value, + items: [] + }); + }; + + render() { + const { hasNextPage, isNextPageLoading, items, sortOrder } = this.state; + return ( + <> +
+ +
+ + + ); + } +} + +storiesOf("Components|AdvancedSelector", module) + .addDecorator(withKnobs) + .addDecorator(withReadme(Readme)) + .add("only list", () => { + return ( +
+ +
+ ); + }); diff --git a/web/ASC.Web.Components/src/components/advanced-selector/sub-components/options/body.js b/web/ASC.Web.Components/src/components/advanced-selector/sub-components/options/body.js index 7ab6563b49..46f743c1e8 100644 --- a/web/ASC.Web.Components/src/components/advanced-selector/sub-components/options/body.js +++ b/web/ASC.Web.Components/src/components/advanced-selector/sub-components/options/body.js @@ -1,19 +1,44 @@ import React from "react"; import PropTypes from "prop-types"; import CustomScrollbarsVirtualList from "../../../scrollbar/custom-scrollbars-virtual-list"; -import { FixedSizeList } from "react-window"; +import { FixedSizeList as List } from "react-window"; import InfiniteLoader from "react-window-infinite-loader"; -import ADSelectorRow from "../row"; +//import ADSelectorRow from "../row"; +import Checkbox from "../../../checkbox"; import Loader from "../../../loader"; import { Text } from "../../../text"; import findIndex from "lodash/findIndex"; +//import isEqual from "lodash/isEqual"; class ADSelectorOptionsBody extends React.Component { + constructor(props) { + super(props); + + this.listRef = React.createRef(); + //this.hasMountedRef = React.createRef(false); + } + + /*componentDidMount() { + this.hasMountedRef.current = true; + } + + componentDidUpdate(prevProps) { + if(!isEqual(this.props.selectedOptions, prevProps.selectedOptions)) { + if(this.listRef.current && this.hasMountedRef.current) { + //this.listRef.current.resetloadMoreItemsCache(true); + //console.log("this.listRef.current", this.listRef.current) + } + } + + }*/ + renderRow = ({ index, style }) => { //console.log("renderRow", option, isChecked, this.state.selectedOptions); let content; - if (!this.isItemLoaded(index)) { + const isLoaded = this.isItemLoaded(index); + let key = "loader"; + if (!isLoaded) { content = (
-1 : undefined; + key = option.key; content = ( - + /* + />*/ + /*
+ { + console.log("checkbox click", e); + this.props.onRowChecked(option, e); + }} + checked={isChecked} + /> + +
*/ ); } - return <>{content}; + return
{content}
; }; isItemLoaded = index => @@ -71,12 +116,13 @@ class ADSelectorOptionsBody extends React.Component { return (
{({ onItemsRendered, ref }) => ( - {this.renderRow} - + )}
diff --git a/web/ASC.Web.Components/src/components/advanced-selector/sub-components/row.js b/web/ASC.Web.Components/src/components/advanced-selector/sub-components/row.js index 6575afa96b..299d1c74d5 100644 --- a/web/ASC.Web.Components/src/components/advanced-selector/sub-components/row.js +++ b/web/ASC.Web.Components/src/components/advanced-selector/sub-components/row.js @@ -15,10 +15,10 @@ class ADSelectorRow extends React.Component { isSelected } = this.props; - console.log("ADSelectorRow render", label, isChecked); + //console.log("ADSelectorRow render", label, isChecked); return ( -
+
{isMultiSelect ? ( (props.containerWidth ? `width: ${props.containerWidth};` : "")} - ${props => (props.containerHeight ? `height: ${props.containerHeight};` : "")} - - .data_container { - margin: 16px 16px -5px 16px; - - .head_container { - display: flex; - margin-bottom: ${props => (props.displayType === "dropdown" ? 8 : 16)}px; - - .options_searcher { - display: inline-block; - width: 100%; - + display: flex; + flex-direction: column; + + ${props => (props.containerWidth ? `width: ${props.containerWidth};` : "")} + ${props => + props.containerHeight ? `height: ${props.containerHeight};` : ""} + + .data_container { + margin: 16px 16px -5px 16px; + + .head_container { + display: flex; + margin-bottom: ${props => + props.displayType === "dropdown" ? 8 : 16}px; + + .options_searcher { + display: inline-block; + width: 100%; + + ${props => + props.displayType === "dropdown" && + props.size === "full" && + css` + margin-right: ${props => (props.allowCreation ? 8 : 16)}px; + `} + /*${props => + props.allowCreation + ? css` + width: 272px; + margin-right: 8px; + ` + : css` + width: ${props => (props.isDropDown ? "313px" : "100%")}; + `}*/ + } + + .add_new_btn { + ${props => + props.allowCreation && + css` + display: inline-block; + vertical-align: top; + height: 32px; + width: 36px; + margin-right: 16px; + line-height: 18px; + `} + } + + } + + .options_group_selector { + margin-bottom: 12px; + } + + .data_column_one { + ${props => + props.size === "full" && + props.displayType === "dropdown" && + props.groups && + props.groups.length > 0 + ? css` + width: 50%; + display: inline-block; + ` + : ""} + + .options_list { + margin-top: 4px; + margin-left: -8px; + .option { + line-height: 32px; + padding-left: ${props => (props.isMultiSelect ? 8 : 0)}px; + cursor: pointer; + + .option_checkbox { + /*margin-left: 8px;*/ + } + + .option_link { + padding-left: 8px; + } + + &:hover { + background-color: #f8f9f9; + } + } + } + } + + .data_column_two { ${props => props.displayType === "dropdown" && - props.size === "full" && - css` - margin-right: ${props => (props.allowCreation ? 8 : 16)}px; - `} - /*${props => - props.allowCreation + props.groups && + props.groups.length > 0 ? css` - width: 272px; - margin-right: 8px; + width: 50%; + display: inline-block; + border-left: 1px solid #eceef1; ` - : css` - width: ${props => (props.isDropDown ? "313px" : "100%")}; - `}*/ - } - - .add_new_btn { - ${props => - props.allowCreation && - css` - display: inline-block; - vertical-align: top; - height: 32px; - width: 36px; - margin-right: 16px; - line-height: 18px; - `} - } - - } - - .options_group_selector { - margin-bottom: 12px; - } - - .data_column_one { - ${props => - props.size === "full" && - props.displayType === "dropdown" && - props.groups && - props.groups.length > 0 - ? css` - width: 50%; - display: inline-block; - ` - : ""} - - .options_list { - margin-top: 4px; - margin-left: -8px; - .option { - line-height: 32px; - padding-left: ${props => (props.isMultiSelect ? 8 : 0)}px; - cursor: pointer; - - .option_checkbox { - /*margin-left: 8px;*/ - } - - .option_link { + : ""} + + .group_header { + font-weight: 600; + padding-left: 16px; + padding-bottom: 14px; + } + + .group_list { + margin-left: 8px; + + .option { + line-height: 32px; padding-left: 8px; + cursor: pointer; + + .option_checkbox { + /*margin-left: 8px;*/ + } + + .option_link { + padding-left: 8px; + } + + &:hover { + background-color: #f8f9f9; + } } - - &:hover { - background-color: #f8f9f9; + + .option.selected { + background-color: #ECEEF1; } } } } + `; - .data_column_two { - ${props => - props.displayType === "dropdown" && - props.groups && - props.groups.length > 0 - ? css` - width: 50%; - display: inline-block; - border-left: 1px solid #eceef1; - ` - : ""} +const ADSelector = props => { + const { + options, + groups, + hasNextPage, + isNextPageLoading, + loadNextPage, + value, + placeholder, + isDisabled, + onSearchChanged, + isMultiSelect, + buttonLabel, + selectAllLabel, + size, + displayType, + onAddNewClick, + allowCreation, + selectedOptions, + selectedGroups, + onSelect + } = props; - .group_header { - font-weight: 600; - padding-left: 16px; - padding-bottom: 14px; - } + // We create a reference for the InfiniteLoader + const listRef = useRef(null); + //const hasMountedRef = useRef(false); + const [selected, setSelected] = useState(selectedOptions || []); + const [selectedGrps, setSelectedGroups] = useState(selectedGroups || []); + const [selectedAll, setSelectedAll] = useState(false); - .group_list { - margin-left: 8px; + const convertGroups = items => { + if (!items) return []; - .option { - line-height: 32px; - padding-left: 8px; - cursor: pointer; - - .option_checkbox { - /*margin-left: 8px;*/ - } - - .option_link { - padding-left: 8px; - } - - &:hover { - background-color: #f8f9f9; - } - } - - .option.selected { - background-color: #ECEEF1; - } - } - } - } -`; - -class ADSelector extends React.Component { - constructor(props) { - super(props); - - this.ref = React.createRef(); - - const { groups, selectedOptions, selectedGroups, selectedAll, isOpen } = props; - - const convertedGroups = this.convertGroups(groups); - const currentGroup = this.getCurrentGroup(convertedGroups); - - this.state = { - selectedOptions: selectedOptions || [], - selectedAll: selectedAll || false, - groups: convertedGroups, - selectedGroups: selectedGroups || [], - currentGroup: currentGroup - }; - - if (isOpen) handleAnyClick(true, this.handleClick); - } - - handleClick = e => { - const { onCancel, allowAnyClickClose, isOpen } = this.props; - - if ( - isOpen && - allowAnyClickClose && - this.ref && - this.ref.current && - !this.ref.current.contains(e.target) && - e && - e.target && - e.target.className && - typeof e.target.className.indexOf === "function" && - e.target.className.indexOf("option_checkbox") === -1 - ) { - onCancel && onCancel(); - } - }; - - componentWillUnmount() { - handleAnyClick(false, this.handleClick); - } - - shouldComponentUpdate(nextProps, nextState) { - return !isEqual(this.props, nextProps) || !isEqual(this.state, nextState); - } - - componentDidUpdate(prevProps) { - const { - groups, - selectedAll, - isMultiSelect, - selectedOptions, - selectedGroups, - allowAnyClickClose, - isOpen - } = this.props; - - if (isOpen !== prevProps.isOpen) { - handleAnyClick(isOpen, this.handleClick); - } - - if (allowAnyClickClose !== prevProps.allowAnyClickClose) { - handleAnyClick(allowAnyClickClose, this.handleClick); - } - - let newState = {}; - - if (!isEqual(selectedOptions, prevProps.selectedOptions)) { - newState = { selectedOptions }; - } - - if (!isEqual(selectedGroups, prevProps.selectedGroups)) { - newState = Object.assign({}, newState, { selectedGroups }); - } - if (isMultiSelect !== prevProps.isMultiSelect) { - newState = Object.assign({}, newState, { - selectedOptions: [] - }); - } - - if (selectedAll !== prevProps.selectedAll) { - newState = Object.assign({}, newState, { - selectedAll - }); - } - - if (!isEqual(groups, prevProps.groups)) { - const newGroups = this.convertGroups(groups); - const currentGroup = this.getCurrentGroup(newGroups); - newState = Object.assign({}, newState, { - groups: newGroups, - currentGroup - }); - } - - if (!isEmpty(newState)) { - this.setState({ ...this.state, ...newState }); - } - } - - convertGroups = groups => { - if (!groups) return []; - - const wrappedGroups = groups.map(this.convertGroup); + const wrappedGroups = items.map(convertGroup); return wrappedGroups; }; - convertGroup = group => { + const convertGroup = group => { return { key: group.key, label: `${group.label} (${group.total})`, @@ -307,231 +233,259 @@ class ADSelector extends React.Component { }; }; - getCurrentGroup = groups => { - const currentGroup = groups.length > 0 ? groups[0] : "No groups"; + const getCurrentGroup = items => { + const currentGroup = items.length > 0 ? items[0] : "No groups"; return currentGroup; }; - onButtonClick = () => { - this.props.onSelect && - this.props.onSelect( - this.state.selectedAll ? this.props.options : this.state.selectedOptions - ); + const convertedGroups = convertGroups(groups); + const curGroup = getCurrentGroup(convertedGroups); + + const [currentGroup, setCurrentGroup] = useState(curGroup); + + // Each time the sort prop changed we called the method resetloadMoreItemsCache to clear the cache + /*useEffect(() => { + if (listRef.current && hasMountedRef.current) { + listRef.current.resetloadMoreItemsCache(); + } + hasMountedRef.current = true; + }, [sortOrder]);*/ + + // If there are more items to be loaded then add an extra row to hold a loading indicator. + const itemCount = hasNextPage ? options.length + 1 : options.length; + + // Only load 1 page of items at a time. + // Pass an empty callback to InfiniteLoader in case it asks us to load more than once. + const loadMoreItems = isNextPageLoading ? () => {} : loadNextPage; + + // Every row is loaded except for our loading indicator row. + const isItemLoaded = index => !hasNextPage || index < options.length; + + const onChange = (e, item) => { + const newSelected = e.target.checked + ? [item, ...selected] + : selected.filter(el => el.name !== item.name); + //console.log("OnChange", newSelected); + setSelected(newSelected); }; - onSelectedAllChange = e => { - this.setState({ - selectedAll: e.target.checked, - selectedOptions: e.target.checked ? this.props.options : [] - }); - }; - - onOptionSelect = option => { - this.props.onSelect && this.props.onSelect(option); - }; - - onOptionChange = (option, e) => { - const { selectedOptions } = this.state; - const newSelectedOptions = e.target.checked - ? [...selectedOptions, option] - : filter(selectedOptions, obj => obj.key !== option.key); - - //console.log("onChange", option, e.target.checked, newSelectedOptions); - - this.setState({ - selectedOptions: newSelectedOptions, - selectedAll: newSelectedOptions.length === this.props.options.length - }); - }; - - onGroupChange = (group, e) => { - /*this.setState({ - currentGroup: group - }); - - this.props.onGroupChange && this.props.onGroupChange(group);*/ - - const { selectedGroups } = this.state; + const onGroupChange = (e, item) => { const newSelectedGroups = e.target.checked - ? [...selectedGroups, group] - : filter(selectedGroups, obj => obj.key !== group.key); - - //console.log("onChange", option, e.target.checked, newSelectedOptions); - - this.setState({ - selectedGroups: newSelectedGroups, - currentGroup: group - }); - - this.props.onGroupSelect && this.props.onGroupSelect(group); + ? [item, ...selectedGrps] + : selectedGrps.filter(el => el.name !== item.name); + //console.log("onGroupChange", item); + setSelectedGroups(newSelectedGroups); + }; + const onCurrentGroupChange = (e, item) => { + //console.log("onCurrentGroupChange", item); + setCurrentGroup(item); + }; + const onSelectedAllChange = (e, item) => { + //console.log("onSelectedAllChange", item); + setSelectedAll(item); }; - render() { - //console.log("ADSelector render"); - const { - options, - hasNextPage, - isNextPageLoading, - loadNextPage, - value, - placeholder, - isDisabled, - onSearchChanged, - isMultiSelect, - buttonLabel, - selectAllLabel, - size, - displayType, - onAddNewClick, - allowCreation - } = this.props; + const onButtonClick = () => { + onSelect && onSelect(selectedAll ? options : selectedOptions); + }; - const { selectedOptions, selectedAll, currentGroup, groups, selectedGroups } = this.state; - - let containerHeight; - let containerWidth; - let listHeight; - let listWidth; - const itemHeight = 32; - const hasGroups = groups && groups.length > 0; - - switch (size) { - case "compact": - containerHeight = hasGroups ? "326px" : "100%"; - containerWidth = "379px"; - listWidth = displayType === "dropdown" ? 356 : 356; - listHeight = hasGroups ? 488 : isMultiSelect ? 176 : 226; - break; - case "full": - default: - containerHeight = "100%"; - containerWidth = displayType === "dropdown" ? "690px" : "326px"; - listWidth = displayType === "dropdown" ? 320 : 300; - listHeight = 488; - break; + // Render an item or a loading indicator. + const Item = ({ index, style }) => { + let content; + if (!isItemLoaded(index)) { + content =
+ + Loading... Please wait... +
; + } else { + const option = options[index]; + const checked = selected.findIndex(el => el.key === option.key) > -1; + //console.log("Item render", item, checked, selected); + content = ( + onChange(e, option)} + /> + /*<> + onChange(e, item)} + checked={checked} + /> + + */ + /* onChange(e, item)} + onSelect={e => onSelect(e, item)} + />*/ + ); } - // If there are more items to be loaded then add an extra row to hold a loading indicator. - //const itemCount = hasNextPage ? options.length + 1 : options.length; + return
{content}
; + }; - return ( - 0; + + switch (size) { + case "compact": + containerHeight = hasGroups ? "326px" : "100%"; + containerWidth = "379px"; + listWidth = displayType === "dropdown" ? 356 : 356; + listHeight = hasGroups ? 488 : isMultiSelect ? 176 : 226; + break; + case "full": + default: + containerHeight = "100%"; + containerWidth = displayType === "dropdown" ? "690px" : "326px"; + listWidth = displayType === "dropdown" ? 320 : 300; + listHeight = 488; + break; + } + + // We passed down the ref to the InfiniteLoader component + return ( + -
-
-
- - {displayType === "aside" && groups && groups.length > 0 && ( - - )} - {isMultiSelect && !groups && !groups.length && ( - 0} - className="option_select_all_checkbox" - onChange={this.onSelectedAllChange} - /> - )} - +
+
+ + {displayType === "aside" && convertedGroups && convertedGroups.length > 0 && ( + + )} + {isMultiSelect && !convertedGroups && !convertedGroups.length && ( + 0} + className="option_select_all_checkbox" + onChange={onSelectedAllChange} + /> + )} + + {({ onItemsRendered, ref }) => ( + + {Item} + + )} + +
+ {displayType === "dropdown" && + size === "full" && + convertedGroups && + convertedGroups.length > 0 && ( +
+ +
- {displayType === "dropdown" && - size === "full" && - groups && - groups.length > 0 && ( -
- - -
- )} -
- -
- - ); - } -} + )} +
+ + + ); +}; ADSelector.propTypes = { - isOpen: PropTypes.bool, - options: PropTypes.array, - groups: PropTypes.array, - hasNextPage: PropTypes.bool, - isNextPageLoading: PropTypes.bool, - loadNextPage: PropTypes.func, - value: PropTypes.string, - placeholder: PropTypes.string, - isDisabled: PropTypes.bool, - onSearchChanged: PropTypes.func, - isMultiSelect: PropTypes.bool, - buttonLabel: PropTypes.string, - selectAllLabel: PropTypes.string, - size: PropTypes.string, - displayType: PropTypes.oneOf(["dropdown", "aside"]), - onAddNewClick: PropTypes.func, - allowCreation: PropTypes.bool, - onSelect: PropTypes.func, - onChange: PropTypes.func, - onGroupSelect: PropTypes.func, - onGroupChange: PropTypes.func, - selectedOptions: PropTypes.array, - selectedGroups: PropTypes.array, - selectedAll: PropTypes.bool, - onCancel: PropTypes.func, - allowAnyClickClose: PropTypes.bool, + isOpen: PropTypes.bool, + options: PropTypes.array, + groups: PropTypes.array, + hasNextPage: PropTypes.bool, + isNextPageLoading: PropTypes.bool, + loadNextPage: PropTypes.func, + value: PropTypes.string, + placeholder: PropTypes.string, + isDisabled: PropTypes.bool, + onSearchChanged: PropTypes.func, + isMultiSelect: PropTypes.bool, + buttonLabel: PropTypes.string, + selectAllLabel: PropTypes.string, + size: PropTypes.string, + displayType: PropTypes.oneOf(["dropdown", "aside"]), + onAddNewClick: PropTypes.func, + allowCreation: PropTypes.bool, + onSelect: PropTypes.func, + onChange: PropTypes.func, + onGroupSelect: PropTypes.func, + onGroupChange: PropTypes.func, + selectedOptions: PropTypes.array, + selectedGroups: PropTypes.array, + selectedAll: PropTypes.bool, + onCancel: PropTypes.func, + allowAnyClickClose: PropTypes.bool }; export default ADSelector; diff --git a/web/ASC.Web.Components/src/components/advanced-selector/sub-components/selector_old.js b/web/ASC.Web.Components/src/components/advanced-selector/sub-components/selector_old.js new file mode 100644 index 0000000000..ed1fa41809 --- /dev/null +++ b/web/ASC.Web.Components/src/components/advanced-selector/sub-components/selector_old.js @@ -0,0 +1,537 @@ +import React from "react"; +import PropTypes from "prop-types"; +import styled, { css } from "styled-components"; +import Checkbox from "../../checkbox"; +import ComboBox from "../../combobox"; + +import filter from "lodash/filter"; +import isEqual from "lodash/isEqual"; +import isEmpty from "lodash/isEmpty"; + +import ADSelectorOptionsHeader from "./options/header"; +import ADSelectorOptionsBody from "./options/body"; +import ADSelectorGroupsHeader from "./groups/header"; +import ADSelectorGroupsBody from "./groups/body"; +import ADSelectorFooter from "./footer"; + +import { handleAnyClick } from "../../../utils/event"; + +/* eslint-disable no-unused-vars */ +/* eslint-disable react/prop-types */ +const Container = ({ + value, + placeholder, + isMultiSelect, + size, + width, + maxHeight, + isDisabled, + onSelect, + onSearchChanged, + options, + selectedOptions, + buttonLabel, + selectAllLabel, + groups, + selectedGroups, + onGroupSelect, + onGroupChange, + isOpen, + displayType, + containerWidth, + containerHeight, + allowCreation, + onAddNewClick, + allowAnyClickClose, + hasNextPage, + isNextPageLoading, + loadNextPage, + isSelected, + ...props +}) =>
; +/* eslint-enable react/prop-types */ +/* eslint-enable no-unused-vars */ + +const StyledContainer = styled(Container)` + display: flex; + flex-direction: column; + + ${props => (props.containerWidth ? `width: ${props.containerWidth};` : "")} + ${props => (props.containerHeight ? `height: ${props.containerHeight};` : "")} + + .data_container { + margin: 16px 16px -5px 16px; + + .head_container { + display: flex; + margin-bottom: ${props => (props.displayType === "dropdown" ? 8 : 16)}px; + + .options_searcher { + display: inline-block; + width: 100%; + + ${props => + props.displayType === "dropdown" && + props.size === "full" && + css` + margin-right: ${props => (props.allowCreation ? 8 : 16)}px; + `} + /*${props => + props.allowCreation + ? css` + width: 272px; + margin-right: 8px; + ` + : css` + width: ${props => (props.isDropDown ? "313px" : "100%")}; + `}*/ + } + + .add_new_btn { + ${props => + props.allowCreation && + css` + display: inline-block; + vertical-align: top; + height: 32px; + width: 36px; + margin-right: 16px; + line-height: 18px; + `} + } + + } + + .options_group_selector { + margin-bottom: 12px; + } + + .data_column_one { + ${props => + props.size === "full" && + props.displayType === "dropdown" && + props.groups && + props.groups.length > 0 + ? css` + width: 50%; + display: inline-block; + ` + : ""} + + .options_list { + margin-top: 4px; + margin-left: -8px; + .option { + line-height: 32px; + padding-left: ${props => (props.isMultiSelect ? 8 : 0)}px; + cursor: pointer; + + .option_checkbox { + /*margin-left: 8px;*/ + } + + .option_link { + padding-left: 8px; + } + + &:hover { + background-color: #f8f9f9; + } + } + } + } + + .data_column_two { + ${props => + props.displayType === "dropdown" && + props.groups && + props.groups.length > 0 + ? css` + width: 50%; + display: inline-block; + border-left: 1px solid #eceef1; + ` + : ""} + + .group_header { + font-weight: 600; + padding-left: 16px; + padding-bottom: 14px; + } + + .group_list { + margin-left: 8px; + + .option { + line-height: 32px; + padding-left: 8px; + cursor: pointer; + + .option_checkbox { + /*margin-left: 8px;*/ + } + + .option_link { + padding-left: 8px; + } + + &:hover { + background-color: #f8f9f9; + } + } + + .option.selected { + background-color: #ECEEF1; + } + } + } + } +`; + +class ADSelector extends React.Component { + constructor(props) { + super(props); + + this.ref = React.createRef(); + + const { groups, selectedOptions, selectedGroups, selectedAll, isOpen } = props; + + const convertedGroups = this.convertGroups(groups); + const currentGroup = this.getCurrentGroup(convertedGroups); + + this.state = { + selectedOptions: selectedOptions || [], + selectedAll: selectedAll || false, + groups: convertedGroups, + selectedGroups: selectedGroups || [], + currentGroup: currentGroup + }; + + if (isOpen) handleAnyClick(true, this.handleClick); + } + + handleClick = e => { + const { onCancel, allowAnyClickClose, isOpen } = this.props; + + if ( + isOpen && + allowAnyClickClose && + this.ref && + this.ref.current && + !this.ref.current.contains(e.target) && + e && + e.target && + e.target.className && + typeof e.target.className.indexOf === "function" && + e.target.className.indexOf("option_checkbox") === -1 + ) { + onCancel && onCancel(); + } + }; + + componentWillUnmount() { + handleAnyClick(false, this.handleClick); + } + + shouldComponentUpdate(nextProps, nextState) { + return !isEqual(this.props, nextProps) || !isEqual(this.state, nextState); + } + + componentDidUpdate(prevProps) { + const { + groups, + selectedAll, + isMultiSelect, + selectedOptions, + selectedGroups, + allowAnyClickClose, + isOpen + } = this.props; + + if (isOpen !== prevProps.isOpen) { + handleAnyClick(isOpen, this.handleClick); + } + + if (allowAnyClickClose !== prevProps.allowAnyClickClose) { + handleAnyClick(allowAnyClickClose, this.handleClick); + } + + let newState = {}; + + if (!isEqual(selectedOptions, prevProps.selectedOptions)) { + newState = { selectedOptions }; + } + + if (!isEqual(selectedGroups, prevProps.selectedGroups)) { + newState = Object.assign({}, newState, { selectedGroups }); + } + if (isMultiSelect !== prevProps.isMultiSelect) { + newState = Object.assign({}, newState, { + selectedOptions: [] + }); + } + + if (selectedAll !== prevProps.selectedAll) { + newState = Object.assign({}, newState, { + selectedAll + }); + } + + if (!isEqual(groups, prevProps.groups)) { + const newGroups = this.convertGroups(groups); + const currentGroup = this.getCurrentGroup(newGroups); + newState = Object.assign({}, newState, { + groups: newGroups, + currentGroup + }); + } + + if (!isEmpty(newState)) { + this.setState({ ...this.state, ...newState }); + } + } + + convertGroups = groups => { + if (!groups) return []; + + const wrappedGroups = groups.map(this.convertGroup); + + return wrappedGroups; + }; + + convertGroup = group => { + return { + key: group.key, + label: `${group.label} (${group.total})`, + total: group.total + }; + }; + + getCurrentGroup = groups => { + const currentGroup = groups.length > 0 ? groups[0] : "No groups"; + return currentGroup; + }; + + onButtonClick = () => { + this.props.onSelect && + this.props.onSelect( + this.state.selectedAll ? this.props.options : this.state.selectedOptions + ); + }; + + onSelectedAllChange = e => { + this.setState({ + selectedAll: e.target.checked, + selectedOptions: e.target.checked ? this.props.options : [] + }); + }; + + onOptionSelect = option => { + this.props.onSelect && this.props.onSelect(option); + }; + + onOptionChange = (option, e) => { + const { selectedOptions } = this.state; + const newSelectedOptions = e.target.checked + ? [...selectedOptions, option] + : filter(selectedOptions, obj => obj.key !== option.key); + + //console.log("onChange", option, e.target.checked, newSelectedOptions); + + this.setState({ + selectedOptions: newSelectedOptions, + selectedAll: newSelectedOptions.length === this.props.options.length + }); + }; + + onGroupChange = (group, e) => { + /*this.setState({ + currentGroup: group + }); + + this.props.onGroupChange && this.props.onGroupChange(group);*/ + + const { selectedGroups } = this.state; + const newSelectedGroups = e.target.checked + ? [...selectedGroups, group] + : filter(selectedGroups, obj => obj.key !== group.key); + + //console.log("onChange", option, e.target.checked, newSelectedOptions); + + this.setState({ + selectedGroups: newSelectedGroups, + currentGroup: group + }); + + this.props.onGroupSelect && this.props.onGroupSelect(group); + }; + + render() { + //console.log("ADSelector render"); + const { + options, + hasNextPage, + isNextPageLoading, + loadNextPage, + value, + placeholder, + isDisabled, + onSearchChanged, + isMultiSelect, + buttonLabel, + selectAllLabel, + size, + displayType, + onAddNewClick, + allowCreation + } = this.props; + + const { selectedOptions, selectedAll, currentGroup, groups, selectedGroups } = this.state; + + let containerHeight; + let containerWidth; + let listHeight; + let listWidth; + const itemHeight = 32; + const hasGroups = groups && groups.length > 0; + + switch (size) { + case "compact": + containerHeight = hasGroups ? "326px" : "100%"; + containerWidth = "379px"; + listWidth = displayType === "dropdown" ? 356 : 356; + listHeight = hasGroups ? 488 : isMultiSelect ? 176 : 226; + break; + case "full": + default: + containerHeight = "100%"; + containerWidth = displayType === "dropdown" ? "690px" : "326px"; + listWidth = displayType === "dropdown" ? 320 : 300; + listHeight = 488; + break; + } + + // If there are more items to be loaded then add an extra row to hold a loading indicator. + //const itemCount = hasNextPage ? options.length + 1 : options.length; + + return ( + +
+
+
+ + {displayType === "aside" && groups && groups.length > 0 && ( + + )} + {isMultiSelect && !groups && !groups.length && ( + 0} + className="option_select_all_checkbox" + onChange={this.onSelectedAllChange} + /> + )} + +
+ {displayType === "dropdown" && + size === "full" && + groups && + groups.length > 0 && ( +
+ + +
+ )} +
+ +
+
+ ); + } +} + +ADSelector.propTypes = { + isOpen: PropTypes.bool, + options: PropTypes.array, + groups: PropTypes.array, + hasNextPage: PropTypes.bool, + isNextPageLoading: PropTypes.bool, + loadNextPage: PropTypes.func, + value: PropTypes.string, + placeholder: PropTypes.string, + isDisabled: PropTypes.bool, + onSearchChanged: PropTypes.func, + isMultiSelect: PropTypes.bool, + buttonLabel: PropTypes.string, + selectAllLabel: PropTypes.string, + size: PropTypes.string, + displayType: PropTypes.oneOf(["dropdown", "aside"]), + onAddNewClick: PropTypes.func, + allowCreation: PropTypes.bool, + onSelect: PropTypes.func, + onChange: PropTypes.func, + onGroupSelect: PropTypes.func, + onGroupChange: PropTypes.func, + selectedOptions: PropTypes.array, + selectedGroups: PropTypes.array, + selectedAll: PropTypes.bool, + onCancel: PropTypes.func, + allowAnyClickClose: PropTypes.bool, +}; + +export default ADSelector;