diff --git a/frontend/src/assets/icons/tag.svg b/frontend/src/assets/icons/tag.svg new file mode 100644 index 0000000000..cfaca62d35 --- /dev/null +++ b/frontend/src/assets/icons/tag.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/components/common/common-add-tool.js b/frontend/src/components/common/common-add-tool.js new file mode 100644 index 0000000000..3646a32e5c --- /dev/null +++ b/frontend/src/components/common/common-add-tool.js @@ -0,0 +1,22 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import '../../css/common-add-tool.css'; + +function CommonAddTool(props) { + const { callBack, footerName, className, addIconClassName } = props; + return ( +
{callBack(e);}}> + + {footerName} +
+ ); +} + +CommonAddTool.propTypes = { + className: PropTypes.string, + addIconClassName: PropTypes.string, + footerName: PropTypes.string.isRequired, + callBack: PropTypes.func.isRequired, +}; + +export default CommonAddTool; diff --git a/frontend/src/components/common/seahub-popover.js b/frontend/src/components/common/seahub-popover.js new file mode 100644 index 0000000000..68c9f60a33 --- /dev/null +++ b/frontend/src/components/common/seahub-popover.js @@ -0,0 +1,101 @@ +import React from 'react'; +import { Popover } from 'reactstrap'; +import PropTypes from 'prop-types'; +import { KeyCodes } from '../../constants'; + +const propTypes = { + target: PropTypes.oneOfType([PropTypes.string, PropTypes.object]).isRequired, + boundariesElement: PropTypes.object, + innerClassName: PropTypes.string, + popoverClassName: PropTypes.string, + children: PropTypes.oneOfType([PropTypes.object, PropTypes.array]), + hideSeahubPopover: PropTypes.func.isRequired, + hideSeahubPopoverWithEsc: PropTypes.func, + hideArrow: PropTypes.bool, + canHideSeahubPopover: PropTypes.bool, + placement: PropTypes.string, + modifiers: PropTypes.object +}; + +class SeahubPopover extends React.Component { + + SeahubPopoverRef = null; + isSelectOpen = false; + + componentDidMount() { + document.addEventListener('mousedown', this.onMouseDown, true); + document.addEventListener('keydown', this.onKeyDown); + } + + componentWillUnmount() { + document.removeEventListener('mousedown', this.onMouseDown, true); + document.removeEventListener('keydown', this.onKeyDown); + } + + getEventClassName = (e) => { + // svg mouseEvent event.target.className is an object + if (!e || !e.target) return ''; + return e.target.getAttribute('class') || ''; + }; + + onKeyDown = (e) => { + const { canHideSeahubPopover, hideSeahubPopoverWithEsc } = this.props; + if (e.keyCode === KeyCodes.Escape && typeof hideSeahubPopoverWithEsc === 'function' && !this.isSelectOpen) { + e.preventDefault(); + hideSeahubPopoverWithEsc(); + } else if (e.keyCode === KeyCodes.Enter) { + // Resolve the default behavior of the enter key when entering formulas is blocked + if (canHideSeahubPopover) return; + e.stopImmediatePropagation(); + } + }; + + onMouseDown = (e) => { + if (!this.props.canHideSeahubPopover) return; + if (this.SeahubPopoverRef && e && this.getEventClassName(e).indexOf('popover') === -1 && !this.SeahubPopoverRef.contains(e.target)) { + this.props.hideSeahubPopover(e); + } + }; + + onPopoverInsideClick = (e) => { + e.stopPropagation(); + }; + + render() { + const { + target, boundariesElement, innerClassName, popoverClassName, hideArrow, modifiers, + placement, + } = this.props; + let additionalProps = {}; + if (boundariesElement) { + additionalProps.boundariesElement = boundariesElement; + } + return ( + +
this.SeahubPopoverRef = ref} onClick={this.onPopoverInsideClick}> + {this.props.children} +
+
+ ); + } +} + +SeahubPopover.defaultProps = { + placement: 'bottom-start', + hideArrow: true, + canHideSeahubPopover: true +}; + +SeahubPopover.propTypes = propTypes; + +export default SeahubPopover; diff --git a/frontend/src/components/common/search-input.js b/frontend/src/components/common/search-input.js new file mode 100644 index 0000000000..5e7b4f7bad --- /dev/null +++ b/frontend/src/components/common/search-input.js @@ -0,0 +1,144 @@ +import React, { Component, Fragment } from 'react'; +import PropTypes from 'prop-types'; +import classnames from 'classnames'; + +const propTypes = { + placeholder: PropTypes.string, + autoFocus: PropTypes.bool, + className: PropTypes.string, + onChange: PropTypes.func.isRequired, + onKeyDown: PropTypes.func, + wait: PropTypes.number, + disabled: PropTypes.bool, + style: PropTypes.object, + isClearable: PropTypes.bool, + clearValue: PropTypes.func, + clearClassName: PropTypes.string, + components: PropTypes.object, + value: PropTypes.string, +}; + +class SearchInput extends Component { + + constructor(props) { + super(props); + this.state = { + searchValue: props.value, + }; + this.isInputtingChinese = false; + this.timer = null; + this.inputRef = null; + } + + componentDidMount() { + if (this.props.autoFocus && this.inputRef && this.inputRef !== document.activeElement) { + setTimeout(() => { + this.inputRef.focus(); + }, 0); + } + } + + componentWillReceiveProps(nextProps) { + if (nextProps.value !== this.props.value) { + this.setState({searchValue: nextProps.value}); + } + } + + componentWillUnmount() { + this.timer && clearTimeout(this.timer); + this.timer = null; + this.inputRef = null; + } + + onCompositionStart = () => { + this.isInputtingChinese = true; + }; + + onChange = (e) => { + this.timer && clearTimeout(this.timer); + const { onChange, wait } = this.props; + let text = e.target.value; + this.setState({searchValue: text || ''}, () => { + if (this.isInputtingChinese) return; + this.timer = setTimeout(() => { + onChange && onChange(this.state.searchValue.trim()); + }, wait); + }); + }; + + onCompositionEnd = (e) => { + this.isInputtingChinese = false; + this.onChange(e); + }; + + clearSearch = () => { + const { clearValue } = this.props; + this.setState({searchValue: ''}, () => { + clearValue && clearValue(); + }); + }; + + setFocus = (isSelectAllText) => { + if (this.inputRef === document.activeElement) return; + this.inputRef.focus(); + if (isSelectAllText) { + const txtLength = this.state.searchValue.length; + this.inputRef.setSelectionRange(0, txtLength); + } + }; + + isFunction = (functionToCheck) => { + const getType = {}; + return functionToCheck && getType.toString.call(functionToCheck) === '[object Function]'; + }; + + renderClear = () => { + const { isClearable, clearClassName, components = {} } = this.props; + const { searchValue } = this.state; + if (!isClearable || !searchValue) return null; + const { ClearIndicator } = components; + if (React.isValidElement(ClearIndicator)) { + return React.cloneElement(ClearIndicator, {clearValue: this.clearSearch}); + } else if (this.isFunction(ClearIndicator)) { + return ; + } + return ( + × + ); + }; + + render() { + const { placeholder, autoFocus, className, onKeyDown, disabled, style } = this.props; + const { searchValue } = this.state; + + return ( + + this.inputRef = ref} + /> + {this.renderClear()} + + ); + } +} + +SearchInput.propTypes = propTypes; + +SearchInput.defaultProps = { + wait: 100, + disabled: false, + value: '', +}; + +export default SearchInput; diff --git a/frontend/src/components/cur-dir-path/index.js b/frontend/src/components/cur-dir-path/index.js index 96249aeaad..b88fb84648 100644 --- a/frontend/src/components/cur-dir-path/index.js +++ b/frontend/src/components/cur-dir-path/index.js @@ -20,7 +20,7 @@ const propTypes = { direntList: PropTypes.array, sortBy: PropTypes.string, sortOrder: PropTypes.string, - sortItems: PropTypes.array, + sortItems: PropTypes.func, }; class CurDirPath extends React.Component { diff --git a/frontend/src/components/dialog/create-tag-dialog.js b/frontend/src/components/dialog/create-tag-dialog.js index 69d1f833ed..5107288e99 100644 --- a/frontend/src/components/dialog/create-tag-dialog.js +++ b/frontend/src/components/dialog/create-tag-dialog.js @@ -2,6 +2,7 @@ import React, { Fragment } from 'react'; import PropTypes from 'prop-types'; import { Button, ModalHeader, ModalBody, ModalFooter, Input } from 'reactstrap'; import { gettext } from '../../utils/constants'; +import { TAG_COLORS } from '../../constants'; import { seafileAPI } from '../../utils/seafile-api'; import { Utils } from '../../utils/utils'; @@ -17,10 +18,9 @@ class CreateTagDialog extends React.Component { super(props); this.state = { tagName: '', - tagColor: '', + tagColor: TAG_COLORS[0], newTag: {}, errorMsg: '', - colorList: ['#FBD44A', '#EAA775', '#F4667C', '#DC82D2', '#9860E5', '#9F8CF1', '#59CB74', '#ADDF84', '#89D2EA', '#4ECCCB', '#46A1FD', '#C2C2C2'], }; } @@ -65,14 +65,7 @@ class CreateTagDialog extends React.Component { } }; - componentDidMount() { - this.setState({ - tagColor: this.state.colorList[0] - }); - } - render() { - let colorList = this.state.colorList; let canSave = this.state.tagName.trim() ? true : false; return ( @@ -90,7 +83,7 @@ class CreateTagDialog extends React.Component {
- {colorList.map((item, index)=>{ + {TAG_COLORS.map((item, index)=>{ return (