mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-26 15:26:19 +00:00
Optimize/tags filter UI (#7904)
* optimize tags editor * update tags filter * optimize --------- Co-authored-by: zhouwenxuan <aries@Mac.local>
This commit is contained in:
@@ -13,6 +13,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.sf-metadata-tags-editor .sf-metadata-search-tags-container {
|
.sf-metadata-tags-editor .sf-metadata-search-tags-container {
|
||||||
|
position: relative;
|
||||||
padding: 10px 10px 0;
|
padding: 10px 10px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -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 { 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 { checkIsTreeNodeShown, checkTreeNodeHasChildNodes, getNodesWithAncestors, getTreeNodeDepth, getTreeNodeId, getTreeNodeKey } from '../../../../components/sf-table/utils/tree';
|
||||||
import TagItem from './tag-item';
|
import TagItem from './tag-item';
|
||||||
|
import DeleteTag from './delete-tags';
|
||||||
|
|
||||||
import './index.css';
|
import './index.css';
|
||||||
|
|
||||||
@@ -27,6 +28,8 @@ const TagsEditor = forwardRef(({
|
|||||||
showTagsAsTree,
|
showTagsAsTree,
|
||||||
canEditData = false,
|
canEditData = false,
|
||||||
canAddTag = false,
|
canAddTag = false,
|
||||||
|
showDeletableTags = false,
|
||||||
|
showRecentlyUsed = true,
|
||||||
}, ref) => {
|
}, ref) => {
|
||||||
const { tagsData, context, addTag } = useTags();
|
const { tagsData, context, addTag } = useTags();
|
||||||
|
|
||||||
@@ -419,10 +422,10 @@ const TagsEditor = forwardRef(({
|
|||||||
const noOptionsTip = searchValue ? gettext('No tags available') : gettext('No tag');
|
const noOptionsTip = searchValue ? gettext('No tags available') : gettext('No tag');
|
||||||
return (<span className="none-search-result px-4">{noOptionsTip}</span>);
|
return (<span className="none-search-result px-4">{noOptionsTip}</span>);
|
||||||
}
|
}
|
||||||
const showRecentlyUsed = recentlyUsedTags.length > 0 && !searchValue;
|
const isRecentlyUsedVisible = showRecentlyUsed && recentlyUsedTags.length > 0 && !searchValue;
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{showRecentlyUsed && (
|
{isRecentlyUsedVisible && (
|
||||||
<>
|
<>
|
||||||
<div className="sf-metadata-tags-editor-title">{gettext('Recently used tags')}</div>
|
<div className="sf-metadata-tags-editor-title">{gettext('Recently used tags')}</div>
|
||||||
{renderRecentlyUsed()}
|
{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 (
|
return (
|
||||||
<div className={classnames('sf-metadata-tags-editor', { 'tags-tree-container': showTagsAsTree })} style={style} ref={editorRef}>
|
<div className={classnames('sf-metadata-tags-editor', { 'tags-tree-container': showTagsAsTree })} style={style} ref={editorRef}>
|
||||||
|
{showDeletableTags && <DeleteTag value={value} tags={tagsData} onDelete={handleSelectTags} />}
|
||||||
<div className="sf-metadata-search-tags-container">
|
<div className="sf-metadata-search-tags-container">
|
||||||
<SearchInput
|
<SearchInput
|
||||||
placeholder={gettext('Search tag')}
|
placeholder={gettext('Search tag')}
|
||||||
|
@@ -3,7 +3,6 @@ import PropTypes from 'prop-types';
|
|||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { getTagColor, getTagId, getTagName } from '../../../../../tag/utils/cell';
|
import { getTagColor, getTagId, getTagName } from '../../../../../tag/utils/cell';
|
||||||
import { NODE_CONTENT_LEFT_INDENT, NODE_ICON_LEFT_INDENT } from '../../../../../components/sf-table/constants/tree';
|
import { NODE_CONTENT_LEFT_INDENT, NODE_ICON_LEFT_INDENT } from '../../../../../components/sf-table/constants/tree';
|
||||||
import Icon from '../../../../../components/icon';
|
|
||||||
|
|
||||||
import './index.css';
|
import './index.css';
|
||||||
|
|
||||||
@@ -50,7 +49,7 @@ const TagItem = ({
|
|||||||
<div className="sf-metadata-tag-name">{tagName}</div>
|
<div className="sf-metadata-tag-name">{tagName}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="sf-metadata-tags-editor-tag-check-icon mr-1">
|
<div className="sf-metadata-tags-editor-tag-check-icon mr-1">
|
||||||
{isSelected && <Icon className="sf-metadata-icon" symbol="check-mark" />}
|
{isSelected && <i className="sf2-icon-tick"></i>}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
import React, { useCallback, useMemo } from 'react';
|
import React, { useCallback, useMemo } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import CustomizeSelect from '../../../../../components/customize-select';
|
import CustomizeSelect from '../../../../../components/customize-select';
|
||||||
import Icon from '../../../../../components/icon';
|
|
||||||
import { gettext } from '../../../../../utils/constants';
|
import { gettext } from '../../../../../utils/constants';
|
||||||
|
|
||||||
const OPTIONS = [
|
const OPTIONS = [
|
||||||
@@ -21,7 +20,7 @@ const FileOrFolderFilter = ({ readOnly, value = 'all', onChange: onChangeAPI })
|
|||||||
<div className="select-basic-filter-option">
|
<div className="select-basic-filter-option">
|
||||||
<div className="select-basic-filter-option-name" title={name} aria-label={name}>{name}</div>
|
<div className="select-basic-filter-option-name" title={name} aria-label={name}>{name}</div>
|
||||||
<div className="select-basic-filter-option-check-icon">
|
<div className="select-basic-filter-option-check-icon">
|
||||||
{value === o.value && (<Icon symbol="check-mark" />)}
|
{value === o.value && (<i className="sf2-icon-tick"></i>)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
@@ -31,13 +30,7 @@ const FileOrFolderFilter = ({ readOnly, value = 'all', onChange: onChangeAPI })
|
|||||||
|
|
||||||
const displayValue = useMemo(() => {
|
const displayValue = useMemo(() => {
|
||||||
const selectedOption = OPTIONS.find(o => o.value === value) || OPTIONS[2];
|
const selectedOption = OPTIONS.find(o => o.value === value) || OPTIONS[2];
|
||||||
return {
|
return { label: <>{selectedOption.name}</> };
|
||||||
label: (
|
|
||||||
<div>
|
|
||||||
{selectedOption.name}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
};
|
|
||||||
}, [value]);
|
}, [value]);
|
||||||
|
|
||||||
const onChange = useCallback((newValue) => {
|
const onChange = useCallback((newValue) => {
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
import React, { useCallback, useMemo } from 'react';
|
import React, { useCallback, useMemo } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import CustomizeSelect from '../../../../../components/customize-select';
|
import CustomizeSelect from '../../../../../components/customize-select';
|
||||||
import Icon from '../../../../../components/icon';
|
|
||||||
import { gettext } from '../../../../../utils/constants';
|
import { gettext } from '../../../../../utils/constants';
|
||||||
|
|
||||||
const OPTIONS = [
|
const OPTIONS = [
|
||||||
@@ -21,7 +20,7 @@ const GalleryFileTypeFilter = ({ readOnly, value = 'picture', onChange: onChange
|
|||||||
<div className="select-basic-filter-option">
|
<div className="select-basic-filter-option">
|
||||||
<div className="select-basic-filter-option-name" title={name} aria-label={name}>{name}</div>
|
<div className="select-basic-filter-option-name" title={name} aria-label={name}>{name}</div>
|
||||||
<div className="select-basic-filter-option-check-icon">
|
<div className="select-basic-filter-option-check-icon">
|
||||||
{value === o.value && (<Icon symbol="check-mark" />)}
|
{value === o.value && (<i className="sf2-icon-tick"></i>)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
@@ -31,13 +30,7 @@ const GalleryFileTypeFilter = ({ readOnly, value = 'picture', onChange: onChange
|
|||||||
|
|
||||||
const displayValue = useMemo(() => {
|
const displayValue = useMemo(() => {
|
||||||
const selectedOption = OPTIONS.find(o => o.value === value) || OPTIONS[2];
|
const selectedOption = OPTIONS.find(o => o.value === value) || OPTIONS[2];
|
||||||
return {
|
return { label: <>{selectedOption.name}</> };
|
||||||
label: (
|
|
||||||
<div>
|
|
||||||
{selectedOption.name}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
};
|
|
||||||
}, [value]);
|
}, [value]);
|
||||||
|
|
||||||
const onChange = useCallback((newValue) => {
|
const onChange = useCallback((newValue) => {
|
||||||
|
@@ -9,20 +9,42 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.sf-metadata-basic-filters-select.seafile-customize-select:hover {
|
.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 {
|
.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 {
|
.sf-metadata-basic-filters-select.seafile-customize-select.focus {
|
||||||
border-color: unset;
|
border-color: unset;
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
|
background-color: rgba(255, 152, 0, 0.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
.sf-metadata-basic-filters-select .selected-option-show {
|
.sf-metadata-basic-filters-select.focus .selected-option .selected-option-show,
|
||||||
margin-right: 8px;
|
.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 {
|
.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 {
|
.sf-metadata-table-view-basic-filter-file-type-select .select-basic-filter-option .select-basic-filter-option-checkbox input {
|
||||||
cursor: inherit;
|
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;
|
||||||
|
}
|
||||||
|
@@ -53,7 +53,7 @@ const BasicFilters = ({ readOnly, filters = [], onChange, viewType }) => {
|
|||||||
return (<FileTypeFilter key={column_key} readOnly={readOnly} value={filter_term} onChange={onChangeFileTypeFilter} />);
|
return (<FileTypeFilter key={column_key} readOnly={readOnly} value={filter_term} onChange={onChangeFileTypeFilter} />);
|
||||||
}
|
}
|
||||||
if (column_key === PRIVATE_COLUMN_KEY.TAGS) {
|
if (column_key === PRIVATE_COLUMN_KEY.TAGS) {
|
||||||
return (<TagsFilter key={column_key} readOnly={readOnly} value={filter_term} onChange={onChangeTagsFilter} />);
|
return (<TagsFilter key={column_key} value={filter_term} onChange={onChangeTagsFilter} />);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
})}
|
})}
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
import React, { useCallback, useMemo } from 'react';
|
import React, { useCallback, useMemo } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
import classNames from 'classnames';
|
||||||
import CustomizeSelect from '../../../../../components/customize-select';
|
import CustomizeSelect from '../../../../../components/customize-select';
|
||||||
import { gettext } from '../../../../../utils/constants';
|
import { gettext } from '../../../../../utils/constants';
|
||||||
import { getFileTypeColumnOptions } from '../../../../utils/column';
|
import { getFileTypeColumnOptions } from '../../../../utils/column';
|
||||||
@@ -33,15 +34,8 @@ const TableFileTypeFilter = ({ readOnly, value, onChange: onChangeAPI }) => {
|
|||||||
}, [OPTIONS, value]);
|
}, [OPTIONS, value]);
|
||||||
|
|
||||||
const displayValue = useMemo(() => {
|
const displayValue = useMemo(() => {
|
||||||
const selectedOptions = OPTIONS.filter(o => value.includes(o.value));
|
return { label: <>{gettext('File type')}</> };
|
||||||
return {
|
}, []);
|
||||||
label: (
|
|
||||||
<div className="select-basic-filter-display-name">
|
|
||||||
{selectedOptions.length > 0 ? selectedOptions.map(o => o.name).join(', ') : gettext('File type')}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
};
|
|
||||||
}, [OPTIONS, value]);
|
|
||||||
|
|
||||||
const onChange = useCallback((newValue) => {
|
const onChange = useCallback((newValue) => {
|
||||||
if (value.includes(newValue)) {
|
if (value.includes(newValue)) {
|
||||||
@@ -54,7 +48,9 @@ const TableFileTypeFilter = ({ readOnly, value, onChange: onChangeAPI }) => {
|
|||||||
return (
|
return (
|
||||||
<CustomizeSelect
|
<CustomizeSelect
|
||||||
readOnly={readOnly}
|
readOnly={readOnly}
|
||||||
className="sf-metadata-basic-filters-select sf-metadata-table-view-basic-filter-file-type-select mr-4"
|
className={classNames('sf-metadata-basic-filters-select sf-metadata-table-view-basic-filter-file-type-select mr-4', {
|
||||||
|
'highlighted': value.length > 0,
|
||||||
|
})}
|
||||||
value={displayValue}
|
value={displayValue}
|
||||||
options={options}
|
options={options}
|
||||||
onSelectOption={onChange}
|
onSelectOption={onChange}
|
||||||
|
@@ -1,21 +1,23 @@
|
|||||||
import React, { useCallback, useMemo } from 'react';
|
import React, { useCallback, useMemo, useRef, useState } from 'react';
|
||||||
import { UncontrolledTooltip } from 'reactstrap';
|
import { UncontrolledTooltip } from 'reactstrap';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import CustomizeSelect from '../../../../../components/customize-select';
|
|
||||||
import Icon from '../../../../../components/icon';
|
import Icon from '../../../../../components/icon';
|
||||||
import FileTagsFormatter from '../../../cell-formatter/file-tags';
|
|
||||||
import { gettext } from '../../../../../utils/constants';
|
import { gettext } from '../../../../../utils/constants';
|
||||||
import { useMetadataStatus } from '../../../../../hooks';
|
import { useMetadataStatus } from '../../../../../hooks';
|
||||||
import { useTags } from '../../../../../tag/hooks';
|
import { useTags } from '../../../../../tag/hooks';
|
||||||
import { getTagId, getTagName, getTagColor } from '../../../../../tag/utils/cell';
|
|
||||||
import { getRowById } from '../../../../../components/sf-table/utils/table';
|
import { getRowById } from '../../../../../components/sf-table/utils/table';
|
||||||
import IconBtn from '../../../../../components/icon-btn';
|
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 { tagsData } = useTags();
|
||||||
const { enableTags } = useMetadataStatus();
|
const { enableTags } = useMetadataStatus();
|
||||||
const invalidFilterTip = React.createRef();
|
const invalidFilterTip = useRef(null);
|
||||||
|
const ref = useRef(null);
|
||||||
|
|
||||||
const value = useMemo(() => {
|
const value = useMemo(() => {
|
||||||
if (!enableTags) return [];
|
if (!enableTags) return [];
|
||||||
@@ -24,62 +26,22 @@ const TagsFilter = ({ readOnly, value: oldValue, onChange: onChangeAPI }) => {
|
|||||||
return oldValue.filter(tagId => getRowById(tagsData, tagId));
|
return oldValue.filter(tagId => getRowById(tagsData, tagId));
|
||||||
}, [oldValue, tagsData, enableTags]);
|
}, [oldValue, tagsData, enableTags]);
|
||||||
|
|
||||||
const options = useMemo(() => {
|
const onSelectToggle = useCallback(() => {
|
||||||
if (!tagsData) return [];
|
setIsOptionsVisible(!isOptionsVisible);
|
||||||
const tags = tagsData?.rows || [];
|
}, [isOptionsVisible]);
|
||||||
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: (
|
|
||||||
<div className="select-basic-filter-option">
|
|
||||||
<div className="sf-metadata-tag-color-and-name">
|
|
||||||
<div className="sf-metadata-tag-color" style={{ backgroundColor: tagColor }}></div>
|
|
||||||
<div className="sf-metadata-tag-name">{tagName}</div>
|
|
||||||
</div>
|
|
||||||
<div className="select-basic-filter-option-check-icon">
|
|
||||||
{isSelected && (<Icon symbol="check-mark" />)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}, [value, tagsData]);
|
|
||||||
|
|
||||||
const displayValue = useMemo(() => {
|
const onClickOutside = useCallback((e) => {
|
||||||
const emptyTip = {
|
if (!ref.current.contains(e.target)) {
|
||||||
label: (
|
setIsOptionsVisible(false);
|
||||||
<div className="select-basic-filter-display-name">
|
|
||||||
{gettext('Tags')}
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
};
|
|
||||||
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: (
|
|
||||||
<div className="select-basic-filter-display-name">
|
|
||||||
<FileTagsFormatter className="sf-metadata-basic-tags-filter-formatter pr-2" tagsData={tagsData} value={selectedTags} />
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
};
|
|
||||||
}, [value, tagsData]);
|
|
||||||
|
|
||||||
const onChange = useCallback((newValue) => {
|
|
||||||
if (value.includes(newValue)) {
|
|
||||||
onChangeAPI(value.filter(v => v !== newValue));
|
|
||||||
} else {
|
|
||||||
onChangeAPI([...value, newValue]);
|
|
||||||
}
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleSelect = useCallback((tagId) => {
|
||||||
|
onChangeAPI([...value, tagId]);
|
||||||
|
}, [value, onChangeAPI]);
|
||||||
|
|
||||||
|
const handleDeselect = useCallback((tagId) => {
|
||||||
|
onChangeAPI(value.filter(v => v !== tagId));
|
||||||
}, [value, onChangeAPI]);
|
}, [value, onChangeAPI]);
|
||||||
|
|
||||||
const onDeleteFilter = useCallback((event) => {
|
const onDeleteFilter = useCallback((event) => {
|
||||||
@@ -89,7 +51,6 @@ const TagsFilter = ({ readOnly, value: oldValue, onChange: onChangeAPI }) => {
|
|||||||
oldValue = [];
|
oldValue = [];
|
||||||
}, [value, onChangeAPI, oldValue]);
|
}, [value, onChangeAPI, oldValue]);
|
||||||
|
|
||||||
|
|
||||||
const renderErrorMessage = () => {
|
const renderErrorMessage = () => {
|
||||||
return (
|
return (
|
||||||
<div className="ml-2">
|
<div className="ml-2">
|
||||||
@@ -108,19 +69,38 @@ const TagsFilter = ({ readOnly, value: oldValue, onChange: onChangeAPI }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderCustomizeSelect = () => {
|
const renderTagsTree = () => {
|
||||||
return (
|
return (
|
||||||
<CustomizeSelect
|
<div
|
||||||
readOnly={readOnly}
|
ref={ref}
|
||||||
searchable={true}
|
className={classNames('sf-metadata-basic-filters-select sf-metadata-basic-filter-tags-select seafile-customize-select custom-select mr-4', {
|
||||||
supportMultipleSelect={true}
|
'focus': isOptionsVisible,
|
||||||
className="sf-metadata-basic-filters-select sf-metadata-table-view-basic-filter-file-type-select mr-4"
|
'highlighted': value.length > 0
|
||||||
value={displayValue}
|
})}
|
||||||
options={options}
|
>
|
||||||
onSelectOption={onChange}
|
<div
|
||||||
searchPlaceholder={gettext('Search tag')}
|
className="selected-option"
|
||||||
noOptionsPlaceholder={gettext('No tags')}
|
onClick={onSelectToggle}
|
||||||
|
>
|
||||||
|
<span className="selected-option-show">{gettext('Tags')}</span>
|
||||||
|
<i className="sf3-font sf3-font-down" aria-hidden="true"></i>
|
||||||
|
</div>
|
||||||
|
{isOptionsVisible && (
|
||||||
|
<ClickOutside onClickOutside={onClickOutside}>
|
||||||
|
<div className="tag-options-container">
|
||||||
|
<TagsEditor
|
||||||
|
value={value.map(tagId => ({ row_id: tagId }))}
|
||||||
|
column={{ width: 400 }}
|
||||||
|
onSelect={handleSelect}
|
||||||
|
onDeselect={handleDeselect}
|
||||||
|
showTagsAsTree={true}
|
||||||
|
showDeletableTags={true}
|
||||||
|
showRecentlyUsed={false}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
</ClickOutside>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -128,7 +108,7 @@ const TagsFilter = ({ readOnly, value: oldValue, onChange: onChangeAPI }) => {
|
|||||||
if (oldValue.length !== 0) {
|
if (oldValue.length !== 0) {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{renderCustomizeSelect()}
|
{renderTagsTree()}
|
||||||
{renderErrorMessage()}
|
{renderErrorMessage()}
|
||||||
<div className="delete-filter" onClick={onDeleteFilter}>
|
<div className="delete-filter" onClick={onDeleteFilter}>
|
||||||
<Icon className="sf-metadata-icon" symbol="fork-number"/>
|
<Icon className="sf-metadata-icon" symbol="fork-number"/>
|
||||||
@@ -141,9 +121,7 @@ const TagsFilter = ({ readOnly, value: oldValue, onChange: onChangeAPI }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<>{renderTagsTree()}</>
|
||||||
{renderCustomizeSelect()}
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
|
|
||||||
};
|
};
|
||||||
|
Reference in New Issue
Block a user