Skip to content

Commit

Permalink
feat(tag): support search tags (#7450)
Browse files Browse the repository at this point in the history
  • Loading branch information
renjie-run authored Feb 9, 2025
1 parent 89382b4 commit 4c15caf
Show file tree
Hide file tree
Showing 12 changed files with 530 additions and 34 deletions.
2 changes: 2 additions & 0 deletions frontend/src/components/cur-dir-path/dir-tool.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import ListTagPopover from '../popover/list-tag-popover';
import ViewModes from '../../components/view-modes';
import SortMenu from '../../components/sort-menu';
import MetadataViewToolBar from '../../metadata/components/view-toolbar';
import TagsTableSearcher from '../../tag/views/all-tags/tags-table/tags-searcher';
import { PRIVATE_FILE_TYPE } from '../../constants';

const propTypes = {
Expand Down Expand Up @@ -116,6 +117,7 @@ class DirTool extends React.Component {
if (isTagView) {
return (
<div className="dir-tool">
<TagsTableSearcher />
</div>
);
}
Expand Down
94 changes: 94 additions & 0 deletions frontend/src/components/sf-table/index.css
Original file line number Diff line number Diff line change
@@ -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 {
height: 100%;
width: 100%;
Expand Down
100 changes: 100 additions & 0 deletions frontend/src/components/sf-table/searcher/index.js
Original file line number Diff line number Diff line change
@@ -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 changes: 59 additions & 0 deletions frontend/src/components/sf-table/searcher/searcher-input.js
Original file line number Diff line number Diff line change
@@ -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;
21 changes: 11 additions & 10 deletions frontend/src/components/sf-table/table-main/records/record/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,15 +82,15 @@ class Record extends React.Component {
getFrozenCells = () => {
const {
columns, sequenceColumnWidth, lastFrozenColumnKey, groupRecordIndex, index: recordIndex, record,
cellMetaData, isGroupView, height, columnColor
cellMetaData, isGroupView, height, columnColor, treeNodeKey,
} = this.props;
const frozenColumns = getFrozenColumns(columns);
if (frozenColumns.length === 0) return null;
const recordId = record._id;
return frozenColumns.map((column, index) => {
const { key } = column;
const isCellHighlight = this.checkIsCellHighlight(key, recordId);
const isCurrentCellHighlight = this.checkIsCurrentCellHighlight(key, recordId);
const isCellHighlight = this.checkIsCellHighlight(key, recordId, treeNodeKey);
const isCurrentCellHighlight = this.checkIsCurrentCellHighlight(key, recordId, treeNodeKey);
const highlightClassName = isCurrentCellHighlight ? 'cell-current-highlight' : isCellHighlight ? 'cell-highlight' : null;
const isCellSelected = this.checkIsCellSelected(index);
const isLastCell = this.checkIsLastCell(columns, key);
Expand Down Expand Up @@ -126,42 +126,43 @@ class Record extends React.Component {
});
};

checkIsCellHighlight = (columnKey, rowId) => {
checkIsCellHighlight = (columnKey, rowId, treeNodeKey) => {
const { searchResult } = this.props;
if (searchResult) {
const matchedColumns = searchResult.matchedRows[rowId];
const matchedColumns = this.props.showRecordAsTree ? searchResult.matchedRows[treeNodeKey] : searchResult.matchedRows[rowId];
if (matchedColumns && matchedColumns.includes(columnKey)) {
return true;
}
}
return false;
};

checkIsCurrentCellHighlight = (columnKey, rowId) => {
checkIsCurrentCellHighlight = (columnKey, rowId, treeNodeKey) => {
const { searchResult } = this.props;
if (searchResult) {
const { currentSelectIndex } = searchResult;
if (typeof(currentSelectIndex) !== 'number') return false;
const currentSelectCell = searchResult.matchedCells[currentSelectIndex];
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;
};

getColumnCells = () => {
const {
columns, sequenceColumnWidth, colOverScanStartIdx, colOverScanEndIdx, groupRecordIndex, index: recordIndex,
record, cellMetaData, isGroupView, height, columnColor,
record, cellMetaData, isGroupView, height, columnColor, treeNodeKey,
} = this.props;
const recordId = record._id;
const rendererColumns = columns.slice(colOverScanStartIdx, colOverScanEndIdx);
return rendererColumns.map((column) => {
const { key, frozen } = column;
const needBindEvents = !frozen;
const isCellSelected = this.checkIsCellSelected(columns.findIndex(col => col.key === column.key));
const isCellHighlight = this.checkIsCellHighlight(key, recordId);
const isCurrentCellHighlight = this.checkIsCurrentCellHighlight(key, recordId);
const isCellHighlight = this.checkIsCellHighlight(key, recordId, treeNodeKey);
const isCurrentCellHighlight = this.checkIsCurrentCellHighlight(key, recordId, treeNodeKey);
const highlightClassName = isCurrentCellHighlight ? 'cell-current-highlight' : isCellHighlight ? 'cell-highlight' : null;
const isLastCell = this.checkIsLastCell(columns, key);
const bgColor = columnColor && columnColor[key];
Expand Down
Loading

0 comments on commit 4c15caf

Please sign in to comment.