mirror of
https://github.com/haiwen/seahub.git
synced 2025-08-25 10:11:24 +00:00
feat(tag): support search tags (#7450)
This commit is contained in:
parent
89382b4efb
commit
4c15cafb72
@ -9,6 +9,7 @@ import ListTagPopover from '../popover/list-tag-popover';
|
|||||||
import ViewModes from '../../components/view-modes';
|
import ViewModes from '../../components/view-modes';
|
||||||
import SortMenu from '../../components/sort-menu';
|
import SortMenu from '../../components/sort-menu';
|
||||||
import MetadataViewToolBar from '../../metadata/components/view-toolbar';
|
import MetadataViewToolBar from '../../metadata/components/view-toolbar';
|
||||||
|
import TagsTableSearcher from '../../tag/views/all-tags/tags-table/tags-searcher';
|
||||||
import { PRIVATE_FILE_TYPE } from '../../constants';
|
import { PRIVATE_FILE_TYPE } from '../../constants';
|
||||||
|
|
||||||
const propTypes = {
|
const propTypes = {
|
||||||
@ -116,6 +117,7 @@ class DirTool extends React.Component {
|
|||||||
if (isTagView) {
|
if (isTagView) {
|
||||||
return (
|
return (
|
||||||
<div className="dir-tool">
|
<div className="dir-tool">
|
||||||
|
<TagsTableSearcher />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,97 @@
|
|||||||
|
.sf-table-searcher-container {
|
||||||
|
margin-left: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sf-table-searcher-btn {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
border-radius: 3px;
|
||||||
|
height: 22px;
|
||||||
|
padding: 0 .5rem;
|
||||||
|
transition: all .1s ease-in;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sf-table-searcher-btn:hover {
|
||||||
|
background-color: #efefef;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sf-table-searcher-btn .sf3-font-search {
|
||||||
|
display: inline-block;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sf-table-searcher-input-wrapper {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sf-table-searcher-input {
|
||||||
|
padding-left: 30px;
|
||||||
|
padding-right: 60px;
|
||||||
|
height: 30px;
|
||||||
|
width: 260px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sf-table-searcher-input-wrapper .btn-close-searcher-wrapper {
|
||||||
|
pointer-events: all;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 20px;
|
||||||
|
min-width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
right: 4px;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sf-table-searcher-input-wrapper .btn-close-searcher {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sf-table-searcher-input-wrapper .btn-close-searcher-wrapper:hover {
|
||||||
|
background-color: #efefef;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sf-table-searcher-input-wrapper .input-icon-addon.search-poll-button {
|
||||||
|
display: flex;
|
||||||
|
font-size: 12px;
|
||||||
|
height: 30px;
|
||||||
|
left: auto;
|
||||||
|
line-height: 30px;
|
||||||
|
min-width: 35px;
|
||||||
|
pointer-events: all;
|
||||||
|
right: 28px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sf-table-searcher-input-wrapper .search-upward,
|
||||||
|
.sf-table-searcher-input-wrapper .search-backward {
|
||||||
|
color: #666;
|
||||||
|
font-size: 12px;
|
||||||
|
background-color: #efefef;
|
||||||
|
display: inline-block;
|
||||||
|
height: 20px;
|
||||||
|
line-height: 20px;
|
||||||
|
width: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sf-table-searcher-input-wrapper .search-upward:hover,
|
||||||
|
.sf-table-searcher-input-wrapper .search-backward:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
background-color: #DBDBDB;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sf-table-searcher-input-wrapper .search-upward {
|
||||||
|
margin-left: 8px;
|
||||||
|
border-radius: 2px 0 0 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sf-table-searcher-input-wrapper .search-backward {
|
||||||
|
border-radius: 0 2px 2px 0;
|
||||||
|
}
|
||||||
|
|
||||||
.sf-table-wrapper {
|
.sf-table-wrapper {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
100
frontend/src/components/sf-table/searcher/index.js
Normal file
100
frontend/src/components/sf-table/searcher/index.js
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
import React, { useMemo, useState } from 'react';
|
||||||
|
import { gettext } from '../../../utils/constants';
|
||||||
|
import { KeyCodes } from '../../../constants';
|
||||||
|
import { isModG, isModShiftG } from '../../../metadata/utils/hotkey';
|
||||||
|
import SFTableSearcherInput from './searcher-input';
|
||||||
|
import { checkHasSearchResult } from '../utils/search';
|
||||||
|
|
||||||
|
const SFTableSearcher = ({ recordsCount, columnsCount, searchResult, searchCells, closeSearcher, focusNextMatchedCell, focusPreviousMatchedCell }) => {
|
||||||
|
const [isSearchActive, setIsSearchActive] = useState(false);
|
||||||
|
const [hasSearchValue, setHasSearchValue] = useState(false);
|
||||||
|
|
||||||
|
const hasSearchResult = useMemo(() => {
|
||||||
|
return checkHasSearchResult(searchResult);
|
||||||
|
}, [searchResult]);
|
||||||
|
|
||||||
|
const onToggleSearch = () => {
|
||||||
|
setIsSearchActive(!isSearchActive);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCloseSearcher = () => {
|
||||||
|
setIsSearchActive(false);
|
||||||
|
closeSearcher && closeSearcher();
|
||||||
|
};
|
||||||
|
|
||||||
|
const onKeyDown = (e) => {
|
||||||
|
const isEmptySearchResult = !hasSearchResult;
|
||||||
|
if (e.keyCode === KeyCodes.Escape) {
|
||||||
|
e.preventDefault();
|
||||||
|
handleCloseSearcher();
|
||||||
|
} else if (isModG(e)) {
|
||||||
|
e.preventDefault();
|
||||||
|
if (isEmptySearchResult) return;
|
||||||
|
focusNextMatchedCell && focusNextMatchedCell();
|
||||||
|
} else if (isModShiftG(e)) {
|
||||||
|
e.preventDefault();
|
||||||
|
if (isEmptySearchResult) return;
|
||||||
|
focusPreviousMatchedCell && focusPreviousMatchedCell();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderSearchPollButton = () => {
|
||||||
|
return (
|
||||||
|
<span className="input-icon-addon search-poll-button">
|
||||||
|
{hasSearchValue &&
|
||||||
|
<span className="search-description">
|
||||||
|
{hasSearchResult ?
|
||||||
|
(searchResult.currentSelectIndex + 1 + ' of ' + searchResult.matchedCells.length) : '0 of 0'
|
||||||
|
}
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
{hasSearchResult &&
|
||||||
|
<>
|
||||||
|
<i className="sf3-font sf3-font-down rotate-180 search-upward"
|
||||||
|
onClick={focusPreviousMatchedCell ? focusPreviousMatchedCell : () => {}}>
|
||||||
|
</i>
|
||||||
|
<i className="sf3-font sf3-font-down search-backward"
|
||||||
|
onClick={focusNextMatchedCell ? focusNextMatchedCell : () => {}}>
|
||||||
|
</i>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="sf-table-searcher-container">
|
||||||
|
{!isSearchActive && (
|
||||||
|
<span
|
||||||
|
className='sf-table-searcher-btn'
|
||||||
|
onClick={onToggleSearch}
|
||||||
|
onKeyDown={onToggleSearch}
|
||||||
|
role="button"
|
||||||
|
title={gettext('Search')}
|
||||||
|
aria-label={gettext('Search')}
|
||||||
|
tabIndex={0}
|
||||||
|
>
|
||||||
|
<i className='active-search m-0 sf3-font sf3-font-search'></i>
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
{isSearchActive && (
|
||||||
|
<div className='sf-table-searcher-input-wrapper'>
|
||||||
|
<i className='input-icon-addon sf3-font sf3-font-search' />
|
||||||
|
<SFTableSearcherInput
|
||||||
|
recordsCount={recordsCount}
|
||||||
|
columnsCount={columnsCount}
|
||||||
|
onKeyDown={onKeyDown}
|
||||||
|
setHasSearchValue={setHasSearchValue}
|
||||||
|
searchCells={searchCells}
|
||||||
|
/>
|
||||||
|
{renderSearchPollButton()}
|
||||||
|
<span className="btn-close-searcher-wrapper input-icon-addon" onClick={handleCloseSearcher}>
|
||||||
|
<i className='btn-close-searcher sf3-font sf3-font-x-01'></i>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SFTableSearcher;
|
59
frontend/src/components/sf-table/searcher/searcher-input.js
Normal file
59
frontend/src/components/sf-table/searcher/searcher-input.js
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
import React, { useRef, useState } from 'react';
|
||||||
|
import { gettext } from '../../../utils/constants';
|
||||||
|
|
||||||
|
const SFTableSearcherInput = ({ recordsCount, columnsCount, setHasSearchValue, searchCells, onKeyDown }) => {
|
||||||
|
const [searchValue, setSearchValue] = useState('');
|
||||||
|
|
||||||
|
const isInputtingChinese = useRef(false);
|
||||||
|
const inputTimer = useRef(null);
|
||||||
|
|
||||||
|
const getSearchDelayTime = () => {
|
||||||
|
const viewCellsCount = (recordsCount || 0) * (columnsCount || 0);
|
||||||
|
let delayTime = viewCellsCount * 0.1;
|
||||||
|
delayTime = delayTime > 500 ? 500 : Math.floor(delayTime);
|
||||||
|
if (delayTime < 100) {
|
||||||
|
delayTime = 100;
|
||||||
|
}
|
||||||
|
return delayTime;
|
||||||
|
};
|
||||||
|
|
||||||
|
const onChangeSearchValue = (e) => {
|
||||||
|
inputTimer.current && clearTimeout(inputTimer.current);
|
||||||
|
const text = e.target.value;
|
||||||
|
const wait = getSearchDelayTime();
|
||||||
|
const currSearchValue = text || '';
|
||||||
|
const trimmedSearchValue = currSearchValue.trim();
|
||||||
|
setSearchValue(currSearchValue);
|
||||||
|
setHasSearchValue(!!trimmedSearchValue);
|
||||||
|
if (!isInputtingChinese.current) {
|
||||||
|
inputTimer.current = setTimeout(() => {
|
||||||
|
searchCells && searchCells(trimmedSearchValue);
|
||||||
|
}, wait);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onCompositionStart = () => {
|
||||||
|
isInputtingChinese.current = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const onCompositionEnd = (e) => {
|
||||||
|
isInputtingChinese.current = false;
|
||||||
|
onChangeSearchValue(e);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<input
|
||||||
|
className='sf-table-searcher-input form-control'
|
||||||
|
type='text'
|
||||||
|
autoFocus
|
||||||
|
value={searchValue}
|
||||||
|
onChange={onChangeSearchValue}
|
||||||
|
placeholder={gettext('Search')}
|
||||||
|
onKeyDown={onKeyDown}
|
||||||
|
onCompositionStart={onCompositionStart}
|
||||||
|
onCompositionEnd={onCompositionEnd}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SFTableSearcherInput;
|
@ -82,15 +82,15 @@ class Record extends React.Component {
|
|||||||
getFrozenCells = () => {
|
getFrozenCells = () => {
|
||||||
const {
|
const {
|
||||||
columns, sequenceColumnWidth, lastFrozenColumnKey, groupRecordIndex, index: recordIndex, record,
|
columns, sequenceColumnWidth, lastFrozenColumnKey, groupRecordIndex, index: recordIndex, record,
|
||||||
cellMetaData, isGroupView, height, columnColor
|
cellMetaData, isGroupView, height, columnColor, treeNodeKey,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const frozenColumns = getFrozenColumns(columns);
|
const frozenColumns = getFrozenColumns(columns);
|
||||||
if (frozenColumns.length === 0) return null;
|
if (frozenColumns.length === 0) return null;
|
||||||
const recordId = record._id;
|
const recordId = record._id;
|
||||||
return frozenColumns.map((column, index) => {
|
return frozenColumns.map((column, index) => {
|
||||||
const { key } = column;
|
const { key } = column;
|
||||||
const isCellHighlight = this.checkIsCellHighlight(key, recordId);
|
const isCellHighlight = this.checkIsCellHighlight(key, recordId, treeNodeKey);
|
||||||
const isCurrentCellHighlight = this.checkIsCurrentCellHighlight(key, recordId);
|
const isCurrentCellHighlight = this.checkIsCurrentCellHighlight(key, recordId, treeNodeKey);
|
||||||
const highlightClassName = isCurrentCellHighlight ? 'cell-current-highlight' : isCellHighlight ? 'cell-highlight' : null;
|
const highlightClassName = isCurrentCellHighlight ? 'cell-current-highlight' : isCellHighlight ? 'cell-highlight' : null;
|
||||||
const isCellSelected = this.checkIsCellSelected(index);
|
const isCellSelected = this.checkIsCellSelected(index);
|
||||||
const isLastCell = this.checkIsLastCell(columns, key);
|
const isLastCell = this.checkIsLastCell(columns, key);
|
||||||
@ -126,10 +126,10 @@ class Record extends React.Component {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
checkIsCellHighlight = (columnKey, rowId) => {
|
checkIsCellHighlight = (columnKey, rowId, treeNodeKey) => {
|
||||||
const { searchResult } = this.props;
|
const { searchResult } = this.props;
|
||||||
if (searchResult) {
|
if (searchResult) {
|
||||||
const matchedColumns = searchResult.matchedRows[rowId];
|
const matchedColumns = this.props.showRecordAsTree ? searchResult.matchedRows[treeNodeKey] : searchResult.matchedRows[rowId];
|
||||||
if (matchedColumns && matchedColumns.includes(columnKey)) {
|
if (matchedColumns && matchedColumns.includes(columnKey)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -137,14 +137,15 @@ class Record extends React.Component {
|
|||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
checkIsCurrentCellHighlight = (columnKey, rowId) => {
|
checkIsCurrentCellHighlight = (columnKey, rowId, treeNodeKey) => {
|
||||||
const { searchResult } = this.props;
|
const { searchResult } = this.props;
|
||||||
if (searchResult) {
|
if (searchResult) {
|
||||||
const { currentSelectIndex } = searchResult;
|
const { currentSelectIndex } = searchResult;
|
||||||
if (typeof(currentSelectIndex) !== 'number') return false;
|
if (typeof(currentSelectIndex) !== 'number') return false;
|
||||||
const currentSelectCell = searchResult.matchedCells[currentSelectIndex];
|
const currentSelectCell = searchResult.matchedCells[currentSelectIndex];
|
||||||
if (!currentSelectCell) return false;
|
if (!currentSelectCell) return false;
|
||||||
if (currentSelectCell.row === rowId && currentSelectCell.column === columnKey) return true;
|
const isCurrentRow = this.props.showRecordAsTree ? currentSelectCell.nodeKey === treeNodeKey : currentSelectCell.row === rowId;
|
||||||
|
return isCurrentRow && currentSelectCell.column === columnKey;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
@ -152,7 +153,7 @@ class Record extends React.Component {
|
|||||||
getColumnCells = () => {
|
getColumnCells = () => {
|
||||||
const {
|
const {
|
||||||
columns, sequenceColumnWidth, colOverScanStartIdx, colOverScanEndIdx, groupRecordIndex, index: recordIndex,
|
columns, sequenceColumnWidth, colOverScanStartIdx, colOverScanEndIdx, groupRecordIndex, index: recordIndex,
|
||||||
record, cellMetaData, isGroupView, height, columnColor,
|
record, cellMetaData, isGroupView, height, columnColor, treeNodeKey,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const recordId = record._id;
|
const recordId = record._id;
|
||||||
const rendererColumns = columns.slice(colOverScanStartIdx, colOverScanEndIdx);
|
const rendererColumns = columns.slice(colOverScanStartIdx, colOverScanEndIdx);
|
||||||
@ -160,8 +161,8 @@ class Record extends React.Component {
|
|||||||
const { key, frozen } = column;
|
const { key, frozen } = column;
|
||||||
const needBindEvents = !frozen;
|
const needBindEvents = !frozen;
|
||||||
const isCellSelected = this.checkIsCellSelected(columns.findIndex(col => col.key === column.key));
|
const isCellSelected = this.checkIsCellSelected(columns.findIndex(col => col.key === column.key));
|
||||||
const isCellHighlight = this.checkIsCellHighlight(key, recordId);
|
const isCellHighlight = this.checkIsCellHighlight(key, recordId, treeNodeKey);
|
||||||
const isCurrentCellHighlight = this.checkIsCurrentCellHighlight(key, recordId);
|
const isCurrentCellHighlight = this.checkIsCurrentCellHighlight(key, recordId, treeNodeKey);
|
||||||
const highlightClassName = isCurrentCellHighlight ? 'cell-current-highlight' : isCellHighlight ? 'cell-highlight' : null;
|
const highlightClassName = isCurrentCellHighlight ? 'cell-current-highlight' : isCellHighlight ? 'cell-highlight' : null;
|
||||||
const isLastCell = this.checkIsLastCell(columns, key);
|
const isLastCell = this.checkIsLastCell(columns, key);
|
||||||
const bgColor = columnColor && columnColor[key];
|
const bgColor = columnColor && columnColor[key];
|
||||||
|
@ -13,6 +13,7 @@ import { checkEditableViaClickCell, checkIsColumnSupportDirectEdit, getColumnByI
|
|||||||
import { checkIsCellSupportOpenEditor } from '../../utils/selected-cell-utils';
|
import { checkIsCellSupportOpenEditor } from '../../utils/selected-cell-utils';
|
||||||
import { LOCAL_KEY_TREE_NODE_FOLDED } from '../../constants/tree';
|
import { LOCAL_KEY_TREE_NODE_FOLDED } from '../../constants/tree';
|
||||||
import { TreeMetrics } from '../../utils/tree-metrics';
|
import { TreeMetrics } from '../../utils/tree-metrics';
|
||||||
|
import { checkHasSearchResult } from '../../utils/search';
|
||||||
|
|
||||||
const ROW_HEIGHT = 33;
|
const ROW_HEIGHT = 33;
|
||||||
const RENDER_MORE_NUMBER = 10;
|
const RENDER_MORE_NUMBER = 10;
|
||||||
@ -36,6 +37,7 @@ class TreeBody extends Component {
|
|||||||
startRenderIndex: 0,
|
startRenderIndex: 0,
|
||||||
endRenderIndex: this.getInitEndIndex(nodes),
|
endRenderIndex: this.getInitEndIndex(nodes),
|
||||||
keyNodeFoldedMap: validKeyTreeNodeFoldedMap,
|
keyNodeFoldedMap: validKeyTreeNodeFoldedMap,
|
||||||
|
keyNodeFoldedMapForSearch: {},
|
||||||
selectedPosition: null,
|
selectedPosition: null,
|
||||||
isScrollingRightScrollbar: false,
|
isScrollingRightScrollbar: false,
|
||||||
};
|
};
|
||||||
@ -58,12 +60,19 @@ class TreeBody extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
UNSAFE_componentWillReceiveProps(nextProps) {
|
UNSAFE_componentWillReceiveProps(nextProps) {
|
||||||
const { recordsCount, recordIds, treeNodesCount, recordsTree } = nextProps;
|
const { recordsCount, recordIds, treeNodesCount, recordsTree, searchResult } = nextProps;
|
||||||
|
const searchResultChanged = searchResult !== this.props.searchResult;
|
||||||
if (
|
if (
|
||||||
recordsCount !== this.props.recordsCount || recordIds !== this.props.recordIds ||
|
recordsCount !== this.props.recordsCount || recordIds !== this.props.recordIds ||
|
||||||
treeNodesCount !== this.props.treeNodesCount || recordsTree !== this.props.recordsTree
|
treeNodesCount !== this.props.treeNodesCount || recordsTree !== this.props.recordsTree ||
|
||||||
|
searchResultChanged
|
||||||
) {
|
) {
|
||||||
this.recalculateRenderIndex(recordsTree);
|
const hasSearchResult = checkHasSearchResult(searchResult);
|
||||||
|
const keyNodeFoldedMap = hasSearchResult ? {} : this.state.keyNodeFoldedMap;
|
||||||
|
this.recalculateRenderIndex(recordsTree, keyNodeFoldedMap);
|
||||||
|
if (searchResultChanged) {
|
||||||
|
this.setState({ keyNodeFoldedMapForSearch: {} });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -119,8 +128,8 @@ class TreeBody extends Component {
|
|||||||
return Math.min(Math.ceil((contentScrollTop + height) / ROW_HEIGHT) + RENDER_MORE_NUMBER, nodes.length);
|
return Math.min(Math.ceil((contentScrollTop + height) / ROW_HEIGHT) + RENDER_MORE_NUMBER, nodes.length);
|
||||||
};
|
};
|
||||||
|
|
||||||
recalculateRenderIndex = (recordsTree) => {
|
recalculateRenderIndex = (recordsTree, keyNodeFoldedMap) => {
|
||||||
const { startRenderIndex, endRenderIndex, keyNodeFoldedMap } = this.state;
|
const { startRenderIndex, endRenderIndex } = this.state;
|
||||||
const nodes = this.getShownNodes(recordsTree, keyNodeFoldedMap);
|
const nodes = this.getShownNodes(recordsTree, keyNodeFoldedMap);
|
||||||
const contentScrollTop = this.resultContentRef.scrollTop;
|
const contentScrollTop = this.resultContentRef.scrollTop;
|
||||||
const start = Math.max(0, Math.floor(contentScrollTop / ROW_HEIGHT) - RENDER_MORE_NUMBER);
|
const start = Math.max(0, Math.floor(contentScrollTop / ROW_HEIGHT) - RENDER_MORE_NUMBER);
|
||||||
@ -339,6 +348,14 @@ class TreeBody extends Component {
|
|||||||
this.columnVisibleEnd = columnVisibleEnd;
|
this.columnVisibleEnd = columnVisibleEnd;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
jumpToRow = (scrollToRowIndex) => {
|
||||||
|
const { treeNodesCount } = this.props;
|
||||||
|
const rowHeight = this.getRowHeight();
|
||||||
|
const height = this.resultContentRef.offsetHeight;
|
||||||
|
const scrollTop = Math.min(scrollToRowIndex * rowHeight, treeNodesCount * rowHeight - height);
|
||||||
|
this.setScrollTop(scrollTop);
|
||||||
|
};
|
||||||
|
|
||||||
scrollToColumn = (idx) => {
|
scrollToColumn = (idx) => {
|
||||||
const { columns, getTableContentRect } = this.props;
|
const { columns, getTableContentRect } = this.props;
|
||||||
const { width: tableContentWidth } = getTableContentRect();
|
const { width: tableContentWidth } = getTableContentRect();
|
||||||
@ -496,22 +513,41 @@ class TreeBody extends Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
toggleExpandNode = (nodeKey) => {
|
toggleExpandNode = (nodeKey) => {
|
||||||
const { recordsTree } = this.props;
|
const { recordsTree, searchResult } = this.props;
|
||||||
const { keyNodeFoldedMap, endRenderIndex } = this.state;
|
const { keyNodeFoldedMap, keyNodeFoldedMapForSearch, endRenderIndex } = this.state;
|
||||||
|
const hasSearchResult = checkHasSearchResult(searchResult);
|
||||||
let updatedKeyNodeFoldedMap = { ...keyNodeFoldedMap };
|
let updatedKeyNodeFoldedMap = { ...keyNodeFoldedMap };
|
||||||
|
let updatedKeyNodeFoldedMapForSearch = { ...keyNodeFoldedMapForSearch };
|
||||||
|
if (hasSearchResult) {
|
||||||
|
if (updatedKeyNodeFoldedMapForSearch[nodeKey]) {
|
||||||
|
delete updatedKeyNodeFoldedMapForSearch[nodeKey];
|
||||||
|
delete updatedKeyNodeFoldedMap[nodeKey];
|
||||||
|
} else {
|
||||||
|
updatedKeyNodeFoldedMapForSearch[nodeKey] = true;
|
||||||
|
updatedKeyNodeFoldedMap[nodeKey] = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
if (updatedKeyNodeFoldedMap[nodeKey]) {
|
if (updatedKeyNodeFoldedMap[nodeKey]) {
|
||||||
delete updatedKeyNodeFoldedMap[nodeKey];
|
delete updatedKeyNodeFoldedMap[nodeKey];
|
||||||
} else {
|
} else {
|
||||||
updatedKeyNodeFoldedMap[nodeKey] = true;
|
updatedKeyNodeFoldedMap[nodeKey] = true;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (this.props.storeFoldedTreeNodes) {
|
if (this.props.storeFoldedTreeNodes) {
|
||||||
// store folded status
|
// store folded status
|
||||||
this.props.storeFoldedTreeNodes(LOCAL_KEY_TREE_NODE_FOLDED, updatedKeyNodeFoldedMap);
|
this.props.storeFoldedTreeNodes(LOCAL_KEY_TREE_NODE_FOLDED, updatedKeyNodeFoldedMap);
|
||||||
}
|
}
|
||||||
|
|
||||||
const updatedNodes = this.getShownNodes(recordsTree, updatedKeyNodeFoldedMap);
|
const updatedNodes = this.getShownNodes(recordsTree, hasSearchResult ? updatedKeyNodeFoldedMapForSearch : updatedKeyNodeFoldedMap);
|
||||||
let updates = { nodes: updatedNodes, keyNodeFoldedMap: updatedKeyNodeFoldedMap };
|
let updates = {
|
||||||
|
nodes: updatedNodes,
|
||||||
|
keyNodeFoldedMap: updatedKeyNodeFoldedMap,
|
||||||
|
};
|
||||||
|
if (hasSearchResult) {
|
||||||
|
updates.keyNodeFoldedMapForSearch = updatedKeyNodeFoldedMapForSearch;
|
||||||
|
}
|
||||||
|
|
||||||
const end = this.recalculateRenderEndIndex(updatedNodes);
|
const end = this.recalculateRenderEndIndex(updatedNodes);
|
||||||
if (end !== endRenderIndex) {
|
if (end !== endRenderIndex) {
|
||||||
updates.endRenderIndex = end;
|
updates.endRenderIndex = end;
|
||||||
@ -525,8 +561,8 @@ class TreeBody extends Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
renderRecords = () => {
|
renderRecords = () => {
|
||||||
const { treeMetrics, showCellColoring, columnColors } = this.props;
|
const { treeMetrics, showCellColoring, columnColors, searchResult } = this.props;
|
||||||
const { nodes, keyNodeFoldedMap, startRenderIndex, endRenderIndex, selectedPosition } = this.state;
|
const { nodes, keyNodeFoldedMap, keyNodeFoldedMapForSearch, startRenderIndex, endRenderIndex, selectedPosition } = this.state;
|
||||||
this.initFrozenNodesRef();
|
this.initFrozenNodesRef();
|
||||||
const visibleNodes = this.getVisibleNodesInRange();
|
const visibleNodes = this.getVisibleNodesInRange();
|
||||||
const nodesCount = nodes.length;
|
const nodesCount = nodes.length;
|
||||||
@ -534,6 +570,7 @@ class TreeBody extends Component {
|
|||||||
const scrollLeft = this.props.getScrollLeft();
|
const scrollLeft = this.props.getScrollLeft();
|
||||||
const rowHeight = this.getRowHeight();
|
const rowHeight = this.getRowHeight();
|
||||||
const cellMetaData = this.getCellMetaData();
|
const cellMetaData = this.getCellMetaData();
|
||||||
|
const hasSearchResult = checkHasSearchResult(searchResult);
|
||||||
let shownNodes = visibleNodes.map((node, index) => {
|
let shownNodes = visibleNodes.map((node, index) => {
|
||||||
const { _id: recordId, node_key, node_depth, node_index } = node;
|
const { _id: recordId, node_key, node_depth, node_index } = node;
|
||||||
const hasChildNodes = checkTreeNodeHasChildNodes(node);
|
const hasChildNodes = checkTreeNodeHasChildNodes(node);
|
||||||
@ -543,7 +580,7 @@ class TreeBody extends Component {
|
|||||||
const isLastRecord = lastRecordIndex === recordIndex;
|
const isLastRecord = lastRecordIndex === recordIndex;
|
||||||
const hasSelectedCell = this.props.hasSelectedCell({ recordIndex }, selectedPosition);
|
const hasSelectedCell = this.props.hasSelectedCell({ recordIndex }, selectedPosition);
|
||||||
const columnColor = showCellColoring ? columnColors[recordId] : {};
|
const columnColor = showCellColoring ? columnColors[recordId] : {};
|
||||||
const isFoldedNode = !!keyNodeFoldedMap[node_key];
|
const isFoldedNode = hasSearchResult ? !!keyNodeFoldedMapForSearch[node_key] : !!keyNodeFoldedMap[node_key];
|
||||||
return (
|
return (
|
||||||
<Record
|
<Record
|
||||||
showRecordAsTree
|
showRecordAsTree
|
||||||
@ -565,7 +602,7 @@ class TreeBody extends Component {
|
|||||||
height={rowHeight}
|
height={rowHeight}
|
||||||
cellMetaData={cellMetaData}
|
cellMetaData={cellMetaData}
|
||||||
columnColor={columnColor}
|
columnColor={columnColor}
|
||||||
searchResult={this.props.searchResult}
|
searchResult={searchResult}
|
||||||
treeNodeIndex={node_index}
|
treeNodeIndex={node_index}
|
||||||
treeNodeKey={node_key}
|
treeNodeKey={node_key}
|
||||||
treeNodeDepth={node_depth}
|
treeNodeDepth={node_depth}
|
||||||
|
22
frontend/src/components/sf-table/utils/search.js
Normal file
22
frontend/src/components/sf-table/utils/search.js
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
const escapeRegExp = (value) => {
|
||||||
|
if (typeof value !== 'string') return '';
|
||||||
|
return value.replace(/[.\\[\]{}()|^$?*+]/g, '\\$&');
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getSearchRule = (value) => {
|
||||||
|
if (typeof value !== 'string') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let searchRule = value;
|
||||||
|
searchRule = searchRule.trim();
|
||||||
|
if (searchRule.length === 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// i: search value uppercase and lowercase are not sensitive
|
||||||
|
return new RegExp(escapeRegExp(searchRule), 'i');
|
||||||
|
};
|
||||||
|
|
||||||
|
export const checkHasSearchResult = (searchResult) => {
|
||||||
|
const { matchedCells } = searchResult || {};
|
||||||
|
return Array.isArray(matchedCells) ? matchedCells.length > 0 : false;
|
||||||
|
};
|
@ -41,6 +41,7 @@ export const EVENT_BUS_TYPE = {
|
|||||||
|
|
||||||
// metadata
|
// metadata
|
||||||
RELOAD_DATA: 'reload_data',
|
RELOAD_DATA: 'reload_data',
|
||||||
|
UPDATE_SEARCH_RESULT: 'update_search_result',
|
||||||
|
|
||||||
// view
|
// view
|
||||||
MODIFY_FILTERS: 'modify_filters',
|
MODIFY_FILTERS: 'modify_filters',
|
||||||
|
@ -31,3 +31,7 @@ export const EDITABLE_PRIVATE_COLUMN_KEYS = [
|
|||||||
PRIVATE_COLUMN_KEY.TAG_COLOR,
|
PRIVATE_COLUMN_KEY.TAG_COLOR,
|
||||||
PRIVATE_COLUMN_KEY.TAG_FILE_LINKS,
|
PRIVATE_COLUMN_KEY.TAG_FILE_LINKS,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export const VISIBLE_COLUMNS_KEYS = [
|
||||||
|
PRIVATE_COLUMN_KEY.TAG_NAME, PRIVATE_COLUMN_KEY.PARENT_LINKS, PRIVATE_COLUMN_KEY.SUB_LINKS, PRIVATE_COLUMN_KEY.TAG_FILE_LINKS,
|
||||||
|
];
|
||||||
|
@ -30,6 +30,11 @@
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sf-metadata-all-tags-wrapper .sf-table-row .name-cell.cell-selected {
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
.sf-table-cell.cell-selected .sf-table-tag-name-formatter .sf-table-tag-name:hover {
|
.sf-table-cell.cell-selected .sf-table-tag-name-formatter .sf-table-tag-name:hover {
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React, { useCallback, useMemo, useRef, useState } from 'react';
|
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import SFTable from '../../../../components/sf-table';
|
import SFTable from '../../../../components/sf-table';
|
||||||
import EditTagDialog from '../../../components/dialog/edit-tag-dialog';
|
import EditTagDialog from '../../../components/dialog/edit-tag-dialog';
|
||||||
@ -6,11 +6,13 @@ import MergeTagsSelector from '../../../components/merge-tags-selector';
|
|||||||
import { createTableColumns } from './columns-factory';
|
import { createTableColumns } from './columns-factory';
|
||||||
import { createContextMenuOptions } from './context-menu-options';
|
import { createContextMenuOptions } from './context-menu-options';
|
||||||
import { gettext } from '../../../../utils/constants';
|
import { gettext } from '../../../../utils/constants';
|
||||||
import { PRIVATE_COLUMN_KEY } from '../../../constants';
|
import { PRIVATE_COLUMN_KEY, VISIBLE_COLUMNS_KEYS } from '../../../constants';
|
||||||
import { useTags } from '../../../hooks';
|
import { useTags } from '../../../hooks';
|
||||||
import EventBus from '../../../../components/common/event-bus';
|
import EventBus from '../../../../components/common/event-bus';
|
||||||
import { EVENT_BUS_TYPE } from '../../../../components/sf-table/constants/event-bus-type';
|
import { EVENT_BUS_TYPE } from '../../../../metadata/constants';
|
||||||
|
import { EVENT_BUS_TYPE as TABLE_EVENT_BUS_TYPE } from '../../../../components/sf-table/constants/event-bus-type';
|
||||||
import { LOCAL_KEY_TREE_NODE_FOLDED } from '../../../../components/sf-table/constants/tree';
|
import { LOCAL_KEY_TREE_NODE_FOLDED } from '../../../../components/sf-table/constants/tree';
|
||||||
|
import { isNumber } from '../../../../utils/number';
|
||||||
|
|
||||||
import './index.css';
|
import './index.css';
|
||||||
|
|
||||||
@ -24,10 +26,6 @@ const DEFAULT_TABLE_DATA = {
|
|||||||
|
|
||||||
const KEY_STORE_SCROLL = 'table_scroll';
|
const KEY_STORE_SCROLL = 'table_scroll';
|
||||||
|
|
||||||
const VISIBLE_COLUMNS_KEYS = [
|
|
||||||
PRIVATE_COLUMN_KEY.TAG_NAME, PRIVATE_COLUMN_KEY.PARENT_LINKS, PRIVATE_COLUMN_KEY.SUB_LINKS, PRIVATE_COLUMN_KEY.TAG_FILE_LINKS,
|
|
||||||
];
|
|
||||||
|
|
||||||
const TagsTable = ({
|
const TagsTable = ({
|
||||||
context,
|
context,
|
||||||
isLoadingMoreRecords,
|
isLoadingMoreRecords,
|
||||||
@ -40,6 +38,7 @@ const TagsTable = ({
|
|||||||
|
|
||||||
const [isShowNewSubTagDialog, setIsShowNewSubTagDialog] = useState(false);
|
const [isShowNewSubTagDialog, setIsShowNewSubTagDialog] = useState(false);
|
||||||
const [isShowMergeTagsSelector, setIsShowMergeTagsSelector] = useState(false);
|
const [isShowMergeTagsSelector, setIsShowMergeTagsSelector] = useState(false);
|
||||||
|
const [searchResult, setSearchResult] = useState(null);
|
||||||
|
|
||||||
const parentTagIdRef = useRef(null);
|
const parentTagIdRef = useRef(null);
|
||||||
const mergeTagsSelectorProps = useRef({});
|
const mergeTagsSelectorProps = useRef({});
|
||||||
@ -108,7 +107,7 @@ const TagsTable = ({
|
|||||||
deleteTags(tagsIds);
|
deleteTags(tagsIds);
|
||||||
|
|
||||||
const eventBus = EventBus.getInstance();
|
const eventBus = EventBus.getInstance();
|
||||||
eventBus.dispatch(EVENT_BUS_TYPE.SELECT_NONE);
|
eventBus.dispatch(TABLE_EVENT_BUS_TYPE.SELECT_NONE);
|
||||||
}, [deleteTags]);
|
}, [deleteTags]);
|
||||||
|
|
||||||
const onNewSubTag = useCallback((parentTagId) => {
|
const onNewSubTag = useCallback((parentTagId) => {
|
||||||
@ -179,6 +178,44 @@ const TagsTable = ({
|
|||||||
return false;
|
return false;
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const scrollToCurrentSelectedCell = useCallback((searchResult, currentSelectIndex) => {
|
||||||
|
if (!window.sfTableBody) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const cell = searchResult.matchedCells[currentSelectIndex];
|
||||||
|
if (!cell) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const { column: cellColumn, rowIndex: focusRowIndex } = cell;
|
||||||
|
const { rowVisibleStart, rowVisibleEnd, columnVisibleStart, columnVisibleEnd } = window.sfTableBody;
|
||||||
|
if (focusRowIndex < rowVisibleStart) {
|
||||||
|
window.sfTableBody.jumpToRow(focusRowIndex - 1);
|
||||||
|
} else if (focusRowIndex >= rowVisibleEnd) {
|
||||||
|
window.sfTableBody.jumpToRow(focusRowIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
const focusColumnIndex = visibleColumns.findIndex((column) => column.key === cellColumn);
|
||||||
|
if (columnVisibleStart >= focusColumnIndex || focusColumnIndex > columnVisibleEnd) {
|
||||||
|
window.sfTableBody.scrollToColumn(focusColumnIndex);
|
||||||
|
}
|
||||||
|
}, [visibleColumns]);
|
||||||
|
|
||||||
|
const updateSearchResult = useCallback((searchResult) => {
|
||||||
|
setSearchResult(searchResult);
|
||||||
|
const { currentSelectIndex } = searchResult || {};
|
||||||
|
if (searchResult && isNumber(currentSelectIndex)) {
|
||||||
|
scrollToCurrentSelectedCell(searchResult, currentSelectIndex);
|
||||||
|
}
|
||||||
|
}, [scrollToCurrentSelectedCell]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const eventBus = EventBus.getInstance();
|
||||||
|
const unsubscribeUpdateSearchResult = eventBus.subscribe(EVENT_BUS_TYPE.UPDATE_SEARCH_RESULT, updateSearchResult);
|
||||||
|
return () => {
|
||||||
|
unsubscribeUpdateSearchResult();
|
||||||
|
};
|
||||||
|
}, [updateSearchResult]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<SFTable
|
<SFTable
|
||||||
@ -196,6 +233,7 @@ const TagsTable = ({
|
|||||||
isLoadingMoreRecords={isLoadingMoreRecords}
|
isLoadingMoreRecords={isLoadingMoreRecords}
|
||||||
hasMoreRecords={table.hasMore}
|
hasMoreRecords={table.hasMore}
|
||||||
showGridFooter={false}
|
showGridFooter={false}
|
||||||
|
searchResult={searchResult}
|
||||||
createContextMenuOptions={createTagContextMenuOptions}
|
createContextMenuOptions={createTagContextMenuOptions}
|
||||||
storeGridScroll={storeGridScroll}
|
storeGridScroll={storeGridScroll}
|
||||||
storeFoldedGroups={storeFoldedGroups}
|
storeFoldedGroups={storeFoldedGroups}
|
||||||
|
133
frontend/src/tag/views/all-tags/tags-table/tags-searcher.js
Normal file
133
frontend/src/tag/views/all-tags/tags-table/tags-searcher.js
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
import React, { useCallback, useMemo, useState } from 'react';
|
||||||
|
import SFTableSearcher from '../../../../components/sf-table/searcher';
|
||||||
|
import { PRIVATE_COLUMN_KEY, VISIBLE_COLUMNS_KEYS } from '../../../constants';
|
||||||
|
import { useTags } from '../../../hooks';
|
||||||
|
import { getSearchRule } from '../../../../components/sf-table/utils/search';
|
||||||
|
import { getTreeNodeId, getTreeNodeKey } from '../../../../components/sf-table/utils/tree';
|
||||||
|
import { getRowById } from '../../../../components/sf-table/utils/table';
|
||||||
|
import EventBus from '../../../../components/common/event-bus';
|
||||||
|
import { EVENT_BUS_TYPE } from '../../../../metadata/constants';
|
||||||
|
|
||||||
|
const SUPPORT_SEARCH_COLUMNS_KEYS = [PRIVATE_COLUMN_KEY.TAG_NAME];
|
||||||
|
|
||||||
|
const TagsTableSearcher = () => {
|
||||||
|
const { tagsData } = useTags();
|
||||||
|
const [searchResult, setSearchResult] = useState(null);
|
||||||
|
|
||||||
|
const recordsTree = useMemo(() => {
|
||||||
|
return tagsData.rows_tree || [];
|
||||||
|
}, [tagsData]);
|
||||||
|
|
||||||
|
const recordsCount = useMemo(() => {
|
||||||
|
return recordsTree.length;
|
||||||
|
}, [recordsTree]);
|
||||||
|
|
||||||
|
const getTagStrContentByNode = useCallback((tagNode) => {
|
||||||
|
const tagId = getTreeNodeId(tagNode);
|
||||||
|
const tag = getRowById(tagsData, tagId);
|
||||||
|
if (!tag) return null;
|
||||||
|
let strContent = {};
|
||||||
|
VISIBLE_COLUMNS_KEYS.forEach((columnKey) => {
|
||||||
|
switch (columnKey) {
|
||||||
|
case PRIVATE_COLUMN_KEY.TAG_NAME: {
|
||||||
|
strContent[columnKey] = tag[columnKey] || '';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return strContent;
|
||||||
|
}, [tagsData]);
|
||||||
|
|
||||||
|
const checkIsCellValueMatchedRegVal = useCallback((tagContent, columnKey, regVal) => {
|
||||||
|
const cellValueDisplayString = (tagContent && tagContent[columnKey]) || '';
|
||||||
|
const isMatched = regVal.test(cellValueDisplayString);
|
||||||
|
return isMatched;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleUpdateSearchResult = useCallback((searchResult) => {
|
||||||
|
setSearchResult(searchResult);
|
||||||
|
|
||||||
|
const eventBus = EventBus.getInstance();
|
||||||
|
eventBus.dispatch(EVENT_BUS_TYPE.UPDATE_SEARCH_RESULT, searchResult);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const searchCells = useCallback((searchRegRule) => {
|
||||||
|
let searchResult = {};
|
||||||
|
let matchedRows = {};
|
||||||
|
searchResult.matchedCells = [];
|
||||||
|
searchResult.matchedRows = matchedRows;
|
||||||
|
recordsTree.forEach((tagNode, nodeIndex) => {
|
||||||
|
const nodeKey = getTreeNodeKey(tagNode);
|
||||||
|
const tagStrContent = getTagStrContentByNode(tagNode);
|
||||||
|
SUPPORT_SEARCH_COLUMNS_KEYS.forEach((columnKey) => {
|
||||||
|
const isMatched = checkIsCellValueMatchedRegVal(tagStrContent, columnKey, searchRegRule);
|
||||||
|
if (isMatched) {
|
||||||
|
if (matchedRows[nodeKey]) {
|
||||||
|
matchedRows[nodeKey].push(columnKey);
|
||||||
|
} else {
|
||||||
|
matchedRows[nodeKey] = [columnKey];
|
||||||
|
}
|
||||||
|
searchResult.matchedCells.push({ nodeKey, tagStrContent, column: columnKey, rowIndex: nodeIndex });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
searchResult.currentSelectIndex = 0;
|
||||||
|
return searchResult;
|
||||||
|
}, [recordsTree, getTagStrContentByNode, checkIsCellValueMatchedRegVal]);
|
||||||
|
|
||||||
|
|
||||||
|
const handleSearchTags = useCallback((searchVal) => {
|
||||||
|
if (searchVal.length === 0) {
|
||||||
|
handleUpdateSearchResult(null);
|
||||||
|
} else {
|
||||||
|
const searchRegRule = getSearchRule(searchVal);
|
||||||
|
const searchResult = searchRegRule ? searchCells(searchRegRule) : null;
|
||||||
|
handleUpdateSearchResult(searchResult);
|
||||||
|
}
|
||||||
|
}, [searchCells, handleUpdateSearchResult]);
|
||||||
|
|
||||||
|
const focusNextMatchedCell = useCallback(() => {
|
||||||
|
if (!searchResult) return;
|
||||||
|
const matchedCellsLength = searchResult.matchedCells.length;
|
||||||
|
let currentSelectIndex = searchResult.currentSelectIndex;
|
||||||
|
currentSelectIndex++;
|
||||||
|
if (currentSelectIndex === matchedCellsLength) {
|
||||||
|
currentSelectIndex = 0;
|
||||||
|
}
|
||||||
|
const nextSearchResult = Object.assign({}, searchResult, { currentSelectIndex: currentSelectIndex });
|
||||||
|
handleUpdateSearchResult(nextSearchResult);
|
||||||
|
}, [searchResult, handleUpdateSearchResult]);
|
||||||
|
|
||||||
|
const focusPreviousMatchedCell = useCallback(() => {
|
||||||
|
if (!searchResult) return;
|
||||||
|
const matchedCellsLength = searchResult.matchedCells.length;
|
||||||
|
let currentSelectIndex = searchResult.currentSelectIndex;
|
||||||
|
currentSelectIndex--;
|
||||||
|
if (currentSelectIndex === -1) {
|
||||||
|
currentSelectIndex = matchedCellsLength - 1;
|
||||||
|
}
|
||||||
|
const nextSearchResult = Object.assign({}, searchResult, { currentSelectIndex: currentSelectIndex });
|
||||||
|
handleUpdateSearchResult(nextSearchResult);
|
||||||
|
}, [searchResult, handleUpdateSearchResult]);
|
||||||
|
|
||||||
|
const closeSearcher = useCallback(() => {
|
||||||
|
handleUpdateSearchResult(null);
|
||||||
|
}, [handleUpdateSearchResult]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SFTableSearcher
|
||||||
|
recordsCount={recordsCount}
|
||||||
|
columnsCount={VISIBLE_COLUMNS_KEYS.length}
|
||||||
|
searchResult={searchResult}
|
||||||
|
closeSearcher={closeSearcher}
|
||||||
|
focusNextMatchedCell={focusNextMatchedCell}
|
||||||
|
focusPreviousMatchedCell={focusPreviousMatchedCell}
|
||||||
|
searchCells={handleSearchTags}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TagsTableSearcher;
|
Loading…
Reference in New Issue
Block a user