@@ -253,8 +258,10 @@ const CollaboratorEditor = forwardRef(({
CollaboratorEditor.propTypes = {
saveImmediately: PropTypes.bool,
+ height: PropTypes.number,
column: PropTypes.object,
value: PropTypes.array,
+ editorPosition: PropTypes.object,
onCommit: PropTypes.func,
onClose: PropTypes.func,
onPressTab: PropTypes.func,
diff --git a/frontend/src/metadata/metadata-view/components/cell-editor/editor-container/index.js b/frontend/src/metadata/metadata-view/components/cell-editor/editor-container/index.js
index 2ca13495168..ccf9167a9e5 100644
--- a/frontend/src/metadata/metadata-view/components/cell-editor/editor-container/index.js
+++ b/frontend/src/metadata/metadata-view/components/cell-editor/editor-container/index.js
@@ -9,6 +9,7 @@ const POPUP_EDITOR_COLUMN_TYPES = [
CellType.DATE,
CellType.COLLABORATOR,
CellType.SINGLE_SELECT,
+ CellType.MULTIPLE_SELECT,
CellType.LONG_TEXT,
];
diff --git a/frontend/src/metadata/metadata-view/components/cell-editor/editor-container/popup-editor-container.js b/frontend/src/metadata/metadata-view/components/cell-editor/editor-container/popup-editor-container.js
index 9a5ef665eda..2ab9c0f97c4 100644
--- a/frontend/src/metadata/metadata-view/components/cell-editor/editor-container/popup-editor-container.js
+++ b/frontend/src/metadata/metadata-view/components/cell-editor/editor-container/popup-editor-container.js
@@ -2,15 +2,16 @@ import React from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import { ClickOutside } from '@seafile/sf-metadata-ui-component';
-import { CellType, isFunction, Z_INDEX, getCellValueByColumn, getColumnOptionNameById, PRIVATE_COLUMN_KEYS } from '../../../_basic';
+import { CellType, isFunction, Z_INDEX, getCellValueByColumn, getColumnOptionNameById, PRIVATE_COLUMN_KEYS,
+ getColumnOptionNamesByIds,
+} from '../../../_basic';
import { isCellValueChanged } from '../../../utils/cell-comparer';
import { EVENT_BUS_TYPE } from '../../../constants';
import Editor from '../editor';
import { canEditCell } from '../../../utils/column-utils';
const NOT_SUPPORT_EDITOR_COLUMN_TYPES = [
- CellType.CTIME, CellType.MTIME, CellType.CREATOR, CellType.LAST_MODIFIER,
- CellType.FILE_NAME, CellType.COLLABORATOR, CellType.LONG_TEXT, CellType.SINGLE_SELECT,
+ CellType.CTIME, CellType.MTIME, CellType.CREATOR, CellType.LAST_MODIFIER, CellType.FILE_NAME
];
class PopupEditorContainer extends React.Component {
@@ -21,7 +22,7 @@ class PopupEditorContainer extends React.Component {
super(props);
const { column, width, height, left, top } = this.props;
let additionalStyles = {};
- if (column.type === CellType.SINGLE_SELECT) {
+ if (column.type === CellType.SINGLE_SELECT || column.type === CellType.MULTIPLE_SELECT) {
additionalStyles = { width, height };
}
this.state = {
@@ -146,6 +147,8 @@ class PopupEditorContainer extends React.Component {
let updated = columnType === CellType.DATE ? { [columnKey]: newValue } : newValue;
if (columnType === CellType.SINGLE_SELECT) {
updated[columnKey] = newValue[columnKey] ? getColumnOptionNameById(column, newValue[columnKey]) : '';
+ } else if (columnType === CellType.MULTIPLE_SELECT) {
+ updated[columnKey] = newValue[columnKey] ? getColumnOptionNamesByIds(column, newValue[columnKey]) : [];
}
this.commitData(updated, true);
diff --git a/frontend/src/metadata/metadata-view/components/cell-editor/editor.js b/frontend/src/metadata/metadata-view/components/cell-editor/editor.js
index db30b1e1ea4..506413d00e3 100644
--- a/frontend/src/metadata/metadata-view/components/cell-editor/editor.js
+++ b/frontend/src/metadata/metadata-view/components/cell-editor/editor.js
@@ -6,6 +6,7 @@ import FileNameEditor from './file-name-editor';
import TextEditor from './text-editor';
import NumberEditor from './number-editor';
import SingleSelectEditor from './single-select-editor';
+import MultipleSelectEditor from './multiple-select-editor';
import CollaboratorEditor from './collaborator-editor';
// eslint-disable-next-line react/display-name
@@ -28,6 +29,9 @@ const Editor = React.forwardRef((props, ref) => {
case CellType.SINGLE_SELECT: {
return (
);
}
+ case CellType.MULTIPLE_SELECT: {
+ return (
);
+ }
case CellType.COLLABORATOR: {
return (
);
}
diff --git a/frontend/src/metadata/metadata-view/components/cell-editor/multiple-select-editor/delete-options/index.css b/frontend/src/metadata/metadata-view/components/cell-editor/multiple-select-editor/delete-options/index.css
new file mode 100644
index 00000000000..53bab4fa889
--- /dev/null
+++ b/frontend/src/metadata/metadata-view/components/cell-editor/multiple-select-editor/delete-options/index.css
@@ -0,0 +1,29 @@
+.sf-metadata-delete-select-options {
+ background-color: #f6f6f6;
+ border-bottom: 1px solid #dde2ea;
+ border-radius: 3px 3px 0 0;
+ min-height: 35px;
+ padding: 2px 10px;
+ line-height: 1;
+}
+
+.sf-metadata-delete-select-options .sf-metadata-delete-select-option {
+ margin-top: 5px;
+ margin-bottom: 5px;
+ margin-right: 10px;
+ align-items: center;
+ max-width: 100% !important;
+ overflow: hidden
+}
+
+.sf-metadata-delete-select-options .sf-metadata-delete-select-option .sf-metadata-delete-select-remove {
+ height: 14px;
+ width: 14px;
+ position: relative;
+ left: 2px;
+}
+
+.sf-metadata-delete-select-options .sf-metadata-delete-select-option .sf-metadata-icon-x-01 {
+ fill: inherit;
+ font-size: 12px;
+}
diff --git a/frontend/src/metadata/metadata-view/components/cell-editor/multiple-select-editor/delete-options/index.js b/frontend/src/metadata/metadata-view/components/cell-editor/multiple-select-editor/delete-options/index.js
new file mode 100644
index 00000000000..76c55b6b4e4
--- /dev/null
+++ b/frontend/src/metadata/metadata-view/components/cell-editor/multiple-select-editor/delete-options/index.js
@@ -0,0 +1,57 @@
+import React, { useMemo } from 'react';
+import PropTypes from 'prop-types';
+import { IconBtn } from '@seafile/sf-metadata-ui-component';
+import { gettext } from '../../../../utils';
+import { DELETED_OPTION_TIPS, DELETED_OPTION_BACKGROUND_COLOR } from '../../../../constants';
+
+import './index.css';
+
+const DeleteOption = ({ value, options, onDelete }) => {
+
+ const displayOptions = useMemo(() => {
+ if (!Array.isArray(value) || value.length === 0) return [];
+ const selectedOptions = options.filter((option) => value.includes(option.id) || value.includes(option.name));
+ const invalidOptionIds = value.filter(optionId => optionId && !options.find(o => o.id === optionId || o.name === optionId));
+ const invalidOptions = invalidOptionIds.map(optionId => ({
+ id: optionId,
+ name: gettext(DELETED_OPTION_TIPS),
+ color: DELETED_OPTION_BACKGROUND_COLOR,
+ }));
+ return [...selectedOptions, ...invalidOptions];
+ }, [options, value]);
+
+ return (
+
+ {displayOptions.map(option => {
+ if (!option) return null;
+ const { id, name } = option;
+ const style = {
+ display: 'inline-flex',
+ padding: '0px 10px',
+ height: '20px',
+ lineHeight: '20px',
+ textAlign: 'center',
+ borderRadius: '10px',
+ maxWidth: '250px',
+ fontSize: 13,
+ backgroundColor: option.color,
+ color: option.textColor || null,
+ fill: option.textColor || '#666',
+ };
+ return (
+
+ {name}
+ onDelete(id, event)} iconName="x-01" />
+
+ );
+ })}
+
+ );
+};
+
+DeleteOption.propTypes = {
+ value: PropTypes.array.isRequired,
+ onDelete: PropTypes.func.isRequired
+};
+
+export default DeleteOption;
diff --git a/frontend/src/metadata/metadata-view/components/cell-editor/multiple-select-editor/index.css b/frontend/src/metadata/metadata-view/components/cell-editor/multiple-select-editor/index.css
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/frontend/src/metadata/metadata-view/components/cell-editor/multiple-select-editor/index.js b/frontend/src/metadata/metadata-view/components/cell-editor/multiple-select-editor/index.js
new file mode 100644
index 00000000000..34b204e63a0
--- /dev/null
+++ b/frontend/src/metadata/metadata-view/components/cell-editor/multiple-select-editor/index.js
@@ -0,0 +1,283 @@
+import React, { forwardRef, useMemo, useImperativeHandle, useCallback, useState, useRef, useEffect } from 'react';
+import PropTypes from 'prop-types';
+import classnames from 'classnames';
+import { SearchInput, CustomizeAddTool, Icon } from '@seafile/sf-metadata-ui-component';
+import { isFunction, getColumnOptions, getColumnOptionIdsByNames } from '../../../_basic';
+import { generateNewOption } from '../../../utils/select-utils';
+import { KeyCodes } from '../../../../../constants';
+import { gettext } from '../../../../../utils/constants';
+import DeleteOption from './delete-options';
+
+import './index.css';
+
+const MultipleSelectEditor = forwardRef(({
+ height,
+ saveImmediately,
+ column,
+ value: oldValue,
+ editorPosition = { left: 0, top: 0 },
+ onCommit,
+ onPressTab,
+ modifyColumnData,
+}, ref) => {
+ const [value, setValue] = useState(getColumnOptionIdsByNames(column, oldValue));
+ const [searchValue, setSearchValue] = useState('');
+ const [highlightIndex, setHighlightIndex] = useState(-1);
+ const [maxItemNum, setMaxItemNum] = useState(0);
+ const itemHeight = 30;
+ const editorContainerRef = useRef(null);
+ const editorRef = useRef(null);
+ const selectItemRef = useRef(null);
+ const canEditData = window.sfMetadataContext.canModifyColumnData(column);
+
+ const options = useMemo(() => {
+ return getColumnOptions(column);
+ }, [column]);
+
+ const displayOptions = useMemo(() => {
+ if (!searchValue) return options;
+ const value = searchValue.toLowerCase().trim();
+ if (!value) return options;
+ return options.filter((item) => item.name && item.name.toLowerCase().indexOf(value) > -1);
+ }, [searchValue, options]);
+
+ const isShowCreateBtn = useMemo(() => {
+ if (!canEditData || !searchValue) return false;
+ return displayOptions.findIndex(option => option.name === searchValue) === -1 ? true : false;
+ }, [canEditData, displayOptions, searchValue]);
+
+ const style = useMemo(() => {
+ return { width: column.width };
+ }, [column]);
+
+ const blur = useCallback(() => {
+ onCommit && onCommit(value);
+ }, [value, onCommit]);
+
+ const onChangeSearch = useCallback((newSearchValue) => {
+ if (searchValue === newSearchValue) return;
+ setSearchValue(newSearchValue);
+ }, [searchValue]);
+
+ const onSelectOption = useCallback((optionId) => {
+ const newValue = value.slice(0);
+ let optionIdx = value.indexOf(optionId);
+ if (optionIdx > -1) {
+ newValue.splice(optionIdx, 1);
+ } else {
+ newValue.push(optionId);
+ }
+ setValue(newValue);
+ if (saveImmediately) {
+ onCommit && onCommit(newValue);
+ }
+ }, [saveImmediately, value, onCommit]);
+
+ const onMenuMouseEnter = useCallback((highlightIndex) => {
+ setHighlightIndex(highlightIndex);
+ }, []);
+
+ const onMenuMouseLeave = useCallback((index) => {
+ setHighlightIndex(-1);
+ }, []);
+
+ const createOption = useCallback((event) => {
+ event && event.stopPropagation();
+ event && event.nativeEvent.stopImmediatePropagation();
+ const newOption = generateNewOption(options, searchValue?.trim() || '');
+ let newOptions = options.slice(0);
+ newOptions.push(newOption);
+ modifyColumnData(column.key, { options: newOptions }, { options: column.data.options || [] });
+ onSelectOption(newOption.id);
+ }, [column, searchValue, options, onSelectOption, modifyColumnData]);
+
+ const onDeleteOption = useCallback((optionId) => {
+ const newValue = value.slice(0);
+ const index = newValue.indexOf(optionId);
+ if (index > -1) {
+ newValue.splice(index, 1);
+ }
+ setValue(newValue);
+ if (saveImmediately) {
+ onCommit && onCommit(newValue);
+ }
+ }, [saveImmediately, value, onCommit]);
+
+ const getMaxItemNum = useCallback(() => {
+ let selectContainerStyle = getComputedStyle(editorContainerRef.current, null);
+ let selectItemStyle = getComputedStyle(selectItemRef.current, null);
+ let maxSelectItemNum = Math.floor(parseInt(selectContainerStyle.maxHeight) / parseInt(selectItemStyle.height));
+ return maxSelectItemNum - 1;
+ }, [editorContainerRef, selectItemRef]);
+
+ const onEnter = useCallback((event) => {
+ event.preventDefault();
+ let option;
+ if (displayOptions.length === 1) {
+ option = displayOptions[0];
+ } else if (highlightIndex > -1) {
+ option = displayOptions[highlightIndex];
+ }
+ if (option) {
+ let newOptionId = option.id;
+ if (value === option.id) newOptionId = null;
+ onSelectOption(newOptionId);
+ return;
+ }
+ let isShowCreateBtn = false;
+ if (searchValue) {
+ isShowCreateBtn = canEditData && displayOptions.findIndex(option => option.name === searchValue) === -1 ? true : false;
+ }
+ if (!isShowCreateBtn || displayOptions.length === 0) return;
+ createOption();
+ }, [canEditData, displayOptions, highlightIndex, value, searchValue, onSelectOption, createOption]);
+
+ const onUpArrow = useCallback((event) => {
+ event.preventDefault();
+ event.stopPropagation();
+ if (highlightIndex === 0) return;
+ setHighlightIndex(highlightIndex - 1);
+ if (highlightIndex > displayOptions.length - maxItemNum) {
+ editorContainerRef.current.scrollTop -= itemHeight;
+ }
+ }, [editorContainerRef, highlightIndex, maxItemNum, displayOptions, itemHeight]);
+
+ const onDownArrow = useCallback((event) => {
+ event.preventDefault();
+ event.stopPropagation();
+ if (highlightIndex === displayOptions.length - 1) return;
+ setHighlightIndex(highlightIndex + 1);
+ if (highlightIndex >= maxItemNum) {
+ editorContainerRef.current.scrollTop += itemHeight;
+ }
+ }, [editorContainerRef, highlightIndex, maxItemNum, displayOptions, itemHeight]);
+
+ const onHotKey = useCallback((event) => {
+ if (event.keyCode === KeyCodes.Enter) {
+ onEnter(event);
+ } else if (event.keyCode === KeyCodes.UpArrow) {
+ onUpArrow(event);
+ } else if (event.keyCode === KeyCodes.DownArrow) {
+ onDownArrow(event);
+ } else if (event.keyCode === KeyCodes.Tab) {
+ if (isFunction(onPressTab)) {
+ onPressTab(event);
+ }
+ }
+ }, [onEnter, onUpArrow, onDownArrow, onPressTab]);
+
+ const onKeyDown = useCallback((event) => {
+ if (
+ event.keyCode === KeyCodes.ChineseInputMethod ||
+ event.keyCode === KeyCodes.Enter ||
+ event.keyCode === KeyCodes.LeftArrow ||
+ event.keyCode === KeyCodes.RightArrow
+ ) {
+ event.stopPropagation();
+ }
+ }, []);
+
+ useEffect(() => {
+ if (editorRef.current) {
+ const { bottom } = editorRef.current.getBoundingClientRect();
+ if (bottom > window.innerHeight) {
+ editorRef.current.style.top = 'unset';
+ editorRef.current.style.bottom = editorPosition.top + height - window.innerHeight + 'px';
+ }
+ }
+ if (editorContainerRef.current && selectItemRef.current) {
+ setMaxItemNum(getMaxItemNum());
+ }
+ document.addEventListener('keydown', onHotKey, true);
+ return () => {
+ document.removeEventListener('keydown', onHotKey, true);
+ };
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [onHotKey]);
+
+ useEffect(() => {
+ const highlightIndex = displayOptions.length === 0 ? -1 : 0;
+ setHighlightIndex(highlightIndex);
+ }, [displayOptions]);
+
+ useImperativeHandle(ref, () => ({
+ getValue: () => {
+ const { key } = column;
+ return { [key]: value };
+ },
+ onBlur: () => blur(),
+
+ }), [column, value, blur]);
+
+ const renderOptions = useCallback(() => {
+ if (displayOptions.length === 0) {
+ const noOptionsTip = searchValue ? gettext('No options available') : gettext('No option');
+ return (
{noOptionsTip});
+ }
+
+ return displayOptions.map((option, i) => {
+ const isSelected = value.includes(option.id);
+ return (
+
+
onSelectOption(option.id)}
+ onMouseEnter={() => onMenuMouseEnter(i)}
+ onMouseLeave={() => onMenuMouseLeave(i)}
+ >
+
+
+ {option.name}
+
+
+
+ {isSelected && ()}
+
+
+
+ );
+ });
+
+ }, [displayOptions, searchValue, value, highlightIndex, onMenuMouseEnter, onMenuMouseLeave, onSelectOption]);
+
+ return (
+
+
+
+
+
+
+ {renderOptions()}
+
+ {isShowCreateBtn && (
+
+ )}
+
+ );
+});
+
+MultipleSelectEditor.propTypes = {
+ height: PropTypes.number,
+ column: PropTypes.object,
+ value: PropTypes.array,
+ editorPosition: PropTypes.object,
+ onCommit: PropTypes.func,
+ onPressTab: PropTypes.func,
+};
+
+export default MultipleSelectEditor;
diff --git a/frontend/src/metadata/metadata-view/components/cell-editor/single-select-editor/index.js b/frontend/src/metadata/metadata-view/components/cell-editor/single-select-editor/index.js
index c4e025bf434..000e9b378c6 100644
--- a/frontend/src/metadata/metadata-view/components/cell-editor/single-select-editor/index.js
+++ b/frontend/src/metadata/metadata-view/components/cell-editor/single-select-editor/index.js
@@ -15,6 +15,7 @@ const SingleSelectEditor = forwardRef(({
columns,
record,
value: oldValue,
+ editorPosition = { left: 0, top: 0 },
onCommit,
onPressTab,
modifyColumnData,
@@ -175,7 +176,8 @@ const SingleSelectEditor = forwardRef(({
if (editorRef.current) {
const { bottom } = editorRef.current.getBoundingClientRect();
if (bottom > window.innerHeight) {
- editorRef.current.style.top = (parseInt(editorRef.current.style.top) - bottom + window.innerHeight) + 'px';
+ editorRef.current.style.top = 'unset';
+ editorRef.current.style.bottom = editorPosition.top + height - window.innerHeight + 'px';
}
}
if (editorContainerRef.current && selectItemRef.current) {
@@ -269,6 +271,7 @@ SingleSelectEditor.propTypes = {
columns: PropTypes.array,
record: PropTypes.object,
value: PropTypes.string,
+ editorPosition: PropTypes.object,
onCommit: PropTypes.func,
onPressTab: PropTypes.func,
};
diff --git a/frontend/src/metadata/metadata-view/components/detail-editor/index.js b/frontend/src/metadata/metadata-view/components/detail-editor/index.js
index b24c4f6b601..0adbf09e1fc 100644
--- a/frontend/src/metadata/metadata-view/components/detail-editor/index.js
+++ b/frontend/src/metadata/metadata-view/components/detail-editor/index.js
@@ -5,6 +5,7 @@ import CheckboxEditor from './checkbox-editor';
import TextEditor from './text-editor';
import NumberEditor from './number-editor';
import SingleSelectEditor from './single-select-editor';
+import MultipleSelectEditor from './multiple-select-editor';
import CollaboratorEditor from './collaborator-editor';
import DateEditor from './date-editor';
import { lang } from '../../../../utils/constants';
@@ -16,7 +17,6 @@ const DetailEditor = ({ field, onChange: onChangeAPI, ...props }) => {
onChangeAPI(field.key, newValue);
}, [field, onChangeAPI]);
-
switch (field.type) {
case CellType.CHECKBOX: {
return (
);
@@ -33,6 +33,9 @@ const DetailEditor = ({ field, onChange: onChangeAPI, ...props }) => {
case CellType.SINGLE_SELECT: {
return (
);
}
+ case CellType.MULTIPLE_SELECT: {
+ return (
);
+ }
case CellType.COLLABORATOR: {
return (
);
}
diff --git a/frontend/src/metadata/metadata-view/components/detail-editor/multiple-select-editor/index.css b/frontend/src/metadata/metadata-view/components/detail-editor/multiple-select-editor/index.css
new file mode 100644
index 00000000000..6cef818f9f4
--- /dev/null
+++ b/frontend/src/metadata/metadata-view/components/detail-editor/multiple-select-editor/index.css
@@ -0,0 +1,17 @@
+.sf-metadata-multiple-select-property-detail-editor {
+ min-height: 34px;
+ width: 100%;
+ height: auto;
+}
+
+.sf-metadata-multiple-select-property-detail-editor .sf-metadata-delete-select-options {
+ min-height: 34px;
+ border-bottom: none;
+ background-color: inherit;
+ border-radius: unset;
+ padding: 2px 6px;
+}
+
+.sf-metadata-multiple-select-property-editor-popover .sf-metadata-delete-select-options {
+ display: none;
+}
diff --git a/frontend/src/metadata/metadata-view/components/detail-editor/multiple-select-editor/index.js b/frontend/src/metadata/metadata-view/components/detail-editor/multiple-select-editor/index.js
new file mode 100644
index 00000000000..dbdf235a1a2
--- /dev/null
+++ b/frontend/src/metadata/metadata-view/components/detail-editor/multiple-select-editor/index.js
@@ -0,0 +1,113 @@
+import React, { useMemo, useCallback, useState, useRef, useEffect } from 'react';
+import PropTypes from 'prop-types';
+import { Popover } from 'reactstrap';
+import { getColumnOptionIdsByNames, getColumnOptions, KeyCodes } from '../../../_basic';
+import { getEventClassName, gettext } from '../../../utils';
+import Editor from '../../cell-editor/multiple-select-editor';
+import DeleteOptions from '../../cell-editor/multiple-select-editor/delete-options';
+
+import './index.css';
+
+const MultipleSelectEditor = ({ field, value, record, fields, onChange, modifyColumnData }) => {
+ const ref = useRef(null);
+ const [showEditor, setShowEditor] = useState(false);
+ const options = useMemo(() => getColumnOptions(field), [field]);
+
+ const onClick = useCallback((event) => {
+ if (!event.target) return;
+ const className = getEventClassName(event);
+ if (className.indexOf('sf-metadata-search-options') > -1) return;
+ const dom = document.querySelector('.sf-metadata-multiple-select-editor');
+ if (!dom) return;
+ if (dom.contains(event.target)) return;
+ if (ref.current && !ref.current.contains(event.target) && showEditor) {
+ setShowEditor(false);
+ }
+ }, [showEditor]);
+
+ const onHotKey = useCallback((event) => {
+ if (event.keyCode === KeyCodes.Esc) {
+ if (showEditor) {
+ setShowEditor(false);
+ }
+ }
+ }, [showEditor]);
+
+ useEffect(() => {
+ document.addEventListener('mousedown', onClick);
+ document.addEventListener('keydown', onHotKey, true);
+ return () => {
+ document.removeEventListener('mousedown', onClick);
+ document.removeEventListener('keydown', onHotKey, true);
+ };
+ }, [onClick, onHotKey]);
+
+ const openEditor = useCallback(() => {
+ setShowEditor(true);
+ }, []);
+
+ const deleteOption = useCallback((id, event) => {
+ event && event.stopPropagation();
+ event && event.nativeEvent && event.nativeEvent.stopImmediatePropagation();
+ const oldValue = getColumnOptionIdsByNames(field, value);
+ const newValue = oldValue.filter(c => c !== id);
+ onChange(newValue);
+ }, [field, value, onChange]);
+
+ const onCommit = useCallback((newValue) => {
+ onChange(newValue);
+ }, [onChange]);
+
+ const renderEditor = useCallback(() => {
+ if (!showEditor) return null;
+ const { width } = ref.current.getBoundingClientRect();
+ return (
+
+
+
+ );
+ }, [showEditor, onCommit, record, value, modifyColumnData, fields, field]);
+
+ const isEmpty = useMemo(() => {
+ if (!Array.isArray(value) || value.length === 0) return true;
+ const selectedOptions = options.filter((option) => value.includes(option.id) || value.includes(option.name));
+ const invalidOptionIds = value.filter(optionId => optionId && !options.find(o => o.id === optionId || o.name === optionId));
+ return selectedOptions.length + invalidOptionIds.length === 0;
+ }, [options, value]);
+
+ return (
+
+ {!isEmpty && ()}
+ {renderEditor()}
+
+ );
+};
+
+MultipleSelectEditor.propTypes = {
+ field: PropTypes.object.isRequired,
+ value: PropTypes.array,
+ onChange: PropTypes.func.isRequired,
+};
+
+export default MultipleSelectEditor;
diff --git a/frontend/src/metadata/metadata-view/components/popover/column-popover/index.js b/frontend/src/metadata/metadata-view/components/popover/column-popover/index.js
index 00f8b417211..f2771448254 100644
--- a/frontend/src/metadata/metadata-view/components/popover/column-popover/index.js
+++ b/frontend/src/metadata/metadata-view/components/popover/column-popover/index.js
@@ -69,7 +69,7 @@ const ColumnPopover = ({ target, onChange }) => {
if (Object.keys(data).length === 0) {
data = null;
if (!column.unique) {
- if (column.type === CellType.SINGLE_SELECT) {
+ if (column.type === CellType.SINGLE_SELECT || column.type === CellType.MULTIPLE_SELECT) {
data = { options: [] };
} else if (column.type === CellType.DATE) {
data = { format: DEFAULT_DATE_FORMAT };
diff --git a/frontend/src/metadata/metadata-view/components/popover/column-popover/type/index.js b/frontend/src/metadata/metadata-view/components/popover/column-popover/type/index.js
index cd8b3a05d0d..beca8463a28 100644
--- a/frontend/src/metadata/metadata-view/components/popover/column-popover/type/index.js
+++ b/frontend/src/metadata/metadata-view/components/popover/column-popover/type/index.js
@@ -18,12 +18,13 @@ const COLUMNS = [
{ icon: COLUMNS_ICON_CONFIG[CellType.CHECKBOX], type: CellType.CHECKBOX, name: getColumnDisplayName(PRIVATE_COLUMN_KEY.FILE_EXPIRED), unique: true, key: PRIVATE_COLUMN_KEY.FILE_EXPIRED, canChangeName: false, groupby: 'predefined' },
{ icon: COLUMNS_ICON_CONFIG[CellType.SINGLE_SELECT], type: CellType.SINGLE_SELECT, name: getColumnDisplayName(PRIVATE_COLUMN_KEY.FILE_STATUS), unique: true, key: PRIVATE_COLUMN_KEY.FILE_STATUS, canChangeName: false, groupby: 'predefined' },
{ icon: COLUMNS_ICON_CONFIG[CellType.TEXT], type: CellType.TEXT, name: gettext(COLUMNS_ICON_NAME[CellType.TEXT]), canChangeName: true, key: CellType.TEXT, groupby: 'basics' },
- { icon: COLUMNS_ICON_CONFIG[CellType.CHECKBOX], type: CellType.CHECKBOX, name: gettext(COLUMNS_ICON_NAME[CellType.CHECKBOX]), canChangeName: true, key: CellType.CHECKBOX, groupby: 'basics' },
+ { icon: COLUMNS_ICON_CONFIG[CellType.LONG_TEXT], type: CellType.LONG_TEXT, name: gettext(COLUMNS_ICON_NAME[CellType.LONG_TEXT]), canChangeName: true, key: CellType.LONG_TEXT, groupby: 'basics' },
+ { icon: COLUMNS_ICON_CONFIG[CellType.NUMBER], type: CellType.NUMBER, name: gettext(COLUMNS_ICON_NAME[CellType.NUMBER]), canChangeName: true, key: CellType.NUMBER, groupby: 'basics' },
{ icon: COLUMNS_ICON_CONFIG[CellType.COLLABORATOR], type: CellType.COLLABORATOR, name: gettext(COLUMNS_ICON_NAME[CellType.COLLABORATOR]), canChangeName: true, key: CellType.COLLABORATOR, groupby: 'basics' },
+ { icon: COLUMNS_ICON_CONFIG[CellType.CHECKBOX], type: CellType.CHECKBOX, name: gettext(COLUMNS_ICON_NAME[CellType.CHECKBOX]), canChangeName: true, key: CellType.CHECKBOX, groupby: 'basics' },
{ icon: COLUMNS_ICON_CONFIG[CellType.DATE], type: CellType.DATE, name: gettext(COLUMNS_ICON_NAME[CellType.DATE]), canChangeName: true, key: CellType.DATE, groupby: 'basics' },
- { icon: COLUMNS_ICON_CONFIG[CellType.LONG_TEXT], type: CellType.LONG_TEXT, name: gettext(COLUMNS_ICON_NAME[CellType.LONG_TEXT]), canChangeName: true, key: CellType.LONG_TEXT, groupby: 'basics' },
{ icon: COLUMNS_ICON_CONFIG[CellType.SINGLE_SELECT], type: CellType.SINGLE_SELECT, name: gettext(COLUMNS_ICON_NAME[CellType.SINGLE_SELECT]), canChangeName: true, key: CellType.SINGLE_SELECT, groupby: 'basics' },
- { icon: COLUMNS_ICON_CONFIG[CellType.NUMBER], type: CellType.NUMBER, name: gettext(COLUMNS_ICON_NAME[CellType.NUMBER]), canChangeName: true, key: CellType.NUMBER, groupby: 'basics' },
+ { icon: COLUMNS_ICON_CONFIG[CellType.MULTIPLE_SELECT], type: CellType.MULTIPLE_SELECT, name: gettext(COLUMNS_ICON_NAME[CellType.MULTIPLE_SELECT]), canChangeName: true, key: CellType.MULTIPLE_SELECT, groupby: 'basics' },
];
// eslint-disable-next-line react/display-name
diff --git a/frontend/src/metadata/metadata-view/components/popover/filter-popover/widgets/filter-item/index.js b/frontend/src/metadata/metadata-view/components/popover/filter-popover/widgets/filter-item/index.js
index f6be555b8de..3a8734e6a12 100644
--- a/frontend/src/metadata/metadata-view/components/popover/filter-popover/widgets/filter-item/index.js
+++ b/frontend/src/metadata/metadata-view/components/popover/filter-popover/widgets/filter-item/index.js
@@ -481,6 +481,10 @@ class FilterItem extends React.Component {
/>
);
}
+ case CellType.MULTIPLE_SELECT: {
+ let { options = [] } = filterColumn.data || {};
+ return this.renderMultipleSelectOption(options, filter_term, readOnly);
+ }
default: {
return null;
}
@@ -523,9 +527,9 @@ class FilterItem extends React.Component {
} else if (isCheckboxColumn(filterColumn)) {
_isCheckboxColumn = true;
}
- const isContainPredicate = [FILTER_PREDICATE_TYPE.CONTAINS, FILTER_PREDICATE_TYPE.NOT_CONTAIN].includes(filter_predicate);
- const isRenderErrorTips = this.isRenderErrorTips();
- const showToolTip = isContainPredicate && !isRenderErrorTips;
+ // const isContainPredicate = [].includes(filterColumn.type) && [FILTER_PREDICATE_TYPE.CONTAINS, FILTER_PREDICATE_TYPE.NOT_CONTAIN].includes(filter_predicate);
+ // const isRenderErrorTips = this.isRenderErrorTips();
+ // const showToolTip = isContainPredicate && !isRenderErrorTips;
// current predicate is not empty
const isNeedShowTermModifier = !EMPTY_PREDICATE.includes(filter_predicate);
@@ -574,12 +578,14 @@ class FilterItem extends React.Component {
{this.renderFilterTerm(filterColumn)}
- {showToolTip &&
-
-
- {/* */}
-
- }
+ {/* {showToolTip && (
+
+
+
+ {gettext('If there are multiple items in the cell, a random one will be chosen and be compared with the filter value.')}
+
+
+ )} */}
{this.renderErrorMessage()}