Skip to content

Commit

Permalink
filter by tags
Browse files Browse the repository at this point in the history
  • Loading branch information
zhouwenxuan authored and zhouwenxuan committed Dec 23, 2024
1 parent 8dd627b commit 1219001
Show file tree
Hide file tree
Showing 10 changed files with 178 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const TagsEditor = forwardRef(({
editorPosition = { left: 0, top: 0 },
onPressTab,
updateFileTags,
modifyColumnData,
}, ref) => {
const { tagsData, addTag, context } = useTags();

Expand Down Expand Up @@ -104,12 +105,17 @@ const TagsEditor = forwardRef(({
const newValue = [...value, ...tags];
updateFileTags([{ record_id: recordId, tags: newValue, old_tags: value }]);
setValue(newValue);
const options = tagsData.rows.map(tag => ({
row_id: getTagId(tag),
display_value: getTagName(tag),
})) || [];
modifyColumnData(column.key, { options }, { options: column.data.options || [] });
},
fail_callback: () => {

},
});
}, [value, searchValue, record, addTag, updateFileTags]);
}, [value, searchValue, record, addTag, updateFileTags, modifyColumnData, column, tagsData]);

const getMaxItemNum = useCallback(() => {
let selectContainerStyle = getComputedStyle(editorContainerRef.current, null);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import {
filterTermModifierIsWithin,
} from '../../../../../constants';
import FilterItemUtils from '../filter-item-utils';
import TagsFilter from './tags-filter';
import { getTagColor, getTagId, getTagName } from '../../../../../../tag/utils';

import './index.css';

Expand Down Expand Up @@ -506,6 +508,15 @@ class FilterItem extends React.Component {
</div>
);
}
case CellType.TAGS: {
const options = (window.sfTagsDataStore.data?.rows || []).map(tag => ({
id: getTagId(tag),
name: getTagName(tag),
color: getTagColor(tag),
}));

return <TagsFilter options={options} filterTerm={filter_term} readOnly={readOnly} onSelectMultiple={this.onSelectMultiple} />;
}
default: {
return null;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@

.sf-metadata-filters-list .sf-metadata-tag-color {
border-radius: 50%;
flex-shrink: 0;
}

.sf-metadata-filters-list .sf-metadata-tag-name {
flex: 1;
margin-left: 4px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
color: #212529;
}

.sf-metadata-selector-tags.sf-metadata-select .sf-metadata-option {
line-height: 20px;
padding: 5px 10px 5px 10px !important;
}

.sf-metadata-selector-tags.sf-metadata-select .sf-metadata-option:hover {
background-color: #f7f7f7;
color: #212529;
}

.sf-metadata-selector-tags.sf-metadata-select .selected-option-show {
text-overflow: clip;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import React, { useMemo } from 'react';
import PropTypes from 'prop-types';
import { CustomizeSelect, Icon } from '@seafile/sf-metadata-ui-component';
import { DELETED_OPTION_BACKGROUND_COLOR, DELETED_OPTION_TIPS } from '../../../../../../constants';
import { gettext } from '../../../../../../../utils/constants';

import './index.css';

const TagsFilter = ({ options, filterTerm, readOnly, onSelectMultiple }) => {

const selectValue = useMemo(() => {
return Array.isArray(filterTerm) && filterTerm.length > 0 && filterTerm.map((item) => {
const option = options.find(option => option.id === item);
if (!option) return null;
const optionStyle = { margin: '0 10px 0 0' };
let optionName = null;
let optionColor = null;

if (option) {
optionName = option.name;
optionColor = option.color;
optionStyle.background = option.color;
} else {
optionStyle.background = DELETED_OPTION_BACKGROUND_COLOR;
optionName = gettext(DELETED_OPTION_TIPS);
}

return (
<div
key={'option_' + item}
className="tag-item"
title={optionName}
aria-label={optionName}
>
<span className="sf-metadata-tag-color" style={{ background: optionColor }}></span>
<span className="sf-metadata-tag-name">{optionName}</span>
</div>
);
});
}, [filterTerm, options]);

const dataOptions = useMemo(() => {
return options.map(option => ({
value: { columnOption: option },
label: (
<div className="select-option-name option-tag">
<div className="sf-metadata-tag-container">
<span className="sf-metadata-tag-color" style={{ background: option.color }}></span>
<span className="sf-metadata-tag-name">{option.name}</span>
</div>
<div className="tag-check-icon">
{filterTerm.indexOf(option.id) > -1 && (<Icon iconName="check-mark" />)}
</div>
</div>
)
}));
}, [options, filterTerm]);

return (
<CustomizeSelect
readOnly={readOnly}
className="sf-metadata-selector-tags"
value={selectValue ? { label: selectValue } : {}}
options={dataOptions}
onSelectOption={onSelectMultiple}
placeholder={gettext('Select tag(s)')}
searchable={true}
searchPlaceholder={gettext('Search tag')}
noOptionsPlaceholder={gettext('No tags available')}
supportMultipleSelect={true}
/>
);
};

TagsFilter.propTypes = {
filterTerm: PropTypes.oneOfType([PropTypes.array, PropTypes.string]),
readOnly: PropTypes.bool.isRequired,
onSelectMultiple: PropTypes.func.isRequired,
};

export default TagsFilter;
Original file line number Diff line number Diff line change
Expand Up @@ -107,24 +107,29 @@

.filter-term .selector-single-select .sf-metadata-option:hover .select-option-name,
.filter-term .selector-multiple-select .sf-metadata-option:hover .select-option-name,
.filter-term .selector-collaborator .sf-metadata-option:hover .select-option-name {
.filter-term .selector-collaborator .sf-metadata-option:hover .select-option-name,
.filter-term .selector-tags .sf-metadata-option:hover .select-option-name {
color: unset;
}

.sf-metadata-filters-list .selector-collaborator .selected-option-show {
.sf-metadata-filters-list .selector-collaborator .selected-option-show,
.sf-metadata-filters-list .selector-tags .selected-option-show {
text-overflow: unset;
}

.sf-metadata-filters-list .selector-multiple-select .sf-metadata-option:hover,
.sf-metadata-filters-list .selector-multiple-select .sf-metadata-option.sf-metadata-option-active,
.sf-metadata-filters-list .selector-collaborator .sf-metadata-option:hover,
.sf-metadata-filters-list .selector-collaborator .sf-metadata-option.sf-metadata-option-active {
.sf-metadata-filters-list .selector-collaborator .sf-metadata-option.sf-metadata-option-active,
.sf-metadata-filters-list .selector-tags .sf-metadata-option:hover,
.sf-metadata-filters-list .selector-tags .sf-metadata-option.sf-metadata-option-active {
color: #212529;
background-color: #f7f7f7;
}

.sf-metadata-filters-list .selector-multiple-select .sf-metadata-option.sf-metadata-option-active .select-option-name,
.sf-metadata-filters-list .selector-collaborator .sf-metadata-option.sf-metadata-option-active .select-option-name {
.sf-metadata-filters-list .selector-collaborator .sf-metadata-option.sf-metadata-option-active .select-option-name,
.sf-metadata-filters-list .selector-tags .sf-metadata-option.sf-metadata-option-active .select-option-name {
color: #212529;
}

Expand Down Expand Up @@ -213,13 +218,15 @@
fill: #666;
}

.sf-metadata-filters-list .multiple-option-name {
.sf-metadata-filters-list .multiple-option-name,
.sf-metadata-filters-list .tags-option-name {
display: flex;
align-items: center;
}

.sf-metadata-filters-list .multiple-check-icon,
.sf-metadata-filters-list .collaborator-check-icon {
.sf-metadata-filters-list .collaborator-check-icon,
.sf-metadata-filters-list .tag-check-icon {
display: inline-flex;
width: 20px;
height: 20px;
Expand All @@ -228,13 +235,15 @@
}

.sf-metadata-filters-list .multiple-check-icon .sf-metadata-icon-check-mark,
.sf-metadata-filters-list .collaborator-check-icon .sf-metadata-icon-check-mark {
.sf-metadata-filters-list .collaborator-check-icon .sf-metadata-icon-check-mark,
.sf-metadata-filters-list .tag-check-icon .sf-metadata-icon-check-mark {
fill: #666 !important;
font-size: 12px;
}

.user-select-item,
.collaborator {
.collaborator,
.tag-item {
display: inline-flex;
align-items: center;
height: 20px;
Expand All @@ -245,11 +254,13 @@
background: #eaeaea;
}

.sf-metadata-filters-list .collaborator-show {
.sf-metadata-filters-list .collaborator-show,
.sf-metadata-filters-list .tags-show {
flex: 1;
}

.sf-metadata-filters-list .collaborator-avatar-container {
.sf-metadata-filters-list .collaborator-avatar-container,
.sf-metadata-filters-list .sf-metadata-tag-color {
width: 16px;
height: 16px;
display: flex;
Expand All @@ -262,16 +273,19 @@
border-radius: 50%;
}

.sf-metadata-filters-list .collaborator-name {
.sf-metadata-filters-list .collaborator-name,
.sf-metadata-filters-list .tag-name {
margin-left: 5px;
max-width: 200px;
}

.sf-metadata-filters-list .option-collaborator {
.sf-metadata-filters-list .option-collaborator,
.sf-metadata-filters-list .option-tag {
display: flex;
}

.sf-metadata-filters-list .collaborator-container {
.sf-metadata-filters-list .collaborator-container,
.sf-metadata-filters-list .sf-metadata-tag-container {
flex: 1;
display: flex;
align-items: center;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { FilterSetter, GroupbySetter, SortSetter, HideColumnSetter } from '../..
import { PRIVATE_COLUMN_KEY } from '../../../constants';

const TableViewToolbar = ({
readOnly, view, collaborators,
readOnly, view, collaborators, tags,
modifyFilters, modifySorts, modifyGroupbys, modifyHiddenColumns, modifyColumnOrder
}) => {
const viewType = useMemo(() => view.type, [view]);
Expand Down Expand Up @@ -33,6 +33,7 @@ const TableViewToolbar = ({
modifyFilters={modifyFilters}
collaborators={collaborators}
viewType={viewType}
tags={tags}
/>
<SortSetter
isNeedSubmit={true}
Expand Down Expand Up @@ -77,6 +78,7 @@ TableViewToolbar.propTypes = {
modifyGroupbys: PropTypes.func,
modifyHiddenColumns: PropTypes.func,
modifyColumnOrder: PropTypes.func,
tags: PropTypes.array,
};

export default TableViewToolbar;
2 changes: 1 addition & 1 deletion frontend/src/metadata/constants/column/type.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const CellType = {
GEOLOCATION: 'geolocation',
RATE: 'rate',
LINK: 'link',
TAGS: 'tags'
TAGS: 'tags',
};

export default CellType;
12 changes: 12 additions & 0 deletions frontend/src/metadata/constants/filter/filter-column-options.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,15 @@ const dateTermModifiers = [
FILTER_TERM_MODIFIER_TYPE.EXACT_DATE,
];

const tagsPredicates = [
FILTER_PREDICATE_TYPE.HAS_ANY_OF,
FILTER_PREDICATE_TYPE.HAS_ALL_OF,
FILTER_PREDICATE_TYPE.HAS_NONE_OF,
FILTER_PREDICATE_TYPE.IS_EXACTLY,
FILTER_PREDICATE_TYPE.EMPTY,
FILTER_PREDICATE_TYPE.NOT_EMPTY,
];

const FILTER_COLUMN_OPTIONS = {
[CellType.TEXT]: {
filterPredicateList: textPredicates,
Expand Down Expand Up @@ -143,6 +152,9 @@ const FILTER_COLUMN_OPTIONS = {
[CellType.RATE]: {
filterPredicateList: numberPredicates,
},
[CellType.TAGS]: {
filterPredicateList: tagsPredicates,
},
};

export {
Expand Down
7 changes: 7 additions & 0 deletions frontend/src/metadata/utils/validate/filter.js
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,13 @@ class ValidateFilter {
const options = getColumnOptions(filterColumn);
return this.isValidSelectedOptions(term, options);
}
case CellType.TAGS: {
if (!this.isValidTermType(term, TERM_TYPE_MAP.ARRAY)) {
return false;
}

return true;
}
default: {
return false;
}
Expand Down
2 changes: 1 addition & 1 deletion seahub/repo_metadata/metadata_server_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ def list_metadata_view_records(repo_id, user, view, start=0, limit=1000):
metadata_server_api = MetadataServerAPI(repo_id, user)
columns = metadata_server_api.list_columns(METADATA_TABLE.id).get('columns')
sql = gen_view_data_sql(METADATA_TABLE, columns, view, start, limit, user)

print(sql)
# Remove face-vectors from the query SQL because they are too large
query_fields_str = ''
for column in columns:
Expand Down

0 comments on commit 1219001

Please sign in to comment.