mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-02 07:27:04 +00:00
feat(metadata-table): delete/rename folder/file via contextmenu (#6848)
This commit is contained in:
@@ -60,6 +60,8 @@ const propTypes = {
|
|||||||
onItemSelected: PropTypes.func.isRequired,
|
onItemSelected: PropTypes.func.isRequired,
|
||||||
onItemDelete: PropTypes.func.isRequired,
|
onItemDelete: PropTypes.func.isRequired,
|
||||||
onItemRename: PropTypes.func.isRequired,
|
onItemRename: PropTypes.func.isRequired,
|
||||||
|
deleteFilesCallback: PropTypes.func,
|
||||||
|
renameFileCallback: PropTypes.func,
|
||||||
onItemMove: PropTypes.func.isRequired,
|
onItemMove: PropTypes.func.isRequired,
|
||||||
onItemCopy: PropTypes.func.isRequired,
|
onItemCopy: PropTypes.func.isRequired,
|
||||||
onItemConvert: PropTypes.func.isRequired,
|
onItemConvert: PropTypes.func.isRequired,
|
||||||
@@ -198,6 +200,8 @@ class DirColumnView extends React.Component {
|
|||||||
repoID={this.props.repoID}
|
repoID={this.props.repoID}
|
||||||
repoInfo={this.props.currentRepoInfo}
|
repoInfo={this.props.currentRepoInfo}
|
||||||
viewID={this.props.viewId}
|
viewID={this.props.viewId}
|
||||||
|
deleteFilesCallback={this.props.deleteFilesCallback}
|
||||||
|
renameFileCallback={this.props.renameFileCallback}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
{currentMode === LIST_MODE &&
|
{currentMode === LIST_MODE &&
|
||||||
|
@@ -68,6 +68,9 @@ class TreeHelper {
|
|||||||
renameNodeByPath(tree, nodePath, newName) {
|
renameNodeByPath(tree, nodePath, newName) {
|
||||||
let treeCopy = tree.clone();
|
let treeCopy = tree.clone();
|
||||||
let node = treeCopy.getNodeByPath(nodePath);
|
let node = treeCopy.getNodeByPath(nodePath);
|
||||||
|
if (!node) {
|
||||||
|
return treeCopy;
|
||||||
|
}
|
||||||
treeCopy.renameNode(node, newName);
|
treeCopy.renameNode(node, newName);
|
||||||
return treeCopy;
|
return treeCopy;
|
||||||
}
|
}
|
||||||
|
@@ -239,11 +239,11 @@ class MetadataManagerAPI {
|
|||||||
* @param {string[]} dirents - Array of file/folder paths to delete
|
* @param {string[]} dirents - Array of file/folder paths to delete
|
||||||
* @returns {Promise} Axios delete request promise
|
* @returns {Promise} Axios delete request promise
|
||||||
*/
|
*/
|
||||||
deleteImages(repoID, dirents) {
|
batchDeleteFiles(repo_id, file_names) {
|
||||||
const url = this.server + '/api/v2.1/repos/batch-delete-folders-item/';
|
const url = this.server + '/api/v2.1/repos/batch-delete-folders-item/';
|
||||||
const data = {
|
const data = {
|
||||||
repo_id: repoID,
|
repo_id,
|
||||||
file_names: dirents
|
file_names,
|
||||||
};
|
};
|
||||||
return this.req.delete(url, { data });
|
return this.req.delete(url, { data });
|
||||||
}
|
}
|
||||||
|
@@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
|
|||||||
import NormalEditorContainer from './normal-editor-container';
|
import NormalEditorContainer from './normal-editor-container';
|
||||||
import PopupEditorContainer from './popup-editor-container';
|
import PopupEditorContainer from './popup-editor-container';
|
||||||
import PreviewEditorContainer from './preview-editor-container';
|
import PreviewEditorContainer from './preview-editor-container';
|
||||||
import { CellType } from '../../../constants';
|
import { CellType, EDITOR_TYPE } from '../../../constants';
|
||||||
|
|
||||||
const POPUP_EDITOR_COLUMN_TYPES = [
|
const POPUP_EDITOR_COLUMN_TYPES = [
|
||||||
CellType.DATE,
|
CellType.DATE,
|
||||||
@@ -18,12 +18,12 @@ const PREVIEW_EDITOR_COLUMN_TYPES = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
const EditorContainer = (props) => {
|
const EditorContainer = (props) => {
|
||||||
const { column } = props;
|
const { column, openEditorMode } = props;
|
||||||
if (!column) return null;
|
if (!column) return null;
|
||||||
const { type } = column;
|
const { type } = column;
|
||||||
if (POPUP_EDITOR_COLUMN_TYPES.includes(type)) {
|
if (POPUP_EDITOR_COLUMN_TYPES.includes(type)) {
|
||||||
return <PopupEditorContainer { ...props } />;
|
return <PopupEditorContainer { ...props } />;
|
||||||
} else if (PREVIEW_EDITOR_COLUMN_TYPES.includes(type)) {
|
} else if (PREVIEW_EDITOR_COLUMN_TYPES.includes(type) && openEditorMode === EDITOR_TYPE.PREVIEWER) {
|
||||||
return <PreviewEditorContainer { ...props } />;
|
return <PreviewEditorContainer { ...props } />;
|
||||||
} else {
|
} else {
|
||||||
return <NormalEditorContainer { ...props } />;
|
return <NormalEditorContainer { ...props } />;
|
||||||
@@ -32,6 +32,7 @@ const EditorContainer = (props) => {
|
|||||||
|
|
||||||
EditorContainer.propTypes = {
|
EditorContainer.propTypes = {
|
||||||
column: PropTypes.object,
|
column: PropTypes.object,
|
||||||
|
openEditorMode: PropTypes.string,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default EditorContainer;
|
export default EditorContainer;
|
||||||
|
@@ -2,7 +2,7 @@ import React from 'react';
|
|||||||
import Editor from '../editor';
|
import Editor from '../editor';
|
||||||
|
|
||||||
const PreviewEditorContainer = (props) => {
|
const PreviewEditorContainer = (props) => {
|
||||||
return (<Editor { ...props } />);
|
return (<Editor { ...props } mode={props.openEditorMode} />);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default PreviewEditorContainer;
|
export default PreviewEditorContainer;
|
||||||
|
@@ -1,62 +1,29 @@
|
|||||||
import React, { useEffect, useMemo, useState } from 'react';
|
import React, { useImperativeHandle, useRef } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { ModalPortal } from '@seafile/sf-metadata-ui-component';
|
|
||||||
import { Utils } from '../../../utils/utils';
|
import { Utils } from '../../../utils/utils';
|
||||||
import ImageDialog from '../../../components/dialog/image-dialog';
|
import { EDITOR_TYPE } from '../../constants';
|
||||||
import { siteRoot, thumbnailSizeForOriginal, fileServerRoot, thumbnailDefaultSize } from '../../../utils/constants';
|
import ImagePreviewer from '../cell-formatter/image-previewer';
|
||||||
import { PRIVATE_COLUMN_KEY } from '../../constants';
|
import TextEditor from './text-editor';
|
||||||
import imageAPI from '../../../utils/image-api';
|
import { checkIsDir } from '../../utils/row';
|
||||||
import { seafileAPI } from '../../../utils/seafile-api';
|
|
||||||
import toaster from '../../../components/toast';
|
|
||||||
|
|
||||||
const FileNameEditor = ({ column, record, table, onCommitCancel }) => {
|
const FileNameEditor = React.forwardRef((props, ref) => {
|
||||||
const [imageIndex, setImageIndex] = useState(0);
|
const { column, record, mode } = props;
|
||||||
const [imageItems, setImageItems] = useState([]);
|
const textEditorRef = useRef(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useImperativeHandle(ref, () => {
|
||||||
const repoID = window.sfMetadataContext.getSetting('repoID');
|
return textEditorRef.current;
|
||||||
const repoInfo = window.sfMetadataContext.getSetting('repoInfo');
|
});
|
||||||
const newImageItems = table.rows
|
|
||||||
.filter(row => Utils.imageCheck(row[PRIVATE_COLUMN_KEY.FILE_NAME]))
|
|
||||||
.map(item => {
|
|
||||||
const fileName = item[PRIVATE_COLUMN_KEY.FILE_NAME];
|
|
||||||
const parentDir = item[PRIVATE_COLUMN_KEY.PARENT_DIR];
|
|
||||||
const path = Utils.encodePath(Utils.joinPath(parentDir, fileName));
|
|
||||||
const fileExt = fileName.substr(fileName.lastIndexOf('.') + 1).toLowerCase();
|
|
||||||
const isGIF = fileExt === 'gif';
|
|
||||||
const useThumbnail = repoInfo?.encrypted;
|
|
||||||
const basePath = `${siteRoot}${useThumbnail && !isGIF ? 'thumbnail' : 'repo'}/${repoID}`;
|
|
||||||
const src = `${basePath}/${useThumbnail && !isGIF ? thumbnailSizeForOriginal : 'raw'}${path}`;
|
|
||||||
return {
|
|
||||||
name: fileName,
|
|
||||||
url: `${siteRoot}lib/${repoID}/file${path}`,
|
|
||||||
thumbnail: `${siteRoot}thumbnail/${repoID}/${thumbnailSizeForOriginal}${path}`,
|
|
||||||
src: src,
|
|
||||||
downloadURL: `${fileServerRoot}repos/${repoID}/files${path}/?op=download`,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
setImageItems(newImageItems);
|
|
||||||
}, [table]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
const getFileName = () => {
|
||||||
if (imageItems.length > 0) {
|
const { key } = column;
|
||||||
const index = imageItems.findIndex(item => item.name === record[PRIVATE_COLUMN_KEY.FILE_NAME]);
|
return record[key];
|
||||||
if (index > -1) setImageIndex(index);
|
};
|
||||||
|
|
||||||
|
const getFileType = () => {
|
||||||
|
if (checkIsDir(record)) {
|
||||||
|
return 'folder';
|
||||||
}
|
}
|
||||||
}, [imageItems, record]);
|
const fileName = getFileName();
|
||||||
|
|
||||||
const _isDir = useMemo(() => {
|
|
||||||
const isDirValue = record[PRIVATE_COLUMN_KEY.IS_DIR];
|
|
||||||
if (typeof isDirValue === 'string') return isDirValue.toUpperCase() === 'TRUE';
|
|
||||||
return isDirValue;
|
|
||||||
}, [record]);
|
|
||||||
|
|
||||||
const fileName = useMemo(() => {
|
|
||||||
return record[column.key];
|
|
||||||
}, [column, record]);
|
|
||||||
|
|
||||||
const fileType = useMemo(() => {
|
|
||||||
if (_isDir) return 'folder';
|
|
||||||
if (!fileName) return '';
|
if (!fileName) return '';
|
||||||
const index = fileName.lastIndexOf('.');
|
const index = fileName.lastIndexOf('.');
|
||||||
if (index === -1) return '';
|
if (index === -1) return '';
|
||||||
@@ -66,63 +33,27 @@ const FileNameEditor = ({ column, record, table, onCommitCancel }) => {
|
|||||||
if (Utils.isMarkdownFile(fileName)) return 'markdown';
|
if (Utils.isMarkdownFile(fileName)) return 'markdown';
|
||||||
if (Utils.isSdocFile(fileName)) return 'sdoc';
|
if (Utils.isSdocFile(fileName)) return 'sdoc';
|
||||||
return '';
|
return '';
|
||||||
}, [_isDir, fileName]);
|
|
||||||
|
|
||||||
const moveToPrevImage = () => {
|
|
||||||
const imageItemsLength = imageItems.length;
|
|
||||||
setImageIndex((prevState) => (prevState + imageItemsLength - 1) % imageItemsLength);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const moveToNextImage = () => {
|
if (mode === EDITOR_TYPE.PREVIEWER) {
|
||||||
const imageItemsLength = imageItems.length;
|
const fileType = getFileType();
|
||||||
setImageIndex((prevState) => (prevState + 1) % imageItemsLength);
|
if (fileType === 'image') {
|
||||||
};
|
return (
|
||||||
|
<ImagePreviewer {...props} closeImagePopup={props.onCommitCancel} />
|
||||||
const rotateImage = (imageIndex, angle) => {
|
);
|
||||||
if (imageIndex >= 0 && angle !== 0) {
|
|
||||||
const repoID = window.sfMetadataContext.getSetting('repoID');
|
|
||||||
const imageItem = imageItems[imageIndex];
|
|
||||||
const path = imageItem.url.slice(imageItem.url.indexOf('/file/') + 5);
|
|
||||||
imageAPI.rotateImage(repoID, path, 360 - angle).then((res) => {
|
|
||||||
if (res.data?.success) {
|
|
||||||
seafileAPI.createThumbnail(repoID, path, thumbnailDefaultSize).then((res) => {
|
|
||||||
if (res.data?.encoded_thumbnail_src) {
|
|
||||||
const cacheBuster = new Date().getTime();
|
|
||||||
const newThumbnailSrc = `${res.data.encoded_thumbnail_src}?t=${cacheBuster}`;
|
|
||||||
imageItems[imageIndex].src = newThumbnailSrc;
|
|
||||||
setImageItems(imageItems);
|
|
||||||
}
|
|
||||||
}).catch(error => {
|
|
||||||
toaster.danger(Utils.getErrorMsg(error));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}).catch(error => {
|
|
||||||
toaster.danger(Utils.getErrorMsg(error));
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
if (fileType === 'image') {
|
return null;
|
||||||
return (
|
|
||||||
<ModalPortal>
|
|
||||||
<ImageDialog
|
|
||||||
imageItems={imageItems}
|
|
||||||
imageIndex={imageIndex}
|
|
||||||
closeImagePopup={onCommitCancel}
|
|
||||||
moveToPrevImage={moveToPrevImage}
|
|
||||||
moveToNextImage={moveToNextImage}
|
|
||||||
onRotateImage={rotateImage}
|
|
||||||
/>
|
|
||||||
</ModalPortal>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return (<TextEditor ref={textEditorRef} { ...props } readOnly={false} />);
|
||||||
};
|
});
|
||||||
|
|
||||||
FileNameEditor.propTypes = {
|
FileNameEditor.propTypes = {
|
||||||
|
table: PropTypes.object,
|
||||||
column: PropTypes.object,
|
column: PropTypes.object,
|
||||||
record: PropTypes.object,
|
record: PropTypes.object,
|
||||||
|
mode: PropTypes.string,
|
||||||
onCommitCancel: PropTypes.func,
|
onCommitCancel: PropTypes.func,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -0,0 +1,104 @@
|
|||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { ModalPortal } from '@seafile/sf-metadata-ui-component';
|
||||||
|
import toaster from '../../../components/toast';
|
||||||
|
import ImageDialog from '../../../components/dialog/image-dialog';
|
||||||
|
import imageAPI from '../../../utils/image-api';
|
||||||
|
import { seafileAPI } from '../../../utils/seafile-api';
|
||||||
|
import { Utils } from '../../../utils/utils';
|
||||||
|
import { siteRoot, thumbnailSizeForOriginal, fileServerRoot, thumbnailDefaultSize } from '../../../utils/constants';
|
||||||
|
import { getFileNameFromRecord, getParentDirFromRecord } from '../../utils/cell';
|
||||||
|
|
||||||
|
const ImagePreviewer = (props) => {
|
||||||
|
const { record, table, closeImagePopup } = props;
|
||||||
|
const [imageIndex, setImageIndex] = useState(0);
|
||||||
|
const [imageItems, setImageItems] = useState([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const repoID = window.sfMetadataContext.getSetting('repoID');
|
||||||
|
const repoInfo = window.sfMetadataContext.getSetting('repoInfo');
|
||||||
|
const newImageItems = table.rows
|
||||||
|
.filter((row) => Utils.imageCheck(getFileNameFromRecord(row)))
|
||||||
|
.map((row) => {
|
||||||
|
const fileName = getFileNameFromRecord(row);
|
||||||
|
const parentDir = getParentDirFromRecord(row);
|
||||||
|
const path = Utils.encodePath(Utils.joinPath(parentDir, fileName));
|
||||||
|
const fileExt = fileName.substr(fileName.lastIndexOf('.') + 1).toLowerCase();
|
||||||
|
const isGIF = fileExt === 'gif';
|
||||||
|
const useThumbnail = repoInfo?.encrypted;
|
||||||
|
const basePath = `${siteRoot}${useThumbnail && !isGIF ? 'thumbnail' : 'repo'}/${repoID}`;
|
||||||
|
const src = `${basePath}/${useThumbnail && !isGIF ? thumbnailSizeForOriginal : 'raw'}${path}`;
|
||||||
|
return {
|
||||||
|
name: fileName,
|
||||||
|
url: `${siteRoot}lib/${repoID}/file${path}`,
|
||||||
|
thumbnail: `${siteRoot}thumbnail/${repoID}/${thumbnailSizeForOriginal}${path}`,
|
||||||
|
src: src,
|
||||||
|
downloadURL: `${fileServerRoot}repos/${repoID}/files${path}/?op=download`,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
setImageItems(newImageItems);
|
||||||
|
}, [table]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (imageItems.length > 0) {
|
||||||
|
const index = imageItems.findIndex(item => item.name === getFileNameFromRecord(record));
|
||||||
|
if (index > -1) setImageIndex(index);
|
||||||
|
}
|
||||||
|
}, [imageItems, record]);
|
||||||
|
|
||||||
|
const moveToPrevImage = () => {
|
||||||
|
const imageItemsLength = imageItems.length;
|
||||||
|
setImageIndex((prevState) => (prevState + imageItemsLength - 1) % imageItemsLength);
|
||||||
|
};
|
||||||
|
|
||||||
|
const moveToNextImage = () => {
|
||||||
|
const imageItemsLength = imageItems.length;
|
||||||
|
setImageIndex((prevState) => (prevState + 1) % imageItemsLength);
|
||||||
|
};
|
||||||
|
|
||||||
|
const rotateImage = (imageIndex, angle) => {
|
||||||
|
if (imageIndex >= 0 && angle !== 0) {
|
||||||
|
const repoID = window.sfMetadataContext.getSetting('repoID');
|
||||||
|
const imageItem = imageItems[imageIndex];
|
||||||
|
const path = imageItem.url.slice(imageItem.url.indexOf('/file/') + 5);
|
||||||
|
imageAPI.rotateImage(repoID, path, 360 - angle).then((res) => {
|
||||||
|
if (res.data?.success) {
|
||||||
|
seafileAPI.createThumbnail(repoID, path, thumbnailDefaultSize).then((res) => {
|
||||||
|
if (res.data?.encoded_thumbnail_src) {
|
||||||
|
const cacheBuster = new Date().getTime();
|
||||||
|
const newThumbnailSrc = `${res.data.encoded_thumbnail_src}?t=${cacheBuster}`;
|
||||||
|
imageItems[imageIndex].src = newThumbnailSrc;
|
||||||
|
setImageItems(imageItems);
|
||||||
|
}
|
||||||
|
}).catch(error => {
|
||||||
|
toaster.danger(Utils.getErrorMsg(error));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}).catch(error => {
|
||||||
|
toaster.danger(Utils.getErrorMsg(error));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ModalPortal>
|
||||||
|
<ImageDialog
|
||||||
|
imageItems={imageItems}
|
||||||
|
imageIndex={imageIndex}
|
||||||
|
closeImagePopup={closeImagePopup}
|
||||||
|
moveToPrevImage={moveToPrevImage}
|
||||||
|
moveToNextImage={moveToNextImage}
|
||||||
|
onRotateImage={rotateImage}
|
||||||
|
/>
|
||||||
|
</ModalPortal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
ImagePreviewer.propTypes = {
|
||||||
|
table: PropTypes.object,
|
||||||
|
column: PropTypes.object,
|
||||||
|
record: PropTypes.object,
|
||||||
|
closeImagePopup: PropTypes.func,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ImagePreviewer;
|
@@ -7,7 +7,7 @@ import DetailItem from '../../../components/dirent-detail/detail-item';
|
|||||||
import { Utils } from '../../../utils/utils';
|
import { Utils } from '../../../utils/utils';
|
||||||
import metadataAPI from '../../api';
|
import metadataAPI from '../../api';
|
||||||
import Column from '../../model/metadata/column';
|
import Column from '../../model/metadata/column';
|
||||||
import { getCellValueByColumn, getOptionName, getColumnOptionNamesByIds, getColumnOptionNameById } from '../../utils/cell';
|
import { getCellValueByColumn, getOptionName, getColumnOptionNamesByIds, getColumnOptionNameById, getFileNameFromRecord } from '../../utils/cell';
|
||||||
import { normalizeFields } from './utils';
|
import { normalizeFields } from './utils';
|
||||||
import { gettext } from '../../../utils/constants';
|
import { gettext } from '../../../utils/constants';
|
||||||
import { CellType, PREDEFINED_COLUMN_KEYS, PRIVATE_COLUMN_KEY } from '../../constants';
|
import { CellType, PREDEFINED_COLUMN_KEYS, PRIVATE_COLUMN_KEY } from '../../constants';
|
||||||
@@ -102,7 +102,7 @@ const MetadataDetails = ({ repoID, filePath, repoInfo, direntType }) => {
|
|||||||
if (isLoading) return null;
|
if (isLoading) return null;
|
||||||
const { fields, record } = metadata;
|
const { fields, record } = metadata;
|
||||||
if (!record._id) return null;
|
if (!record._id) return null;
|
||||||
const fileName = record[PRIVATE_COLUMN_KEY.FILE_NAME];
|
const fileName = getFileNameFromRecord(record);
|
||||||
const isImage = record && (Utils.imageCheck(fileName) || Utils.videoCheck(fileName));
|
const isImage = record && (Utils.imageCheck(fileName) || Utils.videoCheck(fileName));
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@@ -104,6 +104,11 @@ class Context {
|
|||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
checkCanDeleteRow = () => {
|
||||||
|
if (this.permission === 'r') return false;
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
canModifyRows = () => {
|
canModifyRows = () => {
|
||||||
if (this.permission === 'r') return false;
|
if (this.permission === 'r') return false;
|
||||||
return true;
|
return true;
|
||||||
@@ -189,6 +194,10 @@ class Context {
|
|||||||
return this.metadataAPI.modifyRecords(repoId, recordsData, isCopyPaste);
|
return this.metadataAPI.modifyRecords(repoId, recordsData, isCopyPaste);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
batchDeleteFiles = (repoId, fileNames) => {
|
||||||
|
return this.metadataAPI.batchDeleteFiles(repoId, fileNames);
|
||||||
|
};
|
||||||
|
|
||||||
// view
|
// view
|
||||||
modifyView = (repoId, viewId, viewData) => {
|
modifyView = (repoId, viewId, viewData) => {
|
||||||
return this.metadataAPI.modifyView(repoId, viewId, viewData);
|
return this.metadataAPI.modifyView(repoId, viewId, viewData);
|
||||||
|
@@ -5,7 +5,8 @@ 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 } from '../constants';
|
||||||
import { Utils } from '../../utils/utils';
|
import { Utils } from '../../utils/utils';
|
||||||
import { useCollaborators, useMetadata } from '.';
|
import { useMetadata } from './metadata';
|
||||||
|
import { useCollaborators } from './collaborators';
|
||||||
import { gettext } from '../../utils/constants';
|
import { gettext } from '../../utils/constants';
|
||||||
|
|
||||||
const MetadataViewContext = React.createContext(null);
|
const MetadataViewContext = React.createContext(null);
|
||||||
@@ -116,7 +117,15 @@ export const MetadataViewProvider = ({
|
|||||||
}, [repoID, viewID]);
|
}, [repoID, viewID]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MetadataViewContext.Provider value={{ isLoading, metadata, store: storeRef.current }}>
|
<MetadataViewContext.Provider
|
||||||
|
value={{
|
||||||
|
isLoading,
|
||||||
|
metadata,
|
||||||
|
store: storeRef.current,
|
||||||
|
deleteFilesCallback: params.deleteFilesCallback,
|
||||||
|
renameFileCallback: params.renameFileCallback,
|
||||||
|
}}
|
||||||
|
>
|
||||||
{children}
|
{children}
|
||||||
</MetadataViewContext.Provider>
|
</MetadataViewContext.Provider>
|
||||||
);
|
);
|
||||||
@@ -127,6 +136,5 @@ export const useMetadataView = () => {
|
|||||||
if (!context) {
|
if (!context) {
|
||||||
throw new Error('\'MetadataContext\' is null');
|
throw new Error('\'MetadataContext\' is null');
|
||||||
}
|
}
|
||||||
const { isLoading, metadata, store } = context;
|
return context;
|
||||||
return { isLoading, metadata, store };
|
|
||||||
};
|
};
|
||||||
|
@@ -109,6 +109,19 @@ class DataProcessor {
|
|||||||
// todo update sort and filter and ui change
|
// todo update sort and filter and ui change
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static updatePageDataWithDeleteRecords(deletedRowsIds, table) {
|
||||||
|
const { available_columns, groupbys, groups, rows } = table.view;
|
||||||
|
const idNeedDeletedMap = deletedRowsIds.reduce((currIdNeedDeletedMap, rowId) => ({ ...currIdNeedDeletedMap, [rowId]: true }), {});
|
||||||
|
table.view.rows = rows.filter(rowId => !idNeedDeletedMap[rowId]);
|
||||||
|
|
||||||
|
// remove record from group view
|
||||||
|
const _isGroupView = isGroupView({ groupbys }, available_columns);
|
||||||
|
if (_isGroupView) {
|
||||||
|
this.deleteGroupRows(groups, idNeedDeletedMap);
|
||||||
|
table.view.groups = this.deleteEmptyGroups(groups);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static handleReloadedRecords(table, reloadedRecords, relatedColumnKeyMap) {
|
static handleReloadedRecords(table, reloadedRecords, relatedColumnKeyMap) {
|
||||||
const idReloadedRecordMap = reloadedRecords.reduce((map, record) => {
|
const idReloadedRecordMap = reloadedRecords.reduce((map, record) => {
|
||||||
map[record._id] = record;
|
map[record._id] = record;
|
||||||
@@ -175,7 +188,6 @@ class DataProcessor {
|
|||||||
|
|
||||||
static syncOperationOnData(table, operation, { collaborators }) {
|
static syncOperationOnData(table, operation, { collaborators }) {
|
||||||
switch (operation.op_type) {
|
switch (operation.op_type) {
|
||||||
case OPERATION_TYPE.MODIFY_RECORD:
|
|
||||||
case OPERATION_TYPE.MODIFY_RECORDS: {
|
case OPERATION_TYPE.MODIFY_RECORDS: {
|
||||||
const { available_columns } = table.view;
|
const { available_columns } = table.view;
|
||||||
const { id_original_row_updates, row_ids } = operation;
|
const { id_original_row_updates, row_ids } = operation;
|
||||||
@@ -213,6 +225,12 @@ class DataProcessor {
|
|||||||
this.updateSummaries();
|
this.updateSummaries();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case OPERATION_TYPE.DELETE_RECORDS: {
|
||||||
|
const { rows_ids } = operation;
|
||||||
|
this.updatePageDataWithDeleteRecords(rows_ids, table);
|
||||||
|
this.updateSummaries();
|
||||||
|
break;
|
||||||
|
}
|
||||||
case OPERATION_TYPE.RESTORE_RECORDS: {
|
case OPERATION_TYPE.RESTORE_RECORDS: {
|
||||||
const { rows_data, upper_row_ids } = operation;
|
const { rows_data, upper_row_ids } = operation;
|
||||||
const { rows } = table.view;
|
const { rows } = table.view;
|
||||||
|
@@ -5,10 +5,13 @@ import {
|
|||||||
Operation, LOCAL_APPLY_OPERATION_TYPE, NEED_APPLY_AFTER_SERVER_OPERATION, OPERATION_TYPE, UNDO_OPERATION_TYPE,
|
Operation, LOCAL_APPLY_OPERATION_TYPE, NEED_APPLY_AFTER_SERVER_OPERATION, OPERATION_TYPE, UNDO_OPERATION_TYPE,
|
||||||
VIEW_OPERATION, COLUMN_OPERATION
|
VIEW_OPERATION, COLUMN_OPERATION
|
||||||
} from './operations';
|
} from './operations';
|
||||||
import { EVENT_BUS_TYPE, PER_LOAD_NUMBER } from '../constants';
|
import { EVENT_BUS_TYPE, PER_LOAD_NUMBER, PRIVATE_COLUMN_KEY } from '../constants';
|
||||||
import DataProcessor from './data-processor';
|
import DataProcessor from './data-processor';
|
||||||
import ServerOperator from './server-operator';
|
import ServerOperator from './server-operator';
|
||||||
import Metadata from '../model/metadata';
|
import Metadata from '../model/metadata';
|
||||||
|
import { checkIsDir } from '../utils/row';
|
||||||
|
import { Utils } from '../../utils/utils';
|
||||||
|
import { getFileNameFromRecord } from '../utils/cell';
|
||||||
|
|
||||||
class Store {
|
class Store {
|
||||||
|
|
||||||
@@ -140,13 +143,13 @@ class Store {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const operation = this.pendingOperations.shift();
|
const operation = this.pendingOperations.shift();
|
||||||
this.serverOperator.applyOperation(operation, this.sendOperationCallback.bind(this, undoRedoHandler));
|
this.serverOperator.applyOperation(operation, this.data, this.sendOperationCallback.bind(this, undoRedoHandler));
|
||||||
}
|
}
|
||||||
|
|
||||||
sendOperationCallback = (undoRedoHandler, { operation, error }) => {
|
sendOperationCallback = (undoRedoHandler, { operation, error }) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
operation && operation.fail_callback && operation.fail_callback();
|
|
||||||
this.context.eventBus.dispatch(EVENT_BUS_TYPE.TABLE_ERROR, { error });
|
this.context.eventBus.dispatch(EVENT_BUS_TYPE.TABLE_ERROR, { error });
|
||||||
|
operation && operation.fail_callback && operation.fail_callback(error);
|
||||||
this.sendNextOperation(undoRedoHandler);
|
this.sendNextOperation(undoRedoHandler);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -160,8 +163,8 @@ class Store {
|
|||||||
window.sfMetadataContext.eventBus.dispatch(EVENT_BUS_TYPE.VIEW_CHANGED, this.data.view);
|
window.sfMetadataContext.eventBus.dispatch(EVENT_BUS_TYPE.VIEW_CHANGED, this.data.view);
|
||||||
}
|
}
|
||||||
|
|
||||||
operation.success_callback && operation.success_callback();
|
|
||||||
this.context.eventBus.dispatch(EVENT_BUS_TYPE.SERVER_TABLE_CHANGED);
|
this.context.eventBus.dispatch(EVENT_BUS_TYPE.SERVER_TABLE_CHANGED);
|
||||||
|
operation.success_callback && operation.success_callback();
|
||||||
|
|
||||||
// need reload records if has related formula columns
|
// need reload records if has related formula columns
|
||||||
this.serverOperator.handleReloadRecords(this.data, operation, ({ reloadedRecords, idRecordNotExistMap, relatedColumnKeyMap }) => {
|
this.serverOperator.handleReloadRecords(this.data, operation, ({ reloadedRecords, idRecordNotExistMap, relatedColumnKeyMap }) => {
|
||||||
@@ -230,30 +233,7 @@ class Store {
|
|||||||
DataProcessor.syncOperationOnData(this.data, operation, { collaborators: this.collaborators });
|
DataProcessor.syncOperationOnData(this.data, operation, { collaborators: this.collaborators });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
modifyRecords(row_ids, id_row_updates, id_original_row_updates, id_old_row_data, id_original_old_row_data, is_copy_paste, is_rename, { fail_callback, success_callback }) {
|
||||||
* @param {String} row_id target row id
|
|
||||||
* @param {Object} updates { [column.name]: cell_value }
|
|
||||||
* @param {Object} original_updates { [column.key]: cell_value }
|
|
||||||
* @param {Object} old_row_data { [column.name]: cell_value }
|
|
||||||
* @param {Object} original_old_row_data { [column.key]: cell_value }
|
|
||||||
*/
|
|
||||||
modifyRecord(row_id, updates, old_row_data, original_updates, original_old_row_data) {
|
|
||||||
const row = getRowById(this.data, row_id);
|
|
||||||
if (!row || !this.context.canModifyRow(row)) return;
|
|
||||||
const type = OPERATION_TYPE.MODIFY_RECORD;
|
|
||||||
const operation = this.createOperation({
|
|
||||||
type,
|
|
||||||
repo_id: this.repoId,
|
|
||||||
row_id,
|
|
||||||
updates,
|
|
||||||
old_row_data,
|
|
||||||
original_updates,
|
|
||||||
original_old_row_data,
|
|
||||||
});
|
|
||||||
this.applyOperation(operation);
|
|
||||||
}
|
|
||||||
|
|
||||||
modifyRecords(row_ids, id_row_updates, id_original_row_updates, id_old_row_data, id_original_old_row_data, is_copy_paste) {
|
|
||||||
const originalRows = getRowsByIds(this.data, row_ids);
|
const originalRows = getRowsByIds(this.data, row_ids);
|
||||||
let valid_row_ids = [];
|
let valid_row_ids = [];
|
||||||
let valid_id_row_updates = {};
|
let valid_id_row_updates = {};
|
||||||
@@ -262,18 +242,48 @@ class Store {
|
|||||||
let valid_id_original_old_row_data = {};
|
let valid_id_original_old_row_data = {};
|
||||||
let id_obj_id = {};
|
let id_obj_id = {};
|
||||||
originalRows.forEach(row => {
|
originalRows.forEach(row => {
|
||||||
if (!row || !this.context.canModifyRow(row)) {
|
if (row && this.context.canModifyRow(row)) {
|
||||||
return;
|
const rowId = row._id;
|
||||||
|
valid_row_ids.push(rowId);
|
||||||
|
id_obj_id[rowId] = row._obj_id;
|
||||||
|
valid_id_row_updates[rowId] = id_row_updates[rowId];
|
||||||
|
valid_id_original_row_updates[rowId] = id_original_row_updates[rowId];
|
||||||
|
valid_id_old_row_data[rowId] = id_old_row_data[rowId];
|
||||||
|
valid_id_original_old_row_data[rowId] = id_original_old_row_data[rowId];
|
||||||
}
|
}
|
||||||
const rowId = row._id;
|
|
||||||
valid_row_ids.push(rowId);
|
|
||||||
valid_id_row_updates[rowId] = id_row_updates[rowId];
|
|
||||||
id_obj_id[rowId] = row._obj_id;
|
|
||||||
valid_id_original_row_updates[rowId] = id_original_row_updates[rowId];
|
|
||||||
valid_id_old_row_data[rowId] = id_old_row_data[rowId];
|
|
||||||
valid_id_original_old_row_data[rowId] = id_original_old_row_data[rowId];
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// get updates which the parent dir is changed
|
||||||
|
let oldParentDirPath = null;
|
||||||
|
let newParentDirPath = null;
|
||||||
|
if (is_rename) {
|
||||||
|
const rowId = valid_row_ids[0];
|
||||||
|
const row = getRowById(this.data, rowId);
|
||||||
|
if (row && checkIsDir(row)) {
|
||||||
|
const rowUpdates = id_original_row_updates[rowId];
|
||||||
|
const oldName = getFileNameFromRecord(row);
|
||||||
|
const newName = getFileNameFromRecord(rowUpdates);
|
||||||
|
const { _parent_dir } = row;
|
||||||
|
oldParentDirPath = Utils.joinPath(_parent_dir, oldName);
|
||||||
|
newParentDirPath = Utils.joinPath(_parent_dir, newName);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newParentDirPath) {
|
||||||
|
this.data.rows.forEach((row) => {
|
||||||
|
const { _id: rowId, _parent_dir: currentParentDir } = row;
|
||||||
|
if (currentParentDir.includes(oldParentDirPath) && !valid_row_ids.includes(rowId)) {
|
||||||
|
valid_row_ids.push(rowId);
|
||||||
|
id_obj_id[rowId] = row._obj_id;
|
||||||
|
const updates = { _parent_dir: currentParentDir.replace(oldParentDirPath, newParentDirPath) };
|
||||||
|
valid_id_row_updates[rowId] = Object.assign({}, valid_id_row_updates[rowId], updates);
|
||||||
|
valid_id_original_row_updates[rowId] = Object.assign({}, valid_id_original_row_updates[rowId], updates);
|
||||||
|
valid_id_old_row_data[rowId] = Object.assign({}, valid_id_old_row_data[rowId], { _parent_dir: currentParentDir });
|
||||||
|
valid_id_original_old_row_data[rowId] = Object.assign({}, valid_id_original_old_row_data[rowId], { _parent_dir: currentParentDir });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const type = OPERATION_TYPE.MODIFY_RECORDS;
|
const type = OPERATION_TYPE.MODIFY_RECORDS;
|
||||||
const operation = this.createOperation({
|
const operation = this.createOperation({
|
||||||
type,
|
type,
|
||||||
@@ -284,7 +294,53 @@ class Store {
|
|||||||
id_old_row_data: valid_id_old_row_data,
|
id_old_row_data: valid_id_old_row_data,
|
||||||
id_original_old_row_data: valid_id_original_old_row_data,
|
id_original_old_row_data: valid_id_original_old_row_data,
|
||||||
is_copy_paste,
|
is_copy_paste,
|
||||||
id_obj_id: id_obj_id
|
is_rename,
|
||||||
|
id_obj_id: id_obj_id,
|
||||||
|
fail_callback,
|
||||||
|
success_callback,
|
||||||
|
});
|
||||||
|
this.applyOperation(operation);
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteRecords(rows_ids, { fail_callback, success_callback }) {
|
||||||
|
const type = OPERATION_TYPE.DELETE_RECORDS;
|
||||||
|
|
||||||
|
if (!Array.isArray(rows_ids) || rows_ids.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const valid_rows_ids = Array.isArray(rows_ids) ? rows_ids.filter((rowId) => {
|
||||||
|
const row = getRowById(this.data, rowId);
|
||||||
|
return row && this.context.canModifyRow(row);
|
||||||
|
}) : [];
|
||||||
|
|
||||||
|
|
||||||
|
// delete rows where parent dir is deleted
|
||||||
|
const deletedDirsPaths = rows_ids.map((rowId) => {
|
||||||
|
const row = getRowById(this.data, rowId);
|
||||||
|
if (row && checkIsDir(row)) {
|
||||||
|
const { _parent_dir, _name } = row;
|
||||||
|
return Utils.joinPath(_parent_dir, _name);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}).filter(Boolean);
|
||||||
|
if (deletedDirsPaths.length > 0) {
|
||||||
|
this.data.rows.forEach((row) => {
|
||||||
|
if (deletedDirsPaths.some((deletedDirPath) => row._parent_dir.includes(deletedDirPath)) && !valid_rows_ids.includes(row._id)) {
|
||||||
|
valid_rows_ids.push(row._id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (valid_rows_ids.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const operation = this.createOperation({
|
||||||
|
type,
|
||||||
|
repo_id: this.repoId,
|
||||||
|
rows_ids: valid_rows_ids,
|
||||||
|
fail_callback,
|
||||||
|
success_callback,
|
||||||
});
|
});
|
||||||
this.applyOperation(operation);
|
this.applyOperation(operation);
|
||||||
}
|
}
|
||||||
@@ -443,6 +499,24 @@ class Store {
|
|||||||
this.applyOperation(operation);
|
this.applyOperation(operation);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
checkIsRenameFileOperator = (rows_ids, id_original_row_updates) => {
|
||||||
|
if (rows_ids.length > 1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const rowId = rows_ids[0];
|
||||||
|
const rowUpdates = id_original_row_updates[rowId];
|
||||||
|
const updatedKeys = rowUpdates && Object.keys(rowUpdates);
|
||||||
|
if (!updatedKeys || updatedKeys.length > 1 || updatedKeys[0] !== PRIVATE_COLUMN_KEY.FILE_NAME) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
checkDuplicatedName = (name, parentDir) => {
|
||||||
|
const newPath = Utils.joinPath(parentDir, name);
|
||||||
|
return this.data.rows.some((row) => newPath === Utils.joinPath(row._parent_dir, row._name));
|
||||||
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Store;
|
export default Store;
|
||||||
|
@@ -12,46 +12,42 @@ export default function apply(data, operation) {
|
|||||||
const { op_type } = operation;
|
const { op_type } = operation;
|
||||||
|
|
||||||
switch (op_type) {
|
switch (op_type) {
|
||||||
|
|
||||||
case OPERATION_TYPE.MODIFY_RECORD: {
|
|
||||||
const { row_id, original_updates } = operation;
|
|
||||||
const { rows } = data;
|
|
||||||
const updatedRowIndex = rows.findIndex(row => row_id === row._id);
|
|
||||||
if (updatedRowIndex < 0) {
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
const modifyTime = dayjs().utc().format(UTC_FORMAT_DEFAULT);
|
|
||||||
const modifier = window.sfMetadataContext.getUsername();
|
|
||||||
const updatedRow = Object.assign({},
|
|
||||||
rows[updatedRowIndex],
|
|
||||||
original_updates,
|
|
||||||
{ '_mtime': modifyTime, '_last_modifier': modifier },
|
|
||||||
);
|
|
||||||
data.rows[updatedRowIndex] = updatedRow;
|
|
||||||
data.id_row_map[row_id] = updatedRow;
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
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;
|
const { rows } = data;
|
||||||
const modifyTime = dayjs().utc().format(UTC_FORMAT_DEFAULT);
|
const modifyTime = dayjs().utc().format(UTC_FORMAT_DEFAULT);
|
||||||
const modifier = window.sfMetadataContext.getUsername();
|
const modifier = window.sfMetadataContext.getUsername();
|
||||||
let updatedRows = [...rows];
|
let updatedRows = [...rows];
|
||||||
|
|
||||||
rows.forEach((row, index) => {
|
rows.forEach((row, index) => {
|
||||||
const rowId = row._id;
|
const { _id: rowId } = row;
|
||||||
const originalRowUpdates = id_original_row_updates[rowId];
|
const originalRowUpdates = id_original_row_updates[rowId];
|
||||||
const rowUpdates = id_row_updates[rowId];
|
const rowUpdates = id_row_updates[rowId];
|
||||||
if (!rowUpdates && !originalRowUpdates) return;
|
if (rowUpdates || originalRowUpdates) {
|
||||||
const updatedRow = Object.assign({}, row, rowUpdates, originalRowUpdates, {
|
const updatedRow = Object.assign({}, row, rowUpdates, originalRowUpdates, {
|
||||||
'_mtime': modifyTime,
|
'_mtime': modifyTime,
|
||||||
'_last_modifier': modifier,
|
'_last_modifier': modifier,
|
||||||
});
|
});
|
||||||
updatedRows[index] = updatedRow;
|
updatedRows[index] = updatedRow;
|
||||||
data.id_row_map[rowId] = updatedRow;
|
data.id_row_map[rowId] = updatedRow;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
data.rows = updatedRows;
|
data.rows = updatedRows;
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
case OPERATION_TYPE.DELETE_RECORDS: {
|
||||||
|
const { rows_ids } = operation;
|
||||||
|
const idNeedDeletedMap = rows_ids.reduce((currIdNeedDeletedMap, rowId) => ({ ...currIdNeedDeletedMap, [rowId]: true }), {});
|
||||||
|
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;
|
||||||
|
}
|
||||||
case OPERATION_TYPE.RESTORE_RECORDS: {
|
case OPERATION_TYPE.RESTORE_RECORDS: {
|
||||||
const { original_rows } = operation;
|
const { original_rows } = operation;
|
||||||
const currentTime = dayjs().utc().format(UTC_FORMAT_DEFAULT);
|
const currentTime = dayjs().utc().format(UTC_FORMAT_DEFAULT);
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
export const OPERATION_TYPE = {
|
export const OPERATION_TYPE = {
|
||||||
MODIFY_RECORD: 'modify_record',
|
|
||||||
MODIFY_RECORDS: 'modify_records',
|
MODIFY_RECORDS: 'modify_records',
|
||||||
|
DELETE_RECORDS: 'delete_records',
|
||||||
RESTORE_RECORDS: 'restore_records',
|
RESTORE_RECORDS: 'restore_records',
|
||||||
RELOAD_RECORDS: 'reload_records',
|
RELOAD_RECORDS: 'reload_records',
|
||||||
MODIFY_FILTERS: 'modify_filters',
|
MODIFY_FILTERS: 'modify_filters',
|
||||||
@@ -28,8 +28,8 @@ export const COLUMN_DATA_OPERATION_TYPE = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const OPERATION_ATTRIBUTES = {
|
export const OPERATION_ATTRIBUTES = {
|
||||||
[OPERATION_TYPE.MODIFY_RECORD]: ['repo_id', 'row_id', 'updates', 'old_row_data', 'original_updates', 'original_old_row_data'],
|
[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', 'id_obj_id'],
|
[OPERATION_TYPE.DELETE_RECORDS]: ['repo_id', 'rows_ids'],
|
||||||
[OPERATION_TYPE.RESTORE_RECORDS]: ['repo_id', 'rows_data', 'original_rows', 'link_infos', 'upper_row_ids'],
|
[OPERATION_TYPE.RESTORE_RECORDS]: ['repo_id', 'rows_data', 'original_rows', 'link_infos', 'upper_row_ids'],
|
||||||
[OPERATION_TYPE.RELOAD_RECORDS]: ['repo_id', 'row_ids'],
|
[OPERATION_TYPE.RELOAD_RECORDS]: ['repo_id', 'row_ids'],
|
||||||
[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'],
|
||||||
@@ -47,7 +47,6 @@ export const OPERATION_ATTRIBUTES = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const UNDO_OPERATION_TYPE = [
|
export const UNDO_OPERATION_TYPE = [
|
||||||
// OPERATION_TYPE.MODIFY_RECORD,
|
|
||||||
// OPERATION_TYPE.MODIFY_RECORDS,
|
// OPERATION_TYPE.MODIFY_RECORDS,
|
||||||
// OPERATION_TYPE.RESTORE_RECORDS,
|
// OPERATION_TYPE.RESTORE_RECORDS,
|
||||||
// OPERATION_TYPE.INSERT_COLUMN,
|
// OPERATION_TYPE.INSERT_COLUMN,
|
||||||
@@ -60,8 +59,8 @@ export const LOCAL_APPLY_OPERATION_TYPE = [
|
|||||||
|
|
||||||
// apply operation after exec operation on the server
|
// apply operation after exec operation on the server
|
||||||
export const NEED_APPLY_AFTER_SERVER_OPERATION = [
|
export const NEED_APPLY_AFTER_SERVER_OPERATION = [
|
||||||
OPERATION_TYPE.MODIFY_RECORD,
|
|
||||||
OPERATION_TYPE.MODIFY_RECORDS,
|
OPERATION_TYPE.MODIFY_RECORDS,
|
||||||
|
OPERATION_TYPE.DELETE_RECORDS,
|
||||||
OPERATION_TYPE.MODIFY_FILTERS,
|
OPERATION_TYPE.MODIFY_FILTERS,
|
||||||
OPERATION_TYPE.MODIFY_SORTS,
|
OPERATION_TYPE.MODIFY_SORTS,
|
||||||
OPERATION_TYPE.MODIFY_GROUPBYS,
|
OPERATION_TYPE.MODIFY_GROUPBYS,
|
||||||
|
@@ -9,18 +9,6 @@ function createOperation(op) {
|
|||||||
export default function invert(operation) {
|
export default function invert(operation) {
|
||||||
const { op_type } = operation.clone();
|
const { op_type } = operation.clone();
|
||||||
switch (op_type) {
|
switch (op_type) {
|
||||||
case OPERATION_TYPE.MODIFY_RECORD: {
|
|
||||||
const { page_id, row_id, updates, old_row_data, original_updates, original_old_row_data } = operation;
|
|
||||||
return createOperation({
|
|
||||||
type: OPERATION_TYPE.MODIFY_RECORD,
|
|
||||||
page_id,
|
|
||||||
row_id,
|
|
||||||
updates: deepCopy(old_row_data),
|
|
||||||
old_row_data: deepCopy(updates),
|
|
||||||
original_updates: deepCopy(original_old_row_data),
|
|
||||||
original_old_row_data: deepCopy(original_updates),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
case OPERATION_TYPE.MODIFY_RECORDS: {
|
case OPERATION_TYPE.MODIFY_RECORDS: {
|
||||||
const {
|
const {
|
||||||
page_id, is_copy_paste, row_ids, id_row_updates, id_original_row_updates,
|
page_id, is_copy_paste, row_ids, id_row_updates, id_original_row_updates,
|
||||||
|
@@ -1,27 +1,36 @@
|
|||||||
|
import { seafileAPI } from '../../utils/seafile-api';
|
||||||
|
import { gettext } from '../../utils/constants';
|
||||||
|
import { Utils } from '../../utils/utils';
|
||||||
import { OPERATION_TYPE } from './operations';
|
import { OPERATION_TYPE } from './operations';
|
||||||
import { getColumnByKey } from '../utils/column';
|
import { getColumnByKey } from '../utils/column';
|
||||||
import { gettext } from '../../utils/constants';
|
import { getRowById } from '../utils/table';
|
||||||
|
import { checkIsDir } from '../utils/row';
|
||||||
|
import { getFileNameFromRecord } from '../utils/cell';
|
||||||
|
|
||||||
const MAX_LOAD_RECORDS = 100;
|
const MAX_LOAD_RECORDS = 100;
|
||||||
|
|
||||||
class ServerOperator {
|
class ServerOperator {
|
||||||
|
|
||||||
applyOperation(operation, callback) {
|
applyOperation(operation, data, callback) {
|
||||||
const { op_type } = operation;
|
const { op_type } = operation;
|
||||||
|
|
||||||
switch (op_type) {
|
switch (op_type) {
|
||||||
case OPERATION_TYPE.MODIFY_RECORD: {
|
|
||||||
const { repo_id, row_id, updates } = operation;
|
|
||||||
const recordsData = [{ record_id: row_id, record: updates }];
|
|
||||||
window.sfMetadataContext.modifyRecords(repo_id, recordsData).then(res => {
|
|
||||||
callback({ operation });
|
|
||||||
}).catch(error => {
|
|
||||||
callback({ error: gettext('Failed to modify record') });
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case OPERATION_TYPE.MODIFY_RECORDS: {
|
case OPERATION_TYPE.MODIFY_RECORDS: {
|
||||||
const { repo_id, row_ids, id_row_updates, is_copy_paste, id_obj_id } = operation;
|
const { repo_id, row_ids, id_row_updates, id_original_row_updates, is_copy_paste, is_rename, id_obj_id } = operation;
|
||||||
|
if (is_rename) {
|
||||||
|
const rowId = row_ids[0];
|
||||||
|
const rowUpdates = id_original_row_updates[rowId];
|
||||||
|
const newName = getFileNameFromRecord(rowUpdates);
|
||||||
|
this.renameFile(newName, repo_id, rowId, data, {
|
||||||
|
fail_callback: (error) => {
|
||||||
|
callback({ error });
|
||||||
|
},
|
||||||
|
success_callback: () => {
|
||||||
|
callback({ operation });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
const recordsData = row_ids.map(rowId => {
|
const recordsData = row_ids.map(rowId => {
|
||||||
return { record_id: rowId, record: id_row_updates[rowId], obj_id: id_obj_id[rowId] };
|
return { record_id: rowId, record: id_row_updates[rowId], obj_id: id_obj_id[rowId] };
|
||||||
});
|
});
|
||||||
@@ -32,6 +41,23 @@ class ServerOperator {
|
|||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case OPERATION_TYPE.DELETE_RECORDS: {
|
||||||
|
const { repo_id, rows_ids } = operation;
|
||||||
|
const file_names = rows_ids.map((rowId) => {
|
||||||
|
const row = getRowById(data, rowId);
|
||||||
|
const { _parent_dir, _name } = row || {};
|
||||||
|
if (_parent_dir && _name) {
|
||||||
|
return Utils.joinPath(_parent_dir, _name);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}).filter(Boolean);
|
||||||
|
window.sfMetadataContext.batchDeleteFiles(repo_id, file_names).then(res => {
|
||||||
|
callback({ operation });
|
||||||
|
}).catch(error => {
|
||||||
|
callback({ error: gettext('Failed to delete records') });
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
case OPERATION_TYPE.RESTORE_RECORDS: {
|
case OPERATION_TYPE.RESTORE_RECORDS: {
|
||||||
const { repo_id, rows_data } = operation;
|
const { repo_id, rows_data } = operation;
|
||||||
if (!Array.isArray(rows_data) || rows_data.length === 0) {
|
if (!Array.isArray(rows_data) || rows_data.length === 0) {
|
||||||
@@ -307,6 +333,46 @@ class ServerOperator {
|
|||||||
return keys;
|
return keys;
|
||||||
}, []);
|
}, []);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renameFile = (newName, repo_id, rowId, data, { fail_callback, success_callback }) => {
|
||||||
|
const row = getRowById(data, rowId);
|
||||||
|
if (!row) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { _parent_dir, _name } = row;
|
||||||
|
const path = Utils.joinPath(_parent_dir, _name);
|
||||||
|
|
||||||
|
// rename folder
|
||||||
|
if (checkIsDir(row)) {
|
||||||
|
seafileAPI.renameDir(repo_id, path, newName).then(() => {
|
||||||
|
success_callback();
|
||||||
|
}).catch((error) => {
|
||||||
|
let errMessage = Utils.getErrorMsg(error);
|
||||||
|
if (errMessage === gettext('Error')) {
|
||||||
|
errMessage = gettext('Renaming {name} failed').replace('{name}', _name);
|
||||||
|
}
|
||||||
|
fail_callback(errMessage);
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// rename file
|
||||||
|
seafileAPI.renameFile(repo_id, path, newName).then(() => {
|
||||||
|
success_callback();
|
||||||
|
}).catch((error) => {
|
||||||
|
let errMessage = '';
|
||||||
|
if (error && error.response.status == 403 && error.response.data && error.response.data['error_msg']) {
|
||||||
|
errMessage = error.response.data['error_msg'];
|
||||||
|
} else {
|
||||||
|
errMessage = Utils.getErrorMsg(error);
|
||||||
|
}
|
||||||
|
if (errMessage === gettext('Error')) {
|
||||||
|
errMessage = gettext('Renaming {name} failed').replace('{name}', _name);
|
||||||
|
}
|
||||||
|
fail_callback(errMessage);
|
||||||
|
});
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ServerOperator;
|
export default ServerOperator;
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { PRIVATE_COLUMN_KEYS } from '../../constants';
|
import { PRIVATE_COLUMN_KEY, PRIVATE_COLUMN_KEYS } from '../../constants';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {any} value
|
* @param {any} value
|
||||||
@@ -23,3 +23,11 @@ export const getCellValueByColumn = (record, column) => {
|
|||||||
if (PRIVATE_COLUMN_KEYS.includes(key)) return record[key];
|
if (PRIVATE_COLUMN_KEYS.includes(key)) return record[key];
|
||||||
return record[name];
|
return record[name];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getParentDirFromRecord = (record) => {
|
||||||
|
return record ? record[PRIVATE_COLUMN_KEY.PARENT_DIR] : '';
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getFileNameFromRecord = (record) => {
|
||||||
|
return record ? record[PRIVATE_COLUMN_KEY.FILE_NAME] : '';
|
||||||
|
};
|
||||||
|
@@ -1,6 +1,8 @@
|
|||||||
export {
|
export {
|
||||||
isValidCellValue,
|
isValidCellValue,
|
||||||
getCellValueByColumn,
|
getCellValueByColumn,
|
||||||
|
getParentDirFromRecord,
|
||||||
|
getFileNameFromRecord,
|
||||||
} from './core';
|
} from './core';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
|
import { PRIVATE_COLUMN_KEY } from '../../constants';
|
||||||
import { getTableById } from '../table';
|
import { getTableById } from '../table';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -25,6 +26,14 @@ const updateTableRowsWithRowsData = (tables, tableId, recordsData = []) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const checkIsDir = (record) => {
|
||||||
|
const isDir = record[PRIVATE_COLUMN_KEY.IS_DIR];
|
||||||
|
if (typeof isDir === 'string') {
|
||||||
|
return isDir.toUpperCase() === 'TRUE';
|
||||||
|
}
|
||||||
|
return isDir;
|
||||||
|
};
|
||||||
|
|
||||||
export {
|
export {
|
||||||
isTableRows,
|
isTableRows,
|
||||||
updateTableRowsWithRowsData,
|
updateTableRowsWithRowsData,
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
export {
|
export {
|
||||||
|
checkIsDir,
|
||||||
isTableRows,
|
isTableRows,
|
||||||
updateTableRowsWithRowsData,
|
updateTableRowsWithRowsData,
|
||||||
} from './core';
|
} from './core';
|
||||||
|
@@ -10,7 +10,7 @@ import ZipDownloadDialog from '../../../components/dialog/zip-download-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 } from '../../utils/cell';
|
import { getDateDisplayString, getFileNameFromRecord, getParentDirFromRecord } from '../../utils/cell';
|
||||||
import { siteRoot, fileServerRoot, useGoFileserver, gettext, thumbnailSizeForGrid, thumbnailSizeForOriginal } from '../../../utils/constants';
|
import { siteRoot, fileServerRoot, useGoFileserver, gettext, thumbnailSizeForGrid, thumbnailSizeForOriginal } from '../../../utils/constants';
|
||||||
import { EVENT_BUS_TYPE, PER_LOAD_NUMBER, PRIVATE_COLUMN_KEY, GALLERY_DATE_MODE, DATE_TAG_HEIGHT, GALLERY_IMAGE_GAP } from '../../constants';
|
import { EVENT_BUS_TYPE, PER_LOAD_NUMBER, PRIVATE_COLUMN_KEY, GALLERY_DATE_MODE, DATE_TAG_HEIGHT, GALLERY_IMAGE_GAP } from '../../constants';
|
||||||
|
|
||||||
@@ -59,11 +59,11 @@ const Gallery = () => {
|
|||||||
const groups = useMemo(() => {
|
const groups = useMemo(() => {
|
||||||
if (isFirstLoading) return [];
|
if (isFirstLoading) return [];
|
||||||
const firstSort = metadata.view.sorts[0];
|
const firstSort = metadata.view.sorts[0];
|
||||||
let init = metadata.rows.filter(row => Utils.imageCheck(row[PRIVATE_COLUMN_KEY.FILE_NAME]))
|
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 = record[PRIVATE_COLUMN_KEY.ID];
|
||||||
const fileName = record[PRIVATE_COLUMN_KEY.FILE_NAME];
|
const fileName = getFileNameFromRecord(record);
|
||||||
const parentDir = record[PRIVATE_COLUMN_KEY.PARENT_DIR];
|
const parentDir = getParentDirFromRecord(record);
|
||||||
const path = Utils.encodePath(Utils.joinPath(parentDir, fileName));
|
const path = Utils.encodePath(Utils.joinPath(parentDir, fileName));
|
||||||
const date = mode !== GALLERY_DATE_MODE.ALL ? getDateDisplayString(record[firstSort.column_key], dateMode) : '';
|
const date = mode !== GALLERY_DATE_MODE.ALL ? getDateDisplayString(record[firstSort.column_key], dateMode) : '';
|
||||||
const img = {
|
const img = {
|
||||||
@@ -279,7 +279,7 @@ const Gallery = () => {
|
|||||||
|
|
||||||
const handleDelete = () => {
|
const handleDelete = () => {
|
||||||
if (selectedImages.length) {
|
if (selectedImages.length) {
|
||||||
metadataAPI.deleteImages(repoID, selectedImages.map(image => image.path === '/' ? image.name : `${image.path}/${image.name}`))
|
metadataAPI.batchDeleteFiles(repoID, selectedImages.map(image => image.path === '/' ? image.name : `${image.path}/${image.name}`))
|
||||||
.then(() => {
|
.then(() => {
|
||||||
setSelectedImages([]);
|
setSelectedImages([]);
|
||||||
let msg = selectedImages.length > 1
|
let msg = selectedImages.length > 1
|
||||||
|
@@ -1,12 +1,14 @@
|
|||||||
import React, { useState, useRef, useEffect, useCallback, useMemo } from 'react';
|
import React, { useState, useRef, useEffect, useCallback, useMemo } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import toaster from '../../../../components/toast';
|
import toaster from '../../../../components/toast';
|
||||||
import { getColumnByKey } from '../../../utils/column';
|
|
||||||
import { gettext, siteRoot } from '../../../../utils/constants';
|
import { gettext, siteRoot } from '../../../../utils/constants';
|
||||||
import { Utils } from '../../../../utils/utils';
|
import { Utils } from '../../../../utils/utils';
|
||||||
import { useMetadataView } from '../../../hooks/metadata-view';
|
import { useMetadataView } from '../../../hooks/metadata-view';
|
||||||
import { PRIVATE_COLUMN_KEY } from '../../../constants';
|
import { getColumnByKey, isNameColumn } from '../../../utils/column';
|
||||||
import { VIEW_TYPE } from '../../../constants/view';
|
import { checkIsDir } from '../../../utils/row';
|
||||||
|
import { EVENT_BUS_TYPE, EVENT_BUS_TYPE as METADATA_EVENT_BUS_TYPE, PRIVATE_COLUMN_KEY } from '../../../constants';
|
||||||
|
import { getFileNameFromRecord, getParentDirFromRecord } from '../../../utils/cell';
|
||||||
|
|
||||||
import './index.css';
|
import './index.css';
|
||||||
|
|
||||||
const OPERATION = {
|
const OPERATION = {
|
||||||
@@ -16,105 +18,144 @@ const OPERATION = {
|
|||||||
OPEN_IN_NEW_TAB: 'open-new-tab',
|
OPEN_IN_NEW_TAB: 'open-new-tab',
|
||||||
GENERATE_DESCRIPTION: 'generate-description',
|
GENERATE_DESCRIPTION: 'generate-description',
|
||||||
IMAGE_CAPTION: 'image-caption',
|
IMAGE_CAPTION: 'image-caption',
|
||||||
DOWNLOAD: 'download',
|
DELETE_RECORD: 'delete-record',
|
||||||
DELETE: 'delete',
|
DELETE_RECORDS: 'delete-records',
|
||||||
|
RENAME_FILE: 'rename-file',
|
||||||
};
|
};
|
||||||
|
|
||||||
const ContextMenu = ({
|
const ContextMenu = (props) => {
|
||||||
isGroupView,
|
const {
|
||||||
selectedRange,
|
isGroupView, selectedRange, selectedPosition, recordMetrics, recordGetterByIndex, onClearSelected, onCopySelected, updateRecords,
|
||||||
selectedPosition,
|
getTableContentRect, getTableCanvasContainerRect, deleteRecords, toggleDeleteFolderDialog, selectNone,
|
||||||
recordMetrics,
|
} = props;
|
||||||
recordGetterByIndex,
|
|
||||||
onClearSelected,
|
|
||||||
onCopySelected,
|
|
||||||
updateRecords,
|
|
||||||
getTableContentRect,
|
|
||||||
getTableCanvasContainerRect,
|
|
||||||
onDownload,
|
|
||||||
onDelete,
|
|
||||||
}) => {
|
|
||||||
const menuRef = useRef(null);
|
const menuRef = 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 { metadata } = useMetadataView();
|
const { metadata } = useMetadataView();
|
||||||
|
|
||||||
|
const checkCanModifyRow = (row) => {
|
||||||
|
return window.sfMetadataContext.canModifyRow(row);
|
||||||
|
};
|
||||||
|
|
||||||
|
const checkIsDescribableDoc = useCallback((record) => {
|
||||||
|
const fileName = getFileNameFromRecord(record);
|
||||||
|
return checkCanModifyRow(record) && Utils.isDescriptionSupportedFile(fileName);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const getAbleDeleteRecords = useCallback((records) => {
|
||||||
|
return records.filter(record => window.sfMetadataContext.checkCanDeleteRow(record));
|
||||||
|
}, []);
|
||||||
|
|
||||||
const options = useMemo(() => {
|
const options = useMemo(() => {
|
||||||
if (!visible) return [];
|
if (!visible) return [];
|
||||||
const permission = window.sfMetadataContext.getPermission();
|
const permission = window.sfMetadataContext.getPermission();
|
||||||
const isReadonly = permission === 'r';
|
const isReadonly = permission === 'r';
|
||||||
const { columns } = metadata;
|
const { columns } = metadata;
|
||||||
const descriptionColumn = getColumnByKey(columns, PRIVATE_COLUMN_KEY.FILE_DESCRIPTION);
|
const descriptionColumn = getColumnByKey(columns, PRIVATE_COLUMN_KEY.FILE_DESCRIPTION);
|
||||||
const canModifyRow = window.sfMetadataContext.canModifyRow;
|
|
||||||
let list = [];
|
let list = [];
|
||||||
|
|
||||||
if (metadata.view.type === VIEW_TYPE.GALLERY) {
|
// handle selected multiple cells
|
||||||
list.push({ value: OPERATION.DOWNLOAD, label: gettext('Download') });
|
|
||||||
list.push({ value: OPERATION.DELETE, label: gettext('Delete') });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (selectedRange) {
|
if (selectedRange) {
|
||||||
!isReadonly && list.push({ value: OPERATION.CLEAR_SELECTED, label: gettext('Clear selected') });
|
!isReadonly && list.push({ value: OPERATION.CLEAR_SELECTED, label: gettext('Clear selected') });
|
||||||
list.push({ value: OPERATION.COPY_SELECTED, label: gettext('Copy selected') });
|
list.push({ value: OPERATION.COPY_SELECTED, label: gettext('Copy selected') });
|
||||||
|
|
||||||
|
const { topLeft, bottomRight } = selectedRange;
|
||||||
|
let records = [];
|
||||||
|
for (let i = topLeft.rowIdx; i <= bottomRight.rowIdx; i++) {
|
||||||
|
const record = recordGetterByIndex({ isGroupView, groupRecordIndex: topLeft.groupRecordIndex, recordIndex: i });
|
||||||
|
if (record) {
|
||||||
|
records.push(record);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const ableDeleteRecords = getAbleDeleteRecords(records);
|
||||||
|
if (ableDeleteRecords.length > 0) {
|
||||||
|
list.push({ value: OPERATION.DELETE_RECORDS, label: gettext('Delete'), records: ableDeleteRecords });
|
||||||
|
}
|
||||||
|
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
const selectedRecords = recordMetrics ? Object.keys(recordMetrics.idSelectedRecordMap) : [];
|
// handle selected records
|
||||||
if (selectedRecords.length > 1) {
|
const selectedRecordsIds = recordMetrics ? Object.keys(recordMetrics.idSelectedRecordMap) : [];
|
||||||
|
if (selectedRecordsIds.length > 1) {
|
||||||
|
let records = [];
|
||||||
|
selectedRecordsIds.forEach(id => {
|
||||||
|
const record = metadata.id_row_map[id];
|
||||||
|
if (record) {
|
||||||
|
records.push(record);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const ableDeleteRecords = getAbleDeleteRecords(records);
|
||||||
|
if (ableDeleteRecords.length > 0) {
|
||||||
|
list.push({ value: OPERATION.DELETE_RECORDS, label: gettext('Delete'), records: ableDeleteRecords });
|
||||||
|
}
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// handle selected cell
|
||||||
if (!selectedPosition) return list;
|
if (!selectedPosition) return list;
|
||||||
const { groupRecordIndex, rowIdx: recordIndex } = selectedPosition;
|
const { groupRecordIndex, rowIdx: recordIndex, idx } = selectedPosition;
|
||||||
|
const column = columns[idx];
|
||||||
const record = recordGetterByIndex({ isGroupView, groupRecordIndex, recordIndex });
|
const record = recordGetterByIndex({ isGroupView, groupRecordIndex, recordIndex });
|
||||||
if (!record) return list;
|
if (!record) return list;
|
||||||
const isFolder = record[PRIVATE_COLUMN_KEY.IS_DIR];
|
|
||||||
list.push({ value: OPERATION.OPEN_IN_NEW_TAB, label: isFolder ? gettext('Open folder in new tab') : gettext('Open file in new tab') });
|
const canModifyRow = checkCanModifyRow(record);
|
||||||
list.push({ value: OPERATION.OPEN_PARENT_FOLDER, label: gettext('Open parent folder') });
|
const canDeleteRow = window.sfMetadataContext.checkCanDeleteRow(record);
|
||||||
|
const isFolder = checkIsDir(record);
|
||||||
|
list.push({ value: OPERATION.OPEN_IN_NEW_TAB, label: isFolder ? gettext('Open folder in new tab') : gettext('Open file in new tab'), record });
|
||||||
|
list.push({ value: OPERATION.OPEN_PARENT_FOLDER, label: gettext('Open parent folder'), record });
|
||||||
|
|
||||||
if (descriptionColumn) {
|
if (descriptionColumn) {
|
||||||
const fileName = record[PRIVATE_COLUMN_KEY.FILE_NAME];
|
if (checkIsDescribableDoc(record)) {
|
||||||
if (Utils.isDescriptionSupportedFile(fileName) && canModifyRow(record)) {
|
list.push({ value: OPERATION.GENERATE_DESCRIPTION, label: gettext('Generate description'), record });
|
||||||
list.push({ value: OPERATION.GENERATE_DESCRIPTION, label: gettext('Generate description') });
|
} else if (canModifyRow && Utils.imageCheck(getFileNameFromRecord(record))) {
|
||||||
} else if (Utils.imageCheck(fileName) && canModifyRow(record)) {
|
list.push({ value: OPERATION.IMAGE_CAPTION, label: gettext('Generate image description'), record });
|
||||||
list.push({ value: OPERATION.IMAGE_CAPTION, label: gettext('Generate image description') });
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// handle delete folder/file
|
||||||
|
if (canDeleteRow) {
|
||||||
|
list.push({ value: OPERATION.DELETE_RECORD, label: gettext('Delete'), record });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (canModifyRow && column && isNameColumn(column)) {
|
||||||
|
list.push({ value: OPERATION.RENAME_FILE, label: gettext('Rename'), record });
|
||||||
|
}
|
||||||
|
|
||||||
return list;
|
return list;
|
||||||
}, [visible, isGroupView, selectedPosition, recordMetrics, selectedRange, metadata, recordGetterByIndex]);
|
}, [visible, isGroupView, selectedPosition, recordMetrics, selectedRange, metadata, recordGetterByIndex, checkIsDescribableDoc, getAbleDeleteRecords]);
|
||||||
|
|
||||||
const handleHide = useCallback((event) => {
|
const handleHide = useCallback((event) => {
|
||||||
|
if (!menuRef.current && visible) {
|
||||||
|
setVisible(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (menuRef.current && !menuRef.current.contains(event.target)) {
|
if (menuRef.current && !menuRef.current.contains(event.target)) {
|
||||||
setVisible(false);
|
setVisible(false);
|
||||||
}
|
}
|
||||||
}, [menuRef]);
|
}, [menuRef, visible]);
|
||||||
|
|
||||||
const onOpenFileInNewTab = useCallback(() => {
|
const onOpenFileInNewTab = useCallback((record) => {
|
||||||
const { groupRecordIndex, rowIdx } = selectedPosition;
|
|
||||||
const record = recordGetterByIndex({ isGroupView, groupRecordIndex, recordIndex: rowIdx });
|
|
||||||
if (!record) return;
|
|
||||||
const repoID = window.sfMetadataStore.repoId;
|
const repoID = window.sfMetadataStore.repoId;
|
||||||
const isFolder = record[PRIVATE_COLUMN_KEY.IS_DIR];
|
const isFolder = checkIsDir(record);
|
||||||
const parentDir = record[PRIVATE_COLUMN_KEY.PARENT_DIR];
|
const parentDir = getParentDirFromRecord(record);
|
||||||
const fileName = record[PRIVATE_COLUMN_KEY.FILE_NAME];
|
const fileName = getFileNameFromRecord(record);
|
||||||
|
|
||||||
let url;
|
const url = isFolder ?
|
||||||
if (isFolder) {
|
window.location.origin + window.location.pathname + Utils.encodePath(Utils.joinPath(parentDir, fileName)) :
|
||||||
url = window.location.origin + window.location.pathname + Utils.encodePath(Utils.joinPath(parentDir, fileName));
|
`${siteRoot}lib/${repoID}/file${Utils.encodePath(Utils.joinPath(parentDir, fileName))}`;
|
||||||
} else {
|
|
||||||
url = `${siteRoot}lib/${repoID}/file${Utils.encodePath(Utils.joinPath(parentDir, fileName))}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
window.open(url, '_blank');
|
window.open(url, '_blank');
|
||||||
}, [isGroupView, recordGetterByIndex, selectedPosition]);
|
}, []);
|
||||||
|
|
||||||
const onOpenParentFolder = useCallback((event) => {
|
const onOpenParentFolder = useCallback((event, record) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
const { groupRecordIndex, rowIdx } = selectedPosition;
|
let parentDir = getParentDirFromRecord(record);
|
||||||
const record = recordGetterByIndex({ isGroupView, groupRecordIndex, recordIndex: rowIdx });
|
|
||||||
if (!record) return;
|
|
||||||
let parentDir = record[PRIVATE_COLUMN_KEY.PARENT_DIR];
|
|
||||||
|
|
||||||
if (window.location.pathname.endsWith('/')) {
|
if (window.location.pathname.endsWith('/')) {
|
||||||
parentDir = parentDir.slice(1);
|
parentDir = parentDir.slice(1);
|
||||||
@@ -122,19 +163,16 @@ const ContextMenu = ({
|
|||||||
|
|
||||||
const url = window.location.origin + window.location.pathname + Utils.encodePath(parentDir);
|
const url = window.location.origin + window.location.pathname + Utils.encodePath(parentDir);
|
||||||
window.open(url, '_blank');
|
window.open(url, '_blank');
|
||||||
}, [isGroupView, recordGetterByIndex, selectedPosition]);
|
}, []);
|
||||||
|
|
||||||
const generateDescription = useCallback(() => {
|
const generateDescription = useCallback((record) => {
|
||||||
const canModifyRow = window.sfMetadataContext.canModifyRow;
|
|
||||||
const descriptionColumnKey = PRIVATE_COLUMN_KEY.FILE_DESCRIPTION;
|
const descriptionColumnKey = PRIVATE_COLUMN_KEY.FILE_DESCRIPTION;
|
||||||
let path = '';
|
let path = '';
|
||||||
let idOldRecordData = {};
|
let idOldRecordData = {};
|
||||||
let idOriginalOldRecordData = {};
|
let idOriginalOldRecordData = {};
|
||||||
const { groupRecordIndex, rowIdx } = selectedPosition;
|
const fileName = getFileNameFromRecord(record);
|
||||||
const record = recordGetterByIndex({ isGroupView, groupRecordIndex, recordIndex: rowIdx });
|
if (Utils.isDescriptionSupportedFile(fileName) && checkCanModifyRow(record)) {
|
||||||
const fileName = record[PRIVATE_COLUMN_KEY.FILE_NAME];
|
const parentDir = getParentDirFromRecord(record);
|
||||||
if (Utils.isDescriptionSupportedFile(fileName) && canModifyRow(record)) {
|
|
||||||
const parentDir = record[PRIVATE_COLUMN_KEY.PARENT_DIR];
|
|
||||||
path = Utils.joinPath(parentDir, fileName);
|
path = Utils.joinPath(parentDir, fileName);
|
||||||
idOldRecordData[record[PRIVATE_COLUMN_KEY.ID]] = { [descriptionColumnKey]: record[descriptionColumnKey] };
|
idOldRecordData[record[PRIVATE_COLUMN_KEY.ID]] = { [descriptionColumnKey]: record[descriptionColumnKey] };
|
||||||
idOriginalOldRecordData[record[PRIVATE_COLUMN_KEY.ID]] = { [descriptionColumnKey]: record[descriptionColumnKey] };
|
idOriginalOldRecordData[record[PRIVATE_COLUMN_KEY.ID]] = { [descriptionColumnKey]: record[descriptionColumnKey] };
|
||||||
@@ -153,19 +191,16 @@ const ContextMenu = ({
|
|||||||
const errorMessage = gettext('Failed to generate description');
|
const errorMessage = gettext('Failed to generate description');
|
||||||
toaster.danger(errorMessage);
|
toaster.danger(errorMessage);
|
||||||
});
|
});
|
||||||
}, [isGroupView, selectedPosition, recordGetterByIndex, updateRecords]);
|
}, [updateRecords]);
|
||||||
|
|
||||||
const imageCaption = useCallback(() => {
|
const imageCaption = useCallback((record) => {
|
||||||
const canModifyRow = window.sfMetadataContext.canModifyRow;
|
|
||||||
const summaryColumnKey = PRIVATE_COLUMN_KEY.FILE_DESCRIPTION;
|
const summaryColumnKey = PRIVATE_COLUMN_KEY.FILE_DESCRIPTION;
|
||||||
let path = '';
|
let path = '';
|
||||||
let idOldRecordData = {};
|
let idOldRecordData = {};
|
||||||
let idOriginalOldRecordData = {};
|
let idOriginalOldRecordData = {};
|
||||||
const { groupRecordIndex, rowIdx } = selectedPosition;
|
const fileName = getFileNameFromRecord(record);
|
||||||
const record = recordGetterByIndex({ isGroupView, groupRecordIndex, recordIndex: rowIdx });
|
if (Utils.imageCheck(fileName) && checkCanModifyRow(record)) {
|
||||||
const fileName = record[PRIVATE_COLUMN_KEY.FILE_NAME];
|
const parentDir = getParentDirFromRecord(record);
|
||||||
if (Utils.imageCheck(fileName) && canModifyRow(record)) {
|
|
||||||
const parentDir = record[PRIVATE_COLUMN_KEY.PARENT_DIR];
|
|
||||||
path = Utils.joinPath(parentDir, fileName);
|
path = Utils.joinPath(parentDir, fileName);
|
||||||
idOldRecordData[record[PRIVATE_COLUMN_KEY.ID]] = { [summaryColumnKey]: record[summaryColumnKey] };
|
idOldRecordData[record[PRIVATE_COLUMN_KEY.ID]] = { [summaryColumnKey]: record[summaryColumnKey] };
|
||||||
idOriginalOldRecordData[record[PRIVATE_COLUMN_KEY.ID]] = { [summaryColumnKey]: record[summaryColumnKey] };
|
idOriginalOldRecordData[record[PRIVATE_COLUMN_KEY.ID]] = { [summaryColumnKey]: record[summaryColumnKey] };
|
||||||
@@ -184,17 +219,21 @@ const ContextMenu = ({
|
|||||||
const errorMessage = gettext('Failed to generate image description');
|
const errorMessage = gettext('Failed to generate image description');
|
||||||
toaster.danger(errorMessage);
|
toaster.danger(errorMessage);
|
||||||
});
|
});
|
||||||
}, [isGroupView, selectedPosition, recordGetterByIndex, updateRecords]);
|
}, [updateRecords]);
|
||||||
|
|
||||||
const handleOptionClick = useCallback((event, option) => {
|
const handleOptionClick = useCallback((event, option) => {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
switch (option.value) {
|
switch (option.value) {
|
||||||
case OPERATION.OPEN_IN_NEW_TAB: {
|
case OPERATION.OPEN_IN_NEW_TAB: {
|
||||||
onOpenFileInNewTab();
|
const { record } = option;
|
||||||
|
if (!record) break;
|
||||||
|
onOpenFileInNewTab(record);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case OPERATION.OPEN_PARENT_FOLDER: {
|
case OPERATION.OPEN_PARENT_FOLDER: {
|
||||||
onOpenParentFolder(event);
|
const { record } = option;
|
||||||
|
if (!record) break;
|
||||||
|
onOpenParentFolder(event, record);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case OPERATION.COPY_SELECTED: {
|
case OPERATION.COPY_SELECTED: {
|
||||||
@@ -206,19 +245,43 @@ const ContextMenu = ({
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case OPERATION.GENERATE_DESCRIPTION: {
|
case OPERATION.GENERATE_DESCRIPTION: {
|
||||||
generateDescription && generateDescription();
|
const { record } = option;
|
||||||
|
if (!record) break;
|
||||||
|
generateDescription(record);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case OPERATION.IMAGE_CAPTION: {
|
case OPERATION.IMAGE_CAPTION: {
|
||||||
imageCaption && imageCaption();
|
const { record } = option;
|
||||||
|
if (!record) break;
|
||||||
|
imageCaption(record);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case OPERATION.DOWNLOAD: {
|
case OPERATION.DELETE_RECORD: {
|
||||||
onDownload && onDownload();
|
const { record } = option;
|
||||||
|
if (!record || !record._id || !deleteRecords) break;
|
||||||
|
if (checkIsDir(record)) {
|
||||||
|
toggleDeleteFolderDialog(record);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
deleteRecords([record._id]);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case OPERATION.DELETE: {
|
case OPERATION.DELETE_RECORDS: {
|
||||||
onDelete && onDelete();
|
window.sfMetadataContext.eventBus.dispatch(EVENT_BUS_TYPE.SELECT_NONE);
|
||||||
|
selectNone && selectNone();
|
||||||
|
|
||||||
|
const { records } = option;
|
||||||
|
const recordsIds = Array.isArray(records) ? records.map((record) => record._id).filter(Boolean) : [];
|
||||||
|
if (recordsIds.length === 0 || !deleteRecords) break;
|
||||||
|
deleteRecords(recordsIds);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case OPERATION.RENAME_FILE: {
|
||||||
|
const { record } = option;
|
||||||
|
if (!record || !record._id) break;
|
||||||
|
|
||||||
|
// rename file via FileNameEditor
|
||||||
|
window.sfMetadataContext.eventBus.dispatch(METADATA_EVENT_BUS_TYPE.OPEN_EDITOR);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default: {
|
default: {
|
||||||
@@ -226,7 +289,7 @@ const ContextMenu = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
setVisible(false);
|
setVisible(false);
|
||||||
}, [onOpenFileInNewTab, onOpenParentFolder, onCopySelected, onClearSelected, generateDescription, imageCaption, onDownload, onDelete]);
|
}, [onOpenFileInNewTab, onOpenParentFolder, onCopySelected, onClearSelected, generateDescription, imageCaption, selectNone, deleteRecords, toggleDeleteFolderDialog]);
|
||||||
|
|
||||||
const getMenuPosition = useCallback((x = 0, y = 0) => {
|
const getMenuPosition = useCallback((x = 0, y = 0) => {
|
||||||
let menuStyles = {
|
let menuStyles = {
|
||||||
@@ -272,7 +335,8 @@ const ContextMenu = ({
|
|||||||
return () => {
|
return () => {
|
||||||
document.removeEventListener('contextmenu', handleShow);
|
document.removeEventListener('contextmenu', handleShow);
|
||||||
};
|
};
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -314,8 +378,10 @@ ContextMenu.propTypes = {
|
|||||||
selectedRange: PropTypes.object,
|
selectedRange: PropTypes.object,
|
||||||
selectedPosition: PropTypes.object,
|
selectedPosition: PropTypes.object,
|
||||||
recordMetrics: PropTypes.object,
|
recordMetrics: PropTypes.object,
|
||||||
|
selectNone: PropTypes.func,
|
||||||
getTableContentRect: PropTypes.func,
|
getTableContentRect: PropTypes.func,
|
||||||
recordGetterByIndex: PropTypes.func,
|
recordGetterByIndex: PropTypes.func,
|
||||||
|
deleteRecords: PropTypes.func,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ContextMenu;
|
export default ContextMenu;
|
||||||
|
@@ -2,8 +2,10 @@ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
|||||||
import toaster from '../../../components/toast';
|
import toaster from '../../../components/toast';
|
||||||
import TableMain from './table-main';
|
import TableMain from './table-main';
|
||||||
import { useMetadataView } from '../../hooks/metadata-view';
|
import { useMetadataView } from '../../hooks/metadata-view';
|
||||||
import { Utils } from '../../../utils/utils';
|
import { Utils, validateName } from '../../../utils/utils';
|
||||||
import { isModF } from '../../utils/hotkey';
|
import { isModF } from '../../utils/hotkey';
|
||||||
|
import { gettext } from '../../../utils/constants';
|
||||||
|
import { getFileNameFromRecord } from '../../utils/cell';
|
||||||
import { getValidGroupbys } from '../../utils/group';
|
import { getValidGroupbys } from '../../utils/group';
|
||||||
import { EVENT_BUS_TYPE, PER_LOAD_NUMBER, MAX_LOAD_NUMBER } from '../../constants';
|
import { EVENT_BUS_TYPE, PER_LOAD_NUMBER, MAX_LOAD_NUMBER } from '../../constants';
|
||||||
|
|
||||||
@@ -11,7 +13,7 @@ import './index.css';
|
|||||||
|
|
||||||
const Table = () => {
|
const Table = () => {
|
||||||
const [isLoadingMore, setLoadingMore] = useState(false);
|
const [isLoadingMore, setLoadingMore] = useState(false);
|
||||||
const { isLoading, metadata, store } = useMetadataView();
|
const { isLoading, metadata, store, renameFileCallback, deleteFilesCallback } = useMetadataView();
|
||||||
const containerRef = useRef(null);
|
const containerRef = useRef(null);
|
||||||
|
|
||||||
const onKeyDown = useCallback((event) => {
|
const onKeyDown = useCallback((event) => {
|
||||||
@@ -67,18 +69,80 @@ const Table = () => {
|
|||||||
}
|
}
|
||||||
}, [metadata, store]);
|
}, [metadata, store]);
|
||||||
|
|
||||||
const modifyRecords = useCallback((rowIds, idRowUpdates, idOriginalRowUpdates, idOldRowData, idOriginalOldRowData, isCopyPaste = false) => {
|
const modifyRecords = (rowIds, idRowUpdates, idOriginalRowUpdates, idOldRowData, idOriginalOldRowData, isCopyPaste = false) => {
|
||||||
store.modifyRecords(rowIds, idRowUpdates, idOriginalRowUpdates, idOldRowData, idOriginalOldRowData, isCopyPaste);
|
const isRename = store.checkIsRenameFileOperator(rowIds, idOriginalRowUpdates);
|
||||||
}, [store]);
|
let oldPath = null;
|
||||||
|
let newName = null;
|
||||||
|
if (isRename) {
|
||||||
|
const rowId = rowIds[0];
|
||||||
|
const row = recordGetterById(rowId);
|
||||||
|
const rowUpdates = idOriginalRowUpdates[rowId];
|
||||||
|
const { _parent_dir, _name } = row;
|
||||||
|
oldPath = Utils.joinPath(_parent_dir, _name);
|
||||||
|
newName = getFileNameFromRecord(rowUpdates);
|
||||||
|
const { isValid, errMessage } = validateName(newName);
|
||||||
|
if (!isValid) {
|
||||||
|
toaster.danger(errMessage);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (newName === _name) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (store.checkDuplicatedName(newName, _parent_dir)) {
|
||||||
|
let errMessage = gettext('The name "{name}" is already taken. Please choose a different name.');
|
||||||
|
errMessage = errMessage.replace('{name}', Utils.HTMLescape(newName));
|
||||||
|
toaster.danger(errMessage);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
store.modifyRecords(rowIds, idRowUpdates, idOriginalRowUpdates, idOldRowData, idOriginalOldRowData, isCopyPaste, isRename, {
|
||||||
|
fail_callback: (error) => {
|
||||||
|
toaster.danger(error);
|
||||||
|
},
|
||||||
|
success_callback: () => {
|
||||||
|
if (isRename) {
|
||||||
|
renameFileCallback(oldPath, newName);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const modifyRecord = useCallback((rowId, updates, oldRowData, originalUpdates, originalOldRowData) => {
|
const deleteRecords = (recordsIds) => {
|
||||||
|
let paths = [];
|
||||||
|
let fileNames = [];
|
||||||
|
recordsIds.forEach((recordId) => {
|
||||||
|
const record = recordGetterById(recordId);
|
||||||
|
const { _parent_dir, _name } = record || {};
|
||||||
|
if (_parent_dir && _name) {
|
||||||
|
const path = Utils.joinPath(_parent_dir, _name);
|
||||||
|
paths.push(path);
|
||||||
|
fileNames.push(_name);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
store.deleteRecords(recordsIds, {
|
||||||
|
fail_callback: (error) => {
|
||||||
|
toaster.danger(error);
|
||||||
|
},
|
||||||
|
success_callback: () => {
|
||||||
|
deleteFilesCallback(paths, fileNames);
|
||||||
|
let msg = fileNames.length > 1
|
||||||
|
? gettext('Successfully deleted {name} and {n} other items')
|
||||||
|
: gettext('Successfully deleted {name}');
|
||||||
|
msg = msg.replace('{name}', fileNames[0])
|
||||||
|
.replace('{n}', fileNames.length - 1);
|
||||||
|
toaster.success(msg);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const modifyRecord = (rowId, updates, oldRowData, originalUpdates, originalOldRowData) => {
|
||||||
const rowIds = [rowId];
|
const rowIds = [rowId];
|
||||||
const idRowUpdates = { [rowId]: updates };
|
const idRowUpdates = { [rowId]: updates };
|
||||||
const idOriginalRowUpdates = { [rowId]: originalUpdates };
|
const idOriginalRowUpdates = { [rowId]: originalUpdates };
|
||||||
const idOldRowData = { [rowId]: oldRowData };
|
const idOldRowData = { [rowId]: oldRowData };
|
||||||
const idOriginalOldRowData = { [rowId]: originalOldRowData };
|
const idOriginalOldRowData = { [rowId]: originalOldRowData };
|
||||||
modifyRecords(rowIds, idRowUpdates, idOriginalRowUpdates, idOldRowData, idOriginalOldRowData);
|
modifyRecords(rowIds, idRowUpdates, idOriginalRowUpdates, idOldRowData, idOriginalOldRowData);
|
||||||
}, [modifyRecords]);
|
};
|
||||||
|
|
||||||
const getAdjacentRowsIds = useCallback((rowIds) => {
|
const getAdjacentRowsIds = useCallback((rowIds) => {
|
||||||
const rowIdsLen = metadata.row_ids.length;
|
const rowIdsLen = metadata.row_ids.length;
|
||||||
@@ -162,6 +226,7 @@ const Table = () => {
|
|||||||
metadata={metadata}
|
metadata={metadata}
|
||||||
modifyRecord={modifyRecord}
|
modifyRecord={modifyRecord}
|
||||||
modifyRecords={modifyRecords}
|
modifyRecords={modifyRecords}
|
||||||
|
deleteRecords={deleteRecords}
|
||||||
recordGetterById={recordGetterById}
|
recordGetterById={recordGetterById}
|
||||||
recordGetterByIndex={recordGetterByIndex}
|
recordGetterByIndex={recordGetterByIndex}
|
||||||
getTableContentRect={getTableContentRect}
|
getTableContentRect={getTableContentRect}
|
||||||
|
@@ -27,6 +27,7 @@ import RecordMetrics from '../../utils/record-metrics';
|
|||||||
import setEventTransfer from '../../utils/set-event-transfer';
|
import setEventTransfer from '../../utils/set-event-transfer';
|
||||||
import getEventTransfer from '../../utils/get-event-transfer';
|
import getEventTransfer from '../../utils/get-event-transfer';
|
||||||
import { getGroupRecordByIndex } from '../../utils/group-metrics';
|
import { getGroupRecordByIndex } from '../../utils/group-metrics';
|
||||||
|
import { isNameColumn } from '../../../../utils/column';
|
||||||
|
|
||||||
import './index.css';
|
import './index.css';
|
||||||
|
|
||||||
@@ -219,6 +220,7 @@ class InteractionMasks extends React.Component {
|
|||||||
const { selectedPosition, openEditorMode } = this.state;
|
const { selectedPosition, openEditorMode } = this.state;
|
||||||
const { columns } = this.props;
|
const { columns } = this.props;
|
||||||
const selectedColumn = getSelectedColumn({ selectedPosition, columns });
|
const selectedColumn = getSelectedColumn({ selectedPosition, columns });
|
||||||
|
const _isNameColumn = isNameColumn(selectedColumn);
|
||||||
const { type: columnType } = selectedColumn;
|
const { type: columnType } = selectedColumn;
|
||||||
|
|
||||||
if (NOT_SUPPORT_OPEN_EDITOR_COLUMN_TYPES.includes(columnType)) return null;
|
if (NOT_SUPPORT_OPEN_EDITOR_COLUMN_TYPES.includes(columnType)) return null;
|
||||||
@@ -226,7 +228,7 @@ class InteractionMasks extends React.Component {
|
|||||||
// how to open editors?
|
// how to open editors?
|
||||||
// 1. editor is closed
|
// 1. editor is closed
|
||||||
// 2. record-cell is editable or open editor with preview mode
|
// 2. record-cell is editable or open editor with preview mode
|
||||||
if (((this.isSelectedCellEditable() || (openEditorMode === EDITOR_TYPE.PREVIEWER && READONLY_PREVIEW_COLUMNS.includes(columnType))) && !this.state.isEditorEnabled)) {
|
if (((this.isSelectedCellEditable() || _isNameColumn || (openEditorMode === EDITOR_TYPE.PREVIEWER && READONLY_PREVIEW_COLUMNS.includes(columnType))) && !this.state.isEditorEnabled)) {
|
||||||
this.setState({
|
this.setState({
|
||||||
isEditorEnabled: true,
|
isEditorEnabled: true,
|
||||||
firstEditorKeyDown: key,
|
firstEditorKeyDown: key,
|
||||||
|
@@ -7,7 +7,7 @@ import { GROUP_VIEW_OFFSET } from '../../../constants';
|
|||||||
|
|
||||||
import './index.css';
|
import './index.css';
|
||||||
|
|
||||||
const TableMain = ({ metadata, modifyRecord, modifyRecords, loadMore, loadAll, searchResult, recordGetterByIndex, recordGetterById, modifyColumnData, ...params }) => {
|
const TableMain = ({ metadata, modifyRecord, modifyRecords, loadMore, loadAll, searchResult, recordGetterByIndex, recordGetterById, modifyColumnData, ...props }) => {
|
||||||
|
|
||||||
const gridUtils = useMemo(() => {
|
const gridUtils = useMemo(() => {
|
||||||
return new GridUtils(metadata, { modifyRecord, modifyRecords, recordGetterByIndex, recordGetterById, modifyColumnData });
|
return new GridUtils(metadata, { modifyRecord, modifyRecords, recordGetterByIndex, recordGetterById, modifyColumnData });
|
||||||
@@ -60,11 +60,12 @@ const TableMain = ({ metadata, modifyRecord, modifyRecords, loadMore, loadAll, s
|
|||||||
groupOffsetLeft={groupOffset}
|
groupOffsetLeft={groupOffset}
|
||||||
modifyRecord={updateRecord}
|
modifyRecord={updateRecord}
|
||||||
updateRecords={updateRecords}
|
updateRecords={updateRecords}
|
||||||
|
deleteRecords={props.deleteRecords}
|
||||||
getCopiedRecordsAndColumnsFromRange={getCopiedRecordsAndColumnsFromRange}
|
getCopiedRecordsAndColumnsFromRange={getCopiedRecordsAndColumnsFromRange}
|
||||||
recordGetterById={recordGetterById}
|
recordGetterById={recordGetterById}
|
||||||
recordGetterByIndex={recordGetterByIndex}
|
recordGetterByIndex={recordGetterByIndex}
|
||||||
modifyColumnData={modifyColumnData}
|
modifyColumnData={modifyColumnData}
|
||||||
{...params}
|
{...props}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@@ -120,8 +120,8 @@ class RecordsFooter extends React.Component {
|
|||||||
let recordsCountText;
|
let recordsCountText;
|
||||||
if (recordsCount > 1) {
|
if (recordsCount > 1) {
|
||||||
recordsCountText = gettext('xxx records').replace('xxx', recordsCount);
|
recordsCountText = gettext('xxx records').replace('xxx', recordsCount);
|
||||||
} else if (recordsCount === 1) {
|
} else {
|
||||||
recordsCountText = gettext('1 record');
|
recordsCountText = gettext('xxx record').replace('xxx', recordsCount);
|
||||||
}
|
}
|
||||||
if (hasMore) {
|
if (hasMore) {
|
||||||
recordsCountText += ' +';
|
recordsCountText += ' +';
|
||||||
|
@@ -2,6 +2,7 @@ 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';
|
||||||
@@ -10,7 +11,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 } from '../../../../../utils/utils';
|
import { isMobile, Utils } 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';
|
||||||
@@ -42,9 +43,11 @@ 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();
|
||||||
|
this.deletedRecord = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
@@ -614,15 +617,40 @@ 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, recordGetterByIndex, updateRecords } = this.props;
|
const { isGroupView } = this.props;
|
||||||
const { recordMetrics, columnMetrics, colOverScanStartIdx, colOverScanEndIdx } = this.state;
|
const { recordMetrics, columnMetrics, colOverScanStartIdx, colOverScanEndIdx } = this.state;
|
||||||
const { columns, allColumns, totalWidth, lastFrozenColumnKey, frozenColumnsWidth } = columnMetrics;
|
const { columns, allColumns, totalWidth, lastFrozenColumnKey, frozenColumnsWidth } = columnMetrics;
|
||||||
const commonProps = {
|
const commonProps = {
|
||||||
...this.props,
|
...this.props,
|
||||||
columns, allColumns, totalWidth, lastFrozenColumnKey, frozenColumnsWidth,
|
columns, allColumns, totalWidth, lastFrozenColumnKey, frozenColumnsWidth,
|
||||||
recordMetrics, colOverScanStartIdx, colOverScanEndIdx,
|
recordMetrics, colOverScanStartIdx, colOverScanEndIdx,
|
||||||
contextMenu: (<ContextMenu isGroupView={isGroupView} recordGetterByIndex={recordGetterByIndex} updateRecords={updateRecords} />),
|
contextMenu: (
|
||||||
|
<ContextMenu
|
||||||
|
isGroupView={isGroupView}
|
||||||
|
toggleDeleteFolderDialog={this.toggleDeleteFolderDialog}
|
||||||
|
recordGetterByIndex={this.props.recordGetterByIndex}
|
||||||
|
updateRecords={this.props.updateRecords}
|
||||||
|
deleteRecords={this.props.deleteRecords}
|
||||||
|
/>
|
||||||
|
),
|
||||||
hasSelectedRecord: this.hasSelectedRecord(),
|
hasSelectedRecord: this.hasSelectedRecord(),
|
||||||
getScrollLeft: this.getScrollLeft,
|
getScrollLeft: this.getScrollLeft,
|
||||||
getScrollTop: this.getScrollTop,
|
getScrollTop: this.getScrollTop,
|
||||||
@@ -658,15 +686,17 @@ class Records extends Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { recordIds, recordsCount, table, isGroupView, groupOffsetLeft, renameColumn, modifyColumnData,
|
const {
|
||||||
deleteColumn, modifyColumnOrder } = this.props;
|
recordIds, recordsCount, table, isGroupView, groupOffsetLeft, renameColumn, modifyColumnData,
|
||||||
|
deleteColumn, modifyColumnOrder,
|
||||||
|
} = 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;
|
||||||
const containerWidth = totalWidth + SEQUENCE_COLUMN_WIDTH + CANVAS_RIGHT_INTERVAL + groupOffsetLeft;
|
const containerWidth = totalWidth + SEQUENCE_COLUMN_WIDTH + CANVAS_RIGHT_INTERVAL + groupOffsetLeft;
|
||||||
const hasSelectedRecord = this.hasSelectedRecord();
|
const hasSelectedRecord = this.hasSelectedRecord();
|
||||||
const isSelectedAll = RecordMetrics.isSelectedAll(recordIds, recordMetrics);
|
const isSelectedAll = RecordMetrics.isSelectedAll(recordIds, recordMetrics);
|
||||||
|
|
||||||
if (recordsCount === 0) {
|
if (recordsCount === 0 && !this.props.hasMore) {
|
||||||
return (<EmptyTip text={gettext('No record')} />);
|
return (<EmptyTip text={gettext('No record')} />);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -726,6 +756,14 @@ 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}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@@ -6,7 +6,8 @@ import CellOperationBtn from './operation-btn';
|
|||||||
import { Utils } from '../../../../../../../utils/utils';
|
import { Utils } from '../../../../../../../utils/utils';
|
||||||
import ObjectUtils from '../../../../../../utils/object-utils';
|
import ObjectUtils from '../../../../../../utils/object-utils';
|
||||||
import { isCellValueChanged, getCellValueByColumn } from '../../../../../../utils/cell';
|
import { isCellValueChanged, getCellValueByColumn } from '../../../../../../utils/cell';
|
||||||
import { CellType, PRIVATE_COLUMN_KEY, PRIVATE_COLUMN_KEYS, TABLE_SUPPORT_EDIT_TYPE_MAP } from '../../../../../../constants';
|
import { CellType, PRIVATE_COLUMN_KEYS, TABLE_SUPPORT_EDIT_TYPE_MAP } from '../../../../../../constants';
|
||||||
|
import { checkIsDir } from '../../../../../../utils/row';
|
||||||
|
|
||||||
import './index.css';
|
import './index.css';
|
||||||
|
|
||||||
@@ -41,9 +42,7 @@ const Cell = React.memo(({
|
|||||||
return column.type === CellType.FILE_NAME;
|
return column.type === CellType.FILE_NAME;
|
||||||
}, [column]);
|
}, [column]);
|
||||||
const isDir = useMemo(() => {
|
const isDir = useMemo(() => {
|
||||||
const isDirValue = record[PRIVATE_COLUMN_KEY.IS_DIR];
|
return checkIsDir(record);
|
||||||
if (typeof isDirValue === 'string') return isDirValue.toUpperCase() === 'TRUE';
|
|
||||||
return isDirValue;
|
|
||||||
}, [record]);
|
}, [record]);
|
||||||
const style = useMemo(() => {
|
const style = useMemo(() => {
|
||||||
const { left, width } = column;
|
const { left, width } = column;
|
||||||
|
@@ -5,7 +5,9 @@ import { IconBtn } from '@seafile/sf-metadata-ui-component';
|
|||||||
import { Utils } from '../../../../../../../../utils/utils';
|
import { Utils } from '../../../../../../../../utils/utils';
|
||||||
import { gettext, siteRoot } from '../../../../../../../../utils/constants';
|
import { gettext, siteRoot } from '../../../../../../../../utils/constants';
|
||||||
import { EVENT_BUS_TYPE } from '../../../../../../../..//components/common/event-bus-type';
|
import { EVENT_BUS_TYPE } from '../../../../../../../..//components/common/event-bus-type';
|
||||||
import { EVENT_BUS_TYPE as METADATA_EVENT_BUS_TYPE, EDITOR_TYPE, PRIVATE_COLUMN_KEY } from '../../../../../../../constants';
|
import { EVENT_BUS_TYPE as METADATA_EVENT_BUS_TYPE, EDITOR_TYPE } from '../../../../../../../constants';
|
||||||
|
import { getFileNameFromRecord, getParentDirFromRecord } from '../../../../../../../utils/cell';
|
||||||
|
import { checkIsDir } from '../../../../../../../utils/row';
|
||||||
|
|
||||||
import './index.css';
|
import './index.css';
|
||||||
|
|
||||||
@@ -18,19 +20,13 @@ const FILE_TYPE = {
|
|||||||
|
|
||||||
const CellOperationBtn = ({ isDir, column, record, cellValue, ...props }) => {
|
const CellOperationBtn = ({ isDir, column, record, cellValue, ...props }) => {
|
||||||
|
|
||||||
const _isDir = useMemo(() => {
|
|
||||||
const isDirValue = record[PRIVATE_COLUMN_KEY.IS_DIR];
|
|
||||||
if (typeof isDirValue === 'string') return isDirValue.toUpperCase() === 'TRUE';
|
|
||||||
return isDirValue;
|
|
||||||
}, [record]);
|
|
||||||
|
|
||||||
const fileName = useMemo(() => {
|
const fileName = useMemo(() => {
|
||||||
const { key } = column;
|
const { key } = column;
|
||||||
return record[key];
|
return record[key];
|
||||||
}, [column, record]);
|
}, [column, record]);
|
||||||
|
|
||||||
const fileType = useMemo(() => {
|
const fileType = useMemo(() => {
|
||||||
if (_isDir) return FILE_TYPE.FOLDER;
|
if (checkIsDir(record)) return FILE_TYPE.FOLDER;
|
||||||
if (!fileName) return '';
|
if (!fileName) return '';
|
||||||
const index = fileName.lastIndexOf('.');
|
const index = fileName.lastIndexOf('.');
|
||||||
if (index === -1) return '';
|
if (index === -1) return '';
|
||||||
@@ -40,10 +36,10 @@ const CellOperationBtn = ({ isDir, column, record, cellValue, ...props }) => {
|
|||||||
if (Utils.isMarkdownFile(fileName)) return FILE_TYPE.MARKDOWN;
|
if (Utils.isMarkdownFile(fileName)) return FILE_TYPE.MARKDOWN;
|
||||||
if (Utils.isSdocFile(fileName)) return FILE_TYPE.SDOC;
|
if (Utils.isSdocFile(fileName)) return FILE_TYPE.SDOC;
|
||||||
return '';
|
return '';
|
||||||
}, [_isDir, fileName]);
|
}, [record, fileName]);
|
||||||
|
|
||||||
const getParentDir = () => {
|
const getParentDir = () => {
|
||||||
const parentDir = record[PRIVATE_COLUMN_KEY.PARENT_DIR];
|
const parentDir = getParentDirFromRecord(record);
|
||||||
if (parentDir === '/') {
|
if (parentDir === '/') {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
@@ -62,8 +58,8 @@ const CellOperationBtn = ({ isDir, column, record, cellValue, ...props }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const openMarkdown = () => {
|
const openMarkdown = () => {
|
||||||
const fileName = record[PRIVATE_COLUMN_KEY.FILE_NAME];
|
const fileName = getFileNameFromRecord(record);
|
||||||
const parentDir = record[PRIVATE_COLUMN_KEY.PARENT_DIR];
|
const parentDir = getParentDirFromRecord(record);
|
||||||
window.sfMetadataContext.eventBus.dispatch(EVENT_BUS_TYPE.OPEN_MARKDOWN_DIALOG, parentDir, fileName);
|
window.sfMetadataContext.eventBus.dispatch(EVENT_BUS_TYPE.OPEN_MARKDOWN_DIALOG, parentDir, fileName);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import { getCellValueByColumn } from '../../../utils/cell';
|
import { getCellValueByColumn, getFileNameFromRecord } from '../../../utils/cell';
|
||||||
import { getColumnByIndex, getColumnOriginName } from '../../../utils/column';
|
import { getColumnByIndex, getColumnOriginName } from '../../../utils/column';
|
||||||
import { CellType, NOT_SUPPORT_DRAG_COPY_COLUMN_TYPES, PRIVATE_COLUMN_KEY, TRANSFER_TYPES } from '../../../constants';
|
import { CellType, NOT_SUPPORT_DRAG_COPY_COLUMN_TYPES, PRIVATE_COLUMN_KEY, TRANSFER_TYPES } from '../../../constants';
|
||||||
import { getGroupRecordByIndex } from './group-metrics';
|
import { getGroupRecordByIndex } from './group-metrics';
|
||||||
@@ -96,7 +96,7 @@ class GridUtils {
|
|||||||
let originalOldRecordData = {};
|
let originalOldRecordData = {};
|
||||||
let originalKeyOldRecordData = {};
|
let originalKeyOldRecordData = {};
|
||||||
const { canModifyRow, canModifyColumn } = window.sfMetadataContext;
|
const { canModifyRow, canModifyColumn } = window.sfMetadataContext;
|
||||||
const filename = pasteRecord[PRIVATE_COLUMN_KEY.FILE_NAME];
|
const filename = getFileNameFromRecord(pasteRecord);
|
||||||
|
|
||||||
for (let j = 0; j < pasteColumnsLen; j++) {
|
for (let j = 0; j < pasteColumnsLen; j++) {
|
||||||
const pasteColumn = getColumnByIndex(j + startColumnIndex, columns);
|
const pasteColumn = getColumnByIndex(j + startColumnIndex, columns);
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import { Utils } from '../../../../utils/utils';
|
import { Utils } from '../../../../utils/utils';
|
||||||
import { getCellValueByColumn } from '../../../utils/cell';
|
import { getCellValueByColumn, getFileNameFromRecord } from '../../../utils/cell';
|
||||||
import { getGroupByPath } from '../../../utils/view';
|
import { getGroupByPath } from '../../../utils/view';
|
||||||
import { getColumnByIndex, canEditCell } from '../../../utils/column';
|
import { getColumnByIndex, canEditCell } from '../../../utils/column';
|
||||||
import { PRIVATE_COLUMN_KEY, SUPPORT_PREVIEW_COLUMN_TYPES, metadataZIndexes } from '../../../constants';
|
import { PRIVATE_COLUMN_KEY, SUPPORT_PREVIEW_COLUMN_TYPES, metadataZIndexes } from '../../../constants';
|
||||||
@@ -46,7 +46,7 @@ export const isSelectedCellEditable = ({ enableCellSelect, selectedPosition, col
|
|||||||
const row = getSelectedRow({ selectedPosition, isGroupView, recordGetterByIndex });
|
const row = getSelectedRow({ selectedPosition, isGroupView, recordGetterByIndex });
|
||||||
if (!window.sfMetadataContext.canModifyRow(row)) return false;
|
if (!window.sfMetadataContext.canModifyRow(row)) return false;
|
||||||
let isCellEditable = Utils.isFunction(onCheckCellIsEditable) ? onCheckCellIsEditable({ row, column, ...selectedPosition }) : true;
|
let isCellEditable = Utils.isFunction(onCheckCellIsEditable) ? onCheckCellIsEditable({ row, column, ...selectedPosition }) : true;
|
||||||
const fileName = row ? row[PRIVATE_COLUMN_KEY.FILE_NAME] : '';
|
const fileName = getFileNameFromRecord(row);
|
||||||
const imageRow = row && (Utils.imageCheck(fileName) || Utils.videoCheck(fileName));
|
const imageRow = row && (Utils.imageCheck(fileName) || Utils.videoCheck(fileName));
|
||||||
isCellEditable = isCellEditable && canEditCell(column, row, enableCellSelect);
|
isCellEditable = isCellEditable && canEditCell(column, row, enableCellSelect);
|
||||||
if (imageRow) return isCellEditable;
|
if (imageRow) return isCellEditable;
|
||||||
|
@@ -932,13 +932,7 @@ class LibContentView extends React.Component {
|
|||||||
|
|
||||||
this.setState({ currentDirent: null });
|
this.setState({ currentDirent: null });
|
||||||
seafileAPI.deleteMutipleDirents(repoID, this.state.path, dirNames).then(res => {
|
seafileAPI.deleteMutipleDirents(repoID, this.state.path, dirNames).then(res => {
|
||||||
if (this.state.isTreePanelShown) {
|
this.deleteItemsAjaxCallback(direntPaths, dirNames);
|
||||||
this.deleteTreeNodes(direntPaths);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.deleteDirents(dirNames);
|
|
||||||
|
|
||||||
this.removeFromRecentlyUsed(repoID, this.state.path);
|
|
||||||
|
|
||||||
let msg = '';
|
let msg = '';
|
||||||
if (direntPaths.length > 1) {
|
if (direntPaths.length > 1) {
|
||||||
@@ -1174,12 +1168,12 @@ class LibContentView extends React.Component {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
renameItemAjaxCallback(path, newName) {
|
renameItemAjaxCallback = (path, newName) => {
|
||||||
if (this.state.isTreePanelShown) {
|
if (this.state.isTreePanelShown) {
|
||||||
this.renameTreeNode(path, newName);
|
this.renameTreeNode(path, newName);
|
||||||
}
|
}
|
||||||
this.renameDirent(path, newName);
|
this.renameDirent(path, newName);
|
||||||
}
|
};
|
||||||
|
|
||||||
toggleDeleteFolderDialog = () => {
|
toggleDeleteFolderDialog = () => {
|
||||||
this.setState({ isDeleteFolderDialogOpen: !this.state.isDeleteFolderDialogOpen });
|
this.setState({ isDeleteFolderDialogOpen: !this.state.isDeleteFolderDialogOpen });
|
||||||
@@ -1246,6 +1240,14 @@ class LibContentView extends React.Component {
|
|||||||
this.removeFromRecentlyUsed(this.props.repoID, path);
|
this.removeFromRecentlyUsed(this.props.repoID, path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
deleteItemsAjaxCallback = (direntPaths, dirNames) => {
|
||||||
|
if (this.state.isTreePanelShown) {
|
||||||
|
this.deleteTreeNodes(direntPaths);
|
||||||
|
}
|
||||||
|
this.deleteDirents(dirNames);
|
||||||
|
this.removeFromRecentlyUsed(this.props.repoID, this.state.path);
|
||||||
|
};
|
||||||
|
|
||||||
// list operations
|
// list operations
|
||||||
onMoveItem = (destRepo, dirent, moveToDirentPath, nodeParentPath) => {
|
onMoveItem = (destRepo, dirent, moveToDirentPath, nodeParentPath) => {
|
||||||
this.updateCurrentDirent(dirent);
|
this.updateCurrentDirent(dirent);
|
||||||
@@ -2368,6 +2370,8 @@ class LibContentView extends React.Component {
|
|||||||
onItemSelected={this.onDirentSelected}
|
onItemSelected={this.onDirentSelected}
|
||||||
onItemDelete={this.onMainPanelItemDelete}
|
onItemDelete={this.onMainPanelItemDelete}
|
||||||
onItemRename={this.onMainPanelItemRename}
|
onItemRename={this.onMainPanelItemRename}
|
||||||
|
deleteFilesCallback={this.deleteItemsAjaxCallback}
|
||||||
|
renameFileCallback={this.renameItemAjaxCallback}
|
||||||
onItemMove={this.onMoveItem}
|
onItemMove={this.onMoveItem}
|
||||||
onItemCopy={this.onCopyItem}
|
onItemCopy={this.onCopyItem}
|
||||||
onItemConvert={this.onConvertItem}
|
onItemConvert={this.onConvertItem}
|
||||||
|
Reference in New Issue
Block a user