mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-26 07:22:34 +00:00
Feature/table operations (#8149)
* add move/copy/download operations on table rows * optimize * fix permission --------- Co-authored-by: zhouwenxuan <aries@Mac.local>
This commit is contained in:
@@ -8,7 +8,7 @@ import { getFileName } from '../../tag/utils/file';
|
|||||||
import RowUtils from '../../metadata/views/table/utils/row-utils';
|
import RowUtils from '../../metadata/views/table/utils/row-utils';
|
||||||
import { checkIsDir } from '../../metadata/utils/row';
|
import { checkIsDir } from '../../metadata/utils/row';
|
||||||
import { Utils } from '../../utils/utils';
|
import { Utils } from '../../utils/utils';
|
||||||
import { getFileNameFromRecord } from '../../metadata/utils/cell';
|
import { getFileNameFromRecord, getParentDirFromRecord } from '../../metadata/utils/cell';
|
||||||
import { getColumnByKey } from '../../metadata/utils/column';
|
import { getColumnByKey } from '../../metadata/utils/column';
|
||||||
import { openInNewTab, openParentFolder } from '../../metadata/utils/file';
|
import { openInNewTab, openParentFolder } from '../../metadata/utils/file';
|
||||||
|
|
||||||
@@ -22,6 +22,12 @@ const TableFilesToolbar = ({ repoID }) => {
|
|||||||
|
|
||||||
const records = useMemo(() => selectedRecordIds.map(id => RowUtils.getRecordById(id, metadataRef.current)).filter(Boolean) || [], [selectedRecordIds]);
|
const records = useMemo(() => selectedRecordIds.map(id => RowUtils.getRecordById(id, metadataRef.current)).filter(Boolean) || [], [selectedRecordIds]);
|
||||||
|
|
||||||
|
const areRecordsInSameFolder = useMemo(() => {
|
||||||
|
if (records.length <= 1) return true;
|
||||||
|
const firstPath = records[0] ? getParentDirFromRecord(records[0]) : null;
|
||||||
|
return firstPath && records.every(record => getParentDirFromRecord(record) === firstPath);
|
||||||
|
}, [records]);
|
||||||
|
|
||||||
const unSelect = useCallback(() => {
|
const unSelect = useCallback(() => {
|
||||||
setSelectedRecordIds([]);
|
setSelectedRecordIds([]);
|
||||||
eventBus && eventBus.dispatch(EVENT_BUS_TYPE.UPDATE_SELECTED_RECORD_IDS, []);
|
eventBus && eventBus.dispatch(EVENT_BUS_TYPE.UPDATE_SELECTED_RECORD_IDS, []);
|
||||||
@@ -37,16 +43,32 @@ const TableFilesToolbar = ({ repoID }) => {
|
|||||||
}, [eventBus, selectedRecordIds]);
|
}, [eventBus, selectedRecordIds]);
|
||||||
|
|
||||||
const toggleMoveDialog = useCallback(() => {
|
const toggleMoveDialog = useCallback(() => {
|
||||||
eventBus && eventBus.dispatch(EVENT_BUS_TYPE.TOGGLE_MOVE_DIALOG, records[0]);
|
eventBus && eventBus.dispatch(EVENT_BUS_TYPE.TOGGLE_MOVE_DIALOG, records);
|
||||||
}, [eventBus, records]);
|
}, [eventBus, records]);
|
||||||
|
|
||||||
|
const toggleCopyDialog = useCallback(() => {
|
||||||
|
eventBus && eventBus.dispatch(EVENT_BUS_TYPE.TOGGLE_COPY_DIALOG, records);
|
||||||
|
}, [eventBus, records]);
|
||||||
|
|
||||||
|
const downloadRecords = useCallback(() => {
|
||||||
|
eventBus && eventBus.dispatch(EVENT_BUS_TYPE.DOWNLOAD_RECORDS, selectedRecordIds);
|
||||||
|
}, [eventBus, selectedRecordIds]);
|
||||||
|
|
||||||
const checkCanModifyRow = (row) => window.sfMetadataContext.canModifyRow(row);
|
const checkCanModifyRow = (row) => window.sfMetadataContext.canModifyRow(row);
|
||||||
|
|
||||||
const getMenuList = useCallback(() => {
|
const getMenuList = useCallback(() => {
|
||||||
const { EXTRACT_FILE_DETAIL, EXTRACT_FILE_DETAILS, OPEN_FILE_IN_NEW_TAB, OPEN_FOLDER_IN_NEW_TAB, OPEN_PARENT_FOLDER, GENERATE_DESCRIPTION, OCR } = TextTranslation;
|
const { EXTRACT_FILE_DETAIL, EXTRACT_FILE_DETAILS, OPEN_FILE_IN_NEW_TAB, OPEN_FOLDER_IN_NEW_TAB, OPEN_PARENT_FOLDER, GENERATE_DESCRIPTION, OCR, COPY, DOWNLOAD, MOVE } = TextTranslation;
|
||||||
const length = selectedRecordIds.length;
|
const length = selectedRecordIds.length;
|
||||||
const list = [];
|
const list = [];
|
||||||
if (length > 1) {
|
if (length > 1) {
|
||||||
|
if (areRecordsInSameFolder) {
|
||||||
|
if (canModify) {
|
||||||
|
list.push(MOVE);
|
||||||
|
list.push(COPY);
|
||||||
|
}
|
||||||
|
list.push(DOWNLOAD);
|
||||||
|
}
|
||||||
|
|
||||||
if (enableSeafileAI) {
|
if (enableSeafileAI) {
|
||||||
const imageOrVideoRecords = records.filter(record => {
|
const imageOrVideoRecords = records.filter(record => {
|
||||||
if (checkIsDir(record) || !checkCanModifyRow(record)) return false;
|
if (checkIsDir(record) || !checkCanModifyRow(record)) return false;
|
||||||
@@ -54,6 +76,7 @@ const TableFilesToolbar = ({ repoID }) => {
|
|||||||
return Utils.imageCheck(fileName) || Utils.videoCheck(fileName);
|
return Utils.imageCheck(fileName) || Utils.videoCheck(fileName);
|
||||||
});
|
});
|
||||||
if (imageOrVideoRecords.length > 0) {
|
if (imageOrVideoRecords.length > 0) {
|
||||||
|
list.push('Divider');
|
||||||
list.push(EXTRACT_FILE_DETAILS);
|
list.push(EXTRACT_FILE_DETAILS);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -69,6 +92,8 @@ const TableFilesToolbar = ({ repoID }) => {
|
|||||||
list.push(OPEN_PARENT_FOLDER);
|
list.push(OPEN_PARENT_FOLDER);
|
||||||
|
|
||||||
const modifyOptions = [];
|
const modifyOptions = [];
|
||||||
|
modifyOptions.push(COPY);
|
||||||
|
modifyOptions.push(DOWNLOAD);
|
||||||
|
|
||||||
if (modifyOptions.length > 0) {
|
if (modifyOptions.length > 0) {
|
||||||
list.push('Divider');
|
list.push('Divider');
|
||||||
@@ -103,11 +128,23 @@ const TableFilesToolbar = ({ repoID }) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return list;
|
return list;
|
||||||
}, [selectedRecordIds, records]);
|
}, [selectedRecordIds, records, canModify, areRecordsInSameFolder]);
|
||||||
|
|
||||||
const onMenuItemClick = useCallback((operation) => {
|
const onMenuItemClick = useCallback((operation) => {
|
||||||
const records = selectedRecordIds.map(id => RowUtils.getRecordById(id, metadataRef.current)).filter(Boolean);
|
const records = selectedRecordIds.map(id => RowUtils.getRecordById(id, metadataRef.current)).filter(Boolean);
|
||||||
switch (operation) {
|
switch (operation) {
|
||||||
|
case TextTranslation.MOVE.key: {
|
||||||
|
eventBus && eventBus.dispatch(EVENT_BUS_TYPE.TOGGLE_MOVE_DIALOG, records);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case TextTranslation.COPY.key: {
|
||||||
|
eventBus && eventBus.dispatch(EVENT_BUS_TYPE.TOGGLE_COPY_DIALOG, records);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case TextTranslation.DOWNLOAD.key: {
|
||||||
|
eventBus && eventBus.dispatch(EVENT_BUS_TYPE.DOWNLOAD_RECORDS, selectedRecordIds);
|
||||||
|
break;
|
||||||
|
}
|
||||||
case TextTranslation.EXTRACT_FILE_DETAIL.key:
|
case TextTranslation.EXTRACT_FILE_DETAIL.key:
|
||||||
case TextTranslation.EXTRACT_FILE_DETAILS.key: {
|
case TextTranslation.EXTRACT_FILE_DETAILS.key: {
|
||||||
const imageOrVideoRecords = records.filter(record => {
|
const imageOrVideoRecords = records.filter(record => {
|
||||||
@@ -172,6 +209,23 @@ const TableFilesToolbar = ({ repoID }) => {
|
|||||||
</span>
|
</span>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
|
{(length > 1 && canModify && areRecordsInSameFolder) &&
|
||||||
|
<>
|
||||||
|
<span className="cur-view-path-btn" onClick={toggleMoveDialog}>
|
||||||
|
<span className="sf3-font-move1 sf3-font" aria-label={gettext('Move')} title={gettext('Move')}></span>
|
||||||
|
</span>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
{((length === 1) || (length > 1 && areRecordsInSameFolder)) &&
|
||||||
|
<>
|
||||||
|
<span className="cur-view-path-btn" onClick={toggleCopyDialog}>
|
||||||
|
<span className="sf3-font-copy1 sf3-font" aria-label={gettext('Copy')} title={gettext('Copy')}></span>
|
||||||
|
</span>
|
||||||
|
<span className="cur-view-path-btn" onClick={downloadRecords}>
|
||||||
|
<span className="sf3-font-download1 sf3-font" aria-label={gettext('Download')} title={gettext('Download')}></span>
|
||||||
|
</span>
|
||||||
|
</>
|
||||||
|
}
|
||||||
{canModify &&
|
{canModify &&
|
||||||
<span className="cur-view-path-btn" onClick={deleteRecords}>
|
<span className="cur-view-path-btn" onClick={deleteRecords}>
|
||||||
<span className="sf3-font-delete1 sf3-font" aria-label={gettext('Delete')} title={gettext('Delete')}></span>
|
<span className="sf3-font-delete1 sf3-font" aria-label={gettext('Delete')} title={gettext('Delete')}></span>
|
||||||
|
@@ -43,6 +43,9 @@ export const EVENT_BUS_TYPE = {
|
|||||||
TOGGLE_MOVE_DIALOG: 'toggle_move_dialog',
|
TOGGLE_MOVE_DIALOG: 'toggle_move_dialog',
|
||||||
MOVE_RECORD: 'move_record',
|
MOVE_RECORD: 'move_record',
|
||||||
DUPLICATE_RECORD: 'duplicate_record',
|
DUPLICATE_RECORD: 'duplicate_record',
|
||||||
|
TOGGLE_COPY_DIALOG: 'toggle_copy_dialog',
|
||||||
|
COPY_RECORDS: 'copy_records',
|
||||||
|
DOWNLOAD_RECORDS: 'download_records',
|
||||||
DELETE_RECORDS: 'delete_records',
|
DELETE_RECORDS: 'delete_records',
|
||||||
UPDATE_RECORD_DETAILS: 'update_record_details',
|
UPDATE_RECORD_DETAILS: 'update_record_details',
|
||||||
UPDATE_FACE_RECOGNITION: 'update_face_recognition',
|
UPDATE_FACE_RECOGNITION: 'update_face_recognition',
|
||||||
|
@@ -38,7 +38,7 @@ export const MetadataViewProvider = ({
|
|||||||
const { collaborators, collaboratorsCache } = useCollaborators();
|
const { collaborators, collaboratorsCache } = useCollaborators();
|
||||||
const { isBeingBuilt, setIsBeingBuilt } = useMetadata();
|
const { isBeingBuilt, setIsBeingBuilt } = useMetadata();
|
||||||
const { onOCR: OCRAPI, generateDescription, extractFilesDetails, faceRecognition, generateFileTags: generateFileTagsAPI } = useMetadataAIOperations();
|
const { onOCR: OCRAPI, generateDescription, extractFilesDetails, faceRecognition, generateFileTags: generateFileTagsAPI } = useMetadataAIOperations();
|
||||||
const { handleMove } = useFileOperations();
|
const { handleMove, handleCopy, handleDownload } = useFileOperations();
|
||||||
const { globalHiddenColumns } = useMetadataStatus();
|
const { globalHiddenColumns } = useMetadataStatus();
|
||||||
|
|
||||||
const storeRef = useRef(null);
|
const storeRef = useRef(null);
|
||||||
@@ -419,16 +419,206 @@ export const MetadataViewProvider = ({
|
|||||||
});
|
});
|
||||||
}, [updateFileTags, generateFileTagsAPI]);
|
}, [updateFileTags, generateFileTagsAPI]);
|
||||||
|
|
||||||
const handleMoveRecord = (record) => {
|
const areRecordsInSameFolder = useCallback((records) => {
|
||||||
const path = getParentDirFromRecord(record);
|
if (!records || records.length <= 1) return true;
|
||||||
const currentRecordId = getRecordIdFromRecord(record);
|
const firstPath = getParentDirFromRecord(records[0]);
|
||||||
const fileName = getFileNameFromRecord(record);
|
return records.every(record => getParentDirFromRecord(record) === firstPath);
|
||||||
const dirent = new Dirent({ name: fileName });
|
}, []);
|
||||||
|
|
||||||
|
const calculateBatchMoveUpdateData = useCallback((records, targetRepoId, targetParentPath, sourceParentPath) => {
|
||||||
|
const { rows } = storeRef.current.data || metadata;
|
||||||
|
let needDeletedRowIds = [];
|
||||||
|
let updateRowIds = [];
|
||||||
|
let idRowUpdates = {};
|
||||||
|
let idOldRowData = {};
|
||||||
|
|
||||||
|
records.forEach(record => {
|
||||||
|
const rowId = getRecordIdFromRecord(record);
|
||||||
|
const isDir = checkIsDir(record);
|
||||||
|
const oldName = getFileNameFromRecord(record);
|
||||||
|
const oldParentPath = Utils.joinPath(sourceParentPath, oldName);
|
||||||
|
|
||||||
|
if (repoID === targetRepoId) {
|
||||||
|
const newName = getUniqueFileName(rows, targetParentPath, oldName);
|
||||||
|
updateRowIds.push(rowId);
|
||||||
|
idRowUpdates[rowId] = {
|
||||||
|
[PRIVATE_COLUMN_KEY.PARENT_DIR]: targetParentPath,
|
||||||
|
[PRIVATE_COLUMN_KEY.FILE_NAME]: newName
|
||||||
|
};
|
||||||
|
idOldRowData[rowId] = {
|
||||||
|
[PRIVATE_COLUMN_KEY.PARENT_DIR]: sourceParentPath,
|
||||||
|
[PRIVATE_COLUMN_KEY.FILE_NAME]: oldName
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isDir) {
|
||||||
|
const newPath = Utils.joinPath(targetParentPath, newName);
|
||||||
|
rows.forEach((row) => {
|
||||||
|
const parentDir = getParentDirFromRecord(row);
|
||||||
|
if (row && parentDir.startsWith(oldParentPath)) {
|
||||||
|
const updateRowId = getRecordIdFromRecord(row);
|
||||||
|
updateRowIds.push(updateRowId);
|
||||||
|
idRowUpdates[updateRowId] = { [PRIVATE_COLUMN_KEY.PARENT_DIR]: parentDir.replace(oldParentPath, newPath) };
|
||||||
|
idOldRowData[updateRowId] = { [PRIVATE_COLUMN_KEY.PARENT_DIR]: parentDir };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
needDeletedRowIds.push(rowId);
|
||||||
|
if (isDir) {
|
||||||
|
rows.forEach((row) => {
|
||||||
|
const parentDir = getParentDirFromRecord(row);
|
||||||
|
if (row && parentDir.startsWith(oldParentPath)) {
|
||||||
|
const id = getRecordIdFromRecord(row);
|
||||||
|
needDeletedRowIds.push(id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
modify_row_ids: updateRowIds,
|
||||||
|
modify_id_row_updates: idRowUpdates,
|
||||||
|
modify_id_old_row_data: idOldRowData,
|
||||||
|
delete_row_ids: needDeletedRowIds,
|
||||||
|
};
|
||||||
|
}, [metadata, repoID]);
|
||||||
|
|
||||||
|
const handleMoveRecords = (records) => {
|
||||||
|
if (records.length > 1) {
|
||||||
|
if (!areRecordsInSameFolder(records)) {
|
||||||
|
toaster.danger(gettext('Can only move files that are in the same folder'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const path = getParentDirFromRecord(records[0]);
|
||||||
|
const recordIds = records.map(r => getRecordIdFromRecord(r));
|
||||||
|
const dirents = records.map(r => getFileNameFromRecord(r));
|
||||||
|
const selectedDirentList = records.map(r => {
|
||||||
|
const fileName = getFileNameFromRecord(r);
|
||||||
|
return new Dirent({ name: fileName, is_dir: checkIsDir(r) });
|
||||||
|
});
|
||||||
|
|
||||||
|
const callback = (destRepo, destDirentPath, isByDialog = false) => {
|
||||||
|
window.sfMetadataContext.eventBus.dispatch(EVENT_BUS_TYPE.SELECT_NONE);
|
||||||
|
const updateData = calculateBatchMoveUpdateData(records, destRepo.repo_id, destDirentPath, path);
|
||||||
|
|
||||||
|
storeRef.current.moveRecords(recordIds, destRepo.repo_id, dirents, destDirentPath, path, updateData, {
|
||||||
|
success_callback: (operation) => {
|
||||||
|
if (selectedDirentList.length > 0) {
|
||||||
|
moveFileCallback && moveFileCallback(
|
||||||
|
repoID,
|
||||||
|
destRepo,
|
||||||
|
selectedDirentList[0],
|
||||||
|
destDirentPath,
|
||||||
|
path,
|
||||||
|
operation.task_id,
|
||||||
|
isByDialog,
|
||||||
|
{
|
||||||
|
isBatchOperation: true,
|
||||||
|
batchFileNames: dirents,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fail_callback: (error) => {
|
||||||
|
error && toaster.danger(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
handleMove(path, selectedDirentList, true, callback);
|
||||||
|
} else {
|
||||||
|
// Single record
|
||||||
|
const path = getParentDirFromRecord(records[0]);
|
||||||
|
const currentRecordId = getRecordIdFromRecord(records[0]);
|
||||||
|
const fileName = getFileNameFromRecord(records[0]);
|
||||||
|
const dirent = new Dirent({ name: fileName, is_dir: checkIsDir(records[0]) });
|
||||||
const callback = (...params) => {
|
const callback = (...params) => {
|
||||||
window.sfMetadataContext.eventBus.dispatch(EVENT_BUS_TYPE.SELECT_NONE);
|
window.sfMetadataContext.eventBus.dispatch(EVENT_BUS_TYPE.SELECT_NONE);
|
||||||
moveRecord && moveRecord(currentRecordId, ...params);
|
moveRecord && moveRecord(currentRecordId, ...params);
|
||||||
};
|
};
|
||||||
handleMove(path, dirent, false, callback);
|
handleMove(path, dirent, false, callback);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCopyRecords = (records) => {
|
||||||
|
if (records.length > 1) {
|
||||||
|
if (!areRecordsInSameFolder(records)) {
|
||||||
|
toaster.danger(gettext('Can only copy files that are in the same folder'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const path = getParentDirFromRecord(records[0]);
|
||||||
|
const recordIds = records.map(r => getRecordIdFromRecord(r));
|
||||||
|
const dirents = records.map(r => getFileNameFromRecord(r));
|
||||||
|
const selectedDirentList = records.map(r => {
|
||||||
|
const fileName = getFileNameFromRecord(r);
|
||||||
|
return new Dirent({ name: fileName, is_dir: checkIsDir(r) });
|
||||||
|
});
|
||||||
|
|
||||||
|
const callback = (destRepo, destDirentPath, isByDialog = false) => {
|
||||||
|
window.sfMetadataContext.eventBus.dispatch(EVENT_BUS_TYPE.SELECT_NONE);
|
||||||
|
storeRef.current.duplicateRecords(recordIds, destRepo.repo_id, dirents, destDirentPath, path, {
|
||||||
|
success_callback: (operation) => {
|
||||||
|
if (selectedDirentList.length > 0) {
|
||||||
|
copyFileCallback && copyFileCallback(
|
||||||
|
repoID,
|
||||||
|
destRepo,
|
||||||
|
selectedDirentList[0],
|
||||||
|
destDirentPath,
|
||||||
|
path,
|
||||||
|
operation.task_id,
|
||||||
|
isByDialog,
|
||||||
|
{
|
||||||
|
isBatchOperation: true,
|
||||||
|
batchFileNames: dirents,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (repoID === destRepo.repo_id) {
|
||||||
|
delayReloadMetadata();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fail_callback: (error) => {
|
||||||
|
error && toaster.danger(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
handleCopy(path, selectedDirentList, true, callback);
|
||||||
|
} else {
|
||||||
|
// Single record
|
||||||
|
const path = getParentDirFromRecord(records[0]);
|
||||||
|
const currentRecordId = getRecordIdFromRecord(records[0]);
|
||||||
|
const fileName = getFileNameFromRecord(records[0]);
|
||||||
|
const dirent = new Dirent({ name: fileName, is_dir: checkIsDir(records[0]) });
|
||||||
|
const callback = (...params) => {
|
||||||
|
window.sfMetadataContext.eventBus.dispatch(EVENT_BUS_TYPE.SELECT_NONE);
|
||||||
|
duplicateRecord && duplicateRecord(currentRecordId, ...params);
|
||||||
|
};
|
||||||
|
handleCopy(path, dirent, false, callback);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDownloadRecords = (recordIds) => {
|
||||||
|
if (recordIds.length === 0) return;
|
||||||
|
|
||||||
|
const records = recordIds.map(id => storeRef.current?.data?.id_row_map?.[id]).filter(Boolean);
|
||||||
|
if (records.length === 0) return;
|
||||||
|
|
||||||
|
if (!areRecordsInSameFolder(records)) {
|
||||||
|
toaster.danger(gettext('Can only download files that are in the same folder'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const path = getParentDirFromRecord(records[0]);
|
||||||
|
const direntList = records.map(record => {
|
||||||
|
const fileName = getFileNameFromRecord(record);
|
||||||
|
return { name: fileName, is_dir: checkIsDir(record) };
|
||||||
|
});
|
||||||
|
|
||||||
|
handleDownload(path, direntList);
|
||||||
|
window.sfMetadataContext.eventBus.dispatch(EVENT_BUS_TYPE.SELECT_NONE);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getSearchableValue = useCallback((row, column, collaborators, tagsData, collaboratorsCache) => {
|
const getSearchableValue = useCallback((row, column, collaborators, tagsData, collaboratorsCache) => {
|
||||||
@@ -639,7 +829,9 @@ export const MetadataViewProvider = ({
|
|||||||
const unsubscribeUpdateFaceRecognition = eventBus.subscribe(EVENT_BUS_TYPE.UPDATE_FACE_RECOGNITION, updateFaceRecognition);
|
const unsubscribeUpdateFaceRecognition = eventBus.subscribe(EVENT_BUS_TYPE.UPDATE_FACE_RECOGNITION, updateFaceRecognition);
|
||||||
const unsubscribeUpdateDescription = eventBus.subscribe(EVENT_BUS_TYPE.GENERATE_DESCRIPTION, updateRecordDescription);
|
const unsubscribeUpdateDescription = eventBus.subscribe(EVENT_BUS_TYPE.GENERATE_DESCRIPTION, updateRecordDescription);
|
||||||
const unsubscribeOCR = eventBus.subscribe(EVENT_BUS_TYPE.OCR, onOCR);
|
const unsubscribeOCR = eventBus.subscribe(EVENT_BUS_TYPE.OCR, onOCR);
|
||||||
const unsubscribeToggleMoveDialog = eventBus.subscribe(EVENT_BUS_TYPE.TOGGLE_MOVE_DIALOG, handleMoveRecord);
|
const unsubscribeToggleMoveDialog = eventBus.subscribe(EVENT_BUS_TYPE.TOGGLE_MOVE_DIALOG, handleMoveRecords);
|
||||||
|
const unsubscribeToggleCopyDialog = eventBus.subscribe(EVENT_BUS_TYPE.TOGGLE_COPY_DIALOG, handleCopyRecords);
|
||||||
|
const unsubscribeDownloadRecords = eventBus.subscribe(EVENT_BUS_TYPE.DOWNLOAD_RECORDS, handleDownloadRecords);
|
||||||
const unsubscribeSearchRows = eventBus.subscribe(EVENT_BUS_TYPE.SEARCH_ROWS, handleSearchRows);
|
const unsubscribeSearchRows = eventBus.subscribe(EVENT_BUS_TYPE.SEARCH_ROWS, handleSearchRows);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
@@ -669,6 +861,8 @@ export const MetadataViewProvider = ({
|
|||||||
unsubscribeUpdateDescription();
|
unsubscribeUpdateDescription();
|
||||||
unsubscribeOCR();
|
unsubscribeOCR();
|
||||||
unsubscribeToggleMoveDialog();
|
unsubscribeToggleMoveDialog();
|
||||||
|
unsubscribeToggleCopyDialog();
|
||||||
|
unsubscribeDownloadRecords();
|
||||||
unsubscribeSearchRows();
|
unsubscribeSearchRows();
|
||||||
delayReloadDataTimer.current && clearTimeout(delayReloadDataTimer.current);
|
delayReloadDataTimer.current && clearTimeout(delayReloadDataTimer.current);
|
||||||
};
|
};
|
||||||
|
@@ -255,7 +255,9 @@ class DataProcessor {
|
|||||||
this.updateSummaries();
|
this.updateSummaries();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case OPERATION_TYPE.MOVE_RECORD: {
|
case OPERATION_TYPE.MOVE_RECORD:
|
||||||
|
case OPERATION_TYPE.DUPLICATE_RECORD:
|
||||||
|
case OPERATION_TYPE.DUPLICATE_RECORDS: {
|
||||||
this.run(table, { collaborators });
|
this.run(table, { collaborators });
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@@ -436,6 +436,23 @@ class Store {
|
|||||||
this.applyOperation(operation);
|
this.applyOperation(operation);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
moveRecords(row_ids, target_repo_id, dirents, target_parent_path, source_parent_path, update_data, { success_callback, fail_callback }) {
|
||||||
|
const type = OPERATION_TYPE.MOVE_RECORDS;
|
||||||
|
const operation = this.createOperation({
|
||||||
|
type,
|
||||||
|
repo_id: this.repoId,
|
||||||
|
row_ids,
|
||||||
|
target_repo_id,
|
||||||
|
dirents,
|
||||||
|
target_parent_path,
|
||||||
|
source_parent_path,
|
||||||
|
update_data,
|
||||||
|
success_callback,
|
||||||
|
fail_callback,
|
||||||
|
});
|
||||||
|
this.applyOperation(operation);
|
||||||
|
}
|
||||||
|
|
||||||
duplicateRecord(row_id, target_repo_id, dirent, target_parent_path, source_parent_path, { success_callback, fail_callback }) {
|
duplicateRecord(row_id, target_repo_id, dirent, target_parent_path, source_parent_path, { success_callback, fail_callback }) {
|
||||||
const type = OPERATION_TYPE.DUPLICATE_RECORD;
|
const type = OPERATION_TYPE.DUPLICATE_RECORD;
|
||||||
const operation = this.createOperation({
|
const operation = this.createOperation({
|
||||||
@@ -452,6 +469,22 @@ class Store {
|
|||||||
this.applyOperation(operation);
|
this.applyOperation(operation);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
duplicateRecords(row_ids, target_repo_id, dirents, target_parent_path, source_parent_path, { success_callback, fail_callback }) {
|
||||||
|
const type = OPERATION_TYPE.DUPLICATE_RECORDS;
|
||||||
|
const operation = this.createOperation({
|
||||||
|
type,
|
||||||
|
repo_id: this.repoId,
|
||||||
|
row_ids,
|
||||||
|
target_repo_id,
|
||||||
|
dirents,
|
||||||
|
target_parent_path,
|
||||||
|
source_parent_path,
|
||||||
|
success_callback,
|
||||||
|
fail_callback,
|
||||||
|
});
|
||||||
|
this.applyOperation(operation);
|
||||||
|
}
|
||||||
|
|
||||||
modifyFilters(filterConjunction, filters, basicFilters = []) {
|
modifyFilters(filterConjunction, filters, basicFilters = []) {
|
||||||
const type = OPERATION_TYPE.MODIFY_FILTERS;
|
const type = OPERATION_TYPE.MODIFY_FILTERS;
|
||||||
const operation = this.createOperation({
|
const operation = this.createOperation({
|
||||||
|
@@ -128,7 +128,8 @@ export default function apply(data, operation) {
|
|||||||
data.rows = updatedRows;
|
data.rows = updatedRows;
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
case OPERATION_TYPE.MOVE_RECORD: {
|
case OPERATION_TYPE.MOVE_RECORD:
|
||||||
|
case OPERATION_TYPE.MOVE_RECORDS: {
|
||||||
const { update_data } = operation;
|
const { update_data } = operation;
|
||||||
const {
|
const {
|
||||||
modify_row_ids: updateRowIds,
|
modify_row_ids: updateRowIds,
|
||||||
@@ -145,6 +146,11 @@ export default function apply(data, operation) {
|
|||||||
}
|
}
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
case OPERATION_TYPE.DUPLICATE_RECORD:
|
||||||
|
case OPERATION_TYPE.DUPLICATE_RECORDS: {
|
||||||
|
// Duplicate operations don't modify local data immediately
|
||||||
|
return data;
|
||||||
|
}
|
||||||
case OPERATION_TYPE.MODIFY_FILTERS: {
|
case OPERATION_TYPE.MODIFY_FILTERS: {
|
||||||
const { filter_conjunction, filters, basic_filters } = operation;
|
const { filter_conjunction, filters, basic_filters } = operation;
|
||||||
data.view.filter_conjunction = filter_conjunction;
|
data.view.filter_conjunction = filter_conjunction;
|
||||||
|
@@ -26,7 +26,9 @@ export const OPERATION_TYPE = {
|
|||||||
MODIFY_RECORD_VIA_BUTTON: 'modify_record_via_button',
|
MODIFY_RECORD_VIA_BUTTON: 'modify_record_via_button',
|
||||||
MODIFY_LOCAL_RECORD: 'modify_local_record',
|
MODIFY_LOCAL_RECORD: 'modify_local_record',
|
||||||
MOVE_RECORD: 'move_record',
|
MOVE_RECORD: 'move_record',
|
||||||
|
MOVE_RECORDS: 'move_records',
|
||||||
DUPLICATE_RECORD: 'duplicate_record',
|
DUPLICATE_RECORD: 'duplicate_record',
|
||||||
|
DUPLICATE_RECORDS: 'duplicate_records',
|
||||||
|
|
||||||
// face table
|
// face table
|
||||||
RENAME_PEOPLE_NAME: 'rename_people_name',
|
RENAME_PEOPLE_NAME: 'rename_people_name',
|
||||||
@@ -54,6 +56,8 @@ export const OPERATION_ATTRIBUTES = {
|
|||||||
[OPERATION_TYPE.RELOAD_RECORDS]: ['repo_id', 'row_ids'],
|
[OPERATION_TYPE.RELOAD_RECORDS]: ['repo_id', 'row_ids'],
|
||||||
[OPERATION_TYPE.MOVE_RECORD]: ['repo_id', 'row_id', 'target_repo_id', 'dirent', 'target_parent_path', 'source_parent_path', 'update_data'],
|
[OPERATION_TYPE.MOVE_RECORD]: ['repo_id', 'row_id', 'target_repo_id', 'dirent', 'target_parent_path', 'source_parent_path', 'update_data'],
|
||||||
[OPERATION_TYPE.DUPLICATE_RECORD]: ['repo_id', 'row_id', 'target_repo_id', 'dirent', 'target_parent_path', 'source_parent_path'],
|
[OPERATION_TYPE.DUPLICATE_RECORD]: ['repo_id', 'row_id', 'target_repo_id', 'dirent', 'target_parent_path', 'source_parent_path'],
|
||||||
|
[OPERATION_TYPE.MOVE_RECORDS]: ['repo_id', 'row_ids', 'target_repo_id', 'dirents', 'target_parent_path', 'source_parent_path', 'update_data'],
|
||||||
|
[OPERATION_TYPE.DUPLICATE_RECORDS]: ['repo_id', 'row_ids', 'target_repo_id', 'dirents', 'target_parent_path', 'source_parent_path'],
|
||||||
[OPERATION_TYPE.MODIFY_LOCAL_RECORD]: ['repo_id', 'row_id', 'parent_dir', 'file_name', 'updates'],
|
[OPERATION_TYPE.MODIFY_LOCAL_RECORD]: ['repo_id', 'row_id', 'parent_dir', 'file_name', 'updates'],
|
||||||
|
|
||||||
[OPERATION_TYPE.MODIFY_FILTERS]: ['repo_id', 'view_id', 'filter_conjunction', 'filters', 'basic_filters'],
|
[OPERATION_TYPE.MODIFY_FILTERS]: ['repo_id', 'view_id', 'filter_conjunction', 'filters', 'basic_filters'],
|
||||||
@@ -109,6 +113,8 @@ export const NEED_APPLY_AFTER_SERVER_OPERATION = [
|
|||||||
OPERATION_TYPE.MODIFY_SORTS,
|
OPERATION_TYPE.MODIFY_SORTS,
|
||||||
OPERATION_TYPE.MOVE_RECORD,
|
OPERATION_TYPE.MOVE_RECORD,
|
||||||
OPERATION_TYPE.DUPLICATE_RECORD,
|
OPERATION_TYPE.DUPLICATE_RECORD,
|
||||||
|
OPERATION_TYPE.MOVE_RECORDS,
|
||||||
|
OPERATION_TYPE.DUPLICATE_RECORDS,
|
||||||
];
|
];
|
||||||
|
|
||||||
export const VIEW_OPERATION = [
|
export const VIEW_OPERATION = [
|
||||||
|
@@ -98,6 +98,20 @@ class ServerOperator {
|
|||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case OPERATION_TYPE.MOVE_RECORDS: {
|
||||||
|
const { repo_id, target_repo_id, dirents, target_parent_path, source_parent_path } = operation;
|
||||||
|
seafileAPI.moveDir(repo_id, target_repo_id, target_parent_path, source_parent_path, dirents).then(res => {
|
||||||
|
operation.task_id = res.data.task_id || null;
|
||||||
|
callback({ operation });
|
||||||
|
}).catch(error => {
|
||||||
|
const count = dirents.length;
|
||||||
|
const errorMsg = count > 1
|
||||||
|
? gettext('Failed to move {n} items').replace('{n}', count)
|
||||||
|
: gettext('Failed to move file');
|
||||||
|
callback({ error: errorMsg });
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
case OPERATION_TYPE.DUPLICATE_RECORD: {
|
case OPERATION_TYPE.DUPLICATE_RECORD: {
|
||||||
const { row_id, repo_id, target_repo_id, dirent, target_parent_path, source_parent_path } = operation;
|
const { row_id, repo_id, target_repo_id, dirent, target_parent_path, source_parent_path } = operation;
|
||||||
seafileAPI.copyDir(repo_id, target_repo_id, target_parent_path, source_parent_path, dirent.name).then(res => {
|
seafileAPI.copyDir(repo_id, target_repo_id, target_parent_path, source_parent_path, dirent.name).then(res => {
|
||||||
@@ -110,6 +124,20 @@ class ServerOperator {
|
|||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case OPERATION_TYPE.DUPLICATE_RECORDS: {
|
||||||
|
const { repo_id, target_repo_id, dirents, target_parent_path, source_parent_path } = operation;
|
||||||
|
seafileAPI.copyDir(repo_id, target_repo_id, target_parent_path, source_parent_path, dirents).then(res => {
|
||||||
|
operation.task_id = res.data.task_id || null;
|
||||||
|
callback({ operation });
|
||||||
|
}).catch(error => {
|
||||||
|
const count = dirents.length;
|
||||||
|
const errorMsg = count > 1
|
||||||
|
? gettext('Failed to copy {n} items').replace('{n}', count)
|
||||||
|
: gettext('Failed to copy file');
|
||||||
|
callback({ error: errorMsg });
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
case OPERATION_TYPE.INSERT_COLUMN: {
|
case OPERATION_TYPE.INSERT_COLUMN: {
|
||||||
const { repo_id, name, column_type, column_key, data } = operation;
|
const { repo_id, name, column_type, column_key, data } = operation;
|
||||||
window.sfMetadataContext.insertColumn(repo_id, name, column_type, { key: column_key, data }).then(res => {
|
window.sfMetadataContext.insertColumn(repo_id, name, column_type, { key: column_key, data }).then(res => {
|
||||||
|
@@ -29,6 +29,8 @@ const OPERATION = {
|
|||||||
FILE_DETAILS: 'file-details',
|
FILE_DETAILS: 'file-details',
|
||||||
DETECT_FACES: 'detect-faces',
|
DETECT_FACES: 'detect-faces',
|
||||||
MOVE: 'move',
|
MOVE: 'move',
|
||||||
|
COPY: 'copy',
|
||||||
|
DOWNLOAD: 'download',
|
||||||
};
|
};
|
||||||
|
|
||||||
const ContextMenu = ({
|
const ContextMenu = ({
|
||||||
@@ -119,7 +121,6 @@ const ContextMenu = ({
|
|||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
// handle selected records
|
|
||||||
const selectedRecordsIds = recordMetrics ? Object.keys(recordMetrics.idSelectedRecordMap) : [];
|
const selectedRecordsIds = recordMetrics ? Object.keys(recordMetrics.idSelectedRecordMap) : [];
|
||||||
if (selectedRecordsIds.length > 1) {
|
if (selectedRecordsIds.length > 1) {
|
||||||
let records = [];
|
let records = [];
|
||||||
@@ -130,6 +131,20 @@ const ContextMenu = ({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const areRecordsInSameFolder = (() => {
|
||||||
|
if (records.length <= 1) return true;
|
||||||
|
const firstPath = getParentDirFromRecord(records[0]);
|
||||||
|
return records.every(record => getParentDirFromRecord(record) === firstPath);
|
||||||
|
})();
|
||||||
|
|
||||||
|
if (areRecordsInSameFolder) {
|
||||||
|
if (!isReadonly) {
|
||||||
|
list.push({ value: OPERATION.MOVE, label: gettext('Move'), records });
|
||||||
|
list.push({ value: OPERATION.COPY, label: gettext('Copy'), records });
|
||||||
|
}
|
||||||
|
list.push({ value: OPERATION.DOWNLOAD, label: gettext('Download'), records });
|
||||||
|
}
|
||||||
|
|
||||||
const ableDeleteRecords = getAbleDeleteRecords(records);
|
const ableDeleteRecords = getAbleDeleteRecords(records);
|
||||||
if (ableDeleteRecords.length > 0) {
|
if (ableDeleteRecords.length > 0) {
|
||||||
list.push({ value: OPERATION.DELETE_RECORDS, label: gettext('Delete'), records: ableDeleteRecords });
|
list.push({ value: OPERATION.DELETE_RECORDS, label: gettext('Delete'), records: ableDeleteRecords });
|
||||||
@@ -177,6 +192,10 @@ const ContextMenu = ({
|
|||||||
if (canModifyRow) {
|
if (canModifyRow) {
|
||||||
modifyOptions.push({ value: OPERATION.MOVE, label: isFolder ? gettext('Move folder') : gettext('Move file'), record });
|
modifyOptions.push({ value: OPERATION.MOVE, label: isFolder ? gettext('Move folder') : gettext('Move file'), record });
|
||||||
}
|
}
|
||||||
|
// Add copy and download options for single record
|
||||||
|
modifyOptions.push({ value: OPERATION.COPY, label: isFolder ? gettext('Copy folder') : gettext('Copy file'), record });
|
||||||
|
modifyOptions.push({ value: OPERATION.DOWNLOAD, label: isFolder ? gettext('Download folder') : gettext('Download file'), record });
|
||||||
|
|
||||||
if (canDeleteRow) {
|
if (canDeleteRow) {
|
||||||
modifyOptions.push({ value: OPERATION.DELETE_RECORD, label: isFolder ? gettext('Delete folder') : gettext('Delete file'), record });
|
modifyOptions.push({ value: OPERATION.DELETE_RECORD, label: isFolder ? gettext('Delete folder') : gettext('Delete file'), record });
|
||||||
}
|
}
|
||||||
@@ -301,9 +320,33 @@ const ContextMenu = ({
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case OPERATION.MOVE: {
|
case OPERATION.MOVE: {
|
||||||
const { record } = option;
|
const { record, records } = option;
|
||||||
if (!record) break;
|
if (records) {
|
||||||
window.sfMetadataContext.eventBus.dispatch(EVENT_BUS_TYPE.TOGGLE_MOVE_DIALOG, record);
|
window.sfMetadataContext.eventBus.dispatch(EVENT_BUS_TYPE.TOGGLE_MOVE_DIALOG, records);
|
||||||
|
} else if (record) {
|
||||||
|
window.sfMetadataContext.eventBus.dispatch(EVENT_BUS_TYPE.TOGGLE_MOVE_DIALOG, [record]);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case OPERATION.COPY: {
|
||||||
|
const { record, records } = option;
|
||||||
|
if (records) {
|
||||||
|
window.sfMetadataContext.eventBus.dispatch(EVENT_BUS_TYPE.TOGGLE_COPY_DIALOG, records);
|
||||||
|
} else if (record) {
|
||||||
|
window.sfMetadataContext.eventBus.dispatch(EVENT_BUS_TYPE.TOGGLE_COPY_DIALOG, [record]);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case OPERATION.DOWNLOAD: {
|
||||||
|
const { record, records } = option;
|
||||||
|
if (records) {
|
||||||
|
// Multiple records
|
||||||
|
const recordIds = records.map(r => r._id).filter(Boolean);
|
||||||
|
window.sfMetadataContext.eventBus.dispatch(EVENT_BUS_TYPE.DOWNLOAD_RECORDS, recordIds);
|
||||||
|
} else if (record) {
|
||||||
|
// Single record
|
||||||
|
window.sfMetadataContext.eventBus.dispatch(EVENT_BUS_TYPE.DOWNLOAD_RECORDS, [record._id]);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default: {
|
default: {
|
||||||
|
@@ -1305,15 +1305,23 @@ class LibContentView extends React.Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// list operations
|
// list operations
|
||||||
moveItemsAjaxCallback = (repoID, targetRepo, dirent, moveToDirentPath, nodeParentPath, taskId, byDialog = false) => {
|
moveItemsAjaxCallback = (repoID, targetRepo, dirent, moveToDirentPath, nodeParentPath, taskId, byDialog = false, options = {}) => {
|
||||||
this.updateCurrentNotExistDirent(dirent);
|
this.updateCurrentNotExistDirent(dirent);
|
||||||
|
|
||||||
|
const {
|
||||||
|
isBatchOperation = false,
|
||||||
|
batchFileNames = [],
|
||||||
|
customMessage = null
|
||||||
|
} = options;
|
||||||
|
|
||||||
const dirName = dirent.name;
|
const dirName = dirent.name;
|
||||||
const direntPath = Utils.joinPath(nodeParentPath, dirName);
|
const direntPath = Utils.joinPath(nodeParentPath, dirName);
|
||||||
|
|
||||||
if (repoID !== targetRepo.repo_id) {
|
if (repoID !== targetRepo.repo_id) {
|
||||||
|
const operatedFilesLength = isBatchOperation ? batchFileNames.length : 1;
|
||||||
this.setState({
|
this.setState({
|
||||||
asyncCopyMoveTaskId: taskId,
|
asyncCopyMoveTaskId: taskId,
|
||||||
asyncOperatedFilesLength: 1,
|
asyncOperatedFilesLength: operatedFilesLength,
|
||||||
asyncOperationProgress: 0,
|
asyncOperationProgress: 0,
|
||||||
asyncOperationType: 'move',
|
asyncOperationType: 'move',
|
||||||
isCopyMoveProgressDialogShow: true,
|
isCopyMoveProgressDialogShow: true,
|
||||||
@@ -1324,28 +1332,38 @@ class LibContentView extends React.Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1. move to current repo
|
if (this.state.isTreePanelShown) {
|
||||||
// 2. tow columns mode need update left tree
|
if (isBatchOperation) {
|
||||||
const updateAfterMove = () => {
|
const direntPaths = batchFileNames.map(fileName => Utils.joinPath(nodeParentPath, fileName));
|
||||||
if (repoID === targetRepo.repo_id && this.state.isTreePanelShown) {
|
this.deleteTreeNodes(direntPaths, () => {
|
||||||
|
if (repoID === targetRepo.repo_id) {
|
||||||
this.updateMoveCopyTreeNode(moveToDirentPath);
|
this.updateMoveCopyTreeNode(moveToDirentPath);
|
||||||
}
|
}
|
||||||
this.moveDirent(direntPath, moveToDirentPath);
|
});
|
||||||
};
|
|
||||||
|
|
||||||
if (this.state.isTreePanelShown) {
|
|
||||||
this.deleteTreeNode(direntPath, updateAfterMove);
|
|
||||||
} else {
|
} else {
|
||||||
updateAfterMove();
|
this.deleteTreeNode(direntPath, () => {
|
||||||
|
if (repoID === targetRepo.repo_id) {
|
||||||
|
this.updateMoveCopyTreeNode(moveToDirentPath);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// show tip message if move to current repo
|
let message;
|
||||||
if (repoID === targetRepo.repo_id) {
|
|
||||||
let message = gettext('Successfully moved {name}.');
|
if (customMessage) {
|
||||||
|
message = customMessage;
|
||||||
|
} else if (isBatchOperation && batchFileNames.length > 1) {
|
||||||
|
message = gettext('Successfully moved {name} and {n} other items');
|
||||||
|
message = message.replace('{name}', batchFileNames[0])
|
||||||
|
.replace('{n}', batchFileNames.length - 1);
|
||||||
|
} else {
|
||||||
|
message = gettext('Successfully moved {name}.');
|
||||||
message = message.replace('{name}', dirName);
|
message = message.replace('{name}', dirName);
|
||||||
toaster.success(message);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toaster.success(message);
|
||||||
|
|
||||||
if (byDialog) {
|
if (byDialog) {
|
||||||
this.updateRecentlyUsedList(targetRepo, moveToDirentPath);
|
this.updateRecentlyUsedList(targetRepo, moveToDirentPath);
|
||||||
}
|
}
|
||||||
@@ -1382,12 +1400,20 @@ class LibContentView extends React.Component {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
copyItemsAjaxCallback = (repoID, targetRepo, dirent, copyToDirentPath, nodeParentPath, taskId, byDialog = false) => {
|
copyItemsAjaxCallback = (repoID, targetRepo, dirent, copyToDirentPath, nodeParentPath, taskId, byDialog = false, options = {}) => {
|
||||||
this.onSelectedDirentListUpdate([]);
|
this.onSelectedDirentListUpdate([]);
|
||||||
|
|
||||||
|
const {
|
||||||
|
isBatchOperation = false,
|
||||||
|
batchFileNames = [],
|
||||||
|
customMessage = null
|
||||||
|
} = options;
|
||||||
|
|
||||||
if (repoID !== targetRepo.repo_id) {
|
if (repoID !== targetRepo.repo_id) {
|
||||||
|
const operatedFilesLength = isBatchOperation ? batchFileNames.length : 1;
|
||||||
this.setState({
|
this.setState({
|
||||||
asyncCopyMoveTaskId: taskId,
|
asyncCopyMoveTaskId: taskId,
|
||||||
asyncOperatedFilesLength: 1,
|
asyncOperatedFilesLength: operatedFilesLength,
|
||||||
asyncOperationProgress: 0,
|
asyncOperationProgress: 0,
|
||||||
asyncOperationType: 'copy',
|
asyncOperationType: 'copy',
|
||||||
isCopyMoveProgressDialogShow: true
|
isCopyMoveProgressDialogShow: true
|
||||||
@@ -1409,8 +1435,19 @@ class LibContentView extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const dirName = dirent.name;
|
const dirName = dirent.name;
|
||||||
let message = gettext('Successfully copied %(name)s.');
|
let message;
|
||||||
|
|
||||||
|
if (customMessage) {
|
||||||
|
message = customMessage;
|
||||||
|
} else if (isBatchOperation && batchFileNames.length > 1) {
|
||||||
|
message = gettext('Successfully copied {name} and {n} other items');
|
||||||
|
message = message.replace('{name}', batchFileNames[0])
|
||||||
|
.replace('{n}', batchFileNames.length - 1);
|
||||||
|
} else {
|
||||||
|
message = gettext('Successfully copied %(name)s.');
|
||||||
message = message.replace('%(name)s', dirName);
|
message = message.replace('%(name)s', dirName);
|
||||||
|
}
|
||||||
|
|
||||||
toaster.success(message);
|
toaster.success(message);
|
||||||
|
|
||||||
if (byDialog) {
|
if (byDialog) {
|
||||||
@@ -2068,9 +2105,11 @@ class LibContentView extends React.Component {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
deleteTreeNodes = (paths) => {
|
deleteTreeNodes = (paths, callback) => {
|
||||||
let tree = treeHelper.deleteNodeListByPaths(this.state.treeData, paths);
|
let tree = treeHelper.deleteNodeListByPaths(this.state.treeData, paths);
|
||||||
this.setState({ treeData: tree });
|
this.setState({ treeData: tree }, () => {
|
||||||
|
callback && callback();
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
moveTreeNode = (nodePath, moveToPath, moveToRepo, nodeName) => {
|
moveTreeNode = (nodePath, moveToPath, moveToRepo, nodeName) => {
|
||||||
|
Reference in New Issue
Block a user