diff --git a/frontend/src/metadata/components/cell-editors/tags-editor/index.css b/frontend/src/metadata/components/cell-editors/tags-editor/index.css index b7e8b191f5..fec9954f32 100644 --- a/frontend/src/metadata/components/cell-editors/tags-editor/index.css +++ b/frontend/src/metadata/components/cell-editors/tags-editor/index.css @@ -13,6 +13,7 @@ } .sf-metadata-tags-editor .sf-metadata-search-tags-container { + position: relative; padding: 10px 10px 0; } diff --git a/frontend/src/metadata/components/cell-editors/tags-editor/index.js b/frontend/src/metadata/components/cell-editors/tags-editor/index.js index 641a183e23..ffca952d90 100644 --- a/frontend/src/metadata/components/cell-editors/tags-editor/index.js +++ b/frontend/src/metadata/components/cell-editors/tags-editor/index.js @@ -13,6 +13,7 @@ import { SELECT_OPTION_COLORS } from '../../../constants'; import { PRIVATE_COLUMN_KEY as TAG_PRIVATE_COLUMN_KEY, RECENTLY_USED_TAG_IDS } from '../../../../tag/constants'; import { checkIsTreeNodeShown, checkTreeNodeHasChildNodes, getNodesWithAncestors, getTreeNodeDepth, getTreeNodeId, getTreeNodeKey } from '../../../../components/sf-table/utils/tree'; import TagItem from './tag-item'; +import DeleteTag from './delete-tags'; import './index.css'; @@ -27,6 +28,8 @@ const TagsEditor = forwardRef(({ showTagsAsTree, canEditData = false, canAddTag = false, + showDeletableTags = false, + showRecentlyUsed = true, }, ref) => { const { tagsData, context, addTag } = useTags(); @@ -419,10 +422,10 @@ const TagsEditor = forwardRef(({ const noOptionsTip = searchValue ? gettext('No tags available') : gettext('No tag'); return ({noOptionsTip}); } - const showRecentlyUsed = recentlyUsedTags.length > 0 && !searchValue; + const isRecentlyUsedVisible = showRecentlyUsed && recentlyUsedTags.length > 0 && !searchValue; return ( <> - {showRecentlyUsed && ( + {isRecentlyUsedVisible && ( <>
{gettext('Recently used tags')}
{renderRecentlyUsed()} @@ -455,10 +458,11 @@ const TagsEditor = forwardRef(({ })} ); - }, [nodes, tagsData, value, highlightNodeIndex, searchValue, recentlyUsedTags, renderRecentlyUsed, toggleExpandTreeNode, keyNodeFoldedMap, searchedKeyNodeFoldedMap, handleSelectTags, onTreeMenuMouseEnter, onTreeMenuMouseLeave]); + }, [nodes, tagsData, value, highlightNodeIndex, searchValue, recentlyUsedTags, keyNodeFoldedMap, searchedKeyNodeFoldedMap, showRecentlyUsed, renderRecentlyUsed, toggleExpandTreeNode, handleSelectTags, onTreeMenuMouseEnter, onTreeMenuMouseLeave]); return (
+ {showDeletableTags && }
{tagName}
- {isSelected && } + {isSelected && }
diff --git a/frontend/src/metadata/components/popover/filter-popover/basic-filters/file-folder-filter.js b/frontend/src/metadata/components/popover/filter-popover/basic-filters/file-folder-filter.js index daff35cf32..e287e2961c 100644 --- a/frontend/src/metadata/components/popover/filter-popover/basic-filters/file-folder-filter.js +++ b/frontend/src/metadata/components/popover/filter-popover/basic-filters/file-folder-filter.js @@ -1,7 +1,6 @@ import React, { useCallback, useMemo } from 'react'; import PropTypes from 'prop-types'; import CustomizeSelect from '../../../../../components/customize-select'; -import Icon from '../../../../../components/icon'; import { gettext } from '../../../../../utils/constants'; const OPTIONS = [ @@ -21,7 +20,7 @@ const FileOrFolderFilter = ({ readOnly, value = 'all', onChange: onChangeAPI })
{name}
- {value === o.value && ()} + {value === o.value && ()}
) @@ -31,13 +30,7 @@ const FileOrFolderFilter = ({ readOnly, value = 'all', onChange: onChangeAPI }) const displayValue = useMemo(() => { const selectedOption = OPTIONS.find(o => o.value === value) || OPTIONS[2]; - return { - label: ( -
- {selectedOption.name} -
- ) - }; + return { label: <>{selectedOption.name} }; }, [value]); const onChange = useCallback((newValue) => { diff --git a/frontend/src/metadata/components/popover/filter-popover/basic-filters/gallery-file-type-filter.js b/frontend/src/metadata/components/popover/filter-popover/basic-filters/gallery-file-type-filter.js index 76a82ded70..e28430faf0 100644 --- a/frontend/src/metadata/components/popover/filter-popover/basic-filters/gallery-file-type-filter.js +++ b/frontend/src/metadata/components/popover/filter-popover/basic-filters/gallery-file-type-filter.js @@ -1,7 +1,6 @@ import React, { useCallback, useMemo } from 'react'; import PropTypes from 'prop-types'; import CustomizeSelect from '../../../../../components/customize-select'; -import Icon from '../../../../../components/icon'; import { gettext } from '../../../../../utils/constants'; const OPTIONS = [ @@ -21,7 +20,7 @@ const GalleryFileTypeFilter = ({ readOnly, value = 'picture', onChange: onChange
{name}
- {value === o.value && ()} + {value === o.value && ()}
) @@ -31,13 +30,7 @@ const GalleryFileTypeFilter = ({ readOnly, value = 'picture', onChange: onChange const displayValue = useMemo(() => { const selectedOption = OPTIONS.find(o => o.value === value) || OPTIONS[2]; - return { - label: ( -
- {selectedOption.name} -
- ) - }; + return { label: <>{selectedOption.name} }; }, [value]); const onChange = useCallback((newValue) => { diff --git a/frontend/src/metadata/components/popover/filter-popover/basic-filters/index.css b/frontend/src/metadata/components/popover/filter-popover/basic-filters/index.css index 156202cdb6..a079b3322d 100644 --- a/frontend/src/metadata/components/popover/filter-popover/basic-filters/index.css +++ b/frontend/src/metadata/components/popover/filter-popover/basic-filters/index.css @@ -9,20 +9,42 @@ } .sf-metadata-basic-filters-select.seafile-customize-select:hover { - background: #f5f5f5; + background: #efefef; +} + +.sf-metadata-basic-filters-select.seafile-customize-select.highlighted:hover { + background-color: rgba(255, 152, 0, 0.2); } .sf-metadata-basic-filters-select.seafile-customize-select .selected-option { - background-color: inherit; + background-color: transparent; +} + +.sf-metadata-basic-filters-select.seafile-customize-select .selected-option .sf3-font-down, +.sf-metadata-basic-filters-select.sf-metadata-basic-filter-tags-select .sf3-font-down { + margin-left: 0; } .sf-metadata-basic-filters-select.seafile-customize-select.focus { border-color: unset; box-shadow: none; + background-color: rgba(255, 152, 0, 0.2); } -.sf-metadata-basic-filters-select .selected-option-show { - margin-right: 8px; +.sf-metadata-basic-filters-select.focus .selected-option .selected-option-show, +.sf-metadata-basic-filters-select.focus .selected-option .sf3-font-down, +.sf-metadata-basic-filters-select.highlighted .selected-option .selected-option-show, +.sf-metadata-basic-filters-select.highlighted .selected-option .sf3-font-down { + color: #ff9800; +} + +.sf-metadata-basic-filters-select.seafile-customize-select .selected-option .selected-option-show { + width: 100%; +} + +.sf-metadata-basic-filters-select .selected-option-show, +.sf-metadata-basic-filters-select .select-placeholder { + margin-right: 4px; } .sf-metadata-basic-filters-select .selected-option .custom-select-dropdown-icon { @@ -75,3 +97,21 @@ .sf-metadata-table-view-basic-filter-file-type-select .select-basic-filter-option .select-basic-filter-option-checkbox input { cursor: inherit; } + +.sf-metadata-basic-filters-select .select-basic-filter-display-name:hover { + cursor: pointer; +} + +.sf-metadata-basic-filter-tags-select .tag-options-container { + width: auto; + height: auto; + position: absolute; + top: 28px; + left: -340px; + z-index: 10001; +} + +.sf-metadata-basic-filter-tags-select .tag-options-container .sf-metadata-delete-select-tags { + padding: 8px 16px; + min-height: 28px; +} diff --git a/frontend/src/metadata/components/popover/filter-popover/basic-filters/index.js b/frontend/src/metadata/components/popover/filter-popover/basic-filters/index.js index a0488a1257..5be6f1ccbd 100644 --- a/frontend/src/metadata/components/popover/filter-popover/basic-filters/index.js +++ b/frontend/src/metadata/components/popover/filter-popover/basic-filters/index.js @@ -53,7 +53,7 @@ const BasicFilters = ({ readOnly, filters = [], onChange, viewType }) => { return (); } if (column_key === PRIVATE_COLUMN_KEY.TAGS) { - return (); + return (); } return null; })} diff --git a/frontend/src/metadata/components/popover/filter-popover/basic-filters/table-file-type-filter.js b/frontend/src/metadata/components/popover/filter-popover/basic-filters/table-file-type-filter.js index 8554e115ab..86427dfe2c 100644 --- a/frontend/src/metadata/components/popover/filter-popover/basic-filters/table-file-type-filter.js +++ b/frontend/src/metadata/components/popover/filter-popover/basic-filters/table-file-type-filter.js @@ -1,5 +1,6 @@ import React, { useCallback, useMemo } from 'react'; import PropTypes from 'prop-types'; +import classNames from 'classnames'; import CustomizeSelect from '../../../../../components/customize-select'; import { gettext } from '../../../../../utils/constants'; import { getFileTypeColumnOptions } from '../../../../utils/column'; @@ -33,15 +34,8 @@ const TableFileTypeFilter = ({ readOnly, value, onChange: onChangeAPI }) => { }, [OPTIONS, value]); const displayValue = useMemo(() => { - const selectedOptions = OPTIONS.filter(o => value.includes(o.value)); - return { - label: ( -
- {selectedOptions.length > 0 ? selectedOptions.map(o => o.name).join(', ') : gettext('File type')} -
- ) - }; - }, [OPTIONS, value]); + return { label: <>{gettext('File type')} }; + }, []); const onChange = useCallback((newValue) => { if (value.includes(newValue)) { @@ -54,7 +48,9 @@ const TableFileTypeFilter = ({ readOnly, value, onChange: onChangeAPI }) => { return ( 0, + })} value={displayValue} options={options} onSelectOption={onChange} diff --git a/frontend/src/metadata/components/popover/filter-popover/basic-filters/tags-filter.js b/frontend/src/metadata/components/popover/filter-popover/basic-filters/tags-filter.js index ec5cd58146..8858ee1c2e 100644 --- a/frontend/src/metadata/components/popover/filter-popover/basic-filters/tags-filter.js +++ b/frontend/src/metadata/components/popover/filter-popover/basic-filters/tags-filter.js @@ -1,21 +1,23 @@ -import React, { useCallback, useMemo } from 'react'; +import React, { useCallback, useMemo, useRef, useState } from 'react'; import { UncontrolledTooltip } from 'reactstrap'; import PropTypes from 'prop-types'; -import CustomizeSelect from '../../../../../components/customize-select'; import Icon from '../../../../../components/icon'; -import FileTagsFormatter from '../../../cell-formatter/file-tags'; import { gettext } from '../../../../../utils/constants'; import { useMetadataStatus } from '../../../../../hooks'; import { useTags } from '../../../../../tag/hooks'; -import { getTagId, getTagName, getTagColor } from '../../../../../tag/utils/cell'; import { getRowById } from '../../../../../components/sf-table/utils/table'; import IconBtn from '../../../../../components/icon-btn'; +import ClickOutside from '../../../../../components/click-outside'; +import TagsEditor from '../../../cell-editors/tags-editor'; +import classNames from 'classnames'; -const TagsFilter = ({ readOnly, value: oldValue, onChange: onChangeAPI }) => { +const TagsFilter = ({ value: oldValue, onChange: onChangeAPI }) => { + const [isOptionsVisible, setIsOptionsVisible] = useState(false); const { tagsData } = useTags(); const { enableTags } = useMetadataStatus(); - const invalidFilterTip = React.createRef(); + const invalidFilterTip = useRef(null); + const ref = useRef(null); const value = useMemo(() => { if (!enableTags) return []; @@ -24,62 +26,22 @@ const TagsFilter = ({ readOnly, value: oldValue, onChange: onChangeAPI }) => { return oldValue.filter(tagId => getRowById(tagsData, tagId)); }, [oldValue, tagsData, enableTags]); - const options = useMemo(() => { - if (!tagsData) return []; - const tags = tagsData?.rows || []; - if (tags.length === 0) return []; - return tags.map(tag => { - const tagId = getTagId(tag); - const tagName = getTagName(tag); - const tagColor = getTagColor(tag); - const isSelected = Array.isArray(value) ? value.includes(tagId) : false; - return { - name: tagName, - value: tagId, - label: ( -
-
-
-
{tagName}
-
-
- {isSelected && ()} -
-
- ) - }; - }); - }, [value, tagsData]); + const onSelectToggle = useCallback(() => { + setIsOptionsVisible(!isOptionsVisible); + }, [isOptionsVisible]); - const displayValue = useMemo(() => { - const emptyTip = { - label: ( -
- {gettext('Tags')} -
- ), - }; - if (!tagsData) return emptyTip; - const tags = tagsData?.rows || []; - if (tags.length === 0) emptyTip; - if (!Array.isArray(value) || value.length === 0) return emptyTip; - const selectedTags = value.map(tagId => getRowById(tagsData, tagId)).filter(item => item).map(tag => ({ row_id: getTagId(tag) })); - if (selectedTags.length === 0) return emptyTip; - return { - label: ( -
- -
- ) - }; - }, [value, tagsData]); - - const onChange = useCallback((newValue) => { - if (value.includes(newValue)) { - onChangeAPI(value.filter(v => v !== newValue)); - } else { - onChangeAPI([...value, newValue]); + const onClickOutside = useCallback((e) => { + if (!ref.current.contains(e.target)) { + setIsOptionsVisible(false); } + }, []); + + const handleSelect = useCallback((tagId) => { + onChangeAPI([...value, tagId]); + }, [value, onChangeAPI]); + + const handleDeselect = useCallback((tagId) => { + onChangeAPI(value.filter(v => v !== tagId)); }, [value, onChangeAPI]); const onDeleteFilter = useCallback((event) => { @@ -89,7 +51,6 @@ const TagsFilter = ({ readOnly, value: oldValue, onChange: onChangeAPI }) => { oldValue = []; }, [value, onChangeAPI, oldValue]); - const renderErrorMessage = () => { return (
@@ -108,19 +69,38 @@ const TagsFilter = ({ readOnly, value: oldValue, onChange: onChangeAPI }) => { ); }; - const renderCustomizeSelect = () => { + const renderTagsTree = () => { return ( - +
0 + })} + > +
+ {gettext('Tags')} + +
+ {isOptionsVisible && ( + +
+ ({ row_id: tagId }))} + column={{ width: 400 }} + onSelect={handleSelect} + onDeselect={handleDeselect} + showTagsAsTree={true} + showDeletableTags={true} + showRecentlyUsed={false} + /> +
+
+ )} +
); }; @@ -128,7 +108,7 @@ const TagsFilter = ({ readOnly, value: oldValue, onChange: onChangeAPI }) => { if (oldValue.length !== 0) { return (
- {renderCustomizeSelect()} + {renderTagsTree()} {renderErrorMessage()}
@@ -141,9 +121,7 @@ const TagsFilter = ({ readOnly, value: oldValue, onChange: onChangeAPI }) => { } return ( -
- {renderCustomizeSelect()} -
+ <>{renderTagsTree()} ); };