diff --git a/frontend/.eslintrc.json b/frontend/.eslintrc.json index 174d5c0a62..d0708c4bf9 100644 --- a/frontend/.eslintrc.json +++ b/frontend/.eslintrc.json @@ -18,7 +18,6 @@ "no-prototype-builtins": "off", "no-restricted-globals": "off", "brace-style": "off", - "no-console": "off", "no-cond-assign": "off", "no-var": "off", "no-case-declarations": "off", diff --git a/frontend/src/components/dir-view-mode/dir-column-file.js b/frontend/src/components/dir-view-mode/dir-column-file.js index c47eb884c5..54ec0a5170 100644 --- a/frontend/src/components/dir-view-mode/dir-column-file.js +++ b/frontend/src/components/dir-view-mode/dir-column-file.js @@ -2,7 +2,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import { SeafileMetadata } from '../../metadata'; import { Utils } from '../../utils/utils'; -import { gettext, siteRoot, lang, mediaUrl } from '../../utils/constants'; +import { gettext, siteRoot, mediaUrl } from '../../utils/constants'; import SeafileMarkdownViewer from '../seafile-markdown-viewer'; const propTypes = { @@ -54,13 +54,8 @@ class DirColumnFile extends React.Component { if (this.props.content === '__sf-metadata') { const { repoID, currentRepoInfo, metadataViewId } = this.props; - window.sfMetadata = { - siteRoot, - lang, - mediaUrl, - }; - return (); + return (); } return ( diff --git a/frontend/src/metadata/metadata-view/_basic/constants/column/index.js b/frontend/src/metadata/metadata-view/_basic/constants/column/index.js index f056524dd2..bd5a0a419a 100644 --- a/frontend/src/metadata/metadata-view/_basic/constants/column/index.js +++ b/frontend/src/metadata/metadata-view/_basic/constants/column/index.js @@ -27,7 +27,10 @@ export { export { PRIVATE_COLUMN_KEY, - PRIVATE_COLUMN_KEYS + PRIVATE_COLUMN_KEYS, + EDITABLE_PRIVATE_COLUMN_KEYS, + EDITABLE_DATA_PRIVATE_COLUMN_KEYS, + DELETABLE_PRIVATE_COLUMN_KEY, } from './private'; export { diff --git a/frontend/src/metadata/metadata-view/_basic/constants/column/private.js b/frontend/src/metadata/metadata-view/_basic/constants/column/private.js index ef72ea42bc..be882e5fe0 100644 --- a/frontend/src/metadata/metadata-view/_basic/constants/column/private.js +++ b/frontend/src/metadata/metadata-view/_basic/constants/column/private.js @@ -46,3 +46,25 @@ export const PRIVATE_COLUMN_KEYS = [ PRIVATE_COLUMN_KEY.FILE_STATUS, PRIVATE_COLUMN_KEY.LOCATION, ]; + +export const EDITABLE_PRIVATE_COLUMN_KEYS = [ + PRIVATE_COLUMN_KEY.FILE_COLLABORATORS, + PRIVATE_COLUMN_KEY.FILE_EXPIRE_TIME, + PRIVATE_COLUMN_KEY.FILE_KEYWORDS, + PRIVATE_COLUMN_KEY.FILE_SUMMARY, + PRIVATE_COLUMN_KEY.FILE_EXPIRED, + PRIVATE_COLUMN_KEY.FILE_STATUS, +]; + +export const EDITABLE_DATA_PRIVATE_COLUMN_KEYS = [ + +]; + +export const DELETABLE_PRIVATE_COLUMN_KEY = [ + PRIVATE_COLUMN_KEY.FILE_COLLABORATORS, + PRIVATE_COLUMN_KEY.FILE_EXPIRE_TIME, + PRIVATE_COLUMN_KEY.FILE_KEYWORDS, + PRIVATE_COLUMN_KEY.FILE_SUMMARY, + PRIVATE_COLUMN_KEY.FILE_EXPIRED, + PRIVATE_COLUMN_KEY.FILE_STATUS, +]; diff --git a/frontend/src/metadata/metadata-view/_basic/constants/index.js b/frontend/src/metadata/metadata-view/_basic/constants/index.js index 7855f0d936..baf5d82c04 100644 --- a/frontend/src/metadata/metadata-view/_basic/constants/index.js +++ b/frontend/src/metadata/metadata-view/_basic/constants/index.js @@ -24,6 +24,9 @@ export { VIEW_NOT_DISPLAY_COLUMN_KEYS, PREDEFINED_COLUMN_KEYS, GEOLOCATION_FORMAT, + EDITABLE_PRIVATE_COLUMN_KEYS, + EDITABLE_DATA_PRIVATE_COLUMN_KEYS, + DELETABLE_PRIVATE_COLUMN_KEY, } from './column'; export { FILTER_CONJUNCTION_TYPE, diff --git a/frontend/src/metadata/metadata-view/_basic/index.js b/frontend/src/metadata/metadata-view/_basic/index.js index 1294ca30c0..3775b327bf 100644 --- a/frontend/src/metadata/metadata-view/_basic/index.js +++ b/frontend/src/metadata/metadata-view/_basic/index.js @@ -50,6 +50,9 @@ export { NOT_DISPLAY_COLUMN_KEYS, VIEW_NOT_DISPLAY_COLUMN_KEYS, PREDEFINED_COLUMN_KEYS, + EDITABLE_PRIVATE_COLUMN_KEYS, + EDITABLE_DATA_PRIVATE_COLUMN_KEYS, + DELETABLE_PRIVATE_COLUMN_KEY, } from './constants'; export { diff --git a/frontend/src/metadata/metadata-view/_basic/utils/validate/filter.js b/frontend/src/metadata/metadata-view/_basic/utils/validate/filter.js index 68b4f90d8d..46c556814e 100644 --- a/frontend/src/metadata/metadata-view/_basic/utils/validate/filter.js +++ b/frontend/src/metadata/metadata-view/_basic/utils/validate/filter.js @@ -8,6 +8,7 @@ import { FILTER_ERR_MSG, } from '../../constants/filter'; import { isDateColumn } from '../column/date'; +import { getColumnOptions } from '../column'; const TERM_TYPE_MAP = { NUMBER: 'number', @@ -16,6 +17,11 @@ const TERM_TYPE_MAP = { ARRAY: 'array', }; +const PREDICATES_REQUIRE_ARRAY_TERM = [ + FILTER_PREDICATE_TYPE.IS_ANY_OF, + FILTER_PREDICATE_TYPE.IS_NONE_OF, +]; + const TEXT_COLUMN_TYPES = [CellType.TEXT, CellType.FILE_NAME]; const CHECK_EMPTY_PREDICATES = [FILTER_PREDICATE_TYPE.EMPTY, FILTER_PREDICATE_TYPE.NOT_EMPTY]; @@ -142,7 +148,7 @@ class ValidateFilter { if (CHECK_EMPTY_PREDICATES.includes(predicate)) { return true; } - if (array_type === CellType.SINGLE_SELECT || array_type === CellType.DEPARTMENT_SINGLE_SELECT) { + if (array_type === CellType.SINGLE_SELECT) { return this.validatePredicate(predicate, { type: CellType.MULTIPLE_SELECT }); } if (COLLABORATOR_COLUMN_TYPES.includes(array_type)) { @@ -223,9 +229,13 @@ class ValidateFilter { static isValidTerm(term, predicate, modifier, filterColumn) { switch (filterColumn.type) { case CellType.TEXT: + case CellType.GEOLOCATION: case CellType.FILE_NAME: { return this.isValidTermType(term, TERM_TYPE_MAP.STRING); } + case CellType.NUMBER: { + return this.isValidTermType(term, TERM_TYPE_MAP.NUMBER); + } case CellType.CHECKBOX: case CellType.BOOL: { @@ -244,6 +254,24 @@ class ValidateFilter { } return this.isValidTermType(term, TERM_TYPE_MAP.STRING); } + case CellType.SINGLE_SELECT: { + const options = getColumnOptions(filterColumn); + if (PREDICATES_REQUIRE_ARRAY_TERM.includes(predicate)) { + if (!this.isValidTermType(term, TERM_TYPE_MAP.ARRAY)) { + return false; + } + + // contains deleted option(s) + return this.isValidSelectedOptions(term, options); + } + + if (!this.isValidTermType(term, TERM_TYPE_MAP.STRING)) { + return false; + } + + // invalid filter_term if selected option is deleted + return !!options.find((option) => term === option.id); + } default: { return false; } diff --git a/frontend/src/metadata/metadata-view/components/cell-editor/file-name-editor.js b/frontend/src/metadata/metadata-view/components/cell-editor/file-name-editor.js index 4d04cf9743..90ca378ecd 100644 --- a/frontend/src/metadata/metadata-view/components/cell-editor/file-name-editor.js +++ b/frontend/src/metadata/metadata-view/components/cell-editor/file-name-editor.js @@ -57,7 +57,7 @@ const FileNameEditor = ({ column, record, onCommitCancel }) => { if (fileType === 'image') { const fileExt = fileName.substr(fileName.lastIndexOf('.') + 1).toLowerCase(); const isGIF = fileExt === 'gif'; - const useThumbnail = window.sfMetadataContext.getSetting('currentRepoInfo')?.encrypted; + const useThumbnail = window.sfMetadataContext.getSetting('repoInfo')?.encrypted; let src = ''; if (useThumbnail && !isGIF) { src = `${siteRoot}thumbnail/${repoID}/${thumbnailSizeForOriginal}${path}`; diff --git a/frontend/src/metadata/metadata-view/components/popover/column-popover/index.js b/frontend/src/metadata/metadata-view/components/popover/column-popover/index.js index 858f012fa0..30de575607 100644 --- a/frontend/src/metadata/metadata-view/components/popover/column-popover/index.js +++ b/frontend/src/metadata/metadata-view/components/popover/column-popover/index.js @@ -2,7 +2,7 @@ import React, { useCallback, useMemo, useRef, useState } from 'react'; import PropTypes from 'prop-types'; import { Button, UncontrolledPopover } from 'reactstrap'; import classnames from 'classnames'; -import { CellType, DEFAULT_DATE_FORMAT } from '../../../_basic'; +import { CellType, DEFAULT_DATE_FORMAT, PRIVATE_COLUMN_KEY } from '../../../_basic'; import { gettext } from '../../../utils'; import ObjectUtils from '../../../utils/object-utils'; import { ValidateColumnFormFields } from './utils'; @@ -11,6 +11,7 @@ import { useMetadata } from '../../../hooks'; import Name from './name'; import Type from './type'; import Data from './data'; +import { getDefaultFileStatusOptions } from '../../../utils/column-utils'; import './index.css'; @@ -73,6 +74,10 @@ const ColumnPopover = ({ target, onChange }) => { } else if (column.type === CellType.DATE) { data = { format: DEFAULT_DATE_FORMAT }; } + } else { + if (column.type === CellType.SINGLE_SELECT && column.key === PRIVATE_COLUMN_KEY.FILE_STATUS) { + data = { options: getDefaultFileStatusOptions() }; + } } } onChange(columnName, column.type, { key: column.unique ? column.key : '', data }); diff --git a/frontend/src/metadata/metadata-view/components/popover/filter-popover/index.css b/frontend/src/metadata/metadata-view/components/popover/filter-popover/index.css index 1ba5b5fe23..a8bf97c0de 100644 --- a/frontend/src/metadata/metadata-view/components/popover/filter-popover/index.css +++ b/frontend/src/metadata/metadata-view/components/popover/filter-popover/index.css @@ -1,4 +1,4 @@ -.filter-popover .popover { +.sf-metadata-filter-popover .popover { max-width: none; min-width: 300px; } diff --git a/frontend/src/metadata/metadata-view/components/popover/filter-popover/index.js b/frontend/src/metadata/metadata-view/components/popover/filter-popover/index.js index 88745738eb..4fc10d4c78 100644 --- a/frontend/src/metadata/metadata-view/components/popover/filter-popover/index.js +++ b/frontend/src/metadata/metadata-view/components/popover/filter-popover/index.js @@ -155,7 +155,7 @@ class FilterPopover extends Component { target={target} fade={false} hideArrow={true} - className="filter-popover" + className="sf-metadata-filter-popover" boundariesElement={document.body} > {({ scheduleUpdate }) => ( @@ -180,7 +180,7 @@ class FilterPopover extends Component { addIconClassName="popover-add-icon" /> {this.isNeedSubmit() && ( -
+
diff --git a/frontend/src/metadata/metadata-view/components/popover/filter-popover/widgets/filter-item-utils.js b/frontend/src/metadata/metadata-view/components/popover/filter-popover/widgets/filter-item-utils.js index fac862b915..9360c46dd4 100644 --- a/frontend/src/metadata/metadata-view/components/popover/filter-popover/widgets/filter-item-utils.js +++ b/frontend/src/metadata/metadata-view/components/popover/filter-popover/widgets/filter-item-utils.js @@ -40,7 +40,7 @@ class FilterItemUtils {
{option.name}
- {selectedOption?.id === option.id && } + {selectedOption?.id === option.id && ()}
) @@ -54,7 +54,7 @@ class FilterItemUtils {
{option.name}
- {filterTerm.indexOf(option.id) > -1 && } + {filterTerm.indexOf(option.id) > -1 && ()}
) diff --git a/frontend/src/metadata/metadata-view/components/popover/filter-popover/widgets/filter-item/collaborator-filter/index.css b/frontend/src/metadata/metadata-view/components/popover/filter-popover/widgets/filter-item/collaborator-filter/index.css new file mode 100644 index 0000000000..051577140d --- /dev/null +++ b/frontend/src/metadata/metadata-view/components/popover/filter-popover/widgets/filter-item/collaborator-filter/index.css @@ -0,0 +1,13 @@ +.sf-metadata-selector-collaborator.sf-metadata-select .option { + line-height: 20px; + padding: 5px 10px 5px 10px !important; +} + +.sf-metadata-selector-collaborator.sf-metadata-select .option:hover { + background-color: #f7f7f7; + color: #212529; +} + +.sf-metadata-selector-collaborator.sf-metadata-select .selected-option-show { + text-overflow: clip; +} diff --git a/frontend/src/metadata/metadata-view/components/popover/filter-popover/widgets/collaborator-filter.js b/frontend/src/metadata/metadata-view/components/popover/filter-popover/widgets/filter-item/collaborator-filter/index.js similarity index 95% rename from frontend/src/metadata/metadata-view/components/popover/filter-popover/widgets/collaborator-filter.js rename to frontend/src/metadata/metadata-view/components/popover/filter-popover/widgets/filter-item/collaborator-filter/index.js index 26daa087ed..b96c2faf09 100644 --- a/frontend/src/metadata/metadata-view/components/popover/filter-popover/widgets/collaborator-filter.js +++ b/frontend/src/metadata/metadata-view/components/popover/filter-popover/widgets/filter-item/collaborator-filter/index.js @@ -1,8 +1,10 @@ import React, { Fragment, useMemo } from 'react'; import PropTypes from 'prop-types'; import { CustomizeSelect, Icon } from '@seafile/sf-metadata-ui-component'; -import { FILTER_PREDICATE_TYPE } from '../../../../_basic'; -import { gettext } from '../../../../utils'; +import { FILTER_PREDICATE_TYPE } from '../../../../../../_basic'; +import { gettext } from '../../../../../../utils'; + +import './index.css'; const CollaboratorFilter = ({ isLocked, filterIndex, filterTerm, collaborators, placeholder, filter_predicate, onSelectCollaborator }) => { const supportMultipleSelectOptions = useMemo(() => { @@ -73,7 +75,7 @@ const CollaboratorFilter = ({ isLocked, filterIndex, filterTerm, collaborators, return ( { + const collaborators = window.sfMetadata.collaborators; + const collaboratorsCache = window.sfMetadata.collaboratorsCache; + return [...collaborators, ...Object.values(collaboratorsCache)]; + }; + renderFilterTerm = (filterColumn) => { const { index, filter, collaborators } = this.props; const { type } = filterColumn; @@ -410,6 +419,57 @@ class FilterItem extends React.Component { case CellType.CHECKBOX: { return this.getInputComponent('checkbox'); } + case CellType.SINGLE_SELECT: { + // get options + const options = getSelectColumnOptions(filterColumn); + if ([FILTER_PREDICATE_TYPE.IS_ANY_OF, FILTER_PREDICATE_TYPE.IS_NONE_OF].includes(filter_predicate)) { + return this.renderMultipleSelectOption(options, filter_term); + } + let selectedOptionDom = { label: null }; + if (filter_term) { + let selectedOption = options.find(option => option.id === filter_term); + const className = 'select-option-name single-select-option'; + const style = selectedOption ? + { background: selectedOption.color, color: selectedOption.textColor || null } : + { background: DELETED_OPTION_BACKGROUND_COLOR }; + const selectedOptionName = selectedOption ? selectedOption.name : gettext('deleted option'); + selectedOptionDom = { label: ( + {selectedOptionName} + ) }; + } + + let dataOptions = options.map(option => { + return FilterItemUtils.generatorSingleSelectOption(option); + }); + + return ( + + ); + } + case CellType.COLLABORATOR: { + if (filter_predicate === FILTER_PREDICATE_TYPE.INCLUDE_ME) return null; + const allCollaborators = this.getAllCollaborators(); + return ( + + ); + } default: { return null; } diff --git a/frontend/src/metadata/metadata-view/components/popover/filter-popover/widgets/index.css b/frontend/src/metadata/metadata-view/components/popover/filter-popover/widgets/index.css index 39aa36be77..abf84abb67 100644 --- a/frontend/src/metadata/metadata-view/components/popover/filter-popover/widgets/index.css +++ b/frontend/src/metadata/metadata-view/components/popover/filter-popover/widgets/index.css @@ -226,8 +226,8 @@ .filters-list .multiple-check-icon .sf-metadata-icon-check-mark, .filters-list .collaborator-check-icon .sf-metadata-icon-check-mark { + fill: #798d99; font-size: 12px; - color: #798d99; } .user-select-item, diff --git a/frontend/src/metadata/metadata-view/components/popover/index.css b/frontend/src/metadata/metadata-view/components/popover/index.css index 15854b6c09..6e80d5ecd8 100644 --- a/frontend/src/metadata/metadata-view/components/popover/index.css +++ b/frontend/src/metadata/metadata-view/components/popover/index.css @@ -1,4 +1,4 @@ -.filter-popover-footer { +.sf-metadata-filter-popover-footer { display: flex; align-items: center; justify-content: flex-end; @@ -25,6 +25,6 @@ fill: #c2c2c2; } -.filter-popover .popover-add-tool.disabled { +.sf-metadata-filter-popover .popover-add-tool.disabled { color: #c2c2c2; } diff --git a/frontend/src/metadata/metadata-view/components/popover/options-popover/index.css b/frontend/src/metadata/metadata-view/components/popover/options-popover/index.css index d794abb766..6b033ca688 100644 --- a/frontend/src/metadata/metadata-view/components/popover/options-popover/index.css +++ b/frontend/src/metadata/metadata-view/components/popover/options-popover/index.css @@ -1,3 +1,9 @@ +.sf-metadata-edit-column-options-popover .popover { + margin-left: -4px; + margin-top: 4px; + max-width: 600px; +} + .sf-metadata-edit-column-options-container { min-width: 400px; height: auto; @@ -12,6 +18,13 @@ color: #212529; } +.sf-metadata-edit-column-options-container .none-search-result { + height: 100px; + opacity: .5; + padding: 10px; + width: 100%; +} + .sf-metadata-edit-column-options-container .sf-metadata-select-options-list { margin-bottom: 0; margin-top: 1rem; @@ -19,3 +32,13 @@ overflow: auto; padding: 0; } + +.sf-metadata-edit-column-options-container .sf-metadata-add-option { + border-top: none; + color: #666; +} + +.sf-metadata-edit-column-options-container .sf-metadata-add-option .sf-metadata-add-option-icon { + fill: #666; + font-weight: 600; +} diff --git a/frontend/src/metadata/metadata-view/components/popover/options-popover/index.js b/frontend/src/metadata/metadata-view/components/popover/options-popover/index.js index 538849b6b2..a68399c244 100644 --- a/frontend/src/metadata/metadata-view/components/popover/options-popover/index.js +++ b/frontend/src/metadata/metadata-view/components/popover/options-popover/index.js @@ -157,7 +157,7 @@ const OptionsPopover = ({ target, column, onToggle, onSubmit }) => { <>
this.sortPopoverRef = ref} onClick={this.onPopoverInsideClick}> @@ -264,7 +264,7 @@ class SortPopover extends Component { /> } {(this.isNeedSubmit() && !readonly) && ( -
+
diff --git a/frontend/src/metadata/metadata-view/components/table/index.css b/frontend/src/metadata/metadata-view/components/table/index.css index e7ab6bb39e..4051c37a21 100644 --- a/frontend/src/metadata/metadata-view/components/table/index.css +++ b/frontend/src/metadata/metadata-view/components/table/index.css @@ -49,122 +49,6 @@ box-shadow: inset 0 0 0 2px rgb(0 0 0 / 10%); } -.sf-metadata-wrapper .table-right-operations .new-record-btn button { - display: flex; - align-items: center; - justify-content: center; - height: 23px; - font-weight: 400; - border-color: rgba(0, 0, 0, 0.05); -} - -.sf-metadata-wrapper .table-right-operations .more-operation-add-record { - padding: 0; -} - -.sf-metadata-wrapper .table-right-operations .more-operation-add-record:not(:disabled):not(.disabled):active:focus { - box-shadow: none; -} - -.sf-metadata-wrapper .table-right-operations .more-operation-add-record .dropdown { - display: inline-block; - width: 100%; - height: 100%; -} - -.sf-metadata-wrapper .table-right-operations .add-record-dropdown-menu { - display: flex; - align-items: center; - justify-content: center; - height: 100%; - width: 100%; -} - -.sf-metadata-dropdown-menu.add-record { - margin-top: 4px; -} - -.sf-metadata-wrapper .table-right-operations .more-operation-add-record .dropdown .sf-metadata-dropdown-menu { - margin-top: 2px; -} - -.sf-metadata-wrapper .table-right-operations .more-operation-add-record .toggle-icon { - display: inline-block; - font-size: 12px; - transform: scale(0.8); - margin-top: 1px; -} - -.sf-metadata-wrapper .table-right-operations .new-record { - font-size: 14px; - line-height: 1.5rem; -} - -.sf-metadata-wrapper .table-right-operations .table-search-box .input-icon-addon.search-poll-button { - display: flex; - right: 25px; - height: 30px; - line-height: 30px; - left: auto; - text-align: center; - font-size: 12px; - min-width: 35px; - pointer-events: all; -} - -.sf-metadata-wrapper .table-right-operations .table-search-box .search-poll-button .search-description { - height: 30px; - line-height: 30px; - color: #666666; -} - -.search-poll-button .sf-metadata-font { - font-size: 12px; - cursor: pointer; - color: #212529; -} - -.mobile-search-exchange-btn { - width: 30px; - height: 30px; - line-height: 30px; - background-color: #e5e5e5; - color: #212529; - display: block; -} - -.mobile-search-exchange-btn:hover { - background-color: #ededed; - color: #666666; -} - -.mobile-search-exchange-btn.mobile-search-upward { - border-radius: 2px 0 0 2px; - transform: scale(0.8, 0.8) translateX(8px); -} - -.mobile-search-exchange-btn.mobile-search-backward { - border-radius: 0 2px 2px 0; - transform: scale(0.8, 0.8); -} - -.search-text-clear { - cursor: pointer; - min-width: 25px; - pointer-events: all; - font-style: normal; - font-size: 18px; - font-weight: 700; - text-align: center; - line-height: 30px; - height: 30px; - color: #999; -} - -.search-text-clear:hover { - color: #212529; -} - .sf-metadata-result.success { display: flex; flex-direction: column; diff --git a/frontend/src/metadata/metadata-view/components/table/table-main/records/record/cell/index.js b/frontend/src/metadata/metadata-view/components/table/table-main/records/record/cell/index.js index 92ca54138b..d2d4a7101e 100644 --- a/frontend/src/metadata/metadata-view/components/table/table-main/records/record/cell/index.js +++ b/frontend/src/metadata/metadata-view/components/table/table-main/records/record/cell/index.js @@ -28,13 +28,12 @@ const Cell = React.memo(({ const className = useMemo(() => { const { type } = column; const canEditable = window.sfMetadataContext.canModifyCell(column); - return classnames('sf-metadata-result-table-cell', `sf-metadata-result-table-${type}-cell`, { + return classnames('sf-metadata-result-table-cell', `sf-metadata-result-table-${type}-cell`, highlightClassName, { 'table-cell-uneditable': !canEditable || !TABLE_SUPPORT_EDIT_TYPE_MAP[type], - [highlightClassName]: highlightClassName, 'last-cell': isLastCell, 'table-last--frozen': isLastFrozenCell, 'cell-selected': isCellSelected, - // 'draging-file-to-cell': , + // 'dragging-file-to-cell': , // 'row-comment-cell': , }); }, [column, highlightClassName, isLastCell, isLastFrozenCell, isCellSelected]); diff --git a/frontend/src/metadata/metadata-view/components/table/table-main/records/records-header/cell/dropdown-menu/dropdown-item.js b/frontend/src/metadata/metadata-view/components/table/table-main/records/records-header/cell/dropdown-menu/dropdown-item.js index 155f4de4cd..4ce2033f0d 100644 --- a/frontend/src/metadata/metadata-view/components/table/table-main/records/records-header/cell/dropdown-menu/dropdown-item.js +++ b/frontend/src/metadata/metadata-view/components/table/table-main/records/records-header/cell/dropdown-menu/dropdown-item.js @@ -1,18 +1,28 @@ -import React, { useCallback } from 'react'; +import React, { useCallback, useEffect, useState } from 'react'; import PropTypes from 'prop-types'; import { UncontrolledTooltip, DropdownItem } from 'reactstrap'; import classnames from 'classnames'; import { Icon } from '@seafile/sf-metadata-ui-component'; const ColumnDropdownItem = ({ disabled, iconName, target, title, tip, className, onChange, onMouseEnter }) => { + const [isShowToolTip, setToolTipShow] = useState(false); + + useEffect(() => { + if (disabled) { + setToolTipShow(true); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); const onClick = useCallback((event) => { event.preventDefault(); + event.nativeEvent.stopImmediatePropagation(); + event.stopPropagation(); }, []); if (!disabled) { return ( - + {title} @@ -20,21 +30,23 @@ const ColumnDropdownItem = ({ disabled, iconName, target, title, tip, className, } return ( - - - {title} - {disabled && - - {tip} - - } - + <> + + + {title} + {isShowToolTip && ( + + {tip} + + )} + + ); }; diff --git a/frontend/src/metadata/metadata-view/components/table/table-main/records/records-header/cell/dropdown-menu/index.css b/frontend/src/metadata/metadata-view/components/table/table-main/records/records-header/cell/dropdown-menu/index.css index 008e3404d8..c8b46895c3 100644 --- a/frontend/src/metadata/metadata-view/components/table/table-main/records/records-header/cell/dropdown-menu/index.css +++ b/frontend/src/metadata/metadata-view/components/table/table-main/records/records-header/cell/dropdown-menu/index.css @@ -1,3 +1,7 @@ +.sf-metadata-column-dropdown-menu { + margin-top: 20px; +} + .sf-metadata-column-dropdown-menu .dropdown-item .sf-metadata-icon { margin-right: 10px; font-size: 14px; @@ -20,3 +24,18 @@ .sf-metadata-column-dropdown-menu .dropdown-toggle:hover::after { color: #fff; } + +.sf-metadata-column-dropdown-menu .dropdown-item.disabled, +.sf-metadata-column-dropdown-menu .dropdown-item:disabled { + pointer-events: unset !important; +} + +.sf-metadata-column-dropdown-menu .disabled.dropdown-item:hover { + background-color: unset; + cursor: default; + color: #c2c2c2; +} + +.sf-metadata-column-dropdown-menu .disabled.dropdown-item .sf-metadata-icon { + fill: #c2c2c2; +} diff --git a/frontend/src/metadata/metadata-view/components/table/table-main/records/records-header/cell/dropdown-menu/index.js b/frontend/src/metadata/metadata-view/components/table/table-main/records/records-header/cell/dropdown-menu/index.js index f95af5b0bb..3db9987281 100644 --- a/frontend/src/metadata/metadata-view/components/table/table-main/records/records-header/cell/dropdown-menu/index.js +++ b/frontend/src/metadata/metadata-view/components/table/table-main/records/records-header/cell/dropdown-menu/index.js @@ -4,7 +4,6 @@ import { Dropdown, DropdownToggle, DropdownMenu, DropdownItem as DefaultDropdown import classnames from 'classnames'; import { ModalPortal, Icon } from '@seafile/sf-metadata-ui-component'; import { isMobile, gettext } from '../../../../../../../utils'; -import { isFrozen } from '../../../../../../../utils/column-utils'; import DropdownItem from './dropdown-item'; import { CellType, DEFAULT_DATE_FORMAT, getDateDisplayString } from '../../../../../../../_basic'; import { RenamePopover, OptionsPopover } from '../../../../../../popover'; @@ -153,15 +152,12 @@ const HeaderDropdownMenu = ({ column, renameColumn, modifyColumnData, deleteColu }, [today, column, isMenuShow, isSubMenuShow, onChangeDateFormat, openSubMenu]); const renderDropdownMenu = useCallback(() => { - let menuStyle = { transform: 'none' }; const { type } = column; - if (!isFrozen(column)) { - menuStyle['top'] = -5; // - (container padding + menu margin) - menuStyle['left'] = - (column.width - 30); // column width - container width - padding - } - const canModifyColumnData = window.sfMetadataContext.canModifyColumn(column); + const canModifyColumnData = window.sfMetadataContext.canModifyColumnData(column); + const canDeleteColumn = window.sfMetadataContext.canDeleteColumn(column); + const canRenameColumn = window.sfMetadataContext.canRenameColumn(column); return ( - +
{type === CellType.SINGLE_SELECT && ( <> @@ -200,7 +196,7 @@ const HeaderDropdownMenu = ({ column, renameColumn, modifyColumnData, deleteColu )} { if (isHideTriangle) return false; - if (PRIVATE_COLUMN_KEYS.includes(column.key)) return false; return window.sfMetadataContext.canModifyColumn(column); }, [isHideTriangle, column]); diff --git a/frontend/src/metadata/metadata-view/context.js b/frontend/src/metadata/metadata-view/context.js index 14badafe18..eb81ccdbf1 100644 --- a/frontend/src/metadata/metadata-view/context.js +++ b/frontend/src/metadata/metadata-view/context.js @@ -1,5 +1,6 @@ import metadataAPI from '../api'; -import { UserService, LocalStorage } from './_basic'; +import { UserService, LocalStorage, PRIVATE_COLUMN_KEYS, EDITABLE_DATA_PRIVATE_COLUMN_KEYS, + EDITABLE_PRIVATE_COLUMN_KEYS, PREDEFINED_COLUMN_KEYS } from './_basic'; import EventBus from '../../components/common/event-bus'; import { username } from '../../utils/constants'; @@ -12,6 +13,7 @@ class Context { this.userService = null; this.eventBus = null; this.hasInit = false; + this.permission = 'r'; } async init({ otherSettings }) { @@ -21,7 +23,7 @@ class Context { this.settings = otherSettings || {}; // init metadataAPI - const { mediaUrl } = this.settings; + const { mediaUrl, repoInfo } = this.settings; this.metadataAPI = metadataAPI; // init localStorage @@ -34,6 +36,8 @@ class Context { const eventBus = new EventBus(); this.eventBus = eventBus; + this.permission = repoInfo.permission !== 'admin' && repoInfo.permission !== 'rw' ? 'r' : 'rw'; + this.hasInit = true; } @@ -44,6 +48,7 @@ class Context { this.userService = null; this.eventBus = null; this.hasInit = false; + this.permission = 'r'; }; getSetting = (key) => { @@ -81,22 +86,46 @@ class Context { return this.metadataAPI.getView(repoID, viewId); }; + getPermission = () => { + return this.permission; + }; + canModifyCell = (column) => { + if (this.permission === 'r') return false; const { editable } = column; if (!editable) return false; return true; }; canModifyRow = (row) => { + if (this.permission === 'r') return false; return true; }; canModifyColumn = (column) => { + if (this.permission === 'r') return false; + if (PRIVATE_COLUMN_KEYS.includes(column.key) && !EDITABLE_PRIVATE_COLUMN_KEYS.includes(column.key)) return false; return true; }; - getPermission = () => { - return 'rw'; + canRenameColumn = (column) => { + if (this.permission === 'r') return false; + if (PRIVATE_COLUMN_KEYS.includes(column.key)) return false; + return true; + }; + + canModifyColumnData = (column) => { + if (this.permission === 'r') return false; + const { key } = column; + if (PRIVATE_COLUMN_KEYS.includes(key)) return EDITABLE_DATA_PRIVATE_COLUMN_KEYS.includes(key); + return true; + }; + + canDeleteColumn = (column) => { + if (this.permission === 'r') return false; + const { key } = column; + if (PRIVATE_COLUMN_KEYS.includes(key)) return PREDEFINED_COLUMN_KEYS.includes(key); + return true; }; getCollaboratorsFromCache = () => { diff --git a/frontend/src/metadata/metadata-view/hooks/collaborators.js b/frontend/src/metadata/metadata-view/hooks/collaborators.js index 9b6b935ba7..73df4a3e02 100644 --- a/frontend/src/metadata/metadata-view/hooks/collaborators.js +++ b/frontend/src/metadata/metadata-view/hooks/collaborators.js @@ -19,6 +19,12 @@ export const CollaboratorsProvider = ({ setCollaborators(store?.collaborators || []); }, [store?.collaborators]); + useEffect(() => { + if (!window.sfMetadata) return; + window.sfMetadata.collaborators = collaborators; + window.sfMetadata.collaboratorsCache = collaboratorsCache; + }, [collaborators, collaboratorsCache]); + const updateCollaboratorsCache = useCallback((user) => { const newCollaboratorsCache = { ...collaboratorsCacheRef.current, [user.email]: user }; collaboratorsCacheRef.current = newCollaboratorsCache; diff --git a/frontend/src/metadata/metadata-view/hooks/metadata.js b/frontend/src/metadata/metadata-view/hooks/metadata.js index aeb4a3d4cf..231d314c39 100644 --- a/frontend/src/metadata/metadata-view/hooks/metadata.js +++ b/frontend/src/metadata/metadata-view/hooks/metadata.js @@ -12,7 +12,6 @@ export const MetadataProvider = ({ children, repoID, viewID, - currentRepoInfo, ...params }) => { const [isLoading, setLoading] = useState(true); @@ -37,11 +36,11 @@ export const MetadataProvider = ({ setLoading(true); // init context const context = new Context(); + window.sfMetadata = {}; window.sfMetadataContext = context; window.sfMetadataContext.init({ otherSettings: params }); window.sfMetadataContext.setSetting('viewID', viewID); window.sfMetadataContext.setSetting('repoID', repoID); - window.sfMetadataContext.setSetting('currentRepoInfo', currentRepoInfo); storeRef.current = new Store({ context: window.sfMetadataContext, repoId: repoID, viewId: viewID }); window.sfMetadataStore = storeRef.current; storeRef.current.initStartIndex(); @@ -59,6 +58,7 @@ export const MetadataProvider = ({ const unsubscribeUpdateRows = window.sfMetadataContext.eventBus.subscribe(EVENT_BUS_TYPE.UPDATE_TABLE_ROWS, updateMetadata); return () => { + window.sfMetadata = {}; window.sfMetadataContext.destroy(); window.sfMetadataStore.destroy(); unsubscribeServerTableChanged(); @@ -67,7 +67,7 @@ export const MetadataProvider = ({ unsubscribeUpdateRows(); }; // eslint-disable-next-line react-hooks/exhaustive-deps - }, [repoID, viewID, currentRepoInfo]); + }, [repoID, viewID]); return ( diff --git a/frontend/src/metadata/metadata-view/model/metadata/column.js b/frontend/src/metadata/metadata-view/model/metadata/column.js index 0f8b001114..7083338f12 100644 --- a/frontend/src/metadata/metadata-view/model/metadata/column.js +++ b/frontend/src/metadata/metadata-view/model/metadata/column.js @@ -1,5 +1,5 @@ import { normalizeColumnData } from '../../utils/column-utils'; -import { CellType } from '../../_basic'; +import { CellType, PRIVATE_COLUMN_KEYS, EDITABLE_PRIVATE_COLUMN_KEYS } from '../../_basic'; class Column { constructor(object) { @@ -8,10 +8,15 @@ class Column { this.type = object.type || ''; this.data = object.data || null; this.width = object.width || 200; - this.editable = object.editable || !this.key.startsWith('_') && this.type !== CellType.LONG_TEXT || false; + this.editable = this.enable_edit(this.key, this.type); this.data = normalizeColumnData(this); } + enable_edit = (key, type) => { + if (PRIVATE_COLUMN_KEYS.includes(key)) return EDITABLE_PRIVATE_COLUMN_KEYS.includes(key); + return type !== CellType.LONG_TEXT; + }; + } export default Column; diff --git a/frontend/src/metadata/metadata-view/utils/column-utils.js b/frontend/src/metadata/metadata-view/utils/column-utils.js index 6a7a3a4887..2678af898a 100644 --- a/frontend/src/metadata/metadata-view/utils/column-utils.js +++ b/frontend/src/metadata/metadata-view/utils/column-utils.js @@ -255,26 +255,25 @@ const getFileTypeColumnData = (column) => { '_audio': { name: gettext('Audio'), color: '#FBD44A', textColor: '#FFFFFF', borderColor: '#E5C142', id: '_audio' }, '_code': { name: gettext('Code'), color: '#4ad8fb', textColor: '#FFFFFF', borderColor: '#4283e5', id: '_code' }, }; - let newData = { ...data }; - newData.options = Array.isArray(newData.options) ? newData.options.map(o => { - return { ..._OPTIONS[o.name] }; - }) : Object.keys(_OPTIONS); + newData.options = Array.isArray(data.options) ? data.options.map(o => { + return _OPTIONS[o.name]; + }) : Object.values(_OPTIONS); return newData; }; +export const getDefaultFileStatusOptions = () => { + return [ + { name: gettext('Draft'), color: '#EED5FF', textColor: '#202428', id: '_draft' }, + { name: gettext('In review'), color: '#FFFDCF', textColor: '#202428', id: '_in_review' }, + { name: gettext('Done'), color: '#59CB74', textColor: '#FFFFFF', borderColor: '#844BD2', id: '_done' }, + ]; +}; + const getFileStatusColumnData = (column) => { const { data } = column; - const _OPTIONS = { - '_draft': { name: gettext('Draft'), color: '#EED5FF', textColor: '#202428', id: '_draft' }, - '_in_review': { name: gettext('In review'), color: '#FFFDCF', textColor: '#202428', id: '_in_review' }, - '_done': { name: gettext('Done'), color: '#59CB74', textColor: '#FFFFFF', borderColor: '#844BD2', id: '_done' }, - }; - let newData = { ...data }; - newData.options = Array.isArray(newData.options) ? newData.options.map(o => { - return { ..._OPTIONS[o.name] }; - }) : Object.keys(_OPTIONS); + newData.options = Array.isArray(data?.options) ? data.options : getDefaultFileStatusOptions(); return newData; }; @@ -305,7 +304,6 @@ export const normalizeColumns = (columns) => { type: columnType, name: getColumnName(key, name), width: columnsWidth[key] || 200, - editable: !key.startsWith('_') && columnType !== CellType.LONG_TEXT }; }).filter(column => !NOT_DISPLAY_COLUMN_KEYS.includes(column.key)); let displayColumns = []; @@ -332,7 +330,6 @@ export function canEdit(col, record, enableCellSelect) { if (col.editable != null && typeof (col.editable) === 'function') { return enableCellSelect === true && col.editable(record); } - console.log(col); return enableCellSelect === true && !!col.editable; }