From fa56e8a94f92d68f925b170b4f70ffc2873b1228 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=A8=E5=9B=BD=E7=92=87?= Date: Mon, 5 Aug 2024 11:56:40 +0800 Subject: [PATCH 1/5] feat: side properties --- .../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-details/dir-details.js | 4 - .../dirent-details/edit-metadata/index.css | 31 -- .../dirent-details/edit-metadata/index.js | 41 -- .../dirent-details/file-details.js | 9 +- .../components/dirent-detail/header/index.css | 1 - frontend/src/components/search/ai-search.js | 4 +- frontend/src/components/search/search.js | 3 +- frontend/src/constants/index.js | 103 ----- frontend/src/utils/extra-attributes.js | 351 ------------------ frontend/src/utils/number-precision.js | 122 ------ frontend/src/utils/utils.js | 7 +- 26 files changed, 12 insertions(+), 1700 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 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/dirent-details/dir-details.js b/frontend/src/components/dirent-detail/dirent-details/dir-details.js index 36cb42e7b50..3cc54597d56 100644 --- a/frontend/src/components/dirent-detail/dirent-details/dir-details.js +++ b/frontend/src/components/dirent-detail/dirent-details/dir-details.js @@ -4,7 +4,6 @@ import { getDirentPath, getDirentPosition } from './utils'; import DetailItem from '../detail-item'; import { CellType } from '../../../metadata/metadata-view/_basic'; import { gettext } from '../../../utils/constants'; -import EditMetadata from './edit-metadata'; const DirDetails = ({ repoID, repoInfo, dirent, direntType, path, direntDetail }) => { const position = useMemo(() => getDirentPosition(repoInfo, dirent, path), [repoInfo, dirent, path]); @@ -21,9 +20,6 @@ const DirDetails = ({ repoID, repoInfo, dirent, direntType, path, direntDetail } avatar_url: repoInfo.owner_avatar, }]} /> - {direntDetail.permission === 'rw' && window.app.pageOptions.enableMetadataManagement && ( - - )} ); }; 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..165580b636d 100644 --- a/frontend/src/components/dirent-detail/dirent-details/file-details.js +++ b/frontend/src/components/dirent-detail/dirent-details/file-details.js @@ -5,12 +5,12 @@ import { getDirentPath, getDirentPosition } 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'; -const FileDetails = ({ repoID, repoInfo, dirent, direntType, path, direntDetail, onFileTagChanged, repoTags, fileTagList }) => { +// direntType +const FileDetails = ({ repoID, repoInfo, dirent, path, direntDetail, onFileTagChanged, repoTags, fileTagList }) => { const [isEditFileTagShow, setEditFileTagShow] = useState(false); const position = useMemo(() => getDirentPosition(repoInfo, dirent, path), [repoInfo, dirent, path]); @@ -29,7 +29,7 @@ const FileDetails = ({ repoID, repoInfo, dirent, direntType, path, direntDetail, <> - {gettext('Empty')} )} - {direntDetail.permission === 'rw' && window.app.pageOptions.enableMetadataManagement && ( - - )} {isEditFileTagShow && { - 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 54ed04b4571..4480ae66001 100644 --- a/frontend/src/utils/utils.js +++ b/frontend/src/utils/utils.js @@ -1642,7 +1642,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'); + }, }; From 7037891f3a98ff09c1e0c4110e0369e821b3b5f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=A8=E5=9B=BD=E7=92=87?= Date: Mon, 5 Aug 2024 15:52:52 +0800 Subject: [PATCH 2/5] feat: show metadata --- .../dirent-detail/detail-item/index.css | 6 ++ .../dirent-detail/detail-item/index.js | 5 ++ .../dirent-detail/detail-list-view.js | 4 +- .../dirent-details/dir-details.js | 10 +++- .../dirent-details/file-details.js | 29 ++++++---- .../dirent-detail/dirent-details/index.css | 2 +- .../dirent-detail/dirent-details/utils.js | 3 +- frontend/src/metadata/index.js | 2 + .../metadata/metadata-details/constants.js | 22 ++++++++ .../src/metadata/metadata-details/index.js | 56 +++++++++++++++++++ .../src/metadata/metadata-details/utils.js | 25 +++++++++ .../metadata-view/utils/column-utils.js | 2 +- seahub/api2/endpoints/metadata_manage.py | 32 +---------- 13 files changed, 148 insertions(+), 50 deletions(-) create mode 100644 frontend/src/metadata/metadata-details/constants.js create mode 100644 frontend/src/metadata/metadata-details/index.js create mode 100644 frontend/src/metadata/metadata-details/utils.js 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..6f1ee5f8587 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'; @@ -25,6 +26,10 @@ const DetailItem = ({ field, value, valueId, valueClick, children, ...params }) ); }; +DetailItem.defaultProps = { + emptyTip: gettext('Empty') +}; + DetailItem.propTypes = { field: PropTypes.object.isRequired, value: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.array, PropTypes.object]), 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 3cc54597d56..3d666a1d3c5 100644 --- a/frontend/src/components/dirent-detail/dirent-details/dir-details.js +++ b/frontend/src/components/dirent-detail/dirent-details/dir-details.js @@ -1,17 +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 { MetadataDetails } from '../../../metadata'; const DirDetails = ({ repoID, repoInfo, dirent, direntType, path, direntDetail }) => { - 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]); return ( <> - + + {window.app.pageOptions.enableMetadataManagement && ( + + )} ); }; 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 165580b636d..08a9f9d6943 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,19 @@ 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 EditFileTagPopover from '../../popover/edit-filetag-popover'; import FileTagList from '../../file-tag-list'; import { Utils } from '../../../utils/utils'; +import { MetadataDetails } from '../../../metadata'; -// direntType -const FileDetails = ({ repoID, repoInfo, dirent, path, direntDetail, onFileTagChanged, repoTags, fileTagList }) => { +const FileDetails = ({ repoID, repoInfo, dirent, path, direntDetail, direntType, onFileTagChanged, repoTags, fileTagList }) => { 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,7 +27,7 @@ const FileDetails = ({ repoID, repoInfo, dirent, path, direntDetail, onFileTagCh return ( <> - + - - {Array.isArray(fileTagList) && fileTagList.length > 0 ? ( - - ) : ( - {gettext('Empty')} - )} - + {!window.app.pageOptions.enableMetadataManagement && ( + + {Array.isArray(fileTagList) && fileTagList.length > 0 ? ( + + ) : ( + {gettext('Empty')} + )} + + )} + {window.app.pageOptions.enableMetadataManagement && ( + + )} {isEditFileTagShow && { 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/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.js b/frontend/src/metadata/metadata-details/index.js new file mode 100644 index 00000000000..e2ca524bb79 --- /dev/null +++ b/frontend/src/metadata/metadata-details/index.js @@ -0,0 +1,56 @@ +import React, { useEffect, useMemo, 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 toaster from '../../components/toast'; +import DetailItem from '../../components/dirent-detail/detail-item'; + +const MetadataDetails = ({ repoID, filePath, direntType, direntDetail, emptyTip }) => { + const [isLoading, setLoading] = useState(true); + const [metadata, setMetadata] = useState({ record: {}, fields: [] }); + const isEmptyFile = useMemo(() => { + if (direntType === 'dir') return false; + const direntDetailId = direntDetail?.id || ''; + return direntDetailId === '0'.repeat(direntDetailId.length); + }, [direntDetail, direntType]); + + 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 errorMsg = Utils.getErrorMsg(error); + toaster.danger(errorMsg); + 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/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/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) From bf3208d8ebdc55a6fc2e6be7b557ea370ea2b957 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=A8=E5=9B=BD=E7=92=87?= Date: Mon, 5 Aug 2024 17:48:41 +0800 Subject: [PATCH 3/5] feat: delete record expand --- .../dirent-detail/detail-item/index.js | 2 +- .../dirent-details/dir-details.js | 2 +- .../dirent-details/file-details.js | 2 +- .../dirent-detail/dirent-details/index.js | 36 +- .../src/metadata/metadata-details/index.js | 11 +- .../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 - .../components/table/table-main/index.js | 4 - .../table/table-main/records/body.js | 6 - .../table-main/records/group-body/index.js | 5 - .../records/record/actions-cell/index.js | 5 - .../table/table-main/records/record/index.js | 7 - .../records/records-header/actions-cell.jsx | 1 - .../src/metadata/metadata-view/hooks/index.js | 2 - .../metadata-view/hooks/record-details.js | 34 -- frontend/src/metadata/metadata-view/index.js | 6 +- 21 files changed, 26 insertions(+), 647 deletions(-) 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 diff --git a/frontend/src/components/dirent-detail/detail-item/index.js b/frontend/src/components/dirent-detail/detail-item/index.js index 6f1ee5f8587..f0b03dc6ae3 100644 --- a/frontend/src/components/dirent-detail/detail-item/index.js +++ b/frontend/src/components/dirent-detail/detail-item/index.js @@ -32,7 +32,7 @@ DetailItem.defaultProps = { 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/dirent-details/dir-details.js b/frontend/src/components/dirent-detail/dirent-details/dir-details.js index 3d666a1d3c5..5afe937d3f1 100644 --- a/frontend/src/components/dirent-detail/dirent-details/dir-details.js +++ b/frontend/src/components/dirent-detail/dirent-details/dir-details.js @@ -22,7 +22,7 @@ const DirDetails = ({ repoID, repoInfo, dirent, direntType, path, direntDetail } }]} /> {window.app.pageOptions.enableMetadataManagement && ( - + )} ); 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 08a9f9d6943..24a83c1cdf6 100644 --- a/frontend/src/components/dirent-detail/dirent-details/file-details.js +++ b/frontend/src/components/dirent-detail/dirent-details/file-details.js @@ -46,7 +46,7 @@ const FileDetails = ({ repoID, repoInfo, dirent, path, direntDetail, direntType, )} {window.app.pageOptions.enableMetadataManagement && ( - + )} {isEditFileTagShow && { +const DirentDetails = React.memo(({ dirent: propsDirent, path, repoID, currentRepoInfo, repoTags, fileTags, onItemDetailsClose, onFileTagChanged }) => { const [direntType, setDirentType] = useState(''); const [direntDetail, setDirentDetail] = useState(''); - const [folderDirent, setFolderDirent] = useState(null); - const direntRef = useRef(null); + const [dirent, setDirent] = useState(null); const updateDetailView = useCallback((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); + setDirent(dirent); }).catch(error => { const errMessage = Utils.getErrorMsg(error); toaster.danger(errMessage); @@ -29,11 +29,11 @@ const DirentDetails = ({ dirent, path, repoID, currentRepoInfo, repoTags, fileTa }, []); useEffect(() => { - if (direntRef.current && dirent === direntRef.current) return; - direntRef.current = dirent; - if (dirent) { - const direntPath = Utils.joinPath(path, dirent.name); - updateDetailView(repoID, dirent, direntPath); + console.log('index 组件更新'); + setDirent(null); + if (propsDirent) { + const direntPath = Utils.joinPath(path, propsDirent.name); + updateDetailView(repoID, propsDirent, direntPath); return; } const dirPath = Utils.getDirName(path); @@ -47,25 +47,25 @@ const DirentDetails = ({ dirent, path, repoID, currentRepoInfo, repoTags, fileTa break; } } - setFolderDirent(folderDirent); updateDetailView(repoID, folderDirent, path); }).catch(error => { const errMessage = Utils.getErrorMsg(error); toaster.danger(errMessage); }); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [dirent, path, repoID]); + }, [propsDirent, path]); - if (!dirent && !folderDirent) return ''; - const direntName = dirent ? dirent.name : folderDirent.name; - const smallIconUrl = dirent ? Utils.getDirentIcon(dirent) : Utils.getDirentIcon(folderDirent); + if (!dirent) return null; + const direntName = dirent.name; + const smallIconUrl = Utils.getDirentIcon(dirent); // 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 isImg = Utils.imageCheck(dirent.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}`); } + return (
@@ -81,7 +81,7 @@ const DirentDetails = ({ dirent, path, repoID, currentRepoInfo, repoTags, fileTa
); -}; +}); DirentDetails.propTypes = { repoID: PropTypes.string.isRequired, diff --git a/frontend/src/metadata/metadata-details/index.js b/frontend/src/metadata/metadata-details/index.js index e2ca524bb79..881cfa8369a 100644 --- a/frontend/src/metadata/metadata-details/index.js +++ b/frontend/src/metadata/metadata-details/index.js @@ -1,4 +1,4 @@ -import React, { useEffect, useMemo, useState } from 'react'; +import React, { useEffect, useState } from 'react'; import PropTypes from 'prop-types'; import { Utils } from '../../utils/utils'; import metadataAPI from '../api'; @@ -7,14 +7,9 @@ import { normalizeFields, getCellValueByColumn } from './utils'; import toaster from '../../components/toast'; import DetailItem from '../../components/dirent-detail/detail-item'; -const MetadataDetails = ({ repoID, filePath, direntType, direntDetail, emptyTip }) => { +const MetadataDetails = React.memo(({ repoID, filePath, direntType, emptyTip }) => { const [isLoading, setLoading] = useState(true); const [metadata, setMetadata] = useState({ record: {}, fields: [] }); - const isEmptyFile = useMemo(() => { - if (direntType === 'dir') return false; - const direntDetailId = direntDetail?.id || ''; - return direntDetailId === '0'.repeat(direntDetailId.length); - }, [direntDetail, direntType]); useEffect(() => { setLoading(true); @@ -44,7 +39,7 @@ const MetadataDetails = ({ repoID, filePath, direntType, direntDetail, emptyTip const value = getCellValueByColumn(record, field); return (); }); -}; +}); MetadataDetails.propTypes = { repoID: PropTypes.string, 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/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..e4232c1ce3e 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 @@ -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..334e47a3023 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; @@ -261,7 +256,6 @@ class Record extends React.Component { recordId={record._id} index={index} onSelectRecord={this.onSelectRecord} - onRowExpand={this.onRowExpand} isLastFrozenCell={!lastFrozenColumnKey} height={cellHeight} /> @@ -294,7 +288,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/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 ( - -
- +
); From 5b1e7ba0e8c01dd860908dea48ab4f0526a9dd4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=A8=E5=9B=BD=E7=92=87?= Date: Tue, 6 Aug 2024 12:05:21 +0800 Subject: [PATCH 4/5] feat: optimzie code --- .../dirent-details/dir-details.js | 5 +- .../dirent-details/file-details.js | 18 ++++++-- .../dirent-detail/dirent-details/index.js | 42 +++++++++-------- .../src/components/dirent-detail/index.js | 46 +++++++++++++++++-- .../dirent-detail/lib-details/index.js | 14 +++--- .../src/metadata/metadata-details/index.js | 7 +-- .../metadata-view/components/table/index.css | 1 - .../table-main/records/group-body/index.js | 2 +- .../table/table-main/records/record/index.js | 13 +++--- .../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 +- 13 files changed, 114 insertions(+), 72 deletions(-) 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 5afe937d3f1..a749ef303f2 100644 --- a/frontend/src/components/dirent-detail/dirent-details/dir-details.js +++ b/frontend/src/components/dirent-detail/dirent-details/dir-details.js @@ -6,7 +6,7 @@ import { CellType } from '../../../metadata/metadata-view/_basic'; import { gettext } from '../../../utils/constants'; import { MetadataDetails } from '../../../metadata'; -const DirDetails = ({ repoID, repoInfo, dirent, direntType, path, direntDetail }) => { +const DirDetails = ({ repoID, repoInfo, dirent, path, direntDetail }) => { const parent = useMemo(() => getFileParent(repoInfo, dirent, path), [repoInfo, dirent, path]); const direntPath = useMemo(() => getDirentPath(dirent, path), [dirent, path]); @@ -22,7 +22,7 @@ const DirDetails = ({ repoID, repoInfo, dirent, direntType, path, direntDetail } }]} /> {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/file-details.js b/frontend/src/components/dirent-detail/dirent-details/file-details.js index 24a83c1cdf6..645bd9422a4 100644 --- a/frontend/src/components/dirent-detail/dirent-details/file-details.js +++ b/frontend/src/components/dirent-detail/dirent-details/file-details.js @@ -9,8 +9,9 @@ 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, path, direntDetail, direntType, onFileTagChanged, repoTags, fileTagList }) => { +const FileDetails = React.memo(({ repoID, repoInfo, dirent, path, direntDetail, onFileTagChanged, repoTags, fileTagList }) => { const [isEditFileTagShow, setEditFileTagShow] = useState(false); const parent = useMemo(() => getFileParent(repoInfo, dirent, path), [repoInfo, dirent, path]); @@ -46,7 +47,7 @@ const FileDetails = ({ repoID, repoInfo, dirent, path, direntDetail, direntType, )} {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.js b/frontend/src/components/dirent-detail/dirent-details/index.js index be12c07061e..588eb8afec7 100644 --- a/frontend/src/components/dirent-detail/dirent-details/index.js +++ b/frontend/src/components/dirent-detail/dirent-details/index.js @@ -8,18 +8,17 @@ 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 './index.css'; -const DirentDetails = React.memo(({ dirent: propsDirent, path, repoID, currentRepoInfo, repoTags, fileTags, onItemDetailsClose, onFileTagChanged }) => { - const [direntType, setDirentType] = useState(''); +const DirentDetails = React.memo(({ dirent: propsDirent, path, repoID, currentRepoInfo, repoTags, fileTags, onClose, onFileTagChanged }) => { const [direntDetail, setDirentDetail] = useState(''); const [dirent, setDirent] = useState(null); const updateDetailView = useCallback((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); setDirent(dirent); }).catch(error => { @@ -29,7 +28,6 @@ const DirentDetails = React.memo(({ dirent: propsDirent, path, repoID, currentRe }, []); useEffect(() => { - console.log('index 组件更新'); setDirent(null); if (propsDirent) { const direntPath = Utils.joinPath(path, propsDirent.name); @@ -39,13 +37,9 @@ const DirentDetails = React.memo(({ dirent: propsDirent, path, repoID, currentRe 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); } updateDetailView(repoID, folderDirent, path); }).catch(error => { @@ -53,22 +47,22 @@ const DirentDetails = React.memo(({ dirent: propsDirent, path, repoID, currentRe toaster.danger(errMessage); }); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [propsDirent, path]); + }, [propsDirent, path, repoID]); if (!dirent) return null; const direntName = dirent.name; const smallIconUrl = Utils.getDirentIcon(dirent); - // let bigIconUrl = dirent ? Utils.getDirentIcon(dirent, true) : Utils.getDirentIcon(folderDirent, true); + // let bigIconUrl = Utils.getDirentIcon(dirent, true); let bigIconUrl = ''; const isImg = Utils.imageCheck(dirent.name); - // const isVideo = dirent ? Utils.videoCheck(dirent.name) : Utils.videoCheck(folderDirent.name); + // const isVideo = Utils.videoCheck(dirent.name); if (isImg) { bigIconUrl = `${siteRoot}thumbnail/${repoID}/1024` + Utils.encodePath(`${path === '/' ? '' : path}/${dirent.name}`); } return (
-
+
{isImg && (
@@ -77,12 +71,11 @@ const DirentDetails = React.memo(({ dirent: propsDirent, path, repoID, currentRe )} {direntDetail && (
- {direntType === 'dir' ? ( + {dirent.type !== 'file' ? ( @@ -91,7 +84,6 @@ const DirentDetails = React.memo(({ dirent: propsDirent, path, repoID, currentRe repoID={repoID} repoInfo={currentRepoInfo} dirent={dirent} - direntType={direntType} path={path} direntDetail={direntDetail} repoTags={repoTags} @@ -104,6 +96,17 @@ const DirentDetails = React.memo(({ dirent: propsDirent, path, repoID, currentRe
); +}, (props, nextProps) => { + const { dirent, path, repoID, currentRepoInfo, repoTags, fileTags } = props; + const isChanged = ( + !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 + ); + return !isChanged; }); DirentDetails.propTypes = { @@ -111,9 +114,8 @@ DirentDetails.propTypes = { 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/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/metadata/metadata-details/index.js b/frontend/src/metadata/metadata-details/index.js index 881cfa8369a..2edceefd81b 100644 --- a/frontend/src/metadata/metadata-details/index.js +++ b/frontend/src/metadata/metadata-details/index.js @@ -4,10 +4,9 @@ import { Utils } from '../../utils/utils'; import metadataAPI from '../api'; import Column from '../metadata-view/model/metadata/column'; import { normalizeFields, getCellValueByColumn } from './utils'; -import toaster from '../../components/toast'; import DetailItem from '../../components/dirent-detail/detail-item'; -const MetadataDetails = React.memo(({ repoID, filePath, direntType, emptyTip }) => { +const MetadataDetails = ({ repoID, filePath, direntType, emptyTip }) => { const [isLoading, setLoading] = useState(true); const [metadata, setMetadata] = useState({ record: {}, fields: [] }); @@ -26,8 +25,6 @@ const MetadataDetails = React.memo(({ repoID, filePath, direntType, emptyTip }) setMetadata({ record, fields }); setLoading(false); }).catch(error => { - const errorMsg = Utils.getErrorMsg(error); - toaster.danger(errorMsg); setLoading(false); }); }, [repoID, filePath, direntType]); @@ -39,7 +36,7 @@ const MetadataDetails = React.memo(({ repoID, filePath, direntType, emptyTip }) const value = getCellValueByColumn(record, field); return (); }); -}); +}; MetadataDetails.propTypes = { repoID: PropTypes.string, 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/records/group-body/index.js b/frontend/src/metadata/metadata-view/components/table/table-main/records/group-body/index.js index e4232c1ce3e..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'; 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 334e47a3023..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 @@ -175,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; }; 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); From 2321bce40c716ed24b94f6905cdf2afc53910a8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=A8=E5=9B=BD=E7=92=87?= Date: Tue, 6 Aug 2024 16:38:37 +0800 Subject: [PATCH 5/5] fix: bug --- .../dirent-detail/detail-item/index.js | 2 +- .../dirent-details/dir-details.js | 4 +- .../dirent-details/file-details.js | 4 +- .../dirent-detail/dirent-details/index.js | 183 +++++++++++------- .../src/metadata/metadata-details/index.css | 41 ++++ .../src/metadata/metadata-details/index.js | 9 +- .../metadata-view/hooks/collaborators.js | 8 +- 7 files changed, 169 insertions(+), 82 deletions(-) create mode 100644 frontend/src/metadata/metadata-details/index.css diff --git a/frontend/src/components/dirent-detail/detail-item/index.js b/frontend/src/components/dirent-detail/detail-item/index.js index f0b03dc6ae3..6ff377fd7dc 100644 --- a/frontend/src/components/dirent-detail/detail-item/index.js +++ b/frontend/src/components/dirent-detail/detail-item/index.js @@ -20,7 +20,7 @@ const DetailItem = ({ field, value, valueId, valueClick, children, ...params }) {field.name}
- {children ? children : ()} + {children ? children : ()}
); 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 a749ef303f2..d68ebf53000 100644 --- a/frontend/src/components/dirent-detail/dirent-details/dir-details.js +++ b/frontend/src/components/dirent-detail/dirent-details/dir-details.js @@ -6,7 +6,7 @@ import { CellType } from '../../../metadata/metadata-view/_basic'; import { gettext } from '../../../utils/constants'; import { MetadataDetails } from '../../../metadata'; -const DirDetails = ({ repoID, repoInfo, dirent, path, direntDetail }) => { +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]); @@ -22,7 +22,7 @@ const DirDetails = ({ repoID, repoInfo, dirent, path, direntDetail }) => { }]} /> {window.app.pageOptions.enableMetadataManagement && ( - + )} ); 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 645bd9422a4..175de48e2fe 100644 --- a/frontend/src/components/dirent-detail/dirent-details/file-details.js +++ b/frontend/src/components/dirent-detail/dirent-details/file-details.js @@ -11,7 +11,7 @@ import { Utils } from '../../../utils/utils'; import { MetadataDetails } from '../../../metadata'; import ObjectUtils from '../../../metadata/metadata-view/utils/object-utils'; -const FileDetails = React.memo(({ repoID, repoInfo, dirent, path, direntDetail, onFileTagChanged, repoTags, fileTagList }) => { +const FileDetails = React.memo(({ repoID, repoInfo, dirent, path, direntDetail, onFileTagChanged, repoTags, fileTagList, ...params }) => { const [isEditFileTagShow, setEditFileTagShow] = useState(false); const parent = useMemo(() => getFileParent(repoInfo, dirent, path), [repoInfo, dirent, path]); @@ -47,7 +47,7 @@ const FileDetails = React.memo(({ repoID, repoInfo, dirent, path, direntDetail, )} {window.app.pageOptions.enableMetadataManagement && ( - + )} {isEditFileTagShow && { - const [direntDetail, setDirentDetail] = useState(''); - const [dirent, setDirent] = useState(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 => { - setDirentDetail(res.data); - setDirent(dirent); + this.setState(({ direntDetail: res.data, dirent })); }).catch(error => { const errMessage = Utils.getErrorMsg(error); toaster.danger(errMessage); }); - }, []); + }; - useEffect(() => { - setDirent(null); - if (propsDirent) { - const direntPath = Utils.joinPath(path, propsDirent.name); - updateDetailView(repoID, propsDirent, direntPath); + loadDetail = (repoID, dirent, path) => { + if (dirent) { + const direntPath = Utils.joinPath(path, dirent.name); + this.updateDetail(repoID, dirent, direntPath); return; } const dirPath = Utils.getDirName(path); @@ -41,73 +65,92 @@ const DirentDetails = React.memo(({ dirent: propsDirent, path, repoID, currentRe if (folderDirent) { folderDirent = new Dirent(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 - }, [propsDirent, path, repoID]); + }; - if (!dirent) return null; - 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}`); + componentDidMount() { + this.loadCollaborators(); + this.loadDetail(this.props.repoID, this.props.dirent, this.props.path); } - return ( -
-
-
- {isImg && ( -
- -
- )} - {direntDetail && ( -
- {dirent.type !== 'file' ? ( - - ) : ( - - )} -
- )} -
-
- ); -}, (props, nextProps) => { - const { dirent, path, repoID, currentRepoInfo, repoTags, fileTags } = props; - const isChanged = ( - !ObjectUtils.isSameObject(currentRepoInfo, nextProps.currentRepoInfo) || + 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 - ); - return !isChanged; -}); + repoID !== nextProps.repoID) { + this.setState({ dirent: null }, () => { + this.loadDetail(nextProps.repoID, nextProps.dirent, nextProps.path); + }); + } + } + + 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, 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 index 2edceefd81b..857269bbc3f 100644 --- a/frontend/src/metadata/metadata-details/index.js +++ b/frontend/src/metadata/metadata-details/index.js @@ -5,8 +5,11 @@ 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'; -const MetadataDetails = ({ repoID, filePath, direntType, emptyTip }) => { +import './index.css'; + +const MetadataDetails = ({ repoID, filePath, direntType, emptyTip, ...params }) => { const [isLoading, setLoading] = useState(true); const [metadata, setMetadata] = useState({ record: {}, fields: [] }); @@ -25,6 +28,8 @@ const MetadataDetails = ({ repoID, filePath, direntType, emptyTip }) => { setMetadata({ record, fields }); setLoading(false); }).catch(error => { + const errMessage = Utils.getErrorMsg(error); + toaster.danger(errMessage); setLoading(false); }); }, [repoID, filePath, direntType]); @@ -34,7 +39,7 @@ const MetadataDetails = ({ repoID, filePath, direntType, emptyTip }) => { if (!record._id) return null; return fields.map(field => { const value = getCellValueByColumn(record, field); - return (); + return (); }); }; 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);