mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-12 21:30:39 +00:00
feat: metadata multiple select
This commit is contained in:
8
frontend/package-lock.json
generated
8
frontend/package-lock.json
generated
@@ -19,7 +19,7 @@
|
||||
"@seafile/sdoc-editor": "1.0.50",
|
||||
"@seafile/seafile-calendar": "0.0.12",
|
||||
"@seafile/seafile-editor": "1.0.109",
|
||||
"@seafile/sf-metadata-ui-component": "0.0.21",
|
||||
"@seafile/sf-metadata-ui-component": "0.0.22",
|
||||
"@uiw/codemirror-extensions-langs": "^4.19.4",
|
||||
"@uiw/react-codemirror": "^4.19.4",
|
||||
"axios": "^1.7.3",
|
||||
@@ -5093,9 +5093,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@seafile/sf-metadata-ui-component": {
|
||||
"version": "0.0.21",
|
||||
"resolved": "https://registry.npmjs.org/@seafile/sf-metadata-ui-component/-/sf-metadata-ui-component-0.0.21.tgz",
|
||||
"integrity": "sha512-bskuoVgMXDY5sD++MlMx9864+J3BdJ69pXZKifu40op4ebpC6qtJLAdZQV/j/ZqadyzGp1r0T340ClzhUfBQWw==",
|
||||
"version": "0.0.22",
|
||||
"resolved": "https://registry.npmjs.org/@seafile/sf-metadata-ui-component/-/sf-metadata-ui-component-0.0.22.tgz",
|
||||
"integrity": "sha512-sRFGl3JoD4m5+Hdmvxt9b6yer8HxT4mI5hHBPqF+AvKsyaWMQ2Dq108vKUUlADbZOLvFlx0YuVfYfOcxYJQeJQ==",
|
||||
"dependencies": {
|
||||
"@seafile/seafile-calendar": "0.0.24",
|
||||
"@seafile/seafile-editor": "~1.0.102",
|
||||
|
@@ -14,7 +14,7 @@
|
||||
"@seafile/sdoc-editor": "1.0.50",
|
||||
"@seafile/seafile-calendar": "0.0.12",
|
||||
"@seafile/seafile-editor": "1.0.109",
|
||||
"@seafile/sf-metadata-ui-component": "0.0.21",
|
||||
"@seafile/sf-metadata-ui-component": "0.0.22",
|
||||
"@uiw/codemirror-extensions-langs": "^4.19.4",
|
||||
"@uiw/react-codemirror": "^4.19.4",
|
||||
"axios": "^1.7.3",
|
||||
|
1
frontend/src/assets/icons/multiple-select.svg
Normal file
1
frontend/src/assets/icons/multiple-select.svg
Normal file
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1723777705850" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="15418" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M128 128C73.6 128 32 169.6 32 224s41.6 96 96 96 96-41.6 96-96-41.6-96-96-96zM128 416c-54.4 0-96 41.6-96 96s41.6 96 96 96 96-41.6 96-96-41.6-96-96-96zM128 704c-54.4 0-96 41.6-96 96s41.6 96 96 96 96-41.6 96-96-41.6-96-96-96zM963.2 736H380.8c-16 0-28.8 12.8-28.8 32v64c0 19.2 12.8 32 28.8 32h582.4c16 0 28.8-12.8 28.8-32v-64c0-19.2-12.8-32-28.8-32zM963.2 160H380.8c-16 0-28.8 12.8-28.8 32v64c0 19.2 12.8 32 28.8 32h582.4c16 0 28.8-12.8 28.8-32V192c0-19.2-12.8-32-28.8-32zM963.2 448H380.8c-16 0-28.8 12.8-28.8 32v64c0 19.2 12.8 32 28.8 32h582.4c16 0 28.8-12.8 28.8-32v-64c0-19.2-12.8-32-28.8-32z" p-id="15419"></path></svg>
|
After Width: | Height: | Size: 953 B |
@@ -17,7 +17,7 @@ const DirViews = ({ userPerm, repoID, currentPath, currentRepoInfo }) => {
|
||||
return [
|
||||
{ key: 'extended-properties', value: gettext('Extended properties') }
|
||||
];
|
||||
}, [enableMetadataManagement, userPerm]);
|
||||
}, [enableMetadataManagement, currentRepoInfo]);
|
||||
|
||||
const moreOperationClick = useCallback((operationKey) => {
|
||||
if (operationKey === 'extended-properties') {
|
||||
|
@@ -9,7 +9,7 @@ import toaster from '../../components/toast';
|
||||
import { gettext } from '../../utils/constants';
|
||||
import { DetailEditor, CellFormatter } from '../metadata-view';
|
||||
import { getColumnOriginName } from '../metadata-view/utils/column-utils';
|
||||
import { CellType, getColumnOptions, getOptionName, PREDEFINED_COLUMN_KEYS } from '../metadata-view/_basic';
|
||||
import { CellType, getColumnOptions, getOptionName, PREDEFINED_COLUMN_KEYS, getColumnOptionNamesByIds } from '../metadata-view/_basic';
|
||||
|
||||
import './index.css';
|
||||
|
||||
@@ -47,6 +47,8 @@ const MetadataDetails = ({ repoID, filePath, repoInfo, direntType, emptyTip }) =
|
||||
if (!PREDEFINED_COLUMN_KEYS.includes(field.key) && field.type === CellType.SINGLE_SELECT) {
|
||||
const options = getColumnOptions(field);
|
||||
update = { [fileName]: getOptionName(options, newValue) };
|
||||
} else if (field.type === CellType.MULTIPLE_SELECT) {
|
||||
update = { [fileName]: newValue ? getColumnOptionNamesByIds(field, newValue) : [] };
|
||||
}
|
||||
metadataAPI.modifyRecord(repoID, record._id, update, record._obj_id).then(res => {
|
||||
const newMetadata = { ...metadata, record: { ...record, ...update } };
|
||||
@@ -73,6 +75,9 @@ const MetadataDetails = ({ repoID, filePath, repoInfo, direntType, emptyTip }) =
|
||||
update = { [fileName]: newOption.id };
|
||||
if (!PREDEFINED_COLUMN_KEYS.includes(fieldKey) && newField.type === CellType.SINGLE_SELECT) {
|
||||
update = { [fileName]: getOptionName(options, newOption.id) };
|
||||
} else if (newField.type === CellType.MULTIPLE_SELECT) {
|
||||
const oldValue = getCellValueByColumn(record, newField) || [];
|
||||
update = { [fileName]: [...oldValue, newOption.name] };
|
||||
}
|
||||
return metadataAPI.modifyRecord(repoID, record._id, update, record._obj_id);
|
||||
}).then(res => {
|
||||
|
@@ -53,6 +53,7 @@ const NOT_SUPPORT_EDIT_COLUMN_TYPE_MAP = {
|
||||
|
||||
const MULTIPLE_CELL_VALUE_COLUMN_TYPE_MAP = {
|
||||
[CellType.COLLABORATOR]: true,
|
||||
[CellType.MULTIPLE_SELECT]: true,
|
||||
};
|
||||
const SINGLE_CELL_VALUE_COLUMN_TYPE_MAP = {
|
||||
[CellType.TEXT]: true,
|
||||
|
@@ -13,6 +13,7 @@ const COLUMNS_ICON_CONFIG = {
|
||||
[CellType.DATE]: 'date',
|
||||
[CellType.LONG_TEXT]: 'long-text',
|
||||
[CellType.SINGLE_SELECT]: 'single-select',
|
||||
[CellType.MULTIPLE_SELECT]: 'multiple-select',
|
||||
[CellType.NUMBER]: 'number',
|
||||
[CellType.GEOLOCATION]: 'location',
|
||||
};
|
||||
@@ -30,6 +31,7 @@ const COLUMNS_ICON_NAME = {
|
||||
[CellType.DATE]: 'Date',
|
||||
[CellType.LONG_TEXT]: 'Long text',
|
||||
[CellType.SINGLE_SELECT]: 'Single select',
|
||||
[CellType.MULTIPLE_SELECT]: 'Multiple select',
|
||||
[CellType.NUMBER]: 'Number',
|
||||
[CellType.GEOLOCATION]: 'Geolocation',
|
||||
};
|
||||
|
@@ -11,6 +11,7 @@ const CellType = {
|
||||
DATE: 'date',
|
||||
LONG_TEXT: 'long-text',
|
||||
SINGLE_SELECT: 'single-select',
|
||||
MULTIPLE_SELECT: 'multiple-select',
|
||||
NUMBER: 'number',
|
||||
GEOLOCATION: 'geolocation',
|
||||
};
|
||||
|
@@ -72,6 +72,16 @@ const FILTER_COLUMN_OPTIONS = {
|
||||
FILTER_PREDICATE_TYPE.NOT_EMPTY,
|
||||
],
|
||||
},
|
||||
[CellType.MULTIPLE_SELECT]: {
|
||||
filterPredicateList: [
|
||||
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,
|
||||
],
|
||||
},
|
||||
[CellType.CTIME]: {
|
||||
filterPredicateList: datePredicates,
|
||||
filterTermModifierList: dateTermModifiers,
|
||||
|
@@ -12,6 +12,7 @@ const SORT_COLUMN_OPTIONS = [
|
||||
CellType.TEXT,
|
||||
CellType.DATE,
|
||||
CellType.SINGLE_SELECT,
|
||||
CellType.MULTIPLE_SELECT,
|
||||
CellType.COLLABORATOR,
|
||||
CellType.CHECKBOX,
|
||||
CellType.NUMBER,
|
||||
|
@@ -154,4 +154,6 @@ export {
|
||||
isNumber,
|
||||
getCellValueDisplayString,
|
||||
getCellValueStringResult,
|
||||
getColumnOptionNamesByIds,
|
||||
getColumnOptionIdsByNames,
|
||||
} from './utils';
|
||||
|
@@ -11,6 +11,8 @@ export {
|
||||
export {
|
||||
getOption,
|
||||
getColumnOptionNameById,
|
||||
getColumnOptionNamesByIds,
|
||||
getColumnOptionIdsByNames,
|
||||
getOptionName,
|
||||
getMultipleOptionName,
|
||||
} from './option';
|
||||
|
@@ -36,13 +36,46 @@ const getColumnOptionNameById = (column, optionId) => {
|
||||
return getOptionName(options, optionId);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get column option name by id
|
||||
* @param {object} column e.g. { data: { options, ... }, ... }
|
||||
* @param {array} optionIds
|
||||
* @returns options name, array
|
||||
*/
|
||||
const getColumnOptionNamesByIds = (column, optionIds) => {
|
||||
if (PRIVATE_COLUMN_KEYS.includes(column.key)) return optionIds;
|
||||
if (!Array.isArray(optionIds) || optionIds.length === 0) return [];
|
||||
const options = getColumnOptions(column);
|
||||
if (!Array.isArray(options) || options.length === 0) return [];
|
||||
return optionIds.map(optionId => getOptionName(options, optionId)).filter(name => name);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get column option name by id
|
||||
* @param {object} column e.g. { data: { options, ... }, ... }
|
||||
* @param {array} option names
|
||||
* @returns options id, array
|
||||
*/
|
||||
const getColumnOptionIdsByNames = (column, names) => {
|
||||
if (PRIVATE_COLUMN_KEYS.includes(column.key)) return names;
|
||||
if (!Array.isArray(names) || names.length === 0) return [];
|
||||
const options = getColumnOptions(column);
|
||||
if (!Array.isArray(options) || options.length === 0) return [];
|
||||
return names.map(name => {
|
||||
const option = getOption(options, name);
|
||||
if (option) return option.id;
|
||||
return null;
|
||||
}).filter(name => name);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get concatenated options names of given ids.
|
||||
* @param {array} options e.g. [ { id, color, name, ... }, ... ]
|
||||
* @param {array} targetOptionsIds e.g. [ option.id, ... ]
|
||||
* @returns concatenated options names, string. e.g. 'name1, name2'
|
||||
*/
|
||||
const getMultipleOptionName = (options, targetOptionsIds) => {
|
||||
const getMultipleOptionName = (column, targetOptionsIds) => {
|
||||
const options = getColumnOptions(column);
|
||||
if (!Array.isArray(targetOptionsIds) || !Array.isArray(options)) return '';
|
||||
const selectedOptions = options.filter((option) => targetOptionsIds.includes(option.id));
|
||||
if (selectedOptions.length === 0) return '';
|
||||
@@ -53,5 +86,7 @@ export {
|
||||
getOption,
|
||||
getOptionName,
|
||||
getColumnOptionNameById,
|
||||
getColumnOptionNamesByIds,
|
||||
getColumnOptionIdsByNames,
|
||||
getMultipleOptionName,
|
||||
};
|
||||
|
@@ -28,4 +28,6 @@ export {
|
||||
getGeolocationByGranularity,
|
||||
getFloatNumber,
|
||||
isNumber,
|
||||
getColumnOptionNamesByIds,
|
||||
getColumnOptionIdsByNames,
|
||||
} from './column';
|
||||
|
@@ -5,3 +5,4 @@ export { checkboxFilter } from './checkbox';
|
||||
export { singleSelectFilter } from './single-select';
|
||||
export { collaboratorFilter } from './collaborator';
|
||||
export { numberFilter } from './number';
|
||||
export { multipleSelectFilter } from './multiple-select';
|
||||
|
@@ -0,0 +1,54 @@
|
||||
import { FILTER_PREDICATE_TYPE } from '../../../constants/filter/filter-predicate';
|
||||
|
||||
/**
|
||||
* Filter multiple-select
|
||||
* @param {array} optionIds e.g. [ option.id, ... ]
|
||||
* @param {string} filter_predicate
|
||||
* @param {array} filter_term option ids
|
||||
* @returns bool
|
||||
*/
|
||||
const multipleSelectFilter = (optionIds, { filter_predicate, filter_term }) => {
|
||||
switch (filter_predicate) {
|
||||
case FILTER_PREDICATE_TYPE.HAS_ANY_OF: {
|
||||
return (
|
||||
filter_term.length === 0
|
||||
|| (Array.isArray(optionIds) && optionIds.some((optionId) => filter_term.includes(optionId)))
|
||||
);
|
||||
}
|
||||
case FILTER_PREDICATE_TYPE.HAS_ALL_OF: {
|
||||
return (
|
||||
filter_term.length === 0
|
||||
|| (Array.isArray(optionIds) && filter_term.every((optionId) => optionIds.includes(optionId)))
|
||||
);
|
||||
}
|
||||
case FILTER_PREDICATE_TYPE.HAS_NONE_OF: {
|
||||
if (filter_term.length === 0 || !Array.isArray(optionIds) || optionIds.length === 0) {
|
||||
return true;
|
||||
}
|
||||
return filter_term.every((optionId) => optionIds.indexOf(optionId) < 0);
|
||||
}
|
||||
case FILTER_PREDICATE_TYPE.IS_EXACTLY: {
|
||||
if (filter_term.length === 0) {
|
||||
return true;
|
||||
}
|
||||
if (!Array.isArray(optionIds)) {
|
||||
return false;
|
||||
}
|
||||
const uniqueArr = (arr) => [...new Set(arr)].sort();
|
||||
return uniqueArr(optionIds).toString() === uniqueArr(filter_term).toString();
|
||||
}
|
||||
case FILTER_PREDICATE_TYPE.EMPTY: {
|
||||
return !Array.isArray(optionIds) || optionIds.length === 0;
|
||||
}
|
||||
case FILTER_PREDICATE_TYPE.NOT_EMPTY: {
|
||||
return Array.isArray(optionIds) && optionIds.length > 0;
|
||||
}
|
||||
default: {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export {
|
||||
multipleSelectFilter,
|
||||
};
|
@@ -10,6 +10,7 @@ import {
|
||||
singleSelectFilter,
|
||||
collaboratorFilter,
|
||||
numberFilter,
|
||||
multipleSelectFilter,
|
||||
} from './filter-column';
|
||||
import {
|
||||
FILTER_CONJUNCTION_TYPE,
|
||||
@@ -42,6 +43,9 @@ const getFilterResult = (row, filter, { username, userId }) => {
|
||||
case CellType.SINGLE_SELECT: {
|
||||
return singleSelectFilter(cellValue, filter);
|
||||
}
|
||||
case CellType.MULTIPLE_SELECT: {
|
||||
return multipleSelectFilter(cellValue, filter);
|
||||
}
|
||||
case CellType.NUMBER: {
|
||||
return numberFilter(cellValue, filter);
|
||||
}
|
||||
|
@@ -5,6 +5,8 @@ import {
|
||||
sortNumber,
|
||||
sortCheckbox,
|
||||
sortCollaborator,
|
||||
sortSingleSelect,
|
||||
sortMultipleSelect,
|
||||
} from '../sort/sort-column';
|
||||
import { MAX_GROUP_LEVEL } from '../../constants/group';
|
||||
import {
|
||||
@@ -45,6 +47,9 @@ const _getFormattedCellValue = (cellValue, groupby) => {
|
||||
case CellType.SINGLE_SELECT: {
|
||||
return cellValue || null;
|
||||
}
|
||||
case CellType.MULTIPLE_SELECT: {
|
||||
return Array.isArray(cellValue) ? cellValue : [];
|
||||
}
|
||||
case CellType.COLLABORATOR: {
|
||||
return Array.isArray(cellValue) ? cellValue : [];
|
||||
}
|
||||
@@ -98,8 +103,18 @@ const _findGroupIndex = (sCellValue, cellValue2GroupIndexMap, groupsLength) => {
|
||||
const getSortedGroups = (groups, groupbys, level, collaborators = []) => {
|
||||
const sortFlag = 0;
|
||||
const { column, sort_type } = groupbys[level];
|
||||
const { type: columnType } = column;
|
||||
const { type: columnType, data: columnData } = column;
|
||||
const normalizedSortType = sort_type || SORT_TYPE.UP;
|
||||
let option_id_index_map = {};
|
||||
if (columnType === CellType.SINGLE_SELECT || columnType === CellType.MULTIPLE_SELECT) {
|
||||
const { options } = columnData || {};
|
||||
if (Array.isArray(options)) {
|
||||
options.forEach((option, index) => {
|
||||
option_id_index_map[option.id] = index;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
groups.sort((currGroupRow, nextGroupRow) => {
|
||||
let { cell_value: currCellVal } = currGroupRow;
|
||||
let { cell_value: nextCellVal } = nextGroupRow;
|
||||
@@ -121,6 +136,10 @@ const getSortedGroups = (groups, groupbys, level, collaborators = []) => {
|
||||
nextCollaborators = getCollaboratorsNames(nextCollaborators, collaborators);
|
||||
}
|
||||
sortResult = sortCollaborator(currCollaborators, nextCollaborators, normalizedSortType);
|
||||
} else if (columnType === CellType.SINGLE_SELECT) {
|
||||
sortResult = sortSingleSelect(currCellVal, nextCellVal, { sort_type: normalizedSortType, option_id_index_map });
|
||||
} else if (columnType === CellType.MULTIPLE_SELECT) {
|
||||
sortResult = sortMultipleSelect(currCellVal, nextCellVal, { sort_type: normalizedSortType, option_id_index_map });
|
||||
}
|
||||
return sortFlag || sortResult;
|
||||
}
|
||||
|
@@ -25,6 +25,8 @@ export {
|
||||
isNumber,
|
||||
getCellValueDisplayString,
|
||||
getCellValueStringResult,
|
||||
getColumnOptionNamesByIds,
|
||||
getColumnOptionIdsByNames,
|
||||
} from './cell';
|
||||
export {
|
||||
getColumnType,
|
||||
|
@@ -61,7 +61,8 @@ const deleteInvalidSort = (sorts, columns) => {
|
||||
let newSort = { ...sort, column: sortColumn };
|
||||
const { type: columnType } = sortColumn;
|
||||
switch (columnType) {
|
||||
case CellType.SINGLE_SELECT: {
|
||||
case CellType.SINGLE_SELECT:
|
||||
case CellType.MULTIPLE_SELECT: {
|
||||
const options = getColumnOptions(sortColumn);
|
||||
let option_id_index_map = {};
|
||||
options.forEach((option, index) => {
|
||||
|
@@ -13,6 +13,7 @@ export {
|
||||
sortCollaborator,
|
||||
sortNumber,
|
||||
sortSingleSelect,
|
||||
sortMultipleSelect,
|
||||
} from './sort-column';
|
||||
|
||||
export {
|
||||
|
@@ -8,3 +8,4 @@ export { sortCheckbox } from './checkbox';
|
||||
export { sortCollaborator } from './collaborator';
|
||||
export { sortNumber } from './number';
|
||||
export { sortSingleSelect } from './single-select';
|
||||
export { sortMultipleSelect } from './multiple-select';
|
||||
|
@@ -0,0 +1,50 @@
|
||||
import { getMultipleIndexesOrderbyOptions } from '../core';
|
||||
import { SORT_TYPE } from '../../../constants/sort';
|
||||
|
||||
/**
|
||||
* Sort multiple-select
|
||||
* @param {array} leftOptionIds the ids of options
|
||||
* @param {array} rightOptionIds
|
||||
* @param {string} sort_type e.g. 'up' | 'down'
|
||||
* @param {object} option_id_index_map e.g. { [option.id]: 0, ... }
|
||||
* @returns number
|
||||
*/
|
||||
const sortMultipleSelect = (leftOptionIds, rightOptionIds, { sort_type, option_id_index_map }) => {
|
||||
const emptyLeftOptionIds = !leftOptionIds || leftOptionIds.length === 0;
|
||||
const emptyRightOptionIds = !rightOptionIds || rightOptionIds.length === 0;
|
||||
if (emptyLeftOptionIds && emptyRightOptionIds) return 0;
|
||||
if (emptyLeftOptionIds) return 1;
|
||||
if (emptyRightOptionIds) return -1;
|
||||
|
||||
const leftOptionIndexes = getMultipleIndexesOrderbyOptions(leftOptionIds, option_id_index_map);
|
||||
const rightOptionIndexes = getMultipleIndexesOrderbyOptions(rightOptionIds, option_id_index_map);
|
||||
const leftOptionsLen = leftOptionIndexes.length;
|
||||
const rightOptionsLen = rightOptionIndexes.length;
|
||||
|
||||
// current multiple select equal to next multiple select.
|
||||
if (
|
||||
leftOptionsLen === rightOptionsLen
|
||||
&& (leftOptionsLen === 0 || leftOptionIndexes.join('') === rightOptionIndexes.join(''))
|
||||
) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const len = Math.min(leftOptionsLen, rightOptionsLen);
|
||||
for (let i = 0; i < len; i++) {
|
||||
if (leftOptionIndexes[i] > rightOptionIndexes[i]) {
|
||||
return sort_type === SORT_TYPE.UP ? 1 : -1;
|
||||
}
|
||||
if (leftOptionIndexes[i] < rightOptionIndexes[i]) {
|
||||
return sort_type === SORT_TYPE.UP ? -1 : 1;
|
||||
}
|
||||
}
|
||||
if (leftOptionsLen > rightOptionsLen) {
|
||||
return sort_type === SORT_TYPE.UP ? 1 : -1;
|
||||
}
|
||||
|
||||
return sort_type === SORT_TYPE.UP ? -1 : 1;
|
||||
};
|
||||
|
||||
export {
|
||||
sortMultipleSelect,
|
||||
};
|
@@ -6,6 +6,7 @@ import {
|
||||
sortNumber,
|
||||
sortCollaborator,
|
||||
sortCheckbox,
|
||||
sortMultipleSelect,
|
||||
} from './sort-column';
|
||||
import { CellType, DATE_COLUMN_OPTIONS } from '../../constants/column';
|
||||
import { getCellValueByColumn, getCollaboratorsNames } from '../cell';
|
||||
@@ -31,6 +32,8 @@ const sortRowsWithMultiSorts = (tableRows, sorts, { collaborators }) => {
|
||||
initValue = initValue || sortSingleSelect(currCellVal, nextCellVal, sort);
|
||||
} else if (NUMBER_SORTER_COLUMN_TYPES.includes(columnType)) {
|
||||
initValue = initValue || sortNumber(currCellVal, nextCellVal, sort_type);
|
||||
} else if (columnType === CellType.MULTIPLE_SELECT) {
|
||||
initValue = initValue || sortMultipleSelect(currCellVal, nextCellVal, sort);
|
||||
} else if (columnType === CellType.COLLABORATOR) {
|
||||
let currValidCollaborators = currCellVal;
|
||||
let nextValidCollaborators = nextCellVal;
|
||||
|
@@ -145,10 +145,9 @@ class ValidateFilter {
|
||||
}
|
||||
|
||||
// Filter predicate should support: is_empty/is_not_empty(excludes checkbox and bool)
|
||||
if (CHECK_EMPTY_PREDICATES.includes(predicate)) {
|
||||
return true;
|
||||
}
|
||||
if (array_type === CellType.SINGLE_SELECT) {
|
||||
if (CHECK_EMPTY_PREDICATES.includes(predicate)) return true;
|
||||
|
||||
if (array_type === CellType.SINGLE_SELECT || array_type === CellType.DEPARTMENT_SINGLE_SELECT) {
|
||||
return this.validatePredicate(predicate, { type: CellType.MULTIPLE_SELECT });
|
||||
}
|
||||
if (COLLABORATOR_COLUMN_TYPES.includes(array_type)) {
|
||||
@@ -272,6 +271,15 @@ class ValidateFilter {
|
||||
// invalid filter_term if selected option is deleted
|
||||
return !!options.find((option) => term === option.id);
|
||||
}
|
||||
case CellType.MULTIPLE_SELECT: {
|
||||
if (!this.isValidTermType(term, TERM_TYPE_MAP.ARRAY)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// contains deleted option(s)
|
||||
const options = getColumnOptions(filterColumn);
|
||||
return this.isValidSelectedOptions(term, options);
|
||||
}
|
||||
default: {
|
||||
return false;
|
||||
}
|
||||
@@ -299,9 +307,6 @@ class ValidateFilter {
|
||||
type: CellType.MULTIPLE_SELECT, data: array_data,
|
||||
});
|
||||
}
|
||||
if (array_type === CellType.DEPARTMENT_SINGLE_SELECT) {
|
||||
return this.isValidTermType(term, TERM_TYPE_MAP.ARRAY);
|
||||
}
|
||||
if (COLLABORATOR_COLUMN_TYPES.includes(array_type)) {
|
||||
return this.isValidTerm(term, predicate, modifier, { type: CellType.COLLABORATOR });
|
||||
}
|
||||
|
@@ -2,8 +2,9 @@
|
||||
background-color: #f6f6f6;
|
||||
border-bottom: 1px solid #dde2ea;
|
||||
border-radius: 3px 3px 0 0;
|
||||
min-height: 34px;
|
||||
min-height: 35px;
|
||||
padding: 5px 10px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.collaborator {
|
||||
@@ -46,6 +47,11 @@
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.sf-metadata-delete-collaborator .collaborator {
|
||||
margin-top: 2px;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.sf-metadata-delete-collaborator .collaborator .collaborator-remove {
|
||||
height: 14px;
|
||||
width: 14px;
|
||||
|
@@ -9,6 +9,7 @@ const POPUP_EDITOR_COLUMN_TYPES = [
|
||||
CellType.DATE,
|
||||
CellType.COLLABORATOR,
|
||||
CellType.SINGLE_SELECT,
|
||||
CellType.MULTIPLE_SELECT,
|
||||
CellType.LONG_TEXT,
|
||||
];
|
||||
|
||||
|
@@ -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 {
|
||||
@@ -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);
|
||||
|
@@ -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 (<SingleSelectEditor ref={ref} {...props} />);
|
||||
}
|
||||
case CellType.MULTIPLE_SELECT: {
|
||||
return (<MultipleSelectEditor ref={ref} {...props} />);
|
||||
}
|
||||
case CellType.COLLABORATOR: {
|
||||
return (<CollaboratorEditor ref={ref} {...props} />);
|
||||
}
|
||||
|
@@ -0,0 +1,25 @@
|
||||
.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;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.sf-metadata-delete-select-options .sf-metadata-delete-select-option .sf-metadata-delete-select-remove {
|
||||
height: 14px;
|
||||
width: 14px;
|
||||
margin-left: 2px;
|
||||
}
|
||||
|
||||
.sf-metadata-delete-select-options .sf-metadata-delete-select-option .sf-metadata-icon-x-01 {
|
||||
fill: inherit;
|
||||
font-size: 12px;
|
||||
}
|
@@ -0,0 +1,59 @@
|
||||
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]);
|
||||
|
||||
if (displayOptions.length === 0) return null;
|
||||
|
||||
return (
|
||||
<div className="sf-metadata-delete-select-options">
|
||||
{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 (
|
||||
<div key={id} className="sf-metadata-delete-select-option" style={style}>
|
||||
<span className="sf-metadata-delete-select-option-name text-truncate" title={name} aria-label={name}>{name}</span>
|
||||
<IconBtn className="sf-metadata-delete-select-remove" onClick={(event) => onDelete(id, event)} iconName="x-01" />
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
DeleteOption.propTypes = {
|
||||
value: PropTypes.array.isRequired,
|
||||
onDelete: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default DeleteOption;
|
@@ -0,0 +1,278 @@
|
||||
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(({
|
||||
saveImmediately,
|
||||
column,
|
||||
value: oldValue,
|
||||
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 = (parseInt(editorRef.current.style.top) - bottom + 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 (<span className="none-search-result">{noOptionsTip}</span>);
|
||||
}
|
||||
|
||||
return displayOptions.map((option, i) => {
|
||||
const isSelected = value.includes(option.id);
|
||||
return (
|
||||
<div key={option.id} className="sf-metadata-single-select-item" ref={selectItemRef}>
|
||||
<div
|
||||
className={classnames('single-select-container', { 'single-select-container-highlight': i === highlightIndex })}
|
||||
onMouseDown={() => onSelectOption(option.id)}
|
||||
onMouseEnter={() => onMenuMouseEnter(i)}
|
||||
onMouseLeave={() => onMenuMouseLeave(i)}
|
||||
>
|
||||
<div className="single-select">
|
||||
<span
|
||||
className="single-select-name"
|
||||
style={{ backgroundColor: option.color, color: option.textColor || null }}
|
||||
title={option.name}
|
||||
aria-label={option.name}
|
||||
>
|
||||
{option.name}
|
||||
</span>
|
||||
</div>
|
||||
<div className="single-select-check-icon">
|
||||
{isSelected && (<Icon iconName="check-mark" />)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
}, [displayOptions, searchValue, value, highlightIndex, onMenuMouseEnter, onMenuMouseLeave, onSelectOption]);
|
||||
|
||||
return (
|
||||
<div className="sf-metadata-single-select-editor sf-metadata-multiple-select-editor" style={style} ref={editorRef}>
|
||||
<DeleteOption value={value} options={options} onDelete={onDeleteOption} />
|
||||
<div className="sf-metadata-search-single-select-options">
|
||||
<SearchInput
|
||||
placeholder={gettext('Search option')}
|
||||
onKeyDown={onKeyDown}
|
||||
onChange={onChangeSearch}
|
||||
autoFocus={true}
|
||||
className="sf-metadata-search-options"
|
||||
/>
|
||||
</div>
|
||||
<div className="sf-metadata-single-select-editor-container" ref={editorContainerRef}>
|
||||
{renderOptions()}
|
||||
</div>
|
||||
{isShowCreateBtn && (
|
||||
<CustomizeAddTool
|
||||
callBack={createOption}
|
||||
footerName={`${gettext('Add option')} ${searchValue}`}
|
||||
className="add-search-result"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
MultipleSelectEditor.propTypes = {
|
||||
column: PropTypes.object,
|
||||
value: PropTypes.array,
|
||||
onCommit: PropTypes.func,
|
||||
onPressTab: PropTypes.func,
|
||||
};
|
||||
|
||||
export default MultipleSelectEditor;
|
@@ -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 (<CheckboxEditor { ...props } field={field} onChange={onChange} />);
|
||||
@@ -33,6 +33,9 @@ const DetailEditor = ({ field, onChange: onChangeAPI, ...props }) => {
|
||||
case CellType.SINGLE_SELECT: {
|
||||
return (<SingleSelectEditor { ...props } field={field} onChange={onChange} />);
|
||||
}
|
||||
case CellType.MULTIPLE_SELECT: {
|
||||
return (<MultipleSelectEditor { ...props } field={field} onChange={onChange} />);
|
||||
}
|
||||
case CellType.COLLABORATOR: {
|
||||
return (<CollaboratorEditor { ...props } field={field} onChange={onChange} />);
|
||||
}
|
||||
|
@@ -0,0 +1,21 @@
|
||||
.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-detail-editor .sf-metadata-delete-select-options .sf-metadata-delete-select-option {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.sf-metadata-multiple-select-property-editor-popover .sf-metadata-delete-select-options {
|
||||
display: none;
|
||||
}
|
@@ -0,0 +1,106 @@
|
||||
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 (
|
||||
<Popover
|
||||
target={ref}
|
||||
isOpen={true}
|
||||
placement="bottom-end"
|
||||
hideArrow={true}
|
||||
fade={false}
|
||||
className="sf-metadata-property-editor-popover sf-metadata-single-select-property-editor-popover sf-metadata-multiple-select-property-editor-popover"
|
||||
boundariesElement={document.body}
|
||||
>
|
||||
<Editor
|
||||
saveImmediately={true}
|
||||
value={value}
|
||||
column={{ ...field, width: Math.max(width - 2, 200) }}
|
||||
columns={fields}
|
||||
modifyColumnData={modifyColumnData}
|
||||
record={record}
|
||||
onCommit={onCommit}
|
||||
/>
|
||||
</Popover>
|
||||
);
|
||||
}, [showEditor, onCommit, record, value, modifyColumnData, fields, field]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className="sf-metadata-property-detail-editor sf-metadata-single-select-property-detail-editor sf-metadata-multiple-select-property-detail-editor"
|
||||
placeholder={gettext('Empty')}
|
||||
ref={ref}
|
||||
onClick={openEditor}
|
||||
>
|
||||
<DeleteOptions value={value} options={options} onDelete={deleteOption} />
|
||||
{renderEditor()}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
MultipleSelectEditor.propTypes = {
|
||||
field: PropTypes.object.isRequired,
|
||||
value: PropTypes.array,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default MultipleSelectEditor;
|
@@ -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 };
|
||||
|
@@ -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.COLLABORATOR], type: CellType.COLLABORATOR, name: gettext(COLUMNS_ICON_NAME[CellType.COLLABORATOR]), canChangeName: true, key: CellType.COLLABORATOR, 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.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.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.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
|
||||
|
@@ -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 {
|
||||
<div className="filter-term ml-2">
|
||||
{this.renderFilterTerm(filterColumn)}
|
||||
</div>
|
||||
{showToolTip &&
|
||||
<div className="ml-2">
|
||||
<span ref={this.filterToolTip} id="filter_tool_tip" aria-hidden="true" className="sf-metadata-font sf-metadata-icon-exclamation-triangle" style={{ color: '#FFC92C' }}></span>
|
||||
{/* <UncontrolledTooltip placement="bottom" target={this.filterToolTip}></UncontrolledTooltip> */}
|
||||
</div>
|
||||
}
|
||||
{/* {showToolTip && (
|
||||
<div className="ml-2" >
|
||||
<IconBtn id={`filter-tool-tip-${filterColumn.key}`} iconName="exclamation-triangle" style={{ color: '#FFC92C' }} />
|
||||
<UncontrolledTooltip placement="bottom" target={`filter-tool-tip-${filterColumn.key}`} >
|
||||
{gettext('If there are multiple items in the cell, a random one will be chosen and be compared with the filter value.')}
|
||||
</UncontrolledTooltip>
|
||||
</div>
|
||||
)} */}
|
||||
{this.renderErrorMessage()}
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -1,5 +1,6 @@
|
||||
/* basic css */
|
||||
.sf-metadata-single-select-option {
|
||||
.sf-metadata-single-select-option,
|
||||
.sf-metadata-multiple-select-option {
|
||||
border-radius: 10px;
|
||||
font-size: 13px;
|
||||
line-height: 20px;
|
||||
@@ -12,5 +13,3 @@
|
||||
white-space: nowrap;
|
||||
width: min-content;
|
||||
}
|
||||
|
||||
|
||||
|
@@ -7,11 +7,11 @@ import GridUtils from '../../../utils/grid-utils';
|
||||
|
||||
import './index.css';
|
||||
|
||||
const TableMain = ({ metadata, modifyRecord, modifyRecords, loadMore, loadAll, searchResult, recordGetterByIndex, recordGetterById, ...params }) => {
|
||||
const TableMain = ({ metadata, modifyRecord, modifyRecords, loadMore, loadAll, searchResult, recordGetterByIndex, recordGetterById, modifyColumnData, ...params }) => {
|
||||
|
||||
const gridUtils = useMemo(() => {
|
||||
return new GridUtils(metadata, { modifyRecord, modifyRecords, recordGetterByIndex, recordGetterById });
|
||||
}, [metadata, modifyRecord, modifyRecords, recordGetterByIndex, recordGetterById]);
|
||||
return new GridUtils(metadata, { modifyRecord, modifyRecords, recordGetterByIndex, recordGetterById, modifyColumnData });
|
||||
}, [metadata, modifyRecord, modifyRecords, recordGetterByIndex, recordGetterById, modifyColumnData]);
|
||||
|
||||
const groupbysCount = useMemo(() => {
|
||||
const groupbys = metadata?.view?.groupbys || [];
|
||||
@@ -63,6 +63,7 @@ const TableMain = ({ metadata, modifyRecord, modifyRecords, loadMore, loadAll, s
|
||||
getCopiedRecordsAndColumnsFromRange={getCopiedRecordsAndColumnsFromRange}
|
||||
recordGetterById={recordGetterById}
|
||||
recordGetterByIndex={recordGetterByIndex}
|
||||
modifyColumnData={modifyColumnData}
|
||||
{...params}
|
||||
/>
|
||||
</div>
|
||||
|
@@ -45,6 +45,29 @@ const GroupTitle = ({ column, cellValue, originalCellValue }) => {
|
||||
const optionName = selectedOption ? selectedOption.name : deletedOptionTip;
|
||||
return (<div className="sf-metadata-single-select-option" style={style} key={cellValue} title={optionName}>{optionName}</div>);
|
||||
}
|
||||
case CellType.MULTIPLE_SELECT: {
|
||||
const options = getColumnOptions(column);
|
||||
if (options.length === 0 || !Array.isArray(originalCellValue) || originalCellValue.length === 0) return emptyTip;
|
||||
const selectedOptions = options.filter((option) => originalCellValue.includes(option.id) || originalCellValue.includes(option.name));
|
||||
const invalidOptionIds = originalCellValue.filter(optionId => optionId && !options.find(o => o.id === optionId || o.name === optionId));
|
||||
const invalidOptions = invalidOptionIds.map(optionId => ({
|
||||
id: optionId,
|
||||
name: deletedOptionTip,
|
||||
color: DELETED_OPTION_BACKGROUND_COLOR,
|
||||
}));
|
||||
return (
|
||||
<>
|
||||
{selectedOptions.map(option => {
|
||||
const style = { backgroundColor: option.color, color: option.textColor };
|
||||
return (<div className="sf-metadata-multiple-select-option" style={style} key={option.id} title={option.name}>{option.name}</div>);
|
||||
})}
|
||||
{invalidOptions.map(option => {
|
||||
const style = { backgroundColor: option.color };
|
||||
return (<div className="sf-metadata-multiple-select-option" style={style} key={option.id} title={option.name}>{option.name}</div>);
|
||||
})}
|
||||
</>
|
||||
);
|
||||
}
|
||||
default: {
|
||||
return cellValue || emptyTip;
|
||||
}
|
||||
|
@@ -213,6 +213,14 @@
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.canvas-groups-rows .group-title .sf-metadata-multiple-select-option {
|
||||
margin: 0 10px 0 0;
|
||||
}
|
||||
|
||||
.canvas-groups-rows .group-title .sf-metadata-multiple-select-option:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.canvas-groups-rows .collaborator-avatar,
|
||||
.canvas-groups-rows .sf-metadata-ui.collaborator-item .collaborator-avatar {
|
||||
height: 16px;
|
||||
|
@@ -181,6 +181,16 @@ const HeaderDropdownMenu = ({ column, renameColumn, modifyColumnData, deleteColu
|
||||
/> */}
|
||||
</>
|
||||
)}
|
||||
{type === CellType.MULTIPLE_SELECT && (
|
||||
<DropdownItem
|
||||
disabled={!canModifyColumnData}
|
||||
target="sf-metadata-edit-column-options"
|
||||
iconName="multiple-select"
|
||||
title={gettext('Edit multiple select')}
|
||||
tip={gettext('You do not have permission')}
|
||||
onChange={openOptionPopover}
|
||||
/>
|
||||
)}
|
||||
{type === CellType.NUMBER && (
|
||||
<DropdownItem
|
||||
disabled={!canModifyColumnData}
|
||||
@@ -194,7 +204,7 @@ const HeaderDropdownMenu = ({ column, renameColumn, modifyColumnData, deleteColu
|
||||
{type === CellType.DATE && (
|
||||
<>{renderDateFormat(canModifyColumnData)}</>
|
||||
)}
|
||||
{[CellType.DATE, CellType.SINGLE_SELECT, CellType.NUMBER].includes(column.type) && (
|
||||
{[CellType.DATE, CellType.SINGLE_SELECT, CellType.NUMBER, CellType.MULTIPLE_SELECT].includes(column.type) && (
|
||||
<DefaultDropdownItem key="divider-item" divider />
|
||||
)}
|
||||
<DropdownItem
|
||||
|
@@ -1,45 +1,17 @@
|
||||
import { CellType, DEFAULT_DATE_FORMAT, generatorCellOption, getCollaboratorsName, getOptionName, getDateDisplayString,
|
||||
PREDEFINED_COLUMN_KEYS, getFloatNumber, getNumberDisplayString, formatStringToNumber, isNumber, getColumnOptions,
|
||||
generatorCellOptions,
|
||||
getColumnOptionNamesByIds,
|
||||
} from '../_basic';
|
||||
import { formatTextToDate } from './date';
|
||||
|
||||
const SUPPORT_PASTE_FROM_COLUMN = {
|
||||
[CellType.MULTIPLE_SELECT]: [CellType.MULTIPLE_SELECT, CellType.TEXT, CellType.SINGLE_SELECT],
|
||||
[CellType.NUMBER]: [CellType.TEXT, CellType.NUMBER],
|
||||
};
|
||||
|
||||
const reg_chinese_date_format = /(\d{4})年(\d{1,2})月(\d{1,2})日$/;
|
||||
|
||||
function convertCellValue(cellValue, oldCellValue, targetColumn, fromColumn) {
|
||||
const { type: fromColumnType, data: fromColumnData } = fromColumn;
|
||||
const { type: targetColumnType, data: targetColumnData } = targetColumn;
|
||||
switch (targetColumnType) {
|
||||
case CellType.CHECKBOX: {
|
||||
return convert2Checkbox(cellValue, oldCellValue, fromColumnType);
|
||||
}
|
||||
case CellType.NUMBER: {
|
||||
return convert2Number(cellValue, oldCellValue, fromColumnType, targetColumnData);
|
||||
}
|
||||
case CellType.DATE: {
|
||||
return convert2Date(cellValue, oldCellValue, fromColumnType, fromColumnData, targetColumnData);
|
||||
}
|
||||
case CellType.SINGLE_SELECT: {
|
||||
return convert2SingleSelect(cellValue, oldCellValue, fromColumn, targetColumn);
|
||||
}
|
||||
case CellType.LONG_TEXT: {
|
||||
return convert2LongText(cellValue, oldCellValue, fromColumn);
|
||||
}
|
||||
case CellType.TEXT: {
|
||||
return convert2Text(cellValue, oldCellValue, fromColumn);
|
||||
}
|
||||
case CellType.COLLABORATOR: {
|
||||
return convert2Collaborator(cellValue, oldCellValue, fromColumnType);
|
||||
}
|
||||
default: {
|
||||
return oldCellValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function convert2Checkbox(cellValue, oldCellValue, fromColumnType) {
|
||||
switch (fromColumnType) {
|
||||
case CellType.CHECKBOX: {
|
||||
@@ -138,6 +110,12 @@ function convert2SingleSelect(cellValue, oldCellValue, fromColumn, targetColumn)
|
||||
fromOptionName = getOptionName(fromOptions, cellValue) || '';
|
||||
break;
|
||||
}
|
||||
case CellType.MULTIPLE_SELECT: {
|
||||
const copiedOptions = getColumnOptions(fromColumn);
|
||||
const copiedCellVal = cellValue[0];
|
||||
fromOptionName = getOptionName(copiedOptions, copiedCellVal) || '';
|
||||
break;
|
||||
}
|
||||
case CellType.TEXT: {
|
||||
fromOptionName = cellValue;
|
||||
break;
|
||||
@@ -292,4 +270,94 @@ function convert2Collaborator(cellValue, oldCellValue, fromColumnType) {
|
||||
}
|
||||
}
|
||||
|
||||
const _getPasteMultipleSelect = (copiedCellVal, pasteCellVal, copiedColumn, pasteColumn) => {
|
||||
const { type: copiedColumnType } = copiedColumn;
|
||||
if (!copiedCellVal ||
|
||||
(Array.isArray(copiedCellVal) && copiedCellVal.length === 0) ||
|
||||
!SUPPORT_PASTE_FROM_COLUMN[CellType.MULTIPLE_SELECT].includes(copiedColumnType)
|
||||
) {
|
||||
return { selectedOptionIds: pasteCellVal };
|
||||
}
|
||||
let copiedOptionNames = [];
|
||||
if (copiedColumnType === CellType.MULTIPLE_SELECT) {
|
||||
const copiedOptions = getColumnOptions(copiedColumn);
|
||||
copiedOptionNames = copiedOptions.filter((option) => copiedCellVal.includes(option.id) || copiedCellVal.includes(option.name))
|
||||
.map((option) => option.name);
|
||||
} else if (copiedColumnType === CellType.TEXT) {
|
||||
const sCopiedCellVal = String(copiedCellVal);
|
||||
|
||||
// Pass excel test, wps test failed
|
||||
copiedOptionNames = sCopiedCellVal.split('\n');
|
||||
|
||||
// get option names from string like 'a, b, c'
|
||||
if (copiedOptionNames.length === 1) {
|
||||
copiedOptionNames = sCopiedCellVal.split(',');
|
||||
}
|
||||
copiedOptionNames = copiedOptionNames.map(name => name.trim())
|
||||
.filter(name => name !== '');
|
||||
} else if (copiedColumnType === CellType.SINGLE_SELECT) {
|
||||
const copiedOptions = getColumnOptions(copiedColumn);
|
||||
copiedOptionNames = copiedOptions.filter((option) => option.id === copiedCellVal)
|
||||
.map((option) => option.name);
|
||||
}
|
||||
if (copiedOptionNames.length === 0) {
|
||||
return { selectedOptionIds: pasteCellVal };
|
||||
}
|
||||
|
||||
const pasteOptions = getColumnOptions(pasteColumn);
|
||||
const { cellOptions: newCellOptions, selectedOptionIds } = generatorCellOptions(pasteOptions, copiedOptionNames);
|
||||
return { pasteOptions, newCellOptions, selectedOptionIds };
|
||||
};
|
||||
|
||||
const convert2MultipleSelect = (copiedCellVal, pasteCellVal, copiedColumn, pasteColumn, api) => {
|
||||
const { newCellOptions, pasteOptions, selectedOptionIds } = _getPasteMultipleSelect(copiedCellVal, pasteCellVal, copiedColumn, pasteColumn);
|
||||
let newColumn = pasteColumn;
|
||||
|
||||
// the target column have no options with the same name
|
||||
if (newCellOptions) {
|
||||
if (!window.sfMetadataContext.canModifyColumnData(pasteColumn)) return null;
|
||||
const updatedPasteOptions = [...pasteOptions, ...newCellOptions];
|
||||
if (!newColumn.data) {
|
||||
newColumn.data = {};
|
||||
}
|
||||
newColumn.data.options = updatedPasteOptions;
|
||||
api.modifyColumnData(pasteColumn.key, { options: updatedPasteOptions }, pasteColumn.data);
|
||||
}
|
||||
return getColumnOptionNamesByIds(newColumn, selectedOptionIds);
|
||||
};
|
||||
|
||||
function convertCellValue(cellValue, oldCellValue, targetColumn, fromColumn, api) {
|
||||
const { type: fromColumnType, data: fromColumnData } = fromColumn;
|
||||
const { type: targetColumnType, data: targetColumnData } = targetColumn;
|
||||
switch (targetColumnType) {
|
||||
case CellType.CHECKBOX: {
|
||||
return convert2Checkbox(cellValue, oldCellValue, fromColumnType);
|
||||
}
|
||||
case CellType.NUMBER: {
|
||||
return convert2Number(cellValue, oldCellValue, fromColumnType, targetColumnData);
|
||||
}
|
||||
case CellType.DATE: {
|
||||
return convert2Date(cellValue, oldCellValue, fromColumnType, fromColumnData, targetColumnData);
|
||||
}
|
||||
case CellType.SINGLE_SELECT: {
|
||||
return convert2SingleSelect(cellValue, oldCellValue, fromColumn, targetColumn);
|
||||
}
|
||||
case CellType.MULTIPLE_SELECT: {
|
||||
return convert2MultipleSelect(cellValue, oldCellValue, fromColumn, targetColumn, api);
|
||||
}
|
||||
case CellType.LONG_TEXT: {
|
||||
return convert2LongText(cellValue, oldCellValue, fromColumn);
|
||||
}
|
||||
case CellType.TEXT: {
|
||||
return convert2Text(cellValue, oldCellValue, fromColumn);
|
||||
}
|
||||
case CellType.COLLABORATOR: {
|
||||
return convert2Collaborator(cellValue, oldCellValue, fromColumnType);
|
||||
}
|
||||
default: {
|
||||
return oldCellValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export { convertCellValue };
|
||||
|
@@ -107,7 +107,7 @@ class GridUtils {
|
||||
const copiedColumnName = getColumnOriginName(copiedColumn);
|
||||
const pasteCellValue = Object.prototype.hasOwnProperty.call(pasteRecord, pasteColumnName) ? getCellValueByColumn(pasteRecord, pasteColumn) : null;
|
||||
const copiedCellValue = Object.prototype.hasOwnProperty.call(copiedRecord, copiedColumnName) ? getCellValueByColumn(copiedRecord, copiedColumn) : null;
|
||||
const update = convertCellValue(copiedCellValue, pasteCellValue, pasteColumn, copiedColumn);
|
||||
const update = convertCellValue(copiedCellValue, pasteCellValue, pasteColumn, copiedColumn, this.api);
|
||||
if (update === pasteCellValue) {
|
||||
continue;
|
||||
}
|
||||
|
Reference in New Issue
Block a user