mirror of
https://github.com/haiwen/seahub.git
synced 2025-05-11 17:34:56 +00:00
Feature/show tags in search dialog (#7727)
* show related tags * optimize * fix eslint warning * change searched tags background --------- Co-authored-by: zhouwenxuan <aries@Mac.local> Co-authored-by: Michael An <1822852997@qq.com>
This commit is contained in:
parent
da86a8e1b0
commit
c23a153818
frontend/src
@ -14,4 +14,10 @@ export const EVENT_BUS_TYPE = {
|
||||
// migrate tags
|
||||
OPEN_TREE_PANEL: 'open_tree_panel',
|
||||
OPEN_LIBRARY_SETTINGS_TAGS: 'open_library_settings_tags',
|
||||
|
||||
// tags
|
||||
TAG_STATUS: 'tag_status',
|
||||
TAGS_DATA: 'tags_data',
|
||||
SELECT_TAG: 'select_tag',
|
||||
UPDATE_SELECTED_TAG: 'update_selected_tag',
|
||||
};
|
||||
|
@ -1,5 +1,6 @@
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { Dropdown, DropdownItem, DropdownMenu, DropdownToggle } from 'reactstrap';
|
||||
import PropTypes from 'prop-types';
|
||||
import { gettext } from '../../../utils/constants';
|
||||
import { Utils } from '../../../utils/utils';
|
||||
import UserItem from './user-item';
|
||||
@ -7,7 +8,7 @@ import { seafileAPI } from '../../../utils/seafile-api';
|
||||
import ModalPortal from '../../modal-portal';
|
||||
import toaster from '../../toast';
|
||||
|
||||
const FilterByCreator = ({ repoID, onSelect }) => {
|
||||
const FilterByCreator = ({ onSelect }) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [options, setOptions] = useState([]);
|
||||
const [value, setValue] = useState([]);
|
||||
@ -30,7 +31,7 @@ const FilterByCreator = ({ repoID, onSelect }) => {
|
||||
}, [isOpen]);
|
||||
|
||||
const displayOptions = useMemo(() => {
|
||||
if (!searchValue) return options;
|
||||
if (!searchValue) return null;
|
||||
return options.filter((option) => {
|
||||
return option.name.toLowerCase().includes(searchValue.toLowerCase());
|
||||
});
|
||||
@ -48,8 +49,10 @@ const FilterByCreator = ({ repoID, onSelect }) => {
|
||||
}
|
||||
setValue(updated);
|
||||
onSelect('creator', updated);
|
||||
setSearchValue('');
|
||||
}, [value, onSelect]);
|
||||
if (displayOptions.length === 1) {
|
||||
setSearchValue('');
|
||||
}
|
||||
}, [value, displayOptions, onSelect]);
|
||||
|
||||
const handleCancel = useCallback((v) => {
|
||||
const updated = value.filter((item) => item !== v);
|
||||
@ -57,26 +60,39 @@ const FilterByCreator = ({ repoID, onSelect }) => {
|
||||
onSelect('creator', updated);
|
||||
}, [value, onSelect]);
|
||||
|
||||
const handleInputChange = useCallback((e) => {
|
||||
const v = e.target.value;
|
||||
setSearchValue(v);
|
||||
if (!value) {
|
||||
setOptions([]);
|
||||
}
|
||||
}, [value]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!searchValue) return;
|
||||
|
||||
const getUsers = async () => {
|
||||
try {
|
||||
const res = await seafileAPI.listRepoRelatedUsers(repoID);
|
||||
const users = res.data.user_list;
|
||||
const options = users.map((user) => {
|
||||
return {
|
||||
const res = await seafileAPI.searchUsers(searchValue);
|
||||
const userList = res.data.users
|
||||
.filter(user => user.name.toLowerCase().includes(searchValue.toLowerCase()))
|
||||
.map(user => ({
|
||||
key: user.email,
|
||||
value: user.email,
|
||||
name: user.name,
|
||||
label: <UserItem user={user} />,
|
||||
};
|
||||
});
|
||||
setOptions(options);
|
||||
}))
|
||||
.filter(user => !options.some(option => option.key === user.key));
|
||||
|
||||
setOptions(prevOptions => [...prevOptions, ...userList]);
|
||||
} catch (err) {
|
||||
toaster.danger(Utils.getErrorMsg(err));
|
||||
}
|
||||
};
|
||||
|
||||
getUsers();
|
||||
}, [repoID]);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [searchValue]);
|
||||
|
||||
return (
|
||||
<div className="search-filter filter-by-creator-container">
|
||||
@ -104,11 +120,11 @@ const FilterByCreator = ({ repoID, onSelect }) => {
|
||||
type="text"
|
||||
placeholder={value.length ? '' : gettext('Search user')}
|
||||
value={searchValue}
|
||||
onChange={(e) => setSearchValue(e.target.value)}
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{displayOptions.map((option) => (
|
||||
{displayOptions && displayOptions.map((option) => (
|
||||
<DropdownItem
|
||||
key={option.key}
|
||||
tag="div"
|
||||
@ -129,4 +145,8 @@ const FilterByCreator = ({ repoID, onSelect }) => {
|
||||
);
|
||||
};
|
||||
|
||||
FilterByCreator.propTypes = {
|
||||
onSelect: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default FilterByCreator;
|
||||
|
@ -17,10 +17,10 @@ const FilterByText = ({ onSelect }) => {
|
||||
return [
|
||||
{
|
||||
key: TEXT_FILTER_KEY.SEARCH_FILENAME_AND_CONTENT,
|
||||
label: gettext('Search filename and content'),
|
||||
label: gettext('File name and content'),
|
||||
}, {
|
||||
key: TEXT_FILTER_KEY.SEARCH_FILENAME_ONLY,
|
||||
label: gettext('Search filename only'),
|
||||
label: gettext('File name only'),
|
||||
}
|
||||
];
|
||||
}, []);
|
||||
|
@ -3,11 +3,12 @@
|
||||
position: relative;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
margin-top: 4px;
|
||||
padding: 0 16px;
|
||||
align-items: center;
|
||||
margin-top: 12px;
|
||||
padding: 0 16px 10px;
|
||||
overflow: auto hidden;
|
||||
scrollbar-color: #C1C1C1 rgba(0, 0, 0, 0);
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
.search-filters-container .search-filter {
|
||||
|
@ -7,13 +7,11 @@ import FilterBySuffix from './filter-by-suffix';
|
||||
|
||||
import './index.css';
|
||||
|
||||
const SCROLLABLE_CONTAINER_HEIGHT = 44;
|
||||
|
||||
const SearchFilters = ({ repoID, onChange }) => {
|
||||
const SearchFilters = ({ onChange }) => {
|
||||
return (
|
||||
<div className="search-filters-container" style={{ height: SCROLLABLE_CONTAINER_HEIGHT }}>
|
||||
<div className="search-filters-container">
|
||||
<FilterByText onSelect={onChange} />
|
||||
<FilterByCreator repoID={repoID} onSelect={onChange} />
|
||||
<FilterByCreator onSelect={onChange} />
|
||||
<FilterByDate onSelect={onChange} />
|
||||
<FilterBySuffix onSelect={onChange} />
|
||||
</div>
|
||||
@ -21,7 +19,6 @@ const SearchFilters = ({ repoID, onChange }) => {
|
||||
};
|
||||
|
||||
SearchFilters.propTypes = {
|
||||
repoID: PropTypes.string,
|
||||
onChange: PropTypes.func,
|
||||
};
|
||||
|
||||
|
58
frontend/src/components/search/search-tags/index.css
Normal file
58
frontend/src/components/search/search-tags/index.css
Normal file
@ -0,0 +1,58 @@
|
||||
.search-tags-container {
|
||||
width: 100%;
|
||||
height: fit-content;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
font-size: 0.8125rem;
|
||||
}
|
||||
|
||||
.search-tags-container .tags-title {
|
||||
width: 100%;
|
||||
height: 20px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
padding: 0 16px;
|
||||
margin: 10px 0 4px;
|
||||
font-size: 0.875rem;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.search-tags-container .tags-content {
|
||||
width: 100%;
|
||||
height: fit-content;
|
||||
max-height: 180px;
|
||||
overflow: auto;
|
||||
scrollbar-color: #C1C1C1 rgba(0, 0, 0, 0);
|
||||
padding: 0 16px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.search-tags-container .tags-content .tag-item {
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
padding: 0 16px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.search-tags-container .tags-content .tag-item:hover {
|
||||
background-color: #f0f0f0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.search-tags-container .tags-content .tag-item .tag-color {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 50%;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.search-tags-container .search-tags-divider {
|
||||
height: 0;
|
||||
border-top: 1px solid #eee;
|
||||
margin: 0 16px;
|
||||
}
|
70
frontend/src/components/search/search-tags/index.js
Normal file
70
frontend/src/components/search/search-tags/index.js
Normal file
@ -0,0 +1,70 @@
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { gettext } from '../../../utils/constants';
|
||||
import { getTagColor, getTagId, getTagName } from '../../../tag/utils/cell';
|
||||
import { PRIVATE_FILE_TYPE } from '../../../constants';
|
||||
import { EVENT_BUS_TYPE } from '../../common/event-bus-type';
|
||||
|
||||
import './index.css';
|
||||
|
||||
const SearchTags = ({ repoID, tagsData, keyword, onSelectTag }) => {
|
||||
const [displayTags, setDisplayTags] = useState([]);
|
||||
|
||||
const handleClick = useCallback((e, tagId) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
const node = {
|
||||
children: [],
|
||||
path: '/' + PRIVATE_FILE_TYPE.TAGS_PROPERTIES + '/' + tagId,
|
||||
isExpanded: false,
|
||||
isLoaded: true,
|
||||
isPreload: true,
|
||||
object: {
|
||||
file_tags: [],
|
||||
id: tagId,
|
||||
type: PRIVATE_FILE_TYPE.TAGS_PROPERTIES,
|
||||
isDir: () => false,
|
||||
},
|
||||
parentNode: {},
|
||||
key: repoID,
|
||||
tag_id: tagId,
|
||||
};
|
||||
onSelectTag(node);
|
||||
window.sfTagsDataContext?.eventBus?.dispatch(EVENT_BUS_TYPE.UPDATE_SELECTED_TAG, tagId);
|
||||
}, [repoID, onSelectTag]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!tagsData || tagsData.length === 0 || !keyword) return;
|
||||
const tags = tagsData?.filter((tag) => getTagName(tag).toLowerCase().includes(keyword.toLowerCase()));
|
||||
setDisplayTags(tags);
|
||||
}, [tagsData, keyword]);
|
||||
|
||||
if (!tagsData || tagsData.length === 0 || !keyword || displayTags.length === 0) return null;
|
||||
|
||||
return (
|
||||
<div className="search-tags-container">
|
||||
<div className="tags-title">{gettext('Tags')}</div>
|
||||
<div className="tags-content">
|
||||
{displayTags.map((tag) => {
|
||||
const tagId = getTagId(tag);
|
||||
const tagName = getTagName(tag);
|
||||
const tagColor = getTagColor(tag);
|
||||
return (
|
||||
<div className="tag-item" key={tagId} onClick={(e) => handleClick(e, tagId)}>
|
||||
<div className="tag-color" style={{ backgroundColor: tagColor }} />
|
||||
<div className="tag-name">{tagName}</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div className="search-tags-divider" />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
SearchTags.propTypes = {
|
||||
tagsData: PropTypes.array.isRequired,
|
||||
};
|
||||
|
||||
export default SearchTags;
|
@ -14,6 +14,7 @@ import Loading from '../loading';
|
||||
import { SEARCH_MASK, SEARCH_CONTAINER } from '../../constants/zIndexes';
|
||||
import { PRIVATE_FILE_TYPE } from '../../constants';
|
||||
import SearchFilters from './search-filters';
|
||||
import SearchTags from './search-tags';
|
||||
|
||||
const propTypes = {
|
||||
repoID: PropTypes.string,
|
||||
@ -22,6 +23,7 @@ const propTypes = {
|
||||
onSearchedClick: PropTypes.func.isRequired,
|
||||
isPublic: PropTypes.bool,
|
||||
isViewFile: PropTypes.bool,
|
||||
onSelectTag: PropTypes.func,
|
||||
};
|
||||
|
||||
const PER_PAGE = 20;
|
||||
@ -729,6 +731,7 @@ class Search extends Component {
|
||||
return (
|
||||
<>
|
||||
<MediaQuery query="(min-width: 768px)">
|
||||
{!isVisited && <h4 className="search-results-title">{gettext('Files')}</h4>}
|
||||
<div className="search-result-list-container" ref={this.searchResultListContainerRef}>{results}</div>
|
||||
</MediaQuery>
|
||||
<MediaQuery query="(max-width: 767.8px)">
|
||||
@ -759,12 +762,19 @@ class Search extends Component {
|
||||
this.setState({ filters: newFilters }, () => this.forceUpdate());
|
||||
}
|
||||
|
||||
handleSelectTag = (tag) => {
|
||||
this.props.onSelectTag(tag);
|
||||
this.resetToDefault();
|
||||
}
|
||||
|
||||
render() {
|
||||
let width = this.state.width !== 'default' ? this.state.width : '';
|
||||
let style = {'width': width};
|
||||
const { isMaskShow } = this.state;
|
||||
const { repoID, isTagEnabled, tagsData } = this.props;
|
||||
const { isMaskShow, isResultGotten } = this.state;
|
||||
const placeholder = `${this.props.placeholder}${isMaskShow ? '' : ` (${controlKey} + k)`}`;
|
||||
const isFiltersShow = this.props.repoID && isMaskShow;
|
||||
const isFiltersShow = isMaskShow && isResultGotten;
|
||||
const isTagsShow = this.props.repoID && isTagEnabled && isMaskShow && isResultGotten;
|
||||
return (
|
||||
<Fragment>
|
||||
<MediaQuery query="(min-width: 768px)">
|
||||
@ -795,10 +805,10 @@ class Search extends Component {
|
||||
}
|
||||
</div>
|
||||
{isFiltersShow &&
|
||||
<SearchFilters
|
||||
repoID={this.props.repoID}
|
||||
onChange={this.handleFiltersChange}
|
||||
/>
|
||||
<SearchFilters onChange={this.handleFiltersChange} />
|
||||
}
|
||||
{isTagsShow &&
|
||||
<SearchTags repoID={repoID} tagsData={tagsData} keyword={this.state.value} onSelectTag={this.handleSelectTag} />
|
||||
}
|
||||
<div
|
||||
className="search-result-container dropdown-search-result-container"
|
||||
|
@ -7,6 +7,7 @@ import Notification from '../common/notification';
|
||||
import Account from '../common/account';
|
||||
import Logout from '../common/logout';
|
||||
import { EVENT_BUS_TYPE } from '../common/event-bus-type';
|
||||
import tagsAPI from '../../tag/api';
|
||||
|
||||
const propTypes = {
|
||||
repoID: PropTypes.string,
|
||||
@ -32,19 +33,39 @@ class CommonToolbar extends React.Component {
|
||||
path: props.path,
|
||||
isViewFile: props.isViewFile,
|
||||
currentRepoInfo: props.currentRepoInfo,
|
||||
isTagEnabled: false,
|
||||
tagsData: [],
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
if (this.props.eventBus) {
|
||||
this.unsubscribeLibChange = this.props.eventBus.subscribe(EVENT_BUS_TYPE.CURRENT_LIBRARY_CHANGED, this.onRepoChange);
|
||||
this.unsubscribeTagStatus = this.props.eventBus.subscribe(EVENT_BUS_TYPE.TAG_STATUS, (status) => this.onTagStatus(status));
|
||||
this.unsubscribeTagsChanged = this.props.eventBus.subscribe(EVENT_BUS_TYPE.TAGS_CHANGED, (tags) => this.setState({ tagsData: tags }));
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.unsubscribeLibChange && this.unsubscribeLibChange();
|
||||
this.unsubscribeMetadataStatus && this.unsubscribeMetadataStatus();
|
||||
this.unsubscribeTagsChanged && this.unsubscribeTagsChanged();
|
||||
}
|
||||
|
||||
onTagStatus = (status) => {
|
||||
this.setState({ isTagEnabled: status });
|
||||
if (status) {
|
||||
tagsAPI.getTags(this.state.repoID).then((res) => {
|
||||
const tags = res?.data?.results || [];
|
||||
this.setState({ tagsData: tags });
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
onSelectTag = (tag) => {
|
||||
this.props.eventBus.dispatch(EVENT_BUS_TYPE.SELECT_TAG, tag);
|
||||
};
|
||||
|
||||
onRepoChange = ({ repoID, repoName, isLibView, path, isViewFile, currentRepoInfo }) => {
|
||||
this.setState({ repoID, repoName, isLibView, path, isViewFile, currentRepoInfo });
|
||||
};
|
||||
@ -59,7 +80,7 @@ class CommonToolbar extends React.Component {
|
||||
};
|
||||
|
||||
renderSearch = () => {
|
||||
const { repoID, repoName, isLibView, path, isViewFile } = this.state;
|
||||
const { repoID, repoName, isLibView, path, isViewFile, isTagEnabled, tagsData } = this.state;
|
||||
const { searchPlaceholder } = this.props;
|
||||
const placeholder = searchPlaceholder || gettext('Search files');
|
||||
|
||||
@ -72,6 +93,9 @@ class CommonToolbar extends React.Component {
|
||||
isViewFile={isViewFile}
|
||||
isPublic={false}
|
||||
path={path}
|
||||
isTagEnabled={isTagEnabled}
|
||||
tagsData={tagsData}
|
||||
onSelectTag={this.onSelectTag}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
|
@ -91,7 +91,7 @@
|
||||
border-radius: 0 0 3px 3px;
|
||||
box-shadow: 0 3px 8px 0 rgba(116, 129, 141, 0.1);
|
||||
top: 60px;
|
||||
padding: 0 16px;
|
||||
padding-left: 16px;
|
||||
}
|
||||
|
||||
.dropdown-search-result-container {
|
||||
@ -118,10 +118,12 @@
|
||||
|
||||
.search-result-container .search-result-list-container {
|
||||
overflow: auto;
|
||||
scrollbar-color: #C1C1C1 rgba(0, 0, 0, 0);
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.search-result-container .search-result-item {
|
||||
height: 58px;
|
||||
display: flex;
|
||||
padding: 10px 0 10px 8px;
|
||||
font-size: 0.8125rem;
|
||||
@ -149,7 +151,7 @@
|
||||
.search-result-item .item-content {
|
||||
flex: 1;
|
||||
margin-left: 0.25rem;
|
||||
overflow-x: hidden;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.item-content .item-name a {
|
||||
@ -405,11 +407,12 @@
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.search-results-title,
|
||||
.visited-search-results-title {
|
||||
color: #999;
|
||||
font-size: .875rem;
|
||||
font-weight: normal;
|
||||
margin: 7px 0 10px;
|
||||
margin: 10px 0 4px;
|
||||
}
|
||||
|
||||
.search-types {
|
||||
|
@ -160,6 +160,7 @@ class LibContentView extends React.Component {
|
||||
this.unsubscribeEvent = this.props.eventBus.subscribe(EVENT_BUS_TYPE.SEARCH_LIBRARY_CONTENT, this.onSearchedClick);
|
||||
this.unsubscribeOpenTreePanel = eventBus.subscribe(EVENT_BUS_TYPE.OPEN_TREE_PANEL, this.openTreePanel);
|
||||
this.calculatePara(this.props);
|
||||
this.unsubscribeSelectSearchedTag = this.props.eventBus.subscribe(EVENT_BUS_TYPE.SELECT_TAG, this.onTreeNodeClick);
|
||||
}
|
||||
|
||||
onMessageCallback = (noticeData) => {
|
||||
@ -324,6 +325,7 @@ class LibContentView extends React.Component {
|
||||
this.unsubscribeEvent();
|
||||
this.unsubscribeOpenTreePanel();
|
||||
this.unsubscribeEventBus && this.unsubscribeEventBus();
|
||||
this.unsubscribeSelectSearchedTag && this.unsubscribeSelectSearchedTag();
|
||||
this.props.eventBus.dispatch(EVENT_BUS_TYPE.CURRENT_LIBRARY_CHANGED, {
|
||||
repoID: '',
|
||||
repoName: '',
|
||||
@ -2263,6 +2265,7 @@ class LibContentView extends React.Component {
|
||||
};
|
||||
|
||||
metadataStatusCallback = ({ enableMetadata, enableTags }) => {
|
||||
this.props.eventBus.dispatch(EVENT_BUS_TYPE.TAG_STATUS, enableTags);
|
||||
if (enableMetadata && enableTags) {
|
||||
this.updateUsedRepoTags();
|
||||
return;
|
||||
@ -2270,6 +2273,10 @@ class LibContentView extends React.Component {
|
||||
this.clearRepoTags();
|
||||
};
|
||||
|
||||
tagsChangedCallback = (tags) => {
|
||||
this.props.eventBus.dispatch(EVENT_BUS_TYPE.TAGS_CHANGED, tags);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { repoID } = this.props;
|
||||
let { currentRepoInfo, userPerm, isCopyMoveProgressDialogShow, isDeleteFolderDialogOpen, errorMsg,
|
||||
@ -2346,7 +2353,7 @@ class LibContentView extends React.Component {
|
||||
const detailDirent = currentDirent || currentNode?.object || null;
|
||||
return (
|
||||
<MetadataStatusProvider repoID={repoID} repoInfo={currentRepoInfo} hideMetadataView={this.hideMetadataView} statusCallback={this.metadataStatusCallback} >
|
||||
<TagsProvider repoID={repoID} currentPath={path} repoInfo={currentRepoInfo} selectTagsView={this.onTreeNodeClick} >
|
||||
<TagsProvider repoID={repoID} currentPath={path} repoInfo={currentRepoInfo} selectTagsView={this.onTreeNodeClick} tagsChangedCallback={this.tagsChangedCallback} >
|
||||
<MetadataProvider repoID={repoID} currentPath={path} repoInfo={currentRepoInfo} selectMetadataView={this.onTreeNodeClick} >
|
||||
<CollaboratorsProvider repoID={repoID}>
|
||||
<div className="main-panel-center flex-row">
|
||||
|
@ -16,7 +16,7 @@ import { getColumnOriginName } from '../../metadata/utils/column';
|
||||
// This hook provides content related to seahub interaction, such as whether to enable extended attributes, views data, etc.
|
||||
const TagsContext = React.createContext(null);
|
||||
|
||||
export const TagsProvider = ({ repoID, currentPath, selectTagsView, children, ...params }) => {
|
||||
export const TagsProvider = ({ repoID, currentPath, selectTagsView, tagsChangedCallback, children, ...params }) => {
|
||||
|
||||
const [isLoading, setLoading] = useState(true);
|
||||
const [isReloading, setReloading] = useState(false);
|
||||
@ -31,7 +31,8 @@ export const TagsProvider = ({ repoID, currentPath, selectTagsView, children, ..
|
||||
|
||||
const tagsChanged = useCallback(() => {
|
||||
setTagsData(storeRef.current.data);
|
||||
}, []);
|
||||
tagsChangedCallback && tagsChangedCallback(storeRef.current.data.rows);
|
||||
}, [tagsChangedCallback]);
|
||||
|
||||
const handleTableError = useCallback((error) => {
|
||||
toaster.danger(error.error);
|
||||
|
@ -9,6 +9,7 @@ import { checkTreeNodeHasChildNodes, getTreeChildNodes, getTreeNodeDepth, getTre
|
||||
import { getRowById } from '../../components/sf-table/utils/table';
|
||||
import { SIDEBAR_INIT_LEFT_INDENT } from '../constants/sidebar-tree';
|
||||
import { EVENT_BUS_TYPE } from '../../metadata/constants';
|
||||
import { EVENT_BUS_TYPE as COMMON_EVENT_BUS_TYPE } from '../../components/common/event-bus-type';
|
||||
|
||||
import './index.css';
|
||||
|
||||
@ -104,6 +105,21 @@ const TagsTreeView = ({ currentPath }) => {
|
||||
setKeyTreeNodeExpandedMap(getKeyTreeNodeExpandedMap());
|
||||
}, [getKeyTreeNodeExpandedMap]);
|
||||
|
||||
useEffect(() => {
|
||||
const unsubscribeUpdateSelectedTag = window.sfTagsDataContext?.eventBus?.subscribe(COMMON_EVENT_BUS_TYPE.UPDATE_SELECTED_TAG, (tagId) => {
|
||||
if (tagId) {
|
||||
const node = recordsTree.find((node) => getTreeNodeId(node) === tagId);
|
||||
const nodeKey = getTreeNodeKey(node);
|
||||
if (!nodeKey) return;
|
||||
setCurrSelectedNodeKey(nodeKey);
|
||||
}
|
||||
});
|
||||
|
||||
return () => {
|
||||
unsubscribeUpdateSelectedTag && unsubscribeUpdateSelectedTag();
|
||||
};
|
||||
}, [recordsTree]);
|
||||
|
||||
return (
|
||||
<div className="tree-view tree metadata-tree-view metadata-tree-view-tag">
|
||||
<div className="tree-node">
|
||||
|
Loading…
Reference in New Issue
Block a user