diff --git a/package-lock.json b/package-lock.json index 1a57e48b..eae110ac 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,15 +1,15 @@ { "name": "dtable-ui-component", - "version": "6.0.25", + "version": "6.0.28", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "dtable-ui-component", - "version": "6.0.25", + "version": "6.0.28", "dependencies": { "@seafile/react-image-lightbox": "4.0.2", - "@seafile/seafile-calendar": "0.0.24", + "@seafile/seafile-calendar": "0.0.29", "@seafile/seafile-editor": "~2.0.6", "antd-mobile": "2.3.1", "classnames": "2.3.2", @@ -6142,9 +6142,9 @@ } }, "node_modules/@seafile/seafile-calendar": { - "version": "0.0.24", - "resolved": "https://registry.npmjs.org/@seafile/seafile-calendar/-/seafile-calendar-0.0.24.tgz", - "integrity": "sha512-q1efVDcHAxJ2foMgsR8mQPD6Fbd6ISu2WHRM82P7tO0KPiQNS5pz9V0YVCblgi7da085jaog2iAplJM+vH7xLQ==", + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@seafile/seafile-calendar/-/seafile-calendar-0.0.29.tgz", + "integrity": "sha512-8dg6Y8Taz31LawfnZ7T1QVbJUYCp/tFkqn8VLcFJk6A2BLlaakE2WN36vS4kfU4gyNcFZWObt4DmFj2Xc1FuiA==", "dependencies": { "babel-runtime": "6.x", "classnames": "2.x", @@ -40326,9 +40326,9 @@ } }, "@seafile/seafile-calendar": { - "version": "0.0.24", - "resolved": "https://registry.npmjs.org/@seafile/seafile-calendar/-/seafile-calendar-0.0.24.tgz", - "integrity": "sha512-q1efVDcHAxJ2foMgsR8mQPD6Fbd6ISu2WHRM82P7tO0KPiQNS5pz9V0YVCblgi7da085jaog2iAplJM+vH7xLQ==", + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@seafile/seafile-calendar/-/seafile-calendar-0.0.29.tgz", + "integrity": "sha512-8dg6Y8Taz31LawfnZ7T1QVbJUYCp/tFkqn8VLcFJk6A2BLlaakE2WN36vS4kfU4gyNcFZWObt4DmFj2Xc1FuiA==", "requires": { "babel-runtime": "6.x", "classnames": "2.x", diff --git a/package.json b/package.json index 0ffa46f9..d11201a7 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "main": "./lib/index.js", "dependencies": { "@seafile/react-image-lightbox": "4.0.2", - "@seafile/seafile-calendar": "0.0.24", + "@seafile/seafile-calendar": "0.0.29", "@seafile/seafile-editor": "~2.0.6", "antd-mobile": "2.3.1", "classnames": "2.3.2", diff --git a/src/BodyPortal/index.js b/src/BodyPortal/index.js new file mode 100644 index 00000000..7a5afe24 --- /dev/null +++ b/src/BodyPortal/index.js @@ -0,0 +1,41 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import PropTypes from 'prop-types'; + +const canUseDOM = !!( + typeof window !== 'undefined' && + window.document && + window.document.createElement +); + +class BodyPortal extends React.Component { + componentWillUnmount() { + if (this.defaultNode) { + document.body.removeChild(this.defaultNode); + } + this.defaultNode = null; + } + + render() { + if (!canUseDOM) { + return null; + } + + if (!this.props.node && !this.defaultNode) { + this.defaultNode = document.createElement('div'); + document.body.appendChild(this.defaultNode); + } + + return ReactDOM.createPortal( + this.props.children, + this.props.node || this.defaultNode, + ); + } +} + +BodyPortal.propTypes = { + children: PropTypes.node.isRequired, + node: PropTypes.any, +}; + +export default BodyPortal; diff --git a/src/CheckboxEditor/index.js b/src/CheckboxEditor/index.js index 639ed1b7..6081886a 100644 --- a/src/CheckboxEditor/index.js +++ b/src/CheckboxEditor/index.js @@ -1,108 +1,29 @@ -import React, { Component } from 'react'; +import React, { forwardRef } from 'react'; import PropTypes from 'prop-types'; -import classnames from 'classnames'; -import SvgIcon from '../SvgIcon'; -import { isMobile } from '../utils/utils'; -import { KeyCodes, DEFAULT_CHECKBOX_MARK_STYLE } from '../constants'; +import MediaQuery from 'react-responsive'; +import PCCheckboxEditor from './pc-editor'; +import MBCheckboxEditor from './mb-editor'; import './index.css'; -class CheckboxEditor extends Component { - - constructor(props) { - super(props); - this.state = { - value: props.value || false, - }; - } - - componentDidMount() { - document.addEventListener('keydown', this.onKeyDown); - } - - componentWillUnmount() { - document.removeEventListener('keydown', this.onKeyDown); - } - - componentDidUpdate(prevProps) { - const { value } = this.props; - if (value !== prevProps.value) { - this.setState({ value }); - } - } - - onKeyDown = (event) => { - const { isEditorShow, readOnly } = this.props; - if (event.keyCode === KeyCodes.Enter && isEditorShow && !readOnly) { - this.setState({ value: !this.state.value }); - } - }; - - getValue = () => { - const { value } = this.state; - return value; - }; - - updateValue = (value) => { - if (value === this.state.value) { - return; - } - this.setState({ value }); - }; - - onChangeCheckboxValue = (event) => { - if (event) { - event.stopPropagation(); - event.nativeEvent.stopImmediatePropagation(); - event.persist(); - } - const { value } = this.state; - const newValue = !value; - this.setState({ value: newValue }, () => { - if (this.props.onCommit) { - this.props.onCommit(event); - } - }); - }; - - renderIcon = (symbol, color) => { - const className = classnames('dtable-ui-checkbox-check-mark', { 'dtable-ui-checkbox-check-svg': !symbol?.startsWith('dtable-icon') }); - if (symbol.startsWith('dtable-icon')) { - return (); - } - return (); - }; - - render() { - const { className, style, column } = this.props; - const { value } = this.state; - const checkboxProps = { - ...(!isMobile && { onClick: this.onChangeCheckboxValue }), - ...(isMobile && { onTouchStart: this.onChangeCheckboxValue }), - }; - let checkboxStyle = column?.data?.checkbox_style; - if (!checkboxStyle || Object.keys(checkboxStyle).length < 2) { - checkboxStyle = DEFAULT_CHECKBOX_MARK_STYLE; - } - return ( -
- {value && this.renderIcon(checkboxStyle.type, checkboxStyle.color)} -
- ); - } -} +const CheckboxEditor = forwardRef(({ isMobile, ...props }, ref) => { + if (isMobile === false) return (); + if (isMobile === true) return (); + + return ( + <> + + + + + + + + ); +}); CheckboxEditor.propTypes = { - isEditorShow: PropTypes.bool, - className: PropTypes.string, - style: PropTypes.object, - value: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]), - column: PropTypes.object, - onCommit: PropTypes.func, + isMobile: PropTypes.bool, }; export default CheckboxEditor; diff --git a/src/CheckboxEditor/mb-editor.js b/src/CheckboxEditor/mb-editor.js new file mode 100644 index 00000000..0b1381a8 --- /dev/null +++ b/src/CheckboxEditor/mb-editor.js @@ -0,0 +1,84 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import classnames from 'classnames'; +import SvgIcon from '../SvgIcon'; +import { DEFAULT_CHECKBOX_MARK_STYLE } from '../constants'; + +class MBCheckboxEditor extends Component { + + constructor(props) { + super(props); + this.state = { + value: props.value || false, + }; + } + + componentDidUpdate(prevProps) { + const { value } = this.props; + if (value !== prevProps.value) { + this.setState({ value }); + } + } + + getValue = () => { + const { value } = this.state; + return value; + }; + + updateValue = (value) => { + if (value === this.state.value) { + return; + } + this.setState({ value }); + }; + + onChangeCheckboxValue = (event) => { + if (event) { + event.stopPropagation(); + event.nativeEvent.stopImmediatePropagation(); + event.persist(); + } + const { value } = this.state; + const newValue = !value; + this.setState({ value: newValue }, () => { + this.props.onCommit && this.props.onCommit(newValue); + }); + }; + + renderIcon = (symbol, color) => { + const className = classnames('dtable-ui-checkbox-check-mark', { 'dtable-ui-checkbox-check-svg': !symbol?.startsWith('dtable-icon') }); + if (symbol.startsWith('dtable-icon')) { + return (); + } + return (); + }; + + render() { + const { className, style, column } = this.props; + const { value } = this.state; + let checkboxStyle = column?.data?.checkbox_style; + if (!checkboxStyle || Object.keys(checkboxStyle).length < 2) { + checkboxStyle = DEFAULT_CHECKBOX_MARK_STYLE; + } + return ( +
+ {value && this.renderIcon(checkboxStyle.type, checkboxStyle.color)} +
+ ); + } +} + +MBCheckboxEditor.propTypes = { + isEditorShow: PropTypes.bool, + className: PropTypes.string, + style: PropTypes.object, + value: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]), + column: PropTypes.object, + onCommit: PropTypes.func, +}; + +export default MBCheckboxEditor; diff --git a/src/CheckboxEditor/pc-editor.js b/src/CheckboxEditor/pc-editor.js new file mode 100644 index 00000000..585afd54 --- /dev/null +++ b/src/CheckboxEditor/pc-editor.js @@ -0,0 +1,101 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import classnames from 'classnames'; +import SvgIcon from '../SvgIcon'; +import { KeyCodes, DEFAULT_CHECKBOX_MARK_STYLE } from '../constants'; + +class PCCheckboxEditor extends Component { + + constructor(props) { + super(props); + this.state = { + value: props.value || false, + }; + } + + componentDidMount() { + document.addEventListener('keydown', this.onKeyDown); + } + + componentWillUnmount() { + document.removeEventListener('keydown', this.onKeyDown); + } + + componentDidUpdate(prevProps) { + const { value } = this.props; + if (value !== prevProps.value) { + this.setState({ value }); + } + } + + onKeyDown = (event) => { + const { isEditorShow, readOnly } = this.props; + if (event.keyCode === KeyCodes.Enter && isEditorShow && !readOnly) { + this.setState({ value: !this.state.value }); + } + }; + + getValue = () => { + const { value } = this.state; + return value; + }; + + updateValue = (value) => { + if (value === this.state.value) { + return; + } + this.setState({ value }); + }; + + onChangeCheckboxValue = (event) => { + if (event) { + event.stopPropagation(); + event.nativeEvent.stopImmediatePropagation(); + event.persist(); + } + const { value } = this.state; + const newValue = !value; + this.setState({ value: newValue }, () => { + if (this.props.onCommit) { + this.props.onCommit(newValue); + } + }); + }; + + renderIcon = (symbol, color) => { + const className = classnames('dtable-ui-checkbox-check-mark', { 'dtable-ui-checkbox-check-svg': !symbol?.startsWith('dtable-icon') }); + if (symbol.startsWith('dtable-icon')) { + return (); + } + return (); + }; + + render() { + const { className, style, column } = this.props; + const { value } = this.state; + let checkboxStyle = column?.data?.checkbox_style; + if (!checkboxStyle || Object.keys(checkboxStyle).length < 2) { + checkboxStyle = DEFAULT_CHECKBOX_MARK_STYLE; + } + return ( +
+ {value && this.renderIcon(checkboxStyle.type, checkboxStyle.color)} +
+ ); + } +} + +PCCheckboxEditor.propTypes = { + isEditorShow: PropTypes.bool, + className: PropTypes.string, + style: PropTypes.object, + value: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]), + column: PropTypes.object, + onCommit: PropTypes.func, +}; + +export default PCCheckboxEditor; diff --git a/src/CollaboratorEditor/index.js b/src/CollaboratorEditor/index.js index 5f2bb882..3b4e7f3d 100644 --- a/src/CollaboratorEditor/index.js +++ b/src/CollaboratorEditor/index.js @@ -1,33 +1,31 @@ -import React from 'react'; +import React, { forwardRef } from 'react'; import PropTypes from 'prop-types'; import MediaQuery from 'react-responsive'; -import PCCollaboratorEditor from './pc-collaborator-editor'; -import MBCollaboratorEditor from './mb-collaborator-editor'; +import PCCollaboratorEditor from './pc-editor'; +import MBCollaboratorEditor from './mb-editor'; import './index.css'; -const CollaboratorEditor = ({ value: oldValue, ...props }) => { +const CollaboratorEditor = forwardRef(({ isMobile, value: oldValue, ...props }, ref) => { const value = Array.isArray(oldValue) ? oldValue : []; + if (isMobile === false) return (); + if (isMobile === true) return (); return ( <> - - + + - - + + ); -}; +}); CollaboratorEditor.propTypes = { - isReadOnly: PropTypes.bool, + isMobile: PropTypes.bool, value: PropTypes.oneOfType([PropTypes.string, PropTypes.array]), - column: PropTypes.object, - collaborators: PropTypes.array.isRequired, - onCommit: PropTypes.func, - isShowEditButton: PropTypes.bool, }; export default CollaboratorEditor; diff --git a/src/CollaboratorEditor/mb-collaborator-editor/index.css b/src/CollaboratorEditor/mb-collaborator-editor/index.css deleted file mode 100644 index 80936f14..00000000 --- a/src/CollaboratorEditor/mb-collaborator-editor/index.css +++ /dev/null @@ -1,85 +0,0 @@ -@import url('../../css/mb-cell-editor.css'); - -@media screen and (max-width: 767.8px) { - .dtable-ui-mb-collaborator-editor-body .mb-search-collaborator-items { - padding: 8px 16px; - margin-top: 20px; - border-top: 1px solid #e9e9e9; - border-bottom: 1px solid #e9e9e9; - background-color: #fff; - } - - .dtable-ui-mb-collaborator-editor-body .mb-search-collaborator-items > input { - outline: none; - height: 30px; - padding: 0; - line-height: 30px; - border: none; - } - - .dtable-ui-mb-collaborator-editor-body .mb-search-collaborator-items > input:focus, - .dtable-ui-mb-collaborator-editor-body .mb-search-collaborator-items > input:active { - outline: none; - box-shadow: none; - } - - .dtable-ui-mb-collaborator-editor-body .mb-collaborators-container { - flex: 1; - display: flex; - flex-direction: column; - margin-top: 10px; - min-height: 0; - } - - .dtable-ui-mb-collaborator-editor-body .mb-collaborators-container .title { - padding: 6px 16px; - border-bottom: 1px solid #e9e9e9; - } - - .dtable-ui-mb-collaborator-editor-body .mb-collaborators-container .content { - flex: 1; - overflow: auto; - display: flex; - flex-direction: column; - border-bottom: 1px solid #e9e9e9; - } - - .dtable-ui-mb-collaborator-editor-body .search-result-none { - padding: 16px 0 16px 16px; - } - - .dtable-ui-mb-collaborator-editor-body .mb-collaborator-option-item { - display: flex; - align-items: center; - justify-content: space-between; - height: 50px; - padding: 10px 10px 10px 16px; - font-size: 13px; - background-color: #fff; - } - - .dtable-ui-mb-collaborator-editor-body .mb-collaborator-option-item { - border-bottom: 1px solid #e9e9e9; - } - - .dtable-ui-mb-collaborator-editor-body .mb-collaborator-option-item .mb-collaborator-info { - display: flex; - align-items: center; - } - - .dtable-ui-mb-collaborator-editor-body .mb-collaborator-option-item .collaborator-avatar img { - width: 24px; - height: 24px; - border-radius: 50%; - } - - .dtable-ui-mb-collaborator-editor-body .mb-collaborator-option-item .collaborator-name { - padding: 0 10px; - overflow: hidden; - } - - .dtable-ui-mb-collaborator-editor-body .mb-collaborator-option-item .dtable-font { - font-size: 12px; - color: #798d99; - } -} diff --git a/src/CollaboratorEditor/mb-collaborator-editor/index.js b/src/CollaboratorEditor/mb-collaborator-editor/index.js deleted file mode 100644 index 11dc2c96..00000000 --- a/src/CollaboratorEditor/mb-collaborator-editor/index.js +++ /dev/null @@ -1,143 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { searchCollaborators } from 'dtable-utils'; -import MBEditorHeader from '../../MBEditorHeader'; -import DTableSearchInput from '../../DTableSearchInput'; -import { getLocale } from '../../lang'; - -import './index.css'; - -const propTypes = { - isReadOnly: PropTypes.bool.isRequired, - value: PropTypes.array.isRequired, - column: PropTypes.object.isRequired, - collaborators: PropTypes.array.isRequired, - onCommit: PropTypes.func.isRequired, - onClose: PropTypes.func, -}; - -class MBCollaboratorEditor extends React.Component { - - static defaultProps = { - isReadOnly: false, - value: [], - }; - - constructor(props) { - super(props); - this.state = { - searchVal: '', - }; - } - - componentDidMount() { - history.pushState(null, null, '#'); // eslint-disable-line - window.addEventListener('popstate', this.handleHistoryBack, false); - } - - componentWillUnmount() { - window.removeEventListener('popstate', this.handleHistoryBack, false); - } - - handleHistoryBack = (e) => { - e.preventDefault(); - this.props.onClosePopover(); - }; - - onContainerClick = (event) => { - if (this.editorPopover && this.editorPopover.contains(event.target)) { - event.stopPropagation(); - event.nativeEvent.stopImmediatePropagation(); - return false; - } - }; - - onChangeSearch = (newValue) => { - let { searchVal } = this.state; - if (searchVal === newValue) return; - this.setState({ searchVal: newValue }); - }; - - getSelectedCollaborators = () => { - let { value, collaborators } = this.props; - if (!Array.isArray(value)) { - return []; - } - return collaborators.filter(collaborator => { - return value.indexOf(collaborator.email) > -1; - }); - }; - - getFilteredCollaborators = () => { - let { collaborators } = this.props; - let { searchVal } = this.state; - return searchVal ? searchCollaborators(collaborators, searchVal) : collaborators; - }; - - onCollaboratorClick = (collaborator) => { - this.props.onCommit(collaborator); - }; - - onRemoveCollaborator = (collaborator) => { - this.props.onCommit(collaborator); - }; - - renderFilteredCollaborators = (collaborators) => { - let { value = [] } = this.props; - return collaborators.map((collaborator, index) => { - const isSelected = value.includes(collaborator.email); - - return ( -
- - - avatar - - {collaborator.name} - - {isSelected && ()} -
- ); - }); - }; - - setEditorPopover = (editorPopover) => { - this.editorPopover = editorPopover; - }; - - render() { - const { column } = this.props; - const { searchVal } = this.state; - let filteredCollaborators = this.getFilteredCollaborators(); - - return ( -
- )} - rightContent={({getLocale('Done')})} - onLeftClick={this.props.onClose} - onRightClick={this.props.onClose} - /> -
-
- -
-
-
{getLocale('Choose_a_collaborator')}
-
- {filteredCollaborators.length === 0 && ( -
{getLocale('No_collaborators_available')}
- )} - {filteredCollaborators.length > 0 && this.renderFilteredCollaborators(filteredCollaborators)} -
-
-
-
- ); - } -} - -MBCollaboratorEditor.propTypes = propTypes; - -export default MBCollaboratorEditor; diff --git a/src/CollaboratorEditor/mb-editor/index.css b/src/CollaboratorEditor/mb-editor/index.css new file mode 100644 index 00000000..7e976cb8 --- /dev/null +++ b/src/CollaboratorEditor/mb-editor/index.css @@ -0,0 +1,41 @@ +@import url('../../css/mb-cell-editor.css'); + +.dtable-ui-mobile-collaborator-editor .dtable-ui-mobile-selector-option .dtable-ui-mobile-selector-option-name { + height: 36px !important; + line-height: 36px !important; + margin-top: 0px !important; +} + +.dtable-ui-mobile-selector.dtable-ui-mobile-collaborator-editor .dtable-ui-mobile-selector-option { + font-size: 16px; +} + +.dtable-ui-mobile-collaborator-editor .dtable-ui-mobile-selector-option .dtable-ui.collaborator-item { + background-color: inherit; + border-radius: 0; + height: 100%; + width: 100%; +} + +.dtable-ui-mobile-collaborator-editor .dtable-ui-mobile-selector-option .dtable-ui.collaborator-item .collaborator-avatar { + height: 2rem; + margin: 2px 0; + vertical-align: middle; + width: 2rem; +} + +.dtable-ui-mobile-collaborator-editor .dtable-ui-mobile-selector-option .dtable-ui.collaborator-item .collaborator-avatar-icon { + border-radius: 50%; + height: 2rem; + width: 2rem; +} + +.dtable-ui-mobile-collaborator-editor .dtable-ui-mobile-selector-option .dtable-ui.collaborator-item .collaborator-name { + height: 100%; + font-size: 16px; + line-height: 36px; + width: 100%; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} diff --git a/src/CollaboratorEditor/mb-editor/index.js b/src/CollaboratorEditor/mb-editor/index.js new file mode 100644 index 00000000..6870477f --- /dev/null +++ b/src/CollaboratorEditor/mb-editor/index.js @@ -0,0 +1,89 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { searchCollaborators } from 'dtable-utils'; +import MobileSelector from '../../MobileSelector'; +import { getLocale } from '../../lang'; +import CollaboratorItem from '../../CollaboratorItem'; + +import './index.css'; + +const { Search, Option, Options, Empty } = MobileSelector; + +class MBCollaboratorEditor extends React.Component { + + static defaultProps = { + value: [], + }; + + constructor(props) { + super(props); + this.state = { + searchVal: '', + }; + } + + componentDidMount() { + history.pushState(null, null, '#'); // eslint-disable-line + window.addEventListener('popstate', this.handleHistoryBack, false); + } + + componentWillUnmount() { + window.removeEventListener('popstate', this.handleHistoryBack, false); + } + + handleHistoryBack = (e) => { + e.preventDefault(); + this.props.onClose(); + }; + + onChangeSearch = (newValue) => { + let { searchVal } = this.state; + if (searchVal === newValue) return; + this.setState({ searchVal: newValue }); + }; + + onChange = (collaborator) => { + this.props.onCommit(collaborator); + }; + + renderCollaborators = (collaborators) => { + const { value = [] } = this.props; + return collaborators.map(collaborator => { + const isSelected = value.includes(collaborator.email); + + return ( + + ); + }); + }; + + render() { + const { column, collaborators } = this.props; + const { searchVal } = this.state; + const displayCollaborators = searchVal ? searchCollaborators(collaborators, searchVal) : collaborators; + + return ( + + {collaborators.length > 10 && ( + + )} + + {displayCollaborators.length === 0 && ({getLocale('No_collaborators_available')})} + {displayCollaborators.length > 0 && this.renderCollaborators(displayCollaborators)} + + + ); + } +} + +MBCollaboratorEditor.propTypes = { + value: PropTypes.array.isRequired, + column: PropTypes.object.isRequired, + collaborators: PropTypes.array.isRequired, + onCommit: PropTypes.func.isRequired, + onClose: PropTypes.func, +}; + +export default MBCollaboratorEditor; diff --git a/src/CollaboratorEditor/pc-collaborator-editor/index.css b/src/CollaboratorEditor/pc-editor/index.css similarity index 100% rename from src/CollaboratorEditor/pc-collaborator-editor/index.css rename to src/CollaboratorEditor/pc-editor/index.css diff --git a/src/CollaboratorEditor/pc-collaborator-editor/index.js b/src/CollaboratorEditor/pc-editor/index.js similarity index 100% rename from src/CollaboratorEditor/pc-collaborator-editor/index.js rename to src/CollaboratorEditor/pc-editor/index.js diff --git a/src/DTableCommonAddTool/index.js b/src/DTableCommonAddTool/index.js index 604d58a4..f4fd7e9f 100644 --- a/src/DTableCommonAddTool/index.js +++ b/src/DTableCommonAddTool/index.js @@ -5,7 +5,7 @@ import './index.css'; function DTableCommonAddTool({ callBack, footerName, className, addIconClassName, hideIcon, style }) { return ( -
{callBack(e);}}> +
callBack(e)}> {!hideIcon && } {footerName}
diff --git a/src/DTableFiltersPopover/widgets/department-select-filter/department-multiple-select-filter.js b/src/DTableFiltersPopover/widgets/department-select-filter/department-multiple-select-filter.js index 0cc327c6..fbc4e121 100644 --- a/src/DTableFiltersPopover/widgets/department-select-filter/department-multiple-select-filter.js +++ b/src/DTableFiltersPopover/widgets/department-select-filter/department-multiple-select-filter.js @@ -1,9 +1,9 @@ import React, { useState, useRef } from 'react'; import PropTypes from 'prop-types'; import classnames from 'classnames'; -import SelectedDepartments from '../../../Department-editor/selected-departments'; -import DepartmentMultipleSelect from '../../../Department-editor/department-multiple-select'; -import { DEPARTMENT_SELECT_RANGE_OPTIONS } from '../../../Department-editor/constants'; +import SelectedDepartments from '../../../SelectedDepartments'; +import DepartmentMultipleSelectEditor from '../../../DepartmentMultipleSelectEditor'; +import { DEPARTMENT_SELECT_RANGE_OPTIONS } from '../../../constants/departments'; import { useClickOutside } from '../../../hooks/common-hooks'; import { getLocale } from '../../../lang'; @@ -85,7 +85,7 @@ function DepartmentMultipleSelectFilter(props) {
{isShowSelector && -
{isShowSelector && - { + const [isDateInit, setDateInit] = useState(false); + const [value, setValue] = useState(oldValue || ''); -class DateEditor extends React.Component { - - static defaultProps = { - isReadOnly: false, - value: '', - lang: 'en' - }; + const format = useMemo(() => { + return (column?.data && column?.data?.format) || DEFAULT_DATE_FORMAT; + }, [column]); + const showHourAndMinute = useMemo(() => format.indexOf('HH:mm') > -1, [format]); - constructor(props) { - super(props); - this.state = { - isDateInit: false, - newValue: '', - dateFormat: '', - showHourAndMinute: false, - defaultCalendarValue: null, - }; - } + const handelCommit = useCallback((newValue) => { + if (value !== newValue) { + setValue(newValue); + onCommit(newValue); + } + }, [value, onCommit]); - componentDidMount() { - const { value, lang } = this.props; + useEffect(() => { dayjs.locale(lang); - let dateFormat = this.getDateFormat(); - this.setState({ - isDateInit: true, - newValue: value, - dateFormat, - showHourAndMinute: dateFormat.indexOf('HH:mm') > -1, - }); - } + setDateInit(true); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); - getDateFormat = () => { - let { column } = this.props; - let defaultDateFormat = 'YYYY-MM-DD'; - let dateFormat = column.data && column.data.format; - return dateFormat || defaultDateFormat; - }; + if (!isDateInit) return null; - onValueChanged = (value) => { - if (value !== this.state.newValue) { - this.setState({ newValue: value }); - this.onCommit(value); - } + const props = { + lang, + value, + dateFormat: format, + showHourAndMinute, + firstDayOfWeek, + className, + onCommit: handelCommit, + onClose: hideCalendar, }; - onCommit = (newValue) => { - this.props.onCommit(newValue); - }; - - render() { - if (!this.state.isDateInit) { - return null; - } - - let { lang, column, className, isInModal, firstDayOfWeek } = this.props; - let { newValue, dateFormat, showHourAndMinute } = this.state; + if (isMobile === false) return (); + if (isMobile === true) return (); - return ( - <> - - - - - - - - ); - } -} + return ( + <> + + + + + + + + ); +}); -DateEditor.propTypes = propTypes; +DateEditor.propTypes = { + isMobile: PropTypes.bool, + isReadOnly: PropTypes.bool, + isInModal: PropTypes.bool, + value: PropTypes.string, + lang: PropTypes.string, + className: PropTypes.string, + column: PropTypes.object.isRequired, + onCommit: PropTypes.func.isRequired, + onClose: PropTypes.func, + firstDayOfWeek: PropTypes.string, +}; export default DateEditor; diff --git a/src/DateEditor/mb-date-editor-popover/custom-rc-calendar.css b/src/DateEditor/mb-date-editor-popover/custom-rc-calendar.css deleted file mode 100644 index f559e0bf..00000000 --- a/src/DateEditor/mb-date-editor-popover/custom-rc-calendar.css +++ /dev/null @@ -1,118 +0,0 @@ -.dtable-ui.mb-date-editor-picker .rc-calendar-date { - width: 28px; - height: 30px; - border-radius: 50%; - font-size: 14px; - line-height: 30px; -} - -.dtable-ui.mb-date-editor-picker .rc-calendar-date-panel { - width: 100%; -} - -.dtable-ui.mb-date-editor-picker .rc-calendar .rc-calendar-selected-day .rc-calendar-date { - background-color: #FcEcD9; -} - -.dtable-ui.mb-date-editor-picker .rc-calendar .rc-calendar-date:hover, -.dtable-ui.mb-date-editor-picker .rc-calendar .rc-calendar-year-panel-year:hover, -.dtable-ui.mb-date-editor-picker .rc-calendar .rc-calendar-month-panel-cell .rc-calendar-month-panel-month:hover { - background-color: #FcEcD9; -} - -.dtable-ui.mb-date-editor-picker .rc-calendar .rc-calendar-selected-date .rc-calendar-date, -.dtable-ui.mb-date-editor-picker .rc-calendar .rc-calendar-selected-date .rc-calendar-date:hover, -.dtable-ui.mb-date-editor-picker .rc-calendar .rc-calendar-year-panel-selected-cell .rc-calendar-year-panel-year, -.dtable-ui.mb-date-editor-picker .rc-calendar .rc-calendar-month-panel-selected-cell .rc-calendar-month-panel-month { - background-color: #f09f3f; -} - -.dtable-ui.mb-date-editor-picker .rc-calendar .rc-calendar-next-year-btn:not([href]):not([tabindex]), -.dtable-ui.mb-date-editor-picker .rc-calendar .rc-calendar-prev-year-btn:not([href]):not([tabindex]), -.dtable-ui.mb-date-editor-picker .rc-calendar .rc-calendar-next-month-btn:not([href]):not([tabindex]), -.dtable-ui.mb-date-editor-picker .rc-calendar .rc-calendar-prev-month-btn:not([href]):not([tabindex]) { - color: #999; -} - -.dtable-ui.mb-date-editor-picker .rc-calendar .rc-calendar-next-year-btn:not([href]):not([tabindex]):hover, -.dtable-ui.mb-date-editor-picker .rc-calendar .rc-calendar-prev-year-btn:not([href]):not([tabindex]):hover, -.dtable-ui.mb-date-editor-picker .rc-calendar .rc-calendar-next-month-btn:not([href]):not([tabindex]):hover, -.dtable-ui.mb-date-editor-picker .rc-calendar .rc-calendar-prev-month-btn:not([href]):not([tabindex]):hover { - color: #666; -} - -.dtable-ui.mb-date-editor-picker .rc-calendar .rc-calendar-today .rc-calendar-date { - border: none; - position: relative; -} - -.dtable-ui.mb-date-editor-picker .rc-calendar .rc-calendar-today .rc-calendar-date::after { - content: ''; - background: #f09f3f; - position: absolute; - width: 4px; - height: 4px; - border-radius: 50%; - left: 45%; - bottom: 0%; - display: inline-block; -} - -.dtable-ui.mb-date-editor-picker .rc-calendar .rdg-editor-container input.editor-main[readonly] { - background-color: #fff; -} - -.dtable-ui.mb-date-editor-picker .rc-calendar-time-picker { - z-index: 1; -} - -.dtable-ui.mb-date-editor-picker .rc-calendar-right-panel-body ul li.highlight { - margin: 0; - border: none; - background-color: #fff; - font-size: 12px; -} - -.dtable-ui.mb-date-editor-picker .rc-calendar { - box-shadow: none; - border-radius: 0px; - border: 1px solid #e9e9e9; - border-left: none; - border-right: none; -} - -.dtable-ui.mb-date-editor-picker .rc-calendar-date { - font-size: 14px; -} - -.dtable-ui.mb-date-editor-picker .rc-calendar-date-panel { - width: 100%; -} - -.dtable-ui.mb-date-editor-picker .rc-calendar-body { - height: 22rem; -} - -.dtable-ui.mb-date-editor-picker .rc-calendar-table tr { - height: 3rem; -} - -.dtable-ui.mb-date-editor-picker .rc-calendar-prev-month-btn, -.dtable-ui.mb-date-editor-picker .rc-calendar-next-month-btn, -.dtable-ui.mb-date-editor-picker .rc-calendar-prev-year-btn, -.dtable-ui.mb-date-editor-picker .rc-calendar-next-year-btn { - font-size: 30px; - width: 50px; -} - -.dtable-ui.mb-date-editor-picker .rc-calendar-header { - border-bottom: none; - margin-top: 0.5rem; -} - -.dtable-ui.mb-date-editor-picker .rc-calendar-year-select, -.dtable-ui.mb-date-editor-picker .rc-calendar-month-select, -.dtable-ui.mb-date-editor-picker .rc-calendar-day-select { - font-size: 16px; - padding: 5px; -} diff --git a/src/DateEditor/mb-date-editor-popover/index.css b/src/DateEditor/mb-date-editor-popover/index.css deleted file mode 100644 index c99f9406..00000000 --- a/src/DateEditor/mb-date-editor-popover/index.css +++ /dev/null @@ -1,45 +0,0 @@ -@import url('./custom-rc-calendar.css'); -@import url('../../css/mb-cell-editor.css'); - -@media screen and (max-width: 767.8px) { - .dtable-ui-mb-date-editor-body .mb-date-editor-input { - display: flex; - border-top: 1px solid #e9e9e9; - border-bottom: 1px solid #e9e9e9; - } - - .dtable-ui-mb-date-editor-body .mb-date-editor-input .date-input { - flex: 1; - background-color: #fff; - } - - .dtable-ui-mb-date-editor-body .mb-date-editor-input .date-input .date-input-day { - background-color: #f5f5f5; - border: 1px solid #e9e9e9; - margin: 10px; - padding: 3px 5px; - border-radius: 3px; - } - - .dtable-ui-mb-date-editor-body .dtable-ui.mb-date-editor-picker { - display: block; - position: relative; - margin-top: 20px; - line-height: 1.5; - box-sizing: border-box; - } - - .dtable-ui-mb-date-editor-body .mb-date-editor-clear { - padding: 10px 0; - margin-top: 20px; - border-bottom: 1px solid #e9e9e9; - border-top: 1px solid #e9e9e9; - text-align: center; - color: #d27053; - background-color: #fff; - } - - .dtable-ui .am-picker-popup-header .am-picker-popup-item { - color: #E5A252; - } -} diff --git a/src/DateEditor/mb-date-editor-popover/index.js b/src/DateEditor/mb-date-editor-popover/index.js deleted file mode 100644 index 4ff3b414..00000000 --- a/src/DateEditor/mb-date-editor-popover/index.js +++ /dev/null @@ -1,208 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { DatePicker } from 'antd-mobile'; -import classnames from 'classnames'; -import dayjs from 'dayjs'; -import Calendar from '@seafile/seafile-calendar'; -import * as SeaDatePicker from '@seafile/seafile-calendar/lib/Picker'; -import { getLocale } from '../../lang'; -import { initDateEditorLanguage } from '../../utils/editor-utils'; -import MBEditorHeader from '../../MBEditorHeader'; - -import '@seafile/seafile-calendar/assets/index.css'; -import './index.css'; - -const propTypes = { - isReadOnly: PropTypes.bool, - lang: PropTypes.string.isRequired, - value: PropTypes.string, - dateFormat: PropTypes.string.isRequired, - showHourAndMinute: PropTypes.bool.isRequired, - className: PropTypes.string, - column: PropTypes.object.isRequired, - onValueChanged: PropTypes.func.isRequired, - onClosePopover: PropTypes.func, - firstDayOfWeek: PropTypes.string, -}; - -class DateEditorPopover extends React.PureComponent { - - constructor(props) { - super(props); - this.state = { - open: true, - datePickerValue: props.value ? dayjs(props.value) : dayjs().clone(), - }; - this.calendarContainerRef = React.createRef(); - } - - componentDidMount() { - history.pushState(null, null, '#'); // eslint-disable-line - window.addEventListener('popstate', this.handleHistaryBack, false); - } - - componentWillUnmount() { - window.removeEventListener('popstate', this.handleHistaryBack, false); - } - - handleHistaryBack = (e) => { - e.preventDefault(); - this.closePopover(); - }; - - handleDateChange = (date) => { - let { dateFormat, showHourAndMinute } = this.props; - let newValue = dayjs(date); - if (showHourAndMinute) { - const { datePickerValue } = this.state; - const HM = datePickerValue.format('HH:mm'); - const format = dateFormat.split(' ')[0]; // 'YYYY-MM-DD HH:mm' - const newDate = dayjs(date).format(format) + ' ' + HM; - newValue = dayjs(newDate); - } - this.setState({ datePickerValue: dayjs(date) }); - this.props.onValueChanged(newValue.format(dateFormat)); - }; - - handleTimeChange = (time) => { - const { datePickerValue } = this.state; - const { dateFormat } = this.props; - const format = dateFormat.split(' ')[0]; // 'YYYY-MM-DD HH:mm' - const YMD = datePickerValue.format(format); - const newDate = YMD + ' ' + dayjs(time).format('HH:mm'); - const newValue = dayjs(newDate); - - this.setState({ datePickerValue: newValue }); - this.props.onValueChanged(datePickerValue.format(dateFormat)); - }; - - closePopover = () => { - this.props.onClosePopover(); - }; - - deleteDate = () => { - this.props.onValueChanged(''); - this.closePopover(); - }; - - onChange = (value) => { - if (!value) return; - let { dateFormat } = this.props; - this.setState({ datePickerValue: value, }); - this.props.onValueChanged(value.format(dateFormat)); - }; - - onContainerClick = (event) => { - if (this.editorPopover && this.editorPopover.contains(event.target)) { - event.stopPropagation(); - event.nativeEvent.stopImmediatePropagation(); - return false; - } - }; - - setEditorPopover = (editorPopover) => { - this.editorPopover = editorPopover; - }; - - getCalendarContainer = () => { - return this.calendarContainerRef.current; - }; - - getCalender = () => { - let { dateFormat, lang, className, firstDayOfWeek } = this.props; - let defaultValue = dayjs().clone(); - return ( - - ); - }; - - renderDataPicker = () => { - let { dateFormat } = this.props; - let { datePickerValue } = this.state; - let calendar = this.getCalender(); - - return ( - - {({ value }) => { - value = value && value.format(dateFormat); - return ( -
- -
-
- ); - }} - - ); - }; - - render() { - const { lang, column, dateFormat, showHourAndMinute } = this.props; - const leftFormat = dateFormat.split(' ')[0]; - const rightFormat = dateFormat.split(' ')[1]; - const { datePickerValue } = this.state; - - return ( -
- )} - rightContent={({getLocale('Done')})} - onLeftClick={this.props.onClosePopover} - onRightClick={this.props.onClosePopover} - /> -
-
-
- -
{datePickerValue && datePickerValue.format(leftFormat)}
-
-
- {showHourAndMinute && -
- -
{datePickerValue && datePickerValue.format(rightFormat)}
-
-
- } -
-
- {this.renderDataPicker()} -
-
-
{getLocale('Clear')}
-
-
-
- ); - } -} - -DateEditorPopover.propTypes = propTypes; - -export default DateEditorPopover; diff --git a/src/DateEditor/mb-editor/custom-rc-calendar.css b/src/DateEditor/mb-editor/custom-rc-calendar.css new file mode 100644 index 00000000..a5a07cf1 --- /dev/null +++ b/src/DateEditor/mb-editor/custom-rc-calendar.css @@ -0,0 +1,118 @@ +.dtable-ui.dtable-ui-mobile-date-editor-picker .rc-calendar-date { + width: 28px; + height: 30px; + border-radius: 50%; + font-size: 14px; + line-height: 30px; +} + +.dtable-ui.dtable-ui-mobile-date-editor-picker .rc-calendar-date-panel { + width: 100%; +} + +.dtable-ui.dtable-ui-mobile-date-editor-picker .rc-calendar .rc-calendar-selected-day .rc-calendar-date { + background-color: #FcEcD9; +} + +.dtable-ui.dtable-ui-mobile-date-editor-picker .rc-calendar .rc-calendar-date:hover, +.dtable-ui.dtable-ui-mobile-date-editor-picker .rc-calendar .rc-calendar-year-panel-year:hover, +.dtable-ui.dtable-ui-mobile-date-editor-picker .rc-calendar .rc-calendar-month-panel-cell .rc-calendar-month-panel-month:hover { + background-color: #FcEcD9; +} + +.dtable-ui.dtable-ui-mobile-date-editor-picker .rc-calendar .rc-calendar-selected-date .rc-calendar-date, +.dtable-ui.dtable-ui-mobile-date-editor-picker .rc-calendar .rc-calendar-selected-date .rc-calendar-date:hover, +.dtable-ui.dtable-ui-mobile-date-editor-picker .rc-calendar .rc-calendar-year-panel-selected-cell .rc-calendar-year-panel-year, +.dtable-ui.dtable-ui-mobile-date-editor-picker .rc-calendar .rc-calendar-month-panel-selected-cell .rc-calendar-month-panel-month { + background-color: #f09f3f; +} + +.dtable-ui.dtable-ui-mobile-date-editor-picker .rc-calendar .rc-calendar-next-year-btn:not([href]):not([tabindex]), +.dtable-ui.dtable-ui-mobile-date-editor-picker .rc-calendar .rc-calendar-prev-year-btn:not([href]):not([tabindex]), +.dtable-ui.dtable-ui-mobile-date-editor-picker .rc-calendar .rc-calendar-next-month-btn:not([href]):not([tabindex]), +.dtable-ui.dtable-ui-mobile-date-editor-picker .rc-calendar .rc-calendar-prev-month-btn:not([href]):not([tabindex]) { + color: #999; +} + +.dtable-ui.dtable-ui-mobile-date-editor-picker .rc-calendar .rc-calendar-next-year-btn:not([href]):not([tabindex]):hover, +.dtable-ui.dtable-ui-mobile-date-editor-picker .rc-calendar .rc-calendar-prev-year-btn:not([href]):not([tabindex]):hover, +.dtable-ui.dtable-ui-mobile-date-editor-picker .rc-calendar .rc-calendar-next-month-btn:not([href]):not([tabindex]):hover, +.dtable-ui.dtable-ui-mobile-date-editor-picker .rc-calendar .rc-calendar-prev-month-btn:not([href]):not([tabindex]):hover { + color: #666; +} + +.dtable-ui.dtable-ui-mobile-date-editor-picker .rc-calendar .rc-calendar-today .rc-calendar-date { + border: none; + position: relative; +} + +.dtable-ui.dtable-ui-mobile-date-editor-picker .rc-calendar .rc-calendar-today .rc-calendar-date::after { + content: ''; + background: #f09f3f; + position: absolute; + width: 4px; + height: 4px; + border-radius: 50%; + left: 45%; + bottom: 0%; + display: inline-block; +} + +.dtable-ui.dtable-ui-mobile-date-editor-picker .rc-calendar .rdg-editor-container input.editor-main[readonly] { + background-color: #fff; +} + +.dtable-ui.dtable-ui-mobile-date-editor-picker .rc-calendar-time-picker { + z-index: 1; +} + +.dtable-ui.dtable-ui-mobile-date-editor-picker .rc-calendar-right-panel-body ul li.highlight { + margin: 0; + border: none; + background-color: #fff; + font-size: 12px; +} + +.dtable-ui.dtable-ui-mobile-date-editor-picker .rc-calendar { + box-shadow: none; + border-radius: 0px; + border: 1px solid #e9e9e9; + border-left: none; + border-right: none; +} + +.dtable-ui.dtable-ui-mobile-date-editor-picker .rc-calendar-date { + font-size: 14px; +} + +.dtable-ui.dtable-ui-mobile-date-editor-picker .rc-calendar-date-panel { + width: 100%; +} + +.dtable-ui.dtable-ui-mobile-date-editor-picker .rc-calendar-body { + height: 22rem; +} + +.dtable-ui.dtable-ui-mobile-date-editor-picker .rc-calendar-table tr { + height: 3rem; +} + +.dtable-ui.dtable-ui-mobile-date-editor-picker .rc-calendar-prev-month-btn, +.dtable-ui.dtable-ui-mobile-date-editor-picker .rc-calendar-next-month-btn, +.dtable-ui.dtable-ui-mobile-date-editor-picker .rc-calendar-prev-year-btn, +.dtable-ui.dtable-ui-mobile-date-editor-picker .rc-calendar-next-year-btn { + font-size: 30px; + width: 50px; +} + +.dtable-ui.dtable-ui-mobile-date-editor-picker .rc-calendar-header { + border-bottom: none; + margin-top: 0.5rem; +} + +.dtable-ui.dtable-ui-mobile-date-editor-picker .rc-calendar-year-select, +.dtable-ui.dtable-ui-mobile-date-editor-picker .rc-calendar-month-select, +.dtable-ui.dtable-ui-mobile-date-editor-picker .rc-calendar-day-select { + font-size: 16px; + padding: 5px; +} diff --git a/src/DateEditor/mb-editor/index.css b/src/DateEditor/mb-editor/index.css new file mode 100644 index 00000000..0512c2da --- /dev/null +++ b/src/DateEditor/mb-editor/index.css @@ -0,0 +1,39 @@ +@import url('./custom-rc-calendar.css'); + +.dtable-ui-mobile-date-editor .dtable-ui-mobile-date-editor-input { + display: flex; + border-bottom: 1px solid #e9e9e9; +} + +.dtable-ui-mobile-date-editor .dtable-ui-mobile-date-editor-input .date-input { + flex: 1; + background-color: #fff; +} + +.dtable-ui-mobile-date-editor .dtable-ui-mobile-date-editor-input .date-input .date-input-day { + background-color: #f5f5f5; + border: 1px solid #e9e9e9; + margin: 10px; + padding: 3px 5px; + border-radius: 3px; +} + +.dtable-ui-mobile-date-editor .dtable-ui.dtable-ui-mobile-date-editor-picker { + display: block; + position: relative; + margin-top: 20px; + line-height: 1.5; + box-sizing: border-box; +} + +.dtable-ui-mobile-date-editor .dtable-ui-mobile-date-editor-clear { + padding: 10px 0; + border-bottom: 1px solid #e9e9e9; + text-align: center; + color: #d27053; + background-color: #fff; +} + +.dtable-ui .am-picker-popup-header .am-picker-popup-item { + color: #E5A252; +} diff --git a/src/DateEditor/mb-editor/index.js b/src/DateEditor/mb-editor/index.js new file mode 100644 index 00000000..10272b84 --- /dev/null +++ b/src/DateEditor/mb-editor/index.js @@ -0,0 +1,186 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { DatePicker } from 'antd-mobile'; +import classnames from 'classnames'; +import dayjs from 'dayjs'; +import 'dayjs/locale/zh-cn'; +import 'dayjs/locale/en-gb'; +import Calendar from '@seafile/seafile-calendar'; +import Picker from '@seafile/seafile-calendar/lib/Picker'; +import { getLocale } from '../../lang'; +import { initDateEditorLanguage, minDate, maxDate } from '../../utils/editor-utils'; +import MobileFullScreenPage from '../../MobileFullScreenPage'; + +import '@seafile/seafile-calendar/assets/index.css'; +import './index.css'; + +let now = dayjs(); +const { Header, Body } = MobileFullScreenPage; + +class MBDateEditor extends React.PureComponent { + + constructor(props) { + super(props); + const { dateFormat, showHourAndMinute } = props; + this.state = { + time: null, + }; + this.calendarContainerRef = React.createRef(); + this.leftFormat = dateFormat ? (dateFormat.indexOf(' ') === -1 ? dateFormat : dateFormat.slice(0, dateFormat.indexOf(' '))) : 'YYYY-MM-DD'; + this.rightFormat = 'HH:mm'; + this.showTime = showHourAndMinute; + this.format = showHourAndMinute ? 'YYYY-MM-DD HH:mm' : 'YYYY-MM-DD'; + } + + componentDidMount() { + let { value, lang } = this.props; + const iszhcn = lang === 'zh-cn'; + if (iszhcn) { + now = now.locale('zh-cn'); + } else { + now = now.locale('en-gb'); + } + if (!value) { + value = new Date(Date.now()); + } + this.defaultCalendarValue = now.clone(); + this.setState({ time: iszhcn ? dayjs(value).locale('zh-cn') : dayjs(value).locale('en-gb') }); + } + + handleDateChange = (date) => { + if (this.showTime) { + const HM = dayjs(this.state.time).format('HH:mm'); + // In iOS, the time standard is ISO-8601, new Date("YYYY-MM-DD") will be wrong, new Date("YYYY/MM/DD") will be OK. + const newTime = dayjs(date).format('YYYY/MM/DD') + ' ' + HM; + this.setState({ time: new Date(newTime) }); + } else { + this.setState({ time: date }); + } + }; + + handleTimeChange = (time) => { + // In iOS, the time standard is ISO-8601, new Date("YYYY-MM-DD") will be wrong, new Date("YYYY/MM/DD") will be OK. + const YMD = dayjs(this.state.time).format('YYYY/MM/DD'); + const newTime = YMD + ' ' + dayjs(time).format('HH:mm'); + this.setState({ time: new Date(newTime) }); + }; + + onSave = () => { + const value = dayjs(this.state.time).format(this.format); + this.props.onCommit(value); + this.props.onClose(); + }; + + deleteDate = () => { + this.props.onCommit(null); + this.props.onClose(); + }; + + getCalendarContainer = () => { + return this.calendarContainerRef.current; + }; + + onChange = (value) => { + if (!value) return; + const newTime = dayjs(value.format(this.format)); + this.setState({ time: newTime }); + }; + + renderCalender = () => { + const { lang, className, firstDayOfWeek } = this.props; + const { time } = this.state; + const calendar = ( + + ); + + return ( + + {({ time }) => { + return ( +
+ +
+
+ ); + }} + + ); + }; + + render() { + const { lang, column } = this.props; + const { time } = this.state; + + return ( + +
+ <>{getLocale('Cancel')} + <>{column.name} + {getLocale('Done')} +
+ +
+
+ +
{dayjs(time).format(this.leftFormat)}
+
+
+ {this.showTime && +
+ +
{dayjs(time).format(this.rightFormat)}
+
+
+ } +
+
+ {this.renderCalender()} +
+
+
{getLocale('Clear')}
+
+ +
+ ); + } +} + +MBDateEditor.propTypes = { + isReadOnly: PropTypes.bool, + lang: PropTypes.string.isRequired, + value: PropTypes.string, + dateFormat: PropTypes.string.isRequired, + showHourAndMinute: PropTypes.bool.isRequired, + className: PropTypes.string, + column: PropTypes.object.isRequired, + onCommit: PropTypes.func.isRequired, + onClose: PropTypes.func, + firstDayOfWeek: PropTypes.string, +}; + +export default MBDateEditor; diff --git a/src/DateEditor/pc-date-editor-popover.js b/src/DateEditor/pc-editor.js similarity index 92% rename from src/DateEditor/pc-date-editor-popover.js rename to src/DateEditor/pc-editor.js index 96c3aab0..e8644e7a 100644 --- a/src/DateEditor/pc-date-editor-popover.js +++ b/src/DateEditor/pc-editor.js @@ -6,24 +6,13 @@ import DatePicker from '@seafile/seafile-calendar/lib/Picker'; import Calendar from '@seafile/seafile-calendar'; import { initDateEditorLanguage } from '../utils/editor-utils'; import { KeyCodes } from '../constants'; +import { getLocale } from '../lang'; import '@seafile/seafile-calendar/assets/index.css'; -import { getLocale } from '../lang'; let now = dayjs(); -const propTypes = { - isInModal: PropTypes.bool, - lang: PropTypes.string.isRequired, - value: PropTypes.string, - dateFormat: PropTypes.string.isRequired, - className: PropTypes.string, - showHourAndMinute: PropTypes.bool.isRequired, - onValueChanged: PropTypes.func.isRequired, - firstDayOfWeek: PropTypes.string, -}; - -class PCDateEditorPopover extends React.Component { +class PCDateEditor extends React.Component { constructor(props) { super(props); @@ -47,11 +36,11 @@ class PCDateEditorPopover extends React.Component { value: value, open: true, // if value changed, don't close datePicker }); - this.props.onValueChanged(value.format(dateFormat)); + this.props.onCommit(value.format(dateFormat)); }; onBlur = () => { - this.props.onValueChanged(this.getValue()); + this.props.onCommit(this.getValue()); }; getValue = () => { @@ -74,7 +63,7 @@ class PCDateEditorPopover extends React.Component { this.toggleCalendar(open); } if (isInModal && !open) { - this.props.hideCalendar && this.props.hideCalendar(); + this.props.onClose && this.props.onClose(); } }; @@ -84,7 +73,7 @@ class PCDateEditorPopover extends React.Component { onClear = () => { this.setState({ value: null }); - this.props.onValueChanged(null); + this.props.onCommit(null); }; onFocusDatePicker = () => { @@ -131,7 +120,7 @@ class PCDateEditorPopover extends React.Component { onClickRightPanelTime = () => { const { isInModal } = this.props; - let onClickRightPanelTime = isInModal ? this.props.hideCalendar : this.closeEditor; + let onClickRightPanelTime = isInModal ? this.props.onClose : this.closeEditor; // we should change value and save it(async function), then close Editor. setTimeout(() => { onClickRightPanelTime && onClickRightPanelTime(); @@ -181,7 +170,7 @@ class PCDateEditorPopover extends React.Component { if (e.keyCode === KeyCodes.Escape) { if (this.props.isInModal) { e.stopPropagation(); - this.props.hideCalendar && this.props.hideCalendar(); + this.props.onClose && this.props.onClose(); } } }; @@ -258,6 +247,15 @@ class PCDateEditorPopover extends React.Component { } } -PCDateEditorPopover.propTypes = propTypes; +PCDateEditor.propTypes = { + isInModal: PropTypes.bool, + lang: PropTypes.string.isRequired, + value: PropTypes.string, + dateFormat: PropTypes.string.isRequired, + className: PropTypes.string, + showHourAndMinute: PropTypes.bool.isRequired, + onCommit: PropTypes.func.isRequired, + firstDayOfWeek: PropTypes.string, +}; -export default PCDateEditorPopover; +export default PCDateEditor; diff --git a/src/Department-editor/department-multiple-select/index.css b/src/DepartmentMultipleSelectEditor/index.css similarity index 100% rename from src/Department-editor/department-multiple-select/index.css rename to src/DepartmentMultipleSelectEditor/index.css diff --git a/src/Department-editor/department-multiple-select/index.js b/src/DepartmentMultipleSelectEditor/index.js similarity index 98% rename from src/Department-editor/department-multiple-select/index.js rename to src/DepartmentMultipleSelectEditor/index.js index 902605ee..13d8ea34 100644 --- a/src/Department-editor/department-multiple-select/index.js +++ b/src/DepartmentMultipleSelectEditor/index.js @@ -1,8 +1,8 @@ import React, { Fragment, useState, useEffect, useRef } from 'react'; import PropTypes from 'prop-types'; -import { searchDepartments, getNormalizedDepartments } from '../utils'; -import SelectedDepartments from '../selected-departments'; -import { getLocale } from '../../lang'; +import { searchDepartments, getNormalizedDepartments } from '../utils/departments'; +import SelectedDepartments from '../SelectedDepartments'; +import { getLocale } from '../lang'; import './index.css'; diff --git a/src/DepartmentSingleSelectEditor/index.js b/src/DepartmentSingleSelectEditor/index.js new file mode 100644 index 00000000..eb0fccd4 --- /dev/null +++ b/src/DepartmentSingleSelectEditor/index.js @@ -0,0 +1,27 @@ +import React, { forwardRef } from 'react'; +import PropTypes from 'prop-types'; +import MediaQuery from 'react-responsive'; +import PCDepartmentSingleSelectEditor from './pc-editor'; +import MBDepartmentSingleSelectEditor from './mb-editor'; + +const DepartmentSingleSelectEditor = forwardRef(({ isMobile, ...props }, ref) => { + if (isMobile === false) return (); + if (isMobile === true) return (); + + return ( + <> + + + + + + + + ); +}); + +DepartmentSingleSelectEditor.propTypes = { + isMobile: PropTypes.bool, +}; + +export default DepartmentSingleSelectEditor; diff --git a/src/DepartmentSingleSelectEditor/mb-editor/index.css b/src/DepartmentSingleSelectEditor/mb-editor/index.css new file mode 100644 index 00000000..10c22ea9 --- /dev/null +++ b/src/DepartmentSingleSelectEditor/mb-editor/index.css @@ -0,0 +1,4 @@ +.dtable-ui-mobile-department-editor .dtable-ui-mobile-department-editor-department-item .dtable-radio { + margin-top: 2px; + margin-bottom: 0; +} diff --git a/src/DepartmentSingleSelectEditor/mb-editor/index.js b/src/DepartmentSingleSelectEditor/mb-editor/index.js new file mode 100644 index 00000000..049f5844 --- /dev/null +++ b/src/DepartmentSingleSelectEditor/mb-editor/index.js @@ -0,0 +1,227 @@ +import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import PropTypes from 'prop-types'; +import { List } from 'antd-mobile'; +import { DEPARTMENT_SELECT_RANGE_MAP } from 'dtable-utils'; +import DTableRadio from '../../DTableRadio'; +import MobileFullScreenPage from '../../MobileFullScreenPage'; +import { getNormalizedDepartments } from '../../utils/departments'; +import { DEPARTMENT_SELECT_RANGE_OPTIONS } from '../../constants/departments'; +import { getLocale } from '../../lang'; + +import './index.css'; + +const Item = List.Item; +const { Header, Body } = MobileFullScreenPage; + +const MBDepartmentSingleSelectEditor = ({ + value: oldValue, + column, + isSupportMultiple = false, + isFilterEditor = false, + userDepartmentIdsMap = {}, + departments: initialDepartments = [], + onCommit, + onClose, +}) => { + const [value, setValue] = useState(oldValue ? oldValue : isSupportMultiple ? [] : ''); + const [departments, setDepartments] = useState([]); + const [parentId, setParentId] = useState(''); + const [topParentIds, setTopParentIds] = useState([]); + + const initColumnData = useMemo(() => { + const data = column.data || {}; + const enableSelectRange = data.enable_select_range || false; + const selectedRange = data.selected_range || ''; + const specificDepartments = data.specific_departments || []; + return { enableSelectRange, selectedRange, specificDepartments }; + }, [column]); + + const onSave = useCallback(() => { + !isFilterEditor && onCommit && onCommit(value); + onClose && onClose(); + }, [isFilterEditor, value, onCommit, onClose]); + + const onDepartmentClick = useCallback((event, department) => { + event.preventDefault(); + const { id, hasChild } = department; + if (event.target && event.target.className.indexOf('am-list-arrow') > -1) { + if (!hasChild) return; + setParentId(id); + return; + } + let newValue; + if (isSupportMultiple) { + newValue = value.slice(0); + const index = newValue.indexOf(id); + index === -1 ? newValue.push(id) : newValue.splice(index); + } else { + newValue = id === value ? '' : id; + } + if (isFilterEditor) { + onCommit && onCommit({ id }); + } + setValue(newValue); + }, [isSupportMultiple, isFilterEditor, value, onCommit]); + + const onParentDepartmentClick = useCallback((event, type) => { + event.preventDefault(); + let newValue; + if (isSupportMultiple) { + newValue = value.slice(0); + const index = newValue.indexOf(type); + index === -1 ? newValue.push(type) : newValue.splice(index); + } else { + newValue = type === value ? '' : type; + } + if (isFilterEditor) { + onCommit && onCommit({ id: type }); + } + setValue(newValue); + }, [value, isFilterEditor, isSupportMultiple, onCommit]); + + const onLeftClick = useCallback(() => { + const parentDepartment = departments.find(item => item.id === parentId); + if (!parentDepartment) { + onClose && onClose(); + return; + } + const { enableSelectRange } = initColumnData; + if (topParentIds.includes(parentId) && enableSelectRange) { + setParentId(''); + return; + } + const upperLevelDepartmentParentId = parentDepartment.parent_id; + setParentId(upperLevelDepartmentParentId); + }, [initColumnData, departments, parentId, topParentIds, onClose]); + + const renderLeftContent = useCallback(() => { + const parentDepartment = departments.find(item => item.id === parentId); + if (!parentDepartment && !isFilterEditor) return getLocale('Cancel'); + return (); + }, [isFilterEditor, departments, parentId]); + + const renderDepartments = useCallback(() => { + const { enableSelectRange } = initColumnData; + let currentDepartments = []; + if (enableSelectRange && !parentId && !isFilterEditor) { + currentDepartments = departments.filter(department => topParentIds.includes(department.id)); + } else { + currentDepartments = departments.filter(department => department.parent_id === parentId); + } + if (currentDepartments.length === 0) { + return ( +
+ {getLocale('No_departments_available')} +
+ ); + } + + return ( + getLocale('Select_department')}> + {isFilterEditor && parentId === -1 && ( + <> + {DEPARTMENT_SELECT_RANGE_OPTIONS.slice(0, 2).map((option, index) => { + const { type, name } = option; + const isChecked = isSupportMultiple ? value.includes(type) : value === type; + return ( + onParentDepartmentClick(event, type)} + className="dtable-ui-mobile-department-editor-department-item" + > +
+ onParentDepartmentClick(event, type)} + label={getLocale(name)} + /> +
+
+ ); + })} + + )} + {currentDepartments.map((department, index) => { + const { hasChild, name, id } = department; + const isChecked = isSupportMultiple ? value.includes(id) : value === id; + return ( + onDepartmentClick(event, department)} + className="dtable-ui-mobile-department-editor-department-item" + > +
+ onParentDepartmentClick(event, department)} + label={name} + /> +
+
+ ); + })} +
+ ); + }, [initColumnData, parentId, isFilterEditor, isSupportMultiple, departments, value, topParentIds, onParentDepartmentClick, onDepartmentClick]); + + useEffect(() => { + const { enableSelectRange, selectedRange, specificDepartments } = initColumnData; + if (enableSelectRange && !isFilterEditor) { + const { current_user_department_ids = [], current_user_department_and_sub_ids = [] } = userDepartmentIdsMap; + let targetDepartments = []; + let topParentIds = []; + let canExpand = true; + if (selectedRange === DEPARTMENT_SELECT_RANGE_MAP.CURRENT_USER_DEPARTMENT) { + canExpand = false; + topParentIds = current_user_department_ids; + targetDepartments = initialDepartments.filter(department => current_user_department_ids.includes(department.id)); + } else if (selectedRange === DEPARTMENT_SELECT_RANGE_MAP.CURRENT_USER_DEPARTMENT_AND_SUB) { + const currentUserDepartments = initialDepartments.filter(department => current_user_department_ids.includes(department.id)); + topParentIds = currentUserDepartments.filter(department => + !current_user_department_ids.includes(department.parent_id)).map(department => department.id); + targetDepartments = initialDepartments.filter(department => current_user_department_and_sub_ids.includes(department.id)); + } else { + canExpand = false; + topParentIds = specificDepartments; + targetDepartments = initialDepartments.filter(department => specificDepartments.includes(department.id)); + } + const departments = getNormalizedDepartments(targetDepartments, canExpand); + setDepartments(departments); + setTopParentIds(topParentIds); + return; + } + const departments = getNormalizedDepartments(initialDepartments); + setDepartments(departments); + setParentId(-1); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + return ( + +
+ <>{renderLeftContent()} + <>{column.name} + <>{isFilterEditor ? null : ({getLocale('Submit')})} +
+ + {renderDepartments()} + +
+ ); +}; + +MBDepartmentSingleSelectEditor.propTypes = { + value: PropTypes.string, + column: PropTypes.object, + isSupportMultiple: PropTypes.bool, + isFilterEditor: PropTypes.bool, + userDepartmentIdsMap: PropTypes.object, + departments: PropTypes.array, + onCommit: PropTypes.func, + onClose: PropTypes.func, +}; + +export default MBDepartmentSingleSelectEditor; diff --git a/src/Department-editor/department-single-select.js b/src/DepartmentSingleSelectEditor/pc-editor/department-single-select.js similarity index 97% rename from src/Department-editor/department-single-select.js rename to src/DepartmentSingleSelectEditor/pc-editor/department-single-select.js index c78a8cf3..4b18b372 100644 --- a/src/Department-editor/department-single-select.js +++ b/src/DepartmentSingleSelectEditor/pc-editor/department-single-select.js @@ -1,9 +1,9 @@ import React, { Component, Fragment } from 'react'; import PropTypes from 'prop-types'; import { DEPARTMENT_SELECT_RANGE_MAP } from 'dtable-utils'; -import Loading from '../Loading'; -import { getNormalizedDepartments, searchDepartments } from './utils'; -import { getLocale } from '../lang'; +import Loading from '../../Loading'; +import { getNormalizedDepartments, searchDepartments } from '../../utils/departments'; +import { getLocale } from '../../lang'; import './index.css'; @@ -151,7 +151,7 @@ class DepartmentSingleSelect extends Component { >
{hasChild && -
this.onExpand(event, id, isExpanded)} role="button" className='department-item-fold-btn'> +
this.onExpand(event, id, isExpanded)} role="button" className="department-item-fold-btn">
@@ -213,7 +213,7 @@ class DepartmentSingleSelect extends Component { renderEmptyTip = () => { return ( - + {getLocale('No_departments_available')} ); diff --git a/src/Department-editor/index.css b/src/DepartmentSingleSelectEditor/pc-editor/index.css similarity index 93% rename from src/Department-editor/index.css rename to src/DepartmentSingleSelectEditor/pc-editor/index.css index 822a5a46..d41f7aad 100644 --- a/src/Department-editor/index.css +++ b/src/DepartmentSingleSelectEditor/pc-editor/index.css @@ -22,6 +22,8 @@ font-size: 14px; display: inline-block; color: #666666; + padding: 10px; + overflow: auto; } .dtable-ui.department-editor-list .department-item-left-content { @@ -68,19 +70,24 @@ color: #212529; } -.dtable-ui.department-editor-list .editor-department-container .expand { +.dtable-ui.department-editor-list .editor-department-container .dtable-icon-down3 { color: #b5b5b5; font-size: 12px; - transform: scale(0.8); + transform: scale(0.8) rotate(-90deg); + cursor: pointer; display: inline-block; margin-left: 0px !important; } -.dtable-ui.department-editor-list .editor-department-container .expand:hover { +.dtable-ui.department-editor-list .editor-department-container .dtable-icon-down3:hover { color: #555; } +.dtable-ui.department-editor-list .editor-department-container .dtable-icon-down3.expand { + transform: scale(0.8); +} + .selected-departments.dtable-ui { background-color: #f6f6f6; padding: 5px; diff --git a/src/Department-editor/index.js b/src/DepartmentSingleSelectEditor/pc-editor/index.js similarity index 76% rename from src/Department-editor/index.js rename to src/DepartmentSingleSelectEditor/pc-editor/index.js index e4d94d4f..26ea4c8c 100644 --- a/src/Department-editor/index.js +++ b/src/DepartmentSingleSelectEditor/pc-editor/index.js @@ -1,9 +1,7 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; -import DTablePopover from '../DTablePopover'; -import { DEPARTMENT_SELECT_RANGE_OPTIONS } from './constants'; +import DTablePopover from '../../DTablePopover'; import DepartmentSingleSelect from './department-single-select'; -import { getLocale } from '../lang'; import './index.css'; @@ -20,7 +18,7 @@ const propTypes = { toggleDepartmentSelect: PropTypes.func, }; -class DepartmentSingleSelectEditor extends Component { +class PCDepartmentSingleSelectEditor extends Component { constructor(props) { super(props); @@ -45,9 +43,9 @@ class DepartmentSingleSelectEditor extends Component { } onCommit = (value) => { - const { onCommit, column } = this.props; + const { onCommit } = this.props; this.setState({ value }, () => { - this.isRowExpand ? onCommit(this.getValue(), column) : onCommit(); + this.isRowExpand ? onCommit(value) : onCommit(); }); }; @@ -57,16 +55,6 @@ class DepartmentSingleSelectEditor extends Component { return updated; }; - createSpecificDepartments = () => { - return DEPARTMENT_SELECT_RANGE_OPTIONS.slice(0, 2).map((option) => { - const { type, name } = option; - return { - value: type, - label: ({getLocale(name)}) - }; - }); - }; - render() { const { target, column, userDepartmentIdsMap, departments } = this.props; @@ -100,6 +88,6 @@ class DepartmentSingleSelectEditor extends Component { } } -DepartmentSingleSelectEditor.propTypes = propTypes; +PCDepartmentSingleSelectEditor.propTypes = propTypes; -export default DepartmentSingleSelectEditor; +export default PCDepartmentSingleSelectEditor; diff --git a/src/DigitalSignEditor/index.js b/src/DigitalSignEditor/index.js index 347fd2cf..bd85503e 100644 --- a/src/DigitalSignEditor/index.js +++ b/src/DigitalSignEditor/index.js @@ -1,158 +1,28 @@ -import React, { Component } from 'react'; +import React, { forwardRef } from 'react'; import PropTypes from 'prop-types'; -import { Button } from 'reactstrap'; -import DTableCustomFooter from '../DTableCustomFooter'; -import SignatureBoard from './signature-board'; -import DigitalService from './service'; -import DigitalSignUtils from './utils'; -import { generateCurrentBaseImageUrl } from '../utils/url'; -import toaster from '../toaster'; -import { getLocale } from '../lang'; -import { getErrorMsg } from '../utils/utils'; - -import './index.css'; - -class DigitalSignEditor extends Component { - - constructor(props) { - super(props); - this.state = { - editorPosition: {}, - saving: false, - }; - const { uploadFile, config, value } = props; - const { username } = config || {}; - this.value = value || {}; - this.digitalService = new DigitalService({ uploadFile, username }); - } - - componentDidMount() { - this.setPosition(); - } - - setPosition = () => { - if (!this.editor) return; - const { offsetLeft, offsetTop } = this.editor.parentNode; - const { offsetWidth: editorWidth, offsetHeight: editorHeight } = this.editor; - let editorLeft = offsetLeft - editorWidth; - let editorTop = offsetTop; - if (this.props.isInModal) { - const innerHeight = window.innerHeight; - const offsetTop = this.editor.parentNode.getBoundingClientRect().y; - editorTop = offsetTop; - editorTop = editorHeight + editorTop > innerHeight ? innerHeight - editorHeight - 30 : editorTop; - editorLeft = -30; - } else { - if (offsetLeft < editorWidth) { - editorLeft = offsetLeft + this.props.column.width; - } - if (editorLeft + editorWidth > window.innerWidth) { - editorLeft = window.innerWidth - editorWidth; - } - if (offsetTop + editorHeight > window.innerHeight) { - editorTop = window.innerHeight - editorHeight - 10; - } - } - this.setState({ - editorPosition: { - top: editorTop, left: editorLeft, - } - }); - }; - - getValue = () => { - const updated = { - [this.props.column.key]: this.value, - }; - return updated; - }; - - saveSignature = () => { - if (!this.signatureBoard || this.state.saving) return; - if (!this.signatureBoard.checkHasChanged()) { - this.props.onCommitCancel(); - return; - } - this.signatureBoard.convert2BlobPNG((signBlob) => { - if (!signBlob) { - if (!this.props.value) { - this.props.onCommitCancel(); - return; - } - - // clear the old dtable-ui-digital-sign - this.value = null; - this.props.onCommit(this.value); - return; - } - this.setState({ saving: true }); - this.digitalService.uploadSignImage(signBlob, { - successCallback: (signature) => { - this.value = signature; - this.setState({ saving: false }, () => { - this.props.onCommit(Object.assign({}, this.value)); - }); - }, - failedCallback: (error) => { - const errMsg = getErrorMsg(error, true); - if (!error.response || error.response.status !== 403) { - toaster.danger(getLocale(errMsg)); - } - this.setState({ saving: false }); - }, - }); - }); - }; - - clearSignature = () => { - this.signatureBoard.clear(); - }; - - render() { - const { value, config } = this.props; - const { editorPosition, saving } = this.state; - const signImageUrl = generateCurrentBaseImageUrl({ ...config, partUrl: DigitalSignUtils.getSignImageUrl(value) }); - return ( -
this.editor = ref} - style={{ ...editorPosition, zIndex: 1000 }} - > -
-
- - {getLocale('Digital_signature')} -
-
-
- - {getLocale('Re-sign')} -
-
-
-
- this.signatureBoard = ref} signImageUrl={signImageUrl} /> -
- - - - -
- ); - } -} +import MediaQuery from 'react-responsive'; +import PCDigitalSignEditor from './pc-editor'; +import MBDigitalSignEditor from './mb-editor'; + +const DigitalSignEditor = forwardRef(({ isMobile, ...props }, ref) => { + if (isMobile === false) return (); + if (isMobile === true) return (); + + return ( + <> + + + + + + + + ); +}); DigitalSignEditor.propTypes = { - isInModal: PropTypes.bool, - config: PropTypes.object, - value: PropTypes.oneOfType([PropTypes.string, PropTypes.object]), - column: PropTypes.object, - onCommitCancel: PropTypes.func.isRequired, - onCommit: PropTypes.func, + isMobile: PropTypes.bool, }; export default DigitalSignEditor; + diff --git a/src/DigitalSignEditor/mb-editor/index.css b/src/DigitalSignEditor/mb-editor/index.css new file mode 100644 index 00000000..8f1a2096 --- /dev/null +++ b/src/DigitalSignEditor/mb-editor/index.css @@ -0,0 +1,35 @@ +.dtable-ui-mobile-digital-sign-editor .dtable-ui-mobile-full-screen-page-body { + position: relative; + flex: 1; +} + +.dtable-ui-mobile-digital-sign-editor .dtable-ui-digital-sign-panel { + margin-top: 20px; +} + +.dtable-ui-mobile-digital-sign-editor .dtable-ui-digital-sign-editor-operations { + z-index: 3; + top: 10px; + right: 16px; +} + +.dtable-ui-mobile-digital-sign-editor .dtable-ui-digital-sign-editor-operations .btn-clear-dtable-ui-digital-sign { + color: rgb(240, 159, 63); + background-color: unset; +} + +.dtable-ui-mobile-digital-sign-editor .dtable-ui-digital-sign-panel-mask { + top: 0; + left: 0; + z-index: 4; + height: 100%; + width: 100%; + display: flex; + align-items: center; + justify-content: center; + background-color: rgba(0, 0, 0, .4); +} + +.dtable-ui-mobile-digital-sign-editor .dtable-ui-digital-sign-panel-mask .dtable-ui-digital-sign-panel-mask-container { + color: #fff; +} diff --git a/src/DigitalSignEditor/mb-editor/index.js b/src/DigitalSignEditor/mb-editor/index.js new file mode 100644 index 00000000..8970be2e --- /dev/null +++ b/src/DigitalSignEditor/mb-editor/index.js @@ -0,0 +1,133 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import MobileFullScreenPage from '../../MobileFullScreenPage'; +import SignatureBoard from '../signature-board'; +import DigitalService from '../service'; +import DigitalSignUtils from '../utils'; +import { generateCurrentBaseImageUrl } from '../../utils/url'; +import toaster from '../../toaster'; +import { getLocale } from '../../lang'; +import { getErrorMsg } from '../../utils/utils'; + +import './index.css'; + +const { Header, Body } = MobileFullScreenPage; + +class MBDigitalSignEditor extends Component { + + constructor(props) { + super(props); + this.state = { + saving: false, + }; + const { uploadFile, config, value } = props; + const { username } = config || {}; + this.value = value || {}; + this.digitalService = new DigitalService({ uploadFile, username }); + this.signatureBoard = null; + } + + getValue = () => { + const updated = { + [this.props.column.key]: this.value, + }; + return updated; + }; + + onToggle = () => { + this.props.onCommitCancel(); + }; + + onSave = () => { + if (!this.signatureBoard || this.state.saving) return; + if (!this.signatureBoard.checkHasChanged()) { + this.onToggle(); + return; + } + this.signatureBoard.convert2BlobPNG((signBlob) => { + if (!signBlob) { + if (!this.props.value) { + this.onToggle(); + return; + } + + // clear the old dtable-ui-digital-sign + this.value = null; + this.props.onCommit(this.value); + return; + } + this.setState({ saving: true }); + this.digitalService.uploadSignImage(signBlob, { + successCallback: (signature) => { + this.value = signature; + this.setState({ saving: false }, () => { + this.props.onCommit(this.value); + }); + }, + failedCallback: (error) => { + const errMsg = getErrorMsg(error, true); + if (!error.response || error.response.status !== 403) { + toaster.danger(getLocale(errMsg)); + } + this.setState({ saving: false }); + }, + }); + }); + }; + + clearSignature = () => { + this.signatureBoard.clear(); + }; + + historyCallback = (event) => { + if (this.state.saving) return; + event.preventDefault(); + this.onToggle(); + }; + + render() { + const { value, config, column } = this.props; + const { saving } = this.state; + const signImageUrl = generateCurrentBaseImageUrl({ ...config, partUrl: DigitalSignUtils.getSignImageUrl(value) }); + return ( + +
+ <>{getLocale('Cancel')} + {column.name} + {getLocale('Submit')} +
+ +
+
+
+ {getLocale('Re-sign')} +
+
+ this.signatureBoard = ref} signImageUrl={signImageUrl} /> +
+ {saving && ( +
+
+ {getLocale('Saving...')} +
+
+ )} + +
+ ); + } +} + +MBDigitalSignEditor.propTypes = { + config: PropTypes.object, + value: PropTypes.oneOfType([PropTypes.string, PropTypes.object]), + column: PropTypes.object, + onCommitCancel: PropTypes.func.isRequired, + onCommit: PropTypes.func, +}; + +export default MBDigitalSignEditor; diff --git a/src/DigitalSignEditor/index.css b/src/DigitalSignEditor/pc-editor/index.css similarity index 100% rename from src/DigitalSignEditor/index.css rename to src/DigitalSignEditor/pc-editor/index.css diff --git a/src/DigitalSignEditor/pc-editor/index.js b/src/DigitalSignEditor/pc-editor/index.js new file mode 100644 index 00000000..f717a024 --- /dev/null +++ b/src/DigitalSignEditor/pc-editor/index.js @@ -0,0 +1,158 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { Button } from 'reactstrap'; +import DTableCustomFooter from '../../DTableCustomFooter'; +import SignatureBoard from '../signature-board'; +import DigitalService from '../service'; +import DigitalSignUtils from '../utils'; +import { generateCurrentBaseImageUrl } from '../../utils/url'; +import toaster from '../../toaster'; +import { getLocale } from '../../lang'; +import { getErrorMsg } from '../../utils/utils'; + +import './index.css'; + +class PCDigitalSignEditor extends Component { + + constructor(props) { + super(props); + this.state = { + editorPosition: {}, + saving: false, + }; + const { uploadFile, config, value } = props; + const { username } = config || {}; + this.value = value || {}; + this.digitalService = new DigitalService({ uploadFile, username }); + } + + componentDidMount() { + this.setPosition(); + } + + setPosition = () => { + if (!this.editor) return; + const { offsetLeft, offsetTop } = this.editor.parentNode; + const { offsetWidth: editorWidth, offsetHeight: editorHeight } = this.editor; + let editorLeft = offsetLeft - editorWidth; + let editorTop = offsetTop; + if (this.props.isInModal) { + const innerHeight = window.innerHeight; + const offsetTop = this.editor.parentNode.getBoundingClientRect().y; + editorTop = offsetTop; + editorTop = editorHeight + editorTop > innerHeight ? innerHeight - editorHeight - 30 : editorTop; + editorLeft = -30; + } else { + if (offsetLeft < editorWidth) { + editorLeft = offsetLeft + this.props.column.width; + } + if (editorLeft + editorWidth > window.innerWidth) { + editorLeft = window.innerWidth - editorWidth; + } + if (offsetTop + editorHeight > window.innerHeight) { + editorTop = window.innerHeight - editorHeight - 10; + } + } + this.setState({ + editorPosition: { + top: editorTop, left: editorLeft, + } + }); + }; + + getValue = () => { + const updated = { + [this.props.column.key]: this.value, + }; + return updated; + }; + + saveSignature = () => { + if (!this.signatureBoard || this.state.saving) return; + if (!this.signatureBoard.checkHasChanged()) { + this.props.onCommitCancel(); + return; + } + this.signatureBoard.convert2BlobPNG((signBlob) => { + if (!signBlob) { + if (!this.props.value) { + this.props.onCommitCancel(); + return; + } + + // clear the old dtable-ui-digital-sign + this.value = null; + this.props.onCommit(this.value); + return; + } + this.setState({ saving: true }); + this.digitalService.uploadSignImage(signBlob, { + successCallback: (signature) => { + this.value = signature; + this.setState({ saving: false }, () => { + this.props.onCommit(this.value); + }); + }, + failedCallback: (error) => { + const errMsg = getErrorMsg(error, true); + if (!error.response || error.response.status !== 403) { + toaster.danger(getLocale(errMsg)); + } + this.setState({ saving: false }); + }, + }); + }); + }; + + clearSignature = () => { + this.signatureBoard.clear(); + }; + + render() { + const { value, config } = this.props; + const { editorPosition, saving } = this.state; + const signImageUrl = generateCurrentBaseImageUrl({ ...config, partUrl: DigitalSignUtils.getSignImageUrl(value) }); + return ( +
this.editor = ref} + style={{ ...editorPosition, zIndex: 1000 }} + > +
+
+ + {getLocale('Digital_signature')} +
+
+
+ + {getLocale('Re-sign')} +
+
+
+
+ this.signatureBoard = ref} signImageUrl={signImageUrl} /> +
+ + + + +
+ ); + } +} + +PCDigitalSignEditor.propTypes = { + isInModal: PropTypes.bool, + config: PropTypes.object, + value: PropTypes.oneOfType([PropTypes.string, PropTypes.object]), + column: PropTypes.object, + onCommitCancel: PropTypes.func.isRequired, + onCommit: PropTypes.func, +}; + +export default PCDigitalSignEditor; diff --git a/src/FileEditor/addition-previewer/index.css b/src/FileEditor/addition-previewer/index.css deleted file mode 100644 index 3f5c3c7c..00000000 --- a/src/FileEditor/addition-previewer/index.css +++ /dev/null @@ -1 +0,0 @@ -@import url('../../ImageEditor/addition-previewer/index.css'); diff --git a/src/FileEditor/addition-previewer/local-file-addition/index.css b/src/FileEditor/addition-previewer/local-file-addition/index.css deleted file mode 100644 index 293551d2..00000000 --- a/src/FileEditor/addition-previewer/local-file-addition/index.css +++ /dev/null @@ -1 +0,0 @@ -@import url('../../../ImageEditor/addition-previewer/local-image-addition/index.css'); diff --git a/src/FileEditor/files-previewer/index.js b/src/FileEditor/files-previewer/index.js index 268356bd..d7265df3 100644 --- a/src/FileEditor/files-previewer/index.js +++ b/src/FileEditor/files-previewer/index.js @@ -1,263 +1,29 @@ -import React from 'react'; +import React, { forwardRef } from 'react'; import PropTypes from 'prop-types'; -import { getValidFileImageUrls } from '../../utils/url'; -import { downloadFile, downloadFiles } from '../../utils/utils'; -import { FILE_EDITOR_STATUS } from '../../constants'; -import { getLocale } from '../../lang'; -import ImagePreviewerLightbox from '../../ImagePreviewerLightbox'; -import DTableCommonAddTool from '../../DTableCommonAddTool'; -import FilePreviewer from './file-previewer'; +import MediaQuery from 'react-responsive'; +import PCFilesPreviewer from './pc-previewer'; +import MBFilesPreviewer from './mb-previewer'; import './index.css'; -class FilesPreviewer extends React.Component { - - constructor(props) { - super(props); - this.state = { - isShowRename: false, - value: props.value, - isItemFreezed: false, - isShowLargeImage: false, - isSelectMultipleFiles: false, - fileImageUrlList: [], - selectedFilesList: [], - largeImageIndex: -1 - }; - } - - componentDidMount() { - this.getFileItemImageUrlList(this.props.value); - } - - UNSAFE_componentWillReceiveProps(nextProps) { - if (JSON.stringify(this.props.value) !== JSON.stringify(nextProps.value)) { - this.getFileItemImageUrlList(nextProps.value); - } - } - - getFileItemImageUrlList = (value) => { - this.setState({ fileImageUrlList: getValidFileImageUrls(value) }); - }; - - showLargeImage = (itemUrl) => { - let { fileImageUrlList } = this.state; - this.setState({ - isShowLargeImage: true, - largeImageIndex: fileImageUrlList.indexOf(itemUrl) - }); - }; - - moveNext = () => { - let { fileImageUrlList } = this.state; - this.setState(prevState => ({ - largeImageIndex: (prevState.largeImageIndex + 1) % fileImageUrlList.length, - })); - }; - - movePrev = () => { - let { fileImageUrlList } = this.state; - this.setState(prevState => ({ - largeImageIndex: (prevState.largeImageIndex + fileImageUrlList.length - 1) % fileImageUrlList.length, - })); - }; - - hideLargeImage = () => { - this.setState({ - isShowLargeImage: false, - largeImageIndex: -1 - }); - }; - - downloadImage = (imageItemUrl) => { - const { value } = this.props; - const file = value.find(v => v.url === imageItemUrl); - if (!file) return; - this.props.getDownLoadFiles([file], ([dFile]) => { - dFile && downloadFile(dFile); - }); - }; - - onRotateImage = (imageIndex, degree) => { - let { fileImageUrlList } = this.state; - const imageUrl = fileImageUrlList[imageIndex]; - this.props.onRotateImage && this.props.onRotateImage(imageUrl, degree); - }; - - deleteImage = (index, type) => { - const { value } = this.props; - let { fileImageUrlList } = this.state; - const imageUrl = fileImageUrlList[index]; - let fileItemIndex = value.findIndex(fileItem => fileItem.url === imageUrl); - this.props.deleteFile(fileItemIndex, type); - if (index > fileImageUrlList.length - 2) { - if (fileImageUrlList.length - 2 < 0) { - this.hideLargeImage(); - } else { - this.setState({ largeImageIndex: 0 }); - } - } - }; - - togglePreviewer = () => { - this.props.togglePreviewer(FILE_EDITOR_STATUS.ADDITION); - this.props.resetFileValue(); - }; - - freezeItem = () => { - this.setState({ - isItemFreezed: true - }); - }; - - unFreezeItem = () => { - this.setState({ - isItemFreezed: false - }); - }; - - onSelectFiles = (name) => { - const { selectedFilesList } = this.state; - let filesList = selectedFilesList.slice(0); - const selectedFileIndex = selectedFilesList.indexOf(name); - if (selectedFileIndex > -1) { - filesList.splice(selectedFileIndex, 1); - } else { - filesList.push(name); - } - this.setState({ selectedFilesList: filesList }); - }; - - onSelectAllFiles = () => { - const { value } = this.props; - const { selectedFilesList } = this.state; - if (value.length === 0) return; - let allFilesList = selectedFilesList.slice(0); - value.map((item) => { - if (selectedFilesList.indexOf(item.name) === -1) { - allFilesList.push(item.name); - } - return null; - }); - this.setState({ selectedFilesList: allFilesList }); - }; - - onChangeSelectMultipleFiles = (state) => { - this.setState({ - isSelectMultipleFiles: state, - selectedFilesList: [] - }); - }; - - onDownloadAllSelectedFiles = () => { - const { config } = this.props; - const { selectedFilesList } = this.state; - if (selectedFilesList.length === 0) return; - this.props.getDownLoadFiles(selectedFilesList, (downloadFilesUrlList) => { - if (downloadFilesUrlList.length > 0) { - downloadFiles(downloadFilesUrlList, config); - } - this.onChangeSelectMultipleFiles(false); - }); - }; - - onDeleteAllSelectedFiles = () => { - const { selectedFilesList } = this.state; - this.props.deleteFiles(selectedFilesList); - this.onChangeSelectMultipleFiles(false); - }; - - renderFilesOperation = () => { - const { value, deleteFiles, getDownLoadFiles } = this.props; - const { isSelectMultipleFiles, selectedFilesList } = this.state; - if (value.length === 0) return null; - return ( -
- {!isSelectMultipleFiles ? - {getLocale('Select')} : - <> - {selectedFilesList.length > 0 && - <> - {deleteFiles && ({getLocale('Delete')})} - {getDownLoadFiles && ({getLocale('Download')})} - - } - {getLocale('Select_all')} - {getLocale('Cancel')} - - } -
- ); - }; - - render() { - let { value, getDownLoadFiles } = this.props; - const { isSelectMultipleFiles, selectedFilesList } = this.state; - return ( -
-
-
- {selectedFilesList.length > 0 ? - - {selectedFilesList.length === 1 ? getLocale('1_file_selected') : getLocale('Selected_xxx_files', { count: selectedFilesList.length })} - : - - {value.length <= 1 ? getLocale('xxx_existing_file', { count: value.length }) : getLocale('xxx_existing_files', { count: value.length })} - - } - {this.renderFilesOperation()} -
-
- {value.length > 0 && value.map((fileItem, index) => { - const isSelected = selectedFilesList.indexOf(fileItem.name) === -1 ? false : true; - return ( - - ); - })} -
-
- {this.state.isShowLargeImage && - - } - -
- ); - } - -} +const FilesPreviewer = forwardRef(({ isMobile, ...props }, ref) => { + if (isMobile === false) return (); + if (isMobile === true) return (); + + return ( + <> + + + + + + + + ); +}); FilesPreviewer.propTypes = { - value: PropTypes.array, - config: PropTypes.object, - getDownLoadFiles: PropTypes.func, - onRotateImage: PropTypes.func, - deleteFile: PropTypes.func, - renameFile: PropTypes.func, - togglePreviewer: PropTypes.func, - resetFileValue: PropTypes.func, + isMobile: PropTypes.bool, }; export default FilesPreviewer; diff --git a/src/FileEditor/files-previewer/mb-previewer/file-previewer/index.js b/src/FileEditor/files-previewer/mb-previewer/file-previewer/index.js new file mode 100644 index 00000000..da842d7f --- /dev/null +++ b/src/FileEditor/files-previewer/mb-previewer/file-previewer/index.js @@ -0,0 +1,111 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { getFileUploadTime, bytesToSize } from '../../../utils'; +import { getFileThumbnailInfo } from '../../../../utils/url'; +import MobileOperationSheet, { OPERATION_TYPE } from '../../../../MobileOperationSheet'; + +class FilePreviewer extends React.Component { + + constructor(props) { + super(props); + this.state = { + isShowRename: false, + showOperationSheet: false, + }; + this.operations = []; + const { renameFile, downloadFile, deleteFile } = props; + if (renameFile) { + this.operations.push(OPERATION_TYPE.RENAME); + } + if (downloadFile) { + this.operations.push(OPERATION_TYPE.DOWNLOAD); + } + if (deleteFile) { + this.operations.push(OPERATION_TYPE.DELETE); + } + } + + downloadFile = () => { + const downloadUrl = this.props.fileItem.url; + this.props.downloadFile(downloadUrl); + }; + + deleteFile = () => { + const { fileItem } = this.props; + this.props.deleteFile(fileItem); + }; + + closeRenameFile = () => { + this.setState({ isShowRename: false }); + }; + + openRenameFile = () => { + this.setState({ isShowRename: true }); + }; + + openOperationSheet = () => { + this.setState({ showOperationSheet: true }); + }; + + closeOperationSheet = () => { + this.setState({ showOperationSheet: false }); + }; + + onOperationChange = (operation) => { + if (operation === OPERATION_TYPE.RENAME) { + this.openRenameFile(); + return; + } + + if (operation === OPERATION_TYPE.DOWNLOAD) { + this.downloadFile(); + return; + } + + if (operation === OPERATION_TYPE.DELETE) { + this.deleteFile(); + return; + } + }; + + render() { + const { fileItem, config } = this.props; + const { showOperationSheet } = this.state; + const { fileIconUrl } = getFileThumbnailInfo(fileItem, config); + const uploadTime = getFileUploadTime(fileItem); + return ( + <> +
+
+
+ +
+
+
+ {fileItem.name} +
+
+ {uploadTime && ({`${uploadTime}, `})} + {fileItem.size > -1 && ({bytesToSize(fileItem.size)})} +
+
+
+
+ +
+
+ {showOperationSheet && ( + + )} + + ); + } +} + +FilePreviewer.propTypes = { + fileItem: PropTypes.object.isRequired, + renameFile: PropTypes.func, + deleteFile: PropTypes.func.isRequired, +}; + +export default FilePreviewer; diff --git a/src/FileEditor/files-previewer/mb-previewer/index.css b/src/FileEditor/files-previewer/mb-previewer/index.css new file mode 100644 index 00000000..0da60c2a --- /dev/null +++ b/src/FileEditor/files-previewer/mb-previewer/index.css @@ -0,0 +1,39 @@ +@import url('../pc-previewer/file-previewer/index.css'); + +.dtable-ui-file-editor-previewer.dtable-ui-mobile-file-editor-previewer { + margin-top: 20px; + width: 100%; +} + +.dtable-ui-file-editor-previewer.dtable-ui-mobile-file-editor-previewer .dtable-ui-file-editor-previewer-wrapper { + width: 100%; + background-color: #fff; + height: fit-content; + border-top: 1px solid rgb(233, 233, 233); + border-bottom: 1px solid rgb(233, 233, 233); +} + +.dtable-ui-file-editor-previewer.dtable-ui-mobile-file-editor-previewer .dtable-ui-file-editor-previewer-wrapper .dtable-ui-file-editor-previewer-content { + margin-top: 0px; + height: fit-content; +} + +.dtable-ui-file-editor-previewer.dtable-ui-mobile-file-editor-previewer .dtable-ui-file-editor-previewer-wrapper .dtable-ui-file-editor-previewer-content:after { + display: none; +} + +.dtable-ui-file-editor-previewer.dtable-ui-mobile-file-editor-previewer .dtable-ui-file-editor-previewer-content .dtable-ui-file-editor-previewer-box { + height: 64px; + width: calc(100% - 16px); + border-bottom: 1px solid #e9e9e9; + padding: 0.5rem 1rem 0.5rem 0; + margin-left: 16px; +} + +.dtable-ui-file-editor-previewer.dtable-ui-mobile-file-editor-previewer .dtable-ui-file-editor-previewer-content .dtable-ui-file-editor-previewer-box:last-child { + border-bottom: none; +} + +.dtable-ui-mobile-file-editor-previewer .dtable-ui-file-editor-previewer-operation { + color: #666; +} diff --git a/src/FileEditor/files-previewer/mb-previewer/index.js b/src/FileEditor/files-previewer/mb-previewer/index.js new file mode 100644 index 00000000..aa39e483 --- /dev/null +++ b/src/FileEditor/files-previewer/mb-previewer/index.js @@ -0,0 +1,63 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import classnames from 'classnames'; +import { downloadFile } from '../../../utils/utils'; +import { getLocale } from '../../../lang'; +import DTableCommonAddTool from '../../../DTableCommonAddTool'; +import FilePreviewer from './file-previewer'; + +import './index.css'; + +class MBFilesPreviewer extends React.Component { + + downloadImage = (imageItemUrl) => { + const { value } = this.props; + const file = value.find(v => v.url === imageItemUrl); + if (!file) return; + this.props.getDownLoadFiles([file], ([dFile]) => { + dFile && downloadFile(dFile); + }); + }; + + togglePreviewer = () => { + this.props.togglePreviewer(); + this.props.resetFileValue(); + }; + + render() { + const { value, getDownLoadFiles } = this.props; + return ( +
+
+
+ {value.length > 0 && value.map((fileItem, index) => { + return ( + + ); + })} +
+
+ +
+ ); + } + +} + +MBFilesPreviewer.propTypes = { + value: PropTypes.array, + config: PropTypes.object, + deleteFile: PropTypes.func, + renameFile: PropTypes.func, + getDownLoadFiles: PropTypes.func, + togglePreviewer: PropTypes.func, + resetFileValue: PropTypes.func, +}; + +export default MBFilesPreviewer; diff --git a/src/FileEditor/files-previewer/file-previewer/dropdown-menu.js b/src/FileEditor/files-previewer/pc-previewer/file-previewer/dropdown-menu.js similarity index 98% rename from src/FileEditor/files-previewer/file-previewer/dropdown-menu.js rename to src/FileEditor/files-previewer/pc-previewer/file-previewer/dropdown-menu.js index 1ba42925..88a440f9 100644 --- a/src/FileEditor/files-previewer/file-previewer/dropdown-menu.js +++ b/src/FileEditor/files-previewer/pc-previewer/file-previewer/dropdown-menu.js @@ -1,7 +1,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import { Dropdown, DropdownToggle, DropdownMenu, DropdownItem, } from 'reactstrap'; -import { getLocale } from '../../../lang'; +import { getLocale } from '../../../../lang'; class FileDropdownMenu extends React.Component { diff --git a/src/FileEditor/files-previewer/file-previewer/file-name-editor/index.css b/src/FileEditor/files-previewer/pc-previewer/file-previewer/file-name-editor/index.css similarity index 100% rename from src/FileEditor/files-previewer/file-previewer/file-name-editor/index.css rename to src/FileEditor/files-previewer/pc-previewer/file-previewer/file-name-editor/index.css diff --git a/src/FileEditor/files-previewer/file-previewer/file-name-editor/index.js b/src/FileEditor/files-previewer/pc-previewer/file-previewer/file-name-editor/index.js similarity index 97% rename from src/FileEditor/files-previewer/file-previewer/file-name-editor/index.js rename to src/FileEditor/files-previewer/pc-previewer/file-previewer/file-name-editor/index.js index c2e0b518..1d7403a8 100644 --- a/src/FileEditor/files-previewer/file-previewer/file-name-editor/index.js +++ b/src/FileEditor/files-previewer/pc-previewer/file-previewer/file-name-editor/index.js @@ -1,7 +1,7 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import { Input } from 'reactstrap'; -import { KeyCodes } from '../../../../constants'; +import { KeyCodes } from '../../../../../constants'; import './index.css'; diff --git a/src/FileEditor/files-previewer/file-previewer/index.css b/src/FileEditor/files-previewer/pc-previewer/file-previewer/index.css similarity index 99% rename from src/FileEditor/files-previewer/file-previewer/index.css rename to src/FileEditor/files-previewer/pc-previewer/file-previewer/index.css index 83472ca4..2b4a9b34 100644 --- a/src/FileEditor/files-previewer/file-previewer/index.css +++ b/src/FileEditor/files-previewer/pc-previewer/file-previewer/index.css @@ -84,5 +84,3 @@ color: #000; opacity: 0.75; } - - diff --git a/src/FileEditor/files-previewer/file-previewer/index.js b/src/FileEditor/files-previewer/pc-previewer/file-previewer/index.js similarity index 97% rename from src/FileEditor/files-previewer/file-previewer/index.js rename to src/FileEditor/files-previewer/pc-previewer/file-previewer/index.js index 4972209c..cdc43b48 100644 --- a/src/FileEditor/files-previewer/file-previewer/index.js +++ b/src/FileEditor/files-previewer/pc-previewer/file-previewer/index.js @@ -3,8 +3,8 @@ import PropTypes from 'prop-types'; import { UncontrolledTooltip } from 'reactstrap'; import DropDownMenu from './dropdown-menu'; import FileNameEditor from './file-name-editor'; -import { getFileUploadTime, bytesToSize } from '../../utils'; -import { getFileThumbnailInfo, imageCheck } from '../../../utils/url'; +import { getFileUploadTime, bytesToSize } from '../../../utils'; +import { getFileThumbnailInfo, imageCheck } from '../../../../utils/url'; import './index.css'; diff --git a/src/FileEditor/files-previewer/pc-previewer/index.js b/src/FileEditor/files-previewer/pc-previewer/index.js new file mode 100644 index 00000000..876829e9 --- /dev/null +++ b/src/FileEditor/files-previewer/pc-previewer/index.js @@ -0,0 +1,261 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { getValidFileImageUrls } from '../../../utils/url'; +import { downloadFile, downloadFiles } from '../../../utils/utils'; +import { FILE_EDITOR_STATUS } from '../../../constants'; +import { getLocale } from '../../../lang'; +import ImagePreviewerLightbox from '../../../ImagePreviewerLightbox'; +import DTableCommonAddTool from '../../../DTableCommonAddTool'; +import FilePreviewer from './file-previewer'; + +class PCFilesPreviewer extends React.Component { + + constructor(props) { + super(props); + this.state = { + isShowRename: false, + value: props.value, + isItemFreezed: false, + isShowLargeImage: false, + isSelectMultipleFiles: false, + fileImageUrlList: [], + selectedFilesList: [], + largeImageIndex: -1 + }; + } + + componentDidMount() { + this.getFileItemImageUrlList(this.props.value); + } + + UNSAFE_componentWillReceiveProps(nextProps) { + if (JSON.stringify(this.props.value) !== JSON.stringify(nextProps.value)) { + this.getFileItemImageUrlList(nextProps.value); + } + } + + getFileItemImageUrlList = (value) => { + this.setState({ fileImageUrlList: getValidFileImageUrls(value) }); + }; + + showLargeImage = (itemUrl) => { + let { fileImageUrlList } = this.state; + this.setState({ + isShowLargeImage: true, + largeImageIndex: fileImageUrlList.indexOf(itemUrl) + }); + }; + + moveNext = () => { + let { fileImageUrlList } = this.state; + this.setState(prevState => ({ + largeImageIndex: (prevState.largeImageIndex + 1) % fileImageUrlList.length, + })); + }; + + movePrev = () => { + let { fileImageUrlList } = this.state; + this.setState(prevState => ({ + largeImageIndex: (prevState.largeImageIndex + fileImageUrlList.length - 1) % fileImageUrlList.length, + })); + }; + + hideLargeImage = () => { + this.setState({ + isShowLargeImage: false, + largeImageIndex: -1 + }); + }; + + downloadImage = (imageItemUrl) => { + const { value } = this.props; + const file = value.find(v => v.url === imageItemUrl); + if (!file) return; + this.props.getDownLoadFiles([file], ([dFile]) => { + dFile && downloadFile(dFile); + }); + }; + + onRotateImage = (imageIndex, degree) => { + let { fileImageUrlList } = this.state; + const imageUrl = fileImageUrlList[imageIndex]; + this.props.onRotateImage && this.props.onRotateImage(imageUrl, degree); + }; + + deleteImage = (index, type) => { + const { value } = this.props; + let { fileImageUrlList } = this.state; + const imageUrl = fileImageUrlList[index]; + let fileItemIndex = value.findIndex(fileItem => fileItem.url === imageUrl); + this.props.deleteFile(fileItemIndex, type); + if (index > fileImageUrlList.length - 2) { + if (fileImageUrlList.length - 2 < 0) { + this.hideLargeImage(); + } else { + this.setState({ largeImageIndex: 0 }); + } + } + }; + + togglePreviewer = () => { + this.props.togglePreviewer(FILE_EDITOR_STATUS.ADDITION); + this.props.resetFileValue(); + }; + + freezeItem = () => { + this.setState({ + isItemFreezed: true + }); + }; + + unFreezeItem = () => { + this.setState({ + isItemFreezed: false + }); + }; + + onSelectFiles = (name) => { + const { selectedFilesList } = this.state; + let filesList = selectedFilesList.slice(0); + const selectedFileIndex = selectedFilesList.indexOf(name); + if (selectedFileIndex > -1) { + filesList.splice(selectedFileIndex, 1); + } else { + filesList.push(name); + } + this.setState({ selectedFilesList: filesList }); + }; + + onSelectAllFiles = () => { + const { value } = this.props; + const { selectedFilesList } = this.state; + if (value.length === 0) return; + let allFilesList = selectedFilesList.slice(0); + value.map((item) => { + if (selectedFilesList.indexOf(item.name) === -1) { + allFilesList.push(item.name); + } + return null; + }); + this.setState({ selectedFilesList: allFilesList }); + }; + + onChangeSelectMultipleFiles = (state) => { + this.setState({ + isSelectMultipleFiles: state, + selectedFilesList: [] + }); + }; + + onDownloadAllSelectedFiles = () => { + const { config } = this.props; + const { selectedFilesList } = this.state; + if (selectedFilesList.length === 0) return; + this.props.getDownLoadFiles(selectedFilesList, (downloadFilesUrlList) => { + if (downloadFilesUrlList.length > 0) { + downloadFiles(downloadFilesUrlList, config); + } + this.onChangeSelectMultipleFiles(false); + }); + }; + + onDeleteAllSelectedFiles = () => { + const { selectedFilesList } = this.state; + this.props.deleteFiles(selectedFilesList); + this.onChangeSelectMultipleFiles(false); + }; + + renderFilesOperation = () => { + const { value, deleteFiles, getDownLoadFiles } = this.props; + const { isSelectMultipleFiles, selectedFilesList } = this.state; + if (value.length === 0) return null; + return ( +
+ {!isSelectMultipleFiles ? + {getLocale('Select')} : + <> + {selectedFilesList.length > 0 && + <> + {deleteFiles && ({getLocale('Delete')})} + {getDownLoadFiles && ({getLocale('Download')})} + + } + {getLocale('Select_all')} + {getLocale('Cancel')} + + } +
+ ); + }; + + render() { + let { value, getDownLoadFiles } = this.props; + const { isSelectMultipleFiles, selectedFilesList } = this.state; + return ( +
+
+
+ {selectedFilesList.length > 0 ? + + {selectedFilesList.length === 1 ? getLocale('1_file_selected') : getLocale('Selected_xxx_files', { count: selectedFilesList.length })} + : + + {value.length <= 1 ? getLocale('xxx_existing_file', { count: value.length }) : getLocale('xxx_existing_files', { count: value.length })} + + } + {this.renderFilesOperation()} +
+
+ {value.length > 0 && value.map((fileItem, index) => { + const isSelected = selectedFilesList.indexOf(fileItem.name) === -1 ? false : true; + return ( + + ); + })} +
+
+ {this.state.isShowLargeImage && + + } + +
+ ); + } + +} + +PCFilesPreviewer.propTypes = { + value: PropTypes.array, + config: PropTypes.object, + getDownLoadFiles: PropTypes.func, + onRotateImage: PropTypes.func, + deleteFile: PropTypes.func, + renameFile: PropTypes.func, + togglePreviewer: PropTypes.func, + resetFileValue: PropTypes.func, +}; + +export default PCFilesPreviewer; diff --git a/src/FileEditor/index.css b/src/FileEditor/index.css index 856de634..3f8a25ee 100644 --- a/src/FileEditor/index.css +++ b/src/FileEditor/index.css @@ -1,6 +1 @@ @import url('../ImageEditor/index.css'); - -.dtable-ui-file-editor-container { - min-height: 300px; -} - diff --git a/src/FileEditor/index.js b/src/FileEditor/index.js index bba25383..cf549294 100644 --- a/src/FileEditor/index.js +++ b/src/FileEditor/index.js @@ -1,268 +1,29 @@ -import React from 'react'; +import React, { forwardRef } from 'react'; import PropTypes from 'prop-types'; -import { Modal, ModalBody } from 'reactstrap'; -import FilesPreviewer from './files-previewer'; -import AdditionPreviewer from './addition-previewer'; -import { FILE_EDITOR_STATUS } from '../constants'; -import DTableModalHeader from '../DTableModalHeader'; -import { getLocale } from '../lang'; -import { FILES_FOLDER } from './constants'; -import { getErrorMsg } from '../utils/utils'; -import toaster from '../toaster'; +import MediaQuery from 'react-responsive'; +import PCFileEditor from './pc-editor'; +import MBFileEditor from './mb-editor'; import './index.css'; -class FileEditor extends React.Component { - - constructor(props) { - super(props); - this.state = { - value: props.value || [], - editorView: this.getEditorView(), - isOpen: true, - isShowFileChooser: false, - uploadLocalFileValue: [], - imageLinkValue: [], - isUpdated: false, - }; - } - - getValue = () => { - return this.state.value; - }; - - getEditorView = () => { - const { isInModal, value } = this.props; - return !value || value.length === 0 || isInModal ? FILE_EDITOR_STATUS.ADDITION : FILE_EDITOR_STATUS.PREVIEWER; - }; - - deleteImage = (index, type = null) => { - let uploadLocalFileValue = this.state.uploadLocalFileValue.slice(0); - let value = this.state.value.slice(0); - if (this.state.editorView === FILE_EDITOR_STATUS.PREVIEWER) { - value.splice(index, 1); - } else { - if (type === 'localPicture') { - uploadLocalFileValue.splice(index, 1); - } - } - this.setState({ - uploadLocalFileValue: uploadLocalFileValue, - isUpdated: true, - value: value - }); - }; - - resetAdditionImage = () => { - this.setState({ - uploadLocalFileValue: [], - imageLinkValue: [] - }); - }; - - toggle = (e) => { - e.stopPropagation(); - if (this.state.isOpen && this.state.isUpdated) { - if (this.state.editorView === FILE_EDITOR_STATUS.ADDITION) { - let { value, uploadLocalFileValue } = this.state; - let newValue = value.concat(uploadLocalFileValue); - this.setState({ - value: newValue - }, () => { - this.props.isInModal ? this.props.onCommit(this.getValue()) : this.props.onCommit(); - }); - return; - } - this.props.isInModal ? this.props.onCommit(this.getValue()) : this.props.onCommit(); - } - if (this.props.isInModal) { - this.props.onToggle(); - } - const nextIsOpen = !this.state.isOpen; - this.setState({ isOpen: nextIsOpen }, () => { - if (!nextIsOpen) { - this.props.onCommitCancel && this.props.onCommitCancel(); - } - }); - }; - - closeEditor = () => { - this.setState({ isOpen: false }); - }; - - togglePreviewer = (type) => { - this.setState({ - editorView: type, - }); - }; - - fileUploadCompleted = (fileMessage) => { - let uploadLocalFileValue = this.state.uploadLocalFileValue.slice(0); - let fileUploadMessage = { - name: fileMessage.name, - size: fileMessage.size, - type: fileMessage.type, - url: fileMessage.url, - upload_time: fileMessage.upload_time, - }; - uploadLocalFileValue.push(fileUploadMessage); - this.setState({ - uploadLocalFileValue: uploadLocalFileValue, - isUpdated: true - }); - }; - - addUploadedFile = (fileMessageList) => { - const uploadLocalFileValue = [ - ...this.state.uploadLocalFileValue, - ...fileMessageList.map(({ name, size, type, url, mtime }) => ({ - name, - size, - type: type || 'file', - url, - upload_time: mtime, - })), - ]; - this.setState({ uploadLocalFileValue, isUpdated: true }); - }; - - renderHeader = () => { - let { editorView } = this.state; - // if (this.props.isInModal) { - // return ({getLocale('Add_files')}); - // } - if (editorView === FILE_EDITOR_STATUS.PREVIEWER) { - return ({getLocale('All_files')}); - } - return ( -
- - {getLocale('Add_files')} -
- ); - }; - - showImageListPreviewer = () => { - let { value, uploadLocalFileValue, imageLinkValue } = this.state; - let newValue = value.concat(uploadLocalFileValue, imageLinkValue); - this.setState({ value: newValue }); - this.togglePreviewer(FILE_EDITOR_STATUS.PREVIEWER); - }; - - uploadFile = (file, callback) => { - return this.props.uploadFile(file, FILES_FOLDER, callback); - }; - - resetFileValue = () => { - this.setState({ uploadLocalFileValue: [] }); - }; - - deleteFile = (index, type) => { - let uploadLocalFileValue = this.state.uploadLocalFileValue.slice(0); - let value = this.state.value.slice(0); - if (this.state.editorView === FILE_EDITOR_STATUS.PREVIEWER) { - value.splice(index, 1); - } else { - if (type === 'localFile') { - uploadLocalFileValue.splice(index, 1); - } - } - this.setState({ - uploadLocalFileValue: uploadLocalFileValue, - isUpdated: true, - value: value - }); - }; - - deleteFilesByPreviewer = (fileList) => { - const result = this.state.value.filter(file => !fileList.includes(file.name)); - this.setState({ - isUpdated: true, - value: result, - }); - }; - - onRenameFile = (index, newName) => { - let newValue = this.state.value.slice(0); - let fileItem = newValue[index]; - if (!this.props.renameFile) return; - this.props.renameFile(fileItem, newName).then(res => { - const { name, url } = res.data; - fileItem = { ...fileItem, name, url }; - newValue[index] = fileItem; - this.setState({ value: newValue }); - }).catch(error => { - const errorMessage = getErrorMsg(error); - if (!error.response || error.response.status !== 403) { - toaster.danger(getLocale(errorMessage)); - } - }); - }; - - onRotateImage = (url, degree) => { - this.props.rotateImage(url, degree).then(res => { - // todo - }).catch(error => { - const errorMessage = getErrorMsg(error); - if (!error.response || error.response.status !== 403) { - toaster.danger(getLocale(errorMessage)); - } - }); - }; - - render() { - - return ( - - - {this.renderHeader()} - - -
- {this.state.editorView === FILE_EDITOR_STATUS.PREVIEWER && - {})} - /> - } - {this.state.editorView === FILE_EDITOR_STATUS.ADDITION && ( - - )} -
-
-
- ); - } -} +const FileEditor = forwardRef(({ isMobile, ...props }, ref) => { + if (isMobile === false) return (); + if (isMobile === true) return (); + + return ( + <> + + + + + + + + ); +}); FileEditor.propTypes = { - value: PropTypes.array, - isInModal: PropTypes.bool, - openEditorMode: PropTypes.string, - onToggle: PropTypes.func, - onCommit: PropTypes.func, + isMobile: PropTypes.bool, }; export default FileEditor; diff --git a/src/FileEditor/mb-editor/index.css b/src/FileEditor/mb-editor/index.css new file mode 100644 index 00000000..6feb70a9 --- /dev/null +++ b/src/FileEditor/mb-editor/index.css @@ -0,0 +1,10 @@ + +.dtable-ui-mobile-file-editor .dtable-ui-file-editor-previewer .dtable-ui-file-editor-previewer-add-btn { + margin-top: 20px; + border-bottom: 1px solid #dedede; +} + +.dtable-ui-mobile-file-editor .dtable-ui-file-editor-previewer .dtable-ui-file-editor-previewer-add-btn:hover { + background-color: #fff; +} + diff --git a/src/FileEditor/mb-editor/index.js b/src/FileEditor/mb-editor/index.js new file mode 100644 index 00000000..2e70dd25 --- /dev/null +++ b/src/FileEditor/mb-editor/index.js @@ -0,0 +1,140 @@ +import React, { useCallback, useEffect, useRef, useState } from 'react'; +import PropTypes from 'prop-types'; +import { ActivityIndicator } from 'antd-mobile'; +import MobileFullScreenPage from '../../MobileFullScreenPage'; +import FilesPreviewer from '../files-previewer'; +import MobileUpload from '../../MobileUpload'; +import toaster from '../../toaster'; +import { isIPhone, isQQBuiltInBrowser, getErrorMsg } from '../../utils/utils'; +import { getLocale } from '../../lang'; + +import './index.css'; + +const { Header, Body } = MobileFullScreenPage; + +const MBFileEditor = ({ value: oldValue, column, config, onToggle, onCommit, uploadFile }) => { + const [value, setValue] = useState(oldValue || []); + const [isShowEditor, setShowEditor] = useState(false); + const [uploadLocalFiles, setUploadLocalFiles] = useState([]); + const [isUploading, setUploading] = useState(false); + + const uploadingFilesCount = useRef(0); + const uploadedFilesCount = useRef(0); + + const { mediaUrl } = config || {}; + + const resetFileValue = useCallback(() => { + setUploadLocalFiles([]); + }, []); + + const openEditor = useCallback(() => { + setShowEditor(true); + }, []); + + const closeEditor = useCallback(() => { + setShowEditor(false); + }, []); + + const deleteFile = useCallback((file) => { + const newValue = value.slice(0); + const fileIndex = value.fileIndex(v => v.url === file.url); + newValue.splice(fileIndex, 1); + setValue(newValue); + onCommit(newValue); + }, [value, onCommit]); + + const onChange = useCallback(() => { + const newValue = value.concat(uploadLocalFiles); + setValue(newValue); + onCommit(newValue); + setShowEditor(false); + }, [value, uploadLocalFiles, onCommit]); + + const uploadImage = useCallback((file) => { + uploadFile(file).then(res => { + const fileInfo = res; + const newUploadLocalImages = uploadLocalFiles.slice(0); + newUploadLocalImages.push(fileInfo); + setUploadLocalFiles(newUploadLocalImages); + uploadedFilesCount.current = uploadedFilesCount.current + 1; + }).catch(error => { + let errMsg = getErrorMsg(error, true); + if (!error.response || error.response.status !== 403) { + toaster.danger(getLocale(errMsg)); + } + uploadedFilesCount.current = uploadedFilesCount.current + 1; + }); + }, [uploadFile, uploadLocalFiles]); + + const onFilesChange = useCallback((files = []) => { + setUploading(true); + uploadingFilesCount.current = files.length; + uploadedFilesCount.current = 0; + for (let i = 0; i < uploadingFilesCount.current; i++) { + const file = files[i]; + const reader = new FileReader(); + reader.readAsDataURL(file); + reader.addEventListener('load', () => { + uploadImage(file); + }, false); + reader.addEventListener('error', () => { + const message = getLocale('Image_loading_failed'); + toaster.warning(message); + uploadedFilesCount.current = uploadedFilesCount.current + 1; + }, false); + } + }, [uploadImage]); + + useEffect(() => { + if (uploadingFilesCount.current === uploadedFilesCount.current && uploadedFilesCount.current !== 0) { + uploadedFilesCount.current = 0; + uploadingFilesCount.current = 0; + onChange(); + setUploading(false); + } + }, [uploadingFilesCount, uploadedFilesCount, onChange]); + + return ( + +
+ + <>{column.name} +
+ + {(isIPhone() && isQQBuiltInBrowser()) ? ( +
+
{'不支持 QQ 内置浏览器上传图片或者文件'}
+
+ Open browser tip +
+
+ ) : ( + <> + + {isShowEditor && ( + + )} + {isUploading && ()} + + )} + +
+ ); +}; + +MBFileEditor.propTypes = { + value: PropTypes.array, + column: PropTypes.object, + config: PropTypes.object, + onToggle: PropTypes.func, + onCommit: PropTypes.func, + uploadFile: PropTypes.func, +}; + +export default MBFileEditor; diff --git a/src/FileEditor/pc-editor/addition-previewer/index.css b/src/FileEditor/pc-editor/addition-previewer/index.css new file mode 100644 index 00000000..02405912 --- /dev/null +++ b/src/FileEditor/pc-editor/addition-previewer/index.css @@ -0,0 +1 @@ +@import url('../../../ImageEditor/pc-editor/addition-previewer/index.css'); diff --git a/src/FileEditor/addition-previewer/index.js b/src/FileEditor/pc-editor/addition-previewer/index.js similarity index 97% rename from src/FileEditor/addition-previewer/index.js rename to src/FileEditor/pc-editor/addition-previewer/index.js index 85c21d79..6c088742 100644 --- a/src/FileEditor/addition-previewer/index.js +++ b/src/FileEditor/pc-editor/addition-previewer/index.js @@ -1,7 +1,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import LocalFileAddition from './local-file-addition'; -import { getLocale } from '../../lang'; +import { getLocale } from '../../../lang'; import './index.css'; diff --git a/src/FileEditor/pc-editor/addition-previewer/local-file-addition/index.css b/src/FileEditor/pc-editor/addition-previewer/local-file-addition/index.css new file mode 100644 index 00000000..51d54899 --- /dev/null +++ b/src/FileEditor/pc-editor/addition-previewer/local-file-addition/index.css @@ -0,0 +1 @@ +@import url('../../../../ImageEditor/pc-editor/addition-previewer/local-image-addition/index.css'); diff --git a/src/FileEditor/addition-previewer/local-file-addition/index.js b/src/FileEditor/pc-editor/addition-previewer/local-file-addition/index.js similarity index 97% rename from src/FileEditor/addition-previewer/local-file-addition/index.js rename to src/FileEditor/pc-editor/addition-previewer/local-file-addition/index.js index efcd3d6a..13fa820f 100644 --- a/src/FileEditor/addition-previewer/local-file-addition/index.js +++ b/src/FileEditor/pc-editor/addition-previewer/local-file-addition/index.js @@ -2,10 +2,10 @@ import React, { Fragment } from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; import { Tooltip } from 'reactstrap'; -import Progress from '../../../UploadProgress'; -import { getFileThumbnailInfo } from '../../../utils/url'; -import FileUploader from '../../../FileUploader'; -import { getLocale } from '../../../lang'; +import Progress from '../../../../UploadProgress'; +import { getFileThumbnailInfo } from '../../../../utils/url'; +import FileUploader from '../../../../FileUploader'; +import { getLocale } from '../../../../lang'; import './index.css'; diff --git a/src/FileEditor/pc-editor/index.css b/src/FileEditor/pc-editor/index.css new file mode 100644 index 00000000..06c47ae2 --- /dev/null +++ b/src/FileEditor/pc-editor/index.css @@ -0,0 +1,5 @@ +@import url('../../ImageEditor/pc-editor/index.css'); + +.dtable-ui-file-editor-container { + min-height: 300px; +} diff --git a/src/FileEditor/pc-editor/index.js b/src/FileEditor/pc-editor/index.js new file mode 100644 index 00000000..917e7dc9 --- /dev/null +++ b/src/FileEditor/pc-editor/index.js @@ -0,0 +1,271 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { Modal, ModalBody } from 'reactstrap'; +import FilesPreviewer from '../files-previewer'; +import AdditionPreviewer from './addition-previewer'; +import { FILE_EDITOR_STATUS } from '../../constants'; +import DTableModalHeader from '../../DTableModalHeader'; +import { getLocale } from '../../lang'; +import { FILES_FOLDER } from '../constants'; +import { getErrorMsg } from '../../utils/utils'; +import toaster from '../../toaster'; + +import './index.css'; + +class PCFileEditor extends React.Component { + + constructor(props) { + super(props); + this.state = { + value: props.value || [], + editorView: this.getEditorView(), + isOpen: true, + isShowFileChooser: false, + uploadLocalFileValue: [], + imageLinkValue: [], + isUpdated: false, + }; + } + + getValue = () => { + return this.state.value; + }; + + getEditorView = () => { + const { isInModal, value } = this.props; + return !value || value.length === 0 || isInModal ? FILE_EDITOR_STATUS.ADDITION : FILE_EDITOR_STATUS.PREVIEWER; + }; + + deleteImage = (index, type = null) => { + let uploadLocalFileValue = this.state.uploadLocalFileValue.slice(0); + let value = this.state.value.slice(0); + if (this.state.editorView === FILE_EDITOR_STATUS.PREVIEWER) { + value.splice(index, 1); + } else { + if (type === 'localPicture') { + uploadLocalFileValue.splice(index, 1); + } + } + this.setState({ + uploadLocalFileValue: uploadLocalFileValue, + isUpdated: true, + value: value + }); + }; + + resetAdditionImage = () => { + this.setState({ + uploadLocalFileValue: [], + imageLinkValue: [] + }); + }; + + toggle = (e) => { + e.stopPropagation(); + if (this.state.isOpen && this.state.isUpdated) { + if (this.state.editorView === FILE_EDITOR_STATUS.ADDITION) { + let { value, uploadLocalFileValue } = this.state; + let newValue = value.concat(uploadLocalFileValue); + this.setState({ + value: newValue + }, () => { + this.props.isInModal ? this.props.onCommit(this.getValue()) : this.props.onCommit(); + }); + return; + } + this.props.isInModal ? this.props.onCommit(this.getValue()) : this.props.onCommit(); + } + if (this.props.isInModal) { + this.props.onToggle(); + } + const nextIsOpen = !this.state.isOpen; + this.setState({ isOpen: nextIsOpen }, () => { + if (!nextIsOpen) { + this.props.onCommitCancel && this.props.onCommitCancel(); + } + }); + }; + + closeEditor = () => { + this.setState({ isOpen: false }); + }; + + togglePreviewer = (type) => { + this.setState({ + editorView: type, + }); + }; + + fileUploadCompleted = (fileMessage) => { + let uploadLocalFileValue = this.state.uploadLocalFileValue.slice(0); + let fileUploadMessage = { + name: fileMessage.name, + size: fileMessage.size, + type: fileMessage.type, + url: fileMessage.url, + upload_time: fileMessage.upload_time, + }; + uploadLocalFileValue.push(fileUploadMessage); + this.setState({ + uploadLocalFileValue: uploadLocalFileValue, + isUpdated: true + }); + }; + + addUploadedFile = (fileMessageList) => { + const uploadLocalFileValue = [ + ...this.state.uploadLocalFileValue, + ...fileMessageList.map(({ name, size, type, url, mtime }) => ({ + name, + size, + type: type || 'file', + url, + upload_time: mtime, + })), + ]; + this.setState({ uploadLocalFileValue, isUpdated: true }); + }; + + renderHeader = () => { + let { editorView } = this.state; + // if (this.props.isInModal) { + // return ({getLocale('Add_files')}); + // } + if (editorView === FILE_EDITOR_STATUS.PREVIEWER) { + return ({getLocale('All_files')}); + } + return ( +
+ + {getLocale('Add_files')} +
+ ); + }; + + showImageListPreviewer = () => { + let { value, uploadLocalFileValue, imageLinkValue } = this.state; + let newValue = value.concat(uploadLocalFileValue, imageLinkValue); + this.setState({ value: newValue }); + this.togglePreviewer(FILE_EDITOR_STATUS.PREVIEWER); + }; + + uploadFile = (file, callback) => { + return this.props.uploadFile(file, FILES_FOLDER, callback); + }; + + resetFileValue = () => { + this.setState({ uploadLocalFileValue: [] }); + }; + + deleteFile = (index, type) => { + let uploadLocalFileValue = this.state.uploadLocalFileValue.slice(0); + let value = this.state.value.slice(0); + if (this.state.editorView === FILE_EDITOR_STATUS.PREVIEWER) { + value.splice(index, 1); + } else { + if (type === 'localFile') { + uploadLocalFileValue.splice(index, 1); + } + } + this.setState({ + uploadLocalFileValue: uploadLocalFileValue, + isUpdated: true, + value: value + }); + }; + + deleteFilesByPreviewer = (fileList) => { + const result = this.state.value.filter(file => !fileList.includes(file.name)); + this.setState({ + isUpdated: true, + value: result, + }); + }; + + onRenameFile = (index, newName) => { + let newValue = this.state.value.slice(0); + let fileItem = newValue[index]; + if (!this.props.renameFile) return; + this.props.renameFile(fileItem, newName).then(res => { + const { name, url } = res.data; + fileItem = { ...fileItem, name, url }; + newValue[index] = fileItem; + this.setState({ value: newValue }); + }).catch(error => { + const errorMessage = getErrorMsg(error); + if (!error.response || error.response.status !== 403) { + toaster.danger(getLocale(errorMessage)); + } + }); + }; + + onRotateImage = (url, degree) => { + this.props.rotateImage(url, degree).then(res => { + // todo + }).catch(error => { + const errorMessage = getErrorMsg(error); + if (!error.response || error.response.status !== 403) { + toaster.danger(getLocale(errorMessage)); + } + }); + }; + + render() { + + return ( + + + {this.renderHeader()} + + +
+ {this.state.editorView === FILE_EDITOR_STATUS.PREVIEWER && + {})} + /> + } + {this.state.editorView === FILE_EDITOR_STATUS.ADDITION && ( + + )} +
+
+
+ ); + } +} + +PCFileEditor.propTypes = { + value: PropTypes.array, + isInModal: PropTypes.bool, + openEditorMode: PropTypes.string, + column: PropTypes.object, + config: PropTypes.object, + onToggle: PropTypes.func, + onCommit: PropTypes.func, + uploadFile: PropTypes.func, +}; + +export default PCFileEditor; diff --git a/src/FormulaFormatter/index.css b/src/FormulaFormatter/index.css index 42afde25..54067470 100644 --- a/src/FormulaFormatter/index.css +++ b/src/FormulaFormatter/index.css @@ -5,13 +5,13 @@ margin-right: 10px; } -.dtable-ui.dtable-row-expand-formatter .formula-formatter-content-item { +.dtable-ui.dtable-ui-row-expand-formatter .formula-formatter-content-item { margin-top: 0.5rem; margin-bottom: 5px; } -.dtable-ui.dtable-row-expand-formatter .formula-url-formatter-column .formula-url-link, -.dtable-ui.dtable-row-expand-formatter .formula-url-formatter-column .formula-email-link { +.dtable-ui.dtable-ui-row-expand-formatter .formula-url-formatter-column .formula-url-link, +.dtable-ui.dtable-ui-row-expand-formatter .formula-url-formatter-column .formula-email-link { display: flex; align-items: center; font-size: 14px; @@ -28,8 +28,8 @@ box-shadow: 0 0 1px; } -.dtable-ui.dtable-row-expand-formatter .formula-url-formatter-column .formula-email-link:hover, -.dtable-ui.dtable-row-expand-formatter .formula-url-formatter-column .formula-url-link:hover { +.dtable-ui.dtable-ui-row-expand-formatter .formula-url-formatter-column .formula-email-link:hover, +.dtable-ui.dtable-ui-row-expand-formatter .formula-url-formatter-column .formula-url-link:hover { background: #f5f5f5; border: 1px solid #c9c9c9; } diff --git a/src/GeolocationEditor/index.js b/src/GeolocationEditor/index.js index 53066816..57aa1268 100644 --- a/src/GeolocationEditor/index.js +++ b/src/GeolocationEditor/index.js @@ -1,205 +1,75 @@ -import React from 'react'; +import React, { forwardRef, useCallback, useMemo } from 'react'; import PropTypes from 'prop-types'; -import { GEOLOCATION_FORMAT, DEFAULT_GEOLOCATION_FORMAT } from 'dtable-utils'; -import { KeyCodes } from '../constants'; -import ObjectUtils from '../utils/object-utils'; -import LocationEditor from './location-editor'; -import MapEditor from './map-editor'; -import MapSelectionEditor from './map-selection-editor'; -import CountryEditor from './country-editor'; -import ProvinceEditor from './province-editor'; -import ProvinceCityEditor from './province-city-editor'; - -import './index.css'; - -const GeolocationEditorPropTypes = { - isInModal: PropTypes.bool, - value: PropTypes.oneOfType([PropTypes.object, PropTypes.string]), - column: PropTypes.object, - row: PropTypes.object, - onCommit: PropTypes.func, - onCommitCancel: PropTypes.func, - onPressTab: PropTypes.func -}; - -class GeolocationEditor extends React.Component { - - constructor(props) { - super(props); - this.value = props.value || {}; - this.initValue = this.value; - this.state = { - editorPosition: null, - }; - this.editor = null; - } - - componentDidMount() { - this.setEditorPosition(); - document.addEventListener('keydown', this.onHotKey, true); - } - - componentWillUnmount() { - document.removeEventListener('keydown', this.onHotKey, true); - } - - onHotKey = (e) => { - if (e.keyCode === KeyCodes.Enter && e.target.tagName !== 'TEXTAREA') { - this.onSubmit(); - } else if (e.keyCode === KeyCodes.Escape) { - if (this.props.isInModal) { - e.stopPropagation(); - this.onCancel(); - } - } - }; - - getValue = () => { - return this.value; - }; - - setValue = (value) => { - this.value = value; - }; - - onSubmit = () => { - if (ObjectUtils.isSameObject(this.initValue, this.value)) { - this.onCancel(); - return; - } - this.props.onCommit(this.value); - }; - - onCancel = () => { - this.props.onCommitCancel && this.props.onCommitCancel(); - }; - - setEditorPosition = () => { - const { isInModal } = this.props; - const editorFormat = this.getGeoFormat(); - if (this.ref && (editorFormat === GEOLOCATION_FORMAT.COUNTRY_REGION || editorFormat === GEOLOCATION_FORMAT.PROVINCE)) { - let height = 240; - let width = 200; - let left = this.ref.parentNode.getBoundingClientRect().x; - let top = this.ref.parentNode.getBoundingClientRect().y; - if (isInModal) { - left = 220; - } - if (top + height > window.innerHeight) { - top = window.innerHeight - height - 35; - } - this.setState({ editorPosition: { top, left, width, zIndex: 1000, position: 'fixed' } }); - return; - } - - if (this.ref && editorFormat === GEOLOCATION_FORMAT.PROVINCE_CITY) { - let height = 215; - let width = 400; - let left = this.ref.parentNode.getBoundingClientRect().x; - let top = this.ref.parentNode.getBoundingClientRect().y; - if (isInModal) { - left = 220; - if (top + height + 35 + 200 > window.innerHeight) { - top = window.innerHeight - height - 235; - } - } else { - if (top + height + 200 > window.innerHeight) { - top = window.innerHeight - height - 200 - 5; - } - if (left + width > window.innerWidth) { - left = window.innerWidth - width - 10; - } - } - this.setState({ editorPosition: { top, left, width, zIndex: 1000, position: 'fixed' } }); - return; - } - if (this.ref) { - const isMapTypeEditor = editorFormat === GEOLOCATION_FORMAT.LNG_LAT || editorFormat === GEOLOCATION_FORMAT.MAP_SELECTION; - let width = isMapTypeEditor ? 500 : 400; - let height = isMapTypeEditor ? 454 : 310; - let geoEditorTop = 0; - let geoEditorLeft = 0; - if (isInModal) { - let innerHeight = window.innerHeight; - const offsetTop = this.ref.parentNode.getBoundingClientRect().y; - geoEditorTop = offsetTop; - geoEditorTop = height + geoEditorTop > innerHeight ? innerHeight - height - 30 : geoEditorTop; - geoEditorLeft = -30; - } else { - let { offsetLeft, offsetTop } = this.ref.parentNode; - geoEditorLeft = offsetLeft - width; - geoEditorTop = offsetTop; - if (offsetLeft < width) { - geoEditorLeft = offsetLeft + this.props.column.width; +import MediaQuery from 'react-responsive'; +import PCGeolocationEditor from './pc-editor'; +import MBGeolocationEditor from './mb-editor'; + +const GeolocationEditor = forwardRef(({ isMobile, config: propsConfig, ...props }, ref) => { + + const config = useMemo(() => ({ ...window?.dtable, ...propsConfig, }), [propsConfig]); + + const getLocationData = useCallback(() => { + if (window?.app?.location) return new Promise((resolve, reject) => { + resolve(window.app.location); + }); + const { mediaUrl } = config || {}; + return fetch(`${mediaUrl}geo-data/cn-location.json`).then((res) => { // get locations from server + return res.json(); + }).catch(() => { + // get locations from local + return fetch('./geo-data/cn-location.json').then(res => { + return res.json(); + }); + }); + }, [config]); + + const getCountryData = useCallback((lang) => { + if (lang === 'cn' && window.app.countryListCn) return new Promise((resolve, reject) => { + resolve(window.app.countryListCn); + }); + if (lang !== 'cn' && window.app.countryListEn) return new Promise((resolve, reject) => { + resolve(window.app.countryListEn); + }); + + const { mediaUrl } = config; + const geoFileName = lang === 'cn' ? 'cn-region-location' : 'en-region-location'; + return fetch(`${mediaUrl}geo-data/${geoFileName}.json`) + .then(res => { + return res.json(); + }).catch(() => { + // get locations from local + return fetch(`./geo-data/${geoFileName}.json`).then(res => { + return res.json(); + }); + }).then(res => { + const data = res || {}; + if (lang === 'cn') { + window.app.countryListCn = data; + } else { + window.app.countryListEn = data; } - if (geoEditorLeft + width > window.innerWidth) { - geoEditorLeft = window.innerWidth - width; - } - if (offsetTop + height > window.innerHeight) { - geoEditorTop = window.innerHeight - height; - } - } - this.setState({ editorPosition: { top: geoEditorTop, left: geoEditorLeft, zIndex: 1000, position: 'fixed' } }); - } - }; - - getGeoFormat = () => { - const { column } = this.props; - let data = column.data || {}; - return data.geo_format ? data.geo_format : DEFAULT_GEOLOCATION_FORMAT; - }; - - getLargeEditorState = () => { - return this.editor?.state?.isShowLargeEditor; - }; - - createEditor = () => { - const geoFormat = this.getGeoFormat(); - const { config, column } = this.props; - const props = { - column, - config, - value: this.value, - setValue: this.setValue, - onSubmit: this.onSubmit, - onCancel: this.onCancel, - }; - - switch (geoFormat) { - case GEOLOCATION_FORMAT.LNG_LAT: { - return ( this.editor = ref} />); - } - case GEOLOCATION_FORMAT.MAP_SELECTION: { - return ( this.editor = ref} />); - } - case GEOLOCATION_FORMAT.COUNTRY_REGION: { - return ( this.editor = ref} />); - } - case GEOLOCATION_FORMAT.PROVINCE: { - return ( this.editor = ref} />); - } - case GEOLOCATION_FORMAT.PROVINCE_CITY: { - return ( this.editor = ref} />); - } - case GEOLOCATION_FORMAT.PROVINCE_CITY_DISTRICT: { - return ( this.editor = ref} />); - } - default: { - return ( this.editor = ref} />); - } - } - }; - - render() { - const { editorPosition } = this.state; - return ( -
this.ref = ref} style={editorPosition} className="dtable-ui-dtable-ui-geolocation-editor"> - {this.createEditor()} -
- ); - } -} - -GeolocationEditor.propTypes = GeolocationEditorPropTypes; + return data; + }); + }, [config]); + + if (isMobile === false) return (); + if (isMobile === true) return (); + + return ( + <> + + + + + + + + ); +}); + +GeolocationEditor.propTypes = { + isMobile: PropTypes.bool, + config: PropTypes.object, +}; export default GeolocationEditor; diff --git a/src/GeolocationEditor/map-editor-utils.js b/src/GeolocationEditor/map-editor-utils.js index b713e42f..ae9d6c9b 100644 --- a/src/GeolocationEditor/map-editor-utils.js +++ b/src/GeolocationEditor/map-editor-utils.js @@ -1,3 +1,8 @@ +export const INPUT_MODE_MAP = { + ANY_LOCATION: 'any_location', + ONLY_MOBILE_POSITIONING: 'only_mobile_positioning' +}; + export const MAP_TYPES = { B_MAP: 'b_map', // baidu G_MAP: 'g_map', // google @@ -16,7 +21,7 @@ const MINE_MAP_ONLINE_SERVICE = { }; export const getMineMapUrl = (config = {}) => { - const { dtableMineMapCustomConfig = {} } = { ...window.dtable, ...config }; + const { dtableMineMapCustomConfig = {} } = { ...window?.dtable, ...config }; const { domain_url = '', data_domain_url = '', server_domain_url = '', sprite_url = '', service_url = '' } = dtableMineMapCustomConfig; return { @@ -44,7 +49,7 @@ export const getInitCenter = (isSelectionEditor = false) => { }; export const getMapInfo = (config = {}) => { - const { dtableBaiduMapKey: baiduMapKey, dtableGoogleMapKey: googleMapKey, dtableMineMapKey: mineMapKey } = { ...window.dtable, ...config }; + const { dtableBaiduMapKey: baiduMapKey, dtableGoogleMapKey: googleMapKey, dtableMineMapKey: mineMapKey } = { ...window?.dtable, ...config }; let mapType; let mapKey; if (baiduMapKey) { @@ -66,7 +71,7 @@ export const loadMapSource = (mapType, mapKey, callback) => { let script = document.createElement('script'); script.type = 'text/javascript'; if (mapType === MAP_TYPES.B_MAP) { - scriptUrl = `https://api.map.baidu.com/api?v=3.0&ak=${mapKey}}&callback=renderBaiduMap`; + scriptUrl = `https://api.map.baidu.com/api?v=3.0&ak=${mapKey}&callback=renderBaiduMap`; } else if (mapType === MAP_TYPES.G_MAP) { scriptUrl = `https://maps.googleapis.com/maps/api/js?key=${mapKey}&callback=renderGoogleMap&libraries=&v=weekly`; } else { diff --git a/src/GeolocationEditor/mb-editor/country-editor.js b/src/GeolocationEditor/mb-editor/country-editor.js new file mode 100644 index 00000000..ab4892ca --- /dev/null +++ b/src/GeolocationEditor/mb-editor/country-editor.js @@ -0,0 +1,84 @@ +import React, { useCallback, useEffect, useRef, useState } from 'react'; +import PropTypes from 'prop-types'; +import { Picker, List } from 'antd-mobile'; +import MobileFullScreenPage from '../../MobileFullScreenPage'; +import { getLocale } from '../../lang'; + +const { Header, Body } = MobileFullScreenPage; + +const CountryEditor = ({ + column, + value: oldValue, + getData, + onToggle, + onCommit, +}) => { + const [value, setValue] = useState([]); + const [isLoading, setLoading] = useState(true); + + const locations = useRef([]); + + useEffect(() => { + getData().then(data => { + locations.current = data; + const continent = data.find(a => a.children.find(b => b.value === oldValue?.country_region || '')); + const value = [continent?.value || '', oldValue?.country_region || '']; + setValue(value); + setLoading(false); + }); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + const onValueChange = useCallback((newValue) => { + if (value === newValue) return; + setValue(newValue); + }, [value]); + + const onSubmit = useCallback(() => { + if (value[1] !== oldValue?.country_region) { + onCommit({ country_region: value[1] }); + } + onToggle(); + }, [value, oldValue, onCommit, onToggle]); + + return ( + +
+ <>{getLocale('Cancel')} + <>{column.name} + {getLocale('Submit')} +
+ +
+ {getLocale('Address_information')} +
+ onValueChange(e)} + okText={getLocale('Done')} + dismissText={getLocale('Cancel')} + cols={2} + > + {value[1] || getLocale('Select_location')} + + +
+ ); +}; + +CountryEditor.propTypes = { + column: PropTypes.object, + value: PropTypes.object, + getData: PropTypes.func, + onToggle: PropTypes.func, + onCommit: PropTypes.func, +}; + +export default CountryEditor; diff --git a/src/GeolocationEditor/mb-editor/index.css b/src/GeolocationEditor/mb-editor/index.css new file mode 100644 index 00000000..4aa10419 --- /dev/null +++ b/src/GeolocationEditor/mb-editor/index.css @@ -0,0 +1,11 @@ +.dtable-ui-mobile-geolocation-editor-picker .am-picker-popup-content .am-picker-popup-header-left { + color: #666666; +} + +.dtable-ui-mobile-geolocation-editor-picker .am-picker-popup-content .am-picker-popup-title { + color: #212529; +} + +.dtable-ui-mobile-geolocation-editor-picker .am-picker-popup-content .am-picker-popup-header-right { + color: #e5a252; +} diff --git a/src/GeolocationEditor/mb-editor/index.js b/src/GeolocationEditor/mb-editor/index.js new file mode 100644 index 00000000..f1ba3eb6 --- /dev/null +++ b/src/GeolocationEditor/mb-editor/index.js @@ -0,0 +1,103 @@ +import React, { useCallback, useMemo } from 'react'; +import PropTypes from 'prop-types'; +import { GEOLOCATION_FORMAT, DEFAULT_GEOLOCATION_FORMAT } from 'dtable-utils'; +import CountryEditor from './country-editor'; +import ProvinceEditor from './province-editor'; +import ProvinceCityEditor from './province-city-editor'; +import LocationEditor from './location-editor'; +import MapEditor from './map-editor'; +import MapSelectionEditor from './map-selection-editor'; + +import './index.css'; + +const transLocationData = (data) => { + if (Object.prototype.toString.call(data) === '[object Object]') { + const name = data.name; + data.label = name; + data.value = name; + data.name = null; + if (data.children) { + data.children.map(child => { + return transLocationData(child); + }); + } + } + return data.children; +}; + +const transCountryData = (data) => { + let _data = []; + for (let key in data) { + let obj = {}; + let children = []; + obj.label = key; + obj.value = key; + obj.name = null; + data[key].forEach((item) => { + let obj = {}; + obj.label = item; + obj.value = item; + obj.name = null; + children.push(obj); + }); + obj.children = children; + _data.push(obj); + } + return _data; +}; + +const MBGeolocationEditor = ({ + column, + config, + getLocationData, + getCountryData, + ...props +}) => { + const format = useMemo(() => { + let data = column.data || {}; + return data.geo_format ? data.geo_format : DEFAULT_GEOLOCATION_FORMAT; + }, [column]); + + const getData = useCallback(() => { + return getLocationData().then(data => transLocationData(data)); + }, [getLocationData]); + + const _getCountryData = useCallback(() => { + const columnData = column.data || {}; + const lang = columnData.lang === 'cn' ? 'cn' : 'en'; + return getCountryData(lang).then(data => transCountryData(data)); + }, [column, getCountryData]); + + switch (format) { + case GEOLOCATION_FORMAT.LNG_LAT: { + return (); + } + case GEOLOCATION_FORMAT.MAP_SELECTION: { + return (); + } + case GEOLOCATION_FORMAT.COUNTRY_REGION: { + return (); + } + case GEOLOCATION_FORMAT.PROVINCE: { + return (); + } + case GEOLOCATION_FORMAT.PROVINCE_CITY: { + return (); + } + case GEOLOCATION_FORMAT.PROVINCE_CITY_DISTRICT: { + return (); + } + default: { + return (); + } + } +}; + +MBGeolocationEditor.propTypes = { + column: PropTypes.object, + config: PropTypes.object, + getLocationData: PropTypes.func, + getCountryData: PropTypes.func, +}; + +export default MBGeolocationEditor; diff --git a/src/GeolocationEditor/mb-editor/location-editor.js b/src/GeolocationEditor/mb-editor/location-editor.js new file mode 100644 index 00000000..66ac6bc3 --- /dev/null +++ b/src/GeolocationEditor/mb-editor/location-editor.js @@ -0,0 +1,107 @@ +import React, { useCallback, useEffect, useRef, useState } from 'react'; +import PropTypes from 'prop-types'; +import { InputItem, Picker, List } from 'antd-mobile'; +import MobileFullScreenPage from '../../MobileFullScreenPage'; +import { getLocale } from '../../lang'; +import ObjectUtils from '../../utils/object-utils'; + +const { Header, Body } = MobileFullScreenPage; + +const LocationEditor = ({ + isShowDetails, + column, + value: oldValue, + getData, + onToggle, + onCommit, +}) => { + let initValue = [oldValue?.province || '', oldValue?.city || '', oldValue?.district || '']; + if (isShowDetails) { + initValue.push(oldValue?.detail || ''); + } + const [value, setValue] = useState(initValue); + const [isLoading, setLoading] = useState(true); + + const locations = useRef([]); + + useEffect(() => { + getData().then(data => { + locations.current = data; + setLoading(false); + }); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + const onValueChange = useCallback((newValue) => { + if (value === newValue) return; + setValue(newValue); + }, [value]); + + const onAddressChange = useCallback((address) => { + const newValue = value.slice(0, 3); + newValue.push(address); + setValue(newValue); + }, [value]); + + const onSubmit = useCallback(() => { + let newValue = { province: value[0], city: value[1], district: value[2] }; + if (isShowDetails) { + newValue['detail'] = value[3]; + } + + if (!ObjectUtils.isSameObject(newValue, oldValue)) { + onCommit(newValue); + } + onToggle(); + }, [isShowDetails, value, oldValue, onCommit, onToggle]); + + return ( + +
+ <>{getLocale('Cancel')} + <>{column.name} + {getLocale('Submit')} +
+ +
+ {getLocale('Address_information')} +
+ onValueChange(e)} + okText={getLocale('Done')} + dismissText={getLocale('Cancel')} + cols={3} + > + {value.slice(0, 3).join(' ') || getLocale('Select_location')} + + {isShowDetails && ( + <> +
+ {getLocale('Detailed_address')} +
+ + + )} + +
+ ); +}; + +LocationEditor.propTypes = { + isShowDetails: PropTypes.bool, + column: PropTypes.object, + value: PropTypes.object, + getData: PropTypes.func, + onToggle: PropTypes.func, + onCommit: PropTypes.func, +}; + +export default LocationEditor; diff --git a/src/GeolocationEditor/mb-editor/map-editor/index.css b/src/GeolocationEditor/mb-editor/map-editor/index.css new file mode 100644 index 00000000..ead2cb87 --- /dev/null +++ b/src/GeolocationEditor/mb-editor/map-editor/index.css @@ -0,0 +1,11 @@ +.dtable-ui-mobile-geolocation-map-editor .dtable-ui-mobile-geolocation-map-editor-input-container { + height: 60px; + padding-top: 16px; + width: 100%; +} + +.dtable-ui-mobile-geolocation-map-editor .dtable-ui-mobile-geolocation-map-editor-map { + flex: 1; + overflow: hidden; + width: 100%; +} diff --git a/src/GeolocationEditor/mb-editor/map-editor/index.js b/src/GeolocationEditor/mb-editor/map-editor/index.js new file mode 100644 index 00000000..2799a332 --- /dev/null +++ b/src/GeolocationEditor/mb-editor/map-editor/index.js @@ -0,0 +1,399 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { InputItem } from 'antd-mobile'; +import toaster from '../../../toaster'; +import MobileFullScreenPage from '../../../MobileFullScreenPage'; +import { DOMESTIC_MAP_TYPE, MAP_TYPES, getInitCenter, loadMapSource, getMapInfo, locateCurrentPosition, getMineMapUrl } from '../../map-editor-utils'; +import { isValidPosition } from '../../../utils/cell'; +import { getLocale } from '../../../lang'; +import Loading from '../../../Loading'; +import ObjectUtils from '../../../utils/object-utils'; + +import './index.css'; + +const { Header, Body } = MobileFullScreenPage; + +class MapEditor extends React.Component { + + constructor(props) { + super(props); + const value = props.value || {}; + const { mapType, mapKey } = getMapInfo(props.config); + this.mapType = mapType; + this.mapKey = mapKey; + const { lng, lat } = value; + this.map = null; + this.state = { + mode: 'preview', + isLoading: true, + value: value, + inputValue: isValidPosition(lng, lat) ? (DOMESTIC_MAP_TYPE.includes(this.mapType) ? `${lng}, ${lat}` : `${lat}, ${lng}`) : '', + }; + } + + componentDidMount() { + if (this.mapType === MAP_TYPES.B_MAP) { + if (!window.BMap) { + window.renderBaiduMap = () => this.renderBaiduMap(); + loadMapSource(this.mapType, this.mapKey); + } else { + this.renderBaiduMap(); + } + return; + } + if (this.mapType === MAP_TYPES.G_MAP) { + if (!window.google) { + window.renderGoogleMap = () => this.renderGoogleMap(); + loadMapSource(this.mapType, this.mapKey); + } else { + this.renderGoogleMap(); + } + return; + } + if (this.mapType === MAP_TYPES.M_MAP) { + if (!window.minemap) { + loadMapSource(this.mapType, this.mapKey, this.loadMineMapCallBack); + } else { + this.renderMineMap(); + } + return; + } + } + + componentWillUnmount() { + let center = {}; + if (this.map && DOMESTIC_MAP_TYPE.includes(this.mapType)) { + center.zoom = this.map.getZoom(); + let coordinate = this.map.getCenter(); + center.lng = coordinate.lng; + center.lat = coordinate.lat; + this.mineMapMarker = null; + } else if (this.map && this.mapType === MAP_TYPES.G_MAP) { + let zoom = this.map.getZoom(); + let coordinate = this.map.getCenter(); + center.zoom = zoom; + center.lat = coordinate.lat(); + center.lng = coordinate.lng(); + this.googleMarker = null; + } + localStorage.setItem('geolocation-map-center', JSON.stringify(center)); + this.map = null; + } + + loadMineMapCallBack = () => { + if (!this.timer) { + this.timer = setTimeout(() => { + const { domainUrl, dataDomainUrl, serverDomainUrl, spriteUrl, serviceUrl } = getMineMapUrl(); + window.minemap.domainUrl = domainUrl; + window.minemap.dataDomainUrl = dataDomainUrl; + window.minemap.serverDomainUrl = serverDomainUrl; + window.minemap.spriteUrl = spriteUrl; + window.minemap.serviceUrl = serviceUrl; + window.minemap.key = this.mapKey; + window.minemap.solution = 11001; + this.renderMineMap(); + clearTimeout(this.timer); + this.timer = null; + }, 1000); + } + }; + + renderMineMap = () => { + this.setState({ isLoading: false }, () => { + if (!window.minemap.key) return; + setTimeout(() => { + this.map = new window.minemap.Map({ + container: 'dtable-ui-mobile-geolocation-map-container', + style: 'https://service.minedata.cn/map/solu/style/11001', + pitch: 0, + maxZoom: 17, + minZoom: 3, + projection: 'MERCATOR' + }); + let { lng, lat, zoom } = getInitCenter(); + const { value } = this.state; + if (isValidPosition(value.lng, value.lat)) { + lng = value.lng; + lat = value.lat; + this.addMarkerByPosition(lng, lat); + } + this.map.setCenter([lng, lat]); + this.map.setZoom(zoom); + if (this.readOnly) return; + this.map.on('mousedown', (event) => { + const point = event.lngLat; + this.setValue(point); + this.addMarkerByPosition(point.lng, point.lat); + }); + }, 1); + }); + }; + + renderBaiduMap = () => { + this.setState({ isLoading: false }, () => { + if (!window.BMap.Map) return; + setTimeout(() => { + this.map = new window.BMap.Map('dtable-ui-mobile-geolocation-map-container'); + let { lng, lat, zoom } = getInitCenter(); + const { value } = this.state; + if (isValidPosition(value.lng, value.lat)) { + lng = value.lng; + lat = value.lat; + this.addMarkerByPosition(lng, lat); + } + const point = new window.BMap.Point(lng, lat); + this.map.centerAndZoom(point, zoom); + this.map.enableScrollWheelZoom(true); + this.map.addEventListener('click', (event) => { + if (this.state.mode === 'preview') return; + const point = event.point; + this.setValue(point); + this.addMarkerByPosition(point.lng, point.lat); + }); + }, 1); + }); + }; + + geolocationCallback = (error, point) => { + if (!error) { + this.setValue({ ...point }); + this.addMarkerByPosition(point.lng, point.lat); + if (this.mapType === MAP_TYPES.G_MAP) { + this.map.setCenter({ lng: point.lng, lat: point.lat }); + } + } else { + toaster.danger(getLocale('Positioning_failed')); + } + }; + + renderGoogleMap = () => { + this.setState({ isLoading: false }, () => { + setTimeout(() => { + let { lng, lat, zoom } = getInitCenter(); + this.map = new window.google.maps.Map(this.ref, { + zoom, + center: { lng, lat }, + zoomControl: false, + mapTypeControl: false, + scaleControl: false, + streetViewControl: false, + rotateControl: false, + fullscreenControl: false + }); + const { value } = this.state; + if (value.lng && value.lat) { + lng = value.lng; + lat = value.lat; + this.addMarkerByPosition(lng, lat); + } + this.map.setCenter({ lng, lat }); + this.map.addListener('mousedown', (event) => { + const lng = event.latLng.lng(); + const lat = event.latLng.lat(); + const point = { lng, lat }; + this.setValue(point); + this.addMarkerByPosition(lng, lat); + }); + }, 1); + }); + }; + + setValue = (point) => { + this.setState({ + value: { + lng: point.lng, + lat: point.lat + }, + inputValue: DOMESTIC_MAP_TYPE.includes(this.mapType) ? `${point.lng}, ${point.lat}` : `${point.lat}, ${point.lng}` + }); + }; + + addMarkerByPosition = (lng, lat) => { + if (this.mapType === MAP_TYPES.B_MAP) { + let point = new window.BMap.Point(lng, lat); + const marker = new window.BMap.Marker(point, { offset: new window.BMap.Size(-2, -5) }); + this.map && this.map.clearOverlays(); + this.map && this.map.addOverlay(marker); + return; + } + if (this.mapType === MAP_TYPES.G_MAP) { + if (!this.googleMarker) { + this.googleMarker = new window.google.maps.Marker({ + position: { lng, lat }, + map: this.map, + }); + return; + } + this.googleMarker.setPosition({ lng, lat }); + this.googleMarker.setMap(this.map); + return; + } + if (this.mapType === MAP_TYPES.M_MAP) { + if (!this.mineMapMarker) { + this.mineMapMarker = new window.minemap.Marker({ + draggable: false, + anchor: 'top-left', + color: 'red', + rotation: 0, + pitchAlignment: 'map', + rotationAlignment: 'map', + scale: 0.8 + }).setLngLat({ lng, lat }).addTo(this.map); + } else { + this.mineMapMarker.setLngLat({ lng, lat }); + } + return; + } + }; + + clearSearchNumerical = () => { + this.setState({ inputValue: '', value: {} }); + }; + + onSubmit = () => { + const { value } = this.state; + if (!ObjectUtils.isSameObject(value, this.props.value)) { + this.props.onCommit(value); + } + this.onClose(); + }; + + onChange = (inputValue) => { + this.setState({ inputValue }); + const enSplitCodeIndex = inputValue.indexOf(','); + const cnSplitCodeIndex = inputValue.indexOf(','); + if (enSplitCodeIndex > 0 || cnSplitCodeIndex > 0) { + let lng; + let lat; + const splitCodeIndex = enSplitCodeIndex > 0 ? enSplitCodeIndex : cnSplitCodeIndex; + if (this.mapType === MAP_TYPES.G_MAP) { + lat = parseFloat(inputValue.slice(0, splitCodeIndex).trim()); + lng = parseFloat(inputValue.slice(splitCodeIndex + 1).trim()); + } else { + lng = parseFloat(inputValue.slice(0, splitCodeIndex).trim()); + lat = parseFloat(inputValue.slice(splitCodeIndex + 1).trim()); + } + if (!Number.isNaN(lng) && !Number.isNaN(lat)) { + this.setState({ + value: { lng, lat } + }); + if (this.map) { + if (this.mapType === MAP_TYPES.G_MAP || this.mapType === MAP_TYPES.M_MAP) { + this.map.setCenter({ lng, lat }); + } else { + this.map.setCenter(new window.BMap.Point(lng, lat)); + } + } + this.addMarkerByPosition(lng, lat); + } + } + }; + + onZoomIn = () => { + if (!this.map) return; + const currentZoom = this.map.getZoom(); + this.map.setZoom(currentZoom + 1); + }; + + onZoomOut = () => { + if (!this.map) return; + const currentZoom = this.map.getZoom(); + this.map.setZoom(currentZoom - 1); + }; + + onLocateCurrentPosition = () => { + locateCurrentPosition(this.map, this.mapType, this.geolocationCallback); + }; + + onLeftClick = () => { + if (this.state.mode === 'preview') { + this.onClose(); + return; + } + this.setState({ mode: 'preview' }); + }; + + onRightClick = () => { + if (this.state.mode === 'preview') { + this.setState({ mode: 'edit' }); + return; + } + this.onSubmit(); + }; + + onClose = () => { + this.props.onToggle(); + }; + + renderMap = () => { + const { isLoading, inputValue, mode } = this.state; + if (isLoading) return (
); + if (!this.mapType) { + return ( +
+ {getLocale('The_map_plugin_is_not_properly_configured_contact_the_administrator')} +
+ ); + } + const isEdit = mode === 'edit'; + return ( + <> +
+ +
+
+
this.ref = ref} id="dtable-ui-mobile-geolocation-map-container">
+
+
+ +
+
+
+ +
+
+ +
+
+
+
+ + ); + }; + + render() { + const { column } = this.props; + const { mode } = this.state; + const isEdit = mode === 'edit'; + + return ( + +
+ <>{isEdit ? getLocale('Cancel') : getLocale('Close')} + <>{column.name} + {isEdit ? getLocale('Submit') : getLocale('Edit')} +
+ + {this.renderMap()} + +
+ ); + } +} + +MapEditor.propTypes = { + column: PropTypes.object, + value: PropTypes.object, + onToggle: PropTypes.func, + onCommit: PropTypes.func, +}; + +export default MapEditor; diff --git a/src/GeolocationEditor/mb-editor/map-selection-editor/index.css b/src/GeolocationEditor/mb-editor/map-selection-editor/index.css new file mode 100644 index 00000000..42420a63 --- /dev/null +++ b/src/GeolocationEditor/mb-editor/map-selection-editor/index.css @@ -0,0 +1,158 @@ +.dtable-ui-mobile-geolocation-map-selection-editor .dtable-ui-mobile-geolocation-map-editor-input-container { + display: flex; + background: #fff; + align-items: center; + padding: 7px 20px; + height: 44px; + width: 100%; +} + +.dtable-ui-mobile-geolocation-map-selection-editor .dtable-ui-mobile-geolocation-map-editor-input { + height: 30px !important; + min-height: unset; + border: 1px solid #ddd; + width: 86%; + border-radius: 3px 0 0 3px; +} + +.dtable-ui-mobile-geolocation-map-selection-editor .dtable-ui-mobile-geolocation-map-editor-input input { + font-size: 16px !important; +} + +.dtable-ui-mobile-geolocation-map-selection-editor .dtable-ui-mobile-geolocation-map-editor-search-btn { + height: 30px; + width: 14%; + border: 1px solid #ddd; + border-left: none; + border-radius: 0 3px 3px 0; + padding: unset; + box-shadow: unset; + color: #666; + background-color: #f5f5f5; + font-size: 0.875rem; + line-height: 1.8; + display: inline-block; + text-align: center; + white-space: nowrap; + vertical-align: middle; + user-select: none; +} + +.dtable-ui-mobile-geolocation-map-selection-editor .dtable-ui-mobile-geolocation-map-editor-map { + flex: 1; + overflow: hidden; + width: 100%; + position: relative; +} + +/* search results */ +.dtable-ui-mobile-geolocation-map-selection-editor .dtable-ui-mobile-map-selection-editor-search-results { + height: 100%; + width: 100%; + position: absolute; + top: 0; + left: 0; + background: #fff; + border-top: 1px solid #eee; + overflow-y: scroll; +} + +.dtable-ui-mobile-geolocation-map-selection-editor .dtable-ui-mobile-map-selection-editor-search-result { + height: 56px; + border-bottom: 1px solid #ddd; + display: flex; + align-items: center; + padding: 8px 16px; +} + +.dtable-ui-mobile-geolocation-map-selection-editor .dtable-ui-mobile-map-selection-editor-search-result { + height: 56px; + border-bottom: 1px solid #ddd; + display: flex; + align-items: flex-start; + flex-direction: column; + padding: 8px 16px; +} + +.dtable-ui-mobile-geolocation-map-selection-editor .dtable-ui-mobile-map-selection-editor-search-result-title { + font-size: 14px +} + +.dtable-ui-mobile-geolocation-map-selection-editor .dtable-ui-mobile-map-selection-editor-search-result-address { + color: #666; + font-size: 12px; +} + +.dtable-ui-mobile-geolocation-map-selection-editor .dtable-ui-mobile-map-selection-editor-search-result span { + display: inline-block; + width: 100%; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.dtable-ui-mobile-geolocation-map-selection-editor .dtable-ui-mobile-map-selection-editor-search-result:last-child { + border-bottom: none; +} + +.dtable-ui-mobile-geolocation-map-selection-editor .dtable-ui-mobile-geolocation-map-editor-label { + width: 100%; + min-height: 120px; + height: fit-content; + background-color: #fff; + position: relative; + flex-shrink: 0; +} + +.dtable-ui-mobile-geolocation-map-selection-editor .dtable-ui-mobile-geolocation-map-editor-label.simple { + min-height: 95px; +} + +.dtable-ui-mobile-geolocation-map-selection-editor .dtable-ui-mobile-geolocation-map-editor-label-title { + display: block; + width: 85%; + margin-left: 15px; + margin-top: 10px; + font-size: 18px; + font-weight: 500; +} + +.dtable-ui-mobile-geolocation-map-selection-editor .dtable-ui-mobile-geolocation-map-editor-label .dtable-icon-x { + position: absolute; + right: 10px; + top: 5px; + font-size: 18px; + color: #666; +} + +.dtable-ui-mobile-geolocation-map-selection-editor .dtable-ui-mobile-geolocation-map-editor-label-address-tip { + display: block; + margin-left: 15px; + margin-top: 0.25rem; + color: #666; + font-size: 12px; +} + +.dtable-ui-mobile-geolocation-map-selection-editor .dtable-ui-mobile-geolocation-map-editor-label-tag { + margin-left: 15px; + font-size: 12px; +} + +.dtable-ui-mobile-geolocation-map-selection-editor .dtable-ui-mobile-geolocation-map-editor-label-address { + width: 77%; + display: block; + margin-left: 15px; + margin-bottom: 1.5rem; + font-size: 14px; +} + +.dtable-ui-mobile-geolocation-map-selection-editor .dtable-ui-mobile-geolocation-map-editor-label-address.simple { + margin-top: 2rem; +} + +.dtable-ui-mobile-geolocation-map-selection-editor .dtable-ui-mobile-geolocation-map-editor-label-btn { + position: absolute; + right: 10px; + bottom: 1rem; + font-weight: normal; +} diff --git a/src/GeolocationEditor/mb-editor/map-selection-editor/index.js b/src/GeolocationEditor/mb-editor/map-selection-editor/index.js new file mode 100644 index 00000000..51103421 --- /dev/null +++ b/src/GeolocationEditor/mb-editor/map-selection-editor/index.js @@ -0,0 +1,407 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { Button } from 'reactstrap'; +import { InputItem } from 'antd-mobile'; +import { isEmptyObject } from 'dtable-utils'; +import toaster from '../../../toaster'; +import MobileFullScreenPage from '../../../MobileFullScreenPage'; +import { MAP_TYPES, INPUT_MODE_MAP, getInitCenter, loadMapSource, getMapInfo, locateCurrentPosition } from '../../map-editor-utils'; +import { isValidPosition } from '../../../utils/cell'; +import { getLocale } from '../../../lang'; +import Loading from '../../../Loading'; + +import './index.css'; + +const { Header, Body } = MobileFullScreenPage; + +const getInputMode = (column) => { + const data = column.data; + if (!data) return INPUT_MODE_MAP.ANY_LOCATION; + return data.input_mode || INPUT_MODE_MAP.ANY_LOCATION; +}; + +const getInitValue = (value) => { + if (!value || isEmptyObject(value) || !value.address) { + return { address: '', title: '', tag: '', lngLat: {} }; + } + return value; +}; + +class MapSelectionEditor extends React.Component { + + constructor(props) { + super(props); + const { value, config, column } = props; + const oldValue = value || {}; + const { mapType, mapKey } = getMapInfo(config); + this.mapType = mapType; + this.mapKey = mapKey; + const mode = getInputMode(column); + this.canSelectPosition = mode === INPUT_MODE_MAP.ANY_LOCATION; + + const initValue = getInitValue(value); + this.map = null; + this.state = { + isLoading: true, + value: oldValue, + inputValue: initValue.title || initValue.address || '', + isShowSearchView: false, + isShowLabel: false, + searchResults: [], + selectedSelection: {} + }; + } + + componentDidMount() { + if (this.mapType !== MAP_TYPES.B_MAP) { + this.setState({ isLoading: false }); + return; + } + if (!window.BMap) { + window.renderBaiduMap = () => this.renderBaiduMap(); + loadMapSource(this.mapType, this.mapKey); + return; + } + this.renderBaiduMap(); + } + + componentWillUnmount() { + let center = {}; + if (!this.map) return; + center.zoom = this.map.getZoom(); + const coordinate = this.map.getCenter(); + center.lng = coordinate.lng; + center.lat = coordinate.lat; + this.mineMapMarker = null; + localStorage.setItem('geolocation-map-center', JSON.stringify(center)); + this.map = null; + } + + addMarkerByPosition = (value) => { + const { lngLat } = value; + let point = new window.BMap.Point(lngLat.lng, lngLat.lat); + const marker = new window.BMap.Marker(point, { offset: new window.BMap.Size(-2, -5) }); + if (this.map) { + this.map.clearOverlays(); + this.map.addOverlay(marker); + this.map.centerAndZoom(point, 10); + } + }; + + realTimePositioning = (error, point) => { + if (error) { + toaster.danger(getLocale('Positioning_failed')); + return; + } + const geoCoder = new window.BMap.Geocoder(); + let value = {}; + geoCoder.getLocation(point, (result) => { + const { surroundingPois, address, point } = result; + if (surroundingPois.length === 0) { + value.address = address; + value.lngLat = { lng: point.lng, lat: point.lat }; + value.tag = []; + value.title = ''; + } else { + const position = surroundingPois[0]; + const { address, title, tags, point } = position; + value.address = address || ''; + value.title = title || ''; + value.tag = tags || []; + value.lngLat = { lng: point.lng, lat: point.lat }; + } + this.setState({ isShowLabel: true, selectedSelection: value, inputValue: value.title || value.address }); + this.addMarkerByPosition(value); + }); + }; + + renderBaiduMap = () => { + this.setState({ isLoading: false }, () => { + if (!window.BMap.Map) return; + setTimeout(() => { + this.map = new window.BMap.Map('dtable-ui-mobile-geolocation-map-container'); + let { lng, lat, zoom } = getInitCenter(); + const { value } = this.state; + const { lngLat } = value; + if (isValidPosition(lngLat.lng, lngLat.lat)) { + lng = lngLat.lng; + lat = lngLat.lat; + this.setState({ isShowLabel: true, selectedSelection: value }); + this.addMarkerByPosition(value); + } + let point = new window.BMap.Point(lng, lat); + this.map.centerAndZoom(point, zoom); + this.map.enableScrollWheelZoom(true); + // normal map selection + this.canSelectPosition && this.map.addEventListener('click', (event) => { + let value = {}; + const point = event.point; + const geoCoder = new window.BMap.Geocoder(); + geoCoder.getLocation(point, (result) => { + const { surroundingPois, address, point } = result; + if (surroundingPois.length === 0) { + value.address = address; + value.lngLat = { lng: point.lng, lat: point.lat }; + value.tag = []; + value.title = ''; + } else { + const position = surroundingPois[0]; + const { address, title, tags, point } = position; + value.address = address || ''; + value.title = title || ''; + value.tag = tags || []; + value.lngLat = { lng: point.lng, lat: point.lat }; + } + this.setState({ isShowLabel: true, selectedSelection: value, inputValue: value.title || value.address }); + this.addMarkerByPosition(value); + }); + }); + // for mobile device real-time positioning, use current position + !this.canSelectPosition && locateCurrentPosition(this.map, this.mapType, this.realTimePositioning); + }, 1); + }); + }; + + geolocationCallback = (error, point) => { + if (error) { + toaster.danger(getLocale('Positioning_failed')); + return; + } + const geoCoder = new window.BMap.Geocoder(); + geoCoder.getLocation(point, (result) => { + let value = {}; + const { surroundingPois, address, point } = result; + if (surroundingPois.length === 0) { + value.address = address; + value.lngLat = { lng: point.lng, lat: point.lat }; + value.tag = []; + value.title = ''; + } else { + const position = surroundingPois[0]; + const { address, title, tags, point } = position; + value.address = address || ''; + value.title = title || ''; + value.tag = tags || []; + value.lngLat = { lng: point.lng, lat: point.lat }; + } + this.setState({ isShowLabel: true, selectedSelection: value }); + this.addMarkerByPosition(value); + }); + }; + + isEmptySelection = (value) => { + return !value.title && !value.address && !value.tag && isEmptyObject(value.lngLat); + }; + + clearSearchNumerical = () => { + this.setState({ inputValue: '', value: {} }); + }; + + onChange = (inputValue) => { + this.setState({ inputValue }); + }; + + onZoomIn = () => { + if (!this.map) return; + const currentZoom = this.map.getZoom(); + this.map.setZoom(currentZoom + 1); + }; + + onZoomOut = () => { + if (!this.map) return; + const currentZoom = this.map.getZoom(); + this.map.setZoom(currentZoom - 1); + }; + + onLocateCurrentPosition = () => { + locateCurrentPosition(this.map, this.mapType, this.geolocationCallback); + }; + + onClickSelection = (result) => { + this.setState({ + searchResults: [], + inputValue: result.title || result.address, + selectedSelection: result, + isShowSearchView: false, + isShowLabel: true + }, () => { + this.addMarkerByPosition(result); + }); + }; + + onCommit = () => { + const { value } = this.state; + const isEmptyMapSelection = this.isEmptySelection(value); + this.props.onCommit(isEmptyMapSelection ? '' : value); + }; + + onClose = () => { + this.props.onToggle(); + }; + + onFillIn = () => { + const { selectedSelection } = this.state; + this.setState({ value: selectedSelection, isShowLabel: false }, () => { + toaster.success(getLocale('Successfully_filled_in')); + this.onCommit(); + }); + }; + + onCloseLabel = (e) => { + e.stopPropagation(); + this.map.clearOverlays(); + this.setState({ isShowLabel: false, selectedSelection: {}, inputValue: '' }); + }; + + onSearch = (e) => { + e.stopPropagation(); + const { inputValue } = this.state; + if (!inputValue) return; + const options = { + onSearchComplete: (results) => { + const status = local.getStatus(); + if (status !== window.BMAP_STATUS_SUCCESS) { + toaster.danger(getLocale('Search_failed_please_enter_detailed_address')); + return; + } + let searchResults = []; + for (let i = 0; i < results.getCurrentNumPois(); i++) { + const value = results.getPoi(i); + let position = {}; + position.address = value.address || ''; + position.title = value.title || ''; + position.tag = value.tags || []; + position.lngLat = { lng: value.point.lng, lat: value.point.lat }; + searchResults.push(position); + } + this.setState({ searchResults, isShowSearchView: true, isShowLabel: false }); + } + }; + let local = new window.BMap.LocalSearch(this.map, options); + local.search(inputValue); + }; + + renderSearchResult = () => { + const { searchResults } = this.state; + if (searchResults.length === 0) return null; + return ( +
+ {searchResults.map((result, index) => { + return ( +
+ {result.title || ''} + {result.address || ''} +
+ ); + })} +
+ ); + }; + + renderLabel = () => { + const { selectedSelection } = this.state; + const { address, title, tag } = selectedSelection; + const tagContent = Array.isArray(tag) && tag.length > 0 ? tag[0] : ''; + if (title) { + return ( +
+ {title} + {tagContent && {tagContent}} + + {getLocale('Address')} + {address} + +
+ ); + } + return ( +
+ + {address} + +
+ ); + }; + + renderMap = () => { + const { isLoading, inputValue, isShowSearchView, isShowLabel } = this.state; + if (isLoading) return (
); + if (!this.mapType) { + return ( +
+ {getLocale('The_map_plugin_is_not_properly_configured_contact_the_administrator')} +
+ ); + } + if (this.mapType !== MAP_TYPES.B_MAP) { + return ( +
+ {getLocale('This_map_type_currently_does_not_support_map_point_selection')} +
+ ); + } + return ( + <> + {this.canSelectPosition && ( +
+ +
+ +
+
+ )} +
+
this.ref = ref} id="dtable-ui-mobile-geolocation-map-container">
+
+
+ +
+
+
+ +
+
+ +
+
+
+ {isShowSearchView && this.renderSearchResult()} +
+ {isShowLabel && this.renderLabel()} + + ); + }; + + render() { + const { column } = this.props; + + return ( + +
+ + <>{column.name} +
+ + {this.renderMap()} + +
+ ); + } +} + +MapSelectionEditor.propTypes = { + column: PropTypes.object, + value: PropTypes.object, + onToggle: PropTypes.func, + onCommit: PropTypes.func, +}; + +export default MapSelectionEditor; diff --git a/src/GeolocationEditor/mb-editor/province-city-editor.js b/src/GeolocationEditor/mb-editor/province-city-editor.js new file mode 100644 index 00000000..a434806b --- /dev/null +++ b/src/GeolocationEditor/mb-editor/province-city-editor.js @@ -0,0 +1,81 @@ +import React, { useCallback, useEffect, useRef, useState } from 'react'; +import PropTypes from 'prop-types'; +import { Picker, List } from 'antd-mobile'; +import MobileFullScreenPage from '../../MobileFullScreenPage'; +import { getLocale } from '../../lang'; + +const { Header, Body } = MobileFullScreenPage; + +const ProvinceCityEditor = ({ + column, + value: oldValue, + getData, + onToggle, + onCommit, +}) => { + const [value, setValue] = useState([oldValue?.province || '', oldValue?.city || '']); + const [isLoading, setLoading] = useState(true); + + const locations = useRef([]); + + useEffect(() => { + getData().then(data => { + locations.current = data; + setLoading(false); + }); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + const onValueChange = useCallback((newValue) => { + if (value === newValue) return; + setValue(newValue); + }, [value]); + + const onSubmit = useCallback(() => { + if (value[0] !== oldValue?.province || value[1] !== oldValue?.city) { + onCommit({ province: value[0], city: value[1] }); + } + onToggle(); + }, [value, oldValue, onCommit, onToggle]); + + return ( + +
+ <>{getLocale('Cancel')} + <>{column.name} + {getLocale('Submit')} +
+ +
+ {getLocale('Address_information')} +
+ onValueChange(e)} + okText={getLocale('Done')} + dismissText={getLocale('Cancel')} + cols={2} + > + {value.join(' ') || getLocale('Select_location')} + + +
+ ); +}; + +ProvinceCityEditor.propTypes = { + column: PropTypes.object, + value: PropTypes.object, + getData: PropTypes.func, + onToggle: PropTypes.func, + onCommit: PropTypes.func, +}; + +export default ProvinceCityEditor; diff --git a/src/GeolocationEditor/mb-editor/province-editor.js b/src/GeolocationEditor/mb-editor/province-editor.js new file mode 100644 index 00000000..a639f510 --- /dev/null +++ b/src/GeolocationEditor/mb-editor/province-editor.js @@ -0,0 +1,81 @@ +import React, { useCallback, useEffect, useRef, useState } from 'react'; +import PropTypes from 'prop-types'; +import { Picker, List } from 'antd-mobile'; +import MobileFullScreenPage from '../../MobileFullScreenPage'; +import { getLocale } from '../../lang'; + +const { Header, Body } = MobileFullScreenPage; + +const ProvinceEditor = ({ + column, + value: oldValue, + getData, + onToggle, + onCommit, +}) => { + const [value, setValue] = useState([oldValue?.province || '']); + const [isLoading, setLoading] = useState(true); + + const locations = useRef([]); + + useEffect(() => { + getData().then(data => { + locations.current = data; + setLoading(false); + }); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + const onValueChange = useCallback((newValue) => { + if (value[0] === newValue[0]) return; + setValue(newValue); + }, [value]); + + const onSubmit = useCallback(() => { + if (value[0] !== oldValue?.province) { + onCommit({ province: value[0] }); + } + onToggle(); + }, [value, oldValue, onCommit, onToggle]); + + return ( + +
+ <>{getLocale('Cancel')} + <>{column.name} + {getLocale('Submit')} +
+ +
+ {getLocale('Address_information')} +
+ onValueChange(e)} + okText={getLocale('Done')} + dismissText={getLocale('Cancel')} + cols={1} + > + {value[0] || getLocale('Select_location')} + + +
+ ); +}; + +ProvinceEditor.propTypes = { + column: PropTypes.object, + value: PropTypes.object, + getData: PropTypes.func, + onToggle: PropTypes.func, + onCommit: PropTypes.func, +}; + +export default ProvinceEditor; diff --git a/src/GeolocationEditor/country-editor.js b/src/GeolocationEditor/pc-editor/country-editor.js similarity index 86% rename from src/GeolocationEditor/country-editor.js rename to src/GeolocationEditor/pc-editor/country-editor.js index e80ba1fc..77e4c1da 100644 --- a/src/GeolocationEditor/country-editor.js +++ b/src/GeolocationEditor/pc-editor/country-editor.js @@ -1,9 +1,9 @@ import React, { Component, Fragment } from 'react'; import PropTypes from 'prop-types'; import { Input } from 'reactstrap'; -import { KeyCodes } from '../constants'; -import Loading from '../Loading'; -import { getLocale } from '../lang'; +import { KeyCodes } from '../../constants'; +import Loading from '../../Loading'; +import { getLocale } from '../../lang'; const propTypes = { config: PropTypes.object, @@ -36,29 +36,10 @@ class CountryEditor extends Component { componentDidMount() { document.addEventListener('keydown', this.onHotKey, true); - if (this.lang === 'cn' && window.app.countryListCn) { - this.geolocationRegions = window.app.countryListCn; + this.props.getData(this.lang).then(data => { + this.geolocationRegions = data; this.filteredCountry = this.geolocationRegions; this.setState({ isLoadingData: false }); - return; - } - - if (this.lang !== 'cn' && window.app.countryListEn) { - this.geolocationRegions = window.app.countryListEn; - this.filteredCountry = this.geolocationRegions; - this.setState({ isLoadingData: false }); - return; - } - - this.getLocationData().then((data) => { - this.geolocationRegions = data || {}; - this.filteredCountry = this.geolocationRegions; - if (this.lang === 'cn') { - window.app.countryListCn = this.geolocationRegions; - } else { - window.app.countryListEn = this.geolocationRegions; - } - this.setState({ isLoadingData: false }); }); } @@ -175,25 +156,6 @@ class CountryEditor extends Component { }, 300); }; - getLocationData = () => { - const { config } = this.props; - const { mediaUrl } = { ...window.dtable, ...config }; - let geoFileName = 'en-region-location'; - if (this.lang === 'cn') { - geoFileName = 'cn-region-location'; - } - return fetch(`${mediaUrl}geo-data/${geoFileName}.json`) - .then(res => { - return res.json(); - }) - .catch(() => { - // get locations from local - return fetch(`./geo-data/${geoFileName}.json`).then(res => { - return res.json(); - }); - }); - }; - createContinentList = () => { let isSearchResultEmpty = true; const continents = Object.keys(this.filteredCountry); diff --git a/src/GeolocationEditor/index.css b/src/GeolocationEditor/pc-editor/index.css similarity index 98% rename from src/GeolocationEditor/index.css rename to src/GeolocationEditor/pc-editor/index.css index 356b1bb1..23e539a1 100644 --- a/src/GeolocationEditor/index.css +++ b/src/GeolocationEditor/pc-editor/index.css @@ -205,7 +205,6 @@ } .dtable-ui-geolocation-map-editor .search-tables-input { - line-height: 38px; padding-right: 30px; border-bottom: 0; background: #fff; @@ -435,7 +434,7 @@ box-shadow: 0 -0 3px rgb(0 0 0 / 30%); } -.dtable-ui-geolocation-map-editor .error-message { +.dtable-ui-geolocation-map-editor-error-message { word-break: break-all; padding: 10px; margin: auto; @@ -480,7 +479,7 @@ background-color: #f5f5f5; } -@media screen and (max-width: 767.8px) { +@media screen and (max-width: 768px) { .dtable-ui-geolocation-locate-control, .dtable-ui-geolocation-zoom-control .dtable-ui-geolocation-zoom-control-btn { margin-bottom: 4px; @@ -494,7 +493,7 @@ } } -@media screen and (min-width: 767.8px) { +@media screen and (min-width: 768px) { .dtable-ui-geolocation-locate-control, .dtable-ui-geolocation-zoom-control .dtable-ui-geolocation-zoom-control-btn { margin-bottom: 4px; diff --git a/src/GeolocationEditor/pc-editor/index.js b/src/GeolocationEditor/pc-editor/index.js new file mode 100644 index 00000000..5ce4c0be --- /dev/null +++ b/src/GeolocationEditor/pc-editor/index.js @@ -0,0 +1,202 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { GEOLOCATION_FORMAT, DEFAULT_GEOLOCATION_FORMAT } from 'dtable-utils'; +import { KeyCodes } from '../../constants'; +import ObjectUtils from '../../utils/object-utils'; +import LocationEditor from './location-editor'; +import MapEditor from './map-editor'; +import MapSelectionEditor from './map-selection-editor'; +import CountryEditor from './country-editor'; +import ProvinceEditor from './province-editor'; +import ProvinceCityEditor from './province-city-editor'; + +import './index.css'; + +class PCGeolocationEditor extends React.Component { + + constructor(props) { + super(props); + this.value = props.value || {}; + this.initValue = this.value; + this.state = { + editorPosition: null, + }; + this.editor = null; + } + + componentDidMount() { + this.setEditorPosition(); + document.addEventListener('keydown', this.onHotKey, true); + } + + componentWillUnmount() { + document.removeEventListener('keydown', this.onHotKey, true); + } + + onHotKey = (e) => { + if (e.keyCode === KeyCodes.Enter && e.target.tagName !== 'TEXTAREA') { + this.onSubmit(); + } else if (e.keyCode === KeyCodes.Escape) { + if (this.props.isInModal) { + e.stopPropagation(); + this.onCancel(); + } + } + }; + + getValue = () => { + return this.value; + }; + + setValue = (value) => { + this.value = value; + }; + + onSubmit = () => { + if (ObjectUtils.isSameObject(this.initValue, this.value)) { + this.onCancel(); + return; + } + this.props.onCommit(this.value); + }; + + onCancel = () => { + this.props.onToggle && this.props.onToggle(); + }; + + setEditorPosition = () => { + const { isInModal } = this.props; + const editorFormat = this.getGeoFormat(); + if (this.ref && (editorFormat === GEOLOCATION_FORMAT.COUNTRY_REGION || editorFormat === GEOLOCATION_FORMAT.PROVINCE)) { + let height = 240; + let width = 200; + let left = this.ref.parentNode.getBoundingClientRect().x; + let top = this.ref.parentNode.getBoundingClientRect().y; + if (isInModal) { + left = 220; + } + if (top + height > window.innerHeight) { + top = window.innerHeight - height - 35; + } + this.setState({ editorPosition: { top, left, width, zIndex: 1000, position: 'fixed' } }); + return; + } + + if (this.ref && editorFormat === GEOLOCATION_FORMAT.PROVINCE_CITY) { + let height = 215; + let width = 400; + let left = this.ref.parentNode.getBoundingClientRect().x; + let top = this.ref.parentNode.getBoundingClientRect().y; + if (isInModal) { + left = 220; + if (top + height + 35 + 200 > window.innerHeight) { + top = window.innerHeight - height - 235; + } + } else { + if (top + height + 200 > window.innerHeight) { + top = window.innerHeight - height - 200 - 5; + } + if (left + width > window.innerWidth) { + left = window.innerWidth - width - 10; + } + } + this.setState({ editorPosition: { top, left, width, zIndex: 1000, position: 'fixed' } }); + return; + } + if (this.ref) { + const isMapTypeEditor = editorFormat === GEOLOCATION_FORMAT.LNG_LAT || editorFormat === GEOLOCATION_FORMAT.MAP_SELECTION; + let width = isMapTypeEditor ? 500 : 400; + let height = isMapTypeEditor ? 454 : 310; + let geoEditorTop = 0; + let geoEditorLeft = 0; + if (isInModal) { + let innerHeight = window.innerHeight; + const offsetTop = this.ref.parentNode.getBoundingClientRect().y; + geoEditorTop = offsetTop; + geoEditorTop = height + geoEditorTop > innerHeight ? innerHeight - height - 30 : geoEditorTop; + geoEditorLeft = -30; + } else { + let { offsetLeft, offsetTop } = this.ref.parentNode; + geoEditorLeft = offsetLeft - width; + geoEditorTop = offsetTop; + if (offsetLeft < width) { + geoEditorLeft = offsetLeft + this.props.column.width; + } + if (geoEditorLeft + width > window.innerWidth) { + geoEditorLeft = window.innerWidth - width; + } + if (offsetTop + height > window.innerHeight) { + geoEditorTop = window.innerHeight - height; + } + } + this.setState({ editorPosition: { top: geoEditorTop, left: geoEditorLeft, zIndex: 1000, position: 'fixed' } }); + } + }; + + getGeoFormat = () => { + const { column } = this.props; + let data = column.data || {}; + return data.geo_format ? data.geo_format : DEFAULT_GEOLOCATION_FORMAT; + }; + + getLargeEditorState = () => { + return this.editor?.state?.isShowLargeEditor; + }; + + createEditor = () => { + const geoFormat = this.getGeoFormat(); + const { config, column, getCountryData, getLocationData } = this.props; + const props = { + column, + config, + value: this.value, + setValue: this.setValue, + onSubmit: this.onSubmit, + onCancel: this.onCancel, + }; + + switch (geoFormat) { + case GEOLOCATION_FORMAT.LNG_LAT: { + return ( this.editor = ref} />); + } + case GEOLOCATION_FORMAT.MAP_SELECTION: { + return ( this.editor = ref} />); + } + case GEOLOCATION_FORMAT.COUNTRY_REGION: { + return ( this.editor = ref} />); + } + case GEOLOCATION_FORMAT.PROVINCE: { + return ( this.editor = ref} />); + } + case GEOLOCATION_FORMAT.PROVINCE_CITY: { + return ( this.editor = ref} />); + } + case GEOLOCATION_FORMAT.PROVINCE_CITY_DISTRICT: { + return ( this.editor = ref} />); + } + default: { + return ( this.editor = ref} />); + } + } + }; + + render() { + const { editorPosition } = this.state; + return ( +
this.ref = ref} style={editorPosition} className="dtable-ui-dtable-ui-geolocation-editor"> + {this.createEditor()} +
+ ); + } +} + +PCGeolocationEditor.propTypes = { + isInModal: PropTypes.bool, + value: PropTypes.oneOfType([PropTypes.object, PropTypes.string]), + column: PropTypes.object, + onCommit: PropTypes.func, + onToggle: PropTypes.func, + onPressTab: PropTypes.func, +}; + +export default PCGeolocationEditor; diff --git a/src/GeolocationEditor/location-editor.js b/src/GeolocationEditor/pc-editor/location-editor.js similarity index 91% rename from src/GeolocationEditor/location-editor.js rename to src/GeolocationEditor/pc-editor/location-editor.js index f68f1196..8cf634ae 100644 --- a/src/GeolocationEditor/location-editor.js +++ b/src/GeolocationEditor/pc-editor/location-editor.js @@ -1,18 +1,19 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import { Button, Alert } from 'reactstrap'; -import DTableCustomFooter from '../DTableCustomFooter'; +import DTableCustomFooter from '../../DTableCustomFooter'; import GeolocationSelectorList from './selector-list'; import SelectorHeaderItem from './selector-header-item'; -import { KeyCodes } from '../constants'; -import Loading from '../Loading'; +import { KeyCodes } from '../../constants'; +import Loading from '../../Loading'; import parseGeolocation from './parse-geolocation'; -import { getLocale } from '../lang'; +import { getLocale } from '../../lang'; const propTypes = { - isShowAddressDetail: PropTypes.bool, + isShowDetails: PropTypes.bool, value: PropTypes.object, setValue: PropTypes.func, + getData: PropTypes.func, onCancel: PropTypes.func, onSubmit: PropTypes.func }; @@ -42,21 +43,8 @@ class LocationEditor extends Component { }; UNSAFE_componentWillMount() { - if (!window.app.location) { - this.getLocationData().then((data) => { - this.locations = data; - window.app.location = data; - const { selectedProvince, selectedCity, selectedCounty, selectedItem } = this.initLocationSelecting(this.value); - this.setState({ - isLoadingData: false, - selectedProvince, - selectedCity, - selectedCounty, - selectedItem - }); - }); - } else { - this.locations = window.app.location; + this.props.getData().then(data => { + this.locations = data; const { selectedProvince, selectedCity, selectedCounty, selectedItem } = this.initLocationSelecting(this.value); this.setState({ isLoadingData: false, @@ -65,7 +53,7 @@ class LocationEditor extends Component { selectedCounty, selectedItem }); - } + }); } initLocationSelecting = (value) => { @@ -96,20 +84,6 @@ class LocationEditor extends Component { return { selectedProvince, selectedCity, selectedCounty, selectedItem: 'county' }; }; - getLocationData = () => { - const { mediaUrl } = window.dtable; - - // get locations from server - return fetch(`${mediaUrl}geo-data/cn-location.json`).then((res) => { - return res.json(); - }).catch(() => { - // get locations from local - return fetch('./geo-data/cn-location.json').then(res => { - return res.json(); - }); - }); - }; - onKeyDown = (e) => { e.stopPropagation(); e.nativeEvent.stopImmediatePropagation(); @@ -292,7 +266,7 @@ class LocationEditor extends Component {
)}
- {this.props.isShowAddressDetail && + {this.props.isShowDetails &&
{getLocale('Detailed_address') + ':'} diff --git a/src/GeolocationEditor/map-editor/index.js b/src/GeolocationEditor/pc-editor/map-editor/index.js similarity index 88% rename from src/GeolocationEditor/map-editor/index.js rename to src/GeolocationEditor/pc-editor/map-editor/index.js index 4393ab12..0632e789 100644 --- a/src/GeolocationEditor/map-editor/index.js +++ b/src/GeolocationEditor/pc-editor/map-editor/index.js @@ -1,12 +1,12 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; -import toaster from '../../toaster'; -import Loading from '../../Loading'; -import { KeyCodes } from '../../constants'; -import { isValidPosition } from '../../utils/cell'; -import { DOMESTIC_MAP_TYPE, MAP_TYPES, getInitCenter, loadMapSource, getMapInfo, locateCurrentPosition, getMineMapUrl } from '../map-editor-utils'; +import toaster from '../../../toaster'; +import Loading from '../../../Loading'; +import { KeyCodes } from '../../../constants'; +import { isValidPosition } from '../../../utils/cell'; +import { DOMESTIC_MAP_TYPE, MAP_TYPES, getInitCenter, loadMapSource, getMapInfo, locateCurrentPosition, getMineMapUrl } from '../../map-editor-utils'; import LargeMapEditorDialog from './large-editor'; -import { getLocale } from '../../lang'; +import { getLocale } from '../../../lang'; const propTypes = { config: PropTypes.object, @@ -322,23 +322,23 @@ class MapEditor extends Component { if (!isShowLargeEditor) { return ( <> -
-
- - {getLocale('Address')} +
+
+ + {getLocale('Address')}
- +
-
+
{!this.readOnly && -
+
@@ -350,21 +350,21 @@ class MapEditor extends Component {
{(this.mapType && isLoading) && } {(!this.mapType) && ( -
+
{getLocale('The_map_plugin_is_not_properly_configured_contact_the_administrator')}
)} - {(!isLoading && this.mapType) &&
this.ref = ref} id="geolocation-map-container">
} + {(!isLoading && this.mapType) && (
this.ref = ref} id="geolocation-map-container">
)}
-
-
- +
+
+
-
-
+
+
-
+
diff --git a/src/GeolocationEditor/map-editor/large-editor/index.css b/src/GeolocationEditor/pc-editor/map-editor/large-editor/index.css similarity index 100% rename from src/GeolocationEditor/map-editor/large-editor/index.css rename to src/GeolocationEditor/pc-editor/map-editor/large-editor/index.css diff --git a/src/GeolocationEditor/map-editor/large-editor/index.js b/src/GeolocationEditor/pc-editor/map-editor/large-editor/index.js similarity index 89% rename from src/GeolocationEditor/map-editor/large-editor/index.js rename to src/GeolocationEditor/pc-editor/map-editor/large-editor/index.js index c21d0b66..f7bf7e98 100644 --- a/src/GeolocationEditor/map-editor/large-editor/index.js +++ b/src/GeolocationEditor/pc-editor/map-editor/large-editor/index.js @@ -1,12 +1,12 @@ import React from 'react'; import PropTypes from 'prop-types'; import { Modal } from 'reactstrap'; -import toaster from '../../../toaster'; -import Loading from '../../../Loading'; -import { KeyCodes } from '../../../constants'; -import { isValidPosition } from '../../../utils/cell'; -import { DOMESTIC_MAP_TYPE, MAP_TYPES, getInitCenter, loadMapSource, getMapInfo, locateCurrentPosition, getMineMapUrl } from '../../map-editor-utils'; -import { getLocale } from '../../../lang'; +import toaster from '../../../../toaster'; +import Loading from '../../../../Loading'; +import { KeyCodes } from '../../../../constants'; +import { isValidPosition } from '../../../../utils/cell'; +import { DOMESTIC_MAP_TYPE, MAP_TYPES, getInitCenter, loadMapSource, getMapInfo, locateCurrentPosition, getMineMapUrl } from '../../../map-editor-utils'; +import { getLocale } from '../../../../lang'; import './index.css'; @@ -329,19 +329,19 @@ class LargeMapEditorDialog extends React.Component { const { isLoading, inputValue } = this.state; return ( -
-
- - {getLocale('Address')} +
+
+ + {getLocale('Address')}
- +
-
+
{!this.readOnly &&
@@ -362,21 +362,21 @@ class LargeMapEditorDialog extends React.Component {
{(this.mapType && isLoading) && } {(!this.mapType) && ( -
+
{getLocale('The_map_plugin_is_not_properly_configured_contact_the_administrator')}
)} {(!isLoading && this.mapType) &&
this.ref = ref} id="geolocation-map-container-large">
}
-
-
- +
+
+
-
-
+
+
-
+
diff --git a/src/GeolocationEditor/map-selection-editor/index.js b/src/GeolocationEditor/pc-editor/map-selection-editor/index.js similarity index 92% rename from src/GeolocationEditor/map-selection-editor/index.js rename to src/GeolocationEditor/pc-editor/map-selection-editor/index.js index 6d80bf42..bb6422c9 100644 --- a/src/GeolocationEditor/map-selection-editor/index.js +++ b/src/GeolocationEditor/pc-editor/map-selection-editor/index.js @@ -1,13 +1,13 @@ import React, { Component, Fragment } from 'react'; import PropTypes from 'prop-types'; import { isEmptyObject } from 'dtable-utils'; -import toaster from '../../toaster'; -import Loading from '../../Loading'; -import { KeyCodes } from '../../constants'; -import { isValidPosition } from '../../utils/cell'; -import { getMapInfo, MAP_TYPES, loadMapSource, getInitCenter, locateCurrentPosition } from '../map-editor-utils'; +import toaster from '../../../toaster'; +import Loading from '../../../Loading'; +import { KeyCodes } from '../../../constants'; +import { isValidPosition } from '../../../utils/cell'; +import { getMapInfo, MAP_TYPES, loadMapSource, getInitCenter, locateCurrentPosition } from '../../map-editor-utils'; import LargeMapSelectionEditorDialog from './large-editor'; -import { getLocale } from '../../lang'; +import { getLocale } from '../../../lang'; const propTypes = { column: PropTypes.object, @@ -352,7 +352,7 @@ class MapSelectionEditor extends Component {
{!this.readOnly && -
+
{inputValue && } - +
@@ -373,21 +373,21 @@ class MapSelectionEditor extends Component {
{(this.mapType && isLoading) && } {(!this.mapType) && ( -
+
{getLocale('The_map_plugin_is_not_properly_configured_contact_the_administrator')}
)} - {(!isLoading && this.mapType) &&
this.ref = ref} id="geolocation-map-selection-container">
} + {(!isLoading && this.mapType) &&
this.ref = ref} id="geolocation-map-selection-container">
}
-
-
- +
+
+
-
-
+
+
-
+
diff --git a/src/GeolocationEditor/map-selection-editor/large-editor/index.css b/src/GeolocationEditor/pc-editor/map-selection-editor/large-editor/index.css similarity index 100% rename from src/GeolocationEditor/map-selection-editor/large-editor/index.css rename to src/GeolocationEditor/pc-editor/map-selection-editor/large-editor/index.css diff --git a/src/GeolocationEditor/map-selection-editor/large-editor/index.js b/src/GeolocationEditor/pc-editor/map-selection-editor/large-editor/index.js similarity index 97% rename from src/GeolocationEditor/map-selection-editor/large-editor/index.js rename to src/GeolocationEditor/pc-editor/map-selection-editor/large-editor/index.js index 6ddecc42..92a58701 100644 --- a/src/GeolocationEditor/map-selection-editor/large-editor/index.js +++ b/src/GeolocationEditor/pc-editor/map-selection-editor/large-editor/index.js @@ -2,12 +2,12 @@ import React from 'react'; import PropTypes from 'prop-types'; import { Modal } from 'reactstrap'; import { isEmptyObject } from 'dtable-utils'; -import toaster from '../../../toaster'; -import Loading from '../../../Loading'; -import { KeyCodes } from '../../../constants'; -import { isValidPosition } from '../../../utils/cell'; -import { MAP_TYPES, getInitCenter, loadMapSource, getMapInfo, locateCurrentPosition } from '../../map-editor-utils'; -import { getLocale } from '../../../lang'; +import toaster from '../../../../toaster'; +import Loading from '../../../../Loading'; +import { KeyCodes } from '../../../../constants'; +import { isValidPosition } from '../../../../utils/cell'; +import { MAP_TYPES, getInitCenter, loadMapSource, getMapInfo, locateCurrentPosition } from '../../../map-editor-utils'; +import { getLocale } from '../../../../lang'; import './index.css'; @@ -362,7 +362,7 @@ class LargeMapSelectionEditorDialog extends React.Component {
{(this.mapType && isLoading) && } {(!this.mapType) && ( -
+
{getLocale('The_map_plugin_is_not_properly_configured_contact_the_administrator')}
)} diff --git a/src/GeolocationEditor/parse-geolocation.js b/src/GeolocationEditor/pc-editor/parse-geolocation.js similarity index 100% rename from src/GeolocationEditor/parse-geolocation.js rename to src/GeolocationEditor/pc-editor/parse-geolocation.js diff --git a/src/GeolocationEditor/province-city-editor.js b/src/GeolocationEditor/pc-editor/province-city-editor.js similarity index 89% rename from src/GeolocationEditor/province-city-editor.js rename to src/GeolocationEditor/pc-editor/province-city-editor.js index e268f05d..3b5f9318 100644 --- a/src/GeolocationEditor/province-city-editor.js +++ b/src/GeolocationEditor/pc-editor/province-city-editor.js @@ -1,13 +1,13 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import { Button, Alert } from 'reactstrap'; -import DTableCustomFooter from '../DTableCustomFooter'; +import DTableCustomFooter from '../../DTableCustomFooter'; import GeolocationSelectorList from './selector-list'; import SelectorHeaderItem from './selector-header-item'; -import Loading from '../Loading'; +import Loading from '../../Loading'; import parseGeolocation from './parse-geolocation'; -import { KeyCodes } from '../constants'; -import { getLocale } from '../lang'; +import { KeyCodes } from '../../constants'; +import { getLocale } from '../../lang'; const propTypes = { value: PropTypes.object, @@ -40,20 +40,8 @@ class ProvinceCityEditor extends Component { }; componentDidMount() { - if (!window.app.location) { - this.getLocationData().then((data) => { - this.locations = data; - window.app.location = data; - const { selectedProvince, selectedCity, selectedItem } = this.initLocationSelecting(this.value); - this.setState({ - isLoadingData: false, - selectedProvince, - selectedCity, - selectedItem - }); - }); - } else { - this.locations = window.app.location; + this.props.getData().then(data => { + this.locations = data; const { selectedProvince, selectedCity, selectedItem } = this.initLocationSelecting(this.value); this.setState({ isLoadingData: false, @@ -61,7 +49,7 @@ class ProvinceCityEditor extends Component { selectedCity, selectedItem }); - } + }); } initLocationSelecting = (value) => { @@ -84,20 +72,6 @@ class ProvinceCityEditor extends Component { return { selectedProvince, selectedCity, selectedItem: 'city' }; }; - getLocationData = () => { - const { mediaUrl } = window.dtable; - - // get locations from server - return fetch(`${mediaUrl}geo-data/cn-location.json`).then((res) => { - return res.json(); - }).catch(() => { - // get locations from local - return fetch('./geo-data/cn-location.json').then(res => { - return res.json(); - }); - }); - }; - onToggleSelector = () => { this.setState({ isShowSelector: !this.state.isShowSelector, diff --git a/src/GeolocationEditor/province-editor.js b/src/GeolocationEditor/pc-editor/province-editor.js similarity index 86% rename from src/GeolocationEditor/province-editor.js rename to src/GeolocationEditor/pc-editor/province-editor.js index 5d8e26a4..e86a3822 100644 --- a/src/GeolocationEditor/province-editor.js +++ b/src/GeolocationEditor/pc-editor/province-editor.js @@ -1,15 +1,16 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; -import DtableSearchInput from '../DTableSearchInput'; -import { KeyCodes } from '../constants'; -import Loading from '../Loading'; -import { getLocale } from '../lang'; +import DtableSearchInput from '../../DTableSearchInput'; +import { KeyCodes } from '../../constants'; +import Loading from '../../Loading'; +import { getLocale } from '../../lang'; const propTypes = { value: PropTypes.object, setValue: PropTypes.func, - onSubmit: PropTypes.func, column: PropTypes.object, + onSubmit: PropTypes.func, + getData: PropTypes.func, onPressTab: PropTypes.func }; @@ -32,22 +33,13 @@ class ProvinceEditor extends Component { } componentDidMount() { - if (!window.app.location) { - this.getLocationData().then((data) => { - this.locations = data; - window.app.location = data; - this.filteredProvince = this.locations.children; - this.setState({ - isLoadingData: false, - }); - }); - } else { - this.locations = window.app.location; + this.props.getData().then(data => { + this.locations = data; this.filteredProvince = this.locations.children; this.setState({ isLoadingData: false, }); - } + }); document.addEventListener('keydown', this.onHotKey, true); } @@ -59,20 +51,6 @@ class ProvinceEditor extends Component { } } - getLocationData = () => { - // mediaUrl - const { mediaUrl } = window.dtable; - // get locations from server - return fetch(`${mediaUrl}geo-data/cn-location.json`).then((res) => { - return res.json(); - }).catch(() => { - // get locations from local - return fetch('./geo-data/cn-location.json').then(res => { - return res.json(); - }); - }); - }; - onHotKey = (e) => { if (e.keyCode === KeyCodes.Enter) { this.onEnter(e); diff --git a/src/GeolocationEditor/selector-header-item.js b/src/GeolocationEditor/pc-editor/selector-header-item.js similarity index 96% rename from src/GeolocationEditor/selector-header-item.js rename to src/GeolocationEditor/pc-editor/selector-header-item.js index 5e46c717..1bb304d4 100644 --- a/src/GeolocationEditor/selector-header-item.js +++ b/src/GeolocationEditor/pc-editor/selector-header-item.js @@ -1,6 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { getLocale } from '../lang'; +import { getLocale } from '../../lang'; const SelectorHeaderItemPropTypes = { type: PropTypes.string, diff --git a/src/GeolocationEditor/selector-list.js b/src/GeolocationEditor/pc-editor/selector-list.js similarity index 100% rename from src/GeolocationEditor/selector-list.js rename to src/GeolocationEditor/pc-editor/selector-list.js diff --git a/src/ImageEditor/images-previewer/image-preview/index.js b/src/ImageEditor/images-previewer/image-preview/index.js index 09f25c9c..197fea7d 100644 --- a/src/ImageEditor/images-previewer/image-preview/index.js +++ b/src/ImageEditor/images-previewer/image-preview/index.js @@ -3,7 +3,7 @@ import PropTypes from 'prop-types'; import { Tooltip } from 'reactstrap'; import DeleteTip from '../../../DeleteTip'; import { getImageThumbnailUrl, checkImgExists, checkSVGImage, getFileName } from '../../../utils/url'; -import { FILE_EDITOR_STATUS } from '../../../constants'; +import { FILE_EDITOR_STATUS, isMobile } from '../../../constants'; import { getLocale } from '../../../lang'; import './index.css'; @@ -18,7 +18,7 @@ class ImagePreviewer extends React.Component { }; this.canDownLoad = props.canDownLoad; const offsetWidth = document.body.offsetWidth; - this.containerSize = offsetWidth < 767.8 ? ((offsetWidth - 55) / 2) + 'px' : ((offsetWidth - 80) / 3) + 'px'; + this.containerSize = offsetWidth < 768 ? ((offsetWidth - 55) / 2) + 'px' : ((offsetWidth - 80) / 3) + 'px'; this.position = {}; const { mediaUrl } = props.config || {}; this.imageLoadingFailedUrl = `${mediaUrl}img/image-loading-failed.png`; @@ -100,10 +100,11 @@ class ImagePreviewer extends React.Component { render() { const { enterImageItemIndex, itemIndex, imageItemUrl } = this.props; + return (
- {enterImageItemIndex === itemIndex && + {!isMobile && enterImageItemIndex === itemIndex && -
+
+
{value.length > 0 && value.map((imageItemUrl, index) => { return ( diff --git a/src/ImageEditor/index.css b/src/ImageEditor/index.css index 4a7d5305..1913feb4 100644 --- a/src/ImageEditor/index.css +++ b/src/ImageEditor/index.css @@ -1,24 +1,25 @@ -.dtable-ui-image-editor-dialog { - max-width: 620px; -} - -.dtable-ui-image-editor-modal { - width: 620px; - max-height: 620px; +.dtable-ui-file-open-external-browser { + flex: 1; + display: flex; + flex-direction: column; + align-items: center; + min-height: 0; overflow: hidden; } -.dtable-ui-image-editor-container { - width: 100%; - background-color: #fff; +.dtable-ui-file-open-external-browser .seatable-tip-danger { + margin: 16px; + text-align: center; } -.dtable-ui-image-addition-container { +.dtable-ui-file-open-external-browser .image-container { + flex: 1; display: flex; - flex-direction: row; + justify-content: center; + align-items: flex-start; + margin: 0 10px; } -.dtable-ui-image-editor-dialog .dtable-icon-return { - color: #666666; - font-size: 14px; -} +.dtable-ui-file-open-external-browser img { + width: 75%; +} \ No newline at end of file diff --git a/src/ImageEditor/index.js b/src/ImageEditor/index.js index 27ba0167..cdd9bfd6 100644 --- a/src/ImageEditor/index.js +++ b/src/ImageEditor/index.js @@ -1,200 +1,29 @@ -import React from 'react'; +import React, { forwardRef } from 'react'; import PropTypes from 'prop-types'; -import { Modal, ModalBody } from 'reactstrap'; -import ImagesPreviewer from './images-previewer'; -import AdditionPreviewer from './addition-previewer'; -import { FILE_EDITOR_STATUS } from '../constants'; -import DTableModalHeader from '../DTableModalHeader'; -import { getLocale } from '../lang'; -import { IMAGES_FOLDER } from './constants'; +import MediaQuery from 'react-responsive'; +import PCImageEditor from './pc-editor'; +import MBImageEditor from './mb-editor'; import './index.css'; -class ImageEditor extends React.Component { - - constructor(props) { - super(props); - this.state = { - value: props.value || [], - editorView: this.getEditorView(), - isOpen: true, - isShowFileChooser: false, - uploadLocalImageValue: [], - imageLinkValue: [], - isUpdated: false, - }; - } - - getValue = () => { - return this.state.value; - }; - - getEditorView = () => { - const { isInModal, value } = this.props; - return !value || value.length === 0 || isInModal ? FILE_EDITOR_STATUS.ADDITION : FILE_EDITOR_STATUS.PREVIEWER; - }; - - deleteImage = (index, type = null) => { - let uploadLocalImageValue = this.state.uploadLocalImageValue.slice(0); - let value = this.state.value.slice(0); - if (this.state.editorView === FILE_EDITOR_STATUS.PREVIEWER) { - value.splice(index, 1); - } else { - if (type === 'localPicture') { - uploadLocalImageValue.splice(index, 1); - } - } - this.setState({ - uploadLocalImageValue: uploadLocalImageValue, - isUpdated: true, - value: value - }); - }; - - resetAdditionImage = () => { - this.setState({ - uploadLocalImageValue: [], - imageLinkValue: [] - }); - }; - - toggle = (e) => { - e.stopPropagation(); - if (this.state.isOpen && this.state.isUpdated) { - if (this.state.editorView === FILE_EDITOR_STATUS.ADDITION) { - let { value, uploadLocalImageValue } = this.state; - let newValue = value.concat(uploadLocalImageValue); - this.setState({ - value: newValue - }, () => { - this.props.isInModal ? this.props.onCommit(this.getValue()) : this.props.onCommit(); - }); - return; - } - this.props.isInModal ? this.props.onCommit(this.getValue()) : this.props.onCommit(); - } - if (this.props.isInModal) { - this.props.onToggle(); - } - const nextIsOpen = !this.state.isOpen; - this.setState({ isOpen: nextIsOpen }, () => { - if (!nextIsOpen) { - this.props.onCommitCancel && this.props.onCommitCancel(); - } - }); - }; - - closeEditor = () => { - this.setState({ isOpen: false }); - }; - - togglePreviewer = (type) => { - this.setState({ - editorView: type, - }); - }; - - fileUploadCompleted = (fileMessage) => { - let uploadLocalImageValue = this.state.uploadLocalImageValue.slice(0); - const imageUrl = fileMessage.url; - uploadLocalImageValue.push(imageUrl); - this.setState({ - uploadLocalImageValue: uploadLocalImageValue, - isUpdated: true - }); - }; - - addUploadedFile = (fileMessageList) => { - let uploadLocalImageValue = this.state.uploadLocalImageValue.slice(0); - // eslint-disable-next-line no-unused-vars - for (let fileMessage of fileMessageList) { - uploadLocalImageValue.push(fileMessage.url); - } - this.setState({ - uploadLocalImageValue: uploadLocalImageValue, - isUpdated: true - }); - }; - - renderHeader = () => { - let { editorView } = this.state; - if (this.props.isInModal) { - return ({getLocale('Add_images')}); - } - if (editorView === FILE_EDITOR_STATUS.PREVIEWER) { - return ({getLocale('All_images')}); - } - return ( -
- - {getLocale('Add_images')} -
- ); - }; - - showImageListPreviewer = () => { - let { value, uploadLocalImageValue, imageLinkValue } = this.state; - let newValue = value.concat(uploadLocalImageValue, imageLinkValue); - this.setState({ - value: newValue - }); - this.togglePreviewer(FILE_EDITOR_STATUS.PREVIEWER); - }; - - saveImageLink = (imageUrl) => { - this.setState({ - imageLinkValue: imageUrl, - isUpdated: true, - }); - this.showImageListPreviewer(); - }; - - uploadFile = (file, callback) => { - return this.props.uploadFile(file, IMAGES_FOLDER, callback); - }; - - render() { - return ( - - - {this.renderHeader()} - - -
- {this.state.editorView === FILE_EDITOR_STATUS.PREVIEWER && - - } - {this.state.editorView === FILE_EDITOR_STATUS.ADDITION && - - } -
-
-
- ); - } -} +const ImageEditor = forwardRef(({ isMobile, ...props }, ref) => { + if (isMobile === false) return (); + if (isMobile === true) return (); + + return ( + <> + + + + + + + + ); +}); ImageEditor.propTypes = { - value: PropTypes.array, - isInModal: PropTypes.bool, - openEditorMode: PropTypes.string, - onToggle: PropTypes.func, - onCommit: PropTypes.func, + isMobile: PropTypes.bool, }; export default ImageEditor; diff --git a/src/ImageEditor/mb-editor/index.css b/src/ImageEditor/mb-editor/index.css new file mode 100644 index 00000000..090b4db7 --- /dev/null +++ b/src/ImageEditor/mb-editor/index.css @@ -0,0 +1,29 @@ +.dtable-ui-mobile-image-editor .dtable-ui-image-previewer-container { + width: 100%; + margin-top: 20px; +} + +.dtable-ui-mobile-image-editor .dtable-ui-image-previewer-container .dtable-ui-image-previewer-wrapper { + background-color: #fff; + width: 100%; + border-top: 1px solid rgb(233, 233, 233); + border-bottom: 1px solid rgb(233, 233, 233); + height: fit-content; +} + +.dtable-ui-mobile-image-editor .dtable-ui-image-previewer-container .dtable-ui-image-previewer-box { + margin-right: 0; +} + +.dtable-ui-mobile-image-editor .dtable-ui-image-previewer-container .dtable-ui-image-previewer-box:nth-child(even) { + margin-left: 15px; +} + +.dtable-ui-mobile-image-editor .dtable-ui-image-previewer-container .add-item-btn { + margin-top: 20px; + border-bottom: 1px solid #dedede; +} + +.dtable-ui-mobile-image-editor .dtable-ui-image-previewer-container .add-item-btn:hover { + background-color: #fff; +} diff --git a/src/ImageEditor/mb-editor/index.js b/src/ImageEditor/mb-editor/index.js new file mode 100644 index 00000000..96ef937c --- /dev/null +++ b/src/ImageEditor/mb-editor/index.js @@ -0,0 +1,139 @@ +import React, { useCallback, useEffect, useRef, useState } from 'react'; +import PropTypes from 'prop-types'; +import { ActivityIndicator } from 'antd-mobile'; +import MobileFullScreenPage from '../../MobileFullScreenPage'; +import ImagesPreviewer from '../images-previewer'; +import MobileUpload from '../../MobileUpload'; +import toaster from '../../toaster'; +import { isIPhone, isQQBuiltInBrowser, getErrorMsg } from '../../utils/utils'; +import { getLocale } from '../../lang'; + +import './index.css'; + +const { Header, Body } = MobileFullScreenPage; + +const MBImageEditor = ({ value: oldValue, column, config, onToggle, onCommit, uploadFile }) => { + const [value, setValue] = useState(oldValue || []); + const [isShowEditor, setShowEditor] = useState(false); + const [uploadLocalImages, setUploadLocalImages] = useState([]); + const [isUploading, setUploading] = useState(false); + + const uploadingFilesCount = useRef(0); + const uploadedFilesCount = useRef(0); + + const { mediaUrl } = config || {}; + + const resetAdditionImage = useCallback(() => { + setUploadLocalImages([]); + }, []); + + const openEditor = useCallback(() => { + setShowEditor(true); + }, []); + + const closeEditor = useCallback(() => { + setShowEditor(false); + }, []); + + const deleteImage = useCallback((index) => { + const newValue = value.slice(0); + newValue.splice(index, 1); + setValue(newValue); + onCommit(newValue); + }, [value, onCommit]); + + const onChange = useCallback(() => { + const newValue = value.concat(uploadLocalImages); + setValue(newValue); + onCommit(newValue); + setShowEditor(false); + }, [value, uploadLocalImages, onCommit]); + + const uploadImage = useCallback((file) => { + uploadFile(file).then(res => { + const fileInfo = res; + const newUploadLocalImages = uploadLocalImages.slice(0); + newUploadLocalImages.push(fileInfo.url); + setUploadLocalImages(newUploadLocalImages); + uploadedFilesCount.current = uploadedFilesCount.current + 1; + }).catch(error => { + let errMsg = getErrorMsg(error, true); + if (!error.response || error.response.status !== 403) { + toaster.danger(getLocale(errMsg)); + } + uploadedFilesCount.current = uploadedFilesCount.current + 1; + }); + }, [uploadFile, uploadLocalImages]); + + const onFilesChange = useCallback((files = []) => { + setUploading(true); + uploadingFilesCount.current = files.length; + uploadedFilesCount.current = 0; + for (let i = 0; i < uploadingFilesCount.current; i++) { + const file = files[i]; + const reader = new FileReader(); + reader.readAsDataURL(file); + reader.addEventListener('load', () => { + uploadImage(file); + }, false); + reader.addEventListener('error', () => { + const message = getLocale('Image_loading_failed'); + toaster.warning(message); + uploadedFilesCount.current = uploadedFilesCount.current + 1; + }, false); + } + }, [uploadImage]); + + useEffect(() => { + if (uploadingFilesCount.current === uploadedFilesCount.current && uploadedFilesCount.current !== 0) { + uploadedFilesCount.current = 0; + uploadingFilesCount.current = 0; + onChange(); + setUploading(false); + } + }, [uploadingFilesCount, uploadedFilesCount, onChange]); + + return ( + +
+ + <>{column.name} +
+ + {(isIPhone() && isQQBuiltInBrowser()) ? ( +
+
{'不支持 QQ 内置浏览器上传图片或者文件'}
+
+ Open browser tip +
+
+ ) : ( + <> + + {isShowEditor && ( + + )} + {isUploading && ()} + + )} + +
+ ); +}; + +MBImageEditor.propTypes = { + value: PropTypes.array, + column: PropTypes.object, + config: PropTypes.object, + onToggle: PropTypes.func, + onCommit: PropTypes.func, + uploadFile: PropTypes.func, +}; + +export default MBImageEditor; diff --git a/src/ImageEditor/addition-previewer/image-link/index.css b/src/ImageEditor/pc-editor/addition-previewer/image-link/index.css similarity index 100% rename from src/ImageEditor/addition-previewer/image-link/index.css rename to src/ImageEditor/pc-editor/addition-previewer/image-link/index.css diff --git a/src/ImageEditor/addition-previewer/image-link/index.js b/src/ImageEditor/pc-editor/addition-previewer/image-link/index.js similarity index 97% rename from src/ImageEditor/addition-previewer/image-link/index.js rename to src/ImageEditor/pc-editor/addition-previewer/image-link/index.js index 966c9ddd..ab5600b8 100644 --- a/src/ImageEditor/addition-previewer/image-link/index.js +++ b/src/ImageEditor/pc-editor/addition-previewer/image-link/index.js @@ -1,7 +1,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import { Input } from 'reactstrap'; -import { getLocale } from '../../../lang'; +import { getLocale } from '../../../../lang'; import './index.css'; diff --git a/src/ImageEditor/addition-previewer/index.css b/src/ImageEditor/pc-editor/addition-previewer/index.css similarity index 100% rename from src/ImageEditor/addition-previewer/index.css rename to src/ImageEditor/pc-editor/addition-previewer/index.css diff --git a/src/ImageEditor/addition-previewer/index.js b/src/ImageEditor/pc-editor/addition-previewer/index.js similarity index 98% rename from src/ImageEditor/addition-previewer/index.js rename to src/ImageEditor/pc-editor/addition-previewer/index.js index 82a1ff00..739bde2a 100644 --- a/src/ImageEditor/addition-previewer/index.js +++ b/src/ImageEditor/pc-editor/addition-previewer/index.js @@ -2,7 +2,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import LocalImageAddition from './local-image-addition'; import ImageLink from './image-link'; -import { getLocale } from '../../lang'; +import { getLocale } from '../../../lang'; import './index.css'; diff --git a/src/ImageEditor/addition-previewer/local-image-addition/index.css b/src/ImageEditor/pc-editor/addition-previewer/local-image-addition/index.css similarity index 100% rename from src/ImageEditor/addition-previewer/local-image-addition/index.css rename to src/ImageEditor/pc-editor/addition-previewer/local-image-addition/index.css diff --git a/src/ImageEditor/addition-previewer/local-image-addition/index.js b/src/ImageEditor/pc-editor/addition-previewer/local-image-addition/index.js similarity index 97% rename from src/ImageEditor/addition-previewer/local-image-addition/index.js rename to src/ImageEditor/pc-editor/addition-previewer/local-image-addition/index.js index eacec30a..92f0a122 100644 --- a/src/ImageEditor/addition-previewer/local-image-addition/index.js +++ b/src/ImageEditor/pc-editor/addition-previewer/local-image-addition/index.js @@ -1,10 +1,10 @@ import React, { Fragment } from 'react'; import PropTypes from 'prop-types'; import { Input } from 'reactstrap'; -import Progress from '../../../UploadProgress'; -import { getImageThumbnailUrl } from '../../../utils/url'; -import FileUploader from '../../../FileUploader'; -import { getLocale } from '../../../lang'; +import Progress from '../../../../UploadProgress'; +import { getImageThumbnailUrl } from '../../../../utils/url'; +import FileUploader from '../../../../FileUploader'; +import { getLocale } from '../../../../lang'; import './index.css'; diff --git a/src/ImageEditor/pc-editor/images-previewer/index.js b/src/ImageEditor/pc-editor/images-previewer/index.js new file mode 100644 index 00000000..e8c67c10 --- /dev/null +++ b/src/ImageEditor/pc-editor/images-previewer/index.js @@ -0,0 +1,154 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import ImagePreviewerLightbox from '../../ImagePreviewerLightbox'; +import ImagePreviewer from './image-preview'; +import DTableCommonAddTool from '../../DTableCommonAddTool'; +import { downloadFile } from '../../utils/utils'; +import { FILE_EDITOR_STATUS, isMobile } from '../../constants'; +import { getLocale } from '../../lang'; + +import './index.css'; + +class ImagesPreviewer extends React.Component { + + constructor(props) { + super(props); + this.state = { + isShowLargeImage: false, + largeImageIndex: '', + enterImageItemIndex: -1 + }; + } + + componentDidMount() { + if (isMobile) { + window.history.pushState(null, null, '#'); + window.addEventListener('popstate', this.handleHistoryBack, false); + } + } + + componentWillUnmount() { + if (isMobile) { + window.removeEventListener('popstate', this.handleHistoryBack, false); + } + } + + handleHistoryBack = (e) => { + if (this.state.isShowLargeImage) { + this.hideLargeImage(); + } else if (this.props.closeEditor) { + this.props.closeEditor(); + } + }; + + showLargeImage = (itemUrl) => { + let { value } = this.props; + this.setState({ + isShowLargeImage: true, + largeImageIndex: value.indexOf(itemUrl) + }); + }; + + hideLargeImage = () => { + this.setState({ + isShowLargeImage: false, + largeImageIndex: '' + }); + }; + + moveNext = () => { + let images = this.props.value; + this.setState(prevState => ({ + largeImageIndex: (prevState.largeImageIndex + 1) % images.length, + })); + }; + + movePrev = () => { + let images = this.props.value; + this.setState(prevState => ({ + largeImageIndex: (prevState.largeImageIndex + images.length - 1) % images.length, + })); + }; + + togglePreviewer = () => { + this.props.togglePreviewer(FILE_EDITOR_STATUS.ADDITION); + this.props.resetAdditionImage(); + }; + + deleteImage = (index, type) => { + this.props.deleteImage(index, type); + const { value } = this.props; + if (index > value.length - 2) { + if (value.length - 2 < 0) { + this.hideLargeImage(); + } else { + this.setState({ largeImageIndex: 0 }); + } + } + }; + + downloadImage = (imageItemUrl) => { + let rotateIndex = imageItemUrl.indexOf('?a='); + if (rotateIndex > -1) { + imageItemUrl = imageItemUrl.slice(0, rotateIndex); + } + let imageUrlSuffix = imageItemUrl.indexOf('?dl=1'); + let downloadUrl = imageUrlSuffix !== -1 ? imageItemUrl : imageItemUrl + '?dl=1'; + downloadFile(downloadUrl); + }; + + setImageItemIndex = (index) => { + this.setState({ enterImageItemIndex: index }); + }; + + render() { + let { value } = this.props; + return ( +
+
+
+ {value.length > 0 && value.map((imageItemUrl, index) => { + return ( + + ); + })} +
+
+ {this.state.isShowLargeImage && ( + + )} + +
+ ); + } +} + +ImagesPreviewer.propTypes = { + value: PropTypes.array, + togglePreviewer: PropTypes.func, + deleteImage: PropTypes.func, + resetAdditionImage: PropTypes.func, + onRotateImage: PropTypes.func, + closeEditor: PropTypes.func, +}; + +export default ImagesPreviewer; diff --git a/src/ImageEditor/pc-editor/index.css b/src/ImageEditor/pc-editor/index.css new file mode 100644 index 00000000..4a7d5305 --- /dev/null +++ b/src/ImageEditor/pc-editor/index.css @@ -0,0 +1,24 @@ +.dtable-ui-image-editor-dialog { + max-width: 620px; +} + +.dtable-ui-image-editor-modal { + width: 620px; + max-height: 620px; + overflow: hidden; +} + +.dtable-ui-image-editor-container { + width: 100%; + background-color: #fff; +} + +.dtable-ui-image-addition-container { + display: flex; + flex-direction: row; +} + +.dtable-ui-image-editor-dialog .dtable-icon-return { + color: #666666; + font-size: 14px; +} diff --git a/src/ImageEditor/pc-editor/index.js b/src/ImageEditor/pc-editor/index.js new file mode 100644 index 00000000..3d39e41c --- /dev/null +++ b/src/ImageEditor/pc-editor/index.js @@ -0,0 +1,200 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { Modal, ModalBody } from 'reactstrap'; +import ImagesPreviewer from '../images-previewer'; +import AdditionPreviewer from './addition-previewer'; +import { FILE_EDITOR_STATUS } from '../../constants'; +import DTableModalHeader from '../../DTableModalHeader'; +import { getLocale } from '../../lang'; +import { IMAGES_FOLDER } from '../constants'; + +import './index.css'; + +class PCImageEditor extends React.Component { + + constructor(props) { + super(props); + this.state = { + value: props.value || [], + editorView: this.getEditorView(), + isOpen: true, + isShowFileChooser: false, + uploadLocalImageValue: [], + imageLinkValue: [], + isUpdated: false, + }; + } + + getValue = () => { + return this.state.value; + }; + + getEditorView = () => { + const { isInModal, value } = this.props; + return !value || value.length === 0 || isInModal ? FILE_EDITOR_STATUS.ADDITION : FILE_EDITOR_STATUS.PREVIEWER; + }; + + deleteImage = (index, type = null) => { + let uploadLocalImageValue = this.state.uploadLocalImageValue.slice(0); + let value = this.state.value.slice(0); + if (this.state.editorView === FILE_EDITOR_STATUS.PREVIEWER) { + value.splice(index, 1); + } else { + if (type === 'localPicture') { + uploadLocalImageValue.splice(index, 1); + } + } + this.setState({ + uploadLocalImageValue: uploadLocalImageValue, + isUpdated: true, + value: value + }); + }; + + resetAdditionImage = () => { + this.setState({ + uploadLocalImageValue: [], + imageLinkValue: [] + }); + }; + + toggle = (e) => { + e.stopPropagation(); + if (this.state.isOpen && this.state.isUpdated) { + if (this.state.editorView === FILE_EDITOR_STATUS.ADDITION) { + let { value, uploadLocalImageValue } = this.state; + let newValue = value.concat(uploadLocalImageValue); + this.setState({ + value: newValue + }, () => { + this.props.isInModal ? this.props.onCommit(this.getValue()) : this.props.onCommit(); + }); + return; + } + this.props.isInModal ? this.props.onCommit(this.getValue()) : this.props.onCommit(); + } + if (this.props.isInModal) { + this.props.onToggle(); + } + const nextIsOpen = !this.state.isOpen; + this.setState({ isOpen: nextIsOpen }, () => { + if (!nextIsOpen) { + this.props.onCommitCancel && this.props.onCommitCancel(); + } + }); + }; + + closeEditor = () => { + this.setState({ isOpen: false }); + }; + + togglePreviewer = (type) => { + this.setState({ + editorView: type, + }); + }; + + fileUploadCompleted = (fileMessage) => { + let uploadLocalImageValue = this.state.uploadLocalImageValue.slice(0); + const imageUrl = fileMessage.url; + uploadLocalImageValue.push(imageUrl); + this.setState({ + uploadLocalImageValue: uploadLocalImageValue, + isUpdated: true + }); + }; + + addUploadedFile = (fileMessageList) => { + let uploadLocalImageValue = this.state.uploadLocalImageValue.slice(0); + // eslint-disable-next-line no-unused-vars + for (let fileMessage of fileMessageList) { + uploadLocalImageValue.push(fileMessage.url); + } + this.setState({ + uploadLocalImageValue: uploadLocalImageValue, + isUpdated: true + }); + }; + + renderHeader = () => { + let { editorView } = this.state; + if (this.props.isInModal) { + return ({getLocale('Add_images')}); + } + if (editorView === FILE_EDITOR_STATUS.PREVIEWER) { + return ({getLocale('All_images')}); + } + return ( +
+ + {getLocale('Add_images')} +
+ ); + }; + + showImageListPreviewer = () => { + let { value, uploadLocalImageValue, imageLinkValue } = this.state; + let newValue = value.concat(uploadLocalImageValue, imageLinkValue); + this.setState({ + value: newValue + }); + this.togglePreviewer(FILE_EDITOR_STATUS.PREVIEWER); + }; + + saveImageLink = (imageUrl) => { + this.setState({ + imageLinkValue: imageUrl, + isUpdated: true, + }); + this.showImageListPreviewer(); + }; + + uploadFile = (file, callback) => { + return this.props.uploadFile(file, IMAGES_FOLDER, callback); + }; + + render() { + return ( + + + {this.renderHeader()} + + +
+ {this.state.editorView === FILE_EDITOR_STATUS.PREVIEWER && + + } + {this.state.editorView === FILE_EDITOR_STATUS.ADDITION && + + } +
+
+
+ ); + } +} + +PCImageEditor.propTypes = { + value: PropTypes.array, + isInModal: PropTypes.bool, + openEditorMode: PropTypes.string, + onToggle: PropTypes.func, + onCommit: PropTypes.func, +}; + +export default PCImageEditor; diff --git a/src/ImageThumbnail/index.js b/src/ImageThumbnail/index.js index b58d27a4..e50fb080 100644 --- a/src/ImageThumbnail/index.js +++ b/src/ImageThumbnail/index.js @@ -5,6 +5,7 @@ import classnames from 'classnames'; import DeleteTip from '../DeleteTip'; import { getImageThumbnailUrl, checkImgExists, checkSVGImage, getFileName } from '../utils/url'; import { getLocale } from '../lang'; +import { isMobile } from '../constants'; import './index.css'; @@ -102,7 +103,7 @@ class ImageThumbnail extends React.Component { deleteTip={this.props.deleteTip || getLocale('Are_you_sure_you_want_to_delete_this_image')} /> } - {this.ref && ( + {!isMobile && this.ref && ( - + { + return ( +
+ {children} +
+ ); +}; + +Body.propTypes = { + classNamePrefix: PropTypes.string, + className: PropTypes.string, + children: PropTypes.any, +}; + +export default Body; diff --git a/src/MobileFullScreenPage/header/index.css b/src/MobileFullScreenPage/header/index.css new file mode 100644 index 00000000..bd6e05cb --- /dev/null +++ b/src/MobileFullScreenPage/header/index.css @@ -0,0 +1,39 @@ +.dtable-ui-mobile-full-screen-page-header { + width: 100%; + height: 51px; + background-color: #fff; + border-bottom: 1px solid #e9e9e9; + padding: 0; + color: #666; + font-size: 14px; + display: flex; + justify-content: space-between; + flex-shrink: 0; +} + +.dtable-ui-mobile-full-screen-page-header .dtable-ui-mobile-full-screen-page-header-btn { + line-height: 24px; + min-width: 75px; + padding: 13px 16px; +} + +.dtable-ui-mobile-full-screen-page-header .dtable-ui-mobile-full-screen-page-header-btn:hover { + cursor: pointer; +} + +.dtable-ui-mobile-full-screen-page-header .dtable-ui-mobile-full-screen-page-header-title { + color: #212529; + line-height: 50px; + margin-bottom: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.dtable-ui-mobile-full-screen-page-header .dtable-ui-mobile-full-screen-page-header-btn:first-child { + text-align: left; +} + +.dtable-ui-mobile-full-screen-page-header .dtable-ui-mobile-full-screen-page-header-btn:last-child { + text-align: right; +} diff --git a/src/MobileFullScreenPage/header/index.js b/src/MobileFullScreenPage/header/index.js new file mode 100644 index 00000000..fa5fbed4 --- /dev/null +++ b/src/MobileFullScreenPage/header/index.js @@ -0,0 +1,36 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import classnames from 'classnames'; + +import './index.css'; + +const Header = ({ classNamePrefix, children, onLeftClick, onRightClick }) => { + return ( +
+
+ {children[0]} +
+

+ {children[1]} +

+
+ {children[2]} +
+
+ ); +}; + +Header.propTypes = { + classNamePrefix: PropTypes.string, + children: PropTypes.any, + onLeftClick: PropTypes.func, + onRightClick: PropTypes.func, +}; + +export default Header; diff --git a/src/MobileFullScreenPage/index.css b/src/MobileFullScreenPage/index.css new file mode 100644 index 00000000..f4e986b8 --- /dev/null +++ b/src/MobileFullScreenPage/index.css @@ -0,0 +1,13 @@ +.dtable-ui-mobile-full-screen-page { + height: 100%; + width: 100%; + position: fixed; + background-color: #f5f5f5; + top: 0; + left: 0; + bottom: 0; + right: 0; + overflow: hidden; + display: flex; + flex-direction: column; +} diff --git a/src/MobileFullScreenPage/index.js b/src/MobileFullScreenPage/index.js new file mode 100644 index 00000000..91e9356a --- /dev/null +++ b/src/MobileFullScreenPage/index.js @@ -0,0 +1,82 @@ +import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import PropTypes from 'prop-types'; +import classnames from 'classnames'; +import BodyPortal from '../BodyPortal'; +import Header from './header'; +import Body from './body'; +import { isFunction } from '../utils/utils'; + +import './index.css'; + +const MobileFullScreenPage = ({ + classNamePrefix, + className, + style, + zIndex, + children, + onClose, + historyCallback, +}) => { + const [isMount, setMount] = useState(false); + + const element = useMemo(() => { + let _element = document.createElement('div'); + _element.setAttribute('tabindex', '-1'); + _element.style.position = 'relative'; + _element.style.zIndex = zIndex || 100; + return _element; + }, [zIndex]); + + const handelHistoryCallback = useCallback((event) => { + event.preventDefault(); + if (isFunction(historyCallback)) { + historyCallback(); + return; + } + + if (isFunction(onClose)) { + onClose(); + return; + } + }, [onClose, historyCallback]); + + useEffect(() => { + // eslint-disable-next-line no-restricted-globals + history.pushState(null, null, '#'); + window.addEventListener('popstate', handelHistoryCallback, false); + return () => { + window.removeEventListener('popstate', handelHistoryCallback, false); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [handelHistoryCallback]); + + useEffect(() => { + document.body.appendChild(element); + setMount(true); + return () => { + document.body.removeChild(element); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + return ( + +
+ {isMount && children} +
+
+ ); +}; + +MobileFullScreenPage.propTypes = { + classNamePrefix: PropTypes.string, + className: PropTypes.string, + style: PropTypes.object, + children: PropTypes.any, + historyCallback: PropTypes.func, +}; + +MobileFullScreenPage.Header = Header; +MobileFullScreenPage.Body = Body; + +export default MobileFullScreenPage; diff --git a/src/MobileModal/index.js b/src/MobileModal/index.js new file mode 100644 index 00000000..28650f25 --- /dev/null +++ b/src/MobileModal/index.js @@ -0,0 +1,42 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { Modal } from 'antd-mobile'; + +class MobileModal extends React.PureComponent { + + componentDidMount() { + window.history.pushState(null, null, '#'); + window.addEventListener('popstate', this.handleHistoryBack, false); + } + + componentWillUnmount() { + window.removeEventListener('popstate', this.handleHistoryBack, false); + } + + handleHistoryBack = (e) => { + e.preventDefault(); + this.props.onClose(); + }; + + render() { + const { children, ...props } = this.props; + return ( + + {children} + + ); + } +} + +MobileModal.defaultProps = { + visible: true, + animationType: 'slide-up', + transitionName: 'transitionName', + maskTransitionName: 'maskTransitionName' +}; + +MobileModal.propTypes = { + onClose: PropTypes.func.isRequired, +}; + +export default MobileModal; diff --git a/src/MobileOperationSheet/index.css b/src/MobileOperationSheet/index.css new file mode 100644 index 00000000..4b2df9aa --- /dev/null +++ b/src/MobileOperationSheet/index.css @@ -0,0 +1,27 @@ +.dtable-ui-mobile-action-sheet .am-action-sheet-button-list { + text-align: left; + color: #212529; +} + +.dtable-ui-mobile-action-sheet .am-action-sheet-button-list .am-action-sheet-button-list-item { + padding: 0; + height: 44px; + line-height: 44px; + font-size: 16px; + overflow-y: hidden; +} + +html:not([data-scale]) .dtable-ui-mobile-action-sheet .am-action-sheet-button-list .am-action-sheet-button-list-item::before { + background-color: #fff; +} + +.dtable-ui-mobile-action-sheet .my-am-action { + padding: 0 16px; + align-items: center; +} + +.dtable-ui-mobile-action-sheet .am-action-sheet-button-list .am-action-sheet-button-list-item .dtable-font { + color: #888; + font-size: 20px; + margin-right: 16px; +} diff --git a/src/MobileOperationSheet/index.js b/src/MobileOperationSheet/index.js new file mode 100644 index 00000000..d8804de4 --- /dev/null +++ b/src/MobileOperationSheet/index.js @@ -0,0 +1,99 @@ +import React, { useCallback, useEffect, useMemo, useRef } from 'react'; +import PropTypes from 'prop-types'; +import { ActionSheet } from 'antd-mobile'; +import { getLocale } from '../lang'; + +import './index.css'; + +const OPERATION_TYPE = { + RENAME: 'rename', + DOWNLOAD: 'download', + DELETE: 'delete', + CLOSE: 'close' +}; + +const OPERATION_TYPES = [ + OPERATION_TYPE.RENAME, + OPERATION_TYPE.DOWNLOAD, + OPERATION_TYPE.DELETE, +]; + +const MobileOperationSheet = ({ onChange, onClose, operations = OPERATION_TYPES }) => { + const actionSheet = useRef(ActionSheet); + + const btns = useMemo(() => { + let _btns = []; + + if (operations.includes(OPERATION_TYPE.RENAME)) { + _btns.push({ + dom: ( +
+ {getLocale('Rename')} +
+ ), + value: OPERATION_TYPE.RENAME + }); + } + if (operations.includes(OPERATION_TYPE.DOWNLOAD)) { + _btns.push({ + dom: ( +
+ {getLocale('Download')} +
+ ), + value: OPERATION_TYPE.DOWNLOAD + }); + } + if (operations.includes(OPERATION_TYPE.DELETE)) { + _btns.push({ + dom: ( +
+ {getLocale('Delete')} +
+ ), + value: OPERATION_TYPE.DELETE + }); + } + return _btns; + }, [operations]); + + const showActionSheet = useCallback(() => { + const _btns = btns.map(btn => btn.dom); + actionSheet.current.showActionSheetWithOptions({ + options: _btns, + maskClosable: true, + className: 'dtable-ui-mobile-action-sheet' + }, (index) => { + const { value = 'close' } = btns[index] || {}; + if (value === 'close') { + onClose && onClose(); + return; + } + onChange && onChange(value); + }); + }, [btns, onChange, onClose]); + + useEffect(() => { + showActionSheet(); + return () => { + actionSheet.current.close(); + actionSheet.current = null; + onClose && onClose(); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + return null; +}; + +MobileOperationSheet.propTypes = { + operations: PropTypes.array, + onChange: PropTypes.func, + onClose: PropTypes.func, +}; + +export default MobileOperationSheet; + +export { + OPERATION_TYPE +}; diff --git a/src/MobileSelector/empty/index.css b/src/MobileSelector/empty/index.css new file mode 100644 index 00000000..4fd4f735 --- /dev/null +++ b/src/MobileSelector/empty/index.css @@ -0,0 +1,3 @@ +.dtable-ui-mobile-selector-no-options { + opacity: 0.5; +} diff --git a/src/MobileSelector/empty/index.js b/src/MobileSelector/empty/index.js new file mode 100644 index 00000000..f8f62859 --- /dev/null +++ b/src/MobileSelector/empty/index.js @@ -0,0 +1,20 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { List } from 'antd-mobile'; + +import './index.css'; + +const Empty = ({ children }) => { + + return ( + + {children} + + ); +}; + +Empty.propType = { + children: PropTypes.any, +}; + +export default Empty; diff --git a/src/MobileSelector/index.css b/src/MobileSelector/index.css new file mode 100644 index 00000000..bd6b09ff --- /dev/null +++ b/src/MobileSelector/index.css @@ -0,0 +1,32 @@ +.dtable-ui-mobile-selector .am-list-header { + color: #212529; + font-size: 16px; + font-weight: bold; +} + +.dtable-ui-mobile-selector .am-list-header .mobile-selector-editor-close-btn { + width: 20px; + font-size: 16px; + color: #798d99; + right: 0px; + position: absolute; + bottom: 0px; +} + +html:not([data-scale]) .dtable-ui-mobile-selector .am-list-body:before, +html:not([data-scale]) .dtable-ui-mobile-selector .am-list-body:after { + background-color: #e9e9e9; + transform: scaleY(1) !important; +} + +.dtable-ui-mobile-selector .am-list-body .dtable-ui-mobile-selector-search + .dtable-ui-mobile-selector-options { + border-top: 1px solid #e9e9e9; +} + +.dtable-ui-mobile-selector .am-list-body .add-item-btn { + color: #212529; +} + +.dtable-ui-mobile-selector .am-list-body .am-list-item { + min-height: 50px; +} diff --git a/src/MobileSelector/index.js b/src/MobileSelector/index.js new file mode 100644 index 00000000..3aa54514 --- /dev/null +++ b/src/MobileSelector/index.js @@ -0,0 +1,64 @@ +import React, { useCallback, useEffect } from 'react'; +import PropTypes from 'prop-types'; +import { List } from 'antd-mobile'; +import classnames from 'classnames'; +import MobileModal from '../MobileModal'; +import Search from './search'; +import Options from './options'; +import Option from './option'; +import Empty from './empty'; + +import './index.css'; + +const MobileSelector = ({ + className, + listClassName, + title, + children, + onClose, +}) => { + const handleHistoryBack = useCallback((event) => { + event.preventDefault(); + onClose && onClose(); + }, [onClose]); + + useEffect(() => { + history.pushState(null, null, '#'); // eslint-disable-line + window.addEventListener('popstate', handleHistoryBack, false); + return () => { + window.removeEventListener('popstate', handleHistoryBack, false); + }; + }, [handleHistoryBack]); + + const renderHeader = useCallback(() => { + return ( +
+ {title} + +
+ ); + }, [title, onClose]); + + return ( + + + {children} + + + ); +}; + +MobileSelector.propTypes = { + className: PropTypes.string, + listClassName: PropTypes.string, + title: PropTypes.any, + children: PropTypes.any, + onClose: PropTypes.func, +}; + +MobileSelector.Search = Search; +MobileSelector.Option = Option; +MobileSelector.Options = Options; +MobileSelector.Empty = Empty; + +export default MobileSelector; diff --git a/src/MobileSelector/option/index.css b/src/MobileSelector/option/index.css new file mode 100644 index 00000000..3780193f --- /dev/null +++ b/src/MobileSelector/option/index.css @@ -0,0 +1,28 @@ + +.dtable-ui-mobile-selector .dtable-ui-mobile-selector-option { + display: flex; + align-items: center; + width: 100%; + height: 36px; + border-radius: 2px; + font-size: 13px; + color: #212529; + padding: 0; +} + +.dtable-ui-mobile-selector .dtable-ui-mobile-selector-option .dtable-ui-mobile-selector-option-name { + height: 30px; + flex: 1; + overflow: hidden; + text-overflow: ellipsis; + display: flex; + align-items: center; + margin-right: 10px; +} + +.dtable-ui-mobile-selector .dtable-ui-mobile-selector-option .dtable-ui-mobile-selector-option-check-btn { + width: 20px; + text-align: center; + font-size: 12px; + color: #798d99; +} diff --git a/src/MobileSelector/option/index.js b/src/MobileSelector/option/index.js new file mode 100644 index 00000000..f20371fa --- /dev/null +++ b/src/MobileSelector/option/index.js @@ -0,0 +1,29 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { List } from 'antd-mobile'; + +import './index.css'; + +const Option = ({ isSelected, children, onChange }) => { + + return ( + +
+ + {children} + + + {isSelected && ()} + +
+
+ ); +}; + +Option.propType = { + isSelected: PropTypes.bool, + children: PropTypes.any, + onChange: PropTypes.func, +}; + +export default Option; diff --git a/src/MobileSelector/options/index.css b/src/MobileSelector/options/index.css new file mode 100644 index 00000000..6a22d798 --- /dev/null +++ b/src/MobileSelector/options/index.css @@ -0,0 +1,8 @@ +.dtable-ui-mobile-selector .am-list-body .dtable-ui-mobile-selector-options { + max-height: 200px; + overflow-y: auto; +} + +.dtable-ui-mobile-selector .am-list-body .dtable-ui-mobile-selector-options::-webkit-scrollbar { + width: 0; +} diff --git a/src/MobileSelector/options/index.js b/src/MobileSelector/options/index.js new file mode 100644 index 00000000..3c469907 --- /dev/null +++ b/src/MobileSelector/options/index.js @@ -0,0 +1,13 @@ +import React from 'react'; + +import './index.css'; + +const Options = ({ children }) => { + return ( +
+ {children} +
+ ); +}; + +export default Options; diff --git a/src/MobileSelector/search/index.css b/src/MobileSelector/search/index.css new file mode 100644 index 00000000..341d626a --- /dev/null +++ b/src/MobileSelector/search/index.css @@ -0,0 +1,7 @@ +.dtable-ui-mobile-selector .dtable-ui-mobile-selector-search { + padding: 10px; +} + +.dtable-ui-mobile-selector .dtable-ui-mobile-selector-search .form-control { + background-color: rgb(245, 245, 245); +} diff --git a/src/MobileSelector/search/index.js b/src/MobileSelector/search/index.js new file mode 100644 index 00000000..bc52d2ce --- /dev/null +++ b/src/MobileSelector/search/index.js @@ -0,0 +1,21 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import DTableSearchInput from '../../DTableSearchInput'; + +import './index.css'; + +const Search = ({ value, placeholder, onChange }) => { + return ( +
+ +
+ ); +}; + +Search.propTypes = { + value: PropTypes.string, + placeholder: PropTypes.string, + onChange: PropTypes.func, +}; + +export default Search; diff --git a/src/MobileUpload/index.css b/src/MobileUpload/index.css new file mode 100644 index 00000000..09210432 --- /dev/null +++ b/src/MobileUpload/index.css @@ -0,0 +1 @@ +@import url('../MobileOperationSheet/index.css'); diff --git a/src/MobileUpload/index.js b/src/MobileUpload/index.js new file mode 100644 index 00000000..82054f02 --- /dev/null +++ b/src/MobileUpload/index.js @@ -0,0 +1,120 @@ +import React, { useCallback, useEffect, useMemo, useRef } from 'react'; +import PropTypes from 'prop-types'; +import { ActionSheet } from 'antd-mobile'; +import { getLocale } from '../lang'; + +import './index.css'; + +const MobileUpload = ({ type = 'file', onChange, onClose }) => { + const actionSheet = useRef(ActionSheet); + + const imageRef = useRef(null); + const fileRef = useRef(null); + const cameraRef = useRef(null); + const camcorderRef = useRef(null); + + const btns = useMemo(() => { + const iconStyle = { lineHeight: '56px' }; + if (type === 'image') return [ +
+ {getLocale('Take_a_photo')} +
, +
+ {getLocale('Browse_documents')} +
, +
{getLocale('Cancel')}
, + ]; + return [ +
+ {getLocale('Take_a_photo')} +
, +
+ {getLocale('Take_a_video')} +
, +
+ {getLocale('Browse_documents')} +
, +
{getLocale('Cancel')}
+ ]; + }, [type]); + + const showActionSheet = useCallback(() => { + if (type === 'image') { + actionSheet.current.showActionSheetWithOptions({ + options: btns, + cancelButtonIndex: 3, + maskClosable: true, + className: 'dtable-ui-mobile-action-sheet' + }, (index) => { + if (index === 0) { + imageRef.current.click(); + } else if (index === 1) { + cameraRef.current.click(); + } else { + onClose && onClose(); + } + }); + return; + } + actionSheet.current.showActionSheetWithOptions({ + options: btns, + cancelButtonIndex: 4, + maskClosable: true, + className: 'dtable-ui-mobile-action-sheet' + }, (index) => { + if (index === 0) { + fileRef.current.click(); + } else if (index === 1) { + cameraRef.current.click(); + } else if (index === 2) { + camcorderRef.current.click(); + } else { + onClose && onClose(); + } + }); + }, [type, btns, onClose]); + + const onInputClick = useCallback((event) => { + event.stopPropagation(); + event.nativeEvent.stopImmediatePropagation(); + }, []); + + const handleChange = useCallback((event) => { + event.persist(); + onChange && onChange(event.target.files); + }, [onChange]); + + useEffect(() => { + showActionSheet(); + return () => { + actionSheet.current.close(); + actionSheet.current = null; + onClose && onClose(); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + if (type === 'image') { + return ( +
+ + +
+ ); + } + return ( +
+ + + +
+ ); +}; + +MobileUpload.propTypes = { + type: PropTypes.oneOf(['file', 'image']), + onChange: PropTypes.func, + onClose: PropTypes.func, +}; + +export default MobileUpload; diff --git a/src/MultipleSelectEditor/index.js b/src/MultipleSelectEditor/index.js index 165f8fac..7b943185 100644 --- a/src/MultipleSelectEditor/index.js +++ b/src/MultipleSelectEditor/index.js @@ -1,25 +1,16 @@ -import React from 'react'; +import React, { forwardRef } from 'react'; import PropTypes from 'prop-types'; -import MediaQuery from 'react-responsive'; -import { PCSelectEditor, MBSelectEditor } from '../select-editor'; +import SelectEditor from '../select-editor'; -const MultipleSelectEditor = ({ value: oldValue, ...props }) => { +const MultipleSelectEditor = forwardRef(({ value: oldValue, ...props }, ref) => { const value = oldValue ? Array.isArray(oldValue) ? oldValue : [oldValue] : []; - return ( - <> - - - - - - - - ); -}; + return (); +}); MultipleSelectEditor.propTypes = { value: PropTypes.array, }; export default MultipleSelectEditor; + diff --git a/src/RowExpand/index.js b/src/RowExpand/index.js new file mode 100644 index 00000000..ce1d405d --- /dev/null +++ b/src/RowExpand/index.js @@ -0,0 +1,20 @@ +import React, { forwardRef } from 'react'; +import MediaQuery from 'react-responsive'; +import RowExpandDialog from '../RowExpandDialog'; +import RowExpandView from '../RowExpandView'; + +const RowExpand = forwardRef((props, ref) => { + + return ( + <> + + + + + + + + ); +}); + +export default RowExpand; diff --git a/src/RowExpandDialog/column-content/index.js b/src/RowExpandDialog/column-content/index.js index 642d4a94..23419395 100644 --- a/src/RowExpandDialog/column-content/index.js +++ b/src/RowExpandDialog/column-content/index.js @@ -5,7 +5,7 @@ import { COLUMNS_ICON_CONFIG } from 'dtable-utils'; import './index.css'; -const RowContent = ({ column, children }) => { +const ColumnContent = ({ column, children }) => { const descriptionRef = useRef(null); const { name, type, key, description } = column; return ( @@ -41,9 +41,9 @@ const RowContent = ({ column, children }) => { ); }; -RowContent.propTypes = { +ColumnContent.propTypes = { column: PropTypes.object, children: PropTypes.any, }; -export default RowContent; +export default ColumnContent; diff --git a/src/RowExpandDialog/index.js b/src/RowExpandDialog/index.js index e7d98a42..6e7eff79 100644 --- a/src/RowExpandDialog/index.js +++ b/src/RowExpandDialog/index.js @@ -227,4 +227,7 @@ RowExpandDialog.propTypes = { copyURL: PropTypes.func, }; +RowExpandDialog.Header = Header; +RowExpandDialog.Body = Body; + export default RowExpandDialog; diff --git a/src/RowExpandEditor/RowExpandEmailEditor/index.css b/src/RowExpandEditor/RowExpandEmailEditor/index.css deleted file mode 100644 index 7bffb5c6..00000000 --- a/src/RowExpandEditor/RowExpandEmailEditor/index.css +++ /dev/null @@ -1,2 +0,0 @@ -@import url('../RowExpandUrlEditor/index.css'); - diff --git a/src/RowExpandEditor/RowExpandFileEditor/index.css b/src/RowExpandEditor/RowExpandFileEditor/index.css deleted file mode 100644 index fa8578ef..00000000 --- a/src/RowExpandEditor/RowExpandFileEditor/index.css +++ /dev/null @@ -1 +0,0 @@ -@import url('../RowExpandImageEditor/index.css'); diff --git a/src/RowExpandEditor/RowExpandRateEditor/index.css b/src/RowExpandEditor/RowExpandRateEditor/index.css deleted file mode 100644 index 5a4120ae..00000000 --- a/src/RowExpandEditor/RowExpandRateEditor/index.css +++ /dev/null @@ -1,13 +0,0 @@ -.dtable-ui.dtable-ui-row-expand-rate-editor { - display: flex; -} - -.dtable-ui.dtable-ui-row-expand-rate-editor > div { - padding-right: 5px; - cursor: pointer; - line-height: 1.5; -} - -.dtable-ui.dtable-ui-row-expand-rate-editor .rate-item-active { - opacity: 1 !important; -} diff --git a/src/RowExpandEditor/add-btn/index.js b/src/RowExpandEditor/add-btn/index.js new file mode 100644 index 00000000..e8c935a0 --- /dev/null +++ b/src/RowExpandEditor/add-btn/index.js @@ -0,0 +1,19 @@ +import React, { forwardRef } from 'react'; +import MediaQuery from 'react-responsive'; +import RowExpandPCAddBtn from './pc-add-btn'; +import RowExpandMBAddBtn from './mb-add-btn'; + +const RowExpandAddBtn = forwardRef((props, ref) => { + return ( + <> + + + + + + + + ); +}); + +export default RowExpandAddBtn; diff --git a/src/RowExpandEditor/add-btn/mb-add-btn/index.css b/src/RowExpandEditor/add-btn/mb-add-btn/index.css new file mode 100644 index 00000000..8ccac2ae --- /dev/null +++ b/src/RowExpandEditor/add-btn/mb-add-btn/index.css @@ -0,0 +1,3 @@ +.dtable-ui-mobile-row-expand-add-btn { + color: #666666; +} diff --git a/src/RowExpandEditor/add-btn/mb-add-btn/index.js b/src/RowExpandEditor/add-btn/mb-add-btn/index.js new file mode 100644 index 00000000..bd279cd3 --- /dev/null +++ b/src/RowExpandEditor/add-btn/mb-add-btn/index.js @@ -0,0 +1,25 @@ +import React, { forwardRef } from 'react'; +import PropTypes from 'prop-types'; +import classnames from 'classnames'; + +import './index.css'; + +const RowExpandMBAddBtn = forwardRef((props, ref) => { + return ( +
+ {props.text} +
+ ); +}); + +RowExpandMBAddBtn.propTypes = { + text: PropTypes.string, + className: PropTypes.string, + onClick: PropTypes.func, +}; + +export default RowExpandMBAddBtn; diff --git a/src/RowExpandEditor/RowExpandAddBtn/index.css b/src/RowExpandEditor/add-btn/pc-add-btn/index.css similarity index 100% rename from src/RowExpandEditor/RowExpandAddBtn/index.css rename to src/RowExpandEditor/add-btn/pc-add-btn/index.css diff --git a/src/RowExpandEditor/RowExpandAddBtn/index.js b/src/RowExpandEditor/add-btn/pc-add-btn/index.js similarity index 58% rename from src/RowExpandEditor/RowExpandAddBtn/index.js rename to src/RowExpandEditor/add-btn/pc-add-btn/index.js index 0dc371ea..508987cd 100644 --- a/src/RowExpandEditor/RowExpandAddBtn/index.js +++ b/src/RowExpandEditor/add-btn/pc-add-btn/index.js @@ -1,10 +1,11 @@ import React, { forwardRef } from 'react'; import PropTypes from 'prop-types'; -import { ROW_EXPAND_BTN_FOCUS_STYLE } from '../../constants'; +import classnames from 'classnames'; +import { ROW_EXPAND_BTN_FOCUS_STYLE } from '../../../constants'; import './index.css'; -const RowExpandAddBtn = forwardRef((props, ref) => { +const RowExpandPCAddBtn = forwardRef((props, ref) => { return (
{ onClick={props.onClick} onFocus={props.onFocus} role="button" - className="dtable-ui-row-expand-add-btn d-print-none" + className={classnames('dtable-ui-row-expand-add-btn d-print-none', props.className)} style={props.isFocus ? ROW_EXPAND_BTN_FOCUS_STYLE : {}} > {props.text} @@ -20,12 +21,12 @@ const RowExpandAddBtn = forwardRef((props, ref) => { ); }); -RowExpandAddBtn.propTypes = { +RowExpandPCAddBtn.propTypes = { isFocus: PropTypes.bool, text: PropTypes.string, + className: PropTypes.string, onFocus: PropTypes.func, onClick: PropTypes.func, - t: PropTypes.func, }; -export default RowExpandAddBtn; +export default RowExpandPCAddBtn; diff --git a/src/RowExpandEditor/RowExpandCheckboxEditor/index.css b/src/RowExpandEditor/checkbox-editor/index.css similarity index 100% rename from src/RowExpandEditor/RowExpandCheckboxEditor/index.css rename to src/RowExpandEditor/checkbox-editor/index.css diff --git a/src/RowExpandEditor/checkbox-editor/index.js b/src/RowExpandEditor/checkbox-editor/index.js new file mode 100644 index 00000000..74b18e2b --- /dev/null +++ b/src/RowExpandEditor/checkbox-editor/index.js @@ -0,0 +1,21 @@ +import React, { forwardRef } from 'react'; +import MediaQuery from 'react-responsive'; +import RowExpandPCCheckboxEditor from './pc-editor'; +import RowExpandMBCheckboxEditor from './mb-editor'; + +import './index.css'; + +const RowExpandCheckboxEditor = forwardRef((props, ref) => { + return ( + <> + + + + + + + + ); +}); + +export default RowExpandCheckboxEditor; diff --git a/src/RowExpandEditor/checkbox-editor/mb-editor/index.css b/src/RowExpandEditor/checkbox-editor/mb-editor/index.css new file mode 100644 index 00000000..39b5ce99 --- /dev/null +++ b/src/RowExpandEditor/checkbox-editor/mb-editor/index.css @@ -0,0 +1,6 @@ +.dtable-ui-mobile-row-expand-checkbox-editor-container { + width: 100%; + height: 50px; + display: flex; + align-items: center; +} diff --git a/src/RowExpandEditor/checkbox-editor/mb-editor/index.js b/src/RowExpandEditor/checkbox-editor/mb-editor/index.js new file mode 100644 index 00000000..dbe11614 --- /dev/null +++ b/src/RowExpandEditor/checkbox-editor/mb-editor/index.js @@ -0,0 +1,41 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import classnames from 'classnames'; +import CheckboxEditor from '../../../CheckboxEditor'; + +import './index.css'; + +class RowExpandMBCheckboxEditor extends React.Component { + + constructor(props) { + super(props); + this.editor = null; + } + + onCommit = (newValue) => { + this.props.onCommit && this.props.onCommit(newValue); + }; + + render() { + const { column, value, className } = this.props; + return ( +
+ +
+ ); + } +} + +RowExpandMBCheckboxEditor.propTypes = { + className: PropTypes.string, + column: PropTypes.object.isRequired, + row: PropTypes.object, + onCommit: PropTypes.func.isRequired, +}; + +export default RowExpandMBCheckboxEditor; diff --git a/src/RowExpandEditor/RowExpandCheckboxEditor/index.js b/src/RowExpandEditor/checkbox-editor/pc-editor.js similarity index 57% rename from src/RowExpandEditor/RowExpandCheckboxEditor/index.js rename to src/RowExpandEditor/checkbox-editor/pc-editor.js index 7e9b63d3..3428a051 100644 --- a/src/RowExpandEditor/RowExpandCheckboxEditor/index.js +++ b/src/RowExpandEditor/checkbox-editor/pc-editor.js @@ -3,36 +3,29 @@ import PropTypes from 'prop-types'; import CheckboxEditor from '../../CheckboxEditor'; import { ROW_EXPAND_FOCUS_STYLE } from '../../constants'; -import './index.css'; +class RowExpandPCCheckboxEditor extends React.Component { -class RowExpandCheckboxEditor extends React.Component { - - onChangeCheckboxValue = () => { - const { columnIndex } = this.props; - if (!this.editor) return; - if (this.props.updateTabIndex) { - this.props.updateTabIndex(columnIndex); - } - const newValue = this.editor.getValue(); - this.props.onCommit(newValue); + onCommit = (newValue) => { + const { columnIndex, updateTabIndex, onCommit } = this.props; + updateTabIndex && updateTabIndex(columnIndex); + onCommit && onCommit(newValue); }; render() { const { isEditorFocus, column, value } = this.props; return ( this.editor = ref} className="dtable-ui-row-expand-checkbox-editor mt-2" column={column} value={value} style={isEditorFocus ? ROW_EXPAND_FOCUS_STYLE : {}} - onCommit={this.onChangeCheckboxValue} + onCommit={this.onCommit} /> ); } } -RowExpandCheckboxEditor.propTypes = { +RowExpandPCCheckboxEditor.propTypes = { columnIndex: PropTypes.number, isEditorFocus: PropTypes.bool, column: PropTypes.object.isRequired, @@ -41,4 +34,4 @@ RowExpandCheckboxEditor.propTypes = { onCommit: PropTypes.func.isRequired, }; -export default RowExpandCheckboxEditor; +export default RowExpandPCCheckboxEditor; diff --git a/src/RowExpandEditor/collaborator-editor/index.js b/src/RowExpandEditor/collaborator-editor/index.js new file mode 100644 index 00000000..191155db --- /dev/null +++ b/src/RowExpandEditor/collaborator-editor/index.js @@ -0,0 +1,19 @@ +import React, { forwardRef } from 'react'; +import MediaQuery from 'react-responsive'; +import RowExpandPCCollaboratorEditor from './pc-editor'; +import RowExpandMBCollaboratorEditor from './mb-editor'; + +const RowExpandCollaboratorEditor = forwardRef((props, ref) => { + return ( + <> + + + + + + + + ); +}); + +export default RowExpandCollaboratorEditor; diff --git a/src/RowExpandEditor/collaborator-editor/mb-editor/index.css b/src/RowExpandEditor/collaborator-editor/mb-editor/index.css new file mode 100644 index 00000000..503e35f0 --- /dev/null +++ b/src/RowExpandEditor/collaborator-editor/mb-editor/index.css @@ -0,0 +1,9 @@ +.dtable-ui-mobile-row-expand-collaborators { + min-height: 50px; + padding: 1rem 0; +} + +.dtable-ui-mobile-row-expand-collaborators .dtable-ui.collaborator-item { + margin-top: 3px; + margin-bottom: 3px; +} \ No newline at end of file diff --git a/src/RowExpandEditor/collaborator-editor/mb-editor/index.js b/src/RowExpandEditor/collaborator-editor/mb-editor/index.js new file mode 100644 index 00000000..216946f4 --- /dev/null +++ b/src/RowExpandEditor/collaborator-editor/mb-editor/index.js @@ -0,0 +1,148 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import CollaboratorEditor from '../../../CollaboratorEditor'; +import { getLocale } from '../../../lang'; +import CollaboratorItem from '../../../CollaboratorItem'; +import RightAngle from '../../right-angle'; +import RowExpandAddBtn from '../../add-btn'; + +import './index.css'; + +class RowExpandMBCollaboratorEditor extends React.Component { + + constructor(props) { + super(props); + const { row, column, valueKey } = this.props; + this.state = { + menuPosition: null, + value: row[column[valueKey]] || [], + showEditor: false, + emailCollaboratorMap: {}, + }; + } + + componentDidMount() { + this.initCollaborators(this.props); + const eventBus = this.props.eventBus; + if (eventBus) { + this.unsubscribeCollaboratorsUpdated = eventBus.subscribe('collaborators-updated', this.updateCollaborators); + } else { + this.updateCollaborators(); + } + } + + UNSAFE_componentWillReceiveProps(nextProps) { + const { column, row, valueKey } = nextProps; + if (row._id !== this.props.row._id) { + this.initCollaborators(nextProps); + this.setState({ + value: row[column[valueKey]] || [], + showEditor: false + }); + } + } + + componentWillUnmount() { + if (this.props.eventBus && this.unsubscribeCollaboratorsUpdated) { + this.unsubscribeCollaboratorsUpdated(); + } + } + + initCollaborators = (props) => { + const { row, column, valueKey } = props; + const emails = row[column[valueKey]] || []; + props.queryCollaborators && props.queryCollaborators(emails); + }; + + getCollaborators = () => { + const { getCollaborators, collaborators } = this.props; + if (getCollaborators) return getCollaborators(); + return collaborators || []; + }; + + updateCollaborators = () => { + const collaborators = this.getCollaborators(); + let emailCollaboratorMap = {}; + collaborators.forEach(c => { + emailCollaboratorMap[c.email] = c; + }); + this.setState({ emailCollaboratorMap }); + }; + + toggleEditor = (value) => { + this.setState({ showEditor: value }); + }; + + openEditor = (event) => { + event.stopPropagation(); + this.toggleEditor(true); + }; + + closeEditor = () => { + this.toggleEditor(false); + }; + + onChange = (collaborator) => { + let newValue = this.state.value.slice(0); + const collaboratorIndex = newValue.findIndex(email => collaborator.email === email); + if (collaboratorIndex === -1) { + newValue.push(collaborator.email); + } else { + newValue.splice(collaboratorIndex, 1); + } + this.setState({ value: newValue }); + this.props.onCommit(newValue); + }; + + renderCollaborators = (value) => { + const { emailCollaboratorMap } = this.state; + if (Array.isArray(value) && value.length > 0) { + return value.map((email, index) => { + let collaborator = emailCollaboratorMap[email]; + if (collaborator) { + const { email } = collaborator; + return (); + } + collaborator = { name: getLocale('Unknown'), avatar_url: '', email: index + '' }; + return (); + }); + } + return (); + }; + + render() { + const { collaborators, column } = this.props; + const { showEditor, value } = this.state; + return ( + <> +
+ {this.renderCollaborators(value)} + +
+ {showEditor && ( + + )} + + ); + } +} + +RowExpandMBCollaboratorEditor.propTypes = { + row: PropTypes.object, + column: PropTypes.object, + valueKey: PropTypes.string, + collaborators: PropTypes.array, + eventBus: PropTypes.object, + getCollaborators: PropTypes.func, + queryCollaborators: PropTypes.func, + onEditorOpen: PropTypes.func, + onCommit: PropTypes.func, +}; + +export default RowExpandMBCollaboratorEditor; diff --git a/src/RowExpandEditor/RowExpandCollaboratorEditor/index.css b/src/RowExpandEditor/collaborator-editor/pc-editor/index.css similarity index 70% rename from src/RowExpandEditor/RowExpandCollaboratorEditor/index.css rename to src/RowExpandEditor/collaborator-editor/pc-editor/index.css index 65195abe..9c377d4c 100644 --- a/src/RowExpandEditor/RowExpandCollaboratorEditor/index.css +++ b/src/RowExpandEditor/collaborator-editor/pc-editor/index.css @@ -1,4 +1,4 @@ -@import url('../../css/cell-editor.css'); +@import url('../../../css/cell-editor.css'); .dtable-ui.dtable-ui-row-expand-select-editor .dtable-ui.collaborator-item { margin: 5px 10px 5px 0; diff --git a/src/RowExpandEditor/RowExpandCollaboratorEditor/index.js b/src/RowExpandEditor/collaborator-editor/pc-editor/index.js similarity index 95% rename from src/RowExpandEditor/RowExpandCollaboratorEditor/index.js rename to src/RowExpandEditor/collaborator-editor/pc-editor/index.js index 544f4e7b..6a5decc7 100644 --- a/src/RowExpandEditor/RowExpandCollaboratorEditor/index.js +++ b/src/RowExpandEditor/collaborator-editor/pc-editor/index.js @@ -1,14 +1,14 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { KeyCodes } from '../../constants'; import classnames from 'classnames'; -import CollaboratorEditor from '../../CollaboratorEditor'; -import { getLocale } from '../../lang'; -import CollaboratorItem from '../../CollaboratorItem'; +import { KeyCodes } from '../../../constants'; +import CollaboratorEditor from '../../../CollaboratorEditor'; +import { getLocale } from '../../../lang'; +import CollaboratorItem from '../../../CollaboratorItem'; import './index.css'; -class RowExpandCollaboratorEditor extends React.Component { +class RowExpandPCCollaboratorEditor extends React.Component { constructor(props) { super(props); @@ -17,7 +17,6 @@ class RowExpandCollaboratorEditor extends React.Component { menuPosition: null, value: row[column[valueKey]] || [], showCollaboratorSelect: false, - isDataLoaded: false, emailCollaboratorMap: {}, }; } @@ -215,7 +214,7 @@ class RowExpandCollaboratorEditor extends React.Component { } -RowExpandCollaboratorEditor.propTypes = { +RowExpandPCCollaboratorEditor.propTypes = { isEditorFocus: PropTypes.bool, row: PropTypes.object, column: PropTypes.object, @@ -231,4 +230,4 @@ RowExpandCollaboratorEditor.propTypes = { updateTabIndex: PropTypes.func, }; -export default RowExpandCollaboratorEditor; +export default RowExpandPCCollaboratorEditor; diff --git a/src/RowExpandEditor/constants.js b/src/RowExpandEditor/constants.js index 9fd5ba0f..28db6a18 100644 --- a/src/RowExpandEditor/constants.js +++ b/src/RowExpandEditor/constants.js @@ -1,21 +1,21 @@ import { CellType } from 'dtable-utils'; -import RowExpandTextEditor from './RowExpandTextEditor'; -import RowExpandSingleSelectEditor from './RowExpandSingleSelectorEditor'; -import RowExpandMultipleSelectEditor from './RowExpandMultipleSelectEditor'; -import RowExpandUrlEditor from './RowExpandUrlEditor'; -import RowExpandNumberEditor from './RowExpandNumberEditor'; -import RowExpandDateEditor from './RowExpandDateEditor'; -import RowExpandLongTextEditor from './RowExpandLongTextEditor'; -import RowExpandCheckboxEditor from './RowExpandCheckboxEditor'; -import RowExpandEmailEditor from './RowExpandEmailEditor'; -import RowExpandDurationEditor from './RowExpandDurationEditor'; -import RowExpandRateEditor from './RowExpandRateEditor'; -import RowExpandCollaboratorEditor from './RowExpandCollaboratorEditor'; -import RowExpandDepartmentEditor from './RowExpandDepartmentEditor'; -import RowExpandImageEditor from './RowExpandImageEditor'; -import RowExpandFileEditor from './RowExpandFileEditor'; -import RowExpandDigitalSignEditor from './RowExpandDigitalSignEditor'; -import RowExpandGeolocationEditor from './RowExpandGeolocationEditor'; +import RowExpandTextEditor from './text-editor'; +import RowExpandSingleSelectEditor from './single-select-editor'; +import RowExpandMultipleSelectEditor from './multiple-select-editor'; +import RowExpandUrlEditor from './url-editor'; +import RowExpandNumberEditor from './number-editor'; +import RowExpandDateEditor from './date-editor'; +import RowExpandLongTextEditor from './long-text-editor'; +import RowExpandCheckboxEditor from './checkbox-editor'; +import RowExpandEmailEditor from './email-editor'; +import RowExpandDurationEditor from './duration-editor'; +import RowExpandRateEditor from './rate-editor'; +import RowExpandCollaboratorEditor from './collaborator-editor'; +import RowExpandDepartmentEditor from './department-editor'; +import RowExpandImageEditor from './image-editor'; +import RowExpandFileEditor from './file-editor'; +import RowExpandDigitalSignEditor from './digital-sign-editor'; +import RowExpandGeolocationEditor from './geolocation-editor'; const CELL_EDITOR_MAP = { [CellType.TEXT]: RowExpandTextEditor, diff --git a/src/RowExpandEditor/date-editor/index.js b/src/RowExpandEditor/date-editor/index.js new file mode 100644 index 00000000..28b5a209 --- /dev/null +++ b/src/RowExpandEditor/date-editor/index.js @@ -0,0 +1,19 @@ +import React, { forwardRef } from 'react'; +import MediaQuery from 'react-responsive'; +import RowExpandPCDateEditor from './pc-editor'; +import RowExpandMBDateEditor from './mb-editor'; + +const RowExpandDateEditor = forwardRef((props, ref) => { + return ( + <> + + + + + + + + ); +}); + +export default RowExpandDateEditor; diff --git a/src/RowExpandEditor/date-editor/mb-editor.js b/src/RowExpandEditor/date-editor/mb-editor.js new file mode 100644 index 00000000..fbd871a7 --- /dev/null +++ b/src/RowExpandEditor/date-editor/mb-editor.js @@ -0,0 +1,72 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { getDateDisplayString } from 'dtable-utils'; +import DateEditor from '../../DateEditor'; +import RightAngle from '../right-angle'; +import { getDateColumnFormat } from '../../utils/column-utils'; + +class RowExpandMBDateEditor extends React.Component { + + constructor(props) { + super(props); + const { column, row, valueKey } = props; + this.state = { + isShowEditor: false, + value: row[column[valueKey]], + }; + } + + UNSAFE_componentWillReceiveProps(nextProps) { + const { column, row, valueKey } = nextProps; + if (row._id !== this.props.row._id) { + this.setState({ value: row[column[valueKey]], isShowEditor: false }); + } + } + + hideCalendar = () => { + this.setState({ isShowEditor: false }); + }; + + toggleEditor = () => { + this.setState({ isShowEditor: !this.state.isShowEditor }); + }; + + onCommit = (value) => { + this.props.onCommit(value); + this.setState({ value }); + }; + + render() { + let { column, lang } = this.props; + let { value, isShowEditor } = this.state; + const format = getDateColumnFormat(column); + return ( + <> +
+
+ {value ? getDateDisplayString(value, format) : ''} +
+ {} +
+ {isShowEditor && ( + + )} + + ); + } +} + +RowExpandMBDateEditor.propTypes = { + onCommit: PropTypes.func, + column: PropTypes.object, + row: PropTypes.object, + lang: PropTypes.string, +}; + +export default RowExpandMBDateEditor; diff --git a/src/RowExpandEditor/RowExpandDateEditor/index.js b/src/RowExpandEditor/date-editor/pc-editor.js similarity index 96% rename from src/RowExpandEditor/RowExpandDateEditor/index.js rename to src/RowExpandEditor/date-editor/pc-editor.js index 6facd60d..1cdd2f7b 100644 --- a/src/RowExpandEditor/RowExpandDateEditor/index.js +++ b/src/RowExpandEditor/date-editor/pc-editor.js @@ -6,7 +6,7 @@ import { getEventClassName } from '../../utils/utils'; import { getDateColumnFormat } from '../../utils/column-utils'; import { KeyCodes, ROW_EXPAND_FOCUS_STYLE } from '../../constants'; -class RowExpandDateEditor extends React.Component { +class RowExpandPCDateEditor extends React.Component { constructor(props) { super(props); @@ -108,7 +108,7 @@ class RowExpandDateEditor extends React.Component { } } -RowExpandDateEditor.propTypes = { +RowExpandPCDateEditor.propTypes = { onCommit: PropTypes.func, column: PropTypes.object, row: PropTypes.object, @@ -120,4 +120,4 @@ RowExpandDateEditor.propTypes = { onEditorClose: PropTypes.func, }; -export default RowExpandDateEditor; +export default RowExpandPCDateEditor; diff --git a/src/RowExpandEditor/department-editor/index.js b/src/RowExpandEditor/department-editor/index.js new file mode 100644 index 00000000..0fad5eb3 --- /dev/null +++ b/src/RowExpandEditor/department-editor/index.js @@ -0,0 +1,19 @@ +import React, { forwardRef } from 'react'; +import MediaQuery from 'react-responsive'; +import RowExpandPCDepartmentEditor from './pc-editor'; +import RowExpandMBDepartmentEditor from './mb-editor'; + +const RowExpandDepartmentEditor = forwardRef((props, ref) => { + return ( + <> + + + + + + + + ); +}); + +export default RowExpandDepartmentEditor; diff --git a/src/RowExpandEditor/department-editor/mb-editor.js b/src/RowExpandEditor/department-editor/mb-editor.js new file mode 100644 index 00000000..a881c820 --- /dev/null +++ b/src/RowExpandEditor/department-editor/mb-editor.js @@ -0,0 +1,69 @@ +import React, { useState, useEffect, useCallback } from 'react'; +import PropTypes from 'prop-types'; +import RightAngle from '../right-angle'; +import RowExpandAddBtn from '../add-btn'; +import DepartmentSingleSelectFormatter from '../../DepartmentSingleSelectFormatter'; +import DepartmentEditor from '../../DepartmentSingleSelectEditor'; +import { getLocale } from '../../lang'; + +const RowExpandMBDepartmentEditor = ({ row, column, valueKey, departments, userDepartmentIdsMap, onCommit }) => { + const [value, setValue] = useState(row[column[valueKey]] || ''); + const [showEditor, setShowEditor] = useState(false); + + const openEditor = useCallback(() => { + setShowEditor(true); + }, []); + + const closeEditor = useCallback(() => { + setShowEditor(false); + }, []); + + const onChange = useCallback((newValue) => { + if (newValue === value) return; + setValue(newValue); + onCommit && onCommit(newValue); + }, [value, onCommit]); + + useEffect(() => { + setValue(row[column[valueKey]] || ''); + setShowEditor(false); + // eslint-disable-next-line + }, [row, column]); + + return ( + <> +
+ {value ? ( +
+ +
+ ) : ( + + )} + +
+ {showEditor && ( + + )} + + ); +}; + +RowExpandMBDepartmentEditor.propTypes = { + row: PropTypes.object, + column: PropTypes.object, + valueKey: PropTypes.string, + departments: PropTypes.array, + userDepartmentIdsMap: PropTypes.object, + onCommit: PropTypes.func, +}; + +export default RowExpandMBDepartmentEditor; diff --git a/src/RowExpandEditor/RowExpandDepartmentEditor/index.js b/src/RowExpandEditor/department-editor/pc-editor.js similarity index 60% rename from src/RowExpandEditor/RowExpandDepartmentEditor/index.js rename to src/RowExpandEditor/department-editor/pc-editor.js index 4c305718..18ac7266 100644 --- a/src/RowExpandEditor/RowExpandDepartmentEditor/index.js +++ b/src/RowExpandEditor/department-editor/pc-editor.js @@ -1,73 +1,65 @@ -import React, { useState, useEffect, useRef } from 'react'; +import React, { useState, useEffect, useRef, useCallback } from 'react'; import PropTypes from 'prop-types'; import classnames from 'classnames'; -import { useClickOutside } from '../../hooks'; import DepartmentSingleSelectFormatter from '../../DepartmentSingleSelectFormatter'; import { KeyCodes } from '../../constants'; -import DepartmentSingleSelect from '../../Department-editor/department-single-select'; +import DepartmentEditor from '../../DepartmentSingleSelectEditor'; -function RowExpandDepartmentEditor(props) { - const { row, column, valueKey, departments, userDepartmentIdsMap, isEditorFocus, columnIndex } = props; + +const RowExpandPCDepartmentEditor = ({ + row, column, valueKey, departments, userDepartmentIdsMap, isEditorFocus, columnIndex, + onCommit, updateTabIndex, +}) => { const [value, setValue] = useState(row[column[valueKey]] || ''); const [showEditor, setShowEditor] = useState(false); + const departmentSelectContainer = useRef(null); const departmentSelectContent = useRef(null); - useClickOutside({ - currDOM: departmentSelectContainer.current, - onClickOutside: hideDropDownMenu, - }, [isEditorFocus, value]); + const onKeyDown = useCallback((event) => { + if (event.keyCode === KeyCodes.Enter && isEditorFocus && !showEditor) { + setShowEditor(true); + } else if (event.keyCode === KeyCodes.Escape && showEditor) { + event.stopPropagation(); + setShowEditor(false); + departmentSelectContent.current.focus(); + } + }, [isEditorFocus, showEditor]); + + const onToggleSelect = useCallback((event) => { + event.preventDefault(); + event.stopPropagation(); + updateTabIndex(columnIndex); + setShowEditor(true); + }, [columnIndex, updateTabIndex]); + + const onFocus = useCallback(() => { + updateTabIndex(columnIndex); + }, [columnIndex, updateTabIndex]); + + const handleCommit = useCallback((newValue) => { + setShowEditor(false); + if (newValue === value) return; + setValue(newValue); + onCommit(newValue); + }, [value, onCommit]); + + const closeEditor = useCallback(() => { + setShowEditor(false); + }, []); useEffect(() => { document.addEventListener('keydown', onKeyDown); return () => { document.removeEventListener('keydown', onKeyDown); }; - // eslint-disable-next-line - }, [isEditorFocus, showEditor]); + }, [onKeyDown]); useEffect(() => { setValue(row[column[valueKey]] || ''); setShowEditor(false); // eslint-disable-next-line - }, [row]); - - function hideDropDownMenu(event) { - if (!event.target) return; - toggleDepartmentSelect(false); - } - - function onKeyDown(e) { - if (e.keyCode === KeyCodes.Enter && props.isEditorFocus && !showEditor) { - setShowEditor(true); - } else if (e.keyCode === KeyCodes.Escape && showEditor) { - e.stopPropagation(); - setShowEditor(false); - departmentSelectContent.current.focus(); - } - } - - function toggleDepartmentSelect(value) { - setShowEditor(value); - } - - function onToggleSelect(e) { - e.preventDefault(); - e.stopPropagation(); - props.updateTabIndex(columnIndex); - toggleDepartmentSelect(true); - } - - function onFocus() { - props.updateTabIndex(columnIndex); - } - - function onCommit(newValue) { - toggleDepartmentSelect(false); - if (newValue === value) return; - setValue(newValue); - props.onCommit(newValue); - } + }, [row, column]); return (
@@ -84,20 +76,23 @@ function RowExpandDepartmentEditor(props) {
{showEditor && ( - )}
); -} +}; -RowExpandDepartmentEditor.propTypes = { +RowExpandPCDepartmentEditor.propTypes = { row: PropTypes.object, column: PropTypes.object, valueKey: PropTypes.string, @@ -109,4 +104,4 @@ RowExpandDepartmentEditor.propTypes = { onCommit: PropTypes.func, }; -export default RowExpandDepartmentEditor; +export default RowExpandPCDepartmentEditor; diff --git a/src/RowExpandEditor/digital-sign-editor/index.js b/src/RowExpandEditor/digital-sign-editor/index.js new file mode 100644 index 00000000..0d60115f --- /dev/null +++ b/src/RowExpandEditor/digital-sign-editor/index.js @@ -0,0 +1,19 @@ +import React, { forwardRef } from 'react'; +import MediaQuery from 'react-responsive'; +import RowExpandPCDigitalSignEditor from './pc-editor'; +import RowExpandMBDigitalSignEditor from './mb-editor'; + +const RowExpandDigitalSignEditor = forwardRef((props, ref) => { + return ( + <> + + + + + + + + ); +}); + +export default RowExpandDigitalSignEditor; diff --git a/src/RowExpandEditor/digital-sign-editor/mb-editor/index.css b/src/RowExpandEditor/digital-sign-editor/mb-editor/index.css new file mode 100644 index 00000000..479009e6 --- /dev/null +++ b/src/RowExpandEditor/digital-sign-editor/mb-editor/index.css @@ -0,0 +1 @@ +@import url('../../image-editor/mb-editor/index.css'); diff --git a/src/RowExpandEditor/digital-sign-editor/mb-editor/index.js b/src/RowExpandEditor/digital-sign-editor/mb-editor/index.js new file mode 100644 index 00000000..5e6ada25 --- /dev/null +++ b/src/RowExpandEditor/digital-sign-editor/mb-editor/index.js @@ -0,0 +1,114 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import ImageThumbnail from '../../../ImageThumbnail'; +import DigitalSignUtils from '../../../DigitalSignEditor/utils'; +import RowExpandAddBtn from '../../add-btn'; +import DigitalSignEditor from '../../../DigitalSignEditor'; +import { getLocale } from '../../../lang'; +import { generateCurrentBaseImageUrl } from '../../../utils/url'; + +import './index.css'; + +class RowExpandMBDigitalSignEditor extends React.Component { + + constructor(props) { + super(props); + this.state = { + isShowEditor: false, + updated: null, + }; + } + + UNSAFE_componentWillReceiveProps(nextProps) { + const { column, row, valueKey } = nextProps; + if (row._id !== this.props.row._id) { + this.setState({ + updated: row[column[valueKey]] || [], + isShowEditor: false, + }); + } + } + + toggleEditor = () => { + this.setState({ isShowEditor: !this.state.isShowEditor }); + }; + + onCommit = (value) => { + this.props.onCommit(value); + this.setState({ updated: value, isShowEditor: false }); + }; + + getValue = () => { + const { column, row, valueKey } = this.props; + const { updated } = this.state; + return updated ? updated : row[column[valueKey]]; + }; + + deleteImage = () => { + const { column, onCommit } = this.props; + const updated = { [ column.key ]: null }; + onCommit(updated, column); + this.setState({ updated: null }); + }; + + getSignImages = () => { + const { config } = this.props; + const value = this.getValue(); + const signImageUrl = generateCurrentBaseImageUrl({ ...config, partUrl: DigitalSignUtils.getSignImageUrl(value) }); + return [signImageUrl].filter(Boolean); + }; + + renderSignature = (signImages) => { + const { column, config } = this.props; + const { key } = column; + return signImages.map((src, index) => { + return ( + + ); + }); + }; + + render() { + const { config, column } = this.props; + const { isShowEditor } = this.state; + const signImages = this.getSignImages(); + const signImageExisted = Array.isArray(signImages) && signImages.length > 0; + return ( + <> +
+ {!signImageExisted && ( + + )} + {this.renderSignature(signImages)} +
+ {isShowEditor && ( + + )} + + ); + } +} + +RowExpandMBDigitalSignEditor.propTypes = { + column: PropTypes.object, + row: PropTypes.object, + onCommit: PropTypes.func, +}; + +export default RowExpandMBDigitalSignEditor; diff --git a/src/RowExpandEditor/RowExpandDigitalSignEditor/index.css b/src/RowExpandEditor/digital-sign-editor/pc-editor/index.css similarity index 82% rename from src/RowExpandEditor/RowExpandDigitalSignEditor/index.css rename to src/RowExpandEditor/digital-sign-editor/pc-editor/index.css index 03299436..a5657239 100644 --- a/src/RowExpandEditor/RowExpandDigitalSignEditor/index.css +++ b/src/RowExpandEditor/digital-sign-editor/pc-editor/index.css @@ -1,4 +1,4 @@ -@import url('../RowExpandImageEditor/index.css'); +@import url('../../image-editor/pc-editor/index.css'); .dtable-ui-row-expand-digital-sign-container .dtable-ui-image-thumbnail { max-width: 200px; diff --git a/src/RowExpandEditor/RowExpandDigitalSignEditor/index.js b/src/RowExpandEditor/digital-sign-editor/pc-editor/index.js similarity index 89% rename from src/RowExpandEditor/RowExpandDigitalSignEditor/index.js rename to src/RowExpandEditor/digital-sign-editor/pc-editor/index.js index 50a037af..bcbcea07 100644 --- a/src/RowExpandEditor/RowExpandDigitalSignEditor/index.js +++ b/src/RowExpandEditor/digital-sign-editor/pc-editor/index.js @@ -1,17 +1,17 @@ import React from 'react'; import PropTypes from 'prop-types'; -import ImagePreviewerLightbox from '../../ImagePreviewerLightbox'; -import ImageThumbnailItem from '../../ImageThumbnail'; -import DigitalSignUtils from '../../DigitalSignEditor/utils'; -import { KeyCodes } from '../../constants'; -import RowExpandAddBtn from '../RowExpandAddBtn'; -import DigitalSignEditor from '../../DigitalSignEditor'; -import { getLocale } from '../../lang'; -import { generateCurrentBaseImageUrl } from '../../utils/url'; +import ImagePreviewerLightbox from '../../../ImagePreviewerLightbox'; +import ImageThumbnailItem from '../../../ImageThumbnail'; +import DigitalSignUtils from '../../../DigitalSignEditor/utils'; +import { KeyCodes } from '../../../constants'; +import RowExpandAddBtn from '../../add-btn'; +import DigitalSignEditor from '../../../DigitalSignEditor'; +import { getLocale } from '../../../lang'; +import { generateCurrentBaseImageUrl } from '../../../utils/url'; import './index.css'; -class RowExpandDigitalSignEditor extends React.Component { +class RowExpandPCDigitalSignEditor extends React.Component { constructor(props) { super(props); @@ -187,7 +187,7 @@ class RowExpandDigitalSignEditor extends React.Component { } } -RowExpandDigitalSignEditor.propTypes = { +RowExpandPCDigitalSignEditor.propTypes = { onCommit: PropTypes.func, column: PropTypes.object, row: PropTypes.object, @@ -199,4 +199,4 @@ RowExpandDigitalSignEditor.propTypes = { onEditorClose: PropTypes.func, }; -export default RowExpandDigitalSignEditor; +export default RowExpandPCDigitalSignEditor; diff --git a/src/RowExpandEditor/duration-editor/index.js b/src/RowExpandEditor/duration-editor/index.js new file mode 100644 index 00000000..3eb91da1 --- /dev/null +++ b/src/RowExpandEditor/duration-editor/index.js @@ -0,0 +1,19 @@ +import React, { forwardRef } from 'react'; +import MediaQuery from 'react-responsive'; +import RowExpandPCDurationEditor from './pc-editor'; +import RowExpandMBDurationEditor from './mb-editor'; + +const RowExpandDurationEditor = forwardRef((props, ref) => { + return ( + <> + + + + + + + + ); +}); + +export default RowExpandDurationEditor; diff --git a/src/RowExpandEditor/duration-editor/mb-editor.js b/src/RowExpandEditor/duration-editor/mb-editor.js new file mode 100644 index 00000000..daf983bb --- /dev/null +++ b/src/RowExpandEditor/duration-editor/mb-editor.js @@ -0,0 +1,57 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { getDurationDisplayString } from 'dtable-utils'; +import DurationEditor from '../../DurationEditor'; + +class RowExpandMBDurationEditor extends React.Component { + + constructor(props) { + super(props); + let { row, column, valueKey } = props; + this.state = { + isShowEditor: false, + value: row[column[valueKey]], + }; + } + + UNSAFE_componentWillReceiveProps(nextProps) { + const { column, row, valueKey } = nextProps; + if (row._id !== this.props.row._id) { + this.setState({ value: row[column[valueKey]], isShowEditor: false }); + } + } + + toggleEditor = () => { + this.setState({ isShowEditor: !this.state.isShowEditor }); + }; + + onCommit = (value) => { + this.props.onCommit(value); + this.setState({ value, isShowEditor: false }); + }; + + render() { + const { column } = this.props; + const { value, isShowEditor } = this.state; + + return ( +
+ {isShowEditor ? ( + + ) : ( +
+ {getDurationDisplayString(value, column.data)} +
+ )} +
+ ); + } +} + +RowExpandMBDurationEditor.propTypes = { + column: PropTypes.object, + row: PropTypes.object, + onCommit: PropTypes.func, +}; + +export default RowExpandMBDurationEditor; diff --git a/src/RowExpandEditor/RowExpandDurationEditor/index.js b/src/RowExpandEditor/duration-editor/pc-editor.js similarity index 94% rename from src/RowExpandEditor/RowExpandDurationEditor/index.js rename to src/RowExpandEditor/duration-editor/pc-editor.js index 26bc7c91..605a286e 100644 --- a/src/RowExpandEditor/RowExpandDurationEditor/index.js +++ b/src/RowExpandEditor/duration-editor/pc-editor.js @@ -4,7 +4,7 @@ import { getDurationDisplayString } from 'dtable-utils'; import { ROW_EXPAND_FOCUS_STYLE, KeyCodes } from '../../constants'; import DurationEditor from '../../DurationEditor'; -class RowExpandDurationEditor extends React.Component { +class RowExpandPCDurationEditor extends React.Component { constructor(props) { super(props); @@ -87,7 +87,7 @@ class RowExpandDurationEditor extends React.Component { } } -RowExpandDurationEditor.propTypes = { +RowExpandPCDurationEditor.propTypes = { onCommit: PropTypes.func, column: PropTypes.object, row: PropTypes.object, @@ -96,4 +96,4 @@ RowExpandDurationEditor.propTypes = { updateTabIndex: PropTypes.func.isRequired, }; -export default RowExpandDurationEditor; +export default RowExpandPCDurationEditor; diff --git a/src/RowExpandEditor/email-editor/index.js b/src/RowExpandEditor/email-editor/index.js new file mode 100644 index 00000000..457cb3d9 --- /dev/null +++ b/src/RowExpandEditor/email-editor/index.js @@ -0,0 +1,19 @@ +import React, { forwardRef } from 'react'; +import MediaQuery from 'react-responsive'; +import RowExpandPCEmailEditor from './pc-editor'; +import RowExpandMBEmailEditor from './mb-editor'; + +const RowExpandEmailEditor = forwardRef((props, ref) => { + return ( + <> + + + + + + + + ); +}); + +export default RowExpandEmailEditor; diff --git a/src/RowExpandEditor/email-editor/mb-editor/index.css b/src/RowExpandEditor/email-editor/mb-editor/index.css new file mode 100644 index 00000000..56fbeb2f --- /dev/null +++ b/src/RowExpandEditor/email-editor/mb-editor/index.css @@ -0,0 +1 @@ +@import url('../../url-editor/mb-editor/index.css'); diff --git a/src/RowExpandEditor/email-editor/mb-editor/index.js b/src/RowExpandEditor/email-editor/mb-editor/index.js new file mode 100644 index 00000000..a7fdf892 --- /dev/null +++ b/src/RowExpandEditor/email-editor/mb-editor/index.js @@ -0,0 +1,76 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +import './index.css'; + +class RowExpandMBEmailEditor extends React.Component { + + constructor(props) { + super(props); + const { row, column, valueKey } = props; + this.state = { + value: row[column[valueKey]] || '', + }; + } + + UNSAFE_componentWillReceiveProps(nextProps) { + const { column, row, valueKey } = nextProps; + if (row._id !== this.props.row._id) { + this.setState({ value: row[column[valueKey]] || '' }); + } + } + + onBlur = () => { + const { onCommit } = this.props; + const updated = this.state.value.trim(); + onCommit(updated); + }; + + onChange = (e) => { + const value = e.target.value; + if (value === this.state.value) return; + this.setState({ value }); + }; + + onCut = (e) => { + e.stopPropagation(); + }; + + onPaste = (e) => { + e.stopPropagation(); + }; + + onOpenEmailLink = () => { + const { value } = this.state; + let newValue = value.trim(); + window.location.href = `mailto:${newValue}`; + }; + + render() { + const { value } = this.state; + return ( +
+ + {(value && value.trim()) && } +
+ ); + } +} + +RowExpandMBEmailEditor.propTypes = { + column: PropTypes.object, + row: PropTypes.object, + onCommit: PropTypes.func.isRequired, +}; + +export default RowExpandMBEmailEditor; diff --git a/src/RowExpandEditor/email-editor/pc-editor/index.css b/src/RowExpandEditor/email-editor/pc-editor/index.css new file mode 100644 index 00000000..c78e17b5 --- /dev/null +++ b/src/RowExpandEditor/email-editor/pc-editor/index.css @@ -0,0 +1 @@ +@import url('../../url-editor/pc-editor/index.css'); diff --git a/src/RowExpandEditor/RowExpandEmailEditor/index.js b/src/RowExpandEditor/email-editor/pc-editor/index.js similarity index 94% rename from src/RowExpandEditor/RowExpandEmailEditor/index.js rename to src/RowExpandEditor/email-editor/pc-editor/index.js index a1d0122e..9c330dd2 100644 --- a/src/RowExpandEditor/RowExpandEmailEditor/index.js +++ b/src/RowExpandEditor/email-editor/pc-editor/index.js @@ -1,10 +1,10 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { KeyCodes } from '../../constants'; +import { KeyCodes } from '../../../constants'; import './index.css'; -class RowExpandEmailEditor extends React.Component { +class RowExpandPCEmailEditor extends React.Component { constructor(props) { super(props); @@ -113,7 +113,7 @@ class RowExpandEmailEditor extends React.Component { } } -RowExpandEmailEditor.propTypes = { +RowExpandPCEmailEditor.propTypes = { onCommit: PropTypes.func.isRequired, column: PropTypes.object, row: PropTypes.object, @@ -122,4 +122,4 @@ RowExpandEmailEditor.propTypes = { updateTabIndex: PropTypes.func.isRequired, }; -export default RowExpandEmailEditor; +export default RowExpandPCEmailEditor; diff --git a/src/RowExpandEditor/file-editor/index.js b/src/RowExpandEditor/file-editor/index.js new file mode 100644 index 00000000..f2a28a85 --- /dev/null +++ b/src/RowExpandEditor/file-editor/index.js @@ -0,0 +1,19 @@ +import React, { forwardRef } from 'react'; +import MediaQuery from 'react-responsive'; +import RowExpandPCFileEditor from './pc-editor'; +import RowExpandMBFileEditor from './mb-editor'; + +const RowExpandFileEditor = forwardRef((props, ref) => { + return ( + <> + + + + + + + + ); +}); + +export default RowExpandFileEditor; diff --git a/src/RowExpandEditor/file-editor/mb-editor/index.js b/src/RowExpandEditor/file-editor/mb-editor/index.js new file mode 100644 index 00000000..12eab612 --- /dev/null +++ b/src/RowExpandEditor/file-editor/mb-editor/index.js @@ -0,0 +1,103 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import RowExpandAddBtn from '../../add-btn'; +import { getLocale } from '../../../lang'; +import ImageThumbnail from '../../../ImageThumbnail'; +import { getFileThumbnailInfo } from '../../../utils/url'; +import FileEditor from '../../../FileEditor'; + +class RowExpandMBFileEditor extends React.Component { + + constructor(props) { + super(props); + const { column, row, valueKey } = props; + this.state = { + isShowEditor: false, + value: row[column[valueKey]] || [], + }; + this.openEditorMode = null; + } + + UNSAFE_componentWillReceiveProps(nextProps) { + const { column, row, valueKey } = nextProps; + if (row._id !== this.props.row._id) { + this.setState({ value: row[column[valueKey]] || [], isShowEditor: false }); + } + } + + toggleEditor = () => { + this.setState({ isShowEditor: !this.state.isShowEditor }); + }; + + closeEditor = () => { + this.setState({ isShowEditor: false }); + }; + + onCommit = (value) => { + this.setState({ value }); + this.props.onCommit(value); + }; + + deleteFile = (index) => { + const { value } = this.state; + const newValue = value.slice(0); + newValue.splice(index, 1); + this.onCommit(newValue); + }; + + renderFiles = () => { + const { value } = this.state; + const { config } = this.props; + return value.map((file, index) => { + const { fileIconUrl } = getFileThumbnailInfo(file); + return ( + + ); + }); + }; + + render() { + const { column, config, uploadFile } = this.props; + const { isShowEditor, value } = this.state; + return ( + <> +
+ {value.length === 0 ? ( + + ) : ( + <>{this.renderFiles()} + )} +
+ {isShowEditor && ( + + )} + + ); + } +} + +RowExpandMBFileEditor.propTypes = { + column: PropTypes.object, + row: PropTypes.object, + onCommit: PropTypes.func, + uploadFile: PropTypes.func.isRequired, +}; + +export default RowExpandMBFileEditor; diff --git a/src/RowExpandEditor/file-editor/pc-editor/index.css b/src/RowExpandEditor/file-editor/pc-editor/index.css new file mode 100644 index 00000000..939101f5 --- /dev/null +++ b/src/RowExpandEditor/file-editor/pc-editor/index.css @@ -0,0 +1 @@ +@import url('../../image-editor/pc-editor/index.css'); diff --git a/src/RowExpandEditor/RowExpandFileEditor/index.js b/src/RowExpandEditor/file-editor/pc-editor/index.js similarity index 89% rename from src/RowExpandEditor/RowExpandFileEditor/index.js rename to src/RowExpandEditor/file-editor/pc-editor/index.js index f34f56c7..2e36df83 100644 --- a/src/RowExpandEditor/RowExpandFileEditor/index.js +++ b/src/RowExpandEditor/file-editor/pc-editor/index.js @@ -1,15 +1,15 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { KeyCodes } from '../../constants'; -import RowExpandAddBtn from '../RowExpandAddBtn'; -import { getLocale } from '../../lang'; -import ImageThumbnail from '../../ImageThumbnail'; -import { getFileThumbnailInfo } from '../../utils/url'; -import FileEditor from '../../FileEditor'; +import { KeyCodes } from '../../../constants'; +import RowExpandAddBtn from '../../add-btn'; +import { getLocale } from '../../../lang'; +import ImageThumbnail from '../../../ImageThumbnail'; +import { getFileThumbnailInfo } from '../../../utils/url'; +import FileEditor from '../../../FileEditor'; import './index.css'; -class RowExpandFileEditor extends React.Component { +class RowExpandPCFileEditor extends React.Component { constructor(props) { super(props); @@ -127,7 +127,7 @@ class RowExpandFileEditor extends React.Component { } } -RowExpandFileEditor.propTypes = { +RowExpandPCFileEditor.propTypes = { onCommit: PropTypes.func, column: PropTypes.object, row: PropTypes.object, @@ -138,4 +138,4 @@ RowExpandFileEditor.propTypes = { onEditorClose: PropTypes.func, }; -export default RowExpandFileEditor; +export default RowExpandPCFileEditor; diff --git a/src/RowExpandEditor/geolocation-editor/index.js b/src/RowExpandEditor/geolocation-editor/index.js new file mode 100644 index 00000000..838bd8f5 --- /dev/null +++ b/src/RowExpandEditor/geolocation-editor/index.js @@ -0,0 +1,19 @@ +import React, { forwardRef } from 'react'; +import MediaQuery from 'react-responsive'; +import RowExpandPCGeolocationEditor from './pc-editor'; +import RowExpandMBGeolocationEditor from './mb-editor'; + +const RowExpandGeolocationEditor = forwardRef((props, ref) => { + return ( + <> + + + + + + + + ); +}); + +export default RowExpandGeolocationEditor; diff --git a/src/RowExpandEditor/geolocation-editor/mb-editor/index.js b/src/RowExpandEditor/geolocation-editor/mb-editor/index.js new file mode 100644 index 00000000..ada01264 --- /dev/null +++ b/src/RowExpandEditor/geolocation-editor/mb-editor/index.js @@ -0,0 +1,84 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { isEmptyObject, getGeolocationDisplayString } from 'dtable-utils'; +import RowExpandAddBtn from '../../add-btn'; +import { getLocale } from '../../../lang'; +import GeolocationEditor from '../../../GeolocationEditor'; + +class RowExpandMBGeolocationEditor extends React.Component { + + constructor(props) { + super(props); + const { row, column, valueKey } = props; + this.state = { + isShowEditor: false, + value: row[column[valueKey]] || {} + }; + } + + UNSAFE_componentWillReceiveProps(nextProps) { + const { column, row, valueKey } = nextProps; + if (row._id !== this.props.row._id) { + this.setState({ + value: row[column[valueKey]] || {}, + isShowEditor: false + }); + } + } + + openEditor = () => { + this.setState({ isShowEditor: true }); + }; + + closeEditor = () => { + this.setState({ isShowEditor: false }); + }; + + onCommit = (value) => { + this.setState({ value }); + this.props.onCommit(value); + }; + + getLocationInfo = (value) => { + const { column, config } = this.props; + const { dtableBaiduMapKey, dtableMineMapKey } = config; + const isBaiduMap = !!(dtableBaiduMapKey || dtableMineMapKey); + return getGeolocationDisplayString(value, column.data, { isBaiduMap, hyphen: ' ' }); + }; + + renderGeoLocation = () => { + const { value } = this.state; + const text = isEmptyObject(value) ? getLocale('Edit_Location') : this.getLocationInfo(value); + return ( + + ); + }; + + render() { + const { column, row, config } = this.props; + const { value } = this.state; + return ( + <> + {this.renderGeoLocation()} + {this.state.isShowEditor && ( + + )} + + ); + } +} + +RowExpandMBGeolocationEditor.propTypes = { + column: PropTypes.object, + row: PropTypes.object, + onCommit: PropTypes.func, +}; + +export default RowExpandMBGeolocationEditor; diff --git a/src/RowExpandEditor/RowExpandGeolocationEditor/index.css b/src/RowExpandEditor/geolocation-editor/pc-editor/index.css similarity index 100% rename from src/RowExpandEditor/RowExpandGeolocationEditor/index.css rename to src/RowExpandEditor/geolocation-editor/pc-editor/index.css diff --git a/src/RowExpandEditor/RowExpandGeolocationEditor/index.js b/src/RowExpandEditor/geolocation-editor/pc-editor/index.js similarity index 86% rename from src/RowExpandEditor/RowExpandGeolocationEditor/index.js rename to src/RowExpandEditor/geolocation-editor/pc-editor/index.js index 422974cc..5ce4f9b0 100644 --- a/src/RowExpandEditor/RowExpandGeolocationEditor/index.js +++ b/src/RowExpandEditor/geolocation-editor/pc-editor/index.js @@ -1,14 +1,14 @@ import React from 'react'; import PropTypes from 'prop-types'; import { isEmptyObject, getGeolocationDisplayString } from 'dtable-utils'; -import { KeyCodes, ROW_EXPAND_BTN_FOCUS_STYLE } from '../../constants'; -import RowExpandAddBtn from '../RowExpandAddBtn'; -import { getLocale } from '../../lang'; -import GeolocationEditor from '../../GeolocationEditor'; +import { KeyCodes, ROW_EXPAND_BTN_FOCUS_STYLE } from '../../../constants'; +import RowExpandAddBtn from '../../add-btn'; +import { getLocale } from '../../../lang'; +import GeolocationEditor from '../../../GeolocationEditor'; import './index.css'; -class RowExpandGeolocationEditor extends React.Component { +class RowExpandPCGeolocationEditor extends React.Component { constructor(props) { super(props); @@ -64,7 +64,6 @@ class RowExpandGeolocationEditor extends React.Component { || !event.target || this.editorContainer.contains(event.target) ) return; - if (this.state.isShowEditor && this.geoEditor.getLargeEditorState()) return; this.setState({ isShowEditor: false }); }; @@ -123,14 +122,14 @@ class RowExpandGeolocationEditor extends React.Component { }; getLocationInfo = (value) => { - const { column } = this.props; - const { dtableBaiduMapKey, dtableMineMapKey } = window.dtable; + const { column, config } = this.props; + const { dtableBaiduMapKey, dtableMineMapKey } = { ...window?.dtable, ...config }; const isBaiduMap = !!(dtableBaiduMapKey || dtableMineMapKey); return getGeolocationDisplayString(value, column.data, { isBaiduMap, hyphen: ' ' }); }; render() { - const { column, row } = this.props; + const { column, config } = this.props; const { value } = this.state; return (
this.editorContainer = ref} className="mt-2"> @@ -140,10 +139,10 @@ class RowExpandGeolocationEditor extends React.Component { ref={ref => this.geoEditor = ref} isInModal={true} column={column} - row={row} value={value} + config={config} onCommit={this.onCommit} - onCommitCancel={this.toggleEditor} + onToggle={this.toggleEditor} onPressTab={this.props.onPressTab} /> )} @@ -152,9 +151,10 @@ class RowExpandGeolocationEditor extends React.Component { } } -RowExpandGeolocationEditor.propTypes = { +RowExpandPCGeolocationEditor.propTypes = { column: PropTypes.object, row: PropTypes.object, + config: PropTypes.object, onCommit: PropTypes.func, isEditorFocus: PropTypes.bool, columnIndex: PropTypes.number, @@ -163,4 +163,4 @@ RowExpandGeolocationEditor.propTypes = { onEditorClose: PropTypes.func, }; -export default RowExpandGeolocationEditor; +export default RowExpandPCGeolocationEditor; diff --git a/src/RowExpandEditor/image-editor/index.js b/src/RowExpandEditor/image-editor/index.js new file mode 100644 index 00000000..16bbceec --- /dev/null +++ b/src/RowExpandEditor/image-editor/index.js @@ -0,0 +1,19 @@ +import React, { forwardRef } from 'react'; +import MediaQuery from 'react-responsive'; +import RowExpandPCImageEditor from './pc-editor'; +import RowExpandMBImageEditor from './mb-editor'; + +const RowExpandImageEditor = forwardRef((props, ref) => { + return ( + <> + + + + + + + + ); +}); + +export default RowExpandImageEditor; diff --git a/src/RowExpandEditor/image-editor/mb-editor/index.css b/src/RowExpandEditor/image-editor/mb-editor/index.css new file mode 100644 index 00000000..48f7e2e1 --- /dev/null +++ b/src/RowExpandEditor/image-editor/mb-editor/index.css @@ -0,0 +1,30 @@ +.dtable-ui-mobile-row-expand-image { + margin: 0; + width: 100%; + display: flex; + flex-wrap: wrap; + justify-content: flex-start; + min-height: 50px; +} + +.dtable-ui-mobile-row-expand-image .dtable-ui-mobile-row-expand-add-btn { + height: 100%; + line-height: 50px; +} + +.dtable-ui-mobile-row-expand-image .dtable-ui-mobile-row-expand-img-item { + height: auto; + max-height: 150px; + width: calc(50% - 10px); + margin-left: 0px; + margin-right: 0px; +} + +.dtable-ui-mobile-row-expand-image .dtable-ui-mobile-row-expand-img-item img { + max-width: 100%; + max-height: 100%; +} + +.dtable-ui-mobile-row-expand-image .dtable-ui-mobile-row-expand-img-item:nth-child(even) { + margin-left: 20px; +} diff --git a/src/RowExpandEditor/image-editor/mb-editor/index.js b/src/RowExpandEditor/image-editor/mb-editor/index.js new file mode 100644 index 00000000..bda75512 --- /dev/null +++ b/src/RowExpandEditor/image-editor/mb-editor/index.js @@ -0,0 +1,93 @@ +import React from 'react'; +import RowExpandAddBtn from '../../add-btn'; +import { getLocale } from '../../../lang'; +import ImageThumbnail from '../../../ImageThumbnail'; +import ImageEditor from '../../../ImageEditor'; + +import './index.css'; + +class RowExpandMBImageEditor extends React.Component { + + constructor(props) { + super(props); + const { column, row, valueKey } = props; + this.state = { + isShowEditor: false, + value: row[column[valueKey]] || [], + }; + } + + UNSAFE_componentWillReceiveProps(nextProps) { + const { column, row, valueKey } = nextProps; + if (row._id !== this.props.row._id) { + this.setState({ value: row[column[valueKey]] || [], isShowEditor: false }); + } + } + + toggleEditor = () => { + this.setState({ isShowEditor: !this.state.isShowEditor }); + }; + + closeEditor = () => { + this.setState({ isShowEditor: false }); + }; + + onCommit = (value) => { + this.setState({ value: value }); + this.props.onCommit(value); + }; + + deleteImage = (index) => { + let value = this.state.value; + let updatedValue = value.slice(0); + updatedValue.splice(index, 1); + this.setState({ value: updatedValue }); + this.props.onCommit(updatedValue); + }; + + renderImages = () => { + const { value } = this.state; + const { config } = this.props; + return value.map((src, index) => { + return ( + + ); + }); + }; + + render() { + const { column, config, uploadFile } = this.props; + const { isShowEditor, value } = this.state; + return ( + <> +
+ {value.length === 0 ? ( + + ) : ( + <>{this.renderImages()} + )} +
+ {isShowEditor && ( + + )} + + ); + } +} + +export default RowExpandMBImageEditor; diff --git a/src/RowExpandEditor/RowExpandImageEditor/index.css b/src/RowExpandEditor/image-editor/pc-editor/index.css similarity index 100% rename from src/RowExpandEditor/RowExpandImageEditor/index.css rename to src/RowExpandEditor/image-editor/pc-editor/index.css diff --git a/src/RowExpandEditor/RowExpandImageEditor/index.js b/src/RowExpandEditor/image-editor/pc-editor/index.js similarity index 91% rename from src/RowExpandEditor/RowExpandImageEditor/index.js rename to src/RowExpandEditor/image-editor/pc-editor/index.js index 10617740..27b30537 100644 --- a/src/RowExpandEditor/RowExpandImageEditor/index.js +++ b/src/RowExpandEditor/image-editor/pc-editor/index.js @@ -1,14 +1,14 @@ import React from 'react'; -import { KeyCodes } from '../../constants'; -import RowExpandAddBtn from '../RowExpandAddBtn'; -import { getLocale } from '../../lang'; -import ImagePreviewerLightbox from '../../ImagePreviewerLightbox'; -import ImageThumbnail from '../../ImageThumbnail'; -import ImageEditor from '../../ImageEditor'; +import { KeyCodes } from '../../../constants'; +import RowExpandAddBtn from '../../add-btn'; +import { getLocale } from '../../../lang'; +import ImagePreviewerLightbox from '../../../ImagePreviewerLightbox'; +import ImageThumbnail from '../../../ImageThumbnail'; +import ImageEditor from '../../../ImageEditor'; import './index.css'; -class RowExpandImageEditor extends React.Component { +class RowExpandPCImageEditor extends React.Component { constructor(props) { super(props); @@ -166,4 +166,4 @@ class RowExpandImageEditor extends React.Component { } } -export default RowExpandImageEditor; +export default RowExpandPCImageEditor; diff --git a/src/RowExpandEditor/long-text-editor/index.js b/src/RowExpandEditor/long-text-editor/index.js new file mode 100644 index 00000000..97c50f70 --- /dev/null +++ b/src/RowExpandEditor/long-text-editor/index.js @@ -0,0 +1,19 @@ +import React, { forwardRef } from 'react'; +import MediaQuery from 'react-responsive'; +import RowExpandPCLongTextEditor from './pc-editor'; +import RowExpandMBLongTextEditor from './mb-editor'; + +const RowExpandLongTextEditor = forwardRef((props, ref) => { + return ( + <> + + + + + + + + ); +}); + +export default RowExpandLongTextEditor; diff --git a/src/RowExpandEditor/long-text-editor/mb-editor/editor/index.css b/src/RowExpandEditor/long-text-editor/mb-editor/editor/index.css new file mode 100644 index 00000000..6d80f4ae --- /dev/null +++ b/src/RowExpandEditor/long-text-editor/mb-editor/editor/index.css @@ -0,0 +1,10 @@ +.dtable-ui-mobile-long-text-editor .dtable-ui-mobile-long-text-editor-container { + margin-top: 20px; + border-top: 1px solid #e9e9e9; +} + +.dtable-ui-mobile-long-text-editor .dtable-ui-mobile-long-text-editor-container .am-textarea-item { + padding: 0 1rem; + border-bottom: 1px solid #e9e9e9; + height: unset; +} diff --git a/src/RowExpandEditor/long-text-editor/mb-editor/editor/index.js b/src/RowExpandEditor/long-text-editor/mb-editor/editor/index.js new file mode 100644 index 00000000..3a0d42c3 --- /dev/null +++ b/src/RowExpandEditor/long-text-editor/mb-editor/editor/index.js @@ -0,0 +1,73 @@ +import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import PropTypes from 'prop-types'; +import { TextareaItem } from 'antd-mobile'; +import { getPreviewContent } from '@seafile/seafile-editor'; +import MobileFullScreenPage from '../../../../MobileFullScreenPage'; +import { getLocale } from '../../../../lang'; + +import './index.css'; + +const Editor = ({ value: oldValue, title, onToggle, onChange }) => { + const [value, setValue] = useState(oldValue || ''); + const hasUnSaved = useRef(false); + const timer = useRef(null); + + const rowCounts = useMemo(() => document.body.offsetWidth < 768 ? 10 : 20, []); + + const handleTextChange = useCallback((newValue) => { + if (value === newValue) return; + setValue(newValue); + hasUnSaved.current = true; + }, [value]); + + const onSave = useCallback(() => { + if (!hasUnSaved.current) return; + const { previewText, images, links, checklist } = getPreviewContent(value); + const newValue = Object.assign({}, { text: value, preview: previewText, images: images, links: links, checklist }); + onChange && onChange(newValue); + hasUnSaved.current = false; + }, [value, onChange]); + + const handleSave = useCallback(() => { + onSave(); + onToggle(); + }, [onSave, onToggle]); + + useEffect(() => { + timer.current = setInterval(() => { + onSave(); + }, 60000); + return () => { + onSave(); + clearInterval(timer.current); + timer.current = null; + }; + }, [onSave]); + + return ( + + <>{getLocale('Cancel')} + <>{title} + {getLocale('Submit')} + <> +
+ +
+ +
+ ); +}; + +Editor.propTypes = { + value: PropTypes.string, + title: PropTypes.any, + onToggle: PropTypes.func, + onChange: PropTypes.func, +}; + +export default Editor; diff --git a/src/RowExpandEditor/long-text-editor/mb-editor/index.css b/src/RowExpandEditor/long-text-editor/mb-editor/index.css new file mode 100644 index 00000000..b0ccd2b4 --- /dev/null +++ b/src/RowExpandEditor/long-text-editor/mb-editor/index.css @@ -0,0 +1,3 @@ +.dtable-ui-mobile-long-text-formatter { + min-height: 38px; +} diff --git a/src/RowExpandEditor/long-text-editor/mb-editor/index.js b/src/RowExpandEditor/long-text-editor/mb-editor/index.js new file mode 100644 index 00000000..e16791b7 --- /dev/null +++ b/src/RowExpandEditor/long-text-editor/mb-editor/index.js @@ -0,0 +1,85 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { processor } from '@seafile/seafile-editor'; +import { getLocale } from '../../../lang'; +import RowExpandAddBtn from '../../add-btn'; +import Editor from './editor'; + +import './index.css'; + +class RowExpandMBLongTextEditor extends React.Component { + + constructor(props) { + super(props); + this.state = { + innerHtml: null, + isShowEditor: false, + }; + } + + componentDidMount() { + this.convertMarkdown(this.props.value); + } + + UNSAFE_componentWillReceiveProps(nextProps) { + const { column, row, valueKey } = nextProps; + const { column: currentColumn, row: currentRow } = this.props; + const value = row[column[valueKey]]; + const currentValue = currentRow[currentColumn[valueKey]]; + if (value !== currentValue && !this.editorRef?.current?.enableEdit) { + this.convertMarkdown(value); + } + } + + convertMarkdown = (value) => { + const newValue = value && typeof value === 'object' ? value.text : value; + processor.process(newValue).then((result) => { + const innerHtml = String(result).replace(/ { + this.convertMarkdown(value.text); + this.props.onCommit(value); + }; + + openEditor = () => { + this.setState({ isShowEditor: true }); + }; + + closeEditor = () => { + this.setState({ isShowEditor: false }); + }; + + render() { + const { column } = this.props; + const { innerHtml, isShowEditor } = this.state; + return ( + <> + {innerHtml ? ( +
+
+ ) : ( + + )} + {isShowEditor && ( + + )} + + ); + } +} + +RowExpandMBLongTextEditor.propTypes = { + onCommit: PropTypes.func.isRequired, + column: PropTypes.object.isRequired, + row: PropTypes.object.isRequired, + readOnly: PropTypes.bool, +}; + +export default RowExpandMBLongTextEditor; diff --git a/src/RowExpandEditor/RowExpandLongTextEditor/index.css b/src/RowExpandEditor/long-text-editor/pc-editor/index.css similarity index 100% rename from src/RowExpandEditor/RowExpandLongTextEditor/index.css rename to src/RowExpandEditor/long-text-editor/pc-editor/index.css diff --git a/src/RowExpandEditor/RowExpandLongTextEditor/index.js b/src/RowExpandEditor/long-text-editor/pc-editor/index.js similarity index 87% rename from src/RowExpandEditor/RowExpandLongTextEditor/index.js rename to src/RowExpandEditor/long-text-editor/pc-editor/index.js index d27ba7da..8974dd0b 100644 --- a/src/RowExpandEditor/RowExpandLongTextEditor/index.js +++ b/src/RowExpandEditor/long-text-editor/pc-editor/index.js @@ -1,16 +1,16 @@ import React, { createRef, Suspense } from 'react'; import PropTypes from 'prop-types'; -import toaster from '../../toaster'; -import Loading from '../../Loading'; import { I18nextProvider } from 'react-i18next'; import { LongTextInlineEditor } from '@seafile/seafile-editor'; -import { LONG_TEXT_EXCEED_LIMIT_MESSAGE } from '../../constants'; -import { isLongTextValueExceedLimit } from '../../utils/cell'; -import { getLocale, LANGUAGE } from '../../lang'; +import toaster from '../../../toaster'; +import Loading from '../../../Loading'; +import { LONG_TEXT_EXCEED_LIMIT_MESSAGE } from '../../../constants'; +import { isLongTextValueExceedLimit } from '../../../utils/cell'; +import { getLocale, LANGUAGE } from '../../../lang'; import './index.css'; -class RowExpandLongTextEditor extends React.Component { +class RowExpandPCLongTextEditor extends React.Component { constructor(props) { super(props); @@ -103,7 +103,7 @@ class RowExpandLongTextEditor extends React.Component { } } -RowExpandLongTextEditor.propTypes = { +RowExpandPCLongTextEditor.propTypes = { onCommit: PropTypes.func, column: PropTypes.object, row: PropTypes.object, @@ -112,4 +112,4 @@ RowExpandLongTextEditor.propTypes = { updateTabIndex: PropTypes.func, }; -export default RowExpandLongTextEditor; +export default RowExpandPCLongTextEditor; diff --git a/src/RowExpandEditor/multiple-select-editor/index.js b/src/RowExpandEditor/multiple-select-editor/index.js new file mode 100644 index 00000000..ea2d5651 --- /dev/null +++ b/src/RowExpandEditor/multiple-select-editor/index.js @@ -0,0 +1,19 @@ +import React, { forwardRef } from 'react'; +import MediaQuery from 'react-responsive'; +import RowExpandPCMultipleSelectEditor from './pc-editor'; +import RowExpandMBMultipleSelectEditor from './mb-editor'; + +const RowExpandMultipleSelectEditor = forwardRef((props, ref) => { + return ( + <> + + + + + + + + ); +}); + +export default RowExpandMultipleSelectEditor; diff --git a/src/RowExpandEditor/multiple-select-editor/mb-editor/index.js b/src/RowExpandEditor/multiple-select-editor/mb-editor/index.js new file mode 100644 index 00000000..ca1e92d2 --- /dev/null +++ b/src/RowExpandEditor/multiple-select-editor/mb-editor/index.js @@ -0,0 +1,129 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { getColumnOptions } from 'dtable-utils'; +import MultipleSelectEditor from '../../../MultipleSelectEditor'; +import SelectItem from '../../../SelectItem'; +import { getLocale } from '../../../lang'; +import RightAngle from '../../right-angle'; +import ObjectUtils from '../../../utils/object-utils'; +import { DELETED_OPTION_BACKGROUND_COLOR, DELETED_OPTION_TIPS } from '../../../constants'; +import RowExpandAddBtn from '../../add-btn'; + +class RowExpandMBMultipleSelectEditor extends React.Component { + + constructor(props) { + super(props); + this.state = { + value: props.value || [], + isShowEditor: false, + }; + this.key = props.valueKey === 'name' ? 'name' : 'id'; + this.options = this.getOptions(props); + } + + UNSAFE_componentWillReceiveProps(nextProps) { + const { value, column } = nextProps; + if (value !== this.props.value || !ObjectUtils.isSameObject(column, this.props.column)) { + this.options = this.getOptions(nextProps); + this.setState({ value, isShowEditor: false }); + } + } + + getOptions = (props) => { + const { column } = props; + return getColumnOptions(column); + }; + + toggleEditor = (value) => { + this.setState({ showEditor: value }); + }; + + openEditor = (event) => { + event.stopPropagation(); + this.toggleEditor(true); + }; + + closeEditor = () => { + this.toggleEditor(false); + }; + + onChange = (option) => { + const optionKey = option[this.key]; + let newValue = this.state.value.slice(0); + const optionKeyIndex = newValue.findIndex(o => o === optionKey); + if (optionKeyIndex === -1) { + newValue.push(optionKey); + } else { + newValue.splice(optionKeyIndex, 1); + } + this.setState({ value: newValue }); + this.props.onCommit(newValue); + }; + + removeOption = (option, event) => { + event.stopPropagation(); + event && event.nativeEvent.stopImmediatePropagation(); + const optionKey = option[this.key]; + let newValue = this.state.value.slice(0); + const optionKeyIndex = newValue.findIndex(o => o === optionKey); + if (optionKeyIndex !== -1) { + newValue.splice(optionKeyIndex, 1); + } + this.setState({ value: newValue }); + this.props.onCommit(newValue); + }; + + renderOptions = () => { + const { value } = this.state; + let displayOptions = []; + if (Array.isArray(value)) { + const selectedOptions = this.options.filter((option) => value.includes(option[this.key])); + const selectedOptionIds = selectedOptions.map(option => option[this.key]); + const invalidOptionIds = value.filter(optionId => !selectedOptionIds.includes(optionId)); + displayOptions = selectedOptions.map((option, optionIdx) => { + return (); + }).concat(invalidOptionIds.map((optionId, index) => { + const option = { name: getLocale(DELETED_OPTION_TIPS), color: DELETED_OPTION_BACKGROUND_COLOR }; + return (); + })); + } + if (displayOptions.length === 0) return (); + return (
{displayOptions}
); + }; + + render() { + const { column, isSupportNewOption, onAddNewOption } = this.props; + const { showEditor, value } = this.state; + return ( + <> +
+ {this.renderOptions()} + +
+ {showEditor && ( + + )} + + ); + } +} + +RowExpandMBMultipleSelectEditor.propTypes = { + column: PropTypes.object, + value: PropTypes.array, + valueKey: PropTypes.string, + isSupportNewOption: PropTypes.bool, + onAddNewOption: PropTypes.func, + onCommit: PropTypes.func, +}; + +export default RowExpandMBMultipleSelectEditor; diff --git a/src/RowExpandEditor/RowExpandMultipleSelectEditor/index.css b/src/RowExpandEditor/multiple-select-editor/pc-editor/index.css similarity index 94% rename from src/RowExpandEditor/RowExpandMultipleSelectEditor/index.css rename to src/RowExpandEditor/multiple-select-editor/pc-editor/index.css index 80a9328e..798304c4 100644 --- a/src/RowExpandEditor/RowExpandMultipleSelectEditor/index.css +++ b/src/RowExpandEditor/multiple-select-editor/pc-editor/index.css @@ -1,4 +1,4 @@ -@import url('../RowExpandSingleSelectorEditor/index.css'); +@import url('../../single-select-editor/pc-editor/index.css'); .dtable-ui-row-expand-select-options { min-height: 30px; diff --git a/src/RowExpandEditor/RowExpandMultipleSelectEditor/index.js b/src/RowExpandEditor/multiple-select-editor/pc-editor/index.js similarity index 95% rename from src/RowExpandEditor/RowExpandMultipleSelectEditor/index.js rename to src/RowExpandEditor/multiple-select-editor/pc-editor/index.js index 091b1e99..c218badd 100644 --- a/src/RowExpandEditor/RowExpandMultipleSelectEditor/index.js +++ b/src/RowExpandEditor/multiple-select-editor/pc-editor/index.js @@ -2,14 +2,14 @@ import React from 'react'; import PropTypes from 'prop-types'; import { getColumnOptions } from 'dtable-utils'; import classnames from 'classnames'; -import { KeyCodes, DELETED_OPTION_BACKGROUND_COLOR, DELETED_OPTION_TIPS } from '../../constants'; -import MultipleSelectEditor from '../../MultipleSelectEditor'; -import { getLocale } from '../../lang'; -import ObjectUtils from '../../utils/object-utils'; +import { KeyCodes, DELETED_OPTION_BACKGROUND_COLOR, DELETED_OPTION_TIPS } from '../../../constants'; +import MultipleSelectEditor from '../../../MultipleSelectEditor'; +import { getLocale } from '../../../lang'; +import ObjectUtils from '../../../utils/object-utils'; import './index.css'; -class RowExpandMultipleSelectEditor extends React.Component { +class RowExpandPCMultipleSelectEditor extends React.Component { constructor(props) { super(props); @@ -206,7 +206,7 @@ class RowExpandMultipleSelectEditor extends React.Component { } -RowExpandMultipleSelectEditor.propTypes = { +RowExpandPCMultipleSelectEditor.propTypes = { column: PropTypes.object, value: PropTypes.array, valueKey: PropTypes.string, @@ -221,4 +221,4 @@ RowExpandMultipleSelectEditor.propTypes = { onEditorClose: PropTypes.func, }; -export default RowExpandMultipleSelectEditor; +export default RowExpandPCMultipleSelectEditor; diff --git a/src/RowExpandEditor/number-editor/index.js b/src/RowExpandEditor/number-editor/index.js new file mode 100644 index 00000000..5855bfb9 --- /dev/null +++ b/src/RowExpandEditor/number-editor/index.js @@ -0,0 +1,19 @@ +import React, { forwardRef } from 'react'; +import MediaQuery from 'react-responsive'; +import RowExpandPCNumberEditor from './pc-editor'; +import RowExpandMBNumberEditor from './mb-editor'; + +const RowExpandNumberEditor = forwardRef((props, ref) => { + return ( + <> + + + + + + + + ); +}); + +export default RowExpandNumberEditor; diff --git a/src/RowExpandEditor/number-editor/mb-editor/index.css b/src/RowExpandEditor/number-editor/mb-editor/index.css new file mode 100644 index 00000000..fce078da --- /dev/null +++ b/src/RowExpandEditor/number-editor/mb-editor/index.css @@ -0,0 +1,3 @@ +.dtable-ui-mobile-row-expand-number-editor-container .form-control { + width: 100% !important; +} diff --git a/src/RowExpandEditor/number-editor/mb-editor/index.js b/src/RowExpandEditor/number-editor/mb-editor/index.js new file mode 100644 index 00000000..6240c6fc --- /dev/null +++ b/src/RowExpandEditor/number-editor/mb-editor/index.js @@ -0,0 +1,67 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { getNumberDisplayString, isNumber } from 'dtable-utils'; +import NumberEditor from '../../../NumberEditor'; + +import './index.css'; + +class RowExpandMBNumberEditor extends React.Component { + + constructor(props) { + super(props); + const { column, row } = props; + this.state = { + isShowEditor: false, + value: row[column.key], + }; + } + + toggleEditor = () => { + if (this.props.readOnly) return; + this.setState({ isShowEditor: !this.state.isShowEditor }); + }; + + onCommit = (value) => { + console.log(value); + this.props.onCommit(value); + this.setState({ value }, () => { + this.setState({ isShowEditor: false }); + }); + }; + + onBlur = () => { + this.setState({ isShowEditor: false }); + }; + + render() { + const { column } = this.props; + const { value, isShowEditor } = this.state; + let formatValue = ''; + if (!isShowEditor && isNumber(value)) { + const { data = {} } = column; + formatValue = getNumberDisplayString(value, data); + } + const dom = isShowEditor ? ( + + ) : ( +
+ {formatValue} +
+ ); + + return ( +
+ {dom} +
+ ); + } +} + +RowExpandMBNumberEditor.propTypes = { + readOnly: PropTypes.bool, + column: PropTypes.object, + row: PropTypes.object, + onCommit: PropTypes.func, +}; + +export default RowExpandMBNumberEditor; diff --git a/src/RowExpandEditor/RowExpandNumberEditor/index.js b/src/RowExpandEditor/number-editor/pc-editor.js similarity index 94% rename from src/RowExpandEditor/RowExpandNumberEditor/index.js rename to src/RowExpandEditor/number-editor/pc-editor.js index 04c338e2..48289a51 100644 --- a/src/RowExpandEditor/RowExpandNumberEditor/index.js +++ b/src/RowExpandEditor/number-editor/pc-editor.js @@ -3,7 +3,7 @@ import PropTypes from 'prop-types'; import { getNumberDisplayString, isNumber } from 'dtable-utils'; import NumberEditor from '../../NumberEditor'; -class RowExpandNumberEditor extends React.Component { +class RowExpandPCNumberEditor extends React.Component { constructor(props) { super(props); @@ -82,7 +82,7 @@ class RowExpandNumberEditor extends React.Component { } } -RowExpandNumberEditor.propTypes = { +RowExpandPCNumberEditor.propTypes = { onCommit: PropTypes.func, column: PropTypes.object, row: PropTypes.object, @@ -92,4 +92,4 @@ RowExpandNumberEditor.propTypes = { onEditorClose: PropTypes.func, }; -export default RowExpandNumberEditor; +export default RowExpandPCNumberEditor; diff --git a/src/RowExpandEditor/rate-editor/index.css b/src/RowExpandEditor/rate-editor/index.css new file mode 100644 index 00000000..e45afcb6 --- /dev/null +++ b/src/RowExpandEditor/rate-editor/index.css @@ -0,0 +1,34 @@ +.dtable-ui.dtable-ui-row-expand-rate-editor { + display: flex; +} + +.dtable-ui.dtable-ui-row-expand-rate-editor > div { + padding-right: 5px; + cursor: pointer; + line-height: 1.5; +} + +.dtable-ui.dtable-ui-row-expand-rate-editor .rate-item-active { + opacity: 1 !important; +} + +.dtable-ui-mobile-row-expand-rate-editor { + min-height: 50px; + align-items: center; + flex-wrap: wrap; + padding: 10px 0; +} + +.dtable-ui-mobile-row-expand-rate-editor .dtable-ui-mobile-row-expand-rate-item { + height: 24px; + width: 24px; + margin-top: 3px; + margin-bottom: 3px; + margin-right: 0.25rem; + padding-right: 0 !important; +} + +.dtable-ui-mobile-row-expand-rate-editor .dtable-ui-mobile-row-expand-rate-item .dtable-font { + font-size: 24px; + line-height: 1; +} diff --git a/src/RowExpandEditor/rate-editor/index.js b/src/RowExpandEditor/rate-editor/index.js new file mode 100644 index 00000000..97b0bf56 --- /dev/null +++ b/src/RowExpandEditor/rate-editor/index.js @@ -0,0 +1,21 @@ +import React, { forwardRef } from 'react'; +import MediaQuery from 'react-responsive'; +import RowExpandPCRateEditor from './pc-editor'; +import RowExpandMBRateEditor from './mb-editor'; + +import './index.css'; + +const RowExpandRateEditor = forwardRef((props, ref) => { + return ( + <> + + + + + + + + ); +}); + +export default RowExpandRateEditor; diff --git a/src/RowExpandEditor/rate-editor/mb-editor.js b/src/RowExpandEditor/rate-editor/mb-editor.js new file mode 100644 index 00000000..4147cb83 --- /dev/null +++ b/src/RowExpandEditor/rate-editor/mb-editor.js @@ -0,0 +1,78 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import classnames from 'classnames'; + +class RowExpandMBRateEditor extends React.Component { + + constructor(props) { + super(props); + this.state = { + value: props.value || '', + }; + let { column } = this.props; + const { editable } = column; + this.canEdit = editable; + } + + UNSAFE_componentWillReceiveProps(nextProps) { + const { column, row, valueKey } = nextProps; + if (row._id !== this.props.row._id) { + this.setState({ value: row[column[valueKey]] || '' }); + } else if (nextProps.value !== this.props.value) { + this.setState({ value: nextProps.value || '' }); + } + } + + onChangeRateNumber = (index) => { + if (!this.canEdit) return; + const { value } = this.state; + const newValue = value === index ? '' : index; + this.setState({ value: newValue }); + this.props.onCommit(newValue); + }; + + getRateMaxStar = () => { + const { value } = this.state; + const { column } = this.props; + const { rate_max_number, rate_style_color, rate_style_type } = column.data || {}; + const rateShowType = rate_style_type ? rate_style_type : 'dtable-icon-rate'; + let rateList = []; + for (let i = 0; i < rate_max_number; i++) { + const rateItemIndex = i + 1; + const style = { + color: value >= rateItemIndex ? rate_style_color : '#e5e5e5', + opacity: 0.4 + }; + const rateItem = ( +
= rateItemIndex })} + > + +
+ ); + rateList.push(rateItem); + } + return rateList; + }; + + render() { + const rateList = this.getRateMaxStar(); + return ( +
+ {rateList} +
+ ); + } +} + +RowExpandMBRateEditor.propTypes = { + column: PropTypes.object, + row: PropTypes.object, + value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), + onCommit: PropTypes.func.isRequired, +}; + +export default RowExpandMBRateEditor; diff --git a/src/RowExpandEditor/RowExpandRateEditor/index.js b/src/RowExpandEditor/rate-editor/pc-editor.js similarity index 96% rename from src/RowExpandEditor/RowExpandRateEditor/index.js rename to src/RowExpandEditor/rate-editor/pc-editor.js index 5056c029..ace103e6 100644 --- a/src/RowExpandEditor/RowExpandRateEditor/index.js +++ b/src/RowExpandEditor/rate-editor/pc-editor.js @@ -3,9 +3,7 @@ import PropTypes from 'prop-types'; import classnames from 'classnames'; import { getLocale } from '../../lang'; -import './index.css'; - -class RowExpandRateEditor extends React.Component { +class RowExpandPCRateEditor extends React.Component { constructor(props) { super(props); @@ -119,7 +117,7 @@ class RowExpandRateEditor extends React.Component { } } -RowExpandRateEditor.propTypes = { +RowExpandPCRateEditor.propTypes = { column: PropTypes.object, row: PropTypes.object, value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), @@ -129,4 +127,4 @@ RowExpandRateEditor.propTypes = { updateTabIndex: PropTypes.func.isRequired, }; -export default RowExpandRateEditor; +export default RowExpandPCRateEditor; diff --git a/src/RowExpandEditor/right-angle/index.css b/src/RowExpandEditor/right-angle/index.css new file mode 100644 index 00000000..b7312895 --- /dev/null +++ b/src/RowExpandEditor/right-angle/index.css @@ -0,0 +1,6 @@ +.dtable-ui-mobile-row-expand-angle-right { + color: #999; + position: absolute; + right: 0; + top: calc(50% - 12px); +} diff --git a/src/RowExpandEditor/right-angle/index.js b/src/RowExpandEditor/right-angle/index.js new file mode 100644 index 00000000..b511176a --- /dev/null +++ b/src/RowExpandEditor/right-angle/index.js @@ -0,0 +1,11 @@ +import React from 'react'; + +import './index.css'; + +export default function RightAngle() { + return ( + + + + ); +} diff --git a/src/RowExpandEditor/single-select-editor/index.js b/src/RowExpandEditor/single-select-editor/index.js new file mode 100644 index 00000000..f36c209e --- /dev/null +++ b/src/RowExpandEditor/single-select-editor/index.js @@ -0,0 +1,19 @@ +import React, { forwardRef } from 'react'; +import MediaQuery from 'react-responsive'; +import RowExpandPCSingleSelectEditor from './pc-editor'; +import RowExpandMBSingleSelectEditor from './mb-editor'; + +const RowExpandSingleSelectEditor = forwardRef((props, ref) => { + return ( + <> + + + + + + + + ); +}); + +export default RowExpandSingleSelectEditor; diff --git a/src/RowExpandEditor/single-select-editor/mb-editor/index.css b/src/RowExpandEditor/single-select-editor/mb-editor/index.css new file mode 100644 index 00000000..a22e3b94 --- /dev/null +++ b/src/RowExpandEditor/single-select-editor/mb-editor/index.css @@ -0,0 +1,8 @@ +.dtable-ui-mobile-row-expand-options-editor { + min-height: 50px; +} + +.dtable-ui-mobile-row-expand-options-editor .dtable-ui-mobile-row-expand-add-btn { + height: 100%; + line-height: 50px; +} diff --git a/src/RowExpandEditor/single-select-editor/mb-editor/index.js b/src/RowExpandEditor/single-select-editor/mb-editor/index.js new file mode 100644 index 00000000..8c86f5f5 --- /dev/null +++ b/src/RowExpandEditor/single-select-editor/mb-editor/index.js @@ -0,0 +1,126 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { getColumnOptions } from 'dtable-utils'; +import SingleSelectEditor from '../../../SingleSelectEditor'; +import SelectItem from '../../../SelectItem'; +import { getLocale } from '../../../lang'; +import RightAngle from '../../right-angle'; +import ObjectUtils from '../../../utils/object-utils'; +import { DELETED_OPTION_BACKGROUND_COLOR, DELETED_OPTION_TIPS } from '../../../constants'; +import RowExpandAddBtn from '../../add-btn'; + +import './index.css'; + +class RowExpandMBSingleSelectEditor extends React.Component { + + constructor(props) { + super(props); + this.state = { + value: props.value, + isShowEditor: false, + }; + this.key = props.valueKey === 'name' ? 'name' : 'id'; + this.options = this.getOptions(props); + } + + UNSAFE_componentWillReceiveProps(nextProps) { + const { value, column } = nextProps; + if (value !== this.props.value || !ObjectUtils.isSameObject(column, this.props.column)) { + this.options = this.getOptions(nextProps); + this.setState({ value, isShowEditor: false }); + } + } + + getOptions = (props) => { + const { column, row, columns } = props; + const options = getColumnOptions(column); + const { data } = column; + const { cascade_column_key, cascade_settings } = data || {}; + if (cascade_column_key && Array.isArray(columns)) { + const cascadeColumn = columns.find(c => c.key === cascade_column_key); + if (cascadeColumn) { + const cascadeColumnValue = row[cascade_column_key]; + if (!cascadeColumnValue) return []; + const cascadeSetting = cascade_settings[cascadeColumnValue]; + if (!cascadeSetting || !Array.isArray(cascadeSetting) || cascadeSetting.length === 0) return []; + return options.filter(option => cascadeSetting.includes(option.id)); + } + } + return options; + }; + + toggleEditor = (value) => { + this.setState({ showEditor: value }); + }; + + openEditor = (event) => { + event.stopPropagation(); + this.toggleEditor(true); + }; + + closeEditor = () => { + this.toggleEditor(false); + }; + + onChange = (option) => { + let newValue = option[this.key]; + if (this.state.value === newValue) newValue = null; + this.setState({ value: newValue }); + this.props.onCommit(newValue); + this.toggleEditor(false); + }; + + renderOption = () => { + const { value } = this.state; + let option = this.options.find(o => o[this.key] === value); + option = { + name: getLocale(DELETED_OPTION_TIPS), + color: DELETED_OPTION_BACKGROUND_COLOR, + ...option, + }; + + return ( + <> + {value ? ( +
+ ) : ()} + + ); + }; + + render() { + const { column, isSupportNewOption, onAddNewOption } = this.props; + const { showEditor, value } = this.state; + return ( + <> +
+ {this.renderOption()} + +
+ {showEditor && ( + + )} + + ); + } +} + +RowExpandMBSingleSelectEditor.propTypes = { + column: PropTypes.object, + value: PropTypes.array, + valueKey: PropTypes.string, + isSupportNewOption: PropTypes.bool, + onAddNewOption: PropTypes.func, + onCommit: PropTypes.func, +}; + +export default RowExpandMBSingleSelectEditor; diff --git a/src/RowExpandEditor/RowExpandSingleSelectorEditor/index.css b/src/RowExpandEditor/single-select-editor/pc-editor/index.css similarity index 100% rename from src/RowExpandEditor/RowExpandSingleSelectorEditor/index.css rename to src/RowExpandEditor/single-select-editor/pc-editor/index.css diff --git a/src/RowExpandEditor/RowExpandSingleSelectorEditor/index.js b/src/RowExpandEditor/single-select-editor/pc-editor/index.js similarity index 85% rename from src/RowExpandEditor/RowExpandSingleSelectorEditor/index.js rename to src/RowExpandEditor/single-select-editor/pc-editor/index.js index f18a5df3..a4bb8fab 100644 --- a/src/RowExpandEditor/RowExpandSingleSelectorEditor/index.js +++ b/src/RowExpandEditor/single-select-editor/pc-editor/index.js @@ -1,21 +1,21 @@ import React from 'react'; import PropTypes from 'prop-types'; import { getColumnOptions } from 'dtable-utils'; -import { KeyCodes, DELETED_OPTION_BACKGROUND_COLOR, DELETED_OPTION_TIPS } from '../../constants'; import classnames from 'classnames'; -import SingleSelectEditor from '../../SingleSelectEditor'; -import { getLocale } from '../../lang'; -import ObjectUtils from '../../utils/object-utils'; +import { KeyCodes, DELETED_OPTION_BACKGROUND_COLOR, DELETED_OPTION_TIPS } from '../../../constants'; +import SingleSelectEditor from '../../../SingleSelectEditor'; +import { getLocale } from '../../../lang'; +import ObjectUtils from '../../../utils/object-utils'; import './index.css'; -class RowExpandSingleSelectEditor extends React.Component { +class RowExpandPCSingleSelectEditor extends React.Component { constructor(props) { super(props); this.state = { value: props.value, - showSelectPopover: false, + isShowEditor: false, }; this.key = props.valueKey === 'name' ? 'name' : 'id'; this.options = this.getOptions(props); @@ -32,16 +32,16 @@ class RowExpandSingleSelectEditor extends React.Component { const { value, column } = nextProps; if (value !== this.props.value || !ObjectUtils.isSameObject(column, this.props.column)) { this.options = this.getOptions(nextProps); - this.setState({ value, showSelectPopover: false }); + this.setState({ value, isShowEditor: false }); } } componentDidUpdate(prevProps, prevState) { - if (this.state.showSelectPopover !== prevState.showSelectPopover) { - if (this.state.showSelectPopover === true && this.props.onEditorOpen) { + if (this.state.isShowEditor !== prevState.isShowEditor) { + if (this.state.isShowEditor === true && this.props.onEditorOpen) { this.props.onEditorOpen(); } - if (this.state.showSelectPopover === false && this.props.onEditorClose) { + if (this.state.isShowEditor === false && this.props.onEditorClose) { this.props.onEditorClose(); } } @@ -71,13 +71,13 @@ class RowExpandSingleSelectEditor extends React.Component { }; onKeyDown = (e) => { - if (e.keyCode === KeyCodes.Enter && this.props.isEditorFocus && !this.state.showSelectPopover) { - this.setState({ showSelectPopover: true }); + if (e.keyCode === KeyCodes.Enter && this.props.isEditorFocus && !this.state.isShowEditor) { + this.setState({ isShowEditor: true }); } }; toggleSingleSelect = (value) => { - this.setState({ showSelectPopover: value }, () => { + this.setState({ isShowEditor: value }, () => { if (value) return; // eslint-disable-next-line no-unused-expressions this.selectRef?.focus(); @@ -90,7 +90,7 @@ class RowExpandSingleSelectEditor extends React.Component { hideDropDownMenu = (event) => { if (!event.target) return; - if (!this.ref.contains(event.target) && this.state.showSelectPopover) { + if (!this.ref.contains(event.target) && this.state.isShowEditor) { const singleSelectEditor = document.getElementsByClassName('dtable-ui-select-editor-container')[0]; if (singleSelectEditor && singleSelectEditor.contains(event.target)) return; this.toggleSingleSelect(false); @@ -151,12 +151,12 @@ class RowExpandSingleSelectEditor extends React.Component { render() { const { isSupportNewOption, onAddNewOption, column, classNamePrefix } = this.props; - const { showSelectPopover, value } = this.state; + const { isShowEditor, value } = this.state; return (
this.ref = ref}> {this.renderOption()} this.targetRef = ref}> - {showSelectPopover && ( + {isShowEditor && ( { + return ( + <> + + + + + + + + ); +}); + +export default RowExpandTextEditor; diff --git a/src/RowExpandEditor/text-editor/mb-editor/index.css b/src/RowExpandEditor/text-editor/mb-editor/index.css new file mode 100644 index 00000000..5e71ada2 --- /dev/null +++ b/src/RowExpandEditor/text-editor/mb-editor/index.css @@ -0,0 +1,14 @@ +.dtable-ui-mobile-row-expand-input-editor-container { + height: 50px; + line-height: 50px; +} + +.dtable-ui-mobile-row-expand-input-editor-container .form-control { + border: none; + padding: 0; + height: 50px; +} + +.dtable-ui-mobile-row-expand-input-editor-container .form-control:focus { + box-shadow: none; +} diff --git a/src/RowExpandEditor/text-editor/mb-editor/index.js b/src/RowExpandEditor/text-editor/mb-editor/index.js new file mode 100644 index 00000000..c9084d9d --- /dev/null +++ b/src/RowExpandEditor/text-editor/mb-editor/index.js @@ -0,0 +1,43 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import SimpleTextEditor from '../../../TextEditor'; + +import './index.css'; + +class RowExpandMBTextEditor extends React.Component { + + constructor(props) { + super(props); + this.editor = null; + } + + onCommit = () => { + const value = this.editor.getValue(); + this.props.onCommit(value); + }; + + render() { + const { readOnly, column, value } = this.props; + + return ( +
+ this.editor = ref} + readOnly={readOnly} + column={column} + value={value} + onCommit={this.onCommit} + /> +
+ ); + } +} + +RowExpandMBTextEditor.propTypes = { + readOnly: PropTypes.bool, + column: PropTypes.object, + value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), + onCommit: PropTypes.func.isRequired, +}; + +export default RowExpandMBTextEditor; diff --git a/src/RowExpandEditor/RowExpandTextEditor/index.css b/src/RowExpandEditor/text-editor/pc-editor/index.css similarity index 100% rename from src/RowExpandEditor/RowExpandTextEditor/index.css rename to src/RowExpandEditor/text-editor/pc-editor/index.css diff --git a/src/RowExpandEditor/RowExpandTextEditor/index.js b/src/RowExpandEditor/text-editor/pc-editor/index.js similarity index 89% rename from src/RowExpandEditor/RowExpandTextEditor/index.js rename to src/RowExpandEditor/text-editor/pc-editor/index.js index e63fb13f..41aaa45e 100644 --- a/src/RowExpandEditor/RowExpandTextEditor/index.js +++ b/src/RowExpandEditor/text-editor/pc-editor/index.js @@ -1,15 +1,15 @@ import React from 'react'; import PropTypes from 'prop-types'; import { FormFeedback } from 'reactstrap'; -import SimpleTextEditor from '../../TextEditor'; -import { isEnableCheckFormat } from '../../utils/column-utils'; -import { checkValueConformityFormat } from '../../utils/cell'; -import { KeyCodes } from '../../constants'; -import { getLocale } from '../../lang'; +import SimpleTextEditor from '../../../TextEditor'; +import { isEnableCheckFormat } from '../../../utils/column-utils'; +import { checkValueConformityFormat } from '../../../utils/cell'; +import { KeyCodes } from '../../../constants'; +import { getLocale } from '../../../lang'; import './index.css'; -class RowExpandSimpleText extends React.Component { +class RowExpandPCTextEditor extends React.Component { constructor(props) { super(props); @@ -133,7 +133,7 @@ class RowExpandSimpleText extends React.Component { } } -RowExpandSimpleText.propTypes = { +RowExpandPCTextEditor.propTypes = { readOnly: PropTypes.bool, columnIndex: PropTypes.number, isEditorFocus: PropTypes.bool, @@ -144,4 +144,4 @@ RowExpandSimpleText.propTypes = { onCommit: PropTypes.func.isRequired, }; -export default RowExpandSimpleText; +export default RowExpandPCTextEditor; diff --git a/src/RowExpandEditor/url-editor/index.js b/src/RowExpandEditor/url-editor/index.js new file mode 100644 index 00000000..334fabda --- /dev/null +++ b/src/RowExpandEditor/url-editor/index.js @@ -0,0 +1,19 @@ +import React, { forwardRef } from 'react'; +import MediaQuery from 'react-responsive'; +import RowExpandPCUrlEditor from './pc-editor'; +import RowExpandMBUrlEditor from './mb-editor'; + +const RowExpandUrlEditor = forwardRef((props, ref) => { + return ( + <> + + + + + + + + ); +}); + +export default RowExpandUrlEditor; diff --git a/src/RowExpandEditor/url-editor/mb-editor/index.css b/src/RowExpandEditor/url-editor/mb-editor/index.css new file mode 100644 index 00000000..5f788978 --- /dev/null +++ b/src/RowExpandEditor/url-editor/mb-editor/index.css @@ -0,0 +1,17 @@ +.dtable-ui-mobile-row-expand-url-editor-container .dtable-ui-mobile-row-expand-url-input { + text-decoration: underline; + padding-right: 40px; +} + +.dtable-ui-mobile-row-expand-url-editor-container .dtable-ui-mobile-row-expand-jump-link { + height: 22px; + line-height: 22px; + position: absolute; + right: 0; + top: 14px; + background: #fff; + color: #999; + border: 1px solid #eee; + box-shadow: 0 0 1px; + padding: 0 3px; +} diff --git a/src/RowExpandEditor/url-editor/mb-editor/index.js b/src/RowExpandEditor/url-editor/mb-editor/index.js new file mode 100644 index 00000000..de8cfe9d --- /dev/null +++ b/src/RowExpandEditor/url-editor/mb-editor/index.js @@ -0,0 +1,84 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import toaster from '../../../toaster'; +import { isValidUrl, openUrlLink } from '../../../utils/utils'; +import { getLocale } from '../../../lang'; + +import './index.css'; + +class RowExpandMBUrlEditor extends React.Component { + + constructor(props) { + super(props); + this.state = { + value: props.value || '', + }; + } + + UNSAFE_componentWillReceiveProps(nextProps) { + const { column, row } = nextProps; + if (row._id !== this.props.row._id) { + this.setState({ value: row[column[this.props.valueKey]] || '' }); + } + } + + onBlur = () => { + const { onCommit } = this.props; + onCommit(this.state.value.trim()); + }; + + onChange = (e) => { + let value = e.target.value; + if (value === this.state.value) return; + this.setState({ value }); + }; + + onCut = (e) => { + e.stopPropagation(); + }; + + onPaste = (e) => { + e.stopPropagation(); + }; + + onOpenUrlLink = () => { + const { value } = this.state; + let newValue = value.trim(); + if (!isValidUrl(newValue)) { + newValue = `http://${newValue}`; + } + try { + openUrlLink(newValue); + } catch { + toaster.danger(getLocale('URL_is_invalid')); + } + }; + + render() { + const { value } = this.state; + return ( +
+ + {(value && value.trim()) && } +
+ ); + } +} + +RowExpandMBUrlEditor.propTypes = { + column: PropTypes.object, + row: PropTypes.object, + onCommit: PropTypes.func.isRequired, +}; + +export default RowExpandMBUrlEditor; diff --git a/src/RowExpandEditor/RowExpandUrlEditor/index.css b/src/RowExpandEditor/url-editor/pc-editor/index.css similarity index 100% rename from src/RowExpandEditor/RowExpandUrlEditor/index.css rename to src/RowExpandEditor/url-editor/pc-editor/index.css diff --git a/src/RowExpandEditor/RowExpandUrlEditor/index.js b/src/RowExpandEditor/url-editor/pc-editor/index.js similarity index 90% rename from src/RowExpandEditor/RowExpandUrlEditor/index.js rename to src/RowExpandEditor/url-editor/pc-editor/index.js index 28d7d312..532457e6 100644 --- a/src/RowExpandEditor/RowExpandUrlEditor/index.js +++ b/src/RowExpandEditor/url-editor/pc-editor/index.js @@ -1,13 +1,13 @@ import React from 'react'; import PropTypes from 'prop-types'; -import toaster from '../../toaster'; -import { isValidUrl, openUrlLink } from '../../utils/utils'; -import { KeyCodes } from '../../constants'; -import { getLocale } from '../../lang'; +import toaster from '../../../toaster'; +import { isValidUrl, openUrlLink } from '../../../utils/utils'; +import { KeyCodes } from '../../../constants'; +import { getLocale } from '../../../lang'; import './index.css'; -class RowExpandUrlEditor extends React.Component { +class RowExpandPCUrlEditor extends React.Component { constructor(props) { super(props); @@ -123,7 +123,7 @@ class RowExpandUrlEditor extends React.Component { } } -RowExpandUrlEditor.propTypes = { +RowExpandPCUrlEditor.propTypes = { isEditorFocus: PropTypes.bool, column: PropTypes.object, row: PropTypes.object, @@ -132,4 +132,4 @@ RowExpandUrlEditor.propTypes = { onCommit: PropTypes.func.isRequired, }; -export default RowExpandUrlEditor; +export default RowExpandPCUrlEditor; diff --git a/src/RowExpandFormatter/RowExpandRateFormatter/index.css b/src/RowExpandFormatter/RowExpandRateFormatter/index.css deleted file mode 100644 index 2b04b02c..00000000 --- a/src/RowExpandFormatter/RowExpandRateFormatter/index.css +++ /dev/null @@ -1,5 +0,0 @@ -@import url('../../css/cell-formatter.css'); - -.dtable-ui.dtable-row-expand-formatter .dtable-rate-formatter .dtable-icon-rate { - padding-right: 5px; -} diff --git a/src/RowExpandFormatter/constants.js b/src/RowExpandFormatter/constants.js index 535e37de..a9db81fb 100644 --- a/src/RowExpandFormatter/constants.js +++ b/src/RowExpandFormatter/constants.js @@ -15,15 +15,15 @@ import MTimeFormatter from '../MTimeFormatter'; import AutoNumberFormatter from '../AutoNumberFormatter'; import DurationFormatter from '../DurationFormatter'; import ButtonFormatter from '../ButtonFormatter'; -import RowExpandUrlFormatter from './RowExpandUrlFormatter'; -import RowExpandEmailFormatter from './RowExpandEmailFormatter'; -import RowExpandRateFormatter from './RowExpandRateFormatter'; -import RowExpandImageFormatter from './RowExpandImageFormatter'; -import RowExpandFileFormatter from './RowExpandFileFormatter'; -import RowExpandLinkFormatter from './RowExpandLinkFormatter'; -import RowExpandFormulaFormatter from './RowExpandFormulaFormatter'; import DigitalSignFormatter from '../DigitalSignFormatter'; -import RowExpandDepartmentFormatter from './RowExpandDepartmentFormatter'; +import RowExpandUrlFormatter from './url-formatter'; +import RowExpandEmailFormatter from './email-formatter'; +import RowExpandRateFormatter from './rate-formatter'; +import RowExpandImageFormatter from './image-formatter'; +import RowExpandFileFormatter from './file-formatter'; +import RowExpandLinkFormatter from './link-formatter'; +import RowExpandFormulaFormatter from './formula-formatter'; +import RowExpandDepartmentFormatter from './department-formatter'; export const DEFAULT_FORMATTER = { [CellType.TEXT]: TextFormatter, diff --git a/src/RowExpandFormatter/department-formatter/index.js b/src/RowExpandFormatter/department-formatter/index.js new file mode 100644 index 00000000..a20aa85f --- /dev/null +++ b/src/RowExpandFormatter/department-formatter/index.js @@ -0,0 +1,20 @@ +import React from 'react'; +import MediaQuery from 'react-responsive'; +import RowExpandPCDepartmentFormatter from './pc-formatter'; +import RowExpandMBDepartmentFormatter from './mb-formatter'; + +const RowExpandDepartmentFormatter = (props) => { + + return ( + <> + + + + + + + + ); +}; + +export default RowExpandDepartmentFormatter; diff --git a/src/RowExpandFormatter/department-formatter/mb-formatter.js b/src/RowExpandFormatter/department-formatter/mb-formatter.js new file mode 100644 index 00000000..b2a9d085 --- /dev/null +++ b/src/RowExpandFormatter/department-formatter/mb-formatter.js @@ -0,0 +1,14 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import DepartmentSingleSelectFormatter from '../../DepartmentSingleSelectFormatter'; + +function RowExpandMBDepartmentFormatter({ value, departments }) { + return (); +} + +RowExpandMBDepartmentFormatter.propTypes = { + value: PropTypes.string, + departments: PropTypes.array, +}; + +export default RowExpandMBDepartmentFormatter; diff --git a/src/RowExpandFormatter/RowExpandDepartmentFormatter/index.js b/src/RowExpandFormatter/department-formatter/pc-formatter.js similarity index 78% rename from src/RowExpandFormatter/RowExpandDepartmentFormatter/index.js rename to src/RowExpandFormatter/department-formatter/pc-formatter.js index 44e997da..1ad854e2 100644 --- a/src/RowExpandFormatter/RowExpandDepartmentFormatter/index.js +++ b/src/RowExpandFormatter/department-formatter/pc-formatter.js @@ -2,7 +2,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import DepartmentSingleSelectFormatter from '../../DepartmentSingleSelectFormatter'; -function RowExpandDepartmentFormatter({ value, departments }) { +function RowExpandPCDepartmentFormatter({ value, departments }) { return (
@@ -14,9 +14,9 @@ function RowExpandDepartmentFormatter({ value, departments }) { ); } -RowExpandDepartmentFormatter.propTypes = { +RowExpandPCDepartmentFormatter.propTypes = { value: PropTypes.string, departments: PropTypes.array, }; -export default RowExpandDepartmentFormatter; +export default RowExpandPCDepartmentFormatter; diff --git a/src/RowExpandFormatter/RowExpandEmailFormatter/index.css b/src/RowExpandFormatter/email-formatter/index.css similarity index 59% rename from src/RowExpandFormatter/RowExpandEmailFormatter/index.css rename to src/RowExpandFormatter/email-formatter/index.css index 3e6e6ad4..57409325 100644 --- a/src/RowExpandFormatter/RowExpandEmailFormatter/index.css +++ b/src/RowExpandFormatter/email-formatter/index.css @@ -1,13 +1,13 @@ @import url('../../css/cell-formatter.css'); -.dtable-row-expand-formatter .dtable-ui.email-formatter .email-formatter-value, -.dtable-row-expand-formatter .dtable-ui.url-formatter .url-formatter-value { +.dtable-ui-row-expand-formatter .dtable-ui.email-formatter .email-formatter-value, +.dtable-ui-row-expand-formatter .dtable-ui.url-formatter .url-formatter-value { text-decoration: underline; width: 95%; display: inline-flex; } -.dtable-row-expand-formatter .dtable-ui .row-expand-jump-link { +.dtable-ui-row-expand-formatter .dtable-ui .row-expand-jump-link { display: inline-flex; align-items: center; font-size: 14px; @@ -24,7 +24,7 @@ box-shadow: 0 0 1px; } -.dtable-row-expand-formatter .dtable-ui .row-expand-jump-link:hover { +.dtable-ui-row-expand-formatter .dtable-ui .row-expand-jump-link:hover { background: #eee; border: 1px solid #c9c9c9; } diff --git a/src/RowExpandFormatter/RowExpandEmailFormatter/index.js b/src/RowExpandFormatter/email-formatter/index.js similarity index 100% rename from src/RowExpandFormatter/RowExpandEmailFormatter/index.js rename to src/RowExpandFormatter/email-formatter/index.js diff --git a/src/RowExpandFormatter/RowExpandFileFormatter/index.css b/src/RowExpandFormatter/file-formatter/index.css similarity index 100% rename from src/RowExpandFormatter/RowExpandFileFormatter/index.css rename to src/RowExpandFormatter/file-formatter/index.css diff --git a/src/RowExpandFormatter/RowExpandFileFormatter/index.js b/src/RowExpandFormatter/file-formatter/index.js similarity index 100% rename from src/RowExpandFormatter/RowExpandFileFormatter/index.js rename to src/RowExpandFormatter/file-formatter/index.js diff --git a/src/RowExpandFormatter/RowExpandFileFormatter/row-expand-file-item-formatter.js b/src/RowExpandFormatter/file-formatter/row-expand-file-item-formatter.js similarity index 100% rename from src/RowExpandFormatter/RowExpandFileFormatter/row-expand-file-item-formatter.js rename to src/RowExpandFormatter/file-formatter/row-expand-file-item-formatter.js diff --git a/src/RowExpandFormatter/RowExpandFormulaFormatter/index.js b/src/RowExpandFormatter/formula-formatter/index.js similarity index 100% rename from src/RowExpandFormatter/RowExpandFormulaFormatter/index.js rename to src/RowExpandFormatter/formula-formatter/index.js diff --git a/src/RowExpandFormatter/RowExpandImageFormatter/index.css b/src/RowExpandFormatter/image-formatter/index.css similarity index 100% rename from src/RowExpandFormatter/RowExpandImageFormatter/index.css rename to src/RowExpandFormatter/image-formatter/index.css diff --git a/src/RowExpandFormatter/RowExpandImageFormatter/index.js b/src/RowExpandFormatter/image-formatter/index.js similarity index 100% rename from src/RowExpandFormatter/RowExpandImageFormatter/index.js rename to src/RowExpandFormatter/image-formatter/index.js diff --git a/src/RowExpandFormatter/RowExpandImageFormatter/row-expand-image-item-formatter.js b/src/RowExpandFormatter/image-formatter/row-expand-image-item-formatter.js similarity index 100% rename from src/RowExpandFormatter/RowExpandImageFormatter/row-expand-image-item-formatter.js rename to src/RowExpandFormatter/image-formatter/row-expand-image-item-formatter.js diff --git a/src/RowExpandFormatter/index.css b/src/RowExpandFormatter/index.css index 701479e4..ef84a4f7 100644 --- a/src/RowExpandFormatter/index.css +++ b/src/RowExpandFormatter/index.css @@ -1,9 +1,9 @@ -.dtable-ui.dtable-row-expand-formatter { +.dtable-ui.dtable-ui-row-expand-formatter { width: 100%; max-width: 100%; } -.dtable-ui.dtable-row-expand-formatter .row-cell-empty { +.dtable-ui.dtable-ui-row-expand-formatter .row-cell-empty { height: 8px; width: 20px; background-color: #f1f1f1; @@ -11,14 +11,14 @@ display: inline-block; } -.dtable-ui.dtable-row-expand-formatter .dtable-ui.collaborator-item { +.dtable-ui.dtable-ui-row-expand-formatter .dtable-ui.collaborator-item { margin: 5px 10px 5px 0; } -.dtable-ui.dtable-row-expand-formatter .dtable-ui.formula-formatter, -.dtable-ui.dtable-row-expand-formatter .dtable-ui.text-formatter, -.dtable-ui.dtable-row-expand-formatter .dtable-ui.url-formatter, -.dtable-ui.dtable-row-expand-formatter .dtable-ui.email-formatter { +.dtable-ui.dtable-ui-row-expand-formatter .dtable-ui.formula-formatter, +.dtable-ui.dtable-ui-row-expand-formatter .dtable-ui.text-formatter, +.dtable-ui.dtable-ui-row-expand-formatter .dtable-ui.url-formatter, +.dtable-ui.dtable-ui-row-expand-formatter .dtable-ui.email-formatter { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; @@ -27,11 +27,11 @@ width: 100%; } -.dtable-ui.dtable-row-expand-formatter .dtable-ui.multiple-select-formatter .dtable-ui.select-item { +.dtable-ui.dtable-ui-row-expand-formatter .dtable-ui.multiple-select-formatter .dtable-ui.select-item { margin: 5px 0; } -.dtable-ui.dtable-row-expand-formatter .dtable-ui.geolocation-formatter { +.dtable-ui.dtable-ui-row-expand-formatter .dtable-ui.geolocation-formatter { width: fit-content; min-width: 80px; max-width: 100%; @@ -45,19 +45,19 @@ text-align: center; } -.dtable-ui.dtable-row-expand-formatter .form-control { +.dtable-ui.dtable-ui-row-expand-formatter .form-control { background-color: #f8f9fa; padding: 0 10px; height: fit-content; min-height: 2.375rem; } -.dtable-ui.dtable-row-expand-formatter .ctime-formatter-container, -.dtable-ui.dtable-row-expand-formatter .mtime-formatter-container { +.dtable-ui.dtable-ui-row-expand-formatter .ctime-formatter-container, +.dtable-ui.dtable-ui-row-expand-formatter .mtime-formatter-container { padding: 0.375rem 0.75rem; } -.dtable-ui.dtable-row-expand-formatter .checkbox-formatter-container { +.dtable-ui.dtable-ui-row-expand-formatter .checkbox-formatter-container { width: 24px; height: 24px; border: 2px solid #e0e0e0; @@ -66,11 +66,11 @@ justify-content: center; } -.dtable-ui.dtable-row-expand-formatter .checkbox-formatter-container .cell-formatter-container { +.dtable-ui.dtable-ui-row-expand-formatter .checkbox-formatter-container .cell-formatter-container { line-height: inherit; } -.dtable-ui.dtable-row-expand-formatter .button-formatter { +.dtable-ui.dtable-ui-row-expand-formatter .button-formatter { text-align: center; height: 26px; line-height: 14px; @@ -91,7 +91,7 @@ letter-spacing: .03em; } -.dtable-ui.dtable-row-expand-formatter .longtext-formatter-container { +.dtable-ui.dtable-ui-row-expand-formatter .longtext-formatter-container { border: 1px solid rgba(0, 40, 100, 0.12); border-radius: 3px; padding: 0.375rem 0.75rem; @@ -99,13 +99,13 @@ background-color: #fff; } -.dtable-ui.dtable-row-expand-formatter .dtable-ui-geolocation-formatter-container { +.dtable-ui.dtable-ui-row-expand-formatter .dtable-ui-geolocation-formatter-container { margin-top: 0.5rem; display: flex; align-items: center; } -.dtable-ui.dtable-row-expand-formatter .dtable-link-formatter { +.dtable-ui.dtable-ui-row-expand-formatter .dtable-link-formatter { padding-top: 8px; } @@ -134,3 +134,18 @@ align-items: center; justify-content: space-between; } + +.mobile-dtable-row-expand-rate-formatter .dtable-ui-row-expand-rate-formatter .dtable-font { + font-size: 24px; + margin-right: 0.25rem; +} + +.mobile-dtable-row-expand-formula-formatter .dtable-ui-row-expand-formula-formatter.text-right { + text-align: left !important; +} + +.mobile-dtable-row-expand-link-formula-formatter .dtable-ui.formula-formatter.multiple { + height: auto; + display: flex; + flex-wrap: wrap; +} diff --git a/src/RowExpandFormatter/index.js b/src/RowExpandFormatter/index.js index bb7128d8..a724381d 100644 --- a/src/RowExpandFormatter/index.js +++ b/src/RowExpandFormatter/index.js @@ -314,7 +314,7 @@ class RowExpandFormatter extends React.Component { render() { const { className } = this.props; return ( -
+
{this.renderFormatter()}
); diff --git a/src/RowExpandFormatter/RowExpandLinkFormatter/collaborator-item-formatter.js b/src/RowExpandFormatter/link-formatter/collaborator-item-formatter.js similarity index 100% rename from src/RowExpandFormatter/RowExpandLinkFormatter/collaborator-item-formatter.js rename to src/RowExpandFormatter/link-formatter/collaborator-item-formatter.js diff --git a/src/RowExpandFormatter/RowExpandLinkFormatter/index.css b/src/RowExpandFormatter/link-formatter/index.css similarity index 100% rename from src/RowExpandFormatter/RowExpandLinkFormatter/index.css rename to src/RowExpandFormatter/link-formatter/index.css diff --git a/src/RowExpandFormatter/RowExpandLinkFormatter/index.jsx b/src/RowExpandFormatter/link-formatter/index.jsx similarity index 100% rename from src/RowExpandFormatter/RowExpandLinkFormatter/index.jsx rename to src/RowExpandFormatter/link-formatter/index.jsx diff --git a/src/RowExpandFormatter/RowExpandLinkFormatter/value-display-utils.js b/src/RowExpandFormatter/link-formatter/value-display-utils.js similarity index 100% rename from src/RowExpandFormatter/RowExpandLinkFormatter/value-display-utils.js rename to src/RowExpandFormatter/link-formatter/value-display-utils.js diff --git a/src/RowExpandFormatter/rate-formatter/index.css b/src/RowExpandFormatter/rate-formatter/index.css new file mode 100644 index 00000000..a57b9959 --- /dev/null +++ b/src/RowExpandFormatter/rate-formatter/index.css @@ -0,0 +1,5 @@ +@import url('../../css/cell-formatter.css'); + +.dtable-ui.dtable-ui-row-expand-formatter .dtable-rate-formatter .dtable-icon-rate { + padding-right: 5px; +} diff --git a/src/RowExpandFormatter/RowExpandRateFormatter/index.js b/src/RowExpandFormatter/rate-formatter/index.js similarity index 100% rename from src/RowExpandFormatter/RowExpandRateFormatter/index.js rename to src/RowExpandFormatter/rate-formatter/index.js diff --git a/src/RowExpandFormatter/RowExpandUrlFormatter/index.css b/src/RowExpandFormatter/url-formatter/index.css similarity index 100% rename from src/RowExpandFormatter/RowExpandUrlFormatter/index.css rename to src/RowExpandFormatter/url-formatter/index.css diff --git a/src/RowExpandFormatter/RowExpandUrlFormatter/index.js b/src/RowExpandFormatter/url-formatter/index.js similarity index 100% rename from src/RowExpandFormatter/RowExpandUrlFormatter/index.js rename to src/RowExpandFormatter/url-formatter/index.js diff --git a/src/RowExpandView/body/index.css b/src/RowExpandView/body/index.css new file mode 100644 index 00000000..1c46ff7d --- /dev/null +++ b/src/RowExpandView/body/index.css @@ -0,0 +1,173 @@ +.dtable-ui-mobile-row-expand-body .dtable-ui-row-expand-formatter { + min-height: 50px; +} + +.dtable-ui-mobile-row-expand-body .dtable-ui-row-expand-formatter .form-control { + background-color: #fff; + border: none; + min-height: 50px !important; + height: 50px; + width: 100% !important; + padding: 0; +} + +.dtable-ui-mobile-row-expand-body .dtable-ui-row-expand-formatter .longtext-formatter-container { + border: none; + border-radius: 0; + padding: 0; +} + +.dtable-ui-mobile-row-expand-body .dtable-ui-row-expand-long-text-formatter { + overflow: hidden !important; + white-space: unset !important; +} + +.dtable-ui-mobile-row-expand-body .dtable-ui-row-expand-long-text-formatter ol li a, +.dtable-ui-mobile-row-expand-body .dtable-ui-row-expand-long-text-formatter ul li a, +.dtable-ui-mobile-row-expand-body .dtable-ui-row-expand-long-text-formatter p a { + word-break: break-all; +} + +.dtable-ui-mobile-row-expand-body .dtable-ui-row-expand-long-text-formatter thead tr { + min-height: 42px; +} + +.dtable-ui-mobile-row-expand-body .dtable-ui-row-expand-long-text-formatter tbody tr { + font-weight: normal; + min-height: 42px; +} + +.dtable-ui-mobile-row-expand-body .dtable-ui-row-expand-long-text-formatter .am-list-item { + height: unset; +} + +.dtable-ui-mobile-row-expand-body .dtable-ui-row-expand-long-text-formatter .am-textarea-item { + padding: 0 1rem; + border-bottom: 1px solid #e9e9e9; +} + +.dtable-ui-mobile-row-expand-body .dtable-ui-row-expand-long-text-formatter .am-textarea-item textarea { + font-size: 14px; + overflow-x: hidden; +} + +.dtable-ui-mobile-row-expand-body .mobile-dtable-row-expand-last-modifier-formatter, +.dtable-ui-mobile-row-expand-body .mobile-dtable-row-expand-creator-formatter, +.dtable-ui-mobile-row-expand-body .mobile-dtable-row-expand-button-formatter, +.dtable-ui-mobile-row-expand-body .mobile-dtable-row-expand-checkbox-formatter, +.dtable-ui-mobile-row-expand-body .mobile-dtable-row-expand-department-single-select-formatter { + display: flex; + align-items: center; +} + +.dtable-ui-mobile-row-expand-body .dtable-ui-row-expand-formatter .collaborator-item { + max-width: 100%; + width: fit-content; +} + +.dtable-ui-mobile-row-expand-body .dtable-ui-row-expand-formatter .collaborator-item .collaborator-name { + flex: 1; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + display: inline-block; +} + +.dtable-ui-mobile-row-expand-body .mobile-dtable-row-expand-geolocation-formatter .dtable-ui-geolocation-formatter-container { + margin-top: 0; + min-height: inherit; + height: 100%; + width: 100%; +} + +.dtable-ui-mobile-row-expand-body .mobile-dtable-row-expand-geolocation-formatter .dtable-ui.geolocation-formatter { + background-color: inherit; + border: none; + padding: 16px 0; + width: 100%; + display: inline-block; + text-align: left; + height: unset; + line-height: 1.5; +} + +.dtable-ui-mobile-row-expand-body .dtable-ui-row-expand-auto-number-formatter { + min-height: 50px; + padding: 16px 0; + line-height: 1.5; +} + +.dtable-ui-mobile-row-expand-body .mobile-dtable-row-expand-image-formatter .row-expand-image-formatter, +.dtable-ui-mobile-row-expand-body .mobile-dtable-row-expand-file-formatter .row-expand-file-formatter, +.dtable-ui-mobile-row-expand-body .mobile-dtable-row-expand-digital-sign-formatter { + width: 100%; + overflow-x: hidden; +} + +.dtable-ui-mobile-row-expand-body .mobile-dtable-row-expand-image-formatter .row-expand-item-image, +.dtable-ui-mobile-row-expand-body .mobile-dtable-row-expand-file-formatter .row-expand-item-file, +.dtable-ui-mobile-row-expand-body .mobile-dtable-row-expand-digital-sign-formatter .dtable-ui-row-expand-digital-sign-formatter { + height: auto; + max-height: 150px; + width: calc(50% - 10px); + margin: 5px 0; +} + +.dtable-ui-mobile-row-expand-body .mobile-dtable-row-expand-image-formatter .row-expand-item-image:nth-child(even), +.dtable-ui-mobile-row-expand-body .mobile-dtable-row-expand-file-formatter .row-expand-item-file:nth-child(even) { + margin-left: 20px; +} + +.dtable-ui-mobile-row-expand-body .mobile-dtable-row-expand-image-formatter .row-expand-item-image img, +.dtable-ui-mobile-row-expand-body .mobile-dtable-row-expand-file-formatter .row-expand-item-file img, +.dtable-ui-mobile-row-expand-body .mobile-dtable-row-expand-digital-sign-formatter .dtable-ui-row-expand-digital-sign-formatter img { + max-height: 100%; + max-width: 100%; + border-radius: 2px; +} + +.dtable-ui-mobile-row-expand-body .mobile-dtable-row-expand-collaborator-formatter .form-control { + height: fit-content; + min-height: 50px; +} + +.dtable-ui-mobile-row-expand-body .mobile-dtable-row-expand-collaborator-formatter .dtable-ui-row-expand-collaborator-formatter, +.dtable-ui-mobile-row-expand-body .mobile-dtable-row-expand-multiple-select-formatter .form-control { + padding: 10px 0; +} + +.dtable-ui-mobile-row-expand-body .mobile-dtable-row-expand-multiple-select-formatter .dtable-ui-row-expand-multiple-select-formatter { + display: flex; + flex-wrap: wrap; +} + +.dtable-ui-mobile-row-expand-body .dtable-ui-row-expand-url-formatter, +.dtable-ui-mobile-row-expand-body .dtable-ui-row-expand-email-formatter { + position: relative; +} + +.dtable-ui-mobile-row-expand-body .dtable-ui-row-expand-url-formatter .row-expand-jump-link, +.dtable-ui-mobile-row-expand-body .dtable-ui-row-expand-email-formatter .row-expand-jump-link { + right: 0; +} + +.dtable-ui-mobile-row-expand-body .dtable-ui-row-expand-formatter .dtable-ui.url-formatter .url-formatter-value, +.dtable-ui-mobile-row-expand-body .dtable-ui-row-expand-formatter .dtable-ui.email-formatter .email-formatter-value { + width: calc(100% - 40px); + display: inline-block; +} + +.dtable-ui-mobile-row-expand-body .mobile-dtable-row-expand-link-formatter { + padding: 13px 0; +} + +.dtable-ui-mobile-row-expand-subtitle { + padding: 1rem 1rem 0.3125rem; + background-color: #f5f5f5; + border-top: 1px solid #e9e9e9; + border-bottom: 1px solid #e9e9e9; +} + +.dtable-ui-mobile-row-expand-body .am-list-item { + height: unset; +} diff --git a/src/RowExpandView/body/index.js b/src/RowExpandView/body/index.js new file mode 100644 index 00000000..6b3c20c1 --- /dev/null +++ b/src/RowExpandView/body/index.js @@ -0,0 +1,92 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import classnames from 'classnames'; +import { CellType } from 'dtable-utils'; +import ColumnContent from '../column-content'; +import RowExpandEditor from '../../RowExpandEditor'; +import RowExpandFormatter from '../../RowExpandFormatter'; +import { getFormulaArrayValue, isArrayFormatColumn, downloadFile } from '../../utils/utils'; + +import './index.css'; + +class Body extends React.Component { + + downloadImage = (imageItemUrl) => { + this.props.getDownLoadFiles([{ url: imageItemUrl }]).then(res => { + const [downloadUrl] = res.data.urls; + downloadFile(downloadUrl); + }).catch(error => { + // todo + }); + }; + + getCellValue = ({ row, column }) => { + if (!row || !column) return null; + const { valueKey } = this.props; + return row[column[valueKey]]; + }; + + renderColumnValue = (row, column) => { + const { component, onChange, className, ...props } = this.props; + const { getDownLoadFiles } = props; + const { editable } = column; + const { editor, formatter } = component || {}; + if (editable) { + return ( + onChange(column, value)} + /> + ); + } + return ( + + ); + }; + + render() { + const { columns, row, placeholder } = this.props; + return ( +
+ {columns.length === 0 && placeholder && (<>{placeholder})} + {columns.map((column) => { + const { type, data } = column; + let isHasMore = false; + if (type === CellType.LINK_FORMULA) { + let { array_type } = data || {}; + const value = this.getCellValue({ row, column }); + const cellValue = getFormulaArrayValue(value, !isArrayFormatColumn(array_type)); + if (cellValue.length >= 10) { + isHasMore = true; + } + } + return ( + + {this.renderColumnValue(row, column)} + + ); + })} +
+ ); + } +} + +Body.propTypes = { + columns: PropTypes.array, + row: PropTypes.object, + placeholder: PropTypes.any, + onChange: PropTypes.func, + onToggle: PropTypes.func, +}; + +export default Body; diff --git a/src/RowExpandView/column-content/index.css b/src/RowExpandView/column-content/index.css new file mode 100644 index 00000000..5c15d41b --- /dev/null +++ b/src/RowExpandView/column-content/index.css @@ -0,0 +1,27 @@ +.dtable-ui-mobile-row-expand-item { + margin-bottom: 0px; + padding: 0 1rem; + background-color: #fff; + min-height: unset; +} + +.dtable-ui-mobile-row-expand-item .dtable-ui-mobile-row-expand-item-title { + background-color: #f5f5f5; + border-bottom: 1px solid #e9e9e9; + border-top: 1px solid #e9e9e9; + margin: 0 -1rem; + padding: 0.625rem 1rem 0; + display: flex; +} + +.dtable-ui-mobile-row-expand-item .dtable-ui-mobile-row-expand-item-title label { + margin-bottom: 5px; + font-weight: bold; + font-size: 14px; +} + +.dtable-ui-mobile-row-expand-item .dtable-ui-mobile-column-uneditable-tip { + font-size: 14px; + margin-left: 4px; + color: #ccc; +} diff --git a/src/RowExpandView/column-content/index.js b/src/RowExpandView/column-content/index.js new file mode 100644 index 00000000..deb0d166 --- /dev/null +++ b/src/RowExpandView/column-content/index.js @@ -0,0 +1,25 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { Label } from 'reactstrap'; + +import './index.css'; + +const ColumnContent = ({ column, children }) => { + const { name } = column; + return ( +
+
+ + {/* {!editable && } */} +
+ {children} +
+ ); +}; + +ColumnContent.propTypes = { + column: PropTypes.object, + children: PropTypes.any, +}; + +export default ColumnContent; diff --git a/src/RowExpandView/index.css b/src/RowExpandView/index.css new file mode 100644 index 00000000..b6427373 --- /dev/null +++ b/src/RowExpandView/index.css @@ -0,0 +1,3 @@ +.dtable-ui-mobile-row-expand-full-screen-page .dtable-ui-mobile-row-expand-body .dtable-ui-mobile-row-expand-item:first-child .dtable-ui-mobile-row-expand-item-title { + border-top: 0; +} diff --git a/src/RowExpandView/index.js b/src/RowExpandView/index.js new file mode 100644 index 00000000..f00ac92d --- /dev/null +++ b/src/RowExpandView/index.js @@ -0,0 +1,177 @@ +import React, { forwardRef, useImperativeHandle, useCallback, useEffect, useRef, useState } from 'react'; +import PropTypes from 'prop-types'; +import classnames from 'classnames'; +import { CellType, NOT_SUPPORT_EDIT_COLUMN_TYPE_MAP } from 'dtable-utils'; +import toaster from '../toaster'; +import Loading from '../Loading'; +import Body from './body'; +import { getLocale } from '../lang'; +import { isCellValueChanged } from '../utils/cell-comparer'; +import MobileFullScreenPage from '../MobileFullScreenPage'; + +import './index.css'; + +const { Header, Body: MobileFullScreenPageBody } = MobileFullScreenPage; + +const RowExpandView = forwardRef(({ + saveImmediately = true, + readonly = false, + isInsertingRow = false, + zIndex, + title, + className, + valueKey = 'name', // name or key + row: defaultRow, + columns: defaultColumns, + commit, + onToggle, + uploadFile, + copyURL, + children = [], + historyCallback, + ...otherProps +}, ref) => { + const [isLoading, setLoading] = useState(true); + const [isSaving, setSaving] = useState(false); + const [row, setRow] = useState({}); + const [columns, setColumns] = useState([]); + + const isChangedRef = useRef(false); + const update = useRef({}); + + const checkEditable = useCallback((column) => { + if (isSaving) return false; + if (readonly || !column || !column.editable || NOT_SUPPORT_EDIT_COLUMN_TYPE_MAP[column.type]) return false; + if (column.type === CellType.IMAGE || column.type === CellType.FILE) return Boolean(uploadFile); + return true; + }, [readonly, isSaving, uploadFile]); + + const initRowData = useCallback(() => { + setLoading(true); + setRow(defaultRow); + let validColumns = defaultColumns.map(c => ({ ...c, editable: checkEditable(c, defaultRow), width: 320 })); + if (isInsertingRow) { + validColumns = validColumns.filter(c => c.editable); + } + + isChangedRef.current = isInsertingRow && Object.keys(defaultRow).length > 0; + setColumns(validColumns); + setLoading(false); + }, [isInsertingRow, defaultColumns, defaultRow, checkEditable]); + + const toggle = useCallback(() => { + if (isSaving) return; + onToggle(); + }, [isSaving, onToggle]); + + const onSave = useCallback((updated, { successCallback, failCallback } = {}) => { + commit(updated, columns, { successCallback, failCallback }); + }, [columns, commit]); + + const onChange = useCallback((column, value) => { + const key = column[valueKey]; + const updated = { [key]: value }; + const oldValue = row[key]; + if (!isCellValueChanged(oldValue, value, column.type)) return; + if (!saveImmediately || isInsertingRow) { + isChangedRef.current = true; + update.current = { ...update.current, ...updated }; + setRow({ ...row, ...updated }); + return; + } + onSave(updated, { + successCallback: () => { + isChangedRef.current = false; + update.current = {}; + setRow({ ...row, ...updated }); + }, + failCallback: () => { + toaster.danger(getLocale('Save_failed')); + } + }); + }, [saveImmediately, isInsertingRow, row, valueKey, onSave]); + + const onSubmit = useCallback(() => { + setSaving(true); + const successCallback = isInsertingRow ? onToggle : () => { + isChangedRef.current = false; + update.current = {}; + setSaving(false); + }; + const failCallback = () => { + setSaving(false); + toaster.danger(getLocale('Save_failed')); + }; + const newRow = isInsertingRow ? { ...row, ...update.current } : update.current; + onSave(newRow, { successCallback, failCallback }); + }, [row, isInsertingRow, onSave, onToggle]); + + useEffect(() => { + initRowData(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + useImperativeHandle(ref, () => ({ + getData: () => ({ row, columns }), + setData: (data = {}) => { + Object.keys(data).forEach(key => { + if (key === 'row') { + setRow(data[key]); + } + if (key === 'columns') { + setColumns(data[key]); + } + }); + }, + }), [row, columns]); + + return ( + +
+ + {title} + <> + {(!saveImmediately || isInsertingRow) && (<>{getLocale('Submit')})} + +
+ + {isLoading ? ( +
+ ) : ( + + )} +
+
+ ); +}); + +RowExpandView.propTypes = { + saveImmediately: PropTypes.bool, + isInsertingRow: PropTypes.bool, + zIndex: PropTypes.number, + title: PropTypes.any, + className: PropTypes.string, + valueKey: PropTypes.oneOf(['key', 'name']), + commit: PropTypes.func.isRequired, + onToggle: PropTypes.func.isRequired, + uploadFile: PropTypes.func, + copyURL: PropTypes.func, +}; + +RowExpandView.Body = Body; + +export default RowExpandView; diff --git a/src/SelectItem/index.js b/src/SelectItem/index.js index 9c6557ad..2e69ee52 100644 --- a/src/SelectItem/index.js +++ b/src/SelectItem/index.js @@ -1,38 +1,59 @@ -import React from 'react'; +import React, { useMemo } from 'react'; import PropTypes from 'prop-types'; +import classnames from 'classnames'; -export default class SelectItem extends React.PureComponent { +const SelectItem = ({ option, fontSize, className, isShowRemove, onRemove }) => { - static propTypes = { - option: PropTypes.shape({ - id: PropTypes.string.isRequired, - name: PropTypes.string.isRequired, - color: PropTypes.string.isRequired, - }).isRequired, - fontSize: PropTypes.number, - }; - - getStyle = (option, fontSize) => { + const style = useMemo(() => { return { - display: 'inline-block', + display: 'flex', + alignItems: 'center', padding: '0px 10px', - marginRight: '8px', - height: '20px', + marginRight: 8, + height: 20, lineHeight: '20px', textAlign: 'center', - borderRadius: '10px', - maxWidth: '250px', - fontSize: fontSize ? `${fontSize}px` : '13px', + borderRadius: 10, + width: 'min-content', + maxWidth: 300, + margin: '5px 10px 5px 0', + fontSize: fontSize || 13, backgroundColor: option.color, color: option.textColor || null, }; - }; + }, [option, fontSize]); + + const opBtnStyle = useMemo(() => { + const textColor = option.textColor || null; + return { + cursor: 'pointer', + color: textColor === '#FFFFFF' ? '#FFFFFF' : '#909090', + marginLeft: 5, + }; + }, [option]); + + return ( +
+
{option.name}
+ {isShowRemove && ( +
+ +
+ )} +
+ ); +}; + +SelectItem.propTypes = { + option: PropTypes.shape({ + id: PropTypes.string.isRequired, + name: PropTypes.string.isRequired, + color: PropTypes.string.isRequired, + }).isRequired, + className: PropTypes.string, + fontSize: PropTypes.number, + isShowRemove: PropTypes.bool, + onRemove: PropTypes.func, +}; - render() { - let { option, fontSize } = this.props; - const style = this.getStyle(option, fontSize); - return ( -
{option.name}
- ); - } -} +export default SelectItem; diff --git a/src/Department-editor/selected-departments/index.css b/src/SelectedDepartments/index.css similarity index 99% rename from src/Department-editor/selected-departments/index.css rename to src/SelectedDepartments/index.css index d5abdce8..26b982dd 100644 --- a/src/Department-editor/selected-departments/index.css +++ b/src/SelectedDepartments/index.css @@ -1,4 +1,3 @@ - .selected-departments.dtable-ui .remove-container { width: 16px; text-align: right; diff --git a/src/Department-editor/selected-departments/index.js b/src/SelectedDepartments/index.js similarity index 96% rename from src/Department-editor/selected-departments/index.js rename to src/SelectedDepartments/index.js index 21d9623c..de9b1e14 100644 --- a/src/Department-editor/selected-departments/index.js +++ b/src/SelectedDepartments/index.js @@ -1,8 +1,8 @@ import React, { useMemo } from 'react'; import PropTypes from 'prop-types'; import { DEPARTMENT_SELECT_RANGE_MAP } from 'dtable-utils'; -import { DEPARTMENT_SELECT_RANGE_OPTIONS } from '../constants'; -import { getLocale } from '../../lang'; +import { DEPARTMENT_SELECT_RANGE_OPTIONS } from '../constants/departments'; +import { getLocale } from '../lang'; import './index.css'; diff --git a/src/SingleSelectEditor/index.js b/src/SingleSelectEditor/index.js index c801f690..86701e1c 100644 --- a/src/SingleSelectEditor/index.js +++ b/src/SingleSelectEditor/index.js @@ -1,22 +1,12 @@ -import React from 'react'; +import React, { forwardRef } from 'react'; import PropTypes from 'prop-types'; -import MediaQuery from 'react-responsive'; -import { PCSelectEditor, MBSelectEditor } from '../select-editor'; +import SelectEditor from '../select-editor'; -const SingleSelectEditor = ({ value: oldValue, ...props }) => { +const SingleSelectEditor = forwardRef(({ value: oldValue, ...props }, ref) => { const value = oldValue ? [oldValue] : []; - return ( - <> - - - - - - - - ); -}; + return (); +}); SingleSelectEditor.propTypes = { value: PropTypes.string, diff --git a/src/SvgIcon/index.js b/src/SvgIcon/index.js index d5e966b3..d97e9c88 100644 --- a/src/SvgIcon/index.js +++ b/src/SvgIcon/index.js @@ -1,5 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; +import { isMobile } from '../constants'; import './index.css'; @@ -8,7 +9,7 @@ const needScaleIcons = ['check', 'dot', 'cross']; const SvgIcon = (props) => { const { className, symbol, color } = props; let iconClass = `dtable-ui-multicolor-icon multicolor-icon-${symbol} ${className || ''}`; - if (needScaleIcons.includes(symbol) && window.isMobile) { + if (needScaleIcons.includes(symbol) && isMobile) { iconClass += ' scale-icon'; } return ( diff --git a/src/app.css b/src/app.css index f6c6ecdd..3969762d 100644 --- a/src/app.css +++ b/src/app.css @@ -44,7 +44,7 @@ html, body, #root { color: #666; } -@media screen and (max-width: 767.8px) { +@media screen and (max-width: 768px) { .app .app-body { border: none; } diff --git a/src/Department-editor/constants.js b/src/constants/departments.js similarity index 100% rename from src/Department-editor/constants.js rename to src/constants/departments.js diff --git a/src/constants/index.js b/src/constants/index.js index 695110eb..7faf0d9b 100644 --- a/src/constants/index.js +++ b/src/constants/index.js @@ -82,3 +82,6 @@ export const FILE_EDITOR_STATUS = { PREVIEWER: 'previewer', ADDITION: 'addition', }; + +export const isMobile = (typeof (window) !== 'undefined') && (window.innerWidth < 768 || + navigator.userAgent.toLowerCase().match(/(ipod|ipad|iphone|android|coolpad|mmp|smartphone|midp|wap|xoom|symbian|j2me|blackberry|wince)/i) != null); diff --git a/src/css/mb-cell-editor.css b/src/css/mb-cell-editor.css index eb214c6f..96759d01 100644 --- a/src/css/mb-cell-editor.css +++ b/src/css/mb-cell-editor.css @@ -1,4 +1,4 @@ -@media screen and (max-width: 767.8px) { +@media screen and (max-width: 768px) { .dtable-ui-mb-editor-popover { position: fixed; top: 0; diff --git a/src/index.js b/src/index.js index 7b434852..a491ef64 100644 --- a/src/index.js +++ b/src/index.js @@ -40,13 +40,15 @@ export { default as SimpleLongTextFormatter } from './SimpleLongTextFormatter'; // row expand export { default as RowExpandFormatter } from './RowExpandFormatter'; export { default as RowExpandEditor } from './RowExpandEditor'; +export { default as RowExpand } from './RowExpand'; export { default as RowExpandDialog } from './RowExpandDialog'; +export { default as RowExpandView } from './RowExpandView'; // editor export { default as CheckboxEditor } from './CheckboxEditor'; export { default as CollaboratorEditor } from './CollaboratorEditor'; export { default as DateEditor } from './DateEditor'; -export { default as DepartmentEditor } from './Department-editor'; +export { default as DepartmentSingleSelectEditor } from './DepartmentSingleSelectEditor'; export { default as DigitalSignEditor } from './DigitalSignEditor'; export { default as DurationEditor } from './DurationEditor'; export { default as EmailEditor } from './EmailEditor'; @@ -104,3 +106,12 @@ export { default as IconButton } from './IconButton'; export { default as UploadProgress } from './UploadProgress'; export { default as SvgIcon } from './SvgIcon'; + +export { default as BodyPortal } from './BodyPortal'; + +// mobile +export { default as MobileModal } from './MobileModal'; +export { default as MobileOperationSheet } from './MobileOperationSheet'; +export { default as MobileUpload } from './MobileUpload'; +export { default as MobileSelector } from './MobileSelector'; +export { default as MobileFullScreenPage } from './MobileFullScreenPage'; diff --git a/src/locales/de.json b/src/locales/de.json index 5d2c85e0..e27be5f8 100644 --- a/src/locales/de.json +++ b/src/locales/de.json @@ -163,5 +163,7 @@ "Mark_as_resolved": "Als erledigt markieren", "No_comment_yet": "Es sind keine Kommentare vorhanden.", "Add_comment": "Kommentar hinzufügen", - "Add_participants": "Teilnehmer hinzufügen" + "Add_participants": "Teilnehmer hinzufügen", + "Edit_long_text": "Langtext bearbeiten", + "Choose_a_department": "Bereich wählen" } diff --git a/src/locales/en.json b/src/locales/en.json index 187487bc..fc23c5ee 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -163,5 +163,7 @@ "Mark_as_resolved": "Mark as resolved", "No_comment_yet": "No comment yet", "Add_comment": "Add comment", - "Add_participants": "Add participants" + "Add_participants": "Add participants", + "Edit_long_text": "Edit long text", + "Choose_a_department": "Choose a department" } \ No newline at end of file diff --git a/src/locales/es.json b/src/locales/es.json index e16ca738..5c567ba8 100644 --- a/src/locales/es.json +++ b/src/locales/es.json @@ -163,5 +163,7 @@ "Mark_as_resolved": "Marcar como resuelto", "No_comment_yet": "No comment yet", "Add_comment": "Añadir comentario", - "Add_participants": "Añadir participantes" + "Add_participants": "Añadir participantes", + "Edit_long_text": "Edit long text", + "Choose_a_department": "Choose a department" } \ No newline at end of file diff --git a/src/locales/fr.json b/src/locales/fr.json index 34087200..a91c29f4 100644 --- a/src/locales/fr.json +++ b/src/locales/fr.json @@ -163,5 +163,7 @@ "Mark_as_resolved": "Marquer comme résolu", "No_comment_yet": "Aucun commentaire n'est disponible.", "Add_comment": "Ajouter un commentaire", - "Add_participants": "Ajouter des participants" + "Add_participants": "Ajouter des participants", + "Edit_long_text": "Modifier le texte long", + "Choose_a_department": "Sélectionner un département" } diff --git a/src/locales/pt.json b/src/locales/pt.json index dd0e838b..ad43e644 100644 --- a/src/locales/pt.json +++ b/src/locales/pt.json @@ -163,5 +163,7 @@ "Mark_as_resolved": "Marca conforme resolvido", "No_comment_yet": "No comment yet", "Add_comment": "Adicionar comentário", - "Add_participants": "Adicione participantes" + "Add_participants": "Adicione participantes", + "Edit_long_text": "Editar texto longo", + "Choose_a_department": "Choose a department" } diff --git a/src/locales/ru.json b/src/locales/ru.json index 0d1e303d..997a50f4 100644 --- a/src/locales/ru.json +++ b/src/locales/ru.json @@ -163,5 +163,7 @@ "Mark_as_resolved": "Пометить как разрешенный", "No_comment_yet": "Пока без комментариев", "Add_comment": "Добавить комментарий", - "Add_participants": "Добавить участников" + "Add_participants": "Добавить участников", + "Edit_long_text": "Редактировать длинный текст", + "Choose_a_department": "Выберите отдел" } \ No newline at end of file diff --git a/src/locales/zh-CN.json b/src/locales/zh-CN.json index e22a9524..808a6ee8 100644 --- a/src/locales/zh-CN.json +++ b/src/locales/zh-CN.json @@ -163,5 +163,7 @@ "Mark_as_resolved": "标记为已解决", "No_comment_yet": "暂无评论", "Add_comment": "增加评论", - "Add_participants": "添加参与人" + "Add_participants": "添加参与人", + "Edit_long_text": "编辑长文本", + "Choose_a_department": "选择一个部门" } diff --git a/src/select-editor/index.js b/src/select-editor/index.js index e3d97f91..26bad4ff 100644 --- a/src/select-editor/index.js +++ b/src/select-editor/index.js @@ -1,5 +1,27 @@ -import MBSelectEditor from './mb-select-editor'; -import PCSelectEditor from './pc-select-editor'; -import SelectEditorOption from './select-editor-option'; +import React, { forwardRef } from 'react'; +import PropTypes from 'prop-types'; +import MediaQuery from 'react-responsive'; +import PCSelectEditor from './pc-editor'; +import MBSelectEditor from './mb-editor'; -export { PCSelectEditor, MBSelectEditor, SelectEditorOption }; +const SelectEditor = forwardRef(({ isMobile, ...props }, ref) => { + if (isMobile === false) return (); + if (isMobile === true) return (); + + return ( + <> + + + + + + + + ); +}); + +SelectEditor.propTypes = { + isMobile: PropTypes.bool, +}; + +export default SelectEditor; diff --git a/src/select-editor/mb-editor/index.css b/src/select-editor/mb-editor/index.css new file mode 100644 index 00000000..d3cf8927 --- /dev/null +++ b/src/select-editor/mb-editor/index.css @@ -0,0 +1 @@ +@import url('../../css/mb-cell-editor.css'); diff --git a/src/select-editor/mb-editor/index.js b/src/select-editor/mb-editor/index.js new file mode 100644 index 00000000..028bd51c --- /dev/null +++ b/src/select-editor/mb-editor/index.js @@ -0,0 +1,98 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { getLocale } from '../../lang'; +import SelectItem from '../../SelectItem'; +import MobileSelector from '../../MobileSelector'; +import DTableCommonAddTool from '../../DTableCommonAddTool'; + +import './index.css'; + +const { Search, Option, Options, Empty } = MobileSelector; + +class MBSelectEditor extends React.Component { + + static defaultProps = { + isShowRemoveIcon: false, + isSupportNewOption: false, + value: [], + valueKey: 'id', + }; + + constructor(props) { + super(props); + this.state = { + searchVal: '', + }; + } + + onChangeSearch = (newValue) => { + const { searchVal } = this.state; + if (searchVal === newValue) return; + this.setState({ searchVal: newValue }); + }; + + getDisplayOptions = () => { + const { options } = this.props; + const { searchVal } = this.state; + return searchVal ? options.filter((item) => item.name.indexOf(searchVal) > -1) : options; + }; + + onChange = (option) => { + this.props.onCommit(option); + }; + + onAddNewOption = (event) => { + event.stopPropagation(); + event.nativeEvent.stopImmediatePropagation(); + const name = this.state.searchValue.trim(); + if (name) { + this.props.onAddNewOption(name); + this.props.onClose && this.props.onClose(); + } + }; + + renderOptions = (options) => { + const { value = [], valueKey } = this.props; + return options.map(option => { + const isSelected = value.includes(option[valueKey]); + + return ( + + ); + }); + }; + + render() { + const { column, isSupportNewOption, options, onAddNewOption } = this.props; + const { searchVal } = this.state; + const displayOptions = this.getDisplayOptions(); + const isShowCreateBtn = isSupportNewOption && onAddNewOption && !!searchVal && !options.find((item) => item.name !== searchVal); + + return ( + + {options.length > 10 && ( + + )} + + {displayOptions.length === 0 && ({getLocale('No_options_available')})} + {displayOptions.length > 0 && this.renderOptions(displayOptions)} + {isShowCreateBtn && ()} + + + ); + } +} + +MBSelectEditor.propTypes = { + value: PropTypes.array.isRequired, + column: PropTypes.object.isRequired, + options: PropTypes.array.isRequired, + onCommit: PropTypes.func, + onClose: PropTypes.func, + isSupportNewOption: PropTypes.bool, + onAddNewOption: PropTypes.func, +}; + +export default MBSelectEditor; diff --git a/src/select-editor/mb-select-editor/index.css b/src/select-editor/mb-select-editor/index.css deleted file mode 100644 index 3fed3809..00000000 --- a/src/select-editor/mb-select-editor/index.css +++ /dev/null @@ -1,127 +0,0 @@ -@import url('../../css/mb-cell-editor.css'); - -@media screen and (max-width: 767.8px) { - .dtable-ui-mb-select-editor-body .mb-selected-item { - margin-top: 10px; - display: flex; - flex-direction: column; - } - - .dtable-ui-mb-select-editor-body .mb-selected-item .title { - padding: 6px 16px; - border-bottom: 1px solid #e9e9e9; - } - - .dtable-ui-mb-select-editor-body .mb-selected-item .content { - display: flex; - flex-wrap: wrap; - align-items: center; - min-height: 50px; - padding: 8px 16px; - border-bottom: 1px solid #e9e9e9; - overflow: auto; - background-color: #fff; - } - - .dtable-ui-mb-select-editor-body .mb-selected-item .content .select-option-item { - margin: 3px 10px 3px 0; - } - - .dtable-ui-mb-select-editor-body .mb-search-select-items { - padding: 8px 16px; - margin-top: 20px; - border-top: 1px solid #e9e9e9; - border-bottom: 1px solid #e9e9e9; - background-color: #fff; - } - - .dtable-ui-mb-select-editor-body .mb-search-select-items > input { - outline: none; - height: 30px; - padding: 0; - line-height: 30px; - border: none; - } - .dtable-ui-mb-select-editor-body .mb-search-select-items > input:focus, - .dtable-ui-mb-select-editor-body .mb-search-select-items > input:active { - outline: none; - box-shadow: none; - } - - .dtable-ui-mb-select-editor-body .mb-select-options-container { - display: flex; - flex-direction: column; - margin-top: 10px; - } - - .dtable-ui-mb-select-editor-body .mb-select-options-container .title { - padding: 6px 16px; - border-bottom: 1px solid #e9e9e9; - } - - .dtable-ui-mb-select-editor-body .mb-select-options-container .content { - display: flex; - flex-direction: column; - border-bottom: 1px solid #e9e9e9; - } - - .dtable-ui-mb-select-editor-body .mb-select-options-container .search-result-none { - padding: 16px 0; - } - - .dtable-ui-mb-select-editor-body .mb-select-options-container .mb-select-option-item { - display: flex; - align-items: center; - justify-content: space-between; - height: 50px; - padding: 10px 10px 10px 16px; - font-size: 13px; - background-color: #fff; - color: #212529; - } - - .dtable-ui-mb-select-editor-body .mb-select-options-container .mb-select-option-item + .mb-select-option-item { - border-top: 1px solid #e9e9e9; - } - - .dtable-ui-mb-select-editor-body .mb-select-options-container .mb-select-option-item .item-name { - display: inline-block; - padding: 0 10px; - margin-top: 5px; - height: 20px; - line-height: 20px; - text-align: center; - text-overflow: ellipsis; - border-radius: 10px; - overflow: hidden; - } - - .dtable-ui-mb-select-editor-body .mb-select-options-container .mb-select-option-item .dtable-font { - font-size: 12px; - color: #798d99; - } - - .dtable-ui-mb-select-editor-body .mb-create-select-item { - display: flex; - align-items: center; - height: 50px; - padding: 0 10px; - margin-top: 20px; - border-top: 1px solid #dedede; - border-bottom: 1px solid #e9e9e9; - background-color: #fff; - } - - .dtable-ui-mb-select-editor-body .mb-create-select-item .dtable-font { - margin-right: 10px; - font-size: 12px; - font-weight: 600px; - transition: translateY(1px); - } - - .dtable-ui-mb-select-editor-body .mb-create-select-item .dtable-ui-add-new-option { - font-size: 14px; - font-weight: 500px; - } - -} diff --git a/src/select-editor/mb-select-editor/index.js b/src/select-editor/mb-select-editor/index.js deleted file mode 100644 index 51d1b479..00000000 --- a/src/select-editor/mb-select-editor/index.js +++ /dev/null @@ -1,205 +0,0 @@ -import React, { Fragment } from 'react'; -import PropTypes from 'prop-types'; -import { getLocale } from '../../lang'; -import SelectEditorOption from '../select-editor-option'; -import MBEditorHeader from '../../MBEditorHeader'; - -import './index.css'; - -const propTypes = { - isReadOnly: PropTypes.bool, - isShowRemoveIcon: PropTypes.bool, - value: PropTypes.array.isRequired, - column: PropTypes.object.isRequired, - options: PropTypes.array.isRequired, - onOptionItemToggle: PropTypes.func, - onClosePopover: PropTypes.func, - isSupportNewOption: PropTypes.bool, - onAddNewOption: PropTypes.func, -}; - -class MBSelectEditorPopover extends React.Component { - - - static defaultProps = { - isReadOnly: false, - isShowRemoveIcon: false, - isSupportNewOption: false, - value: [], - }; - - constructor(props) { - super(props); - this.state = { - searchVal: '', - }; - } - - componentDidMount() { - history.pushState(null, null, '#'); // eslint-disable-line - window.addEventListener('popstate', this.handleHistoryBack, false); - } - - componentWillUnmount() { - window.removeEventListener('popstate', this.handleHistoryBack, false); - } - - handleHistoryBack = (e) => { - e.preventDefault(); - this.props.onClosePopover(); - }; - - onContainerClick = (event) => { - if (this.editorPopover && this.editorPopover.contains(event.target)) { - event.stopPropagation(); - event.nativeEvent.stopImmediatePropagation(); - return false; - } - }; - - onChangeSearch = (event) => { - let { searchVal } = this.state; - if (searchVal === event.target.value) { - return; - } - searchVal = event.target.value; - this.setState({ searchVal }); - }; - - getSelectedOptions = () => { - let { value, options } = this.props; - if (!Array.isArray(value)) { - return []; - } - return options.filter(option => { - return value.indexOf(option.id) > -1; - }); - }; - - getFilteredOptions = () => { - let { options } = this.props; - let { searchVal } = this.state; - return searchVal ? options.filter((item) => item.name.indexOf(searchVal) > -1) : options; - }; - - onSelectOption = (option) => { - this.props.onOptionItemToggle(option); - }; - - onAddNewOption = (event) => { - event.stopPropagation(); - event.nativeEvent.stopImmediatePropagation(); - let newOption = this.state.searchVal.trim(); - if (newOption) { - this.props.onAddNewOption(newOption); - this.props.onClosePopover(); - } - }; - - onRemoveOption = (option) => { - this.props.onOptionItemToggle(option); - }; - - renderSelectOptions = (options) => { - let { value } = this.props; - return options.map((option, index) => { - let isSelect = value.some(item => item === option.id); - let style = { - backgroundColor: option.color, - color: option.textColor || null, - }; - - return ( -
- - {option.name} - - - {isSelect && } - -
- ); - }); - }; - - setEditorPopover = (editorPopover) => { - this.editorPopover = editorPopover; - }; - - render() { - const { isReadOnly, column, isSupportNewOption, isShowRemoveIcon } = this.props; - const { searchVal } = this.state; - const selectedOptions = this.getSelectedOptions(); - const filteredOptions = this.getFilteredOptions(); - let isShowRemoveBtn = !isReadOnly && isShowRemoveIcon; - let isShowCreateBtn = !isReadOnly && isSupportNewOption && !!searchVal; - if (isShowCreateBtn) { - isShowCreateBtn = filteredOptions.length === 0; - } - - return ( -
- )} - onLeftClick={this.props.onClosePopover} - /> -
-
-
{getLocale('Current_option')}
-
- {selectedOptions.length === 0 && ( - {getLocale('No_option')} - )} - {selectedOptions.length > 0 && ( - selectedOptions.map(selectedOption => { - return ( - - ); - }) - )} -
-
-
- -
-
-
- {getLocale('Choose_an_option')} -
-
- - {filteredOptions.length === 0 && ( -
{getLocale('No_options_available')}
- )} - {filteredOptions.length > 0 && this.renderSelectOptions(filteredOptions)} -
-
-
- {isShowCreateBtn && ( -
- - {`${getLocale('Add_an_option')} ${searchVal}`} -
- )} -
-
- ); - } -} - -MBSelectEditorPopover.propTypes = propTypes; - -export default MBSelectEditorPopover; diff --git a/src/select-editor/pc-select-editor/index.css b/src/select-editor/pc-editor/index.css similarity index 95% rename from src/select-editor/pc-select-editor/index.css rename to src/select-editor/pc-editor/index.css index 778a9478..3a01cf77 100644 --- a/src/select-editor/pc-select-editor/index.css +++ b/src/select-editor/pc-editor/index.css @@ -8,7 +8,7 @@ height: 28px; } -.dtable-ui-select-editor-container .select-options-container { +.dtable-ui-select-editor-container .select-dtable-ui-mobile-selector-options-container { min-height: 160px; max-height: 200px; padding: 10px 0; diff --git a/src/select-editor/pc-select-editor/index.js b/src/select-editor/pc-editor/index.js similarity index 96% rename from src/select-editor/pc-select-editor/index.js rename to src/select-editor/pc-editor/index.js index 8d82be90..e4f7a9a3 100644 --- a/src/select-editor/pc-select-editor/index.js +++ b/src/select-editor/pc-editor/index.js @@ -102,7 +102,7 @@ class PCSelectEditor extends React.Component { option = this.filteredOptions[this.state.highlightIndex]; } if (option) { - this.onOptionItemToggle(option); + this.onChange(option); } else { const { searchValue } = this.state; if (searchValue && isSupportNewOption && this.filteredOptions.length === 0 ) { @@ -143,18 +143,18 @@ class PCSelectEditor extends React.Component { const { options } = this.props; if (oldSearchVal === searchValue) return; this.setState({ searchValue }); - let val = searchValue.toLowerCase(); + const val = searchValue.toLowerCase(); this.filteredOptions = val ? options.filter((item) => item.name && item.name.toLowerCase().indexOf(val) > -1) : options; this.setState({ highlightIndex: this.filteredOptions.length > 0 ? 0 : -1 }); }; onAddNewOption = () => { - let name = this.state.searchValue.trim(); + const name = this.state.searchValue.trim(); this.props.onAddNewOption(name); }; - onOptionItemToggle = (item) => { - this.props.onCommit(item); + onChange = (option) => { + this.props.onCommit(option); }; onMenuMouseEnter = (index) => { @@ -221,7 +221,7 @@ class PCSelectEditor extends React.Component { onChange={this.onValueChanged} autoFocus={true}/>
-
this.container = ref}> +
this.container = ref}> {this.filteredOptions.length > 0 && this.filteredOptions.map((option, index) => { let optionStyle = this.getOptionStyle(option); optionStyle = { ...optionStyle, maxWidth }; @@ -230,7 +230,7 @@ class PCSelectEditor extends React.Component {
this.selectItem = ref}>
diff --git a/src/select-editor/select-editor-option.js b/src/select-editor/select-editor-option.js deleted file mode 100644 index b37c30e6..00000000 --- a/src/select-editor/select-editor-option.js +++ /dev/null @@ -1,85 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; - -const propTypes = { - option: PropTypes.shape({ - id: PropTypes.string.isRequired, - name: PropTypes.string.isRequired, - color: PropTypes.string.isRequired, - }).isRequired, - isShowRemoveIcon: PropTypes.bool, - onDeleteSelectOption: PropTypes.func, -}; - -class SelectEditorOption extends React.Component { - - static defaultProps = { - isShowRemoveIcon: false, - }; - - onDeleteOption = (event) => { - event.nativeEvent.stopImmediatePropagation(); - event.stopPropagation(); - this.props.onDeleteSelectOption(this.props.option); - }; - - getContainerStyle = () => { - let option = this.props.option; - return { - display: 'inline-flex', - justifyContent: 'center', - marginRight: '10px', - padding: '0px 10px', - height: '20px', - borderRadius: '10px', - fontSize: '13px', - backgroundColor: option.color, - }; - }; - - getOptionStyle = (option) => { - const textColor = option.textColor || null; - return { - flex: 1, - display: 'flex', - alignContent: 'center', - margin: '0 4px 0 2px', - color: textColor, - }; - }; - - getOperationStyle = (option) => { - const textColor = option.textColor || null; - return { - height: '20px', - width: '16px', - cursor: 'pointer', - color: textColor === '#FFFFFF' ? '#FFFFFF' : '#909090', - transform: 'scale(.8)', - }; - }; - - render() { - let { option, isShowRemoveIcon } = this.props; - let containerStyle = this.getContainerStyle(); - let optionStyle = this.getOptionStyle(option); - let operationStyle = this.getOperationStyle(option); - - return ( -
-
-
{option.name}
-
- {isShowRemoveIcon && ( -
- -
- )} -
- ); - } -} - -SelectEditorOption.propTypes = propTypes; - -export default SelectEditorOption; diff --git a/src/Department-editor/utils.js b/src/utils/departments.js similarity index 100% rename from src/Department-editor/utils.js rename to src/utils/departments.js diff --git a/src/utils/editor-utils.js b/src/utils/editor-utils.js index 2c08ac65..79dda5f4 100644 --- a/src/utils/editor-utils.js +++ b/src/utils/editor-utils.js @@ -61,3 +61,7 @@ export const getSelectOptionItem = (options, optionId) => { export const getTrimmedString = (value) => { return (typeof value === 'string') ? value.trim() : ''; }; + +// min date and max date for mobile date picker +export const minDate = new Date('1900/01/01'); +export const maxDate = new Date('2100/12/31'); diff --git a/src/utils/get-event-transfer.js b/src/utils/get-event-transfer.js index 2b98d74e..844da618 100644 --- a/src/utils/get-event-transfer.js +++ b/src/utils/get-event-transfer.js @@ -1,3 +1,5 @@ +import { isMobile } from '../constants'; + const HTML = 'text/html'; const TEXT = 'text/plain'; // const FILES = 'files'; @@ -35,7 +37,7 @@ function getEventTransfer(event) { let html; let text; let files; - if (window.isMobile) { + if (isMobile) { if (window.dtableTransfer) { text = window.dtableTransfer['TEXT']; } diff --git a/src/utils/utils.js b/src/utils/utils.js index d1af5d39..6d85c648 100644 --- a/src/utils/utils.js +++ b/src/utils/utils.js @@ -27,15 +27,22 @@ export const throttle = (func, delay) => { }; }; -export const isMobile = (typeof (window) !== 'undefined') && (window.innerWidth < 768 || - navigator.userAgent.toLowerCase().match(/(ipod|ipad|iphone|android|coolpad|mmp|smartphone|midp|wap|xoom|symbian|j2me|blackberry|wince)/i) != null); - export const isMac = () => { const platform = navigator.platform; // eslint-disable-next-line eqeqeq return (platform == 'Mac68K') || (platform == 'MacPPC') || (platform == 'Macintosh') || (platform == 'MacIntel'); }; +export const isQQBuiltInBrowser = () => { + const userAgent = navigator.userAgent.toLowerCase(); + return userAgent.indexOf(' qq') > -1 && userAgent.indexOf('mqqbrowser') < 0; +}; + +export const isIPhone = () => { + const userAgent = navigator.userAgent.toLowerCase(); + return /iphone/gi.test(userAgent); +}; + export const downloadFile = (downloadUrl) => { const downloadFrame = document.getElementById('dtableUiComponentDownloadFrame'); if (downloadFrame != null) {