mirror of
https://github.com/haiwen/seahub.git
synced 2025-08-10 19:32:25 +00:00
Optimize/metadata context menu (#7206)
* move record * add copy option in gallery * optimize code * feat: optimize code * feat: optimize code * feat: optimize code * feat: optimzie code --------- Co-authored-by: zhouwenxuan <aries@Mac.local> Co-authored-by: 杨国璇 <ygx@Hello-word.local>
This commit is contained in:
parent
6d17ce3093
commit
b27a0170c7
@ -64,7 +64,9 @@ const propTypes = {
|
|||||||
deleteFilesCallback: PropTypes.func,
|
deleteFilesCallback: PropTypes.func,
|
||||||
renameFileCallback: PropTypes.func,
|
renameFileCallback: PropTypes.func,
|
||||||
onItemMove: PropTypes.func.isRequired,
|
onItemMove: PropTypes.func.isRequired,
|
||||||
|
moveFileCallback: PropTypes.func.isRequired,
|
||||||
onItemCopy: PropTypes.func.isRequired,
|
onItemCopy: PropTypes.func.isRequired,
|
||||||
|
copyFileCallback: PropTypes.func.isRequired,
|
||||||
onItemConvert: PropTypes.func.isRequired,
|
onItemConvert: PropTypes.func.isRequired,
|
||||||
onDirentClick: PropTypes.func.isRequired,
|
onDirentClick: PropTypes.func.isRequired,
|
||||||
isAllItemSelected: PropTypes.bool.isRequired,
|
isAllItemSelected: PropTypes.bool.isRequired,
|
||||||
@ -205,6 +207,9 @@ class DirColumnView extends React.Component {
|
|||||||
viewID={this.props.viewId}
|
viewID={this.props.viewId}
|
||||||
deleteFilesCallback={this.props.deleteFilesCallback}
|
deleteFilesCallback={this.props.deleteFilesCallback}
|
||||||
renameFileCallback={this.props.renameFileCallback}
|
renameFileCallback={this.props.renameFileCallback}
|
||||||
|
moveFileCallback={this.props.moveFileCallback}
|
||||||
|
copyFileCallback={this.props.copyFileCallback}
|
||||||
|
addFolder={this.props.onAddFolder}
|
||||||
updateCurrentDirent={this.props.updateCurrentDirent}
|
updateCurrentDirent={this.props.updateCurrentDirent}
|
||||||
closeDirentDetail={this.props.closeDirentDetail}
|
closeDirentDetail={this.props.closeDirentDetail}
|
||||||
showDirentDetail={this.props.showDirentDetail}
|
showDirentDetail={this.props.showDirentDetail}
|
||||||
|
@ -125,6 +125,12 @@ class Context {
|
|||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
canDuplicateRow = () => {
|
||||||
|
if (this.permission === 'r') return false;
|
||||||
|
const viewId = this.getSetting('viewID');
|
||||||
|
return viewId !== FACE_RECOGNITION_VIEW_ID;
|
||||||
|
};
|
||||||
|
|
||||||
canModifyColumn = (column) => {
|
canModifyColumn = (column) => {
|
||||||
if (this.permission === 'r') return false;
|
if (this.permission === 'r') return false;
|
||||||
const { editable } = column;
|
const { editable } = column;
|
||||||
|
@ -3,13 +3,14 @@ import React, { useContext, useEffect, useRef, useState, useCallback } from 'rea
|
|||||||
import toaster from '../../components/toast';
|
import toaster from '../../components/toast';
|
||||||
import Context from '../context';
|
import Context from '../context';
|
||||||
import Store from '../store';
|
import Store from '../store';
|
||||||
import { EVENT_BUS_TYPE, PER_LOAD_NUMBER } from '../constants';
|
import { EVENT_BUS_TYPE, PER_LOAD_NUMBER, PRIVATE_COLUMN_KEY } from '../constants';
|
||||||
import { Utils, validateName } from '../../utils/utils';
|
import { Utils, validateName } from '../../utils/utils';
|
||||||
import { useMetadata } from './metadata';
|
import { useMetadata } from './metadata';
|
||||||
import { useCollaborators } from './collaborators';
|
import { useCollaborators } from './collaborators';
|
||||||
import { getRowById } from '../utils/table';
|
import { getRowById } from '../utils/table';
|
||||||
import { getFileNameFromRecord, getParentDirFromRecord } from '../utils/cell';
|
import { getFileNameFromRecord, getParentDirFromRecord, getRecordIdFromRecord, getUniqueFileName } from '../utils/cell';
|
||||||
import { gettext } from '../../utils/constants';
|
import { gettext } from '../../utils/constants';
|
||||||
|
import { checkIsDir } from '../utils/row';
|
||||||
|
|
||||||
const MetadataViewContext = React.createContext(null);
|
const MetadataViewContext = React.createContext(null);
|
||||||
|
|
||||||
@ -19,12 +20,17 @@ export const MetadataViewProvider = ({
|
|||||||
viewID,
|
viewID,
|
||||||
renameFileCallback,
|
renameFileCallback,
|
||||||
deleteFilesCallback,
|
deleteFilesCallback,
|
||||||
|
moveFileCallback,
|
||||||
|
copyFileCallback,
|
||||||
...params
|
...params
|
||||||
}) => {
|
}) => {
|
||||||
const [isLoading, setLoading] = useState(true);
|
const [isLoading, setLoading] = useState(true);
|
||||||
const [metadata, setMetadata] = useState({ rows: [], columns: [], view: {} });
|
const [metadata, setMetadata] = useState({ rows: [], columns: [], view: {} });
|
||||||
const [errorMessage, setErrorMessage] = useState(null);
|
const [errorMessage, setErrorMessage] = useState(null);
|
||||||
|
|
||||||
const storeRef = useRef(null);
|
const storeRef = useRef(null);
|
||||||
|
const delayReloadDataTimer = useRef(null);
|
||||||
|
|
||||||
const { collaborators } = useCollaborators();
|
const { collaborators } = useCollaborators();
|
||||||
const { isBeingBuilt, setIsBeingBuilt } = useMetadata();
|
const { isBeingBuilt, setIsBeingBuilt } = useMetadata();
|
||||||
|
|
||||||
@ -45,6 +51,7 @@ export const MetadataViewProvider = ({
|
|||||||
storeRef.current.reload(PER_LOAD_NUMBER).then(() => {
|
storeRef.current.reload(PER_LOAD_NUMBER).then(() => {
|
||||||
setMetadata(storeRef.current.data);
|
setMetadata(storeRef.current.data);
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
|
delayReloadDataTimer.current = null;
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
const errorMsg = Utils.getErrorMsg(error);
|
const errorMsg = Utils.getErrorMsg(error);
|
||||||
setErrorMessage(errorMsg);
|
setErrorMessage(errorMsg);
|
||||||
@ -52,6 +59,13 @@ export const MetadataViewProvider = ({
|
|||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const delayReloadMetadata = useCallback(() => {
|
||||||
|
delayReloadDataTimer.current && clearTimeout(delayReloadDataTimer.current);
|
||||||
|
delayReloadDataTimer.current = setTimeout(() => {
|
||||||
|
reloadMetadata();
|
||||||
|
}, 600);
|
||||||
|
}, [reloadMetadata]);
|
||||||
|
|
||||||
const modifyFilters = useCallback((filters, filterConjunction, basicFilters) => {
|
const modifyFilters = useCallback((filters, filterConjunction, basicFilters) => {
|
||||||
storeRef.current.modifyFilters(filterConjunction, filters, basicFilters);
|
storeRef.current.modifyFilters(filterConjunction, filters, basicFilters);
|
||||||
}, [storeRef]);
|
}, [storeRef]);
|
||||||
@ -162,6 +176,78 @@ export const MetadataViewProvider = ({
|
|||||||
modifyRecords(rowIds, idRowUpdates, idOriginalRowUpdates, idOldRowData, idOriginalOldRowData, isCopyPaste, { success_callback, fail_callback });
|
modifyRecords(rowIds, idRowUpdates, idOriginalRowUpdates, idOldRowData, idOriginalOldRowData, isCopyPaste, { success_callback, fail_callback });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const moveRecord = (rowId, targetRepo, dirent, targetParentPath, sourceParentPath, isByDialog) => {
|
||||||
|
const targetRepoId = targetRepo.repo_id;
|
||||||
|
const row = getRowById(metadata, rowId);
|
||||||
|
const { rows } = metadata;
|
||||||
|
const isDir = checkIsDir(row);
|
||||||
|
const oldName = dirent.name;
|
||||||
|
const oldParentPath = Utils.joinPath(sourceParentPath, oldName);
|
||||||
|
|
||||||
|
let needDeletedRowIds = [];
|
||||||
|
let updateRowIds = [];
|
||||||
|
let idRowUpdates = {};
|
||||||
|
let idOldRowData = {};
|
||||||
|
|
||||||
|
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 = [rowId];
|
||||||
|
if (isDir) {
|
||||||
|
rows.forEach((row) => {
|
||||||
|
const parentDir = getParentDirFromRecord(row);
|
||||||
|
if (row && parentDir.startsWith(oldParentPath)) {
|
||||||
|
const id = getRecordIdFromRecord(row);
|
||||||
|
needDeletedRowIds.push(id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
storeRef.current.moveRecord(rowId, targetRepoId, dirent, targetParentPath, sourceParentPath, {
|
||||||
|
modify_row_ids: updateRowIds,
|
||||||
|
modify_id_row_updates: idRowUpdates,
|
||||||
|
modify_id_old_row_data: idOldRowData,
|
||||||
|
delete_row_ids: needDeletedRowIds,
|
||||||
|
}, {
|
||||||
|
success_callback: (operation) => {
|
||||||
|
moveFileCallback && moveFileCallback(repoID, targetRepo, dirent, targetParentPath, sourceParentPath, operation.task_id, isByDialog);
|
||||||
|
},
|
||||||
|
fail_callback: (error) => {
|
||||||
|
error && toaster.danger(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const duplicateRecord = (rowId, targetRepo, dirent, targetPath, nodeParentPath, isByDialog) => {
|
||||||
|
storeRef.current.duplicateRecord(rowId, targetRepo.repo_id, dirent, targetPath, nodeParentPath, {
|
||||||
|
success_callback: (operation) => {
|
||||||
|
copyFileCallback && copyFileCallback(repoID, targetRepo, dirent, targetPath, nodeParentPath, operation.task_id, isByDialog);
|
||||||
|
if (repoID === targetRepo.repo_id) {
|
||||||
|
delayReloadMetadata();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fail_callback: (error) => {
|
||||||
|
error && toaster.danger(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const renameColumn = useCallback((columnKey, newName, oldName) => {
|
const renameColumn = useCallback((columnKey, newName, oldName) => {
|
||||||
storeRef.current.renameColumn(columnKey, newName, oldName);
|
storeRef.current.renameColumn(columnKey, newName, oldName);
|
||||||
}, [storeRef]);
|
}, [storeRef]);
|
||||||
@ -239,6 +325,7 @@ export const MetadataViewProvider = ({
|
|||||||
unsubscribeModifyColumnOrder();
|
unsubscribeModifyColumnOrder();
|
||||||
unsubscribeModifySettings();
|
unsubscribeModifySettings();
|
||||||
unsubscribeLocalRecordChanged();
|
unsubscribeLocalRecordChanged();
|
||||||
|
delayReloadDataTimer.current && clearTimeout(delayReloadDataTimer.current);
|
||||||
};
|
};
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [repoID, viewID]);
|
}, [repoID, viewID]);
|
||||||
@ -261,6 +348,8 @@ export const MetadataViewProvider = ({
|
|||||||
modifyRecords,
|
modifyRecords,
|
||||||
deleteRecords,
|
deleteRecords,
|
||||||
modifyRecord,
|
modifyRecord,
|
||||||
|
moveRecord,
|
||||||
|
duplicateRecord,
|
||||||
renameColumn,
|
renameColumn,
|
||||||
deleteColumn,
|
deleteColumn,
|
||||||
modifyColumnOrder,
|
modifyColumnOrder,
|
||||||
@ -268,6 +357,7 @@ export const MetadataViewProvider = ({
|
|||||||
modifyColumnWidth,
|
modifyColumnWidth,
|
||||||
insertColumn,
|
insertColumn,
|
||||||
updateFileTags,
|
updateFileTags,
|
||||||
|
addFolder: params.addFolder,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
@ -39,7 +39,6 @@ class DataProcessor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static updateSummaries(table, rows) {
|
static updateSummaries(table, rows) {
|
||||||
// const tableRows = isTableRows(rows) ? rows : getRowsByIds(table, rows);
|
|
||||||
// todo
|
// todo
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -109,7 +108,7 @@ class DataProcessor {
|
|||||||
// todo update sort and filter and ui change
|
// todo update sort and filter and ui change
|
||||||
}
|
}
|
||||||
|
|
||||||
static updatePageDataWithDeleteRecords(deletedRowsIds, table) {
|
static updateDataWithDeleteRecords(deletedRowsIds, table) {
|
||||||
const { available_columns, groupbys, groups, rows } = table.view;
|
const { available_columns, groupbys, groups, rows } = table.view;
|
||||||
const idNeedDeletedMap = deletedRowsIds.reduce((currIdNeedDeletedMap, rowId) => ({ ...currIdNeedDeletedMap, [rowId]: true }), {});
|
const idNeedDeletedMap = deletedRowsIds.reduce((currIdNeedDeletedMap, rowId) => ({ ...currIdNeedDeletedMap, [rowId]: true }), {});
|
||||||
table.view.rows = rows.filter(rowId => !idNeedDeletedMap[rowId]);
|
table.view.rows = rows.filter(rowId => !idNeedDeletedMap[rowId]);
|
||||||
@ -228,7 +227,7 @@ class DataProcessor {
|
|||||||
}
|
}
|
||||||
case OPERATION_TYPE.DELETE_RECORDS: {
|
case OPERATION_TYPE.DELETE_RECORDS: {
|
||||||
const { rows_ids } = operation;
|
const { rows_ids } = operation;
|
||||||
this.updatePageDataWithDeleteRecords(rows_ids, table);
|
this.updateDataWithDeleteRecords(rows_ids, table);
|
||||||
this.updateSummaries();
|
this.updateSummaries();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -255,6 +254,10 @@ class DataProcessor {
|
|||||||
this.updateSummaries();
|
this.updateSummaries();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case OPERATION_TYPE.MOVE_RECORD: {
|
||||||
|
this.run(table, { collaborators });
|
||||||
|
break;
|
||||||
|
}
|
||||||
case OPERATION_TYPE.MODIFY_GROUPBYS: {
|
case OPERATION_TYPE.MODIFY_GROUPBYS: {
|
||||||
const { available_columns, groupbys, rows } = table.view;
|
const { available_columns, groupbys, rows } = table.view;
|
||||||
if (!isGroupView({ groupbys }, available_columns)) {
|
if (!isGroupView({ groupbys }, available_columns)) {
|
||||||
|
@ -12,7 +12,7 @@ import LocalOperator from './local-operator';
|
|||||||
import Metadata from '../model/metadata';
|
import Metadata from '../model/metadata';
|
||||||
import { checkIsDir } from '../utils/row';
|
import { checkIsDir } from '../utils/row';
|
||||||
import { Utils } from '../../utils/utils';
|
import { Utils } from '../../utils/utils';
|
||||||
import { getFileNameFromRecord } from '../utils/cell';
|
import { getFileNameFromRecord, checkDuplicatedName } from '../utils/cell';
|
||||||
|
|
||||||
class Store {
|
class Store {
|
||||||
|
|
||||||
@ -300,16 +300,13 @@ class Store {
|
|||||||
}
|
}
|
||||||
|
|
||||||
deleteRecords(rows_ids, { fail_callback, success_callback }) {
|
deleteRecords(rows_ids, { fail_callback, success_callback }) {
|
||||||
|
if (!Array.isArray(rows_ids) || rows_ids.length === 0) return;
|
||||||
const type = OPERATION_TYPE.DELETE_RECORDS;
|
const type = OPERATION_TYPE.DELETE_RECORDS;
|
||||||
|
|
||||||
if (!Array.isArray(rows_ids) || rows_ids.length === 0) {
|
const valid_rows_ids = rows_ids.filter((rowId) => {
|
||||||
return;
|
|
||||||
}
|
|
||||||
const valid_rows_ids = Array.isArray(rows_ids) ? rows_ids.filter((rowId) => {
|
|
||||||
const row = getRowById(this.data, rowId);
|
const row = getRowById(this.data, rowId);
|
||||||
return row && this.context.canModifyRow(row);
|
return row && this.context.canModifyRow(row);
|
||||||
}) : [];
|
});
|
||||||
|
|
||||||
|
|
||||||
// delete rows where parent dir is deleted
|
// delete rows where parent dir is deleted
|
||||||
const deletedDirsPaths = rows_ids.map((rowId) => {
|
const deletedDirsPaths = rows_ids.map((rowId) => {
|
||||||
@ -408,6 +405,39 @@ class Store {
|
|||||||
this.applyOperation(operation);
|
this.applyOperation(operation);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
moveRecord(row_id, target_repo_id, dirent, target_parent_path, source_parent_path, update_data, { success_callback, fail_callback }) {
|
||||||
|
const type = OPERATION_TYPE.MOVE_RECORD;
|
||||||
|
const operation = this.createOperation({
|
||||||
|
type,
|
||||||
|
repo_id: this.repoId,
|
||||||
|
row_id,
|
||||||
|
target_repo_id,
|
||||||
|
dirent,
|
||||||
|
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 }) {
|
||||||
|
const type = OPERATION_TYPE.DUPLICATE_RECORD;
|
||||||
|
const operation = this.createOperation({
|
||||||
|
type,
|
||||||
|
repo_id: this.repoId,
|
||||||
|
row_id,
|
||||||
|
target_repo_id,
|
||||||
|
dirent,
|
||||||
|
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({
|
||||||
@ -532,8 +562,7 @@ class Store {
|
|||||||
};
|
};
|
||||||
|
|
||||||
checkDuplicatedName = (name, parentDir) => {
|
checkDuplicatedName = (name, parentDir) => {
|
||||||
const newPath = Utils.joinPath(parentDir, name);
|
return checkDuplicatedName(this.data.rows, parentDir, name);
|
||||||
return this.data.rows.some((row) => newPath === Utils.joinPath(row._parent_dir, row._name));
|
|
||||||
};
|
};
|
||||||
|
|
||||||
renamePeopleName = (peopleId, newName, oldName) => {
|
renamePeopleName = (peopleId, newName, oldName) => {
|
||||||
|
@ -12,41 +12,50 @@ dayjs.extend(utc);
|
|||||||
export default function apply(data, operation) {
|
export default function apply(data, operation) {
|
||||||
const { op_type } = operation;
|
const { op_type } = operation;
|
||||||
|
|
||||||
|
const updateDataByModifyRecords = ({ id_original_row_updates = {}, id_row_updates = {} } = {}) => {
|
||||||
|
const { rows } = data;
|
||||||
|
const modifyTime = dayjs().utc().format(UTC_FORMAT_DEFAULT);
|
||||||
|
const modifier = window.sfMetadataContext.getUsername();
|
||||||
|
let updatedRows = [...rows];
|
||||||
|
|
||||||
|
rows.forEach((row, index) => {
|
||||||
|
const { _id: rowId } = row;
|
||||||
|
const originalRowUpdates = id_original_row_updates[rowId];
|
||||||
|
const rowUpdates = id_row_updates[rowId];
|
||||||
|
if (rowUpdates || originalRowUpdates) {
|
||||||
|
const updatedRow = Object.assign({}, row, rowUpdates, originalRowUpdates, {
|
||||||
|
'_mtime': modifyTime,
|
||||||
|
'_last_modifier': modifier,
|
||||||
|
});
|
||||||
|
updatedRows[index] = updatedRow;
|
||||||
|
data.id_row_map[rowId] = updatedRow;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
data.rows = updatedRows;
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateDataByDeleteRecords = (deletedIds) => {
|
||||||
|
const idNeedDeletedMap = deletedIds.reduce((currIdNeedDeletedMap, rowId) => ({ ...currIdNeedDeletedMap, [rowId]: true }), {});
|
||||||
|
data.rows = data.rows.filter((row) => !idNeedDeletedMap[row._id]);
|
||||||
|
|
||||||
|
// delete rows in id_row_map
|
||||||
|
deletedIds.forEach(rowId => {
|
||||||
|
delete data.id_row_map[rowId];
|
||||||
|
});
|
||||||
|
|
||||||
|
data.row_ids = data.row_ids.filter(row_id => !idNeedDeletedMap[row_id]);
|
||||||
|
};
|
||||||
|
|
||||||
switch (op_type) {
|
switch (op_type) {
|
||||||
case OPERATION_TYPE.MODIFY_RECORDS: {
|
case OPERATION_TYPE.MODIFY_RECORDS: {
|
||||||
const { id_original_row_updates, id_row_updates } = operation;
|
const { id_original_row_updates, id_row_updates } = operation;
|
||||||
const { rows } = data;
|
updateDataByModifyRecords({ id_original_row_updates, id_row_updates });
|
||||||
const modifyTime = dayjs().utc().format(UTC_FORMAT_DEFAULT);
|
|
||||||
const modifier = window.sfMetadataContext.getUsername();
|
|
||||||
let updatedRows = [...rows];
|
|
||||||
|
|
||||||
rows.forEach((row, index) => {
|
|
||||||
const { _id: rowId } = row;
|
|
||||||
const originalRowUpdates = id_original_row_updates[rowId];
|
|
||||||
const rowUpdates = id_row_updates[rowId];
|
|
||||||
if (rowUpdates || originalRowUpdates) {
|
|
||||||
const updatedRow = Object.assign({}, row, rowUpdates, originalRowUpdates, {
|
|
||||||
'_mtime': modifyTime,
|
|
||||||
'_last_modifier': modifier,
|
|
||||||
});
|
|
||||||
updatedRows[index] = updatedRow;
|
|
||||||
data.id_row_map[rowId] = updatedRow;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
data.rows = updatedRows;
|
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
case OPERATION_TYPE.DELETE_RECORDS: {
|
case OPERATION_TYPE.DELETE_RECORDS: {
|
||||||
const { rows_ids } = operation;
|
const { rows_ids } = operation;
|
||||||
const idNeedDeletedMap = rows_ids.reduce((currIdNeedDeletedMap, rowId) => ({ ...currIdNeedDeletedMap, [rowId]: true }), {});
|
updateDataByDeleteRecords(rows_ids);
|
||||||
data.rows = data.rows.filter((row) => !idNeedDeletedMap[row._id]);
|
|
||||||
|
|
||||||
// delete rows in id_row_map
|
|
||||||
rows_ids.forEach(rowId => {
|
|
||||||
delete data.id_row_map[rowId];
|
|
||||||
});
|
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
case OPERATION_TYPE.RESTORE_RECORDS: {
|
case OPERATION_TYPE.RESTORE_RECORDS: {
|
||||||
@ -117,6 +126,23 @@ export default function apply(data, operation) {
|
|||||||
data.rows = updatedRows;
|
data.rows = updatedRows;
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
case OPERATION_TYPE.MOVE_RECORD: {
|
||||||
|
const { update_data } = operation;
|
||||||
|
const {
|
||||||
|
modify_row_ids: updateRowIds,
|
||||||
|
modify_id_row_updates: idRowUpdates,
|
||||||
|
delete_row_ids: deletedRowIds,
|
||||||
|
} = update_data;
|
||||||
|
|
||||||
|
if (Array.isArray(updateRowIds) && updateRowIds.length > 0) {
|
||||||
|
updateDataByModifyRecords({ id_row_updates: idRowUpdates });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(deletedRowIds) && deletedRowIds.length > 0) {
|
||||||
|
updateDataByDeleteRecords(deletedRowIds);
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
@ -1,16 +1,10 @@
|
|||||||
export const OPERATION_TYPE = {
|
export const OPERATION_TYPE = {
|
||||||
MODIFY_RECORDS: 'modify_records',
|
// view
|
||||||
DELETE_RECORDS: 'delete_records',
|
|
||||||
RESTORE_RECORDS: 'restore_records',
|
|
||||||
RELOAD_RECORDS: 'reload_records',
|
|
||||||
MODIFY_FILTERS: 'modify_filters',
|
MODIFY_FILTERS: 'modify_filters',
|
||||||
MODIFY_SORTS: 'modify_sorts',
|
MODIFY_SORTS: 'modify_sorts',
|
||||||
MODIFY_GROUPBYS: 'modify_groupbys',
|
MODIFY_GROUPBYS: 'modify_groupbys',
|
||||||
MODIFY_HIDDEN_COLUMNS: 'modify_hidden_columns',
|
MODIFY_HIDDEN_COLUMNS: 'modify_hidden_columns',
|
||||||
LOCK_RECORD_VIA_BUTTON: 'lock_record_via_button',
|
|
||||||
MODIFY_RECORD_VIA_BUTTON: 'modify_record_via_button',
|
|
||||||
MODIFY_SETTINGS: 'modify_settings',
|
MODIFY_SETTINGS: 'modify_settings',
|
||||||
MODIFY_LOCAL_RECORD: 'modify_local_record',
|
|
||||||
|
|
||||||
// column
|
// column
|
||||||
INSERT_COLUMN: 'insert_column',
|
INSERT_COLUMN: 'insert_column',
|
||||||
@ -20,6 +14,17 @@ export const OPERATION_TYPE = {
|
|||||||
MODIFY_COLUMN_WIDTH: 'modify_column_width',
|
MODIFY_COLUMN_WIDTH: 'modify_column_width',
|
||||||
MODIFY_COLUMN_ORDER: 'modify_column_order',
|
MODIFY_COLUMN_ORDER: 'modify_column_order',
|
||||||
|
|
||||||
|
// record
|
||||||
|
MODIFY_RECORDS: 'modify_records',
|
||||||
|
DELETE_RECORDS: 'delete_records',
|
||||||
|
RESTORE_RECORDS: 'restore_records',
|
||||||
|
RELOAD_RECORDS: 'reload_records',
|
||||||
|
LOCK_RECORD_VIA_BUTTON: 'lock_record_via_button',
|
||||||
|
MODIFY_RECORD_VIA_BUTTON: 'modify_record_via_button',
|
||||||
|
MODIFY_LOCAL_RECORD: 'modify_local_record',
|
||||||
|
MOVE_RECORD: 'move_record',
|
||||||
|
DUPLICATE_RECORD: 'duplicate_record',
|
||||||
|
|
||||||
// face table
|
// face table
|
||||||
RENAME_PEOPLE_NAME: 'rename_people_name',
|
RENAME_PEOPLE_NAME: 'rename_people_name',
|
||||||
DELETE_PEOPLE_PHOTOS: 'delete_people_photos',
|
DELETE_PEOPLE_PHOTOS: 'delete_people_photos',
|
||||||
@ -41,6 +46,8 @@ export const OPERATION_ATTRIBUTES = {
|
|||||||
[OPERATION_TYPE.MODIFY_RECORDS]: ['repo_id', 'row_ids', 'id_row_updates', 'id_original_row_updates', 'id_old_row_data', 'id_original_old_row_data', 'is_copy_paste', 'is_rename', 'id_obj_id'],
|
[OPERATION_TYPE.MODIFY_RECORDS]: ['repo_id', 'row_ids', 'id_row_updates', 'id_original_row_updates', 'id_old_row_data', 'id_original_old_row_data', 'is_copy_paste', 'is_rename', 'id_obj_id'],
|
||||||
[OPERATION_TYPE.DELETE_RECORDS]: ['repo_id', 'rows_ids', 'deleted_rows'],
|
[OPERATION_TYPE.DELETE_RECORDS]: ['repo_id', 'rows_ids', 'deleted_rows'],
|
||||||
[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.DUPLICATE_RECORD]: ['repo_id', 'row_id', 'target_repo_id', 'dirent', 'target_parent_path', 'source_parent_path'],
|
||||||
|
|
||||||
[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'],
|
||||||
[OPERATION_TYPE.MODIFY_SORTS]: ['repo_id', 'view_id', 'sorts'],
|
[OPERATION_TYPE.MODIFY_SORTS]: ['repo_id', 'view_id', 'sorts'],
|
||||||
@ -83,6 +90,8 @@ export const NEED_APPLY_AFTER_SERVER_OPERATION = [
|
|||||||
OPERATION_TYPE.INSERT_COLUMN,
|
OPERATION_TYPE.INSERT_COLUMN,
|
||||||
OPERATION_TYPE.MODIFY_FILTERS,
|
OPERATION_TYPE.MODIFY_FILTERS,
|
||||||
OPERATION_TYPE.MODIFY_SORTS,
|
OPERATION_TYPE.MODIFY_SORTS,
|
||||||
|
OPERATION_TYPE.MOVE_RECORD,
|
||||||
|
OPERATION_TYPE.DUPLICATE_RECORD,
|
||||||
];
|
];
|
||||||
|
|
||||||
export const VIEW_OPERATION = [
|
export const VIEW_OPERATION = [
|
||||||
|
@ -82,6 +82,30 @@ class ServerOperator {
|
|||||||
callback({ operation });
|
callback({ operation });
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case OPERATION_TYPE.MOVE_RECORD: {
|
||||||
|
const { row_id, repo_id, target_repo_id, dirent, target_parent_path, source_parent_path } = operation;
|
||||||
|
seafileAPI.moveDir(repo_id, target_repo_id, target_parent_path, source_parent_path, dirent.name).then(res => {
|
||||||
|
operation.task_id = res.data.task_id || null;
|
||||||
|
callback({ operation });
|
||||||
|
}).catch(error => {
|
||||||
|
const row = getRowById(data, row_id);
|
||||||
|
const isDir = checkIsDir(row);
|
||||||
|
callback({ error: isDir ? gettext('Failed to move folder') : gettext('Failed to move file') });
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case OPERATION_TYPE.DUPLICATE_RECORD: {
|
||||||
|
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 => {
|
||||||
|
operation.task_id = res.data.task_id || null;
|
||||||
|
callback({ operation });
|
||||||
|
}).catch(error => {
|
||||||
|
const row = getRowById(data, row_id);
|
||||||
|
const isDir = checkIsDir(row);
|
||||||
|
callback({ error: isDir ? gettext('Failed to duplicate folder') : gettext('Failed to duplicate file') });
|
||||||
|
});
|
||||||
|
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 => {
|
||||||
|
17
frontend/src/metadata/utils/cell/column/file-name.js
Normal file
17
frontend/src/metadata/utils/cell/column/file-name.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { Utils } from '../../../../utils/utils';
|
||||||
|
|
||||||
|
export const checkDuplicatedName = (rows, parentDir, fileName) => {
|
||||||
|
if (!Array.isArray(rows) || rows.length === 0 || !parentDir || !fileName) return false;
|
||||||
|
const newPath = Utils.joinPath(parentDir, fileName);
|
||||||
|
return rows.some((row) => newPath === Utils.joinPath(row._parent_dir, row._name));
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getUniqueFileName = (rows, parentDir, fileName) => {
|
||||||
|
const dotIndex = fileName.lastIndexOf('.');
|
||||||
|
const fileType = dotIndex === -1 ? '' : fileName.slice(dotIndex + 1);
|
||||||
|
let newName = dotIndex === -1 ? fileName : fileName.slice(0, dotIndex);
|
||||||
|
while (checkDuplicatedName(rows, parentDir, dotIndex === -1 ? newName : `${newName}.${fileType}`)) {
|
||||||
|
newName = newName + ' (1)';
|
||||||
|
}
|
||||||
|
return dotIndex === -1 ? newName : `${newName}.${fileType}`;
|
||||||
|
};
|
@ -31,3 +31,7 @@ export {
|
|||||||
export {
|
export {
|
||||||
getTagsDisplayString,
|
getTagsDisplayString,
|
||||||
} from './tag';
|
} from './tag';
|
||||||
|
export {
|
||||||
|
checkDuplicatedName,
|
||||||
|
getUniqueFileName,
|
||||||
|
} from './file-name';
|
||||||
|
@ -9,30 +9,47 @@ import metadataAPI from '../../../api';
|
|||||||
import toaster from '../../../../components/toast';
|
import toaster from '../../../../components/toast';
|
||||||
import { Utils } from '../../../../utils/utils';
|
import { Utils } from '../../../../utils/utils';
|
||||||
import ModalPortal from '../../../../components/modal-portal';
|
import ModalPortal from '../../../../components/modal-portal';
|
||||||
|
import CopyDirent from '../../../../components/dialog/copy-dirent-dialog';
|
||||||
|
import { Dirent } from '../../../../models';
|
||||||
|
|
||||||
const CONTEXT_MENU_KEY = {
|
const CONTEXT_MENU_KEY = {
|
||||||
DOWNLOAD: 'download',
|
DOWNLOAD: 'download',
|
||||||
DELETE: 'delete',
|
DELETE: 'delete',
|
||||||
|
DUPLICATE: 'duplicate',
|
||||||
};
|
};
|
||||||
|
|
||||||
const GalleryContextMenu = ({ metadata, selectedImages, boundaryCoordinates, onDelete }) => {
|
const GalleryContextMenu = ({ metadata, selectedImages, boundaryCoordinates, onDelete, onDuplicate, addFolder }) => {
|
||||||
const [isZipDialogOpen, setIsZipDialogOpen] = useState(false);
|
const [isZipDialogOpen, setIsZipDialogOpen] = useState(false);
|
||||||
|
const [isCopyDialogOpen, setIsCopyDialogOpen] = useState(false);
|
||||||
|
|
||||||
const repoID = window.sfMetadataContext.getSetting('repoID');
|
const repoID = window.sfMetadataContext.getSetting('repoID');
|
||||||
const checkCanDeleteRow = window.sfMetadataContext.checkCanDeleteRow();
|
const checkCanDeleteRow = window.sfMetadataContext.checkCanDeleteRow();
|
||||||
|
const canDuplicateRow = window.sfMetadataContext.canDuplicateRow();
|
||||||
|
|
||||||
const options = useMemo(() => {
|
const options = useMemo(() => {
|
||||||
let validOptions = [{ value: CONTEXT_MENU_KEY.DOWNLOAD, label: gettext('Download') }];
|
let validOptions = [{ value: CONTEXT_MENU_KEY.DOWNLOAD, label: gettext('Download') }];
|
||||||
if (checkCanDeleteRow) {
|
if (checkCanDeleteRow) {
|
||||||
validOptions.push({ value: CONTEXT_MENU_KEY.DELETE, label: selectedImages.length > 1 ? gettext('Delete') : gettext('Delete file') });
|
validOptions.push({ value: CONTEXT_MENU_KEY.DELETE, label: selectedImages.length > 1 ? gettext('Delete') : gettext('Delete file') });
|
||||||
}
|
}
|
||||||
|
if (canDuplicateRow && selectedImages.length === 1) {
|
||||||
|
validOptions.push({ value: CONTEXT_MENU_KEY.DUPLICATE, label: gettext('Duplicate') });
|
||||||
|
}
|
||||||
return validOptions;
|
return validOptions;
|
||||||
}, [checkCanDeleteRow, selectedImages]);
|
}, [checkCanDeleteRow, canDuplicateRow, selectedImages]);
|
||||||
|
|
||||||
const closeZipDialog = () => {
|
const closeZipDialog = () => {
|
||||||
setIsZipDialogOpen(false);
|
setIsZipDialogOpen(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const toggleCopyDialog = useCallback(() => {
|
||||||
|
setIsCopyDialogOpen(!isCopyDialogOpen);
|
||||||
|
}, [isCopyDialogOpen]);
|
||||||
|
|
||||||
|
const handleDuplicate = useCallback((destRepo, dirent, destPath, nodeParentPath, isByDialog) => {
|
||||||
|
const selectedImage = selectedImages[0];
|
||||||
|
onDuplicate(selectedImage.id, destRepo, dirent, destPath, nodeParentPath, isByDialog);
|
||||||
|
}, [selectedImages, onDuplicate]);
|
||||||
|
|
||||||
const handleDownload = useCallback(() => {
|
const handleDownload = useCallback(() => {
|
||||||
if (!selectedImages.length) return;
|
if (!selectedImages.length) return;
|
||||||
if (selectedImages.length === 1) {
|
if (selectedImages.length === 1) {
|
||||||
@ -66,10 +83,16 @@ const GalleryContextMenu = ({ metadata, selectedImages, boundaryCoordinates, onD
|
|||||||
case 'delete':
|
case 'delete':
|
||||||
onDelete(selectedImages);
|
onDelete(selectedImages);
|
||||||
break;
|
break;
|
||||||
|
case CONTEXT_MENU_KEY.DUPLICATE:
|
||||||
|
toggleCopyDialog();
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}, [selectedImages, handleDownload, onDelete]);
|
}, [selectedImages, handleDownload, onDelete, toggleCopyDialog]);
|
||||||
|
|
||||||
|
const dirent = new Dirent({ name: selectedImages[0]?.name });
|
||||||
|
const path = selectedImages[0]?.path;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -89,6 +112,20 @@ const GalleryContextMenu = ({ metadata, selectedImages, boundaryCoordinates, onD
|
|||||||
/>
|
/>
|
||||||
</ModalPortal>
|
</ModalPortal>
|
||||||
)}
|
)}
|
||||||
|
{isCopyDialogOpen && (
|
||||||
|
<ModalPortal>
|
||||||
|
<CopyDirent
|
||||||
|
path={path}
|
||||||
|
repoID={repoID}
|
||||||
|
dirent={dirent}
|
||||||
|
isMultipleOperation={false}
|
||||||
|
repoEncrypted={false}
|
||||||
|
onItemCopy={handleDuplicate}
|
||||||
|
onCancelCopy={toggleCopyDialog}
|
||||||
|
onAddFolder={addFolder}
|
||||||
|
/>
|
||||||
|
</ModalPortal>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -98,6 +135,8 @@ GalleryContextMenu.propTypes = {
|
|||||||
selectedImages: PropTypes.array,
|
selectedImages: PropTypes.array,
|
||||||
boundaryCoordinates: PropTypes.object,
|
boundaryCoordinates: PropTypes.object,
|
||||||
onDelete: PropTypes.func,
|
onDelete: PropTypes.func,
|
||||||
|
onDuplicate: PropTypes.func,
|
||||||
|
addFolder: PropTypes.func,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default GalleryContextMenu;
|
export default GalleryContextMenu;
|
||||||
|
@ -10,7 +10,7 @@ import './index.css';
|
|||||||
const Gallery = () => {
|
const Gallery = () => {
|
||||||
const [isLoadingMore, setLoadingMore] = useState(false);
|
const [isLoadingMore, setLoadingMore] = useState(false);
|
||||||
|
|
||||||
const { metadata, store, deleteRecords } = useMetadataView();
|
const { metadata, store, deleteRecords, duplicateRecord, addFolder } = useMetadataView();
|
||||||
|
|
||||||
const onLoadMore = useCallback(async () => {
|
const onLoadMore = useCallback(async () => {
|
||||||
if (isLoadingMore) return;
|
if (isLoadingMore) return;
|
||||||
@ -43,7 +43,14 @@ const Gallery = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="sf-metadata-container">
|
<div className="sf-metadata-container">
|
||||||
<Main isLoadingMore={isLoadingMore} metadata={metadata} onDelete={handleDelete} onLoadMore={onLoadMore} />
|
<Main
|
||||||
|
isLoadingMore={isLoadingMore}
|
||||||
|
metadata={metadata}
|
||||||
|
onDelete={handleDelete}
|
||||||
|
onLoadMore={onLoadMore}
|
||||||
|
duplicateRecord={duplicateRecord}
|
||||||
|
onAddFolder={addFolder}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -6,9 +6,9 @@ import ImageDialog from '../../../components/dialog/image-dialog';
|
|||||||
import ModalPortal from '../../../components/modal-portal';
|
import ModalPortal from '../../../components/modal-portal';
|
||||||
import { useMetadataView } from '../../hooks/metadata-view';
|
import { useMetadataView } from '../../hooks/metadata-view';
|
||||||
import { Utils } from '../../../utils/utils';
|
import { Utils } from '../../../utils/utils';
|
||||||
import { getDateDisplayString, getFileNameFromRecord, getParentDirFromRecord } from '../../utils/cell';
|
import { getDateDisplayString, getFileNameFromRecord, getParentDirFromRecord, getRecordIdFromRecord } from '../../utils/cell';
|
||||||
import { siteRoot, fileServerRoot, thumbnailSizeForGrid, thumbnailSizeForOriginal } from '../../../utils/constants';
|
import { siteRoot, fileServerRoot, thumbnailSizeForGrid, thumbnailSizeForOriginal } from '../../../utils/constants';
|
||||||
import { EVENT_BUS_TYPE, PRIVATE_COLUMN_KEY, GALLERY_DATE_MODE, DATE_TAG_HEIGHT, GALLERY_IMAGE_GAP } from '../../constants';
|
import { EVENT_BUS_TYPE, GALLERY_DATE_MODE, DATE_TAG_HEIGHT, GALLERY_IMAGE_GAP } from '../../constants';
|
||||||
import { getRowById } from '../../utils/table';
|
import { getRowById } from '../../utils/table';
|
||||||
import { getEventClassName } from '../../utils/common';
|
import { getEventClassName } from '../../utils/common';
|
||||||
import GalleryContextmenu from './context-menu';
|
import GalleryContextmenu from './context-menu';
|
||||||
@ -17,7 +17,7 @@ import './index.css';
|
|||||||
|
|
||||||
const OVER_SCAN_ROWS = 20;
|
const OVER_SCAN_ROWS = 20;
|
||||||
|
|
||||||
const Main = ({ isLoadingMore, metadata, onDelete, onLoadMore }) => {
|
const Main = ({ isLoadingMore, metadata, onDelete, onLoadMore, duplicateRecord, onAddFolder }) => {
|
||||||
const [isFirstLoading, setFirstLoading] = useState(true);
|
const [isFirstLoading, setFirstLoading] = useState(true);
|
||||||
const [zoomGear, setZoomGear] = useState(0);
|
const [zoomGear, setZoomGear] = useState(0);
|
||||||
const [containerWidth, setContainerWidth] = useState(0);
|
const [containerWidth, setContainerWidth] = useState(0);
|
||||||
@ -65,7 +65,7 @@ const Main = ({ isLoadingMore, metadata, onDelete, onLoadMore }) => {
|
|||||||
const firstSort = metadata.view.sorts[0];
|
const firstSort = metadata.view.sorts[0];
|
||||||
let init = metadata.rows.filter(row => Utils.imageCheck(getFileNameFromRecord(row)))
|
let init = metadata.rows.filter(row => Utils.imageCheck(getFileNameFromRecord(row)))
|
||||||
.reduce((_init, record) => {
|
.reduce((_init, record) => {
|
||||||
const id = record[PRIVATE_COLUMN_KEY.ID];
|
const id = getRecordIdFromRecord(record);
|
||||||
const fileName = getFileNameFromRecord(record);
|
const fileName = getFileNameFromRecord(record);
|
||||||
const parentDir = getParentDirFromRecord(record);
|
const parentDir = getParentDirFromRecord(record);
|
||||||
const path = Utils.encodePath(Utils.joinPath(parentDir, fileName));
|
const path = Utils.encodePath(Utils.joinPath(parentDir, fileName));
|
||||||
@ -368,6 +368,8 @@ const Main = ({ isLoadingMore, metadata, onDelete, onLoadMore }) => {
|
|||||||
selectedImages={selectedImages}
|
selectedImages={selectedImages}
|
||||||
boundaryCoordinates={containerRef?.current?.getBoundingClientRect() || {}}
|
boundaryCoordinates={containerRef?.current?.getBoundingClientRect() || {}}
|
||||||
onDelete={handleDeleteSelectedImages}
|
onDelete={handleDeleteSelectedImages}
|
||||||
|
onDuplicate={duplicateRecord}
|
||||||
|
addFolder={onAddFolder}
|
||||||
/>
|
/>
|
||||||
{isImagePopupOpen && (
|
{isImagePopupOpen && (
|
||||||
<ModalPortal>
|
<ModalPortal>
|
||||||
@ -389,7 +391,9 @@ Main.propTypes = {
|
|||||||
isLoadingMore: PropTypes.bool,
|
isLoadingMore: PropTypes.bool,
|
||||||
metadata: PropTypes.object,
|
metadata: PropTypes.object,
|
||||||
onDelete: PropTypes.func,
|
onDelete: PropTypes.func,
|
||||||
onLoadMore: PropTypes.func
|
onLoadMore: PropTypes.func,
|
||||||
|
duplicateRecord: PropTypes.func,
|
||||||
|
onAddFolder: PropTypes.func,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Main;
|
export default Main;
|
||||||
|
@ -13,6 +13,9 @@ import { getFileNameFromRecord, getParentDirFromRecord, getFileObjIdFromRecord,
|
|||||||
} from '../../../utils/cell';
|
} from '../../../utils/cell';
|
||||||
import FileTagsDialog from '../../../components/dialog/file-tags-dialog';
|
import FileTagsDialog from '../../../components/dialog/file-tags-dialog';
|
||||||
import { openInNewTab, openParentFolder } from '../../../utils/file';
|
import { openInNewTab, openParentFolder } from '../../../utils/file';
|
||||||
|
import DeleteFolderDialog from '../../../../components/dialog/delete-folder-dialog';
|
||||||
|
import MoveDirent from '../../../../components/dialog/move-dirent-dialog';
|
||||||
|
import { Dirent } from '../../../../models';
|
||||||
|
|
||||||
const OPERATION = {
|
const OPERATION = {
|
||||||
CLEAR_SELECTED: 'clear-selected',
|
CLEAR_SELECTED: 'clear-selected',
|
||||||
@ -28,21 +31,27 @@ const OPERATION = {
|
|||||||
RENAME_FILE: 'rename-file',
|
RENAME_FILE: 'rename-file',
|
||||||
FILE_DETAIL: 'file-detail',
|
FILE_DETAIL: 'file-detail',
|
||||||
FILE_DETAILS: 'file-details',
|
FILE_DETAILS: 'file-details',
|
||||||
|
MOVE: 'move',
|
||||||
};
|
};
|
||||||
|
|
||||||
const ContextMenu = (props) => {
|
const ContextMenu = ({
|
||||||
const {
|
isGroupView, selectedRange, selectedPosition, recordMetrics, recordGetterByIndex, onClearSelected, onCopySelected, updateRecords,
|
||||||
isGroupView, selectedRange, selectedPosition, recordMetrics, recordGetterByIndex, onClearSelected, onCopySelected, updateRecords,
|
getTableContentRect, getTableCanvasContainerRect, deleteRecords, selectNone, updateFileTags, moveRecord, addFolder
|
||||||
getTableContentRect, getTableCanvasContainerRect, deleteRecords, toggleDeleteFolderDialog, selectNone, updateFileTags,
|
}) => {
|
||||||
} = props;
|
|
||||||
const menuRef = useRef(null);
|
const menuRef = useRef(null);
|
||||||
|
const currentRecord = useRef(null);
|
||||||
|
|
||||||
const [visible, setVisible] = useState(false);
|
const [visible, setVisible] = useState(false);
|
||||||
const [position, setPosition] = useState({ top: 0, left: 0 });
|
const [position, setPosition] = useState({ top: 0, left: 0 });
|
||||||
const [fileTagsRecord, setFileTagsRecord] = useState(null);
|
const [fileTagsRecord, setFileTagsRecord] = useState(null);
|
||||||
|
const [deletedFolderPath, setDeletedFolderPath] = useState('');
|
||||||
|
const [isMoveDialogShow, setMoveDialogShow] = useState(false);
|
||||||
|
|
||||||
const { metadata } = useMetadataView();
|
const { metadata } = useMetadataView();
|
||||||
const { enableOCR } = useMetadataStatus();
|
const { enableOCR } = useMetadataStatus();
|
||||||
|
|
||||||
|
const repoID = window.sfMetadataStore.repoId;
|
||||||
|
|
||||||
const checkCanModifyRow = (row) => {
|
const checkCanModifyRow = (row) => {
|
||||||
return window.sfMetadataContext.canModifyRow(row);
|
return window.sfMetadataContext.canModifyRow(row);
|
||||||
};
|
};
|
||||||
@ -56,6 +65,29 @@ const ContextMenu = (props) => {
|
|||||||
return records.filter(record => window.sfMetadataContext.checkCanDeleteRow(record));
|
return records.filter(record => window.sfMetadataContext.checkCanDeleteRow(record));
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const toggleDeleteFolderDialog = useCallback((record) => {
|
||||||
|
if (deletedFolderPath) {
|
||||||
|
currentRecord.current = null;
|
||||||
|
setDeletedFolderPath('');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const parentDir = getParentDirFromRecord(record);
|
||||||
|
const fileName = getFileNameFromRecord(record);
|
||||||
|
currentRecord.current = record;
|
||||||
|
setDeletedFolderPath(Utils.joinPath(parentDir, fileName));
|
||||||
|
}, [deletedFolderPath]);
|
||||||
|
|
||||||
|
const toggleMoveDialog = useCallback((record) => {
|
||||||
|
currentRecord.current = record || null;
|
||||||
|
setMoveDialogShow(!isMoveDialogShow);
|
||||||
|
}, [isMoveDialogShow]);
|
||||||
|
|
||||||
|
const deleteFolder = useCallback(() => {
|
||||||
|
if (!currentRecord.current) return;
|
||||||
|
const currentRecordId = getRecordIdFromRecord(currentRecord.current);
|
||||||
|
deleteRecords([currentRecordId]);
|
||||||
|
}, [deleteRecords]);
|
||||||
|
|
||||||
const options = useMemo(() => {
|
const options = useMemo(() => {
|
||||||
if (!visible) return [];
|
if (!visible) return [];
|
||||||
const permission = window.sfMetadataContext.getPermission();
|
const permission = window.sfMetadataContext.getPermission();
|
||||||
@ -162,6 +194,10 @@ const ContextMenu = (props) => {
|
|||||||
list.push({ value: OPERATION.RENAME_FILE, label: isFolder ? gettext('Rename folder') : gettext('Rename file'), record });
|
list.push({ value: OPERATION.RENAME_FILE, label: isFolder ? gettext('Rename folder') : gettext('Rename file'), record });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (canModifyRow) {
|
||||||
|
list.push({ value: OPERATION.MOVE, label: isFolder ? gettext('Move folder') : gettext('Move file'), record });
|
||||||
|
}
|
||||||
|
|
||||||
return list;
|
return list;
|
||||||
}, [visible, isGroupView, selectedPosition, recordMetrics, selectedRange, metadata, recordGetterByIndex, checkIsDescribableDoc, enableOCR, getAbleDeleteRecords]);
|
}, [visible, isGroupView, selectedPosition, recordMetrics, selectedRange, metadata, recordGetterByIndex, checkIsDescribableDoc, enableOCR, getAbleDeleteRecords]);
|
||||||
|
|
||||||
@ -300,7 +336,6 @@ const ContextMenu = (props) => {
|
|||||||
|
|
||||||
const handleOptionClick = useCallback((event, option) => {
|
const handleOptionClick = useCallback((event, option) => {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
const repoID = window.sfMetadataStore.repoId;
|
|
||||||
switch (option.value) {
|
switch (option.value) {
|
||||||
case OPERATION.OPEN_IN_NEW_TAB: {
|
case OPERATION.OPEN_IN_NEW_TAB: {
|
||||||
const { record } = option;
|
const { record } = option;
|
||||||
@ -384,12 +419,18 @@ const ContextMenu = (props) => {
|
|||||||
updateFileDetails([record]);
|
updateFileDetails([record]);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case OPERATION.MOVE: {
|
||||||
|
const { record } = option;
|
||||||
|
if (!record) break;
|
||||||
|
toggleMoveDialog(record);
|
||||||
|
break;
|
||||||
|
}
|
||||||
default: {
|
default: {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setVisible(false);
|
setVisible(false);
|
||||||
}, [onCopySelected, onClearSelected, generateDescription, imageCaption, ocr, deleteRecords, toggleDeleteFolderDialog, selectNone, updateFileDetails, toggleFileTagsRecord]);
|
}, [repoID, onCopySelected, onClearSelected, generateDescription, imageCaption, ocr, deleteRecords, toggleDeleteFolderDialog, selectNone, updateFileDetails, toggleFileTagsRecord, toggleMoveDialog]);
|
||||||
|
|
||||||
const getMenuPosition = useCallback((x = 0, y = 0) => {
|
const getMenuPosition = useCallback((x = 0, y = 0) => {
|
||||||
let menuStyles = {
|
let menuStyles = {
|
||||||
@ -473,14 +514,35 @@ const ContextMenu = (props) => {
|
|||||||
);
|
);
|
||||||
}, [visible, options, position, handleOptionClick]);
|
}, [visible, options, position, handleOptionClick]);
|
||||||
|
|
||||||
|
const currentRecordId = getRecordIdFromRecord(currentRecord.current);
|
||||||
|
const fileName = getFileNameFromRecord(currentRecord.current);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{renderMenu()}
|
{renderMenu()}
|
||||||
{fileTagsRecord && (
|
{fileTagsRecord && (
|
||||||
<FileTagsDialog record={fileTagsRecord} onToggle={toggleFileTagsRecord} onSubmit={updateFileTags} />
|
<FileTagsDialog record={fileTagsRecord} onToggle={toggleFileTagsRecord} onSubmit={updateFileTags} />
|
||||||
)}
|
)}
|
||||||
|
{deletedFolderPath && (
|
||||||
|
<DeleteFolderDialog
|
||||||
|
repoID={repoID}
|
||||||
|
path={deletedFolderPath}
|
||||||
|
deleteFolder={deleteFolder}
|
||||||
|
toggleDialog={toggleDeleteFolderDialog}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{isMoveDialogShow && (
|
||||||
|
<MoveDirent
|
||||||
|
path={getParentDirFromRecord(currentRecord.current)}
|
||||||
|
repoID={repoID}
|
||||||
|
dirent={new Dirent({ name: fileName })}
|
||||||
|
isMultipleOperation={false}
|
||||||
|
onItemMove={(...params) => moveRecord(currentRecordId, ...params)}
|
||||||
|
onCancelMove={toggleMoveDialog}
|
||||||
|
onAddFolder={addFolder}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -493,6 +555,8 @@ ContextMenu.propTypes = {
|
|||||||
getTableContentRect: PropTypes.func,
|
getTableContentRect: PropTypes.func,
|
||||||
recordGetterByIndex: PropTypes.func,
|
recordGetterByIndex: PropTypes.func,
|
||||||
deleteRecords: PropTypes.func,
|
deleteRecords: PropTypes.func,
|
||||||
|
moveRecord: PropTypes.func,
|
||||||
|
addFolder: PropTypes.func,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ContextMenu;
|
export default ContextMenu;
|
||||||
|
@ -25,7 +25,9 @@ const Table = () => {
|
|||||||
modifyColumnOrder,
|
modifyColumnOrder,
|
||||||
modifyColumnWidth,
|
modifyColumnWidth,
|
||||||
insertColumn,
|
insertColumn,
|
||||||
updateFileTags
|
updateFileTags,
|
||||||
|
moveRecord,
|
||||||
|
addFolder
|
||||||
} = useMetadataView();
|
} = useMetadataView();
|
||||||
const containerRef = useRef(null);
|
const containerRef = useRef(null);
|
||||||
|
|
||||||
@ -172,6 +174,8 @@ const Table = () => {
|
|||||||
updateFileTags={updateFileTags}
|
updateFileTags={updateFileTags}
|
||||||
onGridKeyDown={onHotKey}
|
onGridKeyDown={onHotKey}
|
||||||
onGridKeyUp={onHotKeyUp}
|
onGridKeyUp={onHotKeyUp}
|
||||||
|
moveRecord={moveRecord}
|
||||||
|
addFolder={addFolder}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -2,7 +2,6 @@ import React, { Component } from 'react';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { HorizontalScrollbar } from '../../../../components/scrollbar';
|
import { HorizontalScrollbar } from '../../../../components/scrollbar';
|
||||||
import EmptyTip from '../../../../../components/empty-tip';
|
import EmptyTip from '../../../../../components/empty-tip';
|
||||||
import DeleteFolderDialog from '../../../../../components/dialog/delete-folder-dialog';
|
|
||||||
import Body from './body';
|
import Body from './body';
|
||||||
import GroupBody from './group-body';
|
import GroupBody from './group-body';
|
||||||
import RecordsHeader from '../records-header';
|
import RecordsHeader from '../records-header';
|
||||||
@ -11,7 +10,7 @@ import ContextMenu from '../../context-menu';
|
|||||||
import { recalculate } from '../../../../utils/column';
|
import { recalculate } from '../../../../utils/column';
|
||||||
import { getEventClassName } from '../../../../utils/common';
|
import { getEventClassName } from '../../../../utils/common';
|
||||||
import { SEQUENCE_COLUMN_WIDTH, CANVAS_RIGHT_INTERVAL, GROUP_ROW_TYPE, EVENT_BUS_TYPE } from '../../../../constants';
|
import { SEQUENCE_COLUMN_WIDTH, CANVAS_RIGHT_INTERVAL, GROUP_ROW_TYPE, EVENT_BUS_TYPE } from '../../../../constants';
|
||||||
import { isMobile, Utils } from '../../../../../utils/utils';
|
import { isMobile } from '../../../../../utils/utils';
|
||||||
import { isShiftKeyDown } from '../../../../utils/keyboard-utils';
|
import { isShiftKeyDown } from '../../../../utils/keyboard-utils';
|
||||||
import { gettext } from '../../../../../utils/constants';
|
import { gettext } from '../../../../../utils/constants';
|
||||||
import RecordMetrics from '../../utils/record-metrics';
|
import RecordMetrics from '../../utils/record-metrics';
|
||||||
@ -43,7 +42,6 @@ class Records extends Component {
|
|||||||
},
|
},
|
||||||
selectedPosition: this.initPosition,
|
selectedPosition: this.initPosition,
|
||||||
...initHorizontalScrollState,
|
...initHorizontalScrollState,
|
||||||
deletedFolderPath: '',
|
|
||||||
};
|
};
|
||||||
this.isWindows = isWindowsBrowser();
|
this.isWindows = isWindowsBrowser();
|
||||||
this.isWebkit = isWebkitBrowser();
|
this.isWebkit = isWebkitBrowser();
|
||||||
@ -623,23 +621,6 @@ class Records extends Component {
|
|||||||
return this.resultContainerRef.getBoundingClientRect();
|
return this.resultContainerRef.getBoundingClientRect();
|
||||||
};
|
};
|
||||||
|
|
||||||
toggleDeleteFolderDialog = (record) => {
|
|
||||||
if (this.state.deletedFolderPath) {
|
|
||||||
this.deletedRecord = null;
|
|
||||||
this.setState({ deletedFolderPath: '' });
|
|
||||||
} else {
|
|
||||||
const { _parent_dir, _name } = record;
|
|
||||||
const deletedFolderPath = Utils.joinPath(_parent_dir, _name);
|
|
||||||
this.deletedRecord = record;
|
|
||||||
this.setState({ deletedFolderPath: deletedFolderPath });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
deleteFolder = () => {
|
|
||||||
if (!this.deletedRecord) return;
|
|
||||||
this.props.deleteRecords([this.deletedRecord._id]);
|
|
||||||
};
|
|
||||||
|
|
||||||
renderRecordsBody = ({ containerWidth }) => {
|
renderRecordsBody = ({ containerWidth }) => {
|
||||||
const { isGroupView } = this.props;
|
const { isGroupView } = this.props;
|
||||||
const { recordMetrics, columnMetrics, colOverScanStartIdx, colOverScanEndIdx } = this.state;
|
const { recordMetrics, columnMetrics, colOverScanStartIdx, colOverScanEndIdx } = this.state;
|
||||||
@ -651,10 +632,11 @@ class Records extends Component {
|
|||||||
contextMenu: (
|
contextMenu: (
|
||||||
<ContextMenu
|
<ContextMenu
|
||||||
isGroupView={isGroupView}
|
isGroupView={isGroupView}
|
||||||
toggleDeleteFolderDialog={this.toggleDeleteFolderDialog}
|
|
||||||
recordGetterByIndex={this.props.recordGetterByIndex}
|
recordGetterByIndex={this.props.recordGetterByIndex}
|
||||||
updateRecords={this.props.updateRecords}
|
updateRecords={this.props.updateRecords}
|
||||||
deleteRecords={this.props.deleteRecords}
|
deleteRecords={this.props.deleteRecords}
|
||||||
|
moveRecord={this.props.moveRecord}
|
||||||
|
addFolder={this.props.addFolder}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
hasSelectedRecord: this.hasSelectedRecord(),
|
hasSelectedRecord: this.hasSelectedRecord(),
|
||||||
@ -694,7 +676,7 @@ class Records extends Component {
|
|||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
recordIds, recordsCount, table, isGroupView, groupOffsetLeft, renameColumn, modifyColumnData,
|
recordIds, recordsCount, table, isGroupView, groupOffsetLeft, renameColumn, modifyColumnData,
|
||||||
deleteColumn, modifyColumnOrder, insertColumn,
|
deleteColumn, modifyColumnOrder, insertColumn
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const { recordMetrics, columnMetrics, selectedRange, colOverScanStartIdx, colOverScanEndIdx } = this.state;
|
const { recordMetrics, columnMetrics, selectedRange, colOverScanStartIdx, colOverScanEndIdx } = this.state;
|
||||||
const { columns, totalWidth, lastFrozenColumnKey } = columnMetrics;
|
const { columns, totalWidth, lastFrozenColumnKey } = columnMetrics;
|
||||||
@ -763,14 +745,6 @@ class Records extends Component {
|
|||||||
getRecordsSummaries={() => { }}
|
getRecordsSummaries={() => { }}
|
||||||
loadAll={this.props.loadAll}
|
loadAll={this.props.loadAll}
|
||||||
/>
|
/>
|
||||||
{this.state.deletedFolderPath && (
|
|
||||||
<DeleteFolderDialog
|
|
||||||
repoID={window.sfMetadataStore.repoId}
|
|
||||||
path={this.state.deletedFolderPath}
|
|
||||||
deleteFolder={this.deleteFolder}
|
|
||||||
toggleDialog={this.toggleDeleteFolderDialog}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -803,6 +777,8 @@ Records.propTypes = {
|
|||||||
modifyColumnWidth: PropTypes.func,
|
modifyColumnWidth: PropTypes.func,
|
||||||
modifyColumnOrder: PropTypes.func,
|
modifyColumnOrder: PropTypes.func,
|
||||||
getCopiedRecordsAndColumnsFromRange: PropTypes.func,
|
getCopiedRecordsAndColumnsFromRange: PropTypes.func,
|
||||||
|
moveRecord: PropTypes.func,
|
||||||
|
addFolder: PropTypes.func,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Records;
|
export default Records;
|
||||||
|
@ -1228,55 +1228,59 @@ class LibContentView extends React.Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// list operations
|
// list operations
|
||||||
onMoveItem = (destRepo, dirent, moveToDirentPath, nodeParentPath, byDialog = false) => {
|
moveItemsAjaxCallback = (repoID, targetRepo, dirent, moveToDirentPath, nodeParentPath, taskId, byDialog = false) => {
|
||||||
this.updateCurrentNotExistDirent(dirent);
|
this.updateCurrentNotExistDirent(dirent);
|
||||||
|
|
||||||
|
const dirName = dirent.name;
|
||||||
|
const direntPath = Utils.joinPath(nodeParentPath, dirName);
|
||||||
|
if (repoID !== targetRepo.repo_id) {
|
||||||
|
this.setState({
|
||||||
|
asyncCopyMoveTaskId: taskId,
|
||||||
|
asyncOperatedFilesLength: 1,
|
||||||
|
asyncOperationProgress: 0,
|
||||||
|
asyncOperationType: 'move',
|
||||||
|
isCopyMoveProgressDialogShow: true,
|
||||||
|
}, () => {
|
||||||
|
this.currentMoveItemName = dirName;
|
||||||
|
this.currentMoveItemPath = direntPath;
|
||||||
|
this.getAsyncCopyMoveProgress(dirName, direntPath);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.state.isTreePanelShown) {
|
||||||
|
this.deleteTreeNode(direntPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. move to current repo
|
||||||
|
// 2. tow columns mode need update left tree
|
||||||
|
if (repoID === targetRepo.repo_id && this.state.isTreePanelShown) {
|
||||||
|
this.updateMoveCopyTreeNode(moveToDirentPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.moveDirent(direntPath, moveToDirentPath);
|
||||||
|
|
||||||
|
// show tip message if move to current repo
|
||||||
|
if (repoID === targetRepo.repo_id) {
|
||||||
|
let message = gettext('Successfully moved {name}.');
|
||||||
|
message = message.replace('{name}', dirName);
|
||||||
|
toaster.success(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (byDialog) {
|
||||||
|
this.updateRecentlyUsedRepos(targetRepo, moveToDirentPath);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onMoveItem = (destRepo, dirent, moveToDirentPath, nodeParentPath, byDialog = false) => {
|
||||||
let repoID = this.props.repoID;
|
let repoID = this.props.repoID;
|
||||||
// just for view list state
|
// just for view list state
|
||||||
let dirName = dirent.name;
|
let dirName = dirent.name;
|
||||||
if (!nodeParentPath) {
|
if (!nodeParentPath) {
|
||||||
nodeParentPath = this.state.path;
|
nodeParentPath = this.state.path;
|
||||||
}
|
}
|
||||||
let direntPath = Utils.joinPath(nodeParentPath, dirName);
|
|
||||||
|
|
||||||
seafileAPI.moveDir(repoID, destRepo.repo_id, moveToDirentPath, nodeParentPath, dirName).then(res => {
|
seafileAPI.moveDir(repoID, destRepo.repo_id, moveToDirentPath, nodeParentPath, dirName).then(res => {
|
||||||
if (repoID !== destRepo.repo_id) {
|
this.moveItemsAjaxCallback(repoID, destRepo, dirent, moveToDirentPath, nodeParentPath, res.data.task_id, byDialog);
|
||||||
this.setState({
|
|
||||||
asyncCopyMoveTaskId: res.data.task_id,
|
|
||||||
asyncOperatedFilesLength: 1,
|
|
||||||
asyncOperationProgress: 0,
|
|
||||||
asyncOperationType: 'move',
|
|
||||||
isCopyMoveProgressDialogShow: true,
|
|
||||||
}, () => {
|
|
||||||
this.currentMoveItemName = dirName;
|
|
||||||
this.currentMoveItemPath = direntPath;
|
|
||||||
this.getAsyncCopyMoveProgress(dirName, direntPath);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.state.isTreePanelShown) {
|
|
||||||
this.deleteTreeNode(direntPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 1. move to current repo
|
|
||||||
// 2. tow columns mode need update left tree
|
|
||||||
if (repoID === destRepo.repo_id &&
|
|
||||||
this.state.isTreePanelShown) {
|
|
||||||
this.updateMoveCopyTreeNode(moveToDirentPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.moveDirent(direntPath, moveToDirentPath);
|
|
||||||
|
|
||||||
// show tip message if move to current repo
|
|
||||||
if (repoID === destRepo.repo_id) {
|
|
||||||
let message = gettext('Successfully moved {name}.');
|
|
||||||
message = message.replace('{name}', dirName);
|
|
||||||
toaster.success(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (byDialog) {
|
|
||||||
this.updateRecentlyUsedRepos(destRepo, moveToDirentPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
}).catch((error) => {
|
}).catch((error) => {
|
||||||
if (!error.response.data.lib_need_decrypt) {
|
if (!error.response.data.lib_need_decrypt) {
|
||||||
let errMessage = Utils.getErrorMsg(error);
|
let errMessage = Utils.getErrorMsg(error);
|
||||||
@ -1285,19 +1289,51 @@ class LibContentView extends React.Component {
|
|||||||
errMessage = errMessage.replace('{name}', dirName);
|
errMessage = errMessage.replace('{name}', dirName);
|
||||||
}
|
}
|
||||||
toaster.danger(errMessage);
|
toaster.danger(errMessage);
|
||||||
} else {
|
return;
|
||||||
this.setState({
|
|
||||||
libNeedDecryptWhenMove: true,
|
|
||||||
destRepoWhenCopyMove: destRepo,
|
|
||||||
destDirentPathWhenCopyMove: moveToDirentPath,
|
|
||||||
copyMoveSingleItem: true,
|
|
||||||
srcDirentWhenCopyMove: dirent,
|
|
||||||
srcNodeParentPathWhenCopyMove: nodeParentPath,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
this.setState({
|
||||||
|
libNeedDecryptWhenMove: true,
|
||||||
|
destRepoWhenCopyMove: destRepo,
|
||||||
|
destDirentPathWhenCopyMove: moveToDirentPath,
|
||||||
|
copyMoveSingleItem: true,
|
||||||
|
srcDirentWhenCopyMove: dirent,
|
||||||
|
srcNodeParentPathWhenCopyMove: nodeParentPath,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
copyItemsAjaxCallback = (repoID, targetRepo, dirent, copyToDirentPath, nodeParentPath, taskId, byDialog = false) => {
|
||||||
|
if (repoID !== targetRepo.repo_id) {
|
||||||
|
this.setState({
|
||||||
|
asyncCopyMoveTaskId: taskId,
|
||||||
|
asyncOperatedFilesLength: 1,
|
||||||
|
asyncOperationProgress: 0,
|
||||||
|
asyncOperationType: 'copy',
|
||||||
|
isCopyMoveProgressDialogShow: true
|
||||||
|
}, () => {
|
||||||
|
this.getAsyncCopyMoveProgress();
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.state.isTreePanelShown) {
|
||||||
|
this.updateMoveCopyTreeNode(copyToDirentPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (copyToDirentPath === nodeParentPath && this.state.currentMode !== METADATA_MODE) {
|
||||||
|
this.loadDirentList(this.state.path);
|
||||||
|
}
|
||||||
|
|
||||||
|
const dirName = dirent.name;
|
||||||
|
let message = gettext('Successfully copied %(name)s.');
|
||||||
|
message = message.replace('%(name)s', dirName);
|
||||||
|
toaster.success(message);
|
||||||
|
|
||||||
|
if (byDialog) {
|
||||||
|
this.updateRecentlyUsedRepos(targetRepo, copyToDirentPath);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
onCopyItem = (destRepo, dirent, copyToDirentPath, nodeParentPath, byDialog = false) => {
|
onCopyItem = (destRepo, dirent, copyToDirentPath, nodeParentPath, byDialog = false) => {
|
||||||
let repoID = this.props.repoID;
|
let repoID = this.props.repoID;
|
||||||
// just for view list state
|
// just for view list state
|
||||||
@ -1307,36 +1343,7 @@ class LibContentView extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
seafileAPI.copyDir(repoID, destRepo.repo_id, copyToDirentPath, nodeParentPath, dirName).then(res => {
|
seafileAPI.copyDir(repoID, destRepo.repo_id, copyToDirentPath, nodeParentPath, dirName).then(res => {
|
||||||
|
this.copyItemsAjaxCallback(repoID, destRepo, dirent, copyToDirentPath, nodeParentPath, res.data.task_id || null, byDialog);
|
||||||
if (repoID !== destRepo.repo_id) {
|
|
||||||
this.setState({
|
|
||||||
asyncCopyMoveTaskId: res.data.task_id,
|
|
||||||
asyncOperatedFilesLength: 1,
|
|
||||||
asyncOperationProgress: 0,
|
|
||||||
asyncOperationType: 'copy',
|
|
||||||
isCopyMoveProgressDialogShow: true
|
|
||||||
}, () => {
|
|
||||||
this.getAsyncCopyMoveProgress();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (repoID === destRepo.repo_id) {
|
|
||||||
if (this.state.isTreePanelShown) {
|
|
||||||
this.updateMoveCopyTreeNode(copyToDirentPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (copyToDirentPath === nodeParentPath) {
|
|
||||||
this.loadDirentList(this.state.path);
|
|
||||||
}
|
|
||||||
|
|
||||||
let message = gettext('Successfully copied %(name)s.');
|
|
||||||
message = message.replace('%(name)s', dirName);
|
|
||||||
toaster.success(message);
|
|
||||||
|
|
||||||
if (byDialog) {
|
|
||||||
this.updateRecentlyUsedRepos(destRepo, copyToDirentPath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}).catch((error) => {
|
}).catch((error) => {
|
||||||
if (!error.response.data.lib_need_decrypt) {
|
if (!error.response.data.lib_need_decrypt) {
|
||||||
let errMessage = Utils.getErrorMsg(error);
|
let errMessage = Utils.getErrorMsg(error);
|
||||||
@ -2348,7 +2355,9 @@ class LibContentView extends React.Component {
|
|||||||
deleteFilesCallback={this.deleteItemsAjaxCallback}
|
deleteFilesCallback={this.deleteItemsAjaxCallback}
|
||||||
renameFileCallback={this.renameItemAjaxCallback}
|
renameFileCallback={this.renameItemAjaxCallback}
|
||||||
onItemMove={this.onMoveItem}
|
onItemMove={this.onMoveItem}
|
||||||
|
moveFileCallback={this.moveItemsAjaxCallback}
|
||||||
onItemCopy={this.onCopyItem}
|
onItemCopy={this.onCopyItem}
|
||||||
|
copyFileCallback={this.copyItemsAjaxCallback}
|
||||||
onItemConvert={this.onConvertItem}
|
onItemConvert={this.onConvertItem}
|
||||||
onDirentClick={this.onDirentClick}
|
onDirentClick={this.onDirentClick}
|
||||||
updateDirent={this.updateDirent}
|
updateDirent={this.updateDirent}
|
||||||
|
Loading…
Reference in New Issue
Block a user