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: (
-
-
-
- {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()}>
);
};