mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-17 15:53:28 +00:00
feat: tags keydown support(ctrl+c, delete, drag) (#7107)
* feat: tags keydown support(ctrl+c, delete, drag) * feat: optimize code * feat: optimize code --------- Co-authored-by: 杨国璇 <ygx@Hello-word.local>
This commit is contained in:
@@ -58,7 +58,7 @@ class PopupEditorContainer extends React.Component {
|
||||
};
|
||||
|
||||
createEditor = () => {
|
||||
const { column, record, height, onPressTab, editorPosition, columns, modifyColumnData, addFileTags, updateFileTags } = this.props;
|
||||
const { column, record, height, onPressTab, editorPosition, columns, modifyColumnData, updateFileTags } = this.props;
|
||||
const readOnly = !canEditCell(column, record, true) || NOT_SUPPORT_EDITOR_COLUMN_TYPES.includes(column.type);
|
||||
const value = this.getInitialValue(readOnly);
|
||||
|
||||
@@ -81,7 +81,6 @@ class PopupEditorContainer extends React.Component {
|
||||
column,
|
||||
readOnly,
|
||||
onPressTab,
|
||||
addFileTags,
|
||||
updateFileTags,
|
||||
};
|
||||
|
||||
|
@@ -22,7 +22,6 @@ const TagsEditor = forwardRef(({
|
||||
value: oldValue,
|
||||
editorPosition = { left: 0, top: 0 },
|
||||
onPressTab,
|
||||
addFileTags,
|
||||
updateFileTags,
|
||||
}, ref) => {
|
||||
const { tagsData, addTag } = useTags();
|
||||
@@ -68,12 +67,8 @@ const TagsEditor = forwardRef(({
|
||||
}
|
||||
setValue(newValue);
|
||||
const recordId = getRecordIdFromRecord(record);
|
||||
if (value.length === 0) {
|
||||
addFileTags(recordId, newValue, value);
|
||||
} else {
|
||||
updateFileTags(recordId, newValue, value);
|
||||
}
|
||||
}, [value, record, addFileTags, updateFileTags]);
|
||||
updateFileTags([{ record_id: recordId, tags: newValue, old_tags: value }]);
|
||||
}, [value, record, updateFileTags]);
|
||||
|
||||
const onDeleteTag = useCallback((tagId) => {
|
||||
const newValue = value.slice(0);
|
||||
@@ -83,7 +78,7 @@ const TagsEditor = forwardRef(({
|
||||
}
|
||||
setValue(newValue);
|
||||
const recordId = getRecordIdFromRecord(record);
|
||||
updateFileTags(recordId, newValue, value);
|
||||
updateFileTags([{ record_id: recordId, tags: newValue, old_tags: value }]);
|
||||
}, [value, record, updateFileTags]);
|
||||
|
||||
const onMenuMouseEnter = useCallback((highlightIndex) => {
|
||||
@@ -103,21 +98,15 @@ const TagsEditor = forwardRef(({
|
||||
success_callback: (operation) => {
|
||||
const tags = operation.tags?.map(tag => getTagId(tag));
|
||||
const recordId = getRecordIdFromRecord(record);
|
||||
let newValue = [];
|
||||
if (value.length === 0) {
|
||||
newValue = tags;
|
||||
addFileTags(recordId, newValue, value);
|
||||
} else {
|
||||
newValue = [...value, ...tags];
|
||||
updateFileTags(recordId, newValue, value);
|
||||
}
|
||||
const newValue = [...value, ...tags];
|
||||
updateFileTags([{ record_id: recordId, tags: newValue, old_tags: value }]);
|
||||
setValue(newValue);
|
||||
},
|
||||
fail_callback: () => {
|
||||
|
||||
},
|
||||
});
|
||||
}, [value, searchValue, record, addTag, addFileTags, updateFileTags]);
|
||||
}, [value, searchValue, record, addTag, updateFileTags]);
|
||||
|
||||
const getMaxItemNum = useCallback(() => {
|
||||
let selectContainerStyle = getComputedStyle(editorContainerRef.current, null);
|
||||
@@ -277,6 +266,7 @@ TagsEditor.propTypes = {
|
||||
value: PropTypes.array,
|
||||
editorPosition: PropTypes.object,
|
||||
onPressTab: PropTypes.func,
|
||||
updateFileTags: PropTypes.func,
|
||||
};
|
||||
|
||||
export default TagsEditor;
|
||||
|
@@ -259,14 +259,9 @@ class Context {
|
||||
};
|
||||
|
||||
// file tag
|
||||
addFileTags = (recordId, tagIds) => {
|
||||
updateFileTags = (data) => {
|
||||
const repoID = this.settings['repoID'];
|
||||
return this.tagsAPI.addFileTags(repoID, recordId, tagIds);
|
||||
};
|
||||
|
||||
updateFileTags = (recordId, tagIds) => {
|
||||
const repoID = this.settings['repoID'];
|
||||
return this.tagsAPI.updateFileTags(repoID, recordId, tagIds);
|
||||
return this.tagsAPI.updateFileTags(repoID, data);
|
||||
};
|
||||
|
||||
}
|
||||
|
@@ -569,18 +569,10 @@ class Store {
|
||||
};
|
||||
|
||||
// tag
|
||||
addFileTags = (recordId, tagIds) => {
|
||||
const type = OPERATION_TYPE.ADD_FILE_TAGS;
|
||||
const operation = this.createOperation({
|
||||
type, repo_id: this.repoId, record_id: recordId, tag_ids: tagIds
|
||||
});
|
||||
this.applyOperation(operation);
|
||||
};
|
||||
|
||||
updateFileTags = (recordId, tagIds) => {
|
||||
updateFileTags = (data) => {
|
||||
const type = OPERATION_TYPE.UPDATE_FILE_TAGS;
|
||||
const operation = this.createOperation({
|
||||
type, repo_id: this.repoId, record_id: recordId, tag_ids: tagIds
|
||||
type, repo_id: this.repoId, file_tags_data: data
|
||||
});
|
||||
this.applyOperation(operation);
|
||||
};
|
||||
|
@@ -257,15 +257,20 @@ export default function apply(data, operation) {
|
||||
}
|
||||
|
||||
// tags
|
||||
case OPERATION_TYPE.ADD_FILE_TAGS:
|
||||
case OPERATION_TYPE.UPDATE_FILE_TAGS: {
|
||||
const { record_id, tag_ids } = operation;
|
||||
const { file_tags_data: filesTagsData } = operation;
|
||||
const { rows } = data;
|
||||
let updateMap = {};
|
||||
Array.isArray(filesTagsData) && filesTagsData.forEach(fileTags => {
|
||||
const { record_id, tags } = fileTags;
|
||||
const value = tags ? tags.map(tagId => ({ row_id: tagId })) : [];
|
||||
updateMap[record_id] = value;
|
||||
});
|
||||
let updatedRows = [...rows];
|
||||
rows.forEach((row, index) => {
|
||||
const { _id: rowId } = row;
|
||||
if (rowId === record_id) {
|
||||
const updatedRow = Object.assign({}, row, { [PRIVATE_COLUMN_KEY.TAGS]: tag_ids ? tag_ids.map(item => ({ row_id: item })) : [] });
|
||||
if (updateMap[rowId]) {
|
||||
const updatedRow = Object.assign({}, row, { [PRIVATE_COLUMN_KEY.TAGS]: updateMap[rowId] });
|
||||
updatedRows[index] = updatedRow;
|
||||
data.id_row_map[rowId] = updatedRow;
|
||||
}
|
||||
|
@@ -25,7 +25,6 @@ export const OPERATION_TYPE = {
|
||||
DELETE_PEOPLE_PHOTOS: 'delete_people_photos',
|
||||
|
||||
// tag
|
||||
ADD_FILE_TAGS: 'add_file_tags',
|
||||
UPDATE_FILE_TAGS: 'update_file_tags',
|
||||
};
|
||||
|
||||
@@ -59,8 +58,7 @@ export const OPERATION_ATTRIBUTES = {
|
||||
[OPERATION_TYPE.DELETE_PEOPLE_PHOTOS]: ['repo_id', 'people_id', 'deleted_photos'],
|
||||
[OPERATION_TYPE.MODIFY_SETTINGS]: ['repo_id', 'view_id', 'settings'],
|
||||
[OPERATION_TYPE.MODIFY_LOCAL_RECORD]: ['repo_id', 'row_id', 'updates'],
|
||||
[OPERATION_TYPE.ADD_FILE_TAGS]: ['repo_id', 'record_id', 'tag_ids'],
|
||||
[OPERATION_TYPE.UPDATE_FILE_TAGS]: ['repo_id', 'record_id', 'tag_ids'],
|
||||
[OPERATION_TYPE.UPDATE_FILE_TAGS]: ['repo_id', 'file_tags_data'],
|
||||
};
|
||||
|
||||
export const UNDO_OPERATION_TYPE = [
|
||||
|
@@ -195,22 +195,18 @@ class ServerOperator {
|
||||
}
|
||||
|
||||
// tags
|
||||
case OPERATION_TYPE.ADD_FILE_TAGS: {
|
||||
const { record_id, tag_ids } = operation;
|
||||
window.sfMetadataContext.addFileTags(record_id, tag_ids).then(res => {
|
||||
callback({ operation });
|
||||
}).catch(error => {
|
||||
callback({ error: gettext('Failed to modify people name') });
|
||||
});
|
||||
break;
|
||||
}
|
||||
case OPERATION_TYPE.UPDATE_FILE_TAGS: {
|
||||
const { record_id, tag_ids } = operation;
|
||||
window.sfMetadataContext.updateFileTags(record_id, tag_ids).then(res => {
|
||||
callback({ operation });
|
||||
|
||||
const { file_tags_data } = operation;
|
||||
let valid_files_tags_data = [];
|
||||
file_tags_data.forEach(item => {
|
||||
const { record_id, tags } = item;
|
||||
valid_files_tags_data.push({ record_id, tags });
|
||||
});
|
||||
window.sfMetadataContext.updateFileTags(valid_files_tags_data).then(res => {
|
||||
const { success: success_record_ids, fail: fail_record_ids } = res.data;
|
||||
callback({ operation, success_record_ids, fail_record_ids });
|
||||
}).catch(error => {
|
||||
callback({ error: gettext('Failed to modify people name') });
|
||||
callback({ error: gettext('Failed to modify tags') });
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
@@ -47,13 +47,13 @@ function convertedToRecordData(originRecordData, keyColumnMap, excludesColumnTyp
|
||||
return recordData;
|
||||
}
|
||||
|
||||
export const getClientCellValueDisplayString = (record, column, { collaborators = [] } = {}) => {
|
||||
export const getClientCellValueDisplayString = (record, column, { collaborators = [], tagsData = {} } = {}) => {
|
||||
const cellValue = getCellValueByColumn(record, column);
|
||||
const { type } = column;
|
||||
if (type === CellType.CTIME || type === CellType.MTIME) {
|
||||
return getAutoTimeDisplayString(cellValue);
|
||||
}
|
||||
return getCellValueDisplayString(record, column, { collaborators });
|
||||
return getCellValueDisplayString(record, column, { collaborators, tagsData });
|
||||
};
|
||||
|
||||
export const getFormatRecordData = (columns, recordData) => {
|
||||
|
@@ -28,3 +28,6 @@ export {
|
||||
getGeolocationDisplayString,
|
||||
getGeolocationByGranularity,
|
||||
} from './geolocation';
|
||||
export {
|
||||
getTagsDisplayString,
|
||||
} from './tag';
|
||||
|
8
frontend/src/metadata/utils/cell/column/tag.js
Normal file
8
frontend/src/metadata/utils/cell/column/tag.js
Normal file
@@ -0,0 +1,8 @@
|
||||
import { getTagName } from '../../../../tag/utils';
|
||||
import { getRowById } from '../../table';
|
||||
|
||||
export const getTagsDisplayString = (tagsData, cellValue) => {
|
||||
if (!tagsData) return '';
|
||||
if (!Array.isArray(cellValue) || cellValue.length === 0) return '';
|
||||
return cellValue.map(item => getRowById(tagsData, item.row_id)).map(tag => getTagName(tag)).join(', ');
|
||||
};
|
@@ -4,10 +4,10 @@ import { getCellValueByColumn } from './core';
|
||||
import { getColumnOptions } from '../column';
|
||||
import {
|
||||
getDateDisplayString, getNumberDisplayString, getLongtextDisplayString, getOptionName, getCollaboratorsName, getColumnOptionNamesByIds,
|
||||
getGeolocationDisplayString, getColumnOptionIdsByNames,
|
||||
getGeolocationDisplayString, getColumnOptionIdsByNames, getTagsDisplayString,
|
||||
} from './column';
|
||||
|
||||
export const getCellValueDisplayString = (row, column, { collaborators = [] } = {}) => {
|
||||
export const getCellValueDisplayString = (row, column, { collaborators = [], tagsData } = {}) => {
|
||||
if (!row) return '';
|
||||
const { type, data } = column;
|
||||
const cellValue = getCellValueByColumn(row, column);
|
||||
@@ -47,6 +47,9 @@ export const getCellValueDisplayString = (row, column, { collaborators = [] } =
|
||||
case CellType.GEOLOCATION: {
|
||||
return getGeolocationDisplayString(cellValue, data, { isBaiduMap: true, hyphen: ' ' });
|
||||
}
|
||||
case CellType.TAGS: {
|
||||
return getTagsDisplayString(tagsData, cellValue);
|
||||
}
|
||||
default: {
|
||||
if (cellValue || typeof cellValue === 'boolean') {
|
||||
return String(cellValue);
|
||||
|
@@ -184,12 +184,8 @@ const Table = () => {
|
||||
store.modifyColumnOrder(sourceColumnKey, targetColumnKey);
|
||||
}, [store]);
|
||||
|
||||
const addFileTags = useCallback((recordId, tagIds) => {
|
||||
store.addFileTags(recordId, tagIds);
|
||||
}, [store]);
|
||||
|
||||
const updateFileTags = useCallback((recordId, tagIds) => {
|
||||
store.updateFileTags(recordId, tagIds);
|
||||
const updateFileTags = useCallback((data) => {
|
||||
store.updateFileTags(data);
|
||||
}, [store]);
|
||||
|
||||
const insertColumn = useCallback((name, type, { key, data }) => {
|
||||
@@ -250,7 +246,6 @@ const Table = () => {
|
||||
modifyColumnData={modifyColumnData}
|
||||
modifyColumnWidth={modifyColumnWidth}
|
||||
modifyColumnOrder={modifyColumnOrder}
|
||||
addFileTags={addFileTags}
|
||||
updateFileTags={updateFileTags}
|
||||
/>
|
||||
</div>
|
||||
|
@@ -11,7 +11,7 @@ import { gettext } from '../../../../../utils/constants';
|
||||
import { KeyCodes } from '../../../../../constants';
|
||||
import { Utils } from '../../../../../utils/utils';
|
||||
import { isEmptyObject } from '../../../../utils/common';
|
||||
import { getCellValueByColumn } from '../../../../utils/cell';
|
||||
import { getCellValueByColumn, getTagsFromRecord, isValidCellValue } from '../../../../utils/cell';
|
||||
import { isCtrlKeyHeldDown, isKeyPrintable } from '../../../../utils/keyboard-utils';
|
||||
import { getFormatRecordData } from '../../../../utils/cell/cell-format-utils';
|
||||
import {
|
||||
@@ -508,27 +508,28 @@ class InteractionMasks extends React.Component {
|
||||
let idOriginalRecordUpdates = {}; // row's id to modified original records data: { [row_id]: { [column.key: null] } }
|
||||
let idOldRecordData = {}; // row's id to old records data: { [row_id]: { [column.name: xxx] } }
|
||||
let idOriginalOldRecordData = {}; // row's id to old original records data: { [row_id]: { [column.key: xxx] } }
|
||||
let idRowLinkItems = {}; // row's id to modified links: { [row_id]: { [column.key]: null } }
|
||||
let idOldRowLinkItems = {}; // row's id to old links: { [row_id]: { [column.key]: [{ row_id: xxx, display_value: 'xxx' }] } }
|
||||
let tagsUpdate = [];
|
||||
editableRecords.forEach(record => {
|
||||
const { _id } = record;
|
||||
let originalUpdate = {};
|
||||
let originalOldRecordData = {};
|
||||
let linkItem = {};
|
||||
let oldLinkItem = {};
|
||||
let hasTagsColumn = false;
|
||||
editableColumns.forEach(column => {
|
||||
const { key } = column;
|
||||
const { key, type } = column;
|
||||
const cellVal = getCellValueByColumn(record, column);
|
||||
if (cellVal || cellVal === 0 || (Array.isArray(cellVal) && cellVal.length > 0)) {
|
||||
originalOldRecordData[key] = cellVal;
|
||||
originalUpdate[key] = null;
|
||||
if (isValidCellValue(cellVal)) {
|
||||
if (type === CellType.TAGS) {
|
||||
hasTagsColumn = true;
|
||||
} else {
|
||||
originalOldRecordData[key] = cellVal;
|
||||
originalUpdate[key] = null;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// links data
|
||||
if (Object.keys(linkItem).length > 0) {
|
||||
idRowLinkItems[_id] = linkItem;
|
||||
idOldRowLinkItems[_id] = oldLinkItem;
|
||||
// tags
|
||||
if (hasTagsColumn) {
|
||||
tagsUpdate.push({ record_id: _id, tags: [], old_tags: getTagsFromRecord(record) });
|
||||
}
|
||||
|
||||
if (Object.keys(originalUpdate).length > 0) {
|
||||
@@ -542,6 +543,10 @@ class InteractionMasks extends React.Component {
|
||||
}
|
||||
});
|
||||
|
||||
if (tagsUpdate.length > 0) {
|
||||
this.props.updateFileTags(tagsUpdate);
|
||||
}
|
||||
|
||||
if (updateRecordIds.length > 0) {
|
||||
const isCopyPaste = true;
|
||||
this.props.updateRecords({
|
||||
@@ -1099,7 +1104,6 @@ class InteractionMasks extends React.Component {
|
||||
onCommit={this.onCommit}
|
||||
onCommitCancel={this.onCommitCancel}
|
||||
modifyColumnData={this.props.modifyColumnData}
|
||||
addFileTags={this.props.addFileTags}
|
||||
updateFileTags={this.props.updateFileTags}
|
||||
editorPosition={editorPosition}
|
||||
{...{
|
||||
|
@@ -7,11 +7,22 @@ import { GROUP_VIEW_OFFSET } from '../../../constants';
|
||||
|
||||
import './index.css';
|
||||
|
||||
const TableMain = ({ metadata, modifyRecord, modifyRecords, loadMore, loadAll, searchResult, recordGetterByIndex, recordGetterById, insertColumn, modifyColumnData, ...props }) => {
|
||||
const TableMain = ({
|
||||
metadata, modifyRecord, modifyRecords, loadMore, loadAll, searchResult, recordGetterByIndex, recordGetterById, insertColumn,
|
||||
modifyColumnData, updateFileTags,
|
||||
...props
|
||||
}) => {
|
||||
|
||||
const gridUtils = useMemo(() => {
|
||||
return new GridUtils(metadata, { modifyRecord, modifyRecords, recordGetterByIndex, recordGetterById, modifyColumnData });
|
||||
}, [metadata, modifyRecord, modifyRecords, recordGetterByIndex, recordGetterById, modifyColumnData]);
|
||||
return new GridUtils(metadata, {
|
||||
modifyRecord,
|
||||
modifyRecords,
|
||||
recordGetterByIndex,
|
||||
recordGetterById,
|
||||
modifyColumnData,
|
||||
updateFileTags,
|
||||
});
|
||||
}, [metadata, modifyRecord, modifyRecords, recordGetterByIndex, recordGetterById, modifyColumnData, updateFileTags]);
|
||||
|
||||
const groupbysCount = useMemo(() => {
|
||||
const groupbys = metadata?.view?.groupbys || [];
|
||||
@@ -70,6 +81,7 @@ const TableMain = ({ metadata, modifyRecord, modifyRecords, loadMore, loadAll, s
|
||||
recordGetterByIndex={recordGetterByIndex}
|
||||
modifyColumnData={modifyColumnData}
|
||||
insertColumn={handelInsertColumn}
|
||||
updateFileTags={updateFileTags}
|
||||
{...props}
|
||||
/>
|
||||
</div>
|
||||
|
@@ -543,7 +543,6 @@ class RecordsBody extends Component {
|
||||
getCopiedRecordsAndColumnsFromRange={this.props.getCopiedRecordsAndColumnsFromRange}
|
||||
modifyColumnData={this.props.modifyColumnData}
|
||||
getTableCanvasContainerRect={this.props.getTableCanvasContainerRect}
|
||||
addFileTags={this.props.addFileTags}
|
||||
updateFileTags={this.props.updateFileTags}
|
||||
/>
|
||||
<div className="sf-metadata-result-table" style={{ width: this.props.totalWidth + SEQUENCE_COLUMN_WIDTH }} ref={this.setResultRef}>
|
||||
|
@@ -905,7 +905,6 @@ class GroupBody extends Component {
|
||||
getCopiedRecordsAndColumnsFromRange={this.props.getCopiedRecordsAndColumnsFromRange}
|
||||
modifyColumnData={this.props.modifyColumnData}
|
||||
getTableCanvasContainerRect={this.props.getTableCanvasContainerRect}
|
||||
addFileTags={this.props.addFileTags}
|
||||
updateFileTags={this.props.updateFileTags}
|
||||
/>
|
||||
<div className="sf-metadata-result-table" ref={this.setResultRef}>
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import {
|
||||
getDateDisplayString, getNumberDisplayString, formatStringToNumber, getOptionName, getCollaboratorsName, getFloatNumber, getColumnOptionNamesByIds,
|
||||
getOption, checkIsPredefinedOption, getColumnOptionNameById,
|
||||
getTagsDisplayString,
|
||||
} from '../../../utils/cell';
|
||||
import { getColumnOptions, generatorCellOption, generatorCellOptions, isLongTextValueExceedLimit, getValidLongTextValue } from '../../../utils/column';
|
||||
import { isNumber } from '../../../utils/number';
|
||||
@@ -198,6 +199,11 @@ function convert2Text(cellValue, oldCellValue, fromColumn) {
|
||||
const collaborators = window.sfMetadata.getCollaborators();
|
||||
return getCollaboratorsName(collaborators, [cellValue]);
|
||||
}
|
||||
case CellType.TAGS: {
|
||||
if (!Array.isArray(cellValue) || cellValue.length === 0) return null;
|
||||
const tagsData = window?.sfTagsDataStore?.data || {};
|
||||
return getTagsDisplayString(tagsData, cellValue);
|
||||
}
|
||||
default: {
|
||||
return oldCellValue;
|
||||
}
|
||||
@@ -326,6 +332,13 @@ const convert2MultipleSelect = (copiedCellVal, pasteCellVal, copiedColumn, paste
|
||||
return getColumnOptionNamesByIds(newColumn, selectedOptionIds);
|
||||
};
|
||||
|
||||
const convert2Tags = (cellValue, oldCellValue, fromColumn, targetColumn, api) => {
|
||||
const { key: copiedColumnKey } = fromColumn;
|
||||
const { key: pasteColumnKey } = targetColumn;
|
||||
if (copiedColumnKey === pasteColumnKey) return cellValue;
|
||||
return;
|
||||
};
|
||||
|
||||
function convertCellValue(cellValue, oldCellValue, targetColumn, fromColumn, api) {
|
||||
const { type: fromColumnType, data: fromColumnData } = fromColumn;
|
||||
const { type: targetColumnType, data: targetColumnData } = targetColumn;
|
||||
@@ -357,6 +370,9 @@ function convertCellValue(cellValue, oldCellValue, targetColumn, fromColumn, api
|
||||
case CellType.RATE: {
|
||||
return convert2Rate(cellValue, oldCellValue, fromColumn, targetColumn);
|
||||
}
|
||||
case CellType.TAGS: {
|
||||
return convert2Tags(cellValue, oldCellValue, fromColumn, targetColumn, api);
|
||||
}
|
||||
default: {
|
||||
return oldCellValue;
|
||||
}
|
||||
|
@@ -75,6 +75,7 @@ class GridUtils {
|
||||
|
||||
if ((copiedRecordsLen > startExpandRecordIndex)) return;
|
||||
|
||||
let updateTags = [];
|
||||
let updateRecordIds = [];
|
||||
let idRecordUpdates = {};
|
||||
let idOriginalRecordUpdates = {};
|
||||
@@ -115,7 +116,9 @@ class GridUtils {
|
||||
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, this.api);
|
||||
if (update === pasteCellValue) {
|
||||
if (!isCellValueChanged(pasteCellValue, update, pasteColumn.type)) continue;
|
||||
if (pasteColumn.type === CellType.TAGS) {
|
||||
updateTags.push({ record_id: updateRecordId, tags: Array.isArray(update) ? update.map(i => i.row_id) : [], old_tags: pasteCellValue });
|
||||
continue;
|
||||
}
|
||||
originalUpdate[pasteColumnName] = update;
|
||||
@@ -133,6 +136,10 @@ class GridUtils {
|
||||
}
|
||||
}
|
||||
|
||||
if (updateTags.length > 0) {
|
||||
this.api.updateFileTags(updateTags);
|
||||
}
|
||||
|
||||
if (updateRecordIds.length === 0) return;
|
||||
this.api.modifyRecords(updateRecordIds, idRecordUpdates, idOriginalRecordUpdates, idOldRecordData, idOriginalOldRecordData, isCopyPaste);
|
||||
}
|
||||
@@ -170,6 +177,7 @@ class GridUtils {
|
||||
}
|
||||
|
||||
getUpdateDraggedRecords(draggedRange, shownColumns, rows, idRowMap, groupMetrics) {
|
||||
let tagsUpdate = [];
|
||||
let rowIds = [];
|
||||
let updatedOriginalRows = {};
|
||||
let oldOriginalRows = {};
|
||||
@@ -211,21 +219,36 @@ class GridUtils {
|
||||
const value = draggedRangeMatrix[j - startColumnIdx][idx];
|
||||
const rule = rules[cellKey];
|
||||
const fillingValue = rule({ n: fillingIndex - 1, value });
|
||||
if (isCellValueChanged(fillingValue, dragRow[columnName], type)) {
|
||||
updatedOriginalRows[dragRowId] = Object.assign({}, updatedOriginalRows[dragRowId], { [columnName]: fillingValue });
|
||||
oldOriginalRows[dragRowId] = Object.assign({}, oldOriginalRows[dragRowId], { [columnName]: dragRow[columnName] });
|
||||
const update = updatedOriginalRows[dragRowId];
|
||||
const oldUpdate = oldOriginalRows[dragRowId];
|
||||
const oldValue = dragRow[columnName];
|
||||
if (isCellValueChanged(fillingValue, oldValue, type)) {
|
||||
if (type === CellType.TAGS) {
|
||||
tagsUpdate.push({ record_id: dragRowId, tags: fillingValue || [], old_tags: oldValue });
|
||||
} else {
|
||||
updatedOriginalRows[dragRowId] = Object.assign({}, updatedOriginalRows[dragRowId], { [columnName]: fillingValue });
|
||||
oldOriginalRows[dragRowId] = Object.assign({}, oldOriginalRows[dragRowId], { [columnName]: oldValue });
|
||||
const update = updatedOriginalRows[dragRowId];
|
||||
const oldUpdate = oldOriginalRows[dragRowId];
|
||||
|
||||
updatedRows[dragRowId] = Object.assign({}, updatedRows[dragRowId], update);
|
||||
oldRows[dragRowId] = Object.assign({}, oldRows[dragRowId], oldUpdate);
|
||||
updatedRows[dragRowId] = Object.assign({}, updatedRows[dragRowId], update);
|
||||
oldRows[dragRowId] = Object.assign({}, oldRows[dragRowId], oldUpdate);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
currentGroupRowIndex++;
|
||||
}
|
||||
|
||||
return { recordIds: rowIds, idOriginalRecordUpdates: updatedOriginalRows, idRecordUpdates: updatedRows, idOriginalOldRecordData: oldOriginalRows, idOldRecordData: oldRows };
|
||||
if (tagsUpdate.length > 0) {
|
||||
this.api.updateFileTags(tagsUpdate);
|
||||
}
|
||||
|
||||
return {
|
||||
recordIds: rowIds,
|
||||
idOriginalRecordUpdates: updatedOriginalRows,
|
||||
idRecordUpdates: updatedRows,
|
||||
idOriginalOldRecordData: oldOriginalRows,
|
||||
idOldRecordData: oldRows
|
||||
};
|
||||
}
|
||||
|
||||
getDraggedRangeMatrix(columns, draggedRange, rows, groupMetrics, idRowMap) {
|
||||
@@ -299,6 +322,14 @@ class GridUtils {
|
||||
ruleMatrixItem = this._getRatingLeastSquares(valueList, data);
|
||||
break;
|
||||
}
|
||||
case CellType.TAGS: {
|
||||
ruleMatrixItem = ({ value }) => {
|
||||
if (!value) return [];
|
||||
if (!Array.isArray(value) || value.length === 0) return [];
|
||||
return value.map(item => item.row_id);
|
||||
};
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
ruleMatrixItem = NORMAL_RULE;
|
||||
break;
|
||||
|
@@ -65,12 +65,13 @@ function getCopiedTextFromSelectedCells(copiedRange, tableData, isGroupView, rec
|
||||
|
||||
function getCopiedText(records, columns) {
|
||||
const collaborators = window.sfMetadata.getCollaborators();
|
||||
const tagsData = window?.sfTagsDataStore?.data || {};
|
||||
const lastRecordIndex = records.length - 1;
|
||||
const lastColumnIndex = columns.length - 1;
|
||||
let copiedText = '';
|
||||
records.forEach((record, recordIndex) => {
|
||||
columns.forEach((column, columnIndex) => {
|
||||
copiedText += (record && getClientCellValueDisplayString(record, column, { collaborators })) || '';
|
||||
copiedText += (record && getClientCellValueDisplayString(record, column, { collaborators, tagsData })) || '';
|
||||
if (columnIndex < lastColumnIndex) {
|
||||
copiedText += '\t';
|
||||
}
|
||||
|
@@ -93,21 +93,11 @@ class TagsManagerAPI {
|
||||
return this.req.get(url);
|
||||
};
|
||||
|
||||
// file tag
|
||||
addFileTags = (repoID, recordId, tagIds) => {
|
||||
// file tags
|
||||
updateFileTags = (repoID, data) => {
|
||||
const url = this.server + '/api/v2.1/repos/' + repoID + '/metadata/file-tags/';
|
||||
const params = {
|
||||
record_id: recordId,
|
||||
tags: tagIds,
|
||||
};
|
||||
return this.req.post(url, params);
|
||||
};
|
||||
|
||||
updateFileTags = (repoID, recordId, tagIds) => {
|
||||
const url = this.server + '/api/v2.1/repos/' + repoID + '/metadata/file-tags/';
|
||||
const params = {
|
||||
record_id: recordId,
|
||||
tags: tagIds,
|
||||
file_tags_data: data,
|
||||
};
|
||||
return this.req.put(url, params);
|
||||
};
|
||||
|
@@ -1,6 +1,7 @@
|
||||
.tag-list-title .sf-metadata-tags-formatter .sf-metadata-tag-formatter {
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.tag-list-title .sf-metadata-tags-formatter .sf-metadata-tag-formatter:last-child {
|
||||
@@ -10,3 +11,8 @@
|
||||
.sf-metadata-tags-main .table-container td.name a {
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.sf-metadata-tags-main .tag-list-title .sf-metadata-tags-formatter {
|
||||
width: fit-content;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
@@ -1556,61 +1556,10 @@ class MetadataFileTags(APIView):
|
||||
permission_classes = (IsAuthenticated,)
|
||||
throttle_classes = (UserRateThrottle,)
|
||||
|
||||
def post(self, request, repo_id):
|
||||
record_id = request.data.get('record_id')
|
||||
if not record_id:
|
||||
error_msg = 'record_id invalid.'
|
||||
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
||||
|
||||
tags = request.data.get('tags', [])
|
||||
if not tags:
|
||||
error_msg = 'tags invalid.'
|
||||
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
||||
|
||||
metadata = RepoMetadata.objects.filter(repo_id=repo_id).first()
|
||||
if not metadata or not metadata.enabled:
|
||||
error_msg = f'The metadata module is disabled for repo {repo_id}.'
|
||||
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
|
||||
|
||||
repo = seafile_api.get_repo(repo_id)
|
||||
if not repo:
|
||||
error_msg = 'Library %s not found.' % repo_id
|
||||
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
|
||||
|
||||
if not can_read_metadata(request, repo_id):
|
||||
error_msg = 'Permission denied.'
|
||||
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
|
||||
|
||||
metadata_server_api = MetadataServerAPI(repo_id, request.user.username)
|
||||
|
||||
from seafevents.repo_metadata.constants import TAGS_TABLE, METADATA_TABLE
|
||||
try:
|
||||
metadata = metadata_server_api.get_metadata()
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
error_msg = 'Internal Server Error'
|
||||
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
|
||||
|
||||
tables = metadata.get('tables', [])
|
||||
tags_table_id = [table['id'] for table in tables if table['name'] == TAGS_TABLE.name]
|
||||
tags_table_id = tags_table_id[0] if tags_table_id else None
|
||||
if not tags_table_id:
|
||||
return api_error(status.HTTP_404_NOT_FOUND, 'tags not be used')
|
||||
|
||||
try:
|
||||
metadata_server_api.insert_link(repo_id, TAGS_TABLE.link_id, METADATA_TABLE.id, { record_id: tags })
|
||||
|
||||
except Exception as e:
|
||||
logger.exception(e)
|
||||
error_msg = 'Internal Server Error'
|
||||
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
|
||||
|
||||
return Response({'success': True})
|
||||
|
||||
def put(self, request, repo_id):
|
||||
record_id = request.data.get('record_id')
|
||||
if not record_id:
|
||||
error_msg = 'record_id invalid.'
|
||||
file_tags_data = request.data.get('file_tags_data')
|
||||
if not file_tags_data:
|
||||
error_msg = 'file_tags_data invalid.'
|
||||
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
||||
|
||||
tags = request.data.get('tags', [])
|
||||
@@ -1645,15 +1594,48 @@ class MetadataFileTags(APIView):
|
||||
if not tags_table_id:
|
||||
return api_error(status.HTTP_404_NOT_FOUND, 'tags not be used')
|
||||
|
||||
file_record_ids = []
|
||||
file_tags_map = {}
|
||||
for file_tags in file_tags_data:
|
||||
record_id = file_tags.get('record_id', '')
|
||||
tags = file_tags.get('tags', [])
|
||||
if record_id:
|
||||
file_record_ids.append(record_id)
|
||||
file_tags_map[record_id] = tags
|
||||
|
||||
if not file_record_ids:
|
||||
return Response({'success': [], 'fail': []})
|
||||
|
||||
file_record_ids_str = ', '.join(["'%s'" % id for id in file_record_ids])
|
||||
current_result_sql = f'SELECT `{METADATA_TABLE.columns.tags.key}`, `{METADATA_TABLE.columns.id.key}` FROM `{METADATA_TABLE.name}` WHERE `{METADATA_TABLE.columns.id.key}` IN ({file_record_ids_str})'
|
||||
try:
|
||||
metadata_server_api.update_link(repo_id, TAGS_TABLE.link_id, METADATA_TABLE.id, { record_id: tags })
|
||||
|
||||
current_result_query = metadata_server_api.query_rows(current_result_sql)
|
||||
except Exception as e:
|
||||
logger.exception(e)
|
||||
logger.error(e)
|
||||
error_msg = 'Internal Server Error'
|
||||
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
|
||||
|
||||
return Response({'success': True})
|
||||
success_records = []
|
||||
failed_records = []
|
||||
files_records = current_result_query.get('results', [])
|
||||
if not files_records:
|
||||
return api_error(status.HTTP_404_NOT_FOUND, 'files not found')
|
||||
|
||||
for file_record in files_records:
|
||||
current_tags = file_record.get(METADATA_TABLE.columns.tags.key, [])
|
||||
record_id = file_record.get(METADATA_TABLE.columns.id.key)
|
||||
tags = file_tags_map[record_id]
|
||||
|
||||
try:
|
||||
if not current_tags:
|
||||
metadata_server_api.insert_link(repo_id, TAGS_TABLE.link_id, METADATA_TABLE.id, { record_id: tags })
|
||||
else:
|
||||
metadata_server_api.update_link(repo_id, TAGS_TABLE.link_id, METADATA_TABLE.id, { record_id: tags })
|
||||
success_records.append(record_id)
|
||||
except Exception as e:
|
||||
failed_records.append(record_id)
|
||||
|
||||
return Response({'success': success_records, 'fail': failed_records})
|
||||
|
||||
|
||||
class MetadataTagFiles(APIView):
|
||||
|
Reference in New Issue
Block a user