From b043cb8491d548d66f740c865c01080d2f3289ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=A8=E5=9B=BD=E7=92=87?= <37972689+YangGuoXuan-0503@users.noreply.github.com> Date: Tue, 6 Aug 2024 17:30:11 +0800 Subject: [PATCH] feat: side properties (#6485) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: side properties * feat: show metadata * feat: delete record expand * feat: optimzie code * fix: bug --------- Co-authored-by: 杨国璇 --- .../column/column-name.js | 22 -- .../column/index.css | 7 - .../column/index.js | 37 -- .../editor/ctime-formatter.js | 22 -- .../editor/date-editor.js | 28 -- .../editor/formula-formatter.js | 31 -- .../editor/index.js | 20 - .../editor/number-editor.js | 90 ----- .../editor/search-input.js | 108 ------ .../editor/simple-text.js | 92 ----- .../editor/single-select/index.css | 102 ----- .../editor/single-select/index.js | 84 ----- .../single-select/single-select-editor.js | 141 ------- .../index.css | 17 - .../extra-metadata-attributes-dialog/index.js | 235 ------------ .../dirent-detail/detail-item/index.css | 6 + .../dirent-detail/detail-item/index.js | 9 +- .../dirent-detail/detail-list-view.js | 4 +- .../dirent-details/dir-details.js | 15 +- .../dirent-details/edit-metadata/index.css | 31 -- .../dirent-details/edit-metadata/index.js | 41 -- .../dirent-details/file-details.js | 46 ++- .../dirent-detail/dirent-details/index.css | 2 +- .../dirent-detail/dirent-details/index.js | 195 ++++++---- .../dirent-detail/dirent-details/utils.js | 3 +- .../components/dirent-detail/header/index.css | 1 - .../src/components/dirent-detail/index.js | 46 ++- .../dirent-detail/lib-details/index.js | 14 +- frontend/src/components/search/ai-search.js | 4 +- frontend/src/components/search/search.js | 3 +- frontend/src/constants/index.js | 103 ----- frontend/src/metadata/index.js | 2 + .../metadata/metadata-details/constants.js | 22 ++ .../src/metadata/metadata-details/index.css | 41 ++ .../src/metadata/metadata-details/index.js | 53 +++ .../src/metadata/metadata-details/utils.js | 25 ++ .../metadata-view/components/index.js | 2 - .../components/record-details-dialog/index.js | 22 -- .../record-details/field-label/index.css | 31 -- .../record-details/field-label/index.js | 42 --- .../record-details/index.css | 318 ---------------- .../record-details/index.js | 133 ------- .../components/table/container.js | 2 - .../metadata-view/components/table/index.css | 1 - .../components/table/table-main/index.js | 4 - .../table/table-main/records/body.js | 6 - .../table-main/records/group-body/index.js | 7 +- .../records/record/actions-cell/index.js | 5 - .../table/table-main/records/record/index.js | 20 +- .../records/records-header/actions-cell.jsx | 1 - .../metadata-view/hooks/collaborators.js | 8 +- .../src/metadata/metadata-view/hooks/index.js | 2 - .../metadata-view/hooks/record-details.js | 34 -- frontend/src/metadata/metadata-view/index.js | 6 +- .../metadata-view/utils/column-utils.js | 2 +- .../metadata-view/utils/group-metrics.js | 4 +- .../metadata-view/utils/object-utils.js | 1 + .../lib-content-view/lib-content-container.js | 29 +- .../lib-content-view/lib-content-view.js | 4 +- frontend/src/utils/extra-attributes.js | 351 ------------------ frontend/src/utils/number-precision.js | 122 ------ frontend/src/utils/utils.js | 7 +- seahub/api2/endpoints/metadata_manage.py | 32 +- 63 files changed, 408 insertions(+), 2490 deletions(-) delete mode 100644 frontend/src/components/dialog/extra-metadata-attributes-dialog/column/column-name.js delete mode 100644 frontend/src/components/dialog/extra-metadata-attributes-dialog/column/index.css delete mode 100644 frontend/src/components/dialog/extra-metadata-attributes-dialog/column/index.js delete mode 100644 frontend/src/components/dialog/extra-metadata-attributes-dialog/editor/ctime-formatter.js delete mode 100644 frontend/src/components/dialog/extra-metadata-attributes-dialog/editor/date-editor.js delete mode 100644 frontend/src/components/dialog/extra-metadata-attributes-dialog/editor/formula-formatter.js delete mode 100644 frontend/src/components/dialog/extra-metadata-attributes-dialog/editor/index.js delete mode 100644 frontend/src/components/dialog/extra-metadata-attributes-dialog/editor/number-editor.js delete mode 100644 frontend/src/components/dialog/extra-metadata-attributes-dialog/editor/search-input.js delete mode 100644 frontend/src/components/dialog/extra-metadata-attributes-dialog/editor/simple-text.js delete mode 100644 frontend/src/components/dialog/extra-metadata-attributes-dialog/editor/single-select/index.css delete mode 100644 frontend/src/components/dialog/extra-metadata-attributes-dialog/editor/single-select/index.js delete mode 100644 frontend/src/components/dialog/extra-metadata-attributes-dialog/editor/single-select/single-select-editor.js delete mode 100644 frontend/src/components/dialog/extra-metadata-attributes-dialog/index.css delete mode 100644 frontend/src/components/dialog/extra-metadata-attributes-dialog/index.js delete mode 100644 frontend/src/components/dirent-detail/dirent-details/edit-metadata/index.css delete mode 100644 frontend/src/components/dirent-detail/dirent-details/edit-metadata/index.js create mode 100644 frontend/src/metadata/metadata-details/constants.js create mode 100644 frontend/src/metadata/metadata-details/index.css create mode 100644 frontend/src/metadata/metadata-details/index.js create mode 100644 frontend/src/metadata/metadata-details/utils.js delete mode 100644 frontend/src/metadata/metadata-view/components/record-details-dialog/index.js delete mode 100644 frontend/src/metadata/metadata-view/components/record-details-dialog/record-details/field-label/index.css delete mode 100644 frontend/src/metadata/metadata-view/components/record-details-dialog/record-details/field-label/index.js delete mode 100644 frontend/src/metadata/metadata-view/components/record-details-dialog/record-details/index.css delete mode 100644 frontend/src/metadata/metadata-view/components/record-details-dialog/record-details/index.js delete mode 100644 frontend/src/metadata/metadata-view/hooks/record-details.js delete mode 100644 frontend/src/utils/extra-attributes.js delete mode 100644 frontend/src/utils/number-precision.js diff --git a/frontend/src/components/dialog/extra-metadata-attributes-dialog/column/column-name.js b/frontend/src/components/dialog/extra-metadata-attributes-dialog/column/column-name.js deleted file mode 100644 index 2252e283c29..00000000000 --- a/frontend/src/components/dialog/extra-metadata-attributes-dialog/column/column-name.js +++ /dev/null @@ -1,22 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { Col } from 'reactstrap'; - -function ColumnName(props) { - const { column } = props; - const { name } = column; - - return ( - -
- {name || ''} -
- - ); -} - -ColumnName.propTypes = { - column: PropTypes.object.isRequired, -}; - -export default ColumnName; diff --git a/frontend/src/components/dialog/extra-metadata-attributes-dialog/column/index.css b/frontend/src/components/dialog/extra-metadata-attributes-dialog/column/index.css deleted file mode 100644 index fe3e8a1d61d..00000000000 --- a/frontend/src/components/dialog/extra-metadata-attributes-dialog/column/index.css +++ /dev/null @@ -1,7 +0,0 @@ -.extra-attributes-dialog .column-name { - padding-top: 9px; -} - -.extra-attributes-dialog .column-item { - min-height: 56px; -} diff --git a/frontend/src/components/dialog/extra-metadata-attributes-dialog/column/index.js b/frontend/src/components/dialog/extra-metadata-attributes-dialog/column/index.js deleted file mode 100644 index 164a7a2a439..00000000000 --- a/frontend/src/components/dialog/extra-metadata-attributes-dialog/column/index.js +++ /dev/null @@ -1,37 +0,0 @@ -import React, { Component } from 'react'; -import PropTypes from 'prop-types'; -import { Col } from 'reactstrap'; -import ColumnName from './column-name'; -import CONFIG from '../editor'; - -import './index.css'; - -class Column extends Component { - render() { - const { column, row, columns } = this.props; - const Editor = CONFIG[column.type] || CONFIG['text']; - - return ( -
- - - - -
- ); - } -} - -Column.propTypes = { - column: PropTypes.object, - row: PropTypes.object, - columns: PropTypes.array, - onCommit: PropTypes.func, -}; - -export default Column; diff --git a/frontend/src/components/dialog/extra-metadata-attributes-dialog/editor/ctime-formatter.js b/frontend/src/components/dialog/extra-metadata-attributes-dialog/editor/ctime-formatter.js deleted file mode 100644 index 43334ade82d..00000000000 --- a/frontend/src/components/dialog/extra-metadata-attributes-dialog/editor/ctime-formatter.js +++ /dev/null @@ -1,22 +0,0 @@ -import React, { Component } from 'react'; -import PropTypes from 'prop-types'; -import { getDateDisplayString } from '../../../../utils/extra-attributes'; - -class CtimeFormatter extends Component { - render() { - const { column, row } = this.props; - const { key } = column; - const value = getDateDisplayString(row[key], 'YYYY-MM-DD HH:mm:ss') || ''; - - return ( -
{value}
- ); - } -} - -CtimeFormatter.propTypes = { - column: PropTypes.object, - row: PropTypes.object, -}; - -export default CtimeFormatter; diff --git a/frontend/src/components/dialog/extra-metadata-attributes-dialog/editor/date-editor.js b/frontend/src/components/dialog/extra-metadata-attributes-dialog/editor/date-editor.js deleted file mode 100644 index e72abd3e0ed..00000000000 --- a/frontend/src/components/dialog/extra-metadata-attributes-dialog/editor/date-editor.js +++ /dev/null @@ -1,28 +0,0 @@ -import React, { Component } from 'react'; -import PropTypes from 'prop-types'; -import { getDateDisplayString } from '../../../../utils/extra-attributes'; - - -class DateEditor extends Component { - render() { - const { column, row } = this.props; - const { data, key } = column; - const value = getDateDisplayString(row[key], data ? data.format : ''); - - return ( - - ); - } -} - -DateEditor.propTypes = { - column: PropTypes.object, - row: PropTypes.object, -}; - -export default DateEditor; diff --git a/frontend/src/components/dialog/extra-metadata-attributes-dialog/editor/formula-formatter.js b/frontend/src/components/dialog/extra-metadata-attributes-dialog/editor/formula-formatter.js deleted file mode 100644 index d901a6648cf..00000000000 --- a/frontend/src/components/dialog/extra-metadata-attributes-dialog/editor/formula-formatter.js +++ /dev/null @@ -1,31 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { FORMULA_RESULT_TYPE } from '../../../../constants'; -import { getDateDisplayString } from '../../../../utils/extra-attributes'; - -function FormulaFormatter(props) { - const { column, row } = props; - const value = row[column.key]; - - const { data } = column; - const { result_type, format } = data || {}; - if (result_type === FORMULA_RESULT_TYPE.DATE) { - return ( -
{getDateDisplayString(value, format)}
- ); - } - if (result_type === FORMULA_RESULT_TYPE.STRING) { - return value; - } - if (typeof value === 'object') { - return null; - } - return <>; -} - -FormulaFormatter.propTypes = { - column: PropTypes.object, - row: PropTypes.object, -}; - -export default FormulaFormatter; diff --git a/frontend/src/components/dialog/extra-metadata-attributes-dialog/editor/index.js b/frontend/src/components/dialog/extra-metadata-attributes-dialog/editor/index.js deleted file mode 100644 index 63cd1f2dd4f..00000000000 --- a/frontend/src/components/dialog/extra-metadata-attributes-dialog/editor/index.js +++ /dev/null @@ -1,20 +0,0 @@ -import SimpleText from './simple-text'; -import FormulaFormatter from './formula-formatter'; -import SingleSelect from './single-select'; -import NumberEditor from './number-editor'; -import DateEditor from './date-editor'; -import CtimeFormatter from './ctime-formatter'; -import { EXTRA_ATTRIBUTES_COLUMN_TYPE } from '../../../../constants'; - - -const CONFIG = { - [EXTRA_ATTRIBUTES_COLUMN_TYPE.TEXT]: SimpleText, - [EXTRA_ATTRIBUTES_COLUMN_TYPE.FORMULA]: FormulaFormatter, - [EXTRA_ATTRIBUTES_COLUMN_TYPE.SINGLE_SELECT]: SingleSelect, - [EXTRA_ATTRIBUTES_COLUMN_TYPE.NUMBER]: NumberEditor, - [EXTRA_ATTRIBUTES_COLUMN_TYPE.DATE]: DateEditor, - [EXTRA_ATTRIBUTES_COLUMN_TYPE.CTIME]: CtimeFormatter, - [EXTRA_ATTRIBUTES_COLUMN_TYPE.MTIME]: CtimeFormatter, -}; - -export default CONFIG; diff --git a/frontend/src/components/dialog/extra-metadata-attributes-dialog/editor/number-editor.js b/frontend/src/components/dialog/extra-metadata-attributes-dialog/editor/number-editor.js deleted file mode 100644 index 00c05cf5905..00000000000 --- a/frontend/src/components/dialog/extra-metadata-attributes-dialog/editor/number-editor.js +++ /dev/null @@ -1,90 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { getNumberDisplayString, replaceNumberNotAllowInput, formatStringToNumber, isMac } from '../../../../utils/extra-attributes'; -import { KeyCodes, DEFAULT_NUMBER_FORMAT } from '../../../../constants'; - -class NumberEditor extends React.Component { - - constructor(props) { - super(props); - const { row, column } = props; - const value = row[column.key]; - this.state = { - value: getNumberDisplayString(value, column.data), - }; - } - - onChange = (event) => { - const { data } = this.props.column; // data maybe 'null' - const format = (data && data.format) ? data.format : DEFAULT_NUMBER_FORMAT; - let currency_symbol = null; - if (data && data.format === 'custom_currency') { - currency_symbol = data['currency_symbol']; - } - const initValue = event.target.value.trim(); - - // Prevent the repetition of periods bug in the Chinese input method of the Windows system - if (!isMac() && initValue.indexOf('.。') > -1) return; - let value = replaceNumberNotAllowInput(initValue, format, currency_symbol); - if (value === this.state.value) return; - this.setState({ value }); - }; - - onKeyDown = (event) => { - let { selectionStart, selectionEnd, value } = event.currentTarget; - if (event.keyCode === KeyCodes.Enter || event.keyCode === KeyCodes.Esc) { - event.preventDefault(); - this.input.blur(); - } else if ((event.keyCode === KeyCodes.LeftArrow && selectionStart === 0) || - (event.keyCode === KeyCodes.RightArrow && selectionEnd === value.length) - ) { - event.stopPropagation(); - } - }; - - onBlur = () => { - const { value } = this.state; - const { column } = this.props; - this.props.onCommit({ [column.key]: formatStringToNumber(value, column.data) }, column); - }; - - setInputRef = (input) => { - this.input = input; - return this.input; - }; - - onPaste = (e) => { - e.stopPropagation(); - }; - - onCut = (e) => { - e.stopPropagation(); - }; - - render() { - const { column } = this.props; - - return ( - - ); - } -} - -NumberEditor.propTypes = { - column: PropTypes.object, - row: PropTypes.object, - onCommit: PropTypes.func, -}; - -export default NumberEditor; diff --git a/frontend/src/components/dialog/extra-metadata-attributes-dialog/editor/search-input.js b/frontend/src/components/dialog/extra-metadata-attributes-dialog/editor/search-input.js deleted file mode 100644 index 2ed758e3a64..00000000000 --- a/frontend/src/components/dialog/extra-metadata-attributes-dialog/editor/search-input.js +++ /dev/null @@ -1,108 +0,0 @@ -import React, { Component } from 'react'; -import PropTypes from 'prop-types'; -import classnames from 'classnames'; - -class SearchInput extends Component { - - constructor(props) { - super(props); - this.state = { - searchValue: props.value, - }; - this.isInputtingChinese = false; - this.timer = null; - this.inputRef = null; - } - - componentDidMount() { - if (this.props.autoFocus && this.inputRef && this.inputRef !== document.activeElement) { - setTimeout(() => { - this.inputRef.focus(); - }, 0); - } - } - - UNSAFE_componentWillReceiveProps(nextProps) { - if (nextProps.value !== this.props.value) { - this.setState({ searchValue: nextProps.value }); - } - } - - componentWillUnmount() { - this.timer && clearTimeout(this.timer); - this.timer = null; - this.inputRef = null; - } - - onCompositionStart = () => { - this.isInputtingChinese = true; - }; - - onChange = (e) => { - this.timer && clearTimeout(this.timer); - const { onChange, wait } = this.props; - let text = e.target.value; - this.setState({ searchValue: text || '' }, () => { - if (this.isInputtingChinese) return; - this.timer = setTimeout(() => { - onChange && onChange(this.state.searchValue.trim()); - }, wait); - }); - }; - - onCompositionEnd = (e) => { - this.isInputtingChinese = false; - this.onChange(e); - }; - - setFocus = (isSelectAllText) => { - if (this.inputRef === document.activeElement) return; - this.inputRef.focus(); - if (isSelectAllText) { - const txtLength = this.state.searchValue.length; - this.inputRef.setSelectionRange(0, txtLength); - } - }; - - render() { - const { placeholder, autoFocus, className, onKeyDown, disabled, style } = this.props; - const { searchValue } = this.state; - - return ( - this.inputRef = ref} - /> - ); - } -} - -SearchInput.propTypes = { - placeholder: PropTypes.string, - autoFocus: PropTypes.bool, - className: PropTypes.string, - onChange: PropTypes.func.isRequired, - onKeyDown: PropTypes.func, - wait: PropTypes.number, - disabled: PropTypes.bool, - style: PropTypes.object, - value: PropTypes.string, -}; - -SearchInput.defaultProps = { - wait: 100, - disabled: false, - value: '', -}; - -export default SearchInput; diff --git a/frontend/src/components/dialog/extra-metadata-attributes-dialog/editor/simple-text.js b/frontend/src/components/dialog/extra-metadata-attributes-dialog/editor/simple-text.js deleted file mode 100644 index 8d9f9fa15a1..00000000000 --- a/frontend/src/components/dialog/extra-metadata-attributes-dialog/editor/simple-text.js +++ /dev/null @@ -1,92 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { KeyCodes } from '../../../../constants'; - -class SimpleText extends React.Component { - - constructor(props) { - super(props); - this.state = { - value: props.row[props.column.key] || '', - }; - this.inputRef = React.createRef(); - } - - UNSAFE_componentWillReceiveProps(nextProps) { - const nextValue = nextProps.row[nextProps.column.key]; - if (nextValue !== this.state.value) { - this.setState({ value: nextValue }); - } - } - - blurInput = () => { - setTimeout(() => { - this.inputRef.current && this.inputRef.current.blur(); - }, 1); - }; - - onBlur = () => { - let { column, onCommit } = this.props; - const updated = {}; - updated[column.key] = this.state.value.trim(); - onCommit(updated, column); - }; - - onChange = (e) => { - let value = e.target.value; - if (value === this.state.value) return; - this.setState({ value }); - }; - - onCut = (e) => { - e.stopPropagation(); - }; - - onPaste = (e) => { - e.stopPropagation(); - }; - - onKeyDown = (e) => { - if (e.keyCode === KeyCodes.Esc) { - e.stopPropagation(); - this.blurInput(); - return; - } - let { selectionStart, selectionEnd, value } = e.currentTarget; - if ( - (e.keyCode === KeyCodes.ChineseInputMethod) || - (e.keyCode === KeyCodes.LeftArrow && selectionStart === 0) || - (e.keyCode === KeyCodes.RightArrow && selectionEnd === value.length) - ) { - e.stopPropagation(); - } - }; - - render() { - const { column } = this.props; - const { value } = this.state; - - return ( - - ); - } -} - -SimpleText.propTypes = { - column: PropTypes.object, - row: PropTypes.object, - onCommit: PropTypes.func.isRequired, -}; - -export default SimpleText; diff --git a/frontend/src/components/dialog/extra-metadata-attributes-dialog/editor/single-select/index.css b/frontend/src/components/dialog/extra-metadata-attributes-dialog/editor/single-select/index.css deleted file mode 100644 index cee008435ee..00000000000 --- a/frontend/src/components/dialog/extra-metadata-attributes-dialog/editor/single-select/index.css +++ /dev/null @@ -1,102 +0,0 @@ -.extra-attributes-dialog .selected-single-select-container { - height: 38px; - width: 100%; - padding: 0 10px; - border-radius: 3px; - user-select: none; - border: 1px solid rgba(0, 40, 100, .12); - appearance: none; - background: #fff; -} - -.extra-attributes-dialog .selected-single-select-container.disable { - background-color: #f8f9fa; -} - -.extra-attributes-dialog .selected-single-select-container.focus { - border-color: #1991eb!important; - box-shadow: 0 0 0 2px rgba(70, 127, 207, .25); -} - -.extra-attributes-dialog .selected-single-select-container:not(.disable):hover { - cursor: pointer; -} - -.extra-attributes-dialog .selected-single-select-container .single-select-option { - text-align: center; - width: min-content; - max-width: 250px; - line-height: 20px; - border-radius: 10px; - padding: 0 10px; - font-size: 13px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -/* editor */ -.single-select-editor-popover .popover, -.single-select-editor-popover .popover-inner { - width: fit-content; - max-width: fit-content; -} - -.single-select-editor-container { - min-height: 160px; - width: 320px; - overflow: hidden; - background-color: #fff; -} - -.single-select-editor-container .search-single-selects { - padding: 10px 10px 0; -} - -.single-select-editor-container .search-single-selects input { - max-height: 30px; - font-size: 14px; -} - -.single-select-editor-container .single-select-editor-content { - max-height: 200px; - min-height: 100px; - padding: 10px; - overflow-x: hidden; - overflow-y: scroll; -} - -.single-select-editor-container .single-select-editor-content .single-select-option-container { - width: 100%; - height: 30px; - border-radius: 2px; - display: flex; - align-items: center; - justify-content: space-between; - font-size: 13px; - color: #212529; - padding-left: 12px; -} - -.single-select-editor-container .single-select-editor-content .single-select-option-container:hover { - background-color: #f5f5f5; - cursor: pointer; -} - -.single-select-editor-container .single-select-editor-content .single-select-option { - padding: 0 10px; - height: 20px; - line-height: 20px; - text-align: center; - border-radius: 10px; - margin-right: 10px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.single-select-editor-container .single-select-editor-content .single-select-option-selected { - width: 20px; - text-align: center; -} - diff --git a/frontend/src/components/dialog/extra-metadata-attributes-dialog/editor/single-select/index.js b/frontend/src/components/dialog/extra-metadata-attributes-dialog/editor/single-select/index.js deleted file mode 100644 index 7ba627fd59e..00000000000 --- a/frontend/src/components/dialog/extra-metadata-attributes-dialog/editor/single-select/index.js +++ /dev/null @@ -1,84 +0,0 @@ -import React, { Component } from 'react'; -import PropTypes from 'prop-types'; -import classnames from 'classnames'; -import { DELETED_OPTION_BACKGROUND_COLOR, DELETED_OPTION_TIPS } from '../../../../../constants'; -import { gettext } from '../../../../../utils/constants'; -import SingleSelectEditor from './single-select-editor'; -import { getSelectColumnOptions } from '../../../../../utils/extra-attributes'; - -import './index.css'; - -class SingleSelect extends Component { - - constructor(props) { - super(props); - const { column } = props; - this.options = getSelectColumnOptions(column); - this.state = { - isShowSingleSelect: false, - }; - this.editorKey = `single-select-editor-${column.key}`; - } - - updateState = () => { - this.setState({ isShowSingleSelect: !this.state.isShowSingleSelect }); - }; - - onCommit = (value, column) => { - this.props.onCommit(value, column); - }; - - render() { - const { isShowSingleSelect } = this.state; - const { column, row } = this.props; - const currentOptionID = row[column.key]; - const option = this.options.find(option => option.id === currentOptionID); - const optionStyle = option ? - { backgroundColor: option.color, color: option.textColor || null } : - { backgroundColor: DELETED_OPTION_BACKGROUND_COLOR }; - const optionName = option ? option.name : gettext(DELETED_OPTION_TIPS); - - return ( - <> -
-
-
- {currentOptionID && ( -
{optionName} -
- )} -
- {column.editable && ( - - )} -
-
- {column.editable && ( - - )} - - ); - } -} - -SingleSelect.propTypes = { - column: PropTypes.object, - row: PropTypes.object, - columns: PropTypes.array, - onCommit: PropTypes.func, -}; - -export default SingleSelect; diff --git a/frontend/src/components/dialog/extra-metadata-attributes-dialog/editor/single-select/single-select-editor.js b/frontend/src/components/dialog/extra-metadata-attributes-dialog/editor/single-select/single-select-editor.js deleted file mode 100644 index 719b8c70241..00000000000 --- a/frontend/src/components/dialog/extra-metadata-attributes-dialog/editor/single-select/single-select-editor.js +++ /dev/null @@ -1,141 +0,0 @@ -import React, { Component } from 'react'; -import PropTypes from 'prop-types'; -import { UncontrolledPopover } from 'reactstrap'; -import { gettext } from '../../../../../utils/constants'; -import SearchInput from '../search-input'; -import { getSelectColumnOptions } from '../../../../../utils/extra-attributes'; - -class SingleSelectEditor extends Component { - - constructor(props) { - super(props); - const options = this.getSelectColumnOptions(props); - this.state = { - value: props.row[props.column.key], - searchVal: '', - highlightIndex: -1, - maxItemNum: 0, - itemHeight: 0, - filteredOptions: options, - }; - this.options = options; - this.timer = null; - this.editorKey = `single-select-editor-${props.column.key}`; - } - - UNSAFE_componentWillReceiveProps(nextProps) { - const currentCascadeColumnValue = this.getCascadeColumnValue(this.props); - const nextCascadeColumnValue = this.getCascadeColumnValue(nextProps); - if (currentCascadeColumnValue !== nextCascadeColumnValue) { - this.options = this.getSelectColumnOptions(nextProps); - this.setState({ filteredOptions: this.options }); - } - } - - getCascadeColumnValue = (props) => { - const { column, row, columns } = props; - const { data } = column; - const { cascade_column_key } = data || {}; - if (!cascade_column_key) return ''; - const cascadeColumn = columns.find(item => item.key === cascade_column_key); - if (!cascadeColumn) return ''; - return row[cascade_column_key]; - }; - - getSelectColumnOptions = (props) => { - const { column, row, columns } = props; - let options = getSelectColumnOptions(column); - const { data } = column; - const { cascade_column_key, cascade_settings } = data || {}; - if (cascade_column_key) { - const cascadeColumn = columns.find(item => item.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; - }; - - toggle = () => { - this.ref.toggle(); - this.props.onUpdateState(); - }; - - onChangeSearch = (searchVal) => { - const { searchVal: oldSearchVal } = this.state; - if (oldSearchVal === searchVal) return; - const val = searchVal.toLowerCase(); - const filteredOptions = val ? - this.options.filter((item) => item.name && item.name.toLowerCase().indexOf(val) > -1) : this.options; - this.setState({ searchVal, filteredOptions }); - }; - - onSelectOption = (optionID) => { - const { column } = this.props; - this.setState({ value: optionID }, () => { - this.props.onCommit({ [column.key]: optionID }, column); - this.toggle(); - }); - }; - - render() { - const { value, filteredOptions } = this.state; - const { column } = this.props; - - return ( - this.ref = ref} - > -
-
- -
-
- {filteredOptions.map(option => { - const isSelected = value === option.id; - const style = { - backgroundColor: option.color, - color: option.textColor || null, - maxWidth: Math.max(200 - 62, column.width ? column.width - 62 : 0) - }; - return ( -
-
{option.name}
-
- {isSelected && ()} -
-
- ); - })} -
-
-
- ); - } -} - -SingleSelectEditor.propTypes = { - value: PropTypes.string, - row: PropTypes.object, - column: PropTypes.object, - columns: PropTypes.array, - onUpdateState: PropTypes.func, - onCommit: PropTypes.func, -}; - -export default SingleSelectEditor; diff --git a/frontend/src/components/dialog/extra-metadata-attributes-dialog/index.css b/frontend/src/components/dialog/extra-metadata-attributes-dialog/index.css deleted file mode 100644 index f96ae21d00e..00000000000 --- a/frontend/src/components/dialog/extra-metadata-attributes-dialog/index.css +++ /dev/null @@ -1,17 +0,0 @@ -.extra-attributes-dialog { - margin: 28px 0 0 0; -} - -.extra-attributes-dialog .extra-attributes-content-container { - height: 100%; - overflow: hidden; -} - -.extra-attributes-dialog .modal-body { - overflow-y: scroll; - padding: 30px; -} - -.extra-attributes-dialog .modal-body .form-control.disabled { - background-color: #f8f9fa; -} diff --git a/frontend/src/components/dialog/extra-metadata-attributes-dialog/index.js b/frontend/src/components/dialog/extra-metadata-attributes-dialog/index.js deleted file mode 100644 index 895bf3c10d7..00000000000 --- a/frontend/src/components/dialog/extra-metadata-attributes-dialog/index.js +++ /dev/null @@ -1,235 +0,0 @@ -import React, { Component } from 'react'; -import PropTypes from 'prop-types'; -import { Modal, ModalHeader, ModalBody } from 'reactstrap'; -import isHotkey from 'is-hotkey'; -import { zIndexes, DIALOG_MAX_HEIGHT } from '../../../constants'; -import { gettext } from '../../../utils/constants'; -import { Utils } from '../../../utils/utils'; -import { getValidColumns } from '../../../utils/extra-attributes'; -import Column from './column'; -import Loading from '../../loading'; -import toaster from '../../toast'; -import metadataAPI from '../../../metadata/api'; - -import './index.css'; - - -class ExtraMetadataAttributesDialog extends Component { - - constructor(props) { - super(props); - const { direntDetail, direntType } = props; - this.state = { - animationEnd: false, - isLoading: true, - update: {}, - row: {}, - columns: [], - errorMsg: '', - }; - if (direntType === 'dir') { - this.isEmptyFile = false; - } else { - const direntDetailId = direntDetail?.id || ''; - this.isEmptyFile = direntDetailId === '0'.repeat(direntDetailId.length); - } - this.isExist = false; - this.modalRef = React.createRef(); - } - - componentDidMount() { - this.startAnimation(this.getData); - window.addEventListener('keydown', this.onHotKey); - } - - componentWillUnmount() { - window.removeEventListener('keydown', this.onHotKey); - } - - startAnimation = (callback) => { - if (this.state.animationEnd === true) { - callback && callback(); - } - - // use setTimeout to make sure real dom rendered - setTimeout(() => { - let dom = this.modalRef.current.firstChild; - const { width, maxWidth, marginLeft, height } = this.getDialogStyle(); - dom.style.width = `${width}px`; - dom.style.maxWidth = `${maxWidth}px`; - dom.style.marginLeft = `${marginLeft}px`; - dom.style.height = `${height}px`; - dom.style.marginRight = 'unset'; - dom.style.marginTop = '28px'; - - // after animation, change style and run callback - setTimeout(() => { - this.setState({ animationEnd: true }, () => { - dom.style.transition = 'none'; - callback && callback(); - }); - }, 280); - }, 1); - }; - - getData = () => { - const { repoID, filePath, direntType } = this.props; - - let dirName = Utils.getDirName(filePath); - let fileName = Utils.getFileName(filePath); - let parentDir = direntType === 'file' ? dirName : dirName.slice(0, dirName.length - fileName.length - 1); - - if (!parentDir.startsWith('/')) { - parentDir = '/' + parentDir; - } - - metadataAPI.getMetadataRecordInfo(repoID, parentDir, fileName).then(res => { - const { row, metadata, editable_columns } = res.data; - this.isExist = Boolean(row._id); - this.setState({ row: row, columns: getValidColumns(metadata, editable_columns, this.isEmptyFile), isLoading: false, errorMsg: '' }); - }).catch(error => { - const errorMsg = Utils.getErrorMsg(error); - this.setState({ isLoading: false, errorMsg }); - }); - }; - - updateData = (update, column) => { - const newRow = { ...this.state.row, ...update }; - this.setState({ row: newRow }, () => { - const { repoID } = this.props; - - let newValue = update[column.key]; - let recordID = this.state.row._id; - if (this.isExist) { - metadataAPI.modifyRecord(repoID, recordID, { [column.name]: newValue }).then(res => { - this.setState({ update: {}, row: res.data.row }); - }).catch(error => { - const errorMsg = Utils.getErrorMsg(error); - toaster.danger(gettext(errorMsg)); - }); - } else { - // this.createData(data); - } - }); - }; - - onHotKey = (event) => { - if (isHotkey('esc', event)) { - this.onToggle(); - return; - } - }; - - onToggle = () => { - this.props.onToggle(); - }; - - getDialogStyle = () => { - const width = 800; - return { - width, - maxWidth: width, - marginLeft: (window.innerWidth - width) / 2, - height: DIALOG_MAX_HEIGHT, - }; - }; - - getInitStyle = () => { - const transition = 'all .3s'; - const defaultMargin = 80; // sequence cell width - const defaultHeight = 100; - const marginTop = '30%'; - const width = window.innerWidth; - return { - width: `${width - defaultMargin}px`, - maxWidth: `${width - defaultMargin}px`, - marginLeft: `${defaultMargin}px`, - height: `${defaultHeight}px`, - marginRight: `${defaultMargin}px`, - marginTop, - transition, - }; - }; - - renderColumns = () => { - const { isLoading, errorMsg, columns, row, update } = this.state; - if (isLoading) { - return ( -
- -
- ); - } - - if (errorMsg) { - return ( -
- {gettext(errorMsg)} -
- ); - } - - const newRow = { ...row, ...update }; - - return ( - <> - {columns.map(column => { - return ( - - ); - })} - - ); - - }; - - renderContent = () => { - if (!this.state.animationEnd) return null; - - return ( - <> - {gettext('Edit extra properties')} - - {this.renderColumns()} - - - ); - }; - - render() { - const { animationEnd } = this.state; - - return ( - - {this.renderContent()} - - ); - } -} - -ExtraMetadataAttributesDialog.propTypes = { - repoID: PropTypes.string, - filePath: PropTypes.string, - direntType: PropTypes.string, - direntDetail: PropTypes.object, - onToggle: PropTypes.func, -}; - -export default ExtraMetadataAttributesDialog; diff --git a/frontend/src/components/dirent-detail/detail-item/index.css b/frontend/src/components/dirent-detail/detail-item/index.css index 95e67b54b98..d58abf70b76 100644 --- a/frontend/src/components/dirent-detail/detail-item/index.css +++ b/frontend/src/components/dirent-detail/detail-item/index.css @@ -49,3 +49,9 @@ .dirent-detail-item-value .creator-formatter { height: 20px; } + +.dirent-detail-item-value .sf-metadata-record-cell-empty::before { + content: attr(placeholder); + color: #666; + font-size: 14px; +} diff --git a/frontend/src/components/dirent-detail/detail-item/index.js b/frontend/src/components/dirent-detail/detail-item/index.js index de51d5c7c30..6ff377fd7dc 100644 --- a/frontend/src/components/dirent-detail/detail-item/index.js +++ b/frontend/src/components/dirent-detail/detail-item/index.js @@ -3,6 +3,7 @@ import PropTypes from 'prop-types'; import { Formatter, Icon } from '@seafile/sf-metadata-ui-component'; import classnames from 'classnames'; import { CellType, COLUMNS_ICON_CONFIG } from '../../../metadata/metadata-view/_basic'; +import { gettext } from '../../../utils/constants'; import './index.css'; @@ -19,15 +20,19 @@ const DetailItem = ({ field, value, valueId, valueClick, children, ...params }) {field.name}
- {children ? children : ()} + {children ? children : ()}
); }; +DetailItem.defaultProps = { + emptyTip: gettext('Empty') +}; + DetailItem.propTypes = { field: PropTypes.object.isRequired, - value: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.array, PropTypes.object]), + value: PropTypes.any, children: PropTypes.any, valueId: PropTypes.string, }; diff --git a/frontend/src/components/dirent-detail/detail-list-view.js b/frontend/src/components/dirent-detail/detail-list-view.js index 0a4d83e4d02..2c0de7f4623 100644 --- a/frontend/src/components/dirent-detail/detail-list-view.js +++ b/frontend/src/components/dirent-detail/detail-list-view.js @@ -32,7 +32,7 @@ class DetailListView extends React.Component { this.tagListTitleID = `detail-list-view-tags-${uuidv4()}`; } - getDirentPosition = () => { + getFileParent = () => { let { repoInfo } = this.props; let direntPath = this.getDirentPath(); let position = repoInfo.repo_name; @@ -69,7 +69,7 @@ class DetailListView extends React.Component { renderTags = () => { const { direntType, direntDetail } = this.props; - const position = this.getDirentPosition(); + const position = this.getFileParent(); if (direntType === 'dir') { return ( diff --git a/frontend/src/components/dirent-detail/dirent-details/dir-details.js b/frontend/src/components/dirent-detail/dirent-details/dir-details.js index 36cb42e7b50..d68ebf53000 100644 --- a/frontend/src/components/dirent-detail/dirent-details/dir-details.js +++ b/frontend/src/components/dirent-detail/dirent-details/dir-details.js @@ -1,18 +1,18 @@ import React, { useMemo } from 'react'; import PropTypes from 'prop-types'; -import { getDirentPath, getDirentPosition } from './utils'; +import { getDirentPath, getFileParent } from './utils'; import DetailItem from '../detail-item'; import { CellType } from '../../../metadata/metadata-view/_basic'; import { gettext } from '../../../utils/constants'; -import EditMetadata from './edit-metadata'; +import { MetadataDetails } from '../../../metadata'; -const DirDetails = ({ repoID, repoInfo, dirent, direntType, path, direntDetail }) => { - const position = useMemo(() => getDirentPosition(repoInfo, dirent, path), [repoInfo, dirent, path]); +const DirDetails = ({ repoID, repoInfo, dirent, path, direntDetail, ...params }) => { + const parent = useMemo(() => getFileParent(repoInfo, dirent, path), [repoInfo, dirent, path]); const direntPath = useMemo(() => getDirentPath(dirent, path), [dirent, path]); return ( <> - + - {direntDetail.permission === 'rw' && window.app.pageOptions.enableMetadataManagement && ( - + {window.app.pageOptions.enableMetadataManagement && ( + )} ); @@ -32,7 +32,6 @@ DirDetails.propTypes = { repoID: PropTypes.string, repoInfo: PropTypes.object, dirent: PropTypes.object, - direntType: PropTypes.string, path: PropTypes.string, direntDetail: PropTypes.object, }; diff --git a/frontend/src/components/dirent-detail/dirent-details/edit-metadata/index.css b/frontend/src/components/dirent-detail/dirent-details/edit-metadata/index.css deleted file mode 100644 index 6fc1a7a56c8..00000000000 --- a/frontend/src/components/dirent-detail/dirent-details/edit-metadata/index.css +++ /dev/null @@ -1,31 +0,0 @@ -.detail-edit-metadata-btn { - height: 34px; - width: fit-content; - max-width: 100%; - padding: 0 6px; - display: flex; - align-items: center; - overflow: hidden; -} - -.detail-edit-metadata-btn .seafile-multicolor-icon { - margin-right: 6px; - flex-shrink: 0; - font-size: 14px; - fill: #999; -} - -.detail-edit-metadata-btn:hover { - background-color: #F5F5F5; - border-radius: 3px; - cursor: pointer; -} - -.detail-edit-metadata-btn .detail-edit-metadata-btn-title { - flex: 1; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - color: #666; - font-size: 14px; -} diff --git a/frontend/src/components/dirent-detail/dirent-details/edit-metadata/index.js b/frontend/src/components/dirent-detail/dirent-details/edit-metadata/index.js deleted file mode 100644 index d393b5b2068..00000000000 --- a/frontend/src/components/dirent-detail/dirent-details/edit-metadata/index.js +++ /dev/null @@ -1,41 +0,0 @@ -import React, { useCallback, useState } from 'react'; -import PropTypes from 'prop-types'; -import ExtraMetadataAttributesDialog from '../../../dialog/extra-metadata-attributes-dialog'; -import { gettext } from '../../../../utils/constants'; -import Icon from '../../../icon'; - -import './index.css'; - -const EditMetadata = ({ repoID, direntPath, direntType, direntDetail }) => { - const [isShowDialog, setShowDialog] = useState(false); - const onToggle = useCallback(() => { - setShowDialog(!isShowDialog); - }, [isShowDialog]); - - return ( - <> -
- - {gettext('Edit metadata properties')} -
- {isShowDialog && ( - - )} - - ); -}; - -EditMetadata.propTypes = { - repoID: PropTypes.string, - direntPath: PropTypes.string, - direntType: PropTypes.string, - direntDetail: PropTypes.object, -}; - -export default EditMetadata; diff --git a/frontend/src/components/dirent-detail/dirent-details/file-details.js b/frontend/src/components/dirent-detail/dirent-details/file-details.js index 42f3199d189..175de48e2fe 100644 --- a/frontend/src/components/dirent-detail/dirent-details/file-details.js +++ b/frontend/src/components/dirent-detail/dirent-details/file-details.js @@ -1,19 +1,20 @@ import React, { useCallback, useMemo, useState } from 'react'; import PropTypes from 'prop-types'; import { v4 as uuidV4 } from 'uuid'; -import { getDirentPath, getDirentPosition } from './utils'; +import { getDirentPath, getFileParent } from './utils'; import DetailItem from '../detail-item'; import { CellType } from '../../../metadata/metadata-view/_basic'; import { gettext } from '../../../utils/constants'; -import EditMetadata from './edit-metadata'; import EditFileTagPopover from '../../popover/edit-filetag-popover'; import FileTagList from '../../file-tag-list'; import { Utils } from '../../../utils/utils'; +import { MetadataDetails } from '../../../metadata'; +import ObjectUtils from '../../../metadata/metadata-view/utils/object-utils'; -const FileDetails = ({ repoID, repoInfo, dirent, direntType, path, direntDetail, onFileTagChanged, repoTags, fileTagList }) => { +const FileDetails = React.memo(({ repoID, repoInfo, dirent, path, direntDetail, onFileTagChanged, repoTags, fileTagList, ...params }) => { const [isEditFileTagShow, setEditFileTagShow] = useState(false); - const position = useMemo(() => getDirentPosition(repoInfo, dirent, path), [repoInfo, dirent, path]); + const parent = useMemo(() => getFileParent(repoInfo, dirent, path), [repoInfo, dirent, path]); const direntPath = useMemo(() => getDirentPath(dirent, path), [dirent, path]); const tagListTitleID = useMemo(() => `detail-list-view-tags-${uuidV4()}`, []); @@ -27,24 +28,26 @@ const FileDetails = ({ repoID, repoInfo, dirent, direntType, path, direntDetail, return ( <> - + - - - {Array.isArray(fileTagList) && fileTagList.length > 0 ? ( - - ) : ( - {gettext('Empty')} - )} - - {direntDetail.permission === 'rw' && window.app.pageOptions.enableMetadataManagement && ( - + {!window.app.pageOptions.enableMetadataManagement && ( + + {Array.isArray(fileTagList) && fileTagList.length > 0 ? ( + + ) : ( + {gettext('Empty')} + )} + + )} + {window.app.pageOptions.enableMetadataManagement && ( + )} {isEditFileTagShow && ); -}; +}, (props, nextProps) => { + const { repoID, repoInfo, dirent, path, direntDetail } = props; + const isChanged = ( + repoID !== nextProps.repoID || + path !== nextProps.path || + !ObjectUtils.isSameObject(repoInfo, nextProps.repoInfo) || + !ObjectUtils.isSameObject(dirent, nextProps.dirent) || + !ObjectUtils.isSameObject(direntDetail, nextProps.direntDetail) + ); + return !isChanged; +}); FileDetails.propTypes = { repoID: PropTypes.string, repoInfo: PropTypes.object, dirent: PropTypes.object, - direntType: PropTypes.string, path: PropTypes.string, direntDetail: PropTypes.object, onFileTagChanged: PropTypes.func, diff --git a/frontend/src/components/dirent-detail/dirent-details/index.css b/frontend/src/components/dirent-detail/dirent-details/index.css index 05b93813d22..3836c6c996c 100644 --- a/frontend/src/components/dirent-detail/dirent-details/index.css +++ b/frontend/src/components/dirent-detail/dirent-details/index.css @@ -25,5 +25,5 @@ } .detail-container .empty-tip-text { - color: #666 + color: #666; } diff --git a/frontend/src/components/dirent-detail/dirent-details/index.js b/frontend/src/components/dirent-detail/dirent-details/index.js index 73cc879df5e..4208fc1415e 100644 --- a/frontend/src/components/dirent-detail/dirent-details/index.js +++ b/frontend/src/components/dirent-detail/dirent-details/index.js @@ -1,6 +1,6 @@ -import React, { useCallback, useEffect, useRef, useState } from 'react'; +import React from 'react'; import PropTypes from 'prop-types'; -import { siteRoot } from '../../../utils/constants'; +import { siteRoot, mediaUrl } from '../../../utils/constants'; import { seafileAPI } from '../../../utils/seafile-api'; import { Utils } from '../../../utils/utils'; import toaster from '../../toast'; @@ -8,112 +8,157 @@ import Dirent from '../../../models/dirent'; import Header from '../header'; import DirDetails from './dir-details'; import FileDetails from './file-details'; +import ObjectUtils from '../../../metadata/metadata-view/utils/object-utils'; +import metadataAPI from '../../../metadata/api'; +import { User } from '../../../metadata/metadata-view/model'; +import { UserService } from '../../../metadata/metadata-view/_basic'; import './index.css'; -const DirentDetails = ({ dirent, path, repoID, currentRepoInfo, repoTags, fileTags, onItemDetailsClose, onFileTagChanged }) => { - const [direntType, setDirentType] = useState(''); - const [direntDetail, setDirentDetail] = useState(''); - const [folderDirent, setFolderDirent] = useState(null); - const direntRef = useRef(null); +class DirentDetails extends React.Component { - const updateDetailView = useCallback((repoID, dirent, direntPath) => { + constructor(props) { + super(props); + this.state = { + direntDetail: '', + dirent: null, + collaborators: [], + collaboratorsCache: {}, + }; + this.userService = new UserService({ mediaUrl, api: metadataAPI.listUserInfo }); + } + + updateCollaboratorsCache = (user) => { + const newCollaboratorsCache = { ...this.state.collaboratorsCache, [user.email]: user }; + this.setState({ collaboratorsCache: newCollaboratorsCache }); + }; + + loadCollaborators = () => { + metadataAPI.getCollaborators(this.props.repoID).then(res => { + const collaborators = Array.isArray(res?.data?.user_list) ? res.data.user_list.map(user => new User(user)) : []; + this.setState({ collaborators }); + }).catch(error => { + this.setState({ collaborators: [] }); + }); + }; + + updateDetail = (repoID, dirent, direntPath) => { const apiName = dirent.type === 'file' ? 'getFileInfo' : 'getDirInfo'; seafileAPI[apiName](repoID, direntPath).then(res => { - setDirentType(dirent.type === 'file' ? 'file' : 'dir'); - setDirentDetail(res.data); + this.setState(({ direntDetail: res.data, dirent })); }).catch(error => { const errMessage = Utils.getErrorMsg(error); toaster.danger(errMessage); }); - }, []); + }; - useEffect(() => { - if (direntRef.current && dirent === direntRef.current) return; - direntRef.current = dirent; + loadDetail = (repoID, dirent, path) => { if (dirent) { const direntPath = Utils.joinPath(path, dirent.name); - updateDetailView(repoID, dirent, direntPath); + this.updateDetail(repoID, dirent, direntPath); return; } const dirPath = Utils.getDirName(path); seafileAPI.listDir(repoID, dirPath).then(res => { const direntList = res.data.dirent_list; - let folderDirent = null; - for (let i = 0; i < direntList.length; i++) { - let dirent = direntList[i]; - if (dirent.parent_dir + dirent.name === path) { - folderDirent = new Dirent(dirent); - break; - } + let folderDirent = direntList.find(item => item.parent_dir + item.name === path) || null; + if (folderDirent) { + folderDirent = new Dirent(folderDirent); } - setFolderDirent(folderDirent); - updateDetailView(repoID, folderDirent, path); + this.updateDetail(repoID, folderDirent, path); }).catch(error => { const errMessage = Utils.getErrorMsg(error); toaster.danger(errMessage); }); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [dirent, path, repoID]); + }; + + componentDidMount() { + this.loadCollaborators(); + this.loadDetail(this.props.repoID, this.props.dirent, this.props.path); + } - if (!dirent && !folderDirent) return ''; - const direntName = dirent ? dirent.name : folderDirent.name; - const smallIconUrl = dirent ? Utils.getDirentIcon(dirent) : Utils.getDirentIcon(folderDirent); - // let bigIconUrl = dirent ? Utils.getDirentIcon(dirent, true) : Utils.getDirentIcon(folderDirent, true); - let bigIconUrl = ''; - const isImg = dirent ? Utils.imageCheck(dirent.name) : Utils.imageCheck(folderDirent.name); - // const isVideo = dirent ? Utils.videoCheck(dirent.name) : Utils.videoCheck(folderDirent.name); - if (isImg) { - bigIconUrl = `${siteRoot}thumbnail/${repoID}/1024` + Utils.encodePath(`${path === '/' ? '' : path}/${dirent.name}`); + UNSAFE_componentWillReceiveProps(nextProps) { + const { dirent, path, repoID, currentRepoInfo, repoTags, fileTags } = this.props; + if (!ObjectUtils.isSameObject(currentRepoInfo, nextProps.currentRepoInfo) || + !ObjectUtils.isSameObject(dirent, nextProps.dirent) || + JSON.stringify(repoTags || []) !== JSON.stringify(nextProps.repoTags || []) || + JSON.stringify(fileTags || []) !== JSON.stringify(nextProps.fileTags || []) || + path !== nextProps.path || + repoID !== nextProps.repoID) { + this.setState({ dirent: null }, () => { + this.loadDetail(nextProps.repoID, nextProps.dirent, nextProps.path); + }); + } } - return ( -
-
-
- {isImg && ( -
- -
- )} - {direntDetail && ( -
- {direntType === 'dir' ? ( - - ) : ( - - )} -
- )} + + render() { + const { dirent, direntDetail, collaborators, collaboratorsCache } = this.state; + if (!dirent) return null; + const { repoID, path, fileTags } = this.props; + const direntName = dirent.name; + const smallIconUrl = Utils.getDirentIcon(dirent); + // let bigIconUrl = Utils.getDirentIcon(dirent, true); + let bigIconUrl = ''; + const isImg = Utils.imageCheck(dirent.name); + // const isVideo = Utils.videoCheck(dirent.name); + if (isImg) { + bigIconUrl = `${siteRoot}thumbnail/${repoID}/1024` + Utils.encodePath(`${path === '/' ? '' : path}/${dirent.name}`); + } + + return ( +
+
+
+ {isImg && ( +
+ +
+ )} + {direntDetail && ( +
+ {dirent.type !== 'file' ? ( + + ) : ( + + )} +
+ )} +
-
- ); -}; + ); + } +} DirentDetails.propTypes = { repoID: PropTypes.string.isRequired, dirent: PropTypes.object, path: PropTypes.string.isRequired, currentRepoInfo: PropTypes.object.isRequired, - onItemDetailsClose: PropTypes.func.isRequired, + onClose: PropTypes.func.isRequired, onFileTagChanged: PropTypes.func.isRequired, - direntDetailPanelTab: PropTypes.string, repoTags: PropTypes.array, fileTags: PropTypes.array, }; diff --git a/frontend/src/components/dirent-detail/dirent-details/utils.js b/frontend/src/components/dirent-detail/dirent-details/utils.js index 0ca48f2f737..f5363dd3c28 100644 --- a/frontend/src/components/dirent-detail/dirent-details/utils.js +++ b/frontend/src/components/dirent-detail/dirent-details/utils.js @@ -2,10 +2,11 @@ import { Utils } from '../../../utils/utils'; export const getDirentPath = (dirent, path) => { if (Utils.isMarkdownFile(path)) return path; // column mode: view file + if (dirent.type === 'dir') return path; return Utils.joinPath(path, dirent.name); }; -export const getDirentPosition = (repoInfo, dirent, path) => { +export const getFileParent = (repoInfo, dirent, path) => { const direntPath = getDirentPath(dirent, path); const position = repoInfo.repo_name; if (direntPath === '/') return position; diff --git a/frontend/src/components/dirent-detail/header/index.css b/frontend/src/components/dirent-detail/header/index.css index df4ee6b249d..0b3240ca2f2 100644 --- a/frontend/src/components/dirent-detail/header/index.css +++ b/frontend/src/components/dirent-detail/header/index.css @@ -4,7 +4,6 @@ align-items: center; justify-content: space-between; line-height: 2.5rem; - background-color: #f9f9f9; border-bottom: 1px solid #e8e8e8; height: 48px; padding: 8px 16px; diff --git a/frontend/src/components/dirent-detail/index.js b/frontend/src/components/dirent-detail/index.js index 63dd0640264..896394456b5 100644 --- a/frontend/src/components/dirent-detail/index.js +++ b/frontend/src/components/dirent-detail/index.js @@ -1,9 +1,49 @@ +import React from 'react'; +import PropTypes from 'prop-types'; import LibDetail from './lib-details'; import DirentDetail from './dirent-details'; import './index.css'; +import ObjectUtils from '../../metadata/metadata-view/utils/object-utils'; -export { - LibDetail, - DirentDetail, +const Index = React.memo(({ repoID, path, dirent, currentRepoInfo, repoTags, fileTags, onClose, onFileTagChanged }) => { + + if (path === '/' && !dirent) { + return ( + + ); + } + return ( + + ); +}, (props, nextProps) => { + const isChanged = props.repoID !== nextProps.repoID || + props.path !== nextProps.path || + !ObjectUtils.isSameObject(props.dirent, nextProps.dirent) || + !ObjectUtils.isSameObject(props.currentRepoInfo, nextProps.currentRepoInfo) || + JSON.stringify(props.repoTags || []) !== JSON.stringify(nextProps.repoTags || []) || + JSON.stringify(props.fileTags || []) !== JSON.stringify(nextProps.fileTags || []); + return !isChanged; +}); + +Index.propTypes = { + repoID: PropTypes.string, + path: PropTypes.string, + dirent: PropTypes.object, + currentRepoInfo: PropTypes.object, + repoTags: PropTypes.array, + fileTags: PropTypes.array, + onClose: PropTypes.func, + onFileTagChanged: PropTypes.func, }; + +export default Index; diff --git a/frontend/src/components/dirent-detail/lib-details/index.js b/frontend/src/components/dirent-detail/lib-details/index.js index 5236507c24e..47c09d92a98 100644 --- a/frontend/src/components/dirent-detail/lib-details/index.js +++ b/frontend/src/components/dirent-detail/lib-details/index.js @@ -10,14 +10,14 @@ import Loading from '../../loading'; import DetailItem from '../detail-item'; import { CellType } from '../../../metadata/metadata-view/_basic'; -const LibDetail = React.memo(({ currentRepo, closeDetails }) => { +const LibDetail = React.memo(({ currentRepoInfo, onClose }) => { const [isLoading, setLoading] = useState(true); const [repo, setRepo] = useState({}); - const smallIconUrl = useMemo(() => Utils.getLibIconUrl(currentRepo), [currentRepo]); + const smallIconUrl = useMemo(() => Utils.getLibIconUrl(currentRepoInfo), [currentRepoInfo]); useEffect(() => { setLoading(true); - seafileAPI.getRepoInfo(currentRepo.repo_id).then(res => { + seafileAPI.getRepoInfo(currentRepoInfo.repo_id).then(res => { const repo = new Repo(res.data); setRepo(repo); setLoading(false); @@ -25,11 +25,11 @@ const LibDetail = React.memo(({ currentRepo, closeDetails }) => { let errMessage = Utils.getErrorMsg(error); toaster.danger(errMessage); }); - }, [currentRepo.repo_id]); + }, [currentRepoInfo.repo_id]); return (
-
+
{isLoading ? (
@@ -55,8 +55,8 @@ const LibDetail = React.memo(({ currentRepo, closeDetails }) => { }); LibDetail.propTypes = { - currentRepo: PropTypes.object.isRequired, - closeDetails: PropTypes.func.isRequired, + currentRepoInfo: PropTypes.object.isRequired, + onClose: PropTypes.func.isRequired, }; export default LibDetail; diff --git a/frontend/src/components/search/ai-search.js b/frontend/src/components/search/ai-search.js index 990728319c9..4d66fe200d7 100644 --- a/frontend/src/components/search/ai-search.js +++ b/frontend/src/components/search/ai-search.js @@ -9,7 +9,7 @@ import Icon from '../icon'; import { gettext, siteRoot, username } from '../../utils/constants'; import SearchResultItem from './search-result-item'; import SearchResultLibrary from './search-result-library'; -import { isMac } from '../../utils/extra-attributes'; +import { Utils } from '../../utils/utils'; import Loading from '../loading'; const INDEX_STATE = { @@ -19,7 +19,7 @@ const INDEX_STATE = { }; const PER_PAGE = 10; -const controlKey = isMac() ? '⌘' : 'Ctrl'; +const controlKey = Utils.isMac() ? '⌘' : 'Ctrl'; export default class AISearch extends Component { diff --git a/frontend/src/components/search/search.js b/frontend/src/components/search/search.js index 66eac859798..f3683f290a0 100644 --- a/frontend/src/components/search/search.js +++ b/frontend/src/components/search/search.js @@ -9,7 +9,6 @@ import { gettext, siteRoot } from '../../utils/constants'; import SearchResultItem from './search-result-item'; import SearchResultLibrary from './search-result-library'; import { Utils } from '../../utils/utils'; -import { isMac } from '../../utils/extra-attributes'; import toaster from '../toast'; import Loading from '../loading'; @@ -23,7 +22,7 @@ const propTypes = { }; const PER_PAGE = 10; -const controlKey = isMac() ? '⌘' : 'Ctrl'; +const controlKey = Utils.isMac() ? '⌘' : 'Ctrl'; const isEnter = isHotkey('enter'); const isUp = isHotkey('up'); diff --git a/frontend/src/constants/index.js b/frontend/src/constants/index.js index 6e5c55f83bb..bed27bfc560 100644 --- a/frontend/src/constants/index.js +++ b/frontend/src/constants/index.js @@ -3,109 +3,6 @@ import KeyCodes from './keyCodes'; export const DIALOG_MAX_HEIGHT = window.innerHeight - 56; // Dialog margin is 3.5rem (56px) -export const EXTRA_ATTRIBUTES_COLUMN_TYPE = { - TEXT: 'text', - NUMBER: 'number', - DATE: 'date', - FORMULA: 'formula', - SINGLE_SELECT: 'single-select', - CTIME: 'ctime', - MTIME: 'mtime' -}; - -export const EXTRA_ATTRIBUTES_NOT_DISPLAY_COLUMN_KEY = [ - '_id', - '_locked', - '_locked_by', - '_archived', - '_creator', - '_last_modifier', - '_ctime', - '_mtime', -]; - -export const EXTRA_ATTRIBUTES_NOT_DISPLAY_COLUMN_NAME = [ - 'Repo ID', - 'UUID', -]; - -export const FORMULA_RESULT_TYPE = { - NUMBER: 'number', - STRING: 'string', - DATE: 'date', - BOOL: 'bool', - ARRAY: 'array', -}; - -export const DELETED_OPTION_BACKGROUND_COLOR = '#eaeaea'; - -export const DELETED_OPTION_TIPS = 'Deleted option'; - -export const DEFAULT_NUMBER_FORMAT = 'number'; - -export const ERROR = 'ERROR'; -export const ERROR_DIV_ZERO = 'DIV/0'; -export const ERROR_NAME = 'NAME'; -export const ERROR_NOT_AVAILABLE = 'N/A'; -export const ERROR_NULL = 'NULL'; -export const ERROR_NUM = 'NUM'; -export const ERROR_REF = 'REF'; -export const ERROR_VALUE = 'VALUE'; -export const GETTING_DATA = 'GETTING_DATA'; - -const errors = { - [ERROR]: '#ERROR!', - [ERROR_DIV_ZERO]: '#DIV/0!', - [ERROR_NAME]: '#NAME?', - [ERROR_NOT_AVAILABLE]: '#N/A', - [ERROR_NULL]: '#NULL!', - [ERROR_NUM]: '#NUM!', - [ERROR_REF]: '#REF!', - [ERROR_VALUE]: '#VALUE!', - [GETTING_DATA]: '#GETTING_DATA', -}; - -export const DISPLAY_INTERNAL_ERRORS = [ - errors[ERROR], - errors[ERROR_DIV_ZERO], - errors[ERROR_NAME], - errors[ERROR_NOT_AVAILABLE], - errors[ERROR_NULL], - errors[ERROR_NUM], - errors[ERROR_REF], - errors[ERROR_VALUE], - errors[GETTING_DATA], -]; - -export const DURATION_FORMATS_MAP = { - H_MM: 'h:mm', - H_MM_SS: 'h:mm:ss', - H_MM_SS_S: 'h:mm:ss.s', - H_MM_SS_SS: 'h:mm:ss.ss', - H_MM_SS_SSS: 'h:mm:ss.sss' -}; - -export const DURATION_FORMATS = [ - { name: DURATION_FORMATS_MAP.H_MM, type: DURATION_FORMATS_MAP.H_MM }, - { name: DURATION_FORMATS_MAP.H_MM_SS, type: DURATION_FORMATS_MAP.H_MM_SS } -]; - -export const DURATION_ZERO_DISPLAY = { - [DURATION_FORMATS_MAP.H_MM]: '0:00', - [DURATION_FORMATS_MAP.H_MM_SS]: '0:00', - [DURATION_FORMATS_MAP.H_MM_SS_S]: '0:00.0', - [DURATION_FORMATS_MAP.H_MM_SS_SS]: '0:00.00', - [DURATION_FORMATS_MAP.H_MM_SS_SSS]: '0:00.000', -}; - -export const DURATION_DECIMAL_DIGITS = { - [DURATION_FORMATS_MAP.H_MM]: 0, - [DURATION_FORMATS_MAP.H_MM_SS]: 0, - [DURATION_FORMATS_MAP.H_MM_SS_S]: 1, - [DURATION_FORMATS_MAP.H_MM_SS_SS]: 2, - [DURATION_FORMATS_MAP.H_MM_SS_SSS]: 3, -}; - export const PRIVATE_FILE_TYPE = { FILE_EXTENDED_PROPERTIES: '__file_extended_properties' }; diff --git a/frontend/src/metadata/index.js b/frontend/src/metadata/index.js index 321bae7f08a..dc4945471d0 100644 --- a/frontend/src/metadata/index.js +++ b/frontend/src/metadata/index.js @@ -1,9 +1,11 @@ import SeafileMetadata from './metadata-view'; import MetadataStatusManagementDialog from './metadata-status-manage-dialog'; import MetadataTreeView from './metadata-tree-view'; +import MetadataDetails from './metadata-details'; export { SeafileMetadata, MetadataStatusManagementDialog, MetadataTreeView, + MetadataDetails, }; diff --git a/frontend/src/metadata/metadata-details/constants.js b/frontend/src/metadata/metadata-details/constants.js new file mode 100644 index 00000000000..62ac0799e15 --- /dev/null +++ b/frontend/src/metadata/metadata-details/constants.js @@ -0,0 +1,22 @@ +import { PRIVATE_COLUMN_KEY } from '../metadata-view/_basic'; + +export const NOT_DISPLAY_COLUMN_KEYS = [ + PRIVATE_COLUMN_KEY.ID, + PRIVATE_COLUMN_KEY.CTIME, + PRIVATE_COLUMN_KEY.MTIME, + PRIVATE_COLUMN_KEY.CREATOR, + PRIVATE_COLUMN_KEY.LAST_MODIFIER, + PRIVATE_COLUMN_KEY.FILE_CREATOR, + PRIVATE_COLUMN_KEY.FILE_CTIME, + PRIVATE_COLUMN_KEY.FILE_MODIFIER, + PRIVATE_COLUMN_KEY.FILE_MTIME, + PRIVATE_COLUMN_KEY.PARENT_DIR, + PRIVATE_COLUMN_KEY.FILE_NAME, + PRIVATE_COLUMN_KEY.IS_DIR, + PRIVATE_COLUMN_KEY.FILE_TYPE, + PRIVATE_COLUMN_KEY.LOCATION, +]; + +export { + PRIVATE_COLUMN_KEY, +}; diff --git a/frontend/src/metadata/metadata-details/index.css b/frontend/src/metadata/metadata-details/index.css new file mode 100644 index 00000000000..9d669ae277e --- /dev/null +++ b/frontend/src/metadata/metadata-details/index.css @@ -0,0 +1,41 @@ +.dirent-detail-item .sf-metadata-checkbox-formatter { + border: 2px solid #e0e0e0; + border-radius: 3px; + height: 20px; + width: 20px; + overflow: hidden; +} + +.dirent-detail-item .sf-metadata-checkbox-formatter .sf-metadata-icon-check-mark { + font-size: 12px; +} + +.dirent-detail-item .sf-metadata-single-select-formatter { + height: 20px; + line-height: 1 !important; +} + +.dirent-detail-item .sf-metadata-ui.collaborator-item .collaborator-avatar { + margin-left: 1px; +} + +.dirent-detail-item .sf-metadata-collaborator-formatter .sf-metadata-ui.collaborator-item { + margin-bottom: 2px; + margin-top: 2px; +} + +.dirent-detail-item .sf-metadata-record-checkbox-cell-empty { + line-height: 1; + height: 20px; +} + +.dirent-detail-item .sf-metadata-record-checkbox-cell-empty::before { + content: ''; + position: relative; + display: inline-block; + height: 20px; + width: 20px; + border: 2px solid #e0e0e0; + border-radius: 3px; + line-height: 1; +} diff --git a/frontend/src/metadata/metadata-details/index.js b/frontend/src/metadata/metadata-details/index.js new file mode 100644 index 00000000000..857269bbc3f --- /dev/null +++ b/frontend/src/metadata/metadata-details/index.js @@ -0,0 +1,53 @@ +import React, { useEffect, useState } from 'react'; +import PropTypes from 'prop-types'; +import { Utils } from '../../utils/utils'; +import metadataAPI from '../api'; +import Column from '../metadata-view/model/metadata/column'; +import { normalizeFields, getCellValueByColumn } from './utils'; +import DetailItem from '../../components/dirent-detail/detail-item'; +import toaster from '../../components/toast'; + +import './index.css'; + +const MetadataDetails = ({ repoID, filePath, direntType, emptyTip, ...params }) => { + const [isLoading, setLoading] = useState(true); + const [metadata, setMetadata] = useState({ record: {}, fields: [] }); + + useEffect(() => { + setLoading(true); + const dirName = Utils.getDirName(filePath); + const fileName = Utils.getFileName(filePath); + let parentDir = direntType === 'file' ? dirName : dirName.slice(0, dirName.length - fileName.length - 1); + if (!parentDir.startsWith('/')) { + parentDir = '/' + parentDir; + } + metadataAPI.getMetadataRecordInfo(repoID, parentDir, fileName).then(res => { + const { results, metadata } = res.data; + const record = Array.isArray(results) && results.length > 0 ? results[0] : {}; + const fields = normalizeFields(metadata).map(field => new Column(field)); + setMetadata({ record, fields }); + setLoading(false); + }).catch(error => { + const errMessage = Utils.getErrorMsg(error); + toaster.danger(errMessage); + setLoading(false); + }); + }, [repoID, filePath, direntType]); + + if (isLoading) return null; + const { fields, record } = metadata; + if (!record._id) return null; + return fields.map(field => { + const value = getCellValueByColumn(record, field); + return (); + }); +}; + +MetadataDetails.propTypes = { + repoID: PropTypes.string, + filePath: PropTypes.string, + direntType: PropTypes.string, + direntDetail: PropTypes.object, +}; + +export default MetadataDetails; diff --git a/frontend/src/metadata/metadata-details/utils.js b/frontend/src/metadata/metadata-details/utils.js new file mode 100644 index 00000000000..b5bbf59afe5 --- /dev/null +++ b/frontend/src/metadata/metadata-details/utils.js @@ -0,0 +1,25 @@ +import { getColumnType } from '../metadata-view/utils/column-utils'; +import { getCellValueByColumn } from '../metadata-view/_basic'; +import { NOT_DISPLAY_COLUMN_KEYS } from './constants'; + +export const normalizeFields = (fields) => { + if (!Array.isArray(fields) || fields.length === 0) return []; + const validFields = fields.map((field) => { + const { type, key, ...params } = field; + return { + ...params, + key, + type: getColumnType(key, type), + width: 200, + }; + }).filter(field => !NOT_DISPLAY_COLUMN_KEYS.includes(field.key)); + let displayFields = []; + validFields.forEach(field => { + displayFields.push(field); + }); + return displayFields; +}; + +export { + getCellValueByColumn, +}; diff --git a/frontend/src/metadata/metadata-view/components/index.js b/frontend/src/metadata/metadata-view/components/index.js index f4e738015a7..b2e6502991a 100644 --- a/frontend/src/metadata/metadata-view/components/index.js +++ b/frontend/src/metadata/metadata-view/components/index.js @@ -1,9 +1,7 @@ import DeleteConfirmDialog from './delete-confirm-dialog'; -import RecordDetailsDialog from './record-details-dialog'; import Table from './table'; export { DeleteConfirmDialog, - RecordDetailsDialog, Table, }; diff --git a/frontend/src/metadata/metadata-view/components/record-details-dialog/index.js b/frontend/src/metadata/metadata-view/components/record-details-dialog/index.js deleted file mode 100644 index 8e2a52011cb..00000000000 --- a/frontend/src/metadata/metadata-view/components/record-details-dialog/index.js +++ /dev/null @@ -1,22 +0,0 @@ -import React, { useMemo } from 'react'; -import RecordDetails from './record-details'; -import { useMetadata, useRecordDetails } from '../../hooks'; - -const RecordDetailsDialog = () => { - const { isShowRecordDetails, recordDetails, closeRecordDetails } = useRecordDetails(); - const { metadata } = useMetadata(); - const fields = useMemo(() => { - const { columns, hidden_columns } = metadata.view; - return columns.filter(column => !hidden_columns.includes(column.key)); - }, [metadata]); - if (!isShowRecordDetails) return null; - - const props = { - record: recordDetails, - fields: fields, - onToggle: closeRecordDetails, - }; - return (); -}; - -export default RecordDetailsDialog; diff --git a/frontend/src/metadata/metadata-view/components/record-details-dialog/record-details/field-label/index.css b/frontend/src/metadata/metadata-view/components/record-details-dialog/record-details/field-label/index.css deleted file mode 100644 index d64507b997e..00000000000 --- a/frontend/src/metadata/metadata-view/components/record-details-dialog/record-details/field-label/index.css +++ /dev/null @@ -1,31 +0,0 @@ -.sf-metadata-record-details-item .sf-metadata-record-details-item-label { - padding-top: 9px; - height: fit-content; -} - -.sf-metadata-record-details-item .sf-metadata-record-details-item-label .header-icon { - display: inline-block; - margin-left: -.3125rem; - padding: 0 .3125rem; -} - -.sf-metadata-record-details-item .sf-metadata-record-details-item-label .field-description-section .header-icon .sf-metadata-icon { - color: #212529a6; - cursor: default; - font-size: 14px; -} - -.sf-metadata-record-details-item .sf-metadata-record-details-item-label .field-description-section .field-description-section-field-name { - color: #212529b3; -} - -.sf-metadata-record-details-item .sf-metadata-record-details-item-label .field-description-section .field-uneditable-tip { - color: #bdbdbd; - cursor: pointer; - font-size: 14px; - position: relative; -} - -.sf-metadata-record-details-item .sf-metadata-record-details-item-label .field-description-section .field-uneditable-tip:hover { - color: #666; -} diff --git a/frontend/src/metadata/metadata-view/components/record-details-dialog/record-details/field-label/index.js b/frontend/src/metadata/metadata-view/components/record-details-dialog/record-details/field-label/index.js deleted file mode 100644 index 7330d60e47d..00000000000 --- a/frontend/src/metadata/metadata-view/components/record-details-dialog/record-details/field-label/index.js +++ /dev/null @@ -1,42 +0,0 @@ -import React, { useRef } from 'react'; -import PropTypes from 'prop-types'; -import { UncontrolledTooltip, Col } from 'reactstrap'; -import { IconBtn, Icon } from '@seafile/sf-metadata-ui-component'; -import { COLUMNS_ICON_CONFIG } from '../../../../_basic'; - -import './index.css'; - -const FieldLabel = ({ field }) => { - const { type, name, description, key } = field; - const iconRef = useRef(null); - return ( -
-
-
- - - - {name || ''} - {description && - <> - - {iconRef.current && ( - - {description} - - )} - - } -
-
- - ); - -}; - -FieldLabel.propTypes = { - field: PropTypes.object, - fieldIconConfig: PropTypes.object, -}; - -export default FieldLabel; diff --git a/frontend/src/metadata/metadata-view/components/record-details-dialog/record-details/index.css b/frontend/src/metadata/metadata-view/components/record-details-dialog/record-details/index.css deleted file mode 100644 index 4b908ba9428..00000000000 --- a/frontend/src/metadata/metadata-view/components/record-details-dialog/record-details/index.css +++ /dev/null @@ -1,318 +0,0 @@ -.sf-metadata-record-details-dialog { - margin: 28px 0 0 0; -} - -.sf-metadata-record-details-dialog .modal-header { - padding: 6px 14px 6px 20px; -} - -.sf-metadata-record-details-dialog .sf-metadata-record-details-content { - display: flex; - flex-direction: row; -} - -.sf-metadata-record-details-dialog .modal-content { - height: 100%; -} - -.sf-metadata-record-details-dialog .sf-metadata-record-details { - width: 100%; - height: 100%; - display: flex; - flex-direction: column; -} - -.sf-metadata-record-details-dialog .header-close-list { - display: flex; -} - -.sf-metadata-record-details-dialog .header-close-list .sf-metadata-icon-btn .sf-metadata-icon { - color: #000; - opacity: 0.5; - font-size: 16px; -} - -.sf-metadata-record-details-dialog .header-close-list .sf-metadata-icon-btn:hover .sf-metadata-icon { - opacity: 0.75; -} - -.sf-metadata-record-details-dialog .sf-metadata-record-details-container { - width: 100%; - height: calc(100% - 37px); - display: flex; - flex-direction: column; - overflow-y: scroll; - padding: 30px; -} - -.sf-metadata-record-details-dialog .sf-metadata-record-details-container .sf-metadata-record-details-item { - min-height: 56px; - flex-shrink: 0; -} - -.sf-metadata-record-details-dialog .sf-metadata-record-details-container .sf-metadata-record-details-item:first-child { - margin-top: 2px; -} - -.sf-metadata-record-details-dialog .sf-metadata-record-details-container .sf-metadata-ui.collaborator-item .collaborator-avatar { - margin-left: 0; -} - -.sf-metadata-record-details-dialog .sf-metadata-record-details-item .sf-metadata-record-cell-empty, -.sf-metadata-record-details-dialog .sf-metadata-record-details-item .sf-metadata-text-formatter, -.sf-metadata-record-details-dialog .sf-metadata-record-details-item .sf-metadata-single-select-formatter, -.sf-metadata-record-details-dialog .sf-metadata-record-details-item .sf-metadata-multiple-select-formatter, -.sf-metadata-record-details-dialog .sf-metadata-record-details-item .multiple-select-formatter, -.sf-metadata-record-details-dialog .sf-metadata-record-details-item .sf-metadata-collaborator-formatter, -.sf-metadata-record-details-dialog .sf-metadata-record-details-item .sf-metadata-date-formatter, -.sf-metadata-record-details-dialog .sf-metadata-record-details-item .sf-metadata-url-formatter, -.sf-metadata-record-details-dialog .sf-metadata-record-details-item .sf-metadata-email-formatter, -.sf-metadata-record-details-dialog .sf-metadata-record-details-item .sf-metadata-ctime-formatter, -.sf-metadata-record-details-dialog .sf-metadata-record-details-item .sf-metadata-mtime-formatter, -.sf-metadata-record-details-dialog .sf-metadata-record-details-item .sf-metadata-number-formatter, -.sf-metadata-record-details-dialog .sf-metadata-record-details-item .sf-metadata-formula-formatter, -.sf-metadata-record-details-dialog .sf-metadata-record-details-item .sf-metadata-duration-formatter, -.sf-metadata-record-details-dialog .sf-metadata-record-details-item .formula-formatter.multiple, -.sf-metadata-record-details-dialog .sf-metadata-record-details-item .sf-metadata-link-formula-formatter, -.sf-metadata-record-details-dialog .sf-metadata-record-details-item .sf-metadata-rate-formatter, -.sf-metadata-record-details-dialog .sf-metadata-record-details-item .sf-metadata-link-formatter { - background-color: #f8f9fa; - cursor: default; - display: block; - width: 100%; - height: 2.375rem; - padding: .375rem .75rem; - font-size: 0.875rem; - font-weight: 400; - line-height: 1.5; - color: #212529; - background-clip: padding-box; - border: 1px solid rgba(0, 40, 100, .12); - border-radius: 3px; - transition: border-color .15s ease-in-out, box-shadow .15s ease-in-out; -} - -.sf-metadata-record-details-dialog .sf-metadata-record-details-item .sf-metadata-text-formatter, -.sf-metadata-record-details-dialog .sf-metadata-record-details-item .sf-metadata-url-formatter, -.sf-metadata-record-details-dialog .sf-metadata-record-details-item .sf-metadata-formula-formatter, -.sf-metadata-record-details-dialog .sf-metadata-record-details-item .sf-metadata-email-formatter, -.sf-metadata-record-details-dialog .sf-metadata-record-details-item .sf-metadata-rate-formatter { - line-height: 24px; - word-break: break-all; -} - -.sf-metadata-record-details-dialog .sf-metadata-record-details-item .sf-metadata-single-select-formatter { - display: flex; - align-items: center; -} - -.sf-metadata-record-details-dialog .sf-metadata-record-details-item .sf-metadata-collaborator-formatter, -.sf-metadata-record-details-dialog .sf-metadata-record-details-item .sf-metadata-multiple-select-formatter, -.sf-metadata-record-details-dialog .sf-metadata-record-details-item .multiple-select-formatter, -.sf-metadata-record-details-dialog .sf-metadata-record-details-item .formula-formatter.multiple, -.sf-metadata-record-details-dialog .sf-metadata-record-details-item .sf-metadata-link-formatter { - padding-top: 4px; - padding-bottom: 4px; - display: flex; - flex-wrap: wrap; - height: fit-content; - min-height: 38px; -} - -.sf-metadata-record-details-dialog .sf-metadata-record-details-item .sf-metadata-multiple-select-formatter .select-item, -.sf-metadata-record-details-dialog .sf-metadata-record-details-item .multiple-select-formatter .select-item, -.sf-metadata-record-details-dialog .sf-metadata-record-details-item .formula-formatter.multiple .formula-formatter-content-item { - margin-top: 5px; - margin-bottom: 5px; -} - -.sf-metadata-record-details-dialog .sf-metadata-record-details-item .sf-metadata-collaborator-formatter .collaborator-item, -.sf-metadata-record-details-dialog .sf-metadata-record-details-item .sf-metadata-link-formatter .sf-metadata-link-item { - margin: 5px 10px 5px 0; -} - -.sf-metadata-record-details-dialog .sf-metadata-record-details-item .sf-metadata-link-formatter .sf-metadata-link-item { - height: 20px; - max-width: 100%; - padding: 0 8px; - background: #eceff4; - border-radius: 3px; - display: inline-block; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.sf-metadata-record-details-dialog .sf-metadata-record-details-item .sf-metadata-date-formatter, -.sf-metadata-record-details-dialog .sf-metadata-record-details-item .sf-metadata-ctime-formatter, -.sf-metadata-record-details-dialog .sf-metadata-record-details-item .sf-metadata-mtime-formatter, -.sf-metadata-record-details-dialog .sf-metadata-record-details-item .sf-metadata-number-formatter, -.sf-metadata-record-details-dialog .sf-metadata-record-details-item .sf-metadata-formula-number-formatter, -.sf-metadata-record-details-dialog .sf-metadata-record-details-item .sf-metadata-duration-formatter, -.sf-metadata-record-details-dialog .sf-metadata-record-details-item .sf-metadata-formula-date-formatter, -.sf-metadata-record-details-dialog .sf-metadata-record-details-item .sf-metadata-rate-formatter { - width: 320px; - line-height: 24px; -} - -.sf-metadata-record-details-dialog .sf-metadata-record-details-item .sf-metadata-rate-formatter .sf-metadata-icon { - margin-right: 5px; -} - -.sf-metadata-record-details-dialog .sf-metadata-record-details-item .sf-metadata-checkbox-formatter { - width: 24px; - height: 24px; - border: 2px solid #e0e0e0; - border-radius: 3px; -} - -.sf-metadata-record-details-dialog .sf-metadata-record-details-item .sf-metadata-record-cell-empty.sf-metadata-record-file-cell-empty, -.sf-metadata-record-details-dialog .sf-metadata-record-details-item .sf-metadata-record-cell-empty.sf-metadata-record-image-cell-empty, -.sf-metadata-record-details-dialog .sf-metadata-record-details-item .sf-metadata-record-creator-cell-empty, -.sf-metadata-record-details-dialog .sf-metadata-record-details-item .sf-metadata-record-link-cell-empty { - display: none; -} - -/* long text */ -.sf-metadata-record-details-dialog .sf-metadata-record-details-item .sf-metadata-long-text-formatter { - background-color: #f8f9fa; - cursor: default; - min-height: 38px; - border: 1px solid rgba(0, 40, 100, 0.12); - border-radius: 3px; - padding: 0 12px; - width: 100%; - overflow-x: hidden; - text-overflow: unset; - white-space: unset; - line-height: 1.5; -} - -.sf-metadata-record-details-dialog .sf-metadata-long-text-formatter ol, -.sf-metadata-record-details-dialog .sf-metadata-long-text-formatter ul { - padding-inline-start: 40px; - margin-bottom: 1em; -} - -.sf-metadata-record-details-dialog .sf-metadata-long-text-formatter ol li a, -.sf-metadata-record-details-dialog .sf-metadata-long-text-formatter ul li a { - word-break: break-all; -} - -.sf-metadata-record-details-dialog .sf-metadata-long-text-formatter ul.contains-task-list, -.sf-metadata-record-details-dialog .sf-metadata-long-text-formatter ol.contains-task-list { - padding-inline-start: 20px; -} - -.sf-metadata-record-details-dialog .sf-metadata-long-text-formatter ul li.task-list-item, -.sf-metadata-record-details-dialog .sf-metadata-long-text-formatter ol li.task-list-item { - min-height: 20px; -} - -.sf-metadata-record-details-dialog .sf-metadata-long-text-formatter li.task-list-item input[type=checkbox] { - position: absolute; - left: -1.4em; - top: .4em; - display: inline-block; -} - -.sf-metadata-record-details-dialog .sf-metadata-long-text-formatter thead tr { - min-height: 42px; -} - -.sf-metadata-record-details-dialog .sf-metadata-long-text-formatter tbody tr { - font-weight: normal; - min-height: 42px; -} - -.sf-metadata-record-details-dialog .sf-metadata-record-details-item .sf-metadata-geolocation-formatter { - min-width: 80px; - width: fit-content; - max-width: 100%; - height: 28px; - display: inline-flex; - justify-content: center; - line-height: 24px; - border: 2px solid transparent; - padding: 0 10px; - background-color: #f0f0f0; - border-radius: 3px; - font-size: 14px; - color: #212529; -} - -.sf-metadata-record-details-dialog .sf-metadata-record-details-item .sf-metadata-formula-number-formatter { - text-align: left !important; -} - -.sf-metadata-record-details-dialog .sf-metadata-record-details-item .sf-metadata-digital-sign-formatter { - display: flex; - flex-wrap: wrap; - margin-left: -5px; - margin-right: -5px; -} - -.sf-metadata-record-details-dialog .sf-metadata-record-details-item .sf-metadata-creator-formatter, -.sf-metadata-record-details-dialog .sf-metadata-record-details-item .sf-metadata-last-modifier-formatter, -.sf-metadata-record-details-dialog .sf-metadata-record-details-item .sf-metadata-checkbox-formatter, -.sf-metadata-record-details-dialog .sf-metadata-record-details-item .sf-metadata-image-formatter, -.sf-metadata-record-details-dialog .sf-metadata-record-details-item .sf-metadata-file-formatter, -.sf-metadata-record-details-dialog .sf-metadata-record-details-item .sf-metadata-geolocation-formatter, -.sf-metadata-record-details-dialog .sf-metadata-record-details-item .sf-metadata-auto-number-formatter -.sf-metadata-record-details-dialog .sf-metadata-record-details-item .sf-metadata-digital-sign-formatter { - margin-top: 8px; -} - -.sf-metadata-record-details-dialog .sf-metadata-record-details-item .sf-metadata-image-formatter, -.sf-metadata-record-details-dialog .sf-metadata-record-details-item .sf-metadata-file-formatter { - flex-wrap: wrap; - margin-left: -5px; - margin-right: -5px; - width: unset; -} - -/* image */ -.sf-metadata-record-details-dialog .sf-metadata-record-details-item .sf-metadata-record-image-item { - height: 100px; - width: 100px; - border: 2px solid #ededed; - border-radius: 4px; - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - margin: 5px; - position: relative; -} - -.sf-metadata-record-details-dialog .sf-metadata-record-details-item .sf-metadata-record-image-item:hover { - border: 2px solid #c9c9c9; -} - -.sf-metadata-record-details-dialog .sf-metadata-record-details-item .sf-metadata-record-image-item .image-item, -.sf-metadata-record-details-dialog .sf-metadata-record-details-item .sf-metadata-record-image-item .file-item-icon { - display: block; - width: 96px; - height: unset; - border: none; - margin-right: 0; - border-radius: 4px; -} - -.sf-metadata-record-details-dialog .sf-metadata-record-details-item .sf-metadata-record-image-item .image-item:hover { - border: none; -} - -.sf-metadata-record-details-dialog .sf-metadata-record-details-item .sf-metadata-digital-sign-formatter .sf-metadata-record-image-item { - width: 200px; -} - -.sf-metadata-record-details-dialog .sf-metadata-record-details-item .sf-metadata-record-checkbox-cell-empty { - background-color: #fff; - width: 24px; - height: 24px; - border: 2px solid #e0e0e0; - border-radius: 3px; - padding: 0; - margin-top: 8px; -} diff --git a/frontend/src/metadata/metadata-view/components/record-details-dialog/record-details/index.js b/frontend/src/metadata/metadata-view/components/record-details-dialog/record-details/index.js deleted file mode 100644 index 54aacdeab46..00000000000 --- a/frontend/src/metadata/metadata-view/components/record-details-dialog/record-details/index.js +++ /dev/null @@ -1,133 +0,0 @@ -import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; -import PropTypes from 'prop-types'; -import { Modal, ModalHeader, ModalBody, Col } from 'reactstrap'; -import { IconBtn, CenteredLoading } from '@seafile/sf-metadata-ui-component'; -import { gettext } from '../../../utils'; -import FieldLabel from './field-label'; -import CellFormatter from '../../cell-formatter'; -import { getCellValueByColumn } from '../../../_basic'; - -import './index.css'; - -const RecordDetails = ({ fields, record, onToggle, ...params }) => { - const [isAnimation, setAnimation] = useState(true); - const [isLoading, setLoading] = useState(true); - const modalRef = useRef(null); - - const initStyle = useMemo(() => { - const defaultMargin = 80; // sequence cell width - const defaultHeight = 100; - return { - width: `${window.innerWidth - defaultMargin}px`, - maxWidth: `${window.innerWidth - defaultMargin}px`, - marginLeft: `${defaultMargin}px`, - height: `${defaultHeight}px`, - marginRight: `${defaultMargin}px`, - marginTop: '30%', - transition: 'all .3s', - }; - }, []); - - const style = useMemo(() => { - const width = 800; - return { - width, - maxWidth: width, - marginLeft: (window.innerWidth - width) / 2, - height: 'calc(100% - 56px)', // Dialog margin is 3.5rem (56px) - }; - }, []); - - useEffect(() => { - // use setTimeout to make sure real dom rendered - setTimeout(() => { - let dom = modalRef.current.firstChild; - const { width, maxWidth, marginLeft, height } = style; - dom.style.width = `${width}px`; - dom.style.maxWidth = `${maxWidth}px`; - dom.style.marginLeft = `${marginLeft}px`; - dom.style.height = height; - dom.style.marginRight = 'unset'; - dom.style.marginTop = '28px'; - // after animation, change style and run callback - setTimeout(() => { - setAnimation(false); - dom.style.transition = 'none'; - setLoading(false); - }, 280); - }, 1); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - const toggle = useCallback(() => { - onToggle(); - }, [onToggle]); - - const renderFieldValue = useCallback((field) => { - const cellValue = getCellValueByColumn(record, field); - return (); - }, [record, params]); - - const renderRowContent = useCallback(() => { - if (isLoading) return (); - if (!Array.isArray(fields) || fields.length === 0) return null; - return ( - <> - {fields.map((field) => { - return ( -
-
- -
- {renderFieldValue(field)} - - - - ); - })} - - ); - }, [isLoading, fields, renderFieldValue]); - - return ( - - {!isAnimation && ( -
- - -
- )}> -
-
{gettext('Details')}
-
- - - {renderRowContent()} - - - )} -
- ); - -}; - -RecordDetails.propTypes = { - record: PropTypes.object, - fields: PropTypes.array, - columns: PropTypes.array, - onToggle: PropTypes.func, -}; - -export default RecordDetails; diff --git a/frontend/src/metadata/metadata-view/components/table/container.js b/frontend/src/metadata/metadata-view/components/table/container.js index 985bda88010..763b295e4e9 100644 --- a/frontend/src/metadata/metadata-view/components/table/container.js +++ b/frontend/src/metadata/metadata-view/components/table/container.js @@ -5,7 +5,6 @@ import { CommonlyUsedHotkey, getValidGroupbys } from '../../_basic'; import { gettext } from '../../utils'; import { useMetadata } from '../../hooks'; import TableMain from './table-main'; -import RecordDetailsDialog from '../record-details-dialog'; import { Utils } from '../../../../utils/utils'; import './index.css'; @@ -202,7 +201,6 @@ const Container = () => { )} - ); }; diff --git a/frontend/src/metadata/metadata-view/components/table/index.css b/frontend/src/metadata/metadata-view/components/table/index.css index c4404f3049f..903d1d3b7c1 100644 --- a/frontend/src/metadata/metadata-view/components/table/index.css +++ b/frontend/src/metadata/metadata-view/components/table/index.css @@ -129,7 +129,6 @@ } .sf-metadata-result-table-content .sf-metadata-result-table-row.sf-metadata-last-table-row { - height: 32px !important; border-bottom: none; } diff --git a/frontend/src/metadata/metadata-view/components/table/table-main/index.js b/frontend/src/metadata/metadata-view/components/table/table-main/index.js index d7fc117205e..92cd0895a17 100644 --- a/frontend/src/metadata/metadata-view/components/table/table-main/index.js +++ b/frontend/src/metadata/metadata-view/components/table/table-main/index.js @@ -4,7 +4,6 @@ import classnames from 'classnames'; import Records from './records'; import { GROUP_VIEW_OFFSET } from '../../../constants'; import GridUtils from '../../../utils/grid-utils'; -import { useRecordDetails } from '../../../hooks'; import './index.css'; @@ -14,8 +13,6 @@ const TableMain = ({ metadata, modifyRecord, modifyRecords, loadMore, loadAll, s return new GridUtils(metadata, { modifyRecord, modifyRecords, recordGetterByIndex, recordGetterById }); }, [metadata, modifyRecord, modifyRecords, recordGetterByIndex, recordGetterById]); - const { openRecordDetails } = useRecordDetails(); - const groupbysCount = useMemo(() => { const groupbys = metadata?.view?.groupbys || []; return groupbys.length; @@ -63,7 +60,6 @@ const TableMain = ({ metadata, modifyRecord, modifyRecords, loadMore, loadAll, s groupOffsetLeft={groupOffset} modifyRecord={updateRecord} updateRecords={updateRecords} - onRowExpand={openRecordDetails} getCopiedRecordsAndColumnsFromRange={getCopiedRecordsAndColumnsFromRange} recordGetterById={recordGetterById} recordGetterByIndex={recordGetterByIndex} diff --git a/frontend/src/metadata/metadata-view/components/table/table-main/records/body.js b/frontend/src/metadata/metadata-view/components/table/table-main/records/body.js index 04ee6ee92fc..42da0b93d90 100644 --- a/frontend/src/metadata/metadata-view/components/table/table-main/records/body.js +++ b/frontend/src/metadata/metadata-view/components/table/table-main/records/body.js @@ -232,10 +232,6 @@ class RecordsBody extends Component { }, 300); }; - onRowExpand = (row) => { - this.props.onRowExpand && this.props.onRowExpand(row); - }; - onScrollbarScroll = (scrollTop) => { // solve canvas&rightScrollbar circle scroll problem if (this.oldScrollTop === scrollTop) { @@ -482,7 +478,6 @@ class RecordsBody extends Component { selectedPosition={this.state.selectedPosition} selectNoneCells={this.selectNoneCells} onSelectRecord={this.props.onSelectRecord} - onRowExpand={this.onRowExpand} modifyRecord={this.props.modifyRecord} searchResult={this.props.searchResult} columnColor={columnColor} @@ -610,7 +605,6 @@ RecordsBody.propTypes = { getCopiedRecordsAndColumnsFromRange: PropTypes.func, openDownloadFilesDialog: PropTypes.func, cacheDownloadFilesProps: PropTypes.func, - onRowExpand: PropTypes.func, }; export default RecordsBody; diff --git a/frontend/src/metadata/metadata-view/components/table/table-main/records/group-body/index.js b/frontend/src/metadata/metadata-view/components/table/table-main/records/group-body/index.js index 2b394c48e18..5448053cf18 100644 --- a/frontend/src/metadata/metadata-view/components/table/table-main/records/group-body/index.js +++ b/frontend/src/metadata/metadata-view/components/table/table-main/records/group-body/index.js @@ -15,7 +15,7 @@ import { getColumnScrollPosition, getColVisibleEndIdx, getColVisibleStartIdx } f import { GROUP_HEADER_HEIGHT, GROUP_ROW_TYPE, GROUP_VIEW_OFFSET, SEQUENCE_COLUMN_WIDTH, EVENT_BUS_TYPE } from '../../../../../constants'; import { addClassName, removeClassName } from '../../../../../utils'; -const ROW_HEIGHT = 32; +const ROW_HEIGHT = 33; const GROUP_OVER_SCAN_ROWS = 10; const MAX_ANIMATION_ROWS = 50; const LOCAL_FOLDED_GROUP_KEY = 'path_folded_group'; @@ -384,10 +384,6 @@ class GroupBody extends Component { }, 300); }; - onRowExpand = (record) => { - this.props.onRowExpand && this.props.onRowExpand(record); - }; - setRightScrollbarScrollTop = (scrollTop) => { this.rightScrollbar && this.rightScrollbar.setScrollTop(scrollTop); }; @@ -826,7 +822,6 @@ class GroupBody extends Component { selectedPosition={this.state.selectedPosition} selectNoneCells={this.selectNoneCells} onSelectRecord={this.props.onSelectRecord} - onRowExpand={this.onRowExpand} modifyRecord={this.props.modifyRecord} lockRecordViaButton={this.props.lockRecordViaButton} modifyRecordViaButton={this.props.modifyRecordViaButton} diff --git a/frontend/src/metadata/metadata-view/components/table/table-main/records/record/actions-cell/index.js b/frontend/src/metadata/metadata-view/components/table/table-main/records/record/actions-cell/index.js index aaa2cde61ee..af7bc38b196 100644 --- a/frontend/src/metadata/metadata-view/components/table/table-main/records/record/actions-cell/index.js +++ b/frontend/src/metadata/metadata-view/components/table/table-main/records/record/actions-cell/index.js @@ -2,7 +2,6 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import classnames from 'classnames'; import { Tooltip } from 'reactstrap'; -import { Icon } from '@seafile/sf-metadata-ui-component'; import { SEQUENCE_COLUMN_WIDTH } from '../../../../../../constants'; import { isMobile, gettext } from '../../../../../../utils'; @@ -86,9 +85,6 @@ class ActionsCell extends Component { - - - {/* {this.getLockedRowTooltip()} */} ); @@ -103,7 +99,6 @@ ActionsCell.propTypes = { index: PropTypes.number, height: PropTypes.number, onSelectRecord: PropTypes.func, - onRowExpand: PropTypes.func, }; export default ActionsCell; diff --git a/frontend/src/metadata/metadata-view/components/table/table-main/records/record/index.js b/frontend/src/metadata/metadata-view/components/table/table-main/records/record/index.js index 2f36c029411..636f0c58d73 100644 --- a/frontend/src/metadata/metadata-view/components/table/table-main/records/record/index.js +++ b/frontend/src/metadata/metadata-view/components/table/table-main/records/record/index.js @@ -57,11 +57,6 @@ class Record extends React.Component { this.props.onSelectRecord({ groupRecordIndex, recordIndex: index }, e); }; - onRowExpand = () => { - const { record } = this.props; - this.props.onRowExpand(record); - }; - isCellSelected = (columnIdx) => { const { hasSelectedCell, selectedPosition } = this.props; if (!selectedPosition) return false; @@ -180,14 +175,15 @@ class Record extends React.Component { }; getRecordStyle = () => { - const { isGroupView, height } = this.props; - let style = { - height: height + 'px', - }; + const { isGroupView, height, isLastRecord } = this.props; + let style = { height: isLastRecord ? height - 1 : height }; if (isGroupView) { const { top, left } = this.props; - style.top = top + 'px'; - style.left = left + 'px'; + style.top = top; + style.left = left; + if (isLastRecord) { + style.height = height + 1; + } } return style; }; @@ -261,7 +257,6 @@ class Record extends React.Component { recordId={record._id} index={index} onSelectRecord={this.onSelectRecord} - onRowExpand={this.onRowExpand} isLastFrozenCell={!lastFrozenColumnKey} height={cellHeight} /> @@ -294,7 +289,6 @@ Record.propTypes = { height: PropTypes.number, selectNoneCells: PropTypes.func, onSelectRecord: PropTypes.func, - onRowExpand: PropTypes.func, modifyRecord: PropTypes.func, lockRecordViaButton: PropTypes.func, modifyRecordViaButton: PropTypes.func, diff --git a/frontend/src/metadata/metadata-view/components/table/table-main/records/records-header/actions-cell.jsx b/frontend/src/metadata/metadata-view/components/table/table-main/records/records-header/actions-cell.jsx index 9dcef651c03..b321ba9de1e 100644 --- a/frontend/src/metadata/metadata-view/components/table/table-main/records/records-header/actions-cell.jsx +++ b/frontend/src/metadata/metadata-view/components/table/table-main/records/records-header/actions-cell.jsx @@ -28,7 +28,6 @@ class ActionsCell extends Component { selectNoneRecords={this.props.selectNoneRecords} selectAllRecords={this.props.selectAllRecords} /> -
); } diff --git a/frontend/src/metadata/metadata-view/hooks/collaborators.js b/frontend/src/metadata/metadata-view/hooks/collaborators.js index 73df4a3e022..1aae3ca0b4c 100644 --- a/frontend/src/metadata/metadata-view/hooks/collaborators.js +++ b/frontend/src/metadata/metadata-view/hooks/collaborators.js @@ -1,5 +1,5 @@ /* eslint-disable react/prop-types */ -import React, { useContext, useState, useRef, useCallback, useEffect } from 'react'; +import React, { useContext, useState, useCallback, useEffect } from 'react'; import { useMetadata } from './metadata'; import { mediaUrl } from '../../../utils/constants'; import { isValidEmail } from '../_basic'; @@ -9,7 +9,6 @@ const CollaboratorsContext = React.createContext(null); export const CollaboratorsProvider = ({ children, }) => { - const collaboratorsCacheRef = useRef({}); const [collaboratorsCache, setCollaboratorsCache] = useState({}); const [collaborators, setCollaborators] = useState([]); @@ -26,10 +25,9 @@ export const CollaboratorsProvider = ({ }, [collaborators, collaboratorsCache]); const updateCollaboratorsCache = useCallback((user) => { - const newCollaboratorsCache = { ...collaboratorsCacheRef.current, [user.email]: user }; - collaboratorsCacheRef.current = newCollaboratorsCache; + const newCollaboratorsCache = { ...collaboratorsCache, [user.email]: user }; setCollaboratorsCache(newCollaboratorsCache); - }, []); + }, [collaboratorsCache]); const getCollaborator = useCallback((email) => { let collaborator = collaborators && collaborators.find(c => c.email === email); diff --git a/frontend/src/metadata/metadata-view/hooks/index.js b/frontend/src/metadata/metadata-view/hooks/index.js index 650e76b7f42..aaa50f5ec24 100644 --- a/frontend/src/metadata/metadata-view/hooks/index.js +++ b/frontend/src/metadata/metadata-view/hooks/index.js @@ -1,9 +1,7 @@ import { CollaboratorsProvider, useCollaborators } from './collaborators'; import { MetadataProvider, useMetadata } from './metadata'; -import { RecordDetailsProvider, useRecordDetails } from './record-details'; export { CollaboratorsProvider, useCollaborators, MetadataProvider, useMetadata, - RecordDetailsProvider, useRecordDetails, }; diff --git a/frontend/src/metadata/metadata-view/hooks/record-details.js b/frontend/src/metadata/metadata-view/hooks/record-details.js deleted file mode 100644 index 639d4fcf225..00000000000 --- a/frontend/src/metadata/metadata-view/hooks/record-details.js +++ /dev/null @@ -1,34 +0,0 @@ -/* eslint-disable react/prop-types */ -import React, { useContext, useState, useCallback } from 'react'; - -const RecordDetailsContext = React.createContext(null); - -export const RecordDetailsProvider = ({ children }) => { - const [isShowRecordDetails, setIsShowRecordDetails] = useState(false); - const [recordDetails, setRecordDetails] = useState({}); - - const openRecordDetails = useCallback((recordDetails) => { - setRecordDetails(recordDetails); - setIsShowRecordDetails(true); - }, []); - - const closeRecordDetails = useCallback(() => { - setRecordDetails({}); - setIsShowRecordDetails(false); - }, []); - - return ( - - {children} - - ); -}; - -export const useRecordDetails = () => { - const context = useContext(RecordDetailsContext); - if (!context) { - throw new Error('\'RecordDetailsContext\' is null'); - } - const { isShowRecordDetails, recordDetails, openRecordDetails, closeRecordDetails } = context; - return { isShowRecordDetails, recordDetails, openRecordDetails, closeRecordDetails }; -}; diff --git a/frontend/src/metadata/metadata-view/index.js b/frontend/src/metadata/metadata-view/index.js index 4aa1cb04ed6..8fd08bfc5d6 100644 --- a/frontend/src/metadata/metadata-view/index.js +++ b/frontend/src/metadata/metadata-view/index.js @@ -1,6 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { MetadataProvider, CollaboratorsProvider, RecordDetailsProvider } from './hooks/index'; +import { MetadataProvider, CollaboratorsProvider } from './hooks/index'; import { Table } from './components/index'; const SeafileMetadata = ({ ...params }) => { @@ -8,9 +8,7 @@ const SeafileMetadata = ({ ...params }) => { return ( - -
- +
); diff --git a/frontend/src/metadata/metadata-view/utils/column-utils.js b/frontend/src/metadata/metadata-view/utils/column-utils.js index 23e6d686a3c..bbab2cd2837 100644 --- a/frontend/src/metadata/metadata-view/utils/column-utils.js +++ b/frontend/src/metadata/metadata-view/utils/column-utils.js @@ -200,7 +200,7 @@ export const getColumnName = (key, name) => { } }; -const getColumnType = (key, type) => { +export const getColumnType = (key, type) => { switch (key) { case PRIVATE_COLUMN_KEY.CTIME: case PRIVATE_COLUMN_KEY.FILE_CTIME: diff --git a/frontend/src/metadata/metadata-view/utils/group-metrics.js b/frontend/src/metadata/metadata-view/utils/group-metrics.js index ad47b1cee26..e4d8e6d6cc8 100644 --- a/frontend/src/metadata/metadata-view/utils/group-metrics.js +++ b/frontend/src/metadata/metadata-view/utils/group-metrics.js @@ -81,7 +81,7 @@ export const getGroupsRows = ( const lastRowIndex = rowsLength - 1; const isRowVisible = isParentGroupVisible && isExpanded; const isBtnInsertRowVisible = isRowVisible && includeInsertRow; - const rowsHeight = isRowVisible ? rowsLength * rowHeight : 0; + const rowsHeight = isRowVisible ? rowsLength * rowHeight + 1 : 0; const btnInsertRowHeight = isBtnInsertRowVisible ? INSERT_ROW_HEIGHT : 0; let rows = row_ids.map((rowId, index) => { return { @@ -90,7 +90,7 @@ export const getGroupsRows = ( rowIdx: index, isLastRow: index === lastRowIndex, visible: isRowVisible, - height: rowHeight, + height: index === lastRowIndex ? rowHeight + 1 : rowHeight, level: currentLevel, rowsLength, left, diff --git a/frontend/src/metadata/metadata-view/utils/object-utils.js b/frontend/src/metadata/metadata-view/utils/object-utils.js index e25cd397f33..859c1a5d0f1 100644 --- a/frontend/src/metadata/metadata-view/utils/object-utils.js +++ b/frontend/src/metadata/metadata-view/utils/object-utils.js @@ -34,6 +34,7 @@ class ObjectUtils { } static isSameObject(source, comparison) { + if (!source && !comparison) return true; if (!source || !comparison) return false; return !this.isObjectChanged(source, comparison); } diff --git a/frontend/src/pages/lib-content-view/lib-content-container.js b/frontend/src/pages/lib-content-view/lib-content-container.js index 112447b782c..76b0ba03402 100644 --- a/frontend/src/pages/lib-content-view/lib-content-container.js +++ b/frontend/src/pages/lib-content-view/lib-content-container.js @@ -2,7 +2,7 @@ import React, { Fragment } from 'react'; import PropTypes from 'prop-types'; import { gettext } from '../../utils/constants'; import CurDirPath from '../../components/cur-dir-path'; -import { LibDetail, DirentDetail } from '../../components/dirent-detail'; +import Detail from '../../components/dirent-detail'; import DirColumnView from '../../components/dir-view-mode/dir-column-view'; import ToolbarForSelectedDirents from '../../components/toolbar/selected-dirents-toolbar'; @@ -319,23 +319,16 @@ class LibContentContainer extends React.Component { )} {this.props.isDirentDetailShow && (
- {(this.props.path === '/' && !this.state.currentDirent) ? - : - - } +
)} diff --git a/frontend/src/pages/lib-content-view/lib-content-view.js b/frontend/src/pages/lib-content-view/lib-content-view.js index 4425360bf55..d214375e162 100644 --- a/frontend/src/pages/lib-content-view/lib-content-view.js +++ b/frontend/src/pages/lib-content-view/lib-content-view.js @@ -416,7 +416,7 @@ class LibContentView extends React.Component { let repoID = this.props.repoID; if (!this.state.isSessionExpired) { - // update stste + // update state this.setState({ isDirentListLoading: true, isViewFile: false, @@ -494,7 +494,7 @@ class LibContentView extends React.Component { showFileMetadata = (filePath, viewId) => { const repoID = this.props.repoID; - this.setState({ path: filePath, isViewFile: true, isFileLoading: false, isFileLoadedErr: false, content: '__sf-metadata', metadataViewId: viewId }); + this.setState({ path: filePath, isViewFile: true, isFileLoading: false, isFileLoadedErr: false, content: '__sf-metadata', metadataViewId: viewId, isDirentDetailShow: false }); const repoInfo = this.state.currentRepoInfo; const url = siteRoot + 'library/' + repoID + '/' + encodeURIComponent(repoInfo.repo_name); window.history.pushState({ url: url, path: '' }, '', url); diff --git a/frontend/src/utils/extra-attributes.js b/frontend/src/utils/extra-attributes.js deleted file mode 100644 index 44fc77798dd..00000000000 --- a/frontend/src/utils/extra-attributes.js +++ /dev/null @@ -1,351 +0,0 @@ -import moment from 'moment'; -import { EXTRA_ATTRIBUTES_NOT_DISPLAY_COLUMN_KEY, DEFAULT_NUMBER_FORMAT, DISPLAY_INTERNAL_ERRORS, DURATION_FORMATS_MAP, - DURATION_FORMATS, DURATION_ZERO_DISPLAY, DURATION_DECIMAL_DIGITS, EXTRA_ATTRIBUTES_NOT_DISPLAY_COLUMN_NAME } from '../constants'; -import NP from './number-precision'; - -NP.enableBoundaryChecking(false); - -export const getValidColumns = (columns, editableColumns = [], isEmptyFile = false) => { - if (!Array.isArray(columns) || columns.length === 0) return []; - return columns - .map(column => { - let validColumn = column; - const canEdit = isEmptyFile ? false : editableColumns.includes(column.name); - if (column.type === 'single-select') { - if (!(column.data && column.data.options)) { - validColumn.data = { options: [] }; - } - } - validColumn.editable = canEdit; - return validColumn; - }) - .filter(column => !EXTRA_ATTRIBUTES_NOT_DISPLAY_COLUMN_KEY.includes(column.key)) - .filter(column => !EXTRA_ATTRIBUTES_NOT_DISPLAY_COLUMN_NAME.includes(column.name)); -}; - -export const getDateDisplayString = (value, format) => { - if (value === '' || !value || typeof value !== 'string') { - return ''; - } - // Compatible with older versions: if format is null, use defaultFormat - const validValue = value.replace(/-/g, '/').replace('T', ' ').replace('Z', ''); - const date = moment(validValue); - - if (!date.isValid()) return value; - switch (format) { - case 'D/M/YYYY': - case 'DD/MM/YYYY': { - const formatValue = date.format('YYYY-MM-DD'); - const formatValueList = formatValue.split('-'); - return `${formatValueList[2]}/${formatValueList[1]}/${formatValueList[0]}`; - } - case 'D/M/YYYY HH:mm': - case 'DD/MM/YYYY HH:mm': { - const formatValues = date.format('YYYY-MM-DD HH:mm'); - const formatValuesList = formatValues.split(' '); - const formatDateList = formatValuesList[0].split('-'); - return `${formatDateList[2]}/${formatDateList[1]}/${formatDateList[0]} ${formatValuesList[1]}`; - } - case 'M/D/YYYY': - return date.format('M/D/YYYY'); - case 'M/D/YYYY HH:mm': - return date.format('M/D/YYYY HH:mm'); - case 'YYYY-MM-DD': - return date.format('YYYY-MM-DD'); - case 'YYYY-MM-DD HH:mm': - return date.format('YYYY-MM-DD HH:mm'); - case 'YYYY-MM-DD HH:mm:ss': { - return date.format('YYYY-MM-DD HH:mm:ss'); - } - case 'DD.MM.YYYY': - return date.format('DD.MM.YYYY'); - case 'DD.MM.YYYY HH:mm': - return date.format('DD.MM.YYYY HH:mm'); - default: - return date.format('YYYY-MM-DD'); - } -}; - -export const getSelectColumnOptions = (column) => { - if (!column || !column.data || !Array.isArray(column.data.options)) { - return []; - } - return column.data.options; -}; - -const _getMathRoundedDuration = (num, duration_format) => { - const decimalDigits = DURATION_DECIMAL_DIGITS[duration_format]; - if (decimalDigits < 1) { - return num; - } - const ratio = Math.pow(10, decimalDigits); - return Math.round(num * ratio) / ratio; -}; - -const _getDurationDecimalSuffix = (duration_format, decimal) => { - if (duration_format === DURATION_FORMATS_MAP.H_MM_SS_S) { - return decimal === 0 ? '.0' : ''; - } else if (duration_format === DURATION_FORMATS_MAP.H_MM_SS_SS) { - if (decimal === 0) { - return '.00'; - } else if (decimal < 10) { - return '0'; - } - } else if (duration_format === DURATION_FORMATS_MAP.H_MM_SS_SSS) { - if (decimal === 0) { - return '.000'; - } else if (decimal < 10) { - return '00'; - } else if (decimal < 100) { - return '0'; - } - } - return ''; -}; - -export const getDurationDisplayString = (value, data) => { - if (!value && value !== 0) return ''; - let { duration_format } = data || {}; - duration_format = duration_format || DURATION_FORMATS_MAP.H_MM; - if (DURATION_FORMATS.findIndex((format) => format.type === duration_format) < 0) { - return ''; - } - if (value === 0) { - return DURATION_ZERO_DISPLAY[duration_format]; - } - const includeDecimal = duration_format.indexOf('.') > -1; - let positiveValue = Math.abs(value); - if (!includeDecimal) { - positiveValue = Math.round(positiveValue); - } - - positiveValue = _getMathRoundedDuration(positiveValue, duration_format); - const decimalParts = (positiveValue + '').split('.'); - const decimalPartsLen = decimalParts.length; - let decimal = 0; - if (decimalPartsLen > 1) { - decimal = decimalParts[decimalPartsLen - 1]; - decimal = decimal ? decimal - 0 : 0; - } - const decimalDigits = DURATION_DECIMAL_DIGITS[duration_format]; - const decimalSuffix = _getDurationDecimalSuffix(duration_format, decimal); - let displayString = value < 0 ? '-' : ''; - let hours = parseInt(positiveValue / 3600); - let minutes = parseInt((positiveValue - hours * 3600) / 60); - if (duration_format === DURATION_FORMATS_MAP.H_MM) { - displayString += `${hours}:${minutes > 9 ? minutes : '0' + minutes}`; - return displayString; - } - let seconds = Number.parseFloat((positiveValue - hours * 3600 - minutes * 60).toFixed(decimalDigits)); - minutes = minutes > 9 ? minutes : `0${minutes}`; - seconds = seconds > 9 ? seconds : `0${seconds}`; - displayString += `${hours}:${minutes}:${seconds}${decimalSuffix}`; - return displayString; -}; - -const _separatorMap = { - 'comma': ',', - 'dot': '.', - 'no': '', - 'space': ' ', -}; - -const _toThousands = (num, isCurrency, formatData) => { - let { decimal = 'dot', thousands = 'no', precision = 2, enable_precision = false } = formatData || {}; - const decimalString = _separatorMap[decimal]; - const thousandsString = _separatorMap[thousands]; - if ((num + '').indexOf('e') > -1) { - if (num < 1 && num > -1) { - // 1.convert to non-scientific number - let numericString = num.toFixed(enable_precision ? precision : 8); - - // 2.remove 0 from end of the number which not set precision. e.g. 0.100000 - if (!enable_precision) { - numericString = removeZerosFromEnd(numericString); - } - - // 3.remove minus from number which equal to 0. e.g. '-0.00' - if (parseFloat(numericString) === 0) { - return numericString.startsWith('-') ? numericString.substring(1) : numericString; - } - return numericString; - } - return num; - } - const decimalDigits = enable_precision ? precision : _getDecimalDigits(num); - let value = parseFloat(num.toFixed(decimalDigits)); - const isMinus = value < 0; - let integer = Math.trunc(value); - // format decimal value - let decimalValue = String(Math.abs(NP.minus(value, integer)).toFixed(decimalDigits)).slice(1); - if (!enable_precision) { - decimalValue = removeZerosFromEnd(decimalValue); - } - if (isCurrency) { - if (!enable_precision) { - if (decimalValue.length === 2) { - decimalValue = decimalValue.padEnd(3, '0'); - } else { - decimalValue = (decimalValue.substring(0, 3) || '.').padEnd(3, '0'); - } - } - } - decimalValue = decimalValue.replace(/./, decimalString); - // format integer value - let result = []; let counter = 0; - integer = Math.abs(integer).toString(); - for (var i = integer.length - 1; i >= 0; i--) { - counter++; - result.unshift(integer[i]); - if (!(counter % 3) && i !== 0) { - result.unshift(thousandsString); - } - } - return (isMinus ? '-' : '') + result.join('') + decimalValue; -}; - -const _getDecimalDigits = (num) => { - if (Number.isInteger(num)) { - return 0; - } - let valueArr = (num + '').split('.'); - let digitsLength = valueArr[1] ? valueArr[1].length : 8; - return digitsLength > 8 ? 8 : digitsLength; -}; - -/** - * @param {string} value - * e.g. removeZerosFromEnd('0.0100') // '0.01' - */ -const removeZerosFromEnd = (value) => { - if (value.endsWith('0')) { - return value.replace(/(?:\.0*|(\.\d+?)0+)$/, '$1'); - } - return value; -}; - -export const getPrecisionNumber = (num, formatData) => { - let { precision = 2, enable_precision = false } = formatData || {}; - let type = Object.prototype.toString.call(num); - if (type !== '[object Number]') { - if (type === '[object String]' && DISPLAY_INTERNAL_ERRORS.includes(num)) { - return num; - } - return null; - } - let decimalDigits = enable_precision ? precision : _getDecimalDigits(num); - return num.toFixed(decimalDigits); -}; - -export const getNumberDisplayString = (value, formatData) => { - // formatData: old version maybe 'null' - const type = Object.prototype.toString.call(value); - if (type !== '[object Number]') { - // return formula internal errors directly. - if (type === '[object String]' && value.startsWith('#')) { - return value; - } - return ''; - } - if (isNaN(value) || value === Infinity || value === -Infinity) return value + ''; - const { format = DEFAULT_NUMBER_FORMAT } = formatData || {}; - switch (format) { - case 'number': { - return _toThousands(value, false, formatData); - } - case 'percent': { - return `${_toThousands(Number.parseFloat((value * 100).toFixed(8)), false, formatData)}%`; - } - case 'yuan': { - return `¥${_toThousands(value, true, formatData)}`; - } - case 'dollar': { - return `$${_toThousands(value, true, formatData)}`; - } - case 'euro': { - return `€${_toThousands(value, true, formatData)}`; - } - case 'duration': { - return getDurationDisplayString(value, formatData); - } - case 'custom_currency': { - if (formatData.currency_symbol_position === 'after') { - return `${_toThousands(value, true, formatData)}${formatData.currency_symbol || ''}`; - } else { - return `${formatData.currency_symbol || ''}${_toThousands(value, true, formatData)}`; - } - } - default: - return '' + value; - } -}; - -export const replaceNumberNotAllowInput = (value, format = DEFAULT_NUMBER_FORMAT, currency_symbol = null) => { - if (!value) { - return ''; - } - value = value.replace(/。/g, '.'); - switch (format) { - case 'number': { - return value.replace(/[^.-\d,]/g, ''); - } - case 'percent': { - return value.replace(/[^.-\d,%]/g, ''); - } - case 'yuan': { - return value.replace(/[^.-\d¥¥,]/g, ''); - } - case 'dollar': { - return value.replace(/[^.-\d$,]/g, ''); - } - case 'euro': { - return value.replace(/[^.-\d€,]/g, ''); - } - case 'custom_currency': { - // eslint-disable-next-line - const reg = new RegExp('[^.-\d' + currency_symbol + ',]', 'g'); - return value.replace(reg, ''); - } - default: - return value.replace(/[^.-\d,]/g, ''); - } -}; - -export const getFloatNumber = (data, format) => { - if (!data && data !== 0) { - return null; - } - let newData = parseFloat(data.replace(/[^.-\d]/g, '')); - if (format === 'percent' && !isNaN(newData)) { - return NP.divide(newData, 100); - } - return isNaN(newData) ? null : newData; -}; - -export const formatStringToNumber = (numberString, formatData) => { - let { format, decimal, thousands, enable_precision, precision } = formatData || {}; - let value = numberString; - if (decimal && thousands && decimal === 'comma') { - if (thousands === 'dot') { - value = value.replace(/,/, '@'); - value = value.replace(/\./g, ','); - value = value.replace(/@/, '.'); - } else { - value = value.replace(/\./g, ''); - value = value.replace(/,/, '.'); - } - } - value = getFloatNumber(value, format); - if (enable_precision && value) { - if (format === 'percent') { - precision += 2; - } - value = Number(parseFloat(value).toFixed(precision)); - } - return value; -}; - -export const isMac = () => { - const platform = navigator.platform; - return (platform == 'Mac68K') || (platform == 'MacPPC') || (platform == 'Macintosh') || (platform == 'MacIntel'); -}; diff --git a/frontend/src/utils/number-precision.js b/frontend/src/utils/number-precision.js deleted file mode 100644 index 79f83ef8bf7..00000000000 --- a/frontend/src/utils/number-precision.js +++ /dev/null @@ -1,122 +0,0 @@ -/** - * @desc Solve the problem of floating calculation, avoid multiple digits after the decimal point and loss of calculation accuracy. - * example: 3 + 2.4 = 4.699999999999999,1.0 - 0.9 = 0.09999999999999998 - */ - -/** - * Correct wrong data - * strip(0.09999999999999998)=0.1 - */ -function strip(num, precision = 12) { - return +parseFloat(num.toPrecision(precision)); -} - -/** - * Return digits length of a number - * @param {*number} num Input number - */ -function digitLength(num) { - // Get digit length of e - const eSplit = num.toString().split(/[eE]/); - const len = (eSplit[0].split('.')[1] || '').length - (+(eSplit[1] || 0)); - return len > 0 ? len : 0; -} - -/** - * Convert decimals to integers and support scientific notation. If it is a decimal, it is enlarged to an integer - * @param {*number} num Number of inputs - */ -function float2Fixed(num) { - if (num.toString().indexOf('e') === -1) { - return Number(num.toString().replace('.', '')); - } - const dLen = digitLength(num); - return dLen > 0 ? strip(num * Math.pow(10, dLen)) : num; -} - -/** - * Check whether the number is out of range, and give a prompt if it is out of range - * @param {*number} num Number of inputs - */ -function checkBoundary(num) { - if (_boundaryCheckingState) { - if (num > Number.MAX_SAFE_INTEGER || num < Number.MIN_SAFE_INTEGER) { - // eslint-disable-next-line no-console - console.warn(`${num} is beyond boundary when transfer to integer, the results may not be accurate`); - } - } -} - -/** - * Exact multiplication - */ -function times(num1, num2, ...others) { - if (others.length > 0) { - return times(times(num1, num2), others[0], ...others.slice(1)); - } - const num1Changed = float2Fixed(num1); - const num2Changed = float2Fixed(num2); - const baseNum = digitLength(num1) + digitLength(num2); - const leftValue = num1Changed * num2Changed; - - checkBoundary(leftValue); - - return leftValue / Math.pow(10, baseNum); -} - -/** - * Exact addition - */ -function plus(num1, num2, ...others) { - if (others.length > 0) { - return plus(plus(num1, num2), others[0], ...others.slice(1)); - } - const baseNum = Math.pow(10, Math.max(digitLength(num1), digitLength(num2))); - return (times(num1, baseNum) + times(num2, baseNum)) / baseNum; -} - -/** - * Exact subtraction - */ -function minus(num1, num2, ...others) { - if (others.length > 0) { - return minus(minus(num1, num2), others[0], ...others.slice(1)); - } - const baseNum = Math.pow(10, Math.max(digitLength(num1), digitLength(num2))); - return (times(num1, baseNum) - times(num2, baseNum)) / baseNum; -} - -/** - * Exact division - */ -function divide(num1, num2, ...others) { - if (others.length > 0) { - return divide(divide(num1, num2), others[0], ...others.slice(1)); - } - const num1Changed = float2Fixed(num1); - const num2Changed = float2Fixed(num2); - checkBoundary(num1Changed); - checkBoundary(num2Changed); - // fix: Similar to 10 ** -4 is 0.00009999999999999999, strip correction - return times((num1Changed / num2Changed), strip(Math.pow(10, digitLength(num2) - digitLength(num1)))); -} - -/** - * rounding - */ -function round(num, ratio) { - const base = Math.pow(10, ratio); - return divide(Math.round(times(num, base)), base); -} - -let _boundaryCheckingState = true; -/** - * Whether to perform boundary check, default true - * @param flag Mark switch, true is on, false is off, default is true - */ -function enableBoundaryChecking(flag = true) { - _boundaryCheckingState = flag; -} -export { strip, plus, minus, times, divide, round, digitLength, float2Fixed, enableBoundaryChecking }; -// eslint-disable-next-line -export default { strip, plus, minus, times, divide, round, digitLength, float2Fixed, enableBoundaryChecking }; diff --git a/frontend/src/utils/utils.js b/frontend/src/utils/utils.js index 371e792a8ce..bbd5951ab00 100644 --- a/frontend/src/utils/utils.js +++ b/frontend/src/utils/utils.js @@ -1650,7 +1650,12 @@ export const Utils = { isRelativePath(url) { let RgExp = new RegExp('^(?:[a-z]+:)?//', 'i'); return !RgExp.test(url); - } + }, + + isMac() { + const platform = navigator.platform; + return (platform == 'Mac68K') || (platform == 'MacPPC') || (platform == 'Macintosh') || (platform == 'MacIntel'); + }, }; diff --git a/seahub/api2/endpoints/metadata_manage.py b/seahub/api2/endpoints/metadata_manage.py index 9dce0b00aa3..6a1122c95b5 100644 --- a/seahub/api2/endpoints/metadata_manage.py +++ b/seahub/api2/endpoints/metadata_manage.py @@ -307,8 +307,8 @@ def get(self, request, repo_id): error_msg = 'name invalid' return api_error(status.HTTP_400_BAD_REQUEST, error_msg) - record = RepoMetadata.objects.filter(repo_id=repo_id).first() - if not record or not record.enabled: + metadata = RepoMetadata.objects.filter(repo_id=repo_id).first() + if not metadata or not metadata.enabled: error_msg = f'The metadata module is disabled for repo {repo_id}.' return api_error(status.HTTP_404_NOT_FOUND, error_msg) @@ -337,40 +337,12 @@ def get(self, request, repo_id): error_msg = 'Internal Server Error' return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg) - sys_columns = [ - METADATA_TABLE.columns.id.key, - METADATA_TABLE.columns.file_creator.key, - METADATA_TABLE.columns.file_ctime.key, - METADATA_TABLE.columns.file_modifier.key, - METADATA_TABLE.columns.file_mtime.key, - METADATA_TABLE.columns.parent_dir.key, - METADATA_TABLE.columns.file_name.key, - METADATA_TABLE.columns.is_dir.key, - ] - rows = query_result.get('results') if not rows: error_msg = 'Record not found' return api_error(status.HTTP_404_NOT_FOUND, error_msg) - metadata = query_result.get('metadata') - editable_columns = [] - name_to_key = {} - for col in metadata: - col_key = col.get('key') - col_name = col.get('name') - name_to_key[col_name] = col_key - if col_key in sys_columns: - continue - editable_columns.append(col.get('name')) - - row = {name_to_key[name]: value for name, value in rows[0].items()} - query_result['row'] = row - query_result['editable_columns'] = editable_columns - - query_result.pop('results', None) - return Response(query_result)