diff --git a/frontend/src/components/common/event-bus-type.js b/frontend/src/components/common/event-bus-type.js index 3ffbff7577..ed1b517352 100644 --- a/frontend/src/components/common/event-bus-type.js +++ b/frontend/src/components/common/event-bus-type.js @@ -23,4 +23,13 @@ export const EVENT_BUS_TYPE = { // download file DOWNLOAD_FILE: 'download_file', + CREATE_FILE: 'create_file', + CREATE_FOLDER: 'create_folder', + MOVE_FILE: 'move_file', + COPY_FILE: 'copy_file', + SHARE_FILE: 'share_file', + RENAME_FILE: 'rename_file', + PERMISSION: 'permission', + ACCESS_LOG: 'access_log', + PREVIEW_IMAGE: 'preview_image', }; diff --git a/frontend/src/components/cur-dir-path/dir-path.js b/frontend/src/components/cur-dir-path/dir-path.js index d10fb7dcba..30f35f22b9 100644 --- a/frontend/src/components/cur-dir-path/dir-path.js +++ b/frontend/src/components/cur-dir-path/dir-path.js @@ -26,8 +26,6 @@ const propTypes = { userPerm: PropTypes.string.isRequired, isGroupOwnedRepo: PropTypes.bool.isRequired, showShareBtn: PropTypes.bool.isRequired, - onAddFile: PropTypes.func.isRequired, - onAddFolder: PropTypes.func.isRequired, onUploadFile: PropTypes.func.isRequired, onUploadFolder: PropTypes.func.isRequired, direntList: PropTypes.array.isRequired, @@ -197,9 +195,8 @@ class DirPath extends React.Component { showShareBtn={this.props.showShareBtn} enableDirPrivateShare={this.props.enableDirPrivateShare} userPerm={this.props.userPerm} + eventBus={this.props.eventBus} isGroupOwnedRepo={this.props.isGroupOwnedRepo} - onAddFile={this.props.onAddFile} - onAddFolder={this.props.onAddFolder} onUploadFile={this.props.onUploadFile} onUploadFolder={this.props.onUploadFolder} loadDirentList={this.props.loadDirentList} @@ -269,8 +266,7 @@ class DirPath extends React.Component { enableDirPrivateShare={this.props.enableDirPrivateShare} userPerm={this.props.userPerm} isGroupOwnedRepo={this.props.isGroupOwnedRepo} - onAddFile={this.props.onAddFile} - onAddFolder={this.props.onAddFolder} + eventBus={this.props.eventBus} onUploadFile={this.props.onUploadFile} onUploadFolder={this.props.onUploadFolder} loadDirentList={this.props.loadDirentList} diff --git a/frontend/src/components/cur-dir-path/index.js b/frontend/src/components/cur-dir-path/index.js index 4c7b1232ad..fcb54a386e 100644 --- a/frontend/src/components/cur-dir-path/index.js +++ b/frontend/src/components/cur-dir-path/index.js @@ -22,8 +22,6 @@ const propTypes = { isGroupOwnedRepo: PropTypes.bool.isRequired, enableDirPrivateShare: PropTypes.bool.isRequired, showShareBtn: PropTypes.bool.isRequired, - onAddFile: PropTypes.func.isRequired, - onAddFolder: PropTypes.func.isRequired, onUploadFile: PropTypes.func.isRequired, onUploadFolder: PropTypes.func.isRequired, fullDirentList: PropTypes.array.isRequired, @@ -67,14 +65,13 @@ class CurDirPath extends React.Component { toggleTreePanel={this.props.toggleTreePanel} enableDirPrivateShare={this.props.enableDirPrivateShare} showShareBtn={this.props.showShareBtn} - onAddFolder={this.props.onAddFolder} - onAddFile={this.props.onAddFile} onUploadFile={this.props.onUploadFile} onUploadFolder={this.props.onUploadFolder} direntList={this.props.fullDirentList} filePermission={this.props.filePermission} onFileTagChanged={this.props.onFileTagChanged} repoTags={this.props.repoTags} + eventBus={this.props.eventBus} onItemMove={this.props.onItemMove} loadDirentList={this.props.loadDirentList} /> diff --git a/frontend/src/components/dialog/create-folder-dialog.js b/frontend/src/components/dialog/create-folder-dialog.js index 2de51eb7c2..1f8ccafd6a 100644 --- a/frontend/src/components/dialog/create-folder-dialog.js +++ b/frontend/src/components/dialog/create-folder-dialog.js @@ -13,7 +13,7 @@ const propTypes = { addFolderCancel: PropTypes.func.isRequired, }; -class CreateForder extends React.Component { +class CreateFolder extends React.Component { constructor(props) { super(props); this.state = { @@ -61,6 +61,7 @@ class CreateForder extends React.Component { return; } this.props.onAddFolder(this.state.parentPath + newName); + this.toggle(); }; handleKeyDown = (e) => { @@ -103,6 +104,6 @@ class CreateForder extends React.Component { } } -CreateForder.propTypes = propTypes; +CreateFolder.propTypes = propTypes; -export default CreateForder; +export default CreateFolder; diff --git a/frontend/src/components/dialog/image-dialog/index.js b/frontend/src/components/dialog/image-dialog/index.js index ccbe884409..5b7875ee94 100644 --- a/frontend/src/components/dialog/image-dialog/index.js +++ b/frontend/src/components/dialog/image-dialog/index.js @@ -2,7 +2,7 @@ import React, { useCallback, useState } from 'react'; import PropTypes from 'prop-types'; import { gettext } from '../../../utils/constants'; import Lightbox from '@seafile/react-image-lightbox'; -import { useMetadataAIOperations } from '../../../hooks/metadata-ai-operation'; +import { useMetadataAIOperations } from '../../../hooks'; import EmbeddedFileDetails from '../../dirent-detail/embedded-file-details'; import { SYSTEM_FOLDERS } from '../../../constants'; import Icon from '../../icon'; diff --git a/frontend/src/components/dialog/move-dirent-dialog.js b/frontend/src/components/dialog/move-dirent-dialog.js index 980d44c278..a4f3425c0d 100644 --- a/frontend/src/components/dialog/move-dirent-dialog.js +++ b/frontend/src/components/dialog/move-dirent-dialog.js @@ -305,13 +305,10 @@ class MoveDirentDialog extends React.Component { renderTitle = () => { const { dirent, isMultipleOperation } = this.props; - let title = gettext('Move {placeholder} to'); + if (isMultipleOperation) return gettext('Move selected item(s) to:'); - if (isMultipleOperation) { - return gettext('Move selected item(s) to:'); - } else { - return title.replace('{placeholder}', `${Utils.HTMLescape(dirent.name)}`); - } + const title = gettext('Move {placeholder} to'); + return title.replace('{placeholder}', `${Utils.HTMLescape(dirent.name)}`); }; render() { diff --git a/frontend/src/components/dialog/rename-dialog.js b/frontend/src/components/dialog/rename-dialog.js index a8befe967e..19d65854c7 100644 --- a/frontend/src/components/dialog/rename-dialog.js +++ b/frontend/src/components/dialog/rename-dialog.js @@ -6,7 +6,7 @@ import { Button, Modal, Input, ModalBody, ModalFooter, Alert } from 'reactstrap' import SeahubModalHeader from '@/components/common/seahub-modal-header'; const propTypes = { - currentNode: PropTypes.object, + dirent: PropTypes.object, onRename: PropTypes.func.isRequired, toggleCancel: PropTypes.func.isRequired, checkDuplicatedName: PropTypes.func.isRequired, @@ -25,16 +25,16 @@ class Rename extends React.Component { } UNSAFE_componentWillMount() { - this.setState({ newName: this.props.currentNode.object.name }); + this.setState({ newName: this.props.dirent.name }); } componentDidMount() { - const { currentNode } = this.props; - this.changeState(currentNode); + const { dirent } = this.props; + this.changeState(dirent); } UNSAFE_componentWillReceiveProps(nextProps) { - this.changeState(nextProps.currentNode); + this.changeState(nextProps.dirent); } handleChange = (e) => { @@ -62,6 +62,7 @@ class Rename extends React.Component { return; } this.props.onRename(newName); + this.toggle(); }; handleKeyDown = (e) => { @@ -75,18 +76,18 @@ class Rename extends React.Component { this.props.toggleCancel(); }; - changeState = (currentNode) => { - let name = currentNode.object.name; + changeState = (dirent) => { + let name = dirent.name; this.setState({ newName: name }); }; onAfterModelOpened = () => { if (!this.newInput.current) return; - const { currentNode } = this.props; - let type = currentNode.object.type; + const { dirent } = this.props; + let type = dirent.type; this.newInput.current.focus(); if (type === 'file') { - var endIndex = currentNode.object.name.lastIndexOf('.md'); + var endIndex = dirent.name.lastIndexOf('.md'); this.newInput.current.setSelectionRange(0, endIndex, 'forward'); } else { this.newInput.current.setSelectionRange(0, -1); @@ -94,7 +95,7 @@ class Rename extends React.Component { }; render() { - let type = this.props.currentNode.object.type; + let type = this.props.dirent.type; return ( {type === 'file' ? gettext('Rename File') : gettext('Rename Folder') } diff --git a/frontend/src/components/dialog/rename-dirent.js b/frontend/src/components/dialog/rename-dirent.js deleted file mode 100644 index 7fda88a574..0000000000 --- a/frontend/src/components/dialog/rename-dirent.js +++ /dev/null @@ -1,119 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { gettext } from '../../utils/constants'; -import { Utils, validateName } from '../../utils/utils'; -import { Button, Modal, Input, ModalBody, ModalFooter, Alert } from 'reactstrap'; -import SeahubModalHeader from '@/components/common/seahub-modal-header'; - -const propTypes = { - onRename: PropTypes.func.isRequired, - toggleCancel: PropTypes.func.isRequired, - checkDuplicatedName: PropTypes.func.isRequired, - dirent: PropTypes.object, -}; - -class Rename extends React.Component { - - constructor(props) { - super(props); - this.state = { - newName: '', - errMessage: '', - isSubmitBtnActive: false, - }; - this.newInput = React.createRef(); - } - - UNSAFE_componentWillMount() { - this.setState({ newName: this.props.dirent.name }); - } - - componentDidMount() { - let { dirent } = this.props; - this.changeState(dirent); - } - - UNSAFE_componentWillReceiveProps(nextProps) { - this.changeState(nextProps.dirent); - } - - handleChange = (e) => { - if (!e.target.value.trim()) { - this.setState({ isSubmitBtnActive: false }); - } else { - this.setState({ isSubmitBtnActive: true }); - } - - this.setState({ newName: e.target.value }); - }; - - handleSubmit = () => { - let newName = this.state.newName.trim(); - let { isValid, errMessage } = validateName(newName); - if (!isValid) { - this.setState({ errMessage }); - return; - } - let isDuplicated = this.props.checkDuplicatedName(newName); - if (isDuplicated) { - let errMessage = gettext('The name "{name}" is already taken. Please choose a different name.'); - errMessage = errMessage.replace('{name}', Utils.HTMLescape(newName)); - this.setState({ errMessage }); - return; - } - this.props.onRename(newName); - this.props.toggleCancel(); - }; - - handleKeyDown = (e) => { - if (e.key === 'Enter') { - e.preventDefault(); - this.handleSubmit(); - } - }; - - toggle = () => { - this.props.toggleCancel(); - }; - - changeState = (dirent) => { - let name = dirent.name; - this.setState({ newName: name }); - }; - - onAfterModelOpened = () => { - const inputElement = this.newInput.current; - if (inputElement) { - inputElement.focus(); - const filename = this.state.newName; - const lastDotIndex = filename.lastIndexOf('.'); - if (lastDotIndex > 0) { - inputElement.setSelectionRange(0, lastDotIndex); - } else { - inputElement.select(); - } - } - }; - - render() { - let type = this.props.dirent.type; - return ( - - {type === 'file' ? gettext('Rename File') : gettext('Rename Folder') } - - {type === 'file' ? gettext('New file name') : gettext('New folder name')} - - {this.state.errMessage && {this.state.errMessage}} - - - {gettext('Cancel')} - {gettext('Submit')} - - - ); - } -} - -Rename.propTypes = propTypes; - -export default Rename; diff --git a/frontend/src/components/dir-view-mode/dir-column-nav/index.js b/frontend/src/components/dir-view-mode/dir-column-nav/index.js index bad0d15d7d..582b4ea60c 100644 --- a/frontend/src/components/dir-view-mode/dir-column-nav/index.js +++ b/frontend/src/components/dir-view-mode/dir-column-nav/index.js @@ -15,7 +15,6 @@ const propTypes = { isTreeDataLoading: PropTypes.bool.isRequired, treeData: PropTypes.object.isRequired, direntList: PropTypes.array, - selectedDirentList: PropTypes.array.isRequired, currentNode: PropTypes.object, repoID: PropTypes.string.isRequired, navRate: PropTypes.number, @@ -25,10 +24,7 @@ const propTypes = { onNodeExpanded: PropTypes.func.isRequired, onRenameNode: PropTypes.func.isRequired, onDeleteNode: PropTypes.func.isRequired, - onAddFileNode: PropTypes.func.isRequired, - onAddFolderNode: PropTypes.func.isRequired, onItemMove: PropTypes.func.isRequired, - onItemCopy: PropTypes.func.isRequired, onItemsMove: PropTypes.func.isRequired, getMenuContainerSize: PropTypes.func, updateDirent: PropTypes.func, @@ -59,17 +55,14 @@ class DirColumnNav extends React.Component { userPerm={userPerm} currentRepoInfo={currentRepoInfo} direntList={this.props.direntList} - selectedDirentList={this.props.selectedDirentList} currentNode={this.props.currentNode} + eventBus={this.props.eventBus} getMenuContainerSize={this.props.getMenuContainerSize} onNodeClick={this.props.onNodeClick} onNodeCollapse={this.props.onNodeCollapse} onNodeExpanded={this.props.onNodeExpanded} onRenameNode={this.props.onRenameNode} onDeleteNode={this.props.onDeleteNode} - onAddFileNode={this.props.onAddFileNode} - onAddFolderNode={this.props.onAddFolderNode} - onItemCopy={this.props.onItemCopy} onItemMove={this.props.onItemMove} onItemsMove={this.props.onItemsMove} updateDirent={this.props.updateDirent} diff --git a/frontend/src/components/dir-view-mode/dir-column-view.js b/frontend/src/components/dir-view-mode/dir-column-view.js index 811089c1ba..ac179188f2 100644 --- a/frontend/src/components/dir-view-mode/dir-column-view.js +++ b/frontend/src/components/dir-view-mode/dir-column-view.js @@ -32,8 +32,6 @@ const propTypes = { onNodeExpanded: PropTypes.func.isRequired, onRenameNode: PropTypes.func.isRequired, onDeleteNode: PropTypes.func.isRequired, - onAddFileNode: PropTypes.func.isRequired, - onAddFolderNode: PropTypes.func.isRequired, // file isViewFile: PropTypes.bool.isRequired, isFileLoading: PropTypes.bool.isRequired, @@ -54,18 +52,14 @@ const propTypes = { sortBy: PropTypes.string.isRequired, sortOrder: PropTypes.string.isRequired, sortItems: PropTypes.func.isRequired, - onAddFolder: PropTypes.func.isRequired, - onAddFile: PropTypes.func.isRequired, updateDirent: PropTypes.func.isRequired, onItemClick: PropTypes.func.isRequired, onItemSelected: PropTypes.func.isRequired, onItemDelete: PropTypes.func.isRequired, - onItemRename: PropTypes.func.isRequired, deleteFilesCallback: PropTypes.func, renameFileCallback: PropTypes.func, onItemMove: PropTypes.func.isRequired, moveFileCallback: PropTypes.func.isRequired, - onItemCopy: PropTypes.func.isRequired, copyFileCallback: PropTypes.func.isRequired, onItemConvert: PropTypes.func.isRequired, onDirentClick: PropTypes.func.isRequired, @@ -74,7 +68,6 @@ const propTypes = { selectedDirentList: PropTypes.array.isRequired, onSelectedDirentListUpdate: PropTypes.func.isRequired, onItemsMove: PropTypes.func.isRequired, - onItemsCopy: PropTypes.func.isRequired, onItemsDelete: PropTypes.func.isRequired, repoTags: PropTypes.array.isRequired, onFileTagChanged: PropTypes.func, @@ -170,18 +163,15 @@ class DirColumnView extends React.Component { onNodeClick={this.props.onNodeClick} onNodeCollapse={this.props.onNodeCollapse} onNodeExpanded={this.props.onNodeExpanded} - onAddFolderNode={this.props.onAddFolderNode} - onAddFileNode={this.props.onAddFileNode} onRenameNode={this.props.onRenameNode} onDeleteNode={this.props.onDeleteNode} repoID={this.props.repoID} + eventBus={this.props.eventBus} navRate={navRate} inResizing={inResizing} currentRepoInfo={this.props.currentRepoInfo} updateRepoInfo={this.props.updateRepoInfo} onItemMove={this.props.onItemMove} - onItemCopy={this.props.onItemCopy} - selectedDirentList={this.props.selectedDirentList} onItemsMove={this.props.onItemsMove} getMenuContainerSize={this.getMenuContainerSize} direntList={this.props.direntList} @@ -214,7 +204,6 @@ class DirColumnView extends React.Component { renameFileCallback={this.props.renameFileCallback} moveFileCallback={this.props.moveFileCallback} copyFileCallback={this.props.copyFileCallback} - addFolder={this.props.onAddFolder} updateCurrentDirent={this.props.updateCurrentDirent} showDirentDetail={this.props.showDirentDetail} updateCurrentPath={this.props.updateCurrentPath} @@ -234,7 +223,6 @@ class DirColumnView extends React.Component { moveFileCallback={this.props.moveFileCallback} copyFileCallback={this.props.copyFileCallback} convertFileCallback={this.props.convertFileCallback} - addFolderCallback={this.props.onAddFolder} toggleShowDirentToolbar={this.props.toggleShowDirentToolbar} /> )} @@ -255,14 +243,11 @@ class DirColumnView extends React.Component { sortBy={this.props.sortBy} sortOrder={this.props.sortOrder} sortItems={this.props.sortItems} - onAddFolder={this.props.onAddFolder} - onAddFile={this.props.onAddFile} onItemClick={this.props.onItemClick} onItemSelected={this.props.onItemSelected} onItemDelete={this.props.onItemDelete} onItemRename={this.props.onItemRename} onItemMove={this.props.onItemMove} - onItemCopy={this.props.onItemCopy} onItemConvert={this.props.onItemConvert} onDirentClick={this.props.onDirentClick} updateDirent={this.props.updateDirent} @@ -270,7 +255,6 @@ class DirColumnView extends React.Component { onAllItemSelected={this.props.onAllItemSelected} selectedDirentList={this.props.selectedDirentList} onItemsMove={this.props.onItemsMove} - onItemsCopy={this.props.onItemsCopy} onItemsDelete={this.props.onItemsDelete} repoTags={this.props.repoTags} onFileTagChanged={this.props.onFileTagChanged} @@ -288,7 +272,6 @@ class DirColumnView extends React.Component { isGroupOwnedRepo={this.props.isGroupOwnedRepo} userPerm={this.props.userPerm} enableDirPrivateShare={this.props.enableDirPrivateShare} - onRenameNode={this.props.onRenameNode} isRepoInfoBarShow={this.props.isRepoInfoBarShow} repoTags={this.props.repoTags} usedRepoTags={this.props.usedRepoTags} @@ -298,20 +281,15 @@ class DirColumnView extends React.Component { fullDirentList={this.props.fullDirentList} selectedDirentList={this.props.selectedDirentList} onSelectedDirentListUpdate={this.props.onSelectedDirentListUpdate} - onAddFile={this.props.onAddFile} onItemClick={this.props.onItemClick} onItemDelete={this.props.onItemDelete} onItemMove={this.props.onItemMove} - onItemCopy={this.props.onItemCopy} onItemConvert={this.props.onItemConvert} onItemsMove={this.props.onItemsMove} - onItemsCopy={this.props.onItemsCopy} onItemsDelete={this.props.onItemsDelete} updateDirent={this.props.updateDirent} - onAddFolder={this.props.onAddFolder} showDirentDetail={this.props.showDirentDetail} onGridItemClick={this.props.onDirentClick} - onItemRename={this.props.onItemRename} onFileTagChanged={this.props.onFileTagChanged} getMenuContainerSize={this.getMenuContainerSize} eventBus={this.props.eventBus} diff --git a/frontend/src/components/dir-view-mode/dir-files.js b/frontend/src/components/dir-view-mode/dir-files.js index 4c691ad63f..0af977fc92 100644 --- a/frontend/src/components/dir-view-mode/dir-files.js +++ b/frontend/src/components/dir-view-mode/dir-files.js @@ -2,11 +2,6 @@ import React from 'react'; import PropTypes from 'prop-types'; import TreeView from '../tree-view/tree-view'; import ModalPortal from '../modal-portal'; -import Rename from '../dialog/rename-dialog'; -import Copy from '../dialog/copy-dirent-dialog'; -import Move from '../dialog/move-dirent-dialog'; -import CreateFolder from '../dialog/create-folder-dialog'; -import CreateFile from '../dialog/create-file-dialog'; import ImageDialog from '../dialog/image-dialog'; import toaster from '../toast'; import ItemDropdownMenu from '../dropdown-menu/item-dropdown-menu'; @@ -16,6 +11,7 @@ import TextTranslation from '../../utils/text-translation'; import TreeSection from '../tree-section'; import imageAPI from '../../utils/image-api'; import { seafileAPI } from '../../utils/seafile-api'; +import { EVENT_BUS_TYPE } from '../common/event-bus-type'; const propTypes = { repoID: PropTypes.string.isRequired, @@ -24,17 +20,14 @@ const propTypes = { userPerm: PropTypes.string.isRequired, currentRepoInfo: PropTypes.object.isRequired, direntList: PropTypes.array, - selectedDirentList: PropTypes.array.isRequired, currentNode: PropTypes.object, + eventBus: PropTypes.object, getMenuContainerSize: PropTypes.func, onNodeClick: PropTypes.func.isRequired, onNodeCollapse: PropTypes.func.isRequired, onNodeExpanded: PropTypes.func.isRequired, onRenameNode: PropTypes.func.isRequired, onDeleteNode: PropTypes.func.isRequired, - onAddFileNode: PropTypes.func.isRequired, - onAddFolderNode: PropTypes.func.isRequired, - onItemCopy: PropTypes.func.isRequired, onItemMove: PropTypes.func.isRequired, onItemsMove: PropTypes.func.isRequired, updateDirent: PropTypes.func, @@ -48,13 +41,9 @@ class DirFiles extends React.Component { opNode: null, isAddFileDialogShow: false, isAddFolderDialogShow: false, - isRenameDialogShow: false, isNodeImagePopupOpen: false, imageNodeItems: [], imageIndex: 0, - isCopyDialogShow: false, - isMoveDialogShow: false, - isMultipleOperation: false, operationList: [], isDisplayFiles: localStorage.getItem('sf_display_files') === 'true' || false, }; @@ -109,34 +98,44 @@ class DirFiles extends React.Component { }; onMenuItemClick = (operation, node) => { + const { eventBus, treeData, onRenameNode } = this.props; this.setState({ opNode: node }); switch (operation) { - case 'New Folder': - if (!node) { - this.onAddFolderToggle('root'); - } else { - this.onAddFolderToggle(); - } + case 'New Folder': { + const validNode = node || treeData.root; + const parentNode = validNode.parentNode ? validNode.parentNode : validNode; + const children = parentNode.children.map(item => item.object); + eventBus.dispatch(EVENT_BUS_TYPE.CREATE_FOLDER, validNode.path, children); break; - case 'New File': - if (!node) { - this.onAddFileToggle('root'); - } else { - this.onAddFileToggle(); - } + } + case 'New File': { + const validNode = node || treeData.root; + const parentNode = validNode.parentNode ? validNode.parentNode : validNode; + const children = parentNode.children.map(item => item.object); + eventBus.dispatch(EVENT_BUS_TYPE.CREATE_FILE, validNode.path, children); break; - case 'Rename': - this.onRenameToggle(); + } + case 'Rename': { + const parentNode = node.parentNode ? node.parentNode : node; + const children = parentNode.children.map(item => item.object); + eventBus.dispatch(EVENT_BUS_TYPE.RENAME_FILE, node.object, children, (newName) => onRenameNode(node, newName)); break; + } case 'Delete': this.onDeleteNode(node); break; - case 'Copy': - this.onCopyToggle(); + case 'Copy': { + const path = node.parentNode.path; + const dirent = node.object; + eventBus.dispatch(EVENT_BUS_TYPE.COPY_FILE, path, dirent, false); break; - case 'Move': - this.onMoveToggle(); + } + case 'Move': { + const path = node.parentNode.path; + const dirent = node.object; + eventBus.dispatch(EVENT_BUS_TYPE.MOVE_FILE, path, dirent, false); break; + } case 'Open in New Tab': this.onOpenFile(node); break; @@ -146,53 +145,6 @@ class DirFiles extends React.Component { } }; - onAddFileToggle = (type) => { - if (type === 'root') { - let root = this.props.treeData.root; - this.setState({ - isAddFileDialogShow: !this.state.isAddFileDialogShow, - opNode: root, - }); - } else { - this.setState({ isAddFileDialogShow: !this.state.isAddFileDialogShow }); - } - }; - - onAddFolderToggle = (type) => { - if (type === 'root') { - let root = this.props.treeData.root; - this.setState({ - isAddFolderDialogShow: !this.state.isAddFolderDialogShow, - opNode: root, - }); - } else { - this.setState({ isAddFolderDialogShow: !this.state.isAddFolderDialogShow }); - } - }; - - onRenameToggle = () => { - this.setState({ isRenameDialogShow: !this.state.isRenameDialogShow }); - }; - - onCopyToggle = () => { - this.setState({ isCopyDialogShow: !this.state.isCopyDialogShow }); - }; - - onMoveToggle = () => { - this.setState({ isMoveDialogShow: !this.state.isMoveDialogShow }); - }; - - onAddFolderNode = (dirPath) => { - this.setState({ isAddFolderDialogShow: !this.state.isAddFolderDialogShow }); - this.props.onAddFolderNode(dirPath); - }; - - onRenameNode = (newName) => { - this.setState({ isRenameDialogShow: !this.state.isRenameDialogShow }); - let node = this.state.opNode; - this.props.onRenameNode(node, newName); - }; - onDeleteNode = (node) => { this.props.onDeleteNode(node); }; @@ -365,7 +317,6 @@ class DirFiles extends React.Component { render() { const { repoID, currentRepoInfo, userPerm } = this.props; - const { encrypted: repoEncrypted } = currentRepoInfo; const { isCustomPermission, customPermission } = Utils.getUserPermission(userPerm); let canModifyFile = false; if (['rw', 'cloud-edit'].indexOf(userPerm) != -1) { @@ -391,7 +342,6 @@ class DirFiles extends React.Component { treeData={this.props.treeData} currentPath={this.props.currentPath} currentRepoInfo={currentRepoInfo} - selectedDirentList={this.props.selectedDirentList} isDisplayFiles={this.state.isDisplayFiles} isNodeMenuShow={this.isNodeMenuShow} onNodeClick={this.onNodeClick} @@ -403,62 +353,6 @@ class DirFiles extends React.Component { onItemsMove={this.props.onItemsMove} /> - {this.state.isAddFolderDialogShow && ( - - - - )} - {this.state.isAddFileDialogShow && ( - - - - )} - {this.state.isRenameDialogShow && ( - - - - )} - {this.state.isCopyDialogShow && ( - - - - )} - {this.state.isMoveDialogShow && ( - - - - )} {this.state.isNodeImagePopupOpen && ( ); @@ -79,22 +72,16 @@ class DirGridView extends React.Component { fullDirentList={this.props.fullDirentList} selectedDirentList={this.props.selectedDirentList} onSelectedDirentListUpdate={this.props.onSelectedDirentListUpdate} - onAddFile={this.props.onAddFile} onItemClick={this.props.onItemClick} onItemDelete={this.props.onItemDelete} onItemMove={this.props.onItemMove} - onItemCopy={this.props.onItemCopy} onItemConvert={this.props.onItemConvert} onItemsMove={this.props.onItemsMove} - onItemsCopy={this.props.onItemsCopy} onItemsDelete={this.props.onItemsDelete} isDirentListLoading={this.props.isDirentListLoading} updateDirent={this.props.updateDirent} - onRenameNode={this.props.onRenameNode} showDirentDetail={this.props.showDirentDetail} onGridItemClick={this.props.onGridItemClick} - onItemRename={this.props.onItemRename} - onAddFolder={this.props.onAddFolder} repoTags={this.props.repoTags} onFileTagChanged={this.props.onFileTagChanged} getMenuContainerSize={this.props.getMenuContainerSize} diff --git a/frontend/src/components/dir-view-mode/dir-list-view.js b/frontend/src/components/dir-view-mode/dir-list-view.js index c4c4f8ea7d..41a6b123e7 100644 --- a/frontend/src/components/dir-view-mode/dir-list-view.js +++ b/frontend/src/components/dir-view-mode/dir-list-view.js @@ -21,21 +21,17 @@ const propTypes = { sortBy: PropTypes.string.isRequired, sortOrder: PropTypes.string.isRequired, sortItems: PropTypes.func.isRequired, - onAddFolder: PropTypes.func.isRequired, - onAddFile: PropTypes.func.isRequired, onItemClick: PropTypes.func.isRequired, onItemSelected: PropTypes.func.isRequired, onItemDelete: PropTypes.func.isRequired, onItemRename: PropTypes.func.isRequired, onItemMove: PropTypes.func.isRequired, - onItemCopy: PropTypes.func.isRequired, onDirentClick: PropTypes.func.isRequired, updateDirent: PropTypes.func.isRequired, isAllItemSelected: PropTypes.bool.isRequired, onAllItemSelected: PropTypes.func.isRequired, selectedDirentList: PropTypes.array.isRequired, onItemsMove: PropTypes.func.isRequired, - onItemsCopy: PropTypes.func.isRequired, onItemConvert: PropTypes.func.isRequired, onItemsDelete: PropTypes.func.isRequired, onFileTagChanged: PropTypes.func, @@ -56,10 +52,9 @@ class DirListView extends React.Component { ); @@ -96,18 +91,14 @@ class DirListView extends React.Component { onItemDelete={this.props.onItemDelete} onItemRename={this.props.onItemRename} onItemMove={this.props.onItemMove} - onItemCopy={this.props.onItemCopy} onDirentClick={this.props.onDirentClick} updateDirent={this.props.updateDirent} isAllItemSelected={this.props.isAllItemSelected} onAllItemSelected={this.props.onAllItemSelected} selectedDirentList={this.props.selectedDirentList} onItemsMove={this.props.onItemsMove} - onItemsCopy={this.props.onItemsCopy} onItemConvert={this.props.onItemConvert} onItemsDelete={this.props.onItemsDelete} - onAddFile={this.props.onAddFile} - onAddFolder={this.props.onAddFolder} repoTags={this.props.repoTags} onFileTagChanged={this.props.onFileTagChanged} showDirentDetail={this.props.showDirentDetail} diff --git a/frontend/src/components/dirent-grid-view/dirent-grid-view.js b/frontend/src/components/dirent-grid-view/dirent-grid-view.js index 2a25ccbd76..4230b32155 100644 --- a/frontend/src/components/dirent-grid-view/dirent-grid-view.js +++ b/frontend/src/components/dirent-grid-view/dirent-grid-view.js @@ -11,16 +11,8 @@ import DirentGridItem from '../../components/dirent-grid-view/dirent-grid-item'; import ContextMenu from '../context-menu/context-menu'; import { hideMenu, showMenu } from '../context-menu/actions'; import TextTranslation from '../../utils/text-translation'; -import MoveDirentDialog from '../dialog/move-dirent-dialog'; -import CopyDirentDialog from '../dialog/copy-dirent-dialog'; -import ShareDialog from '../dialog/share-dialog'; -import Rename from '../../components/dialog/rename-dirent'; -import CreateFile from '../dialog/create-file-dialog'; -import CreateFolder from '../dialog/create-folder-dialog'; -import LibSubFolderPermissionDialog from '../dialog/lib-sub-folder-permission-dialog'; import toaster from '../toast'; import imageAPI from '../../utils/image-api'; -import FileAccessLog from '../dialog/file-access-log'; import { EVENT_BUS_TYPE } from '../common/event-bus-type'; import EmptyTip from '../empty-tip'; import { Dirent } from '../../models'; @@ -35,26 +27,19 @@ const propTypes = { fullDirentList: PropTypes.array, selectedDirentList: PropTypes.array.isRequired, onSelectedDirentListUpdate: PropTypes.func.isRequired, - onAddFile: PropTypes.func, onItemDelete: PropTypes.func, - onItemCopy: PropTypes.func.isRequired, onItemConvert: PropTypes.func.isRequired, onItemMove: PropTypes.func.isRequired, onItemsMove: PropTypes.func.isRequired, - onItemsCopy: PropTypes.func.isRequired, onItemsDelete: PropTypes.func.isRequired, - onRenameNode: PropTypes.func.isRequired, onItemClick: PropTypes.func.isRequired, isDirentListLoading: PropTypes.bool.isRequired, - isGroupOwnedRepo: PropTypes.bool.isRequired, userPerm: PropTypes.string, // current path's user permission enableDirPrivateShare: PropTypes.bool.isRequired, updateDirent: PropTypes.func.isRequired, onGridItemClick: PropTypes.func, repoTags: PropTypes.array.isRequired, - onAddFolder: PropTypes.func.isRequired, showDirentDetail: PropTypes.func.isRequired, - onItemRename: PropTypes.func.isRequired, posX: PropTypes.number, posY: PropTypes.number, dirent: PropTypes.object, @@ -73,18 +58,6 @@ class DirentGridView extends React.Component { isImagePopupOpen: false, imageItems: [], imageIndex: 0, - // onmenuClick - isFileAccessLogDialogOpen: false, - isShareDialogShow: false, - isMoveDialogShow: false, - isCopyDialogShow: false, - isRenameDialogShow: false, - isCreateFolderDialogShow: false, - isCreateFileDialogShow: false, - fileType: '', - isPermissionDialogOpen: false, - - isMultipleOperation: true, isGridItemFreezed: false, activeDirent: null, downloadItems: [], @@ -256,13 +229,6 @@ class DirentGridView extends React.Component { } }; - onCreateFileToggle = (fileType) => { - this.setState({ - isCreateFileDialogShow: !this.state.isCreateFileDialogShow, - fileType: fileType || '' - }); - }; - onGridItemClick = (dirent, event) => { hideMenu(); if (this.state.activeDirent !== dirent) { @@ -271,28 +237,6 @@ class DirentGridView extends React.Component { this.props.onGridItemClick(dirent, event); }; - onMoveToggle = () => { - this.setState({ isMoveDialogShow: !this.state.isMoveDialogShow }); - }; - - onCopyToggle = () => { - this.setState({ isCopyDialogShow: !this.state.isCopyDialogShow }); - }; - - onAddFolder = (dirPath) => { - this.setState({ isCreateFolderDialogShow: false }); - this.props.onAddFolder(dirPath); - }; - - onItemShare = (e) => { - e.nativeEvent.stopImmediatePropagation(); // for document event - this.setState({ isShareDialogShow: !this.state.isShareDialogShow }); - }; - - closeSharedDialog = () => { - this.setState({ isShareDialogShow: !this.state.isShareDialogShow }); - }; - onItemDelete = (currentObject, e) => { e.nativeEvent.stopImmediatePropagation(); // for document event if (this.props.selectedDirentList.length === 1) { @@ -334,22 +278,22 @@ class DirentGridView extends React.Component { hideMenu(); switch (operation) { case 'Download': - this.onItemsDownload(); + this.onDownload(); break; case 'Share': - this.onItemShare(event); + this.onShare(event); break; case 'Delete': this.onItemDelete(currentObject, event); break; case 'Rename': - this.onItemRenameToggle(); + this.onRename(); break; case 'Move': - this.onItemMoveToggle(); + this.onMove(); break; case 'Copy': - this.onItemCopyToggle(); + this.onCopy(); break; case 'Star': this.onToggleStarItem(); @@ -379,7 +323,7 @@ class DirentGridView extends React.Component { this.onItemConvert(currentObject, event, 'sdoc'); break; case 'Permission': - this.onPermissionItem(); + this.onPermission(); break; case 'Unlock': this.onUnlockItem(currentObject); @@ -391,34 +335,34 @@ class DirentGridView extends React.Component { this.onHistory(currentObject); break; case 'New Folder': - this.onCreateFolderToggle(currentObject); + this.onCreateFolder(); break; case 'New File': - this.onCreateFileToggle(''); + this.onCreateFile(''); break; case 'New Markdown File': - this.onCreateFileToggle('.md'); + this.onCreateFile('.md'); break; case 'New Excel File': - this.onCreateFileToggle('.xlsx'); + this.onCreateFile('.xlsx'); break; case 'New PowerPoint File': - this.onCreateFileToggle('.pptx'); + this.onCreateFile('.pptx'); break; case 'New Word File': - this.onCreateFileToggle('.docx'); + this.onCreateFile('.docx'); break; case 'New Whiteboard File': - this.onCreateFileToggle('.draw'); + this.onCreateFile('.draw'); break; case 'New Excalidraw File': - this.onCreateFileToggle('.exdraw'); + this.onCreateFile('.exdraw'); break; case 'New SeaDoc File': - this.onCreateFileToggle('.sdoc'); + this.onCreateFile('.sdoc'); break; case 'Access Log': - this.toggleFileAccessLogDialog(); + this.openFileAccessLog(); break; case 'Properties': this.props.showDirentDetail('info'); @@ -434,13 +378,13 @@ class DirentGridView extends React.Component { onDirentsMenuItemClick = (operation) => { switch (operation) { case 'Move': - this.onMoveToggle(); + this.onMove(); break; case 'Copy': - this.onCopyToggle(); + this.onCopy(); break; case 'Download': - this.onItemsDownload(); + this.onDownload(); break; case 'Delete': this.props.onItemsDelete(); @@ -457,30 +401,63 @@ class DirentGridView extends React.Component { return path === '/' ? path + dirent.name : path + '/' + dirent.name; }; - onItemsDownload = () => { + onMove = () => { + const { path, selectedDirentList, eventBus } = this.props; + eventBus.dispatch(EVENT_BUS_TYPE.MOVE_FILE, path, selectedDirentList, true); + }; + + onCopy = () => { + const { path, selectedDirentList, eventBus } = this.props; + eventBus.dispatch(EVENT_BUS_TYPE.COPY_FILE, path, selectedDirentList, true); + }; + + onDownload = () => { const { path, selectedDirentList, eventBus } = this.props; const direntList = selectedDirentList.map(dirent => dirent instanceof Dirent ? dirent.toJson() : dirent); eventBus.dispatch(EVENT_BUS_TYPE.DOWNLOAD_FILE, path, direntList); }; - onCreateFolderToggle = () => { - this.setState({ - isCreateFolderDialogShow: !this.state.isCreateFolderDialogShow, - }); + onCreateFolder = () => { + const { path, direntList, eventBus } = this.props; + eventBus.dispatch(EVENT_BUS_TYPE.CREATE_FOLDER, path, direntList); }; - onItemRenameToggle = () => { - this.setState({ - isRenameDialogShow: !this.state.isRenameDialogShow, - }); + onCreateFile = (fileType = '') => { + const { path, direntList, eventBus } = this.props; + eventBus.dispatch(EVENT_BUS_TYPE.CREATE_FILE, path, direntList, fileType); }; - onItemMoveToggle = () => { - this.setState({ isMoveDialogShow: !this.state.isMoveDialogShow }); + onShare = (e) => { + e.nativeEvent.stopImmediatePropagation(); // for document event + + const { path, eventBus } = this.props; + const { activeDirent } = this.state; + const dirent = activeDirent || ''; + const direntPath = Utils.joinPath(path, dirent.name); + eventBus.dispatch(EVENT_BUS_TYPE.SHARE_FILE, direntPath, dirent); }; - onItemCopyToggle = () => { - this.setState({ isCopyDialogShow: !this.state.isCopyDialogShow }); + onRename = () => { + const { eventBus, direntList } = this.props; + const { activeDirent } = this.state; + const dirent = activeDirent || ''; + eventBus.dispatch(EVENT_BUS_TYPE.RENAME_FILE, dirent, direntList); + }; + + onPermission = () => { + const { eventBus, path } = this.props; + const { activeDirent } = this.state; + const dirent = activeDirent || ''; + const direntPath = Utils.joinPath(path, dirent?.name); + eventBus.dispatch(EVENT_BUS_TYPE.PERMISSION, dirent, direntPath); + }; + + openFileAccessLog = () => { + const { eventBus, path } = this.props; + const { activeDirent } = this.state; + const dirent = activeDirent || ''; + const direntPath = Utils.joinPath(path, dirent?.name); + eventBus.dispatch(EVENT_BUS_TYPE.ACCESS_LOG, dirent, direntPath); }; onToggleStarItem = () => { @@ -512,10 +489,6 @@ class DirentGridView extends React.Component { } }; - onPermissionItem = () => { - this.setState({ isPermissionDialogOpen: !this.state.isPermissionDialogOpen }); - }; - handleError = (error) => { toaster.danger(Utils.getErrorMsg(error)); }; @@ -566,12 +539,6 @@ class DirentGridView extends React.Component { location.href = url; }; - toggleFileAccessLogDialog = () => { - this.setState({ - isFileAccessLogDialogOpen: !this.state.isFileAccessLogDialogOpen - }); - }; - onOpenViaClient = (currentObject) => { let repoID = this.props.repoID; let filePath = this.getDirentPath(currentObject); @@ -579,10 +546,6 @@ class DirentGridView extends React.Component { location.href = url; }; - onItemRename = (newName) => { - this.props.onItemRename(this.state.activeDirent, newName); - }; - prepareImageItem = (item) => { const { path: parentDir, repoID, currentRepoInfo } = this.props; const { name, mtime, id } = item; @@ -851,10 +814,8 @@ class DirentGridView extends React.Component { }; render() { - const { direntList, selectedDirentList, path, currentRepoInfo, userPerm } = this.props; + const { direntList, selectedDirentList, currentRepoInfo, userPerm } = this.props; const { encrypted: repoEncrypted } = currentRepoInfo; - let dirent = this.state.activeDirent ? this.state.activeDirent : ''; - let direntPath = Utils.joinPath(path, dirent.name); let canModifyFile = false; let canDeleteFile = false; @@ -934,89 +895,6 @@ class DirentGridView extends React.Component { onMenuItemClick={this.onDirentsMenuItemClick} getMenuContainerSize={this.props.getMenuContainerSize} /> - {this.state.isCreateFolderDialogShow && ( - - - - )} - {this.state.isCreateFileDialogShow && ( - - - - )} - {this.state.isMoveDialogShow && - - } - {this.state.isCopyDialogShow && - - } - {this.state.isShareDialogShow && - - - - } - {this.state.isRenameDialogShow && ( - - 1 ? selectedDirentList[selectedDirentList.length - 1] : this.state.activeDirent} - onRename={this.onItemRename} - checkDuplicatedName={this.checkDuplicatedName} - toggleCancel={this.onItemRenameToggle} - /> - - )} - {this.state.isPermissionDialogOpen && - - - - } {this.state.isImagePopupOpen && this.state.imageItems.length && ( )} - {this.state.isFileAccessLogDialogOpen && - - - - } ); } diff --git a/frontend/src/components/dirent-list-view/dirent-list-item.js b/frontend/src/components/dirent-list-view/dirent-list-item.js index b25f3ed756..281b7c05e4 100644 --- a/frontend/src/components/dirent-list-view/dirent-list-item.js +++ b/frontend/src/components/dirent-list-view/dirent-list-item.js @@ -10,12 +10,6 @@ import URLDecorator from '../../utils/url-decorator'; import { imageThumbnailCenter, videoThumbnailCenter } from '../../utils/thumbnail-center'; import ItemDropdownMenu from '../dropdown-menu/item-dropdown-menu'; import Rename from '../rename'; -import ModalPortal from '../modal-portal'; -import MoveDirentDialog from '../dialog/move-dirent-dialog'; -import CopyDirentDialog from '../dialog/copy-dirent-dialog'; -import ShareDialog from '../dialog/share-dialog'; -import LibSubFolderPermissionDialog from '../dialog/lib-sub-folder-permission-dialog'; -import FileAccessLog from '../dialog/file-access-log'; import toaster from '../toast'; import MobileItemMenu from '../../components/mobile-item-menu'; import { EVENT_BUS_TYPE } from '../common/event-bus-type'; @@ -37,7 +31,6 @@ const propTypes = { onItemDelete: PropTypes.func.isRequired, onItemRename: PropTypes.func.isRequired, onItemMove: PropTypes.func.isRequired, - onItemCopy: PropTypes.func.isRequired, onItemConvert: PropTypes.func.isRequired, onDirentClick: PropTypes.func.isRequired, updateDirent: PropTypes.func.isRequired, @@ -46,7 +39,6 @@ const propTypes = { isRepoOwner: PropTypes.bool, isAdmin: PropTypes.bool.isRequired, repoEncrypted: PropTypes.bool.isRequired, - isGroupOwnedRepo: PropTypes.bool.isRequired, onItemMouseDown: PropTypes.func.isRequired, onItemContextMenu: PropTypes.func.isRequired, selectedDirentList: PropTypes.array.isRequired, @@ -82,16 +74,10 @@ class DirentListItem extends React.Component { dirent, isOperationShow: false, highlight: false, - isFileAccessLogDialogOpen: false, - isMoveDialogShow: false, - isCopyDialogShow: false, - isShareDialogShow: false, - isMultipleOperation: false, canDrag: this.canDrag, isShowTagTooltip: false, isDragTipShow: false, - isDropTipshow: false, - isPermissionDialogOpen: false + isDropTipShow: false, }; this.isGeneratingThumbnail = false; this.thumbnailCenter = null; @@ -302,10 +288,6 @@ class DirentListItem extends React.Component { this.props.onItemDelete(this.state.dirent); }; - onItemShare = () => { - this.setState({ isShareDialogShow: !this.state.isShareDialogShow }); - }; - exportDocx = () => { const serviceUrl = window.app.config.serviceURL; let repoID = this.props.repoID; @@ -322,10 +304,6 @@ class DirentListItem extends React.Component { window.location.href = exportToSdocUrl; }; - closeSharedDialog = () => { - this.setState({ isShareDialogShow: !this.state.isShareDialogShow }); - }; - onMobileMenuItemClick = (e) => { const operation = e.target.getAttribute('data-op'); this.onMenuItemClick(operation, e); @@ -346,13 +324,13 @@ class DirentListItem extends React.Component { this.onItemRenameToggle(); break; case 'Move': - this.onItemMoveToggle(); + this.onItemMove(); break; case 'Copy': - this.onItemCopyToggle(); + this.onItemCopy(); break; case 'Permission': - this.onPermissionItem(); + this.onPermission(); break; case 'Unlock': this.onUnlockItem(); @@ -385,7 +363,7 @@ class DirentListItem extends React.Component { this.onHistory(); break; case 'Access Log': - this.toggleFileAccessLogDialog(); + this.openFileAccessLog(); break; case 'Properties': this.props.onDirentClick(this.state.dirent); @@ -435,16 +413,18 @@ class DirentListItem extends React.Component { this.unfreezeItem(); }; - onItemMoveToggle = () => { - this.setState({ isMoveDialogShow: !this.state.isMoveDialogShow }); + onPermission = () => { + const { path, eventBus } = this.props; + const { dirent } = this.state; + const direntPath = Utils.joinPath(path, dirent.name); + eventBus.dispatch(EVENT_BUS_TYPE.PERMISSION, dirent, direntPath); }; - onItemCopyToggle = () => { - this.setState({ isCopyDialogShow: !this.state.isCopyDialogShow }); - }; - - onPermissionItem = () => { - this.setState({ isPermissionDialogOpen: !this.state.isPermissionDialogOpen }); + openFileAccessLog = () => { + const { path, eventBus } = this.props; + const { dirent } = this.state; + const direntPath = Utils.joinPath(path, dirent.name); + eventBus.dispatch(EVENT_BUS_TYPE.ACCESS_LOG, dirent, direntPath); }; onLockItem = () => { @@ -498,12 +478,6 @@ class DirentListItem extends React.Component { location.href = url; }; - toggleFileAccessLogDialog = () => { - this.setState({ - isFileAccessLogDialogOpen: !this.state.isFileAccessLogDialogOpen - }); - }; - onOpenViaClient = () => { let repoID = this.props.repoID; let filePath = this.getDirentPath(this.state.dirent); @@ -531,6 +505,25 @@ class DirentListItem extends React.Component { eventBus.dispatch(EVENT_BUS_TYPE.DOWNLOAD_FILE, path, direntList); }; + onItemMove = () => { + const { path, eventBus } = this.props; + const { dirent } = this.state; + eventBus.dispatch(EVENT_BUS_TYPE.MOVE_FILE, path, dirent, false); + }; + + onItemCopy = () => { + const { path, eventBus } = this.props; + const { dirent } = this.state; + eventBus.dispatch(EVENT_BUS_TYPE.COPY_FILE, path, dirent, false); + }; + + onItemShare = () => { + const { path, eventBus } = this.props; + const dirent = this.state.dirent; + const direntPath = Utils.joinPath(path, dirent.name); + eventBus.dispatch(EVENT_BUS_TYPE.SHARE_FILE, direntPath, dirent); + }; + getDirentPath = (dirent) => { let path = this.props.path; return path === '/' ? path + dirent.name : path + '/' + dirent.name; @@ -541,10 +534,6 @@ class DirentListItem extends React.Component { this.setState({ isShowTagTooltip: !this.state.isShowTagTooltip }); }; - onItemMove = (destRepo, dirent, selectedPath, currentPath) => { - this.props.onItemMove(destRepo, dirent, selectedPath, currentPath); - }; - onItemDragStart = (e) => { if (Utils.isIEBrowser() || !this.state.canDrag) { return false; @@ -581,7 +570,7 @@ class DirentListItem extends React.Component { } if (this.state.dirent.type === 'dir') { e.stopPropagation(); - this.setState({ isDropTipshow: true }); + this.setState({ isDropTipShow: true }); } }; @@ -604,14 +593,14 @@ class DirentListItem extends React.Component { if (this.state.dirent.type === 'dir') { e.stopPropagation(); } - this.setState({ isDropTipshow: false }); + this.setState({ isDropTipShow: false }); }; onItemDragDrop = (e) => { if (Utils.isIEBrowser() || !this.state.canDrag) { return false; } - this.setState({ isDropTipshow: false }); + this.setState({ isDropTipShow: false }); if (e.dataTransfer.files.length) { // uploaded files return; } @@ -654,7 +643,7 @@ class DirentListItem extends React.Component { } let selectedPath = Utils.joinPath(this.props.path, this.state.dirent.name); - this.onItemMove(this.props.currentRepoInfo, nodeDirent, selectedPath, nodeParentPath); + this.props.onItemMove(this.props.currentRepoInfo, nodeDirent, selectedPath, nodeParentPath); }; onItemMouseDown = (event) => { @@ -759,9 +748,7 @@ class DirentListItem extends React.Component { }; render() { - let { path } = this.props; let dirent = this.state.dirent; - let direntPath = Utils.joinPath(path, dirent.name); let iconUrl = Utils.getDirentIcon(dirent); @@ -781,7 +768,7 @@ class DirentListItem extends React.Component { } - {this.state.isMoveDialogShow && - - - - } - {this.state.isCopyDialogShow && - - - - } - {this.state.isShareDialogShow && - - - - } - {this.state.isPermissionDialogOpen && - - - - } - {this.state.isFileAccessLogDialogOpen && - - - - } > ); } diff --git a/frontend/src/components/dirent-list-view/dirent-list-view.js b/frontend/src/components/dirent-list-view/dirent-list-view.js index 869ed8f397..9092093ec9 100644 --- a/frontend/src/components/dirent-list-view/dirent-list-view.js +++ b/frontend/src/components/dirent-list-view/dirent-list-view.js @@ -6,11 +6,7 @@ import { Utils } from '../../utils/utils'; import TextTranslation from '../../utils/text-translation'; import toaster from '../toast'; import ModalPortal from '../modal-portal'; -import CreateFile from '../dialog/create-file-dialog'; -import CreateFolder from '../dialog/create-folder-dialog'; import ImageDialog from '../dialog/image-dialog'; -import MoveDirentDialog from '../dialog/move-dirent-dialog'; -import CopyDirentDialog from '../dialog/copy-dirent-dialog'; import DirentListItem from './dirent-list-item'; import ContextMenu from '../context-menu/context-menu'; import { hideMenu, showMenu } from '../context-menu/actions'; @@ -31,20 +27,16 @@ const propTypes = { sortBy: PropTypes.string.isRequired, sortOrder: PropTypes.string.isRequired, sortItems: PropTypes.func.isRequired, - onAddFile: PropTypes.func.isRequired, - onAddFolder: PropTypes.func.isRequired, onItemDelete: PropTypes.func.isRequired, onAllItemSelected: PropTypes.func.isRequired, onItemSelected: PropTypes.func.isRequired, onItemRename: PropTypes.func.isRequired, onItemClick: PropTypes.func.isRequired, onItemMove: PropTypes.func.isRequired, - onItemCopy: PropTypes.func.isRequired, onDirentClick: PropTypes.func.isRequired, updateDirent: PropTypes.func.isRequired, selectedDirentList: PropTypes.array.isRequired, onItemsMove: PropTypes.func.isRequired, - onItemsCopy: PropTypes.func.isRequired, onItemConvert: PropTypes.func.isRequired, onItemsDelete: PropTypes.func.isRequired, repoTags: PropTypes.array.isRequired, @@ -70,13 +62,7 @@ class DirentListView extends React.Component { isImagePopupOpen: false, imageItems: [], imageIndex: 0, - fileType: '', - isCreateFileDialogShow: false, - isCreateFolderDialogShow: false, - isMoveDialogShow: false, - isCopyDialogShow: false, downloadItems: [], - isMultipleOperation: true, activeDirent: null, isListDropTipShow: false, isShowDirentsDraggablePreview: false, @@ -294,39 +280,27 @@ class DirentListView extends React.Component { this.setState({ isImagePopupOpen: false }); }; - onCreateFileToggle = (fileType) => { - this.setState({ - isCreateFileDialogShow: !this.state.isCreateFileDialogShow, - fileType: fileType || '' - }); + onCreateFolder = () => { + const { path, direntList, eventBus } = this.props; + eventBus.dispatch(EVENT_BUS_TYPE.CREATE_FOLDER, path, direntList); }; - onCreateFolderToggle = () => { - this.setState({ isCreateFolderDialogShow: !this.state.isCreateFolderDialogShow }); + onCreateFile = (fileType = '') => { + const { path, direntList, eventBus } = this.props; + eventBus.dispatch(EVENT_BUS_TYPE.CREATE_FILE, path, direntList, fileType); }; - onAddFolder = (dirPath) => { - this.setState({ isCreateFolderDialogShow: false }); - this.props.onAddFolder(dirPath); + onMove = () => { + const { path, selectedDirentList, eventBus } = this.props; + eventBus.dispatch(EVENT_BUS_TYPE.MOVE_FILE, path, selectedDirentList, true); }; - checkDuplicatedName = (newName) => { - let direntList = this.props.direntList; - let isDuplicated = direntList.some(object => { - return object.name === newName; - }); - return isDuplicated; + onCopy = () => { + const { path, selectedDirentList, eventBus } = this.props; + eventBus.dispatch(EVENT_BUS_TYPE.COPY_FILE, path, selectedDirentList, true); }; - onMoveToggle = () => { - this.setState({ isMoveDialogShow: !this.state.isMoveDialogShow }); - }; - - onCopyToggle = () => { - this.setState({ isCopyDialogShow: !this.state.isCopyDialogShow }); - }; - - onItemsDownload = () => { + onDownload = () => { const { path, selectedDirentList, eventBus } = this.props; const direntList = selectedDirentList.map(dirent => dirent instanceof Dirent ? dirent.toJson() : dirent); eventBus.dispatch(EVENT_BUS_TYPE.DOWNLOAD_FILE, path, direntList); @@ -483,31 +457,31 @@ class DirentListView extends React.Component { onContainerMenuItemClick = (operation) => { switch (operation) { case 'New Folder': - this.onCreateFolderToggle(); + this.onCreateFolder(); break; case 'New File': - this.onCreateFileToggle(); + this.onCreateFile(); break; case 'New Markdown File': - this.onCreateFileToggle('.md'); + this.onCreateFile('.md'); break; case 'New Excel File': - this.onCreateFileToggle('.xlsx'); + this.onCreateFile('.xlsx'); break; case 'New PowerPoint File': - this.onCreateFileToggle('.pptx'); + this.onCreateFile('.pptx'); break; case 'New Word File': - this.onCreateFileToggle('.docx'); + this.onCreateFile('.docx'); break; case 'New Whiteboard File': - this.onCreateFileToggle('.draw'); + this.onCreateFile('.draw'); break; case 'New Excalidraw File': - this.onCreateFileToggle('.exdraw'); + this.onCreateFile('.exdraw'); break; case 'New SeaDoc File': - this.onCreateFileToggle('.sdoc'); + this.onCreateFile('.sdoc'); break; default: break; @@ -519,13 +493,13 @@ class DirentListView extends React.Component { onDirentsMenuItemClick = (operation) => { switch (operation) { case 'Move': - this.onMoveToggle(); + this.onMove(); break; case 'Copy': - this.onCopyToggle(); + this.onCopy(); break; case 'Download': - this.onItemsDownload(); + this.onDownload(); break; case 'Delete': this.props.onItemsDelete(); @@ -738,8 +712,7 @@ class DirentListView extends React.Component { }; render() { - const { direntList, currentRepoInfo, userPerm } = this.props; - const { encrypted: repoEncrypted } = currentRepoInfo; + const { direntList, userPerm } = this.props; const isDesktop = Utils.isDesktop(); let canModifyFile = false; @@ -794,7 +767,6 @@ class DirentListView extends React.Component { onItemDelete={this.props.onItemDelete} onItemRename={this.onItemRename} onItemMove={this.props.onItemMove} - onItemCopy={this.props.onItemCopy} onItemConvert={this.props.onItemConvert} updateDirent={this.props.updateDirent} isItemFreezed={this.state.isItemFreezed} @@ -866,51 +838,6 @@ class DirentListView extends React.Component { /> )} - {this.state.isCreateFolderDialogShow && ( - - - - )} - {this.state.isCreateFileDialogShow && ( - - - - )} - {this.state.isMoveDialogShow && - - } - {this.state.isCopyDialogShow && - - } ); diff --git a/frontend/src/components/dirent-list-view/dirent-none-view.js b/frontend/src/components/dirent-list-view/dirent-none-view.js index c09dd2161c..371e6b7664 100644 --- a/frontend/src/components/dirent-list-view/dirent-none-view.js +++ b/frontend/src/components/dirent-list-view/dirent-none-view.js @@ -2,62 +2,33 @@ import React from 'react'; import PropTypes from 'prop-types'; import { enableSeadoc, gettext, enableWhiteboard, enableExcalidraw } from '../../utils/constants'; import Loading from '../loading'; -import ModalPortal from '../modal-portal'; -import CreateFile from '../../components/dialog/create-file-dialog'; -import CreateFolder from '../dialog/create-folder-dialog'; import TextTranslation from '../../utils/text-translation'; import { Utils } from '../../utils/utils'; import ContextMenu from '../context-menu/context-menu'; import { hideMenu, showMenu } from '../context-menu/actions'; import '../../css/tip-for-new-file.css'; +import { EVENT_BUS_TYPE } from '../common/event-bus-type'; const propTypes = { path: PropTypes.string.isRequired, isDirentListLoading: PropTypes.bool.isRequired, currentRepoInfo: PropTypes.object.isRequired, - onAddFile: PropTypes.func.isRequired, - onAddFolder: PropTypes.func.isRequired, + eventBus: PropTypes.object, getMenuContainerSize: PropTypes.func.isRequired, userPerm: PropTypes.string, }; class DirentNoneView extends React.Component { - constructor(props) { - super(props); - this.state = { - fileType: '', - isCreateFileDialogShow: false, - isCreateFolderDialogShow: false, - }; - } - - onCreateFolderToggle = () => { - this.setState({ isCreateFolderDialogShow: !this.state.isCreateFolderDialogShow }); + onCreateFile = (type) => { + const { eventBus, path } = this.props; + eventBus.dispatch(EVENT_BUS_TYPE.CREATE_FILE, path, [], type); }; - onAddFolder = (dirPath) => { - this.setState({ isCreateFolderDialogShow: false }); - this.props.onAddFolder(dirPath); - }; - - onCreateNewFile = (type) => { - this.setState({ - fileType: type, - isCreateFileDialogShow: !this.state.isCreateFileDialogShow, - }); - }; - - onCreateFileToggle = () => { - this.setState({ - fileType: '', - isCreateFileDialogShow: !this.state.isCreateFileDialogShow, - }); - }; - - checkDuplicatedName = () => { - return false; // current repo is null, and unnecessary to check duplicated name + onCreateFolder = () => { + const { eventBus, path } = this.props; + eventBus.dispatch(EVENT_BUS_TYPE.CREATE_FOLDER, path, []); }; onContainerContextMenu = (event) => { @@ -133,28 +104,28 @@ class DirentNoneView extends React.Component { onContainerMenuItemClick = (operation) => { switch (operation) { case 'New Folder': - this.onCreateFolderToggle(); + this.onCreateFolder(); break; case 'New File': - this.onCreateNewFile(''); + this.onCreateFile(''); break; case 'New Markdown File': - this.onCreateNewFile('.md'); + this.onCreateFile('.md'); break; case 'New Excel File': - this.onCreateNewFile('.xlsx'); + this.onCreateFile('.xlsx'); break; case 'New PowerPoint File': - this.onCreateNewFile('.pptx'); + this.onCreateFile('.pptx'); break; case 'New Word File': - this.onCreateNewFile('.docx'); + this.onCreateFile('.docx'); break; case 'New Whiteboard File': - this.onCreateNewFile('.draw'); + this.onCreateFile('.draw'); break; case 'New SeaDoc File': - this.onCreateNewFile('.sdoc'); + this.onCreateFile('.sdoc'); break; default: break; @@ -185,39 +156,18 @@ class DirentNoneView extends React.Component { {canCreateFile && ( <> {gettext('You can create files quickly')}{' +'} - {'+ Markdown'} - {'+ PPT'} + {'+ Markdown'} + {'+ PPT'} - {'+ Word'} - {'+ Excel'} + {'+ Word'} + {'+ Excel'} {enableSeadoc && !currentRepoInfo.encrypted && - {'+ SeaDoc'} + {'+ SeaDoc'} } > )} - {this.state.isCreateFileDialogShow && ( - - - - )} - {this.state.isCreateFolderDialogShow && - - - - } { - if (!this.isComponentMounted) return; - const direntData = res.data.dirent_list.find(item => item.type === 'dir' && item.name === newFolderName); - if (direntData) { - const object = new Dirent(direntData); - const direntNode = new TreeNode({ object }); - const newTreeData = treeHelper.addNodeToParentByPath(this.state.treeData, direntNode, selectedPath); - this.setState({ treeData: newTreeData }); - } + const { dirent_list = [], dir_id = '', user_perm = 'r' } = res?.data || {}; + const dirent = dirent_list.find(item => item.type === 'dir' && item.name === newFolderName); + const direntData = dirent || new Dirent({ name: newFolderName, tye: 'dir', id: dir_id, permission: user_perm }); + const object = new Dirent(direntData); + const direntNode = new TreeNode({ object }); + const path = dirent ? selectedPath : '/' + selectedPath.split('/').slice(0, -1).join('/'); + const newTreeData = treeHelper.addNodeToParentByPath(this.state.treeData, direntNode, path); + this.setState({ treeData: newTreeData }); }).catch(error => { if (!this.isComponentMounted) return; const errMessage = Utils.getErrorMsg(error); diff --git a/frontend/src/components/sf-table/table-main/records/index.js b/frontend/src/components/sf-table/table-main/records/index.js index 1e4da9d117..73e02a5678 100644 --- a/frontend/src/components/sf-table/table-main/records/index.js +++ b/frontend/src/components/sf-table/table-main/records/index.js @@ -1065,8 +1065,6 @@ Records.propTypes = { modifyColumnOrder: PropTypes.func, getUpdateDraggedRecords: PropTypes.func, getCopiedRecordsAndColumnsFromRange: PropTypes.func, - moveRecord: PropTypes.func, - addFolder: PropTypes.func, moveRecords: PropTypes.func, updateSelectedRecordIds: PropTypes.func, }; diff --git a/frontend/src/components/toolbar/dir-operation-toolbar.js b/frontend/src/components/toolbar/dir-operation-toolbar.js index 6bc70a9dad..a51ae4f696 100644 --- a/frontend/src/components/toolbar/dir-operation-toolbar.js +++ b/frontend/src/components/toolbar/dir-operation-toolbar.js @@ -3,13 +3,10 @@ import PropTypes from 'prop-types'; import { Dropdown, DropdownToggle, DropdownMenu, DropdownItem } from 'reactstrap'; import { Utils } from '../../utils/utils'; import { enableExcalidraw, enableSeadoc, enableWhiteboard, gettext } from '../../utils/constants'; -import ModalPortal from '../modal-portal'; -import CreateFolder from '../../components/dialog/create-folder-dialog'; -import CreateFile from '../../components/dialog/create-file-dialog'; -import ShareDialog from '../../components/dialog/share-dialog'; import toaster from '../toast'; import { seafileAPI } from '../../utils/seafile-api'; import TipDialog from '../dialog/tip-dailog'; +import { EVENT_BUS_TYPE } from '../common/event-bus-type'; const propTypes = { path: PropTypes.string.isRequired, @@ -20,12 +17,11 @@ const propTypes = { userPerm: PropTypes.string.isRequired, isGroupOwnedRepo: PropTypes.bool.isRequired, showShareBtn: PropTypes.bool.isRequired, - onAddFile: PropTypes.func.isRequired, - onAddFolder: PropTypes.func.isRequired, onUploadFile: PropTypes.func.isRequired, onUploadFolder: PropTypes.func.isRequired, direntList: PropTypes.array.isRequired, children: PropTypes.object, + eventBus: PropTypes.object, loadDirentList: PropTypes.func }; @@ -35,9 +31,6 @@ class DirOperationToolbar extends React.Component { super(props); this.state = { fileType: '.md', - isCreateFileDialogShow: false, - isCreateFolderDialogShow: false, - isShareDialogShow: false, operationMenuStyle: '', isDesktopMenuOpen: false, isSubMenuShown: false, @@ -64,82 +57,21 @@ class DirOperationToolbar extends React.Component { }; onShareClick = () => { - this.setState({ - isShareDialogShow: !this.state.isShareDialogShow - }); + const { eventBus, path, repoName, userPerm } = this.props; + let type = path === '/' ? 'library' : 'dir'; + let name = path == '/' ? repoName : Utils.getFolderName(path); + + eventBus.dispatch(EVENT_BUS_TYPE.SHARE_FILE, path, { type, name, permission: userPerm }); }; - onCreateFolderToggle = () => { - this.setState({ isCreateFolderDialogShow: !this.state.isCreateFolderDialogShow }); + onCreateFolder = () => { + const { eventBus, path, direntList } = this.props; + eventBus.dispatch(EVENT_BUS_TYPE.CREATE_FOLDER, path, direntList); }; - onCreateFileToggle = () => { - this.setState({ - isCreateFileDialogShow: !this.state.isCreateFileDialogShow, - fileType: '' - }); - }; - - onCreateMarkdownToggle = () => { - this.setState({ - isCreateFileDialogShow: !this.state.isCreateFileDialogShow, - fileType: '.md' - }); - }; - - onCreateExcelToggle = () => { - this.setState({ - isCreateFileDialogShow: !this.state.isCreateFileDialogShow, - fileType: '.xlsx' - }); - }; - - onCreatePPTToggle = () => { - this.setState({ - isCreateFileDialogShow: !this.state.isCreateFileDialogShow, - fileType: '.pptx' - }); - }; - - onCreateWordToggle = () => { - this.setState({ - isCreateFileDialogShow: !this.state.isCreateFileDialogShow, - fileType: '.docx' - }); - }; - - onCreateTldrawToggle = () => { - this.setState({ - isCreateFileDialogShow: !this.state.isCreateFileDialogShow, - fileType: '.draw' - }); - }; - - onCreateExcalidrawToggle = () => { - this.setState({ - isCreateFileDialogShow: !this.state.isCreateFileDialogShow, - fileType: '.exdraw' - }); - }; - - onCreateSeaDocToggle = () => { - this.setState({ - isCreateFileDialogShow: !this.state.isCreateFileDialogShow, - fileType: '.sdoc' - }); - }; - - onAddFolder = (dirPath) => { - this.setState({ isCreateFolderDialogShow: false }); - this.props.onAddFolder(dirPath); - }; - - checkDuplicatedName = (newName) => { - let direntList = this.props.direntList; - let isDuplicated = direntList.some(object => { - return object.name === newName; - }); - return isDuplicated; + onCreateFile = (fileType = '') => { + const { eventBus, path, direntList } = this.props; + eventBus.dispatch(EVENT_BUS_TYPE.CREATE_FILE, path, direntList, fileType); }; onDropdownToggleKeyDown = (e) => { @@ -209,7 +141,7 @@ class DirOperationToolbar extends React.Component { }; render() { - let { path, repoName, userPerm } = this.props; + const { userPerm } = this.props; const { isCustomPermission, customPermission } = Utils.getUserPermission(userPerm); const isShowDropdownMenu = (userPerm === 'rw' || userPerm === 'admin' || userPerm === 'cloud-edit' || isCustomPermission); if (!isShowDropdownMenu) { @@ -220,9 +152,6 @@ class DirOperationToolbar extends React.Component { ); } - let itemType = path === '/' ? 'library' : 'dir'; - let itemName = path == '/' ? repoName : Utils.getFolderName(path); - let canUpload = true; let canCreate = true; if (isCustomPermission) { @@ -257,24 +186,24 @@ class DirOperationToolbar extends React.Component { if (canCreate) { let newSubOpList = [ - { 'text': gettext('New Folder'), 'onClick': this.onCreateFolderToggle }, - { 'text': gettext('New File'), 'onClick': this.onCreateFileToggle }, + { 'text': gettext('New Folder'), 'onClick': this.onCreateFolder }, + { 'text': gettext('New File'), 'onClick': () => this.onCreateFile('') }, 'Divider', ]; if (enableSeadoc && !repoEncrypted) { - newSubOpList.push({ 'text': gettext('New SeaDoc File'), 'onClick': this.onCreateSeaDocToggle }); + newSubOpList.push({ 'text': gettext('New SeaDoc File'), 'onClick': () => this.onCreateFile('.sdoc') }); } newSubOpList.push( - { 'text': gettext('New Markdown File'), 'onClick': this.onCreateMarkdownToggle }, - { 'text': gettext('New Excel File'), 'onClick': this.onCreateExcelToggle }, - { 'text': gettext('New PowerPoint File'), 'onClick': this.onCreatePPTToggle }, - { 'text': gettext('New Word File'), 'onClick': this.onCreateWordToggle }, + { 'text': gettext('New Markdown File'), 'onClick': () => this.onCreateFile('.md') }, + { 'text': gettext('New Excel File'), 'onClick': () => this.onCreateFile('.xlsx') }, + { 'text': gettext('New PowerPoint File'), 'onClick': () => this.onCreateFile('.pptx') }, + { 'text': gettext('New Word File'), 'onClick': () => this.onCreateFile('.docx') }, ); if (enableWhiteboard) { - newSubOpList.push({ 'text': gettext('New Whiteboard File'), 'onClick': this.onCreateTldrawToggle }); + newSubOpList.push({ 'text': gettext('New Whiteboard File'), 'onClick': () => this.onCreateFile('.draw') }); } if (enableExcalidraw) { - newSubOpList.push({ 'text': gettext('New Excalidraw File'), 'onClick': this.onCreateExcalidrawToggle }); + newSubOpList.push({ 'text': gettext('New Excalidraw File'), 'onClick': () => this.onCreateFile('.exdraw') }); } opList.push({ 'icon': 'new', @@ -394,42 +323,6 @@ class DirOperationToolbar extends React.Component { {content} )} - {this.state.isCreateFileDialogShow && ( - - - - )} - {this.state.isCreateFolderDialogShow && ( - - - - )} - {this.state.isShareDialogShow && - - - - } {this.state.isImportingSdoc && ( )} diff --git a/frontend/src/components/toolbar/selected-dirents-toolbar.js b/frontend/src/components/toolbar/selected-dirents-toolbar.js index df66b3b40c..55c75d2392 100644 --- a/frontend/src/components/toolbar/selected-dirents-toolbar.js +++ b/frontend/src/components/toolbar/selected-dirents-toolbar.js @@ -1,18 +1,11 @@ -import React, { Fragment } from 'react'; +import React from 'react'; import PropTypes from 'prop-types'; import { gettext, siteRoot, name } from '../../utils/constants'; import { Utils } from '../../utils/utils'; import { seafileAPI } from '../../utils/seafile-api'; import URLDecorator from '../../utils/url-decorator'; -import MoveDirentDialog from '../dialog/move-dirent-dialog'; -import CopyDirentDialog from '../dialog/copy-dirent-dialog'; -import ShareDialog from '../dialog/share-dialog'; -import Rename from '../dialog/rename-dirent'; -import LibSubFolderPermissionDialog from '../dialog/lib-sub-folder-permission-dialog'; -import ModalPortal from '../modal-portal'; import ItemDropdownMenu from '../dropdown-menu/item-dropdown-menu'; import toaster from '../toast'; -import FileAccessLog from '../dialog/file-access-log'; import { Dirent } from '../../models'; import { EVENT_BUS_TYPE } from '../common/event-bus-type'; @@ -25,8 +18,6 @@ const propTypes = { repoEncrypted: PropTypes.bool.isRequired, selectedDirentList: PropTypes.array.isRequired, eventBus: PropTypes.object.isRequired, - onItemsMove: PropTypes.func.isRequired, - onItemsCopy: PropTypes.func.isRequired, onItemsDelete: PropTypes.func.isRequired, isRepoOwner: PropTypes.bool.isRequired, enableDirPrivateShare: PropTypes.bool.isRequired, @@ -36,10 +27,7 @@ const propTypes = { updateDirent: PropTypes.func.isRequired, currentMode: PropTypes.string.isRequired, direntList: PropTypes.array.isRequired, - onItemRename: PropTypes.func.isRequired, showDirentDetail: PropTypes.func.isRequired, - isGroupOwnedRepo: PropTypes.bool.isRequired, - onAddFolder: PropTypes.func.isRequired, }; class SelectedDirentsToolbar extends React.Component { @@ -48,42 +36,42 @@ class SelectedDirentsToolbar extends React.Component { super(props); this.state = { isFileAccessLogDialogOpen: false, - isMoveDialogShow: false, - isCopyDialogShow: false, - isMultipleOperation: true, showLibContentViewDialogs: false, - showShareDialog: false, fileTagList: [], - isRenameDialogOpen: false, - isPermissionDialogOpen: false }; } - onMoveToggle = () => { - this.setState({ isMoveDialogShow: !this.state.isMoveDialogShow }); - }; - - onCopyToggle = () => { - this.setState({ isCopyDialogShow: !this.state.isCopyDialogShow }); - }; - onItemsDelete = () => { this.props.onItemsDelete(); }; - onItemsDownload = () => { + onMove = () => { + const { path, selectedDirentList, eventBus } = this.props; + eventBus.dispatch(EVENT_BUS_TYPE.MOVE_FILE, path, selectedDirentList, true); + }; + + onCopy = () => { + const { path, selectedDirentList, eventBus } = this.props; + eventBus.dispatch(EVENT_BUS_TYPE.COPY_FILE, path, selectedDirentList, true); + }; + + onDownload = () => { const { path, selectedDirentList, eventBus } = this.props; const direntList = selectedDirentList.map(dirent => dirent instanceof Dirent ? dirent.toJson() : dirent); eventBus.dispatch(EVENT_BUS_TYPE.DOWNLOAD_FILE, path, direntList); }; - checkDuplicatedName = (newName) => { - return Utils.checkDuplicatedNameInList(this.props.direntList, newName); + onShare = () => { + const { selectedDirentList, eventBus } = this.props; + const dirent = selectedDirentList[0]; + const direntPath = this.getDirentPath(dirent); + eventBus.dispatch(EVENT_BUS_TYPE.SHARE_FILE, direntPath, dirent); }; - onItemRename = (newName) => { - const dirent = this.props.selectedDirentList[0]; - this.props.onItemRename(dirent, newName); + onRename = () => { + const { selectedDirentList, eventBus, direntList } = this.props; + const dirent = selectedDirentList[0]; + eventBus.dispatch(EVENT_BUS_TYPE.RENAME_FILE, dirent, direntList); }; onToggleStarItem = () => { @@ -115,11 +103,17 @@ class SelectedDirentsToolbar extends React.Component { } }; - onPermissionItem = () => { - this.setState({ - showLibContentViewDialogs: !this.state.showLibContentViewDialogs, - isPermissionDialogOpen: !this.state.isPermissionDialogOpen - }); + onPermission = () => { + const { eventBus, selectedDirentList } = this.props; + const dirent = selectedDirentList[0]; + const direntPath = this.getDirentPath(dirent); + eventBus.dispatch(EVENT_BUS_TYPE.PERMISSION, dirent, direntPath); + }; + + openFileAccessLog = (dirent) => { + const { eventBus } = this.props; + const direntPath = this.getDirentPath(dirent); + eventBus.dispatch(EVENT_BUS_TYPE.ACCESS_LOG, dirent, direntPath); }; onStartRevise = (dirent) => { @@ -140,13 +134,6 @@ class SelectedDirentsToolbar extends React.Component { return Utils.isHasPermissionToShare(currentRepoInfo, dirent.permission, dirent); }; - shareDirent = () => { - this.setState({ - showLibContentViewDialogs: true, - showShareDialog: true - }); - }; - getDirentMenuList = (dirent) => { const isRepoOwner = this.props.isRepoOwner; const currentRepoInfo = this.props.currentRepoInfo; @@ -170,10 +157,7 @@ class SelectedDirentsToolbar extends React.Component { const dirent = dirents[0]; switch (operation) { case 'Rename': - this.setState({ - showLibContentViewDialogs: true, - isRenameDialogOpen: true - }); + this.onRename(); break; case 'Star': this.onToggleStarItem(); @@ -194,7 +178,7 @@ class SelectedDirentsToolbar extends React.Component { this.onHistory(dirent); break; case 'Access Log': - this.toggleFileAccessLogDialog(); + this.openFileAccessLog(dirent); break; case 'Properties': this.props.showDirentDetail('info'); @@ -293,19 +277,9 @@ class SelectedDirentsToolbar extends React.Component { location.href = url; }; - toggleFileAccessLogDialog = () => { - this.setState({ - isFileAccessLogDialogOpen: !this.state.isFileAccessLogDialogOpen, - showLibContentViewDialogs: !this.state.isFileAccessLogDialogOpen - }); - }; - toggleCancel = () => { this.setState({ showLibContentViewDialogs: false, - showShareDialog: false, - isRenameDialogOpen: false, - isPermissionDialogOpen: false, }); }; @@ -314,10 +288,8 @@ class SelectedDirentsToolbar extends React.Component { }; render() { - const { repoID, userPerm, selectedDirentList } = this.props; - const dirent = selectedDirentList[0]; + const { userPerm, selectedDirentList } = this.props; const selectedLen = selectedDirentList.length; - const direntPath = this.getDirentPath(dirent); const { isCustomPermission, customPermission } = Utils.getUserPermission(userPerm); let canModify = false; @@ -351,111 +323,35 @@ class SelectedDirentsToolbar extends React.Component { } return ( - - - - - {selectedLen}{' '}{gettext('selected')} - - {canModify && - - } - {canCopy && - - } - {canDelete && - - } - {canDownload && - - } - {selectedLen == 1 && this.getDirentSharePerm() && - - } - {selectedLen === 1 && - - } - - {this.state.isMoveDialogShow && - + + + {selectedLen}{' '}{gettext('selected')} + + {canModify && + + } + {canCopy && + + } + {canDelete && + + } + {canDownload && + + } + {selectedLen == 1 && this.getDirentSharePerm() && + + } + {selectedLen === 1 && + } - {this.state.isCopyDialogShow && - - } - {this.state.showLibContentViewDialogs && ( - - {this.state.showShareDialog && - - - - } - {this.state.isRenameDialogOpen && - - - - } - {this.state.isPermissionDialogOpen && - - - - } - {this.state.isFileAccessLogDialogOpen && - - - - } - - )} - + ); } } diff --git a/frontend/src/components/toolbar/tag-files-toolbar.js b/frontend/src/components/toolbar/tag-files-toolbar.js index 9e1772b45d..8f5ef41f93 100644 --- a/frontend/src/components/toolbar/tag-files-toolbar.js +++ b/frontend/src/components/toolbar/tag-files-toolbar.js @@ -56,7 +56,7 @@ const TagFilesToolbar = ({ currentRepoInfo }) => { eventBus && eventBus.dispatch(EVENT_BUS_TYPE.SHARE_TAG_FILE); break; case TextTranslation.RENAME.key: - eventBus && eventBus.dispatch(EVENT_BUS_TYPE.TOGGLE_RENAME_DIALOG); + eventBus && eventBus.dispatch(EVENT_BUS_TYPE.RENAME_TAG_FILE_IN_DIALOG); break; case TextTranslation.HISTORY.key: eventBus && eventBus.dispatch(EVENT_BUS_TYPE.FILE_HISTORY); diff --git a/frontend/src/components/tree-view/tree-node.js b/frontend/src/components/tree-view/tree-node.js index 5810e5fa0e..70d14e276a 100644 --- a/frontend/src/components/tree-view/tree-node.js +++ b/frontend/src/components/tree-view/tree-node.js @@ -76,6 +76,7 @@ class TreeNode { rename(newName) { this.object.name = newName; + if (!this.parentNode) return; this.path = this.generatePath(this.parentNode); if (this.isExpanded) { this.updateChildrenPath(this); diff --git a/frontend/src/components/tree-view/tree-view.js b/frontend/src/components/tree-view/tree-view.js index fe21bf9dc3..bdfd302b99 100644 --- a/frontend/src/components/tree-view/tree-view.js +++ b/frontend/src/components/tree-view/tree-view.js @@ -17,7 +17,6 @@ const propTypes = { onNodeCollapse: PropTypes.func.isRequired, onItemMove: PropTypes.func, currentRepoInfo: PropTypes.object, - selectedDirentList: PropTypes.array, onItemsMove: PropTypes.func, repoID: PropTypes.string.isRequired, posX: PropTypes.number, diff --git a/frontend/src/components/tree-view/tree.js b/frontend/src/components/tree-view/tree.js index f9f7870e7d..8ca3ddabb9 100644 --- a/frontend/src/components/tree-view/tree.js +++ b/frontend/src/components/tree-view/tree.js @@ -40,6 +40,7 @@ class Tree { } addNodeToParent(node, parentNode) { + if (!node || !parentNode) return; parentNode.addChild(node); } diff --git a/frontend/src/hooks/file-operations/access-log.js b/frontend/src/hooks/file-operations/access-log.js new file mode 100644 index 0000000000..511a9c3e7e --- /dev/null +++ b/frontend/src/hooks/file-operations/access-log.js @@ -0,0 +1,63 @@ +import React, { useContext, useEffect, useCallback, useState, useRef, forwardRef, useImperativeHandle } from 'react'; +import ModalPortal from '../../components/modal-portal'; +import FileAccessLog from '../../components/dialog/file-access-log'; +import { EVENT_BUS_TYPE } from '../../components/common/event-bus-type'; + +// This hook provides content about access log +const AccessLogContext = React.createContext(null); + +export const AccessLogProvider = forwardRef(({ repoID, eventBus, children }, ref) => { + const [isDialogShow, setDialogShow] = useState(); + + const pathRef = useRef(''); + const nameRef = useRef(''); + + const handleAccessLog = useCallback((path, name) => { + pathRef.current = path; + nameRef.current = name; + setDialogShow(true); + }, []); + + const cancelAccessLog = useCallback(() => { + setDialogShow(false); + pathRef.current = ''; + nameRef.current = ''; + }, []); + + useEffect(() => { + const unsubscribeCreateFile = eventBus.subscribe(EVENT_BUS_TYPE.ACCESS_LOG, handleAccessLog); + return () => { + unsubscribeCreateFile(); + }; + }, [eventBus, handleAccessLog]); + + useImperativeHandle(ref, () => ({ + handleAccessLog, + cancelAccessLog, + }), [handleAccessLog, cancelAccessLog]); + + return ( + + {children} + {isDialogShow && ( + + + + )} + + ); +}); + +export const useAccessLogContext = () => { + const context = useContext(AccessLogContext); + if (!context) { + throw new Error('\'AccessLogContext\' is null'); + } + return context; +}; + diff --git a/frontend/src/hooks/file-operations/copy.js b/frontend/src/hooks/file-operations/copy.js new file mode 100644 index 0000000000..f949d43c84 --- /dev/null +++ b/frontend/src/hooks/file-operations/copy.js @@ -0,0 +1,66 @@ +import React, { useContext, useEffect, useCallback, useState, useRef, forwardRef, useImperativeHandle } from 'react'; +import ModalPortal from '../../components/modal-portal'; +import CopyDirentDialog from '../../components/dialog/copy-dirent-dialog'; +import { EVENT_BUS_TYPE } from '../../components/common/event-bus-type'; + +// This hook provides content about copy file +const CopyFileContext = React.createContext(null); + +export const CopyFileProvider = forwardRef(({ eventBus, repoID, repoInfo, onCopy, onCopyItem, onCreateFolder, children }, ref) => { + const [isDialogShow, setDialogShow] = useState(); + + const propsRef = useRef({}); + + const handleCopy = useCallback((path, direntData, isMultipleOperation = false, customCallback = null) => { + propsRef.current = { + path, + isMultipleOperation, + [isMultipleOperation ? 'selectedDirentList' : 'dirent']: direntData, + [isMultipleOperation ? 'onItemsCopy' : 'onItemCopy']: customCallback || (isMultipleOperation ? onCopy : onCopyItem), + }; + setDialogShow(true); + }, [onCopy, onCopyItem]); + + const cancelCopy = useCallback(() => { + setDialogShow(false); + propsRef.current = {}; + }, []); + + useEffect(() => { + const unsubscribeCopyFile = eventBus.subscribe(EVENT_BUS_TYPE.COPY_FILE, handleCopy); + return () => { + unsubscribeCopyFile(); + }; + }, [eventBus, handleCopy]); + + useImperativeHandle(ref, () => ({ + handleCopy, + cancelCopy, + }), [handleCopy, cancelCopy]); + + const renderDialog = useCallback(() => { + if (!isDialogShow) return null; + const { encrypted: repoEncrypted } = repoInfo || {}; + + return ( + + + + ); + }, [repoID, repoInfo, isDialogShow, onCreateFolder, cancelCopy]); + + return ( + + {children} + {renderDialog()} + + ); +}); + +export const useCopyFile = () => { + const context = useContext(CopyFileContext); + if (!context) { + throw new Error('\'CopyFileContext\' is null'); + } + return context; +}; diff --git a/frontend/src/hooks/file-operations/create-file.js b/frontend/src/hooks/file-operations/create-file.js new file mode 100644 index 0000000000..2ac6d74d1b --- /dev/null +++ b/frontend/src/hooks/file-operations/create-file.js @@ -0,0 +1,71 @@ +import React, { useContext, useEffect, useCallback, useState, useRef, forwardRef, useImperativeHandle } from 'react'; +import ModalPortal from '../../components/modal-portal'; +import CreateFileDialog from '../../components/dialog/create-file-dialog'; +import { EVENT_BUS_TYPE } from '../../components/common/event-bus-type'; + +// This hook provides content about create file +const CreateFileContext = React.createContext(null); + +export const CreateFileProvider = forwardRef(({ eventBus, onCreateFile, children }, ref) => { + const [isDialogShow, setDialogShow] = useState(); + + const pathRef = useRef(''); + const fileTypeRef = useRef(''); + const direntListRef = useRef([]); + + const checkDuplicatedName = useCallback((newName) => { + return direntListRef.current.some(object => object.name === newName); + }, []); + + const handleCreateFile = useCallback((path, direntList = [], fileType = '') => { + pathRef.current = path; + fileTypeRef.current = fileType; + direntListRef.current = direntList; + setDialogShow(true); + }, []); + + const cancelCreateFile = useCallback(() => { + setDialogShow(false); + pathRef.current = ''; + fileTypeRef.current = ''; + direntListRef.current = []; + }, []); + + useEffect(() => { + const unsubscribeCreateFile = eventBus.subscribe(EVENT_BUS_TYPE.CREATE_FILE, handleCreateFile); + return () => { + unsubscribeCreateFile(); + }; + }, [eventBus, handleCreateFile]); + + useImperativeHandle(ref, () => ({ + handleCreateFile, + cancelCreateFile, + }), [handleCreateFile, cancelCreateFile]); + + return ( + + {children} + {isDialogShow && ( + + + + )} + + ); +}); + +export const useCreateFile = () => { + const context = useContext(CreateFileContext); + if (!context) { + throw new Error('\'CreateFileContext\' is null'); + } + return context; +}; + diff --git a/frontend/src/hooks/file-operations/create-folder.js b/frontend/src/hooks/file-operations/create-folder.js new file mode 100644 index 0000000000..367c9000b0 --- /dev/null +++ b/frontend/src/hooks/file-operations/create-folder.js @@ -0,0 +1,67 @@ +import React, { useContext, useEffect, useCallback, useState, useRef, forwardRef, useImperativeHandle } from 'react'; +import ModalPortal from '../../components/modal-portal'; +import CreateFolderDialog from '../../components/dialog/create-folder-dialog'; +import { EVENT_BUS_TYPE } from '../../components/common/event-bus-type'; + +// This hook provides content about create folder +const CreateFolderContext = React.createContext(null); + +export const CreateFolderProvider = forwardRef(({ eventBus, onCreateFolder, children }, ref) => { + const [isDialogShow, setDialogShow] = useState(); + + const pathRef = useRef(''); + const direntListRef = useRef([]); + + const checkDuplicatedName = useCallback((newName) => { + return direntListRef.current.some(object => object.name === newName); + }, []); + + const handleCreateFolder = useCallback((path, direntList = []) => { + pathRef.current = path; + direntListRef.current = direntList; + setDialogShow(true); + }, []); + + const cancelCreateFolder = useCallback(() => { + setDialogShow(false); + pathRef.current = ''; + direntListRef.current = []; + }, []); + + useEffect(() => { + const unsubscribeCreateFolder = eventBus.subscribe(EVENT_BUS_TYPE.CREATE_FOLDER, handleCreateFolder); + return () => { + unsubscribeCreateFolder(); + }; + }, [eventBus, handleCreateFolder]); + + useImperativeHandle(ref, () => ({ + handleCreateFolder, + cancelCreateFolder, + }), [handleCreateFolder, cancelCreateFolder]); + + return ( + + {children} + {isDialogShow && ( + + + + )} + + ); +}); + +export const useCreateFolder = () => { + const context = useContext(CreateFolderContext); + if (!context) { + throw new Error('\'CreateFolderContext\' is null'); + } + return context; +}; + diff --git a/frontend/src/hooks/download-file.js b/frontend/src/hooks/file-operations/download.js similarity index 74% rename from frontend/src/hooks/download-file.js rename to frontend/src/hooks/file-operations/download.js index c606107e17..4f317897a6 100644 --- a/frontend/src/hooks/download-file.js +++ b/frontend/src/hooks/file-operations/download.js @@ -1,17 +1,17 @@ -import React, { useContext, useEffect, useCallback, useState, useRef } from 'react'; -import { useGoFileserver, fileServerRoot } from '../utils/constants'; -import { Utils } from '../utils/utils'; -import { seafileAPI } from '../utils/seafile-api'; -import URLDecorator from '../utils/url-decorator'; -import ModalPortal from '../components/modal-portal'; -import ZipDownloadDialog from '../components/dialog/zip-download-dialog'; -import toaster from '../components/toast'; -import { EVENT_BUS_TYPE } from '../components/common/event-bus-type'; +import React, { useContext, useEffect, useCallback, useState, useRef, forwardRef, useImperativeHandle } from 'react'; +import { useGoFileserver, fileServerRoot } from '../../utils/constants'; +import { Utils } from '../../utils/utils'; +import { seafileAPI } from '../../utils/seafile-api'; +import URLDecorator from '../../utils/url-decorator'; +import ModalPortal from '../../components/modal-portal'; +import ZipDownloadDialog from '../../components/dialog/zip-download-dialog'; +import toaster from '../../components/toast'; +import { EVENT_BUS_TYPE } from '../../components/common/event-bus-type'; // This hook provides content about download const DownloadFileContext = React.createContext(null); -export const DownloadFileProvider = ({ repoID, eventBus, children }) => { +export const DownloadFileProvider = forwardRef(({ repoID, eventBus, children }, ref) => { const [isZipDialogOpen, setZipDialogOpen] = useState(); const pathRef = useRef(''); @@ -56,6 +56,11 @@ export const DownloadFileProvider = ({ repoID, eventBus, children }) => { }; }, [eventBus, handleDownload]); + useImperativeHandle(ref, () => ({ + handleDownload, + cancelDownload, + }), [handleDownload, cancelDownload]); + return ( {children} @@ -71,7 +76,7 @@ export const DownloadFileProvider = ({ repoID, eventBus, children }) => { )} ); -}; +}); export const useDownloadFile = () => { const context = useContext(DownloadFileContext); diff --git a/frontend/src/hooks/file-operations/index.js b/frontend/src/hooks/file-operations/index.js new file mode 100644 index 0000000000..f7f0f3b4c0 --- /dev/null +++ b/frontend/src/hooks/file-operations/index.js @@ -0,0 +1,100 @@ +import React, { useCallback, useContext, useRef } from 'react'; +import { DownloadFileProvider } from './download'; +import { CreateFileProvider } from './create-file'; +import { CreateFolderProvider } from './create-folder'; +import { RenameFileProvider } from './rename'; +import { MoveFileProvider } from './move'; +import { CopyFileProvider } from './copy'; +import { ShareFileProvider } from './share'; +import { LibSubFolderPermissionProvider } from './permission'; +import { AccessLogProvider } from './access-log'; + +// This hook provides content about file operations, like a middleware +const FileOperationsContext = React.createContext(null); + +export const FileOperationsProvider = ({ + repoID, repoInfo, eventBus, enableDirPrivateShare, isGroupOwnedRepo, + onCreateFolder, onCreateFile, + onRename, + onMove, onMoveItem, + onCopy, onCopyItem, + children +}) => { + const createFileRef = useRef(null); + const createFolderRef = useRef(null); + const renameRef = useRef(null); + const downloadRef = useRef(null); + const moveRef = useRef(null); + const copyRef = useRef(null); + const shareRef = useRef(null); + const permissionRef = useRef(null); + const logRef = useRef(null); + + const handleDownload = useCallback((...params) => { + downloadRef.current.handleDownload(...params); + }, []); + + const handleCreateFolder = useCallback((...params) => { + createFolderRef.current.handleCreateFolder(...params); + }, []); + + const handleCreateFile = useCallback((...params) => { + createFileRef.current.handleCreateFile(...params); + }, []); + + const handleMove = useCallback((...params) => { + moveRef.current.handleMove(...params); + }, []); + + const handleCopy = useCallback((...params) => { + copyRef.current.handleCopy(...params); + }, []); + + const handleShare = useCallback((...params) => { + shareRef.current.handleShare(...params); + }, []); + + const handleRename = useCallback((...params) => { + renameRef.current.handleRename(...params); + }, []); + + const handleLibSubFolderPermission = useCallback((...params) => { + permissionRef.current.handleLibSubFolderPermission(...params); + }, []); + + const handleAccessLog = useCallback((...params) => { + logRef.current.handleAccessLog(...params); + }, []); + + return ( + + + + + + + + + + + {children} + + + + + + + + + + + ); +}; + +export const useFileOperations = () => { + const context = useContext(FileOperationsContext); + if (!context) { + throw new Error('\'FileOperationsContext\' is null'); + } + return context; +}; diff --git a/frontend/src/hooks/file-operations/move.js b/frontend/src/hooks/file-operations/move.js new file mode 100644 index 0000000000..0454d772f8 --- /dev/null +++ b/frontend/src/hooks/file-operations/move.js @@ -0,0 +1,65 @@ +import React, { useContext, useEffect, useCallback, useState, useRef, forwardRef, useImperativeHandle } from 'react'; +import ModalPortal from '../../components/modal-portal'; +import MoveDirentDialog from '../../components/dialog/move-dirent-dialog'; +import { EVENT_BUS_TYPE } from '../../components/common/event-bus-type'; + +// This hook provides content about move file +const MoveFileContext = React.createContext(null); + +export const MoveFileProvider = forwardRef(({ eventBus, repoID, repoInfo, onMove, onMoveItem, onCreateFolder, children }, ref) => { + const [isDialogShow, setDialogShow] = useState(); + + const propsRef = useRef({}); + + const handleMove = useCallback((path, direntData, isMultipleOperation = false, customCallback) => { + propsRef.current = { + path, + isMultipleOperation, + [isMultipleOperation ? 'selectedDirentList' : 'dirent']: direntData, + [isMultipleOperation ? 'onItemsMove' : 'onItemMove']: customCallback || (isMultipleOperation ? onMove : onMoveItem), + }; + setDialogShow(true); + }, [onMove, onMoveItem]); + + const cancelMove = useCallback(() => { + setDialogShow(false); + propsRef.current = {}; + }, []); + + const renderDialog = useCallback(() => { + if (!isDialogShow) return null; + const { encrypted: repoEncrypted } = repoInfo || {}; + return ( + + + + ); + }, [repoID, repoInfo, isDialogShow, onCreateFolder, cancelMove]); + + useEffect(() => { + const unsubscribeMoveFile = eventBus.subscribe(EVENT_BUS_TYPE.MOVE_FILE, handleMove); + return () => { + unsubscribeMoveFile(); + }; + }, [eventBus, handleMove]); + + useImperativeHandle(ref, () => ({ + handleMove, + cancelMove, + }), [handleMove, cancelMove]); + + return ( + + {children} + {renderDialog()} + + ); +}); + +export const useMoveFile = () => { + const context = useContext(MoveFileContext); + if (!context) { + throw new Error('\'MoveFileContext\' is null'); + } + return context; +}; diff --git a/frontend/src/hooks/file-operations/permission.js b/frontend/src/hooks/file-operations/permission.js new file mode 100644 index 0000000000..b2f787e59f --- /dev/null +++ b/frontend/src/hooks/file-operations/permission.js @@ -0,0 +1,64 @@ +import React, { useContext, useEffect, useCallback, useState, useRef, forwardRef, useImperativeHandle } from 'react'; +import ModalPortal from '../../components/modal-portal'; +import LibSubFolderPermissionDialog from '../../components/dialog/lib-sub-folder-permission-dialog'; +import { EVENT_BUS_TYPE } from '../../components/common/event-bus-type'; + +// This hook provides content about lib sub-folder permission +const LibSubFolderPermissionContext = React.createContext(null); + +export const LibSubFolderPermissionProvider = forwardRef(({ repoID, eventBus, isGroupOwnedRepo, children }, ref) => { + const [isDialogShow, setDialogShow] = useState(); + + const pathRef = useRef(''); + const nameRef = useRef(''); + + const handleLibSubFolderPermission = useCallback((path, name) => { + pathRef.current = path; + nameRef.current = name; + setDialogShow(true); + }, []); + + const cancelLibSubFolderPermission = useCallback(() => { + setDialogShow(false); + pathRef.current = ''; + nameRef.current = ''; + }, []); + + useEffect(() => { + const unsubscribeCreateFile = eventBus.subscribe(EVENT_BUS_TYPE.PERMISSION, handleLibSubFolderPermission); + return () => { + unsubscribeCreateFile(); + }; + }, [eventBus, handleLibSubFolderPermission]); + + useImperativeHandle(ref, () => ({ + handleLibSubFolderPermission, + cancelLibSubFolderPermission, + }), [handleLibSubFolderPermission, cancelLibSubFolderPermission]); + + return ( + + {children} + {isDialogShow && ( + + + + )} + + ); +}); + +export const useLibSubFolderPermissionContext = () => { + const context = useContext(LibSubFolderPermissionContext); + if (!context) { + throw new Error('\'LibSubFolderPermissionContext\' is null'); + } + return context; +}; + diff --git a/frontend/src/hooks/file-operations/rename.js b/frontend/src/hooks/file-operations/rename.js new file mode 100644 index 0000000000..1f6813e06c --- /dev/null +++ b/frontend/src/hooks/file-operations/rename.js @@ -0,0 +1,69 @@ +import React, { useContext, useEffect, useCallback, useState, useRef, forwardRef, useImperativeHandle } from 'react'; +import ModalPortal from '../../components/modal-portal'; +import RenameDialog from '../../components/dialog/rename-dialog'; +import { EVENT_BUS_TYPE } from '../../components/common/event-bus-type'; + +// This hook provides content about rename file +const RenameFileContext = React.createContext(null); + +export const RenameFileProvider = forwardRef(({ eventBus, onRename, children }, ref) => { + const [isDialogShow, setDialogShow] = useState(); + + const direntRef = useRef(''); + const direntListRef = useRef([]); + const callbackRef = useRef(() => {}); + + const checkDuplicatedName = useCallback((newName) => { + return direntListRef.current.some(object => object.name === newName); + }, []); + + const handleRename = useCallback((dirent, direntList = [], callback) => { + direntRef.current = dirent; + direntListRef.current = direntList; + callbackRef.current = callback || ((newName) => onRename(dirent, newName)); + setDialogShow(true); + }, [onRename]); + + const cancelRename = useCallback(() => { + setDialogShow(false); + direntRef.current = null; + direntListRef.current = []; + }, []); + + useEffect(() => { + const unsubscribeCreateFile = eventBus.subscribe(EVENT_BUS_TYPE.RENAME_FILE, handleRename); + return () => { + unsubscribeCreateFile(); + }; + }, [eventBus, handleRename]); + + useImperativeHandle(ref, () => ({ + handleRename, + cancelRename, + }), [handleRename, cancelRename]); + + return ( + + {children} + {isDialogShow && ( + + + + )} + + ); +}); + +export const useRenameFile = () => { + const context = useContext(RenameFileContext); + if (!context) { + throw new Error('\'RenameFileContext\' is null'); + } + return context; +}; + diff --git a/frontend/src/hooks/file-operations/share.js b/frontend/src/hooks/file-operations/share.js new file mode 100644 index 0000000000..7a73a2f115 --- /dev/null +++ b/frontend/src/hooks/file-operations/share.js @@ -0,0 +1,68 @@ +import React, { useContext, useEffect, useCallback, useState, useRef, forwardRef, useImperativeHandle } from 'react'; +import ModalPortal from '../../components/modal-portal'; +import ShareDialog from '../../components/dialog/share-dialog'; +import { EVENT_BUS_TYPE } from '../../components/common/event-bus-type'; + +// This hook provides content about share +const ShareFileContext = React.createContext(null); + +export const ShareFileProvider = forwardRef(({ repoID, eventBus, repoInfo, enableDirPrivateShare, isGroupOwnedRepo, children }, ref) => { + const [isDialogShow, setDialogShow] = useState(); + + const pathRef = useRef(''); + const direntRef = useRef([]); + + const handleShare = useCallback((path, dirent) => { + pathRef.current = path; + direntRef.current = dirent; + setDialogShow(true); + }, []); + + const cancelShare = useCallback(() => { + setDialogShow(false); + pathRef.current = ''; + direntRef.current = []; + }, []); + + useEffect(() => { + const unsubscribeShareFile = eventBus.subscribe(EVENT_BUS_TYPE.SHARE_FILE, handleShare); + return () => { + unsubscribeShareFile(); + }; + }, [eventBus, handleShare]); + + useImperativeHandle(ref, () => ({ + handleShare, + cancelShare, + }), [handleShare, cancelShare]); + + return ( + + {children} + {isDialogShow && ( + + + + )} + + ); +}); + +export const useShareFile = () => { + const context = useContext(ShareFileContext); + if (!context) { + throw new Error('\'ShareFileContext\' is null'); + } + return context; +}; + diff --git a/frontend/src/hooks/index.js b/frontend/src/hooks/index.js index 0ede4cad11..7ae0677c2c 100644 --- a/frontend/src/hooks/index.js +++ b/frontend/src/hooks/index.js @@ -1,3 +1,6 @@ -export { DownloadFileProvider } from './download-file'; -export { MetadataMiddlewareProvider } from './metadata-middleware'; +export { FileOperationsProvider, useFileOperations } from './file-operations'; export { MetadataStatusProvider, useMetadataStatus } from './metadata-status'; +export { + MetadataMiddlewareProvider, + MetadataAIOperationsProvider, useMetadataAIOperations, +} from '../metadata'; diff --git a/frontend/src/metadata/components/metadata-details/ai-icon.js b/frontend/src/metadata/components/metadata-details/ai-icon.js index 6372342088..a8acd5d11e 100644 --- a/frontend/src/metadata/components/metadata-details/ai-icon.js +++ b/frontend/src/metadata/components/metadata-details/ai-icon.js @@ -2,13 +2,12 @@ import React, { useCallback, useMemo, useRef, useState } from 'react'; import { Dropdown, DropdownToggle, DropdownMenu, DropdownItem } from 'reactstrap'; import Icon from '../../../components/icon'; import { useMetadataDetails } from '../../hooks'; -import { useMetadataStatus } from '../../../hooks'; +import { useMetadataStatus, useMetadataAIOperations } from '../../../hooks'; import { gettext } from '../../../utils/constants'; import { Utils } from '../../../utils/utils'; import { getFileNameFromRecord, getFileObjIdFromRecord, getParentDirFromRecord, getRecordIdFromRecord } from '../../utils/cell'; import { getColumnByKey } from '../../utils/column'; import { PRIVATE_COLUMN_KEY } from './constants'; -import { useMetadataAIOperations } from '../../../hooks/metadata-ai-operation'; import { checkIsDir } from '../../utils/row'; const OPERATION = { diff --git a/frontend/src/metadata/components/popover/ocr-result-popover/index.js b/frontend/src/metadata/components/popover/ocr-result-popover/index.js index f4a37e7635..eb21a2468c 100644 --- a/frontend/src/metadata/components/popover/ocr-result-popover/index.js +++ b/frontend/src/metadata/components/popover/ocr-result-popover/index.js @@ -8,7 +8,7 @@ import { gettext } from '../../../../utils/constants'; import { getFileNameFromRecord, getParentDirFromRecord } from '../../../utils/cell'; import { Utils } from '../../../../utils/utils'; import metadataAPI from '../../../api'; -import { useMetadataAIOperations } from '../../../../hooks/metadata-ai-operation'; +import { useMetadataAIOperations } from '../../../hooks'; import Loading from '../../../../components/loading'; import { getTarget } from '../../../../utils/dom'; import BodyPortal from '../../../../components/body-portal'; diff --git a/frontend/src/metadata/constants/event-bus-type.js b/frontend/src/metadata/constants/event-bus-type.js index e76f930258..f68240e02d 100644 --- a/frontend/src/metadata/constants/event-bus-type.js +++ b/frontend/src/metadata/constants/event-bus-type.js @@ -92,10 +92,9 @@ export const EVENT_BUS_TYPE = { // tag file MOVE_TAG_FILE: 'move_tag_file', COPY_TAG_FILE: 'copy_tag_file', - RENAME_TAG_FILE: 'rename_tag_file', - TOGGLE_RENAME_DIALOG: 'toggle_rename_dialog', + RENAME_TAG_FILE_IN_SITU: 'rename_tag_file_in_situ', + RENAME_TAG_FILE_IN_DIALOG: 'rename_tag_file_in_dialog', SHARE_TAG_FILE: 'share_tag_file', - TOGGLE_ZIP_DIALOG: 'toggle_zip_dialog', DOWNLOAD_TAG_FILES: 'download_tag_files', DELETE_TAG_FILES: 'delete_tag_files', SELECT_TAG_FILES: 'select_tag_files', diff --git a/frontend/src/metadata/hooks/index.js b/frontend/src/metadata/hooks/index.js index ca990d0368..eeb4da159e 100644 --- a/frontend/src/metadata/hooks/index.js +++ b/frontend/src/metadata/hooks/index.js @@ -1,3 +1,5 @@ export { MetadataProvider, useMetadata } from './metadata'; export { CollaboratorsProvider, useCollaborators } from './collaborators'; export { MetadataDetailsProvider, useMetadataDetails } from './metadata-details'; +export { MetadataAIOperationsProvider, useMetadataAIOperations } from './metadata-ai-operation'; +export { MetadataMiddlewareProvider, useMetadataMiddleware } from './metadata-middleware'; diff --git a/frontend/src/hooks/metadata-ai-operation.js b/frontend/src/metadata/hooks/metadata-ai-operation.js similarity index 93% rename from frontend/src/hooks/metadata-ai-operation.js rename to frontend/src/metadata/hooks/metadata-ai-operation.js index dc09e13bca..1d44b93633 100644 --- a/frontend/src/hooks/metadata-ai-operation.js +++ b/frontend/src/metadata/hooks/metadata-ai-operation.js @@ -1,11 +1,11 @@ import React, { useContext, useCallback, useMemo, useState, useRef } from 'react'; -import metadataAPI from '../metadata/api'; -import { Utils } from '../utils/utils'; -import toaster from '../components/toast'; -import { PRIVATE_COLUMN_KEY, EVENT_BUS_TYPE } from '../metadata/constants'; -import { gettext, lang } from '../utils/constants'; -import { OCRResultPopover } from '../metadata/components/popover'; -import FileTagsDialog from '../metadata/components/dialog/file-tags-dialog'; +import metadataAPI from '../api'; +import { Utils } from '../../utils/utils'; +import toaster from '../../components/toast'; +import { PRIVATE_COLUMN_KEY, EVENT_BUS_TYPE } from '../constants'; +import { gettext, lang } from '../../utils/constants'; +import { OCRResultPopover } from '../components/popover'; +import FileTagsDialog from '../components/dialog/file-tags-dialog'; // This hook provides content related to metadata ai operation const MetadataAIOperationsContext = React.createContext(null); diff --git a/frontend/src/hooks/metadata-middleware.js b/frontend/src/metadata/hooks/metadata-middleware.js similarity index 89% rename from frontend/src/hooks/metadata-middleware.js rename to frontend/src/metadata/hooks/metadata-middleware.js index 6963b132f3..c489d76a06 100644 --- a/frontend/src/hooks/metadata-middleware.js +++ b/frontend/src/metadata/hooks/metadata-middleware.js @@ -1,8 +1,8 @@ import React, { useContext } from 'react'; import { MetadataAIOperationsProvider } from './metadata-ai-operation'; -import { TagsProvider } from '../tag/hooks'; -import { CollaboratorsProvider } from '../metadata'; -import { useMetadataStatus } from './metadata-status'; +import { TagsProvider } from '../../tag/hooks'; +import { CollaboratorsProvider } from './collaborators'; +import { useMetadataStatus } from '../../hooks'; const MetadataMiddlewareContext = React.createContext(null); diff --git a/frontend/src/metadata/hooks/metadata-view.js b/frontend/src/metadata/hooks/metadata-view.js index 121cb7e81f..af61480010 100644 --- a/frontend/src/metadata/hooks/metadata-view.js +++ b/frontend/src/metadata/hooks/metadata-view.js @@ -1,5 +1,6 @@ /* eslint-disable react/prop-types */ import React, { useContext, useEffect, useRef, useState, useCallback } from 'react'; +import { Dirent } from '@/models'; import toaster from '../../components/toast'; import Context from '../context'; import Store from '../store'; @@ -12,7 +13,7 @@ import { getFileNameFromRecord, getFileObjIdFromRecord, getParentDirFromRecord, import { gettext } from '../../utils/constants'; import { checkIsDir } from '../utils/row'; import { useTags } from '../../tag/hooks'; -import { useMetadataAIOperations } from '../../hooks/metadata-ai-operation'; +import { useFileOperations, useMetadataAIOperations } from '../../hooks'; import { getColumnByKey } from '../utils/column'; const MetadataViewContext = React.createContext(null); @@ -39,6 +40,7 @@ export const MetadataViewProvider = ({ const { collaborators } = useCollaborators(); const { isBeingBuilt, setIsBeingBuilt } = useMetadata(); const { onOCR: OCRAPI, generateDescription, extractFilesDetails, faceRecognition, generateFileTags: generateFileTagsAPI } = useMetadataAIOperations(); + const { handleMove } = useFileOperations(); const tableChanged = useCallback(() => { setMetadata(storeRef.current.data); @@ -411,6 +413,18 @@ export const MetadataViewProvider = ({ }); }, [updateFileTags, generateFileTagsAPI]); + const handleMoveRecord = (record) => { + const path = getParentDirFromRecord(record); + const currentRecordId = getRecordIdFromRecord(record); + const fileName = getFileNameFromRecord(record); + const dirent = new Dirent({ name: fileName }); + const callback = (...params) => { + window.sfMetadataContext.eventBus.dispatch(EVENT_BUS_TYPE.SELECT_NONE); + moveRecord && moveRecord(currentRecordId, ...params); + }; + handleMove(path, dirent, false, callback); + }; + // init useEffect(() => { setLoading(true); @@ -450,6 +464,7 @@ export const MetadataViewProvider = ({ const unsubscribeUpdateFaceRecognition = eventBus.subscribe(EVENT_BUS_TYPE.UPDATE_FACE_RECOGNITION, updateFaceRecognition); const unsubscribeUpdateDescription = eventBus.subscribe(EVENT_BUS_TYPE.GENERATE_DESCRIPTION, updateRecordDescription); const unsubscribeOCR = eventBus.subscribe(EVENT_BUS_TYPE.OCR, onOCR); + const unsubscribeToggleMoveDialog = eventBus.subscribe(EVENT_BUS_TYPE.TOGGLE_MOVE_DIALOG, handleMoveRecord); return () => { if (window.sfMetadataContext) { @@ -476,6 +491,7 @@ export const MetadataViewProvider = ({ unsubscribeUpdateFaceRecognition(); unsubscribeUpdateDescription(); unsubscribeOCR(); + unsubscribeToggleMoveDialog(); delayReloadDataTimer.current && clearTimeout(delayReloadDataTimer.current); }; // eslint-disable-next-line react-hooks/exhaustive-deps @@ -509,7 +525,6 @@ export const MetadataViewProvider = ({ modifyColumnWidth, insertColumn, updateFileTags, - addFolder: params.addFolder, updateCurrentPath: params.updateCurrentPath, updateSelectedRecordIds, updateRecordDetails, diff --git a/frontend/src/metadata/views/face-recognition/peoples/people/index.js b/frontend/src/metadata/views/face-recognition/peoples/people/index.js index ebd79bd220..69e30435a4 100644 --- a/frontend/src/metadata/views/face-recognition/peoples/people/index.js +++ b/frontend/src/metadata/views/face-recognition/peoples/people/index.js @@ -61,12 +61,12 @@ const People = ({ haveFreezed, people, onOpenPeople, onRename, onFreezed, onUnFr setRenaming(false); }, [onUnFreezed]); - const handelUnFreezed = useCallback((keepActive) => { + const handleUnFreezed = useCallback((keepActive) => { onUnFreezed(); !keepActive && setActive(false); }, [onUnFreezed]); - const handelClick = useCallback(() => { + const handleClick = useCallback(() => { if (renaming) return; setTimeout(() => onOpenPeople(people), 1); }, [renaming, people, onOpenPeople]); @@ -78,7 +78,7 @@ const People = ({ haveFreezed, people, onOpenPeople, onRename, onFreezed, onUnFr })} onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave} - onClick={handelClick} + onClick={handleClick} > @@ -98,7 +98,7 @@ const People = ({ haveFreezed, people, onOpenPeople, onRename, onFreezed, onUnFr {!readonly && people._is_someone && ( {active && !renaming && ( - + )} )} diff --git a/frontend/src/metadata/views/face-recognition/person-photos/index.js b/frontend/src/metadata/views/face-recognition/person-photos/index.js index 11a3b4ef0e..24a9077451 100644 --- a/frontend/src/metadata/views/face-recognition/person-photos/index.js +++ b/frontend/src/metadata/views/face-recognition/person-photos/index.js @@ -81,7 +81,7 @@ const PeoplePhotos = ({ view, people, onClose, onDeletePeoplePhotos, onAddPeople needDeleteFromParent && onDeletePeoplePhotos && onDeletePeoplePhotos(people._id, ids); }, [metadata, onClose, people, onDeletePeoplePhotos]); - const handelDelete = useCallback((deletedImages, { success_callback } = {}) => { + const handleDelete = useCallback((deletedImages, { success_callback } = {}) => { if (!deletedImages.length) return; let recordIds = []; let paths = []; @@ -110,7 +110,7 @@ const PeoplePhotos = ({ view, people, onClose, onDeletePeoplePhotos, onAddPeople }); }, [deleteFilesCallback, repoID, deletedByIds]); - const handelRemove = useCallback((removedImages, callback) => { + const handleRemove = useCallback((removedImages, callback) => { if (!removedImages.length) return; let recordIds = []; removedImages.forEach((record) => { @@ -128,7 +128,7 @@ const PeoplePhotos = ({ view, people, onClose, onDeletePeoplePhotos, onAddPeople }); }, [people, deletedByIds, onRemovePeoplePhotos]); - const handelAdd = useCallback((peopleIds, addedImages, { success_callback, fail_callback }) => { + const handleAdd = useCallback((peopleIds, addedImages, { success_callback, fail_callback }) => { if (!addedImages.length || !peopleIds.length) return; let recordIds = []; addedImages.forEach((record) => { @@ -252,9 +252,9 @@ const PeoplePhotos = ({ view, people, onClose, onDeletePeoplePhotos, onAddPeople metadata={metadata} isLoadingMore={isLoadingMore} onLoadMore={onLoadMore} - onDelete={handelDelete} - onRemoveImage={people._is_someone ? handelRemove : null} - onAddImage={!people._is_someone ? handelAdd : null} + onDelete={handleDelete} + onRemoveImage={people._is_someone ? handleRemove : null} + onAddImage={!people._is_someone ? handleAdd : null} onSetPeoplePhoto={handleSetPeoplePhoto} /> diff --git a/frontend/src/metadata/views/gallery/context-menu/index.js b/frontend/src/metadata/views/gallery/context-menu/index.js index f2db90533e..c361a46b66 100644 --- a/frontend/src/metadata/views/gallery/context-menu/index.js +++ b/frontend/src/metadata/views/gallery/context-menu/index.js @@ -2,11 +2,10 @@ import React, { useMemo, useCallback, useState } from 'react'; import PropTypes from 'prop-types'; import ContextMenu from '../../../components/context-menu'; import ModalPortal from '../../../../components/modal-portal'; -import CopyDirent from '../../../../components/dialog/copy-dirent-dialog'; import PeoplesDialog from '../../../components/dialog/peoples-dialog'; import { gettext } from '../../../../utils/constants'; import { Dirent } from '../../../../models'; -import { useDownloadFile } from '../../../../hooks/download-file'; +import { useFileOperations } from '../../../../hooks/file-operations'; const CONTEXT_MENU_KEY = { DOWNLOAD: 'download', @@ -17,13 +16,11 @@ const CONTEXT_MENU_KEY = { ADD_PHOTO_TO_GROUPS: 'add_photo_to_groups', }; -const GalleryContextMenu = ({ selectedImages, onDelete, onDuplicate, addFolder, onRemoveImage, onAddImage, onSetPeoplePhoto }) => { - const [isCopyDialogOpen, setIsCopyDialogOpen] = useState(false); +const GalleryContextMenu = ({ selectedImages, onDelete, onDuplicate, onRemoveImage, onAddImage, onSetPeoplePhoto }) => { const [isPeoplesDialogShow, setPeoplesDialogShow] = useState(false); - const { handleDownload: handleDownloadAPI } = useDownloadFile(); + const { handleDownload: handleDownloadAPI, handleCopy: handleCopyAPI } = useFileOperations(); - const repoID = window.sfMetadataContext.getSetting('repoID'); const checkCanDeleteRow = window.sfMetadataContext.checkCanDeleteRow(); const canDuplicateRow = window.sfMetadataContext.canDuplicateRow(); const canRemovePhotoFromPeople = window.sfMetadataContext.canRemovePhotoFromPeople(); @@ -50,15 +47,18 @@ const GalleryContextMenu = ({ selectedImages, onDelete, onDuplicate, addFolder, return validOptions; }, [checkCanDeleteRow, canDuplicateRow, canRemovePhotoFromPeople, canAddPhotoToPeople, selectedImages, onDuplicate, onDelete, onRemoveImage, onAddImage, canSetPeoplePhoto, onSetPeoplePhoto]); - const toggleCopyDialog = useCallback(() => { - setIsCopyDialogOpen(!isCopyDialogOpen); - }, [isCopyDialogOpen]); - const handleDuplicate = useCallback((destRepo, dirent, destPath, nodeParentPath, isByDialog) => { const selectedImage = selectedImages[0]; onDuplicate(selectedImage.id, destRepo, dirent, destPath, nodeParentPath, isByDialog); }, [selectedImages, onDuplicate]); + const handleCopy = useCallback(() => { + if (!selectedImages.length) return; + const dirent = new Dirent({ name: selectedImages[0]?.name }); + const path = selectedImages[0]?.parentDir; + handleCopyAPI(path, dirent, false, handleDuplicate); + }, [selectedImages, handleCopyAPI, handleDuplicate]); + const handleDownload = useCallback(() => { if (!selectedImages.length) return; const direntList = selectedImages.map(image => { @@ -77,7 +77,7 @@ const GalleryContextMenu = ({ selectedImages, onDelete, onDuplicate, addFolder, onDelete(selectedImages); break; case CONTEXT_MENU_KEY.DUPLICATE: - toggleCopyDialog(); + handleCopy(); break; case CONTEXT_MENU_KEY.REMOVE: onRemoveImage(selectedImages); @@ -91,7 +91,7 @@ const GalleryContextMenu = ({ selectedImages, onDelete, onDuplicate, addFolder, default: break; } - }, [handleDownload, onDelete, selectedImages, toggleCopyDialog, onRemoveImage, onSetPeoplePhoto]); + }, [handleDownload, onDelete, selectedImages, handleCopy, onRemoveImage, onSetPeoplePhoto]); const closePeoplesDialog = useCallback(() => { setPeoplesDialogShow(false); @@ -101,9 +101,6 @@ const GalleryContextMenu = ({ selectedImages, onDelete, onDuplicate, addFolder, onAddImage(peopleIds, addedImages, callback); }, [onAddImage]); - const dirent = new Dirent({ name: selectedImages[0]?.name }); - const parentDir = selectedImages[0]?.parentDir; - return ( <> - {isCopyDialogOpen && ( - - - - )} {isPeoplesDialogShow && ( @@ -139,7 +122,6 @@ GalleryContextMenu.propTypes = { selectedImages: PropTypes.array, onDelete: PropTypes.func, onDuplicate: PropTypes.func, - addFolder: PropTypes.func, }; export default GalleryContextMenu; diff --git a/frontend/src/metadata/views/gallery/index.js b/frontend/src/metadata/views/gallery/index.js index fc8fe593bc..c4ba7aade1 100644 --- a/frontend/src/metadata/views/gallery/index.js +++ b/frontend/src/metadata/views/gallery/index.js @@ -10,7 +10,7 @@ import './index.css'; const Gallery = () => { const [isLoadingMore, setLoadingMore] = useState(false); - const { metadata, store, deleteRecords, duplicateRecord, addFolder } = useMetadataView(); + const { metadata, store, deleteRecords, duplicateRecord } = useMetadataView(); const onLoadMore = useCallback(async () => { if (isLoadingMore) return; @@ -49,7 +49,6 @@ const Gallery = () => { onDelete={handleDelete} onLoadMore={onLoadMore} duplicateRecord={duplicateRecord} - onAddFolder={addFolder} /> ); diff --git a/frontend/src/metadata/views/gallery/main.js b/frontend/src/metadata/views/gallery/main.js index fe48005cdd..2f086ce8ad 100644 --- a/frontend/src/metadata/views/gallery/main.js +++ b/frontend/src/metadata/views/gallery/main.js @@ -20,7 +20,7 @@ import './index.css'; const OVER_SCAN_ROWS = 20; -const Main = ({ isLoadingMore, metadata, onDelete, onLoadMore, duplicateRecord, onAddFolder, onRemoveImage, onAddImage, onSetPeoplePhoto }) => { +const Main = ({ isLoadingMore, metadata, onDelete, onLoadMore, duplicateRecord, onRemoveImage, onAddImage, onSetPeoplePhoto }) => { const [isFirstLoading, setFirstLoading] = useState(true); const [zoomGear, setZoomGear] = useState(0); const [containerWidth, setContainerWidth] = useState(0); @@ -268,7 +268,7 @@ const Main = ({ isLoadingMore, metadata, onDelete, onLoadMore, duplicateRecord, }); }, [onDelete, updateCurrentDirent]); - const handelRemoveSelectedImages = useCallback((selectedImages) => { + const handleRemoveSelectedImages = useCallback((selectedImages) => { if (!selectedImages.length) return; onRemoveImage && onRemoveImage(selectedImages, () => { updateCurrentDirent(); @@ -447,8 +447,7 @@ const Main = ({ isLoadingMore, metadata, onDelete, onLoadMore, duplicateRecord, selectedImages={selectedImages} onDelete={handleDeleteSelectedImages} onDuplicate={duplicateRecord} - addFolder={onAddFolder} - onRemoveImage={onRemoveImage ? handelRemoveSelectedImages : null} + onRemoveImage={onRemoveImage ? handleRemoveSelectedImages : null} onAddImage={onAddImage} onSetPeoplePhoto={handleMakeSelectedAsCoverPhoto} /> diff --git a/frontend/src/metadata/views/kanban/boards/board/header/index.js b/frontend/src/metadata/views/kanban/boards/board/header/index.js index f50802f045..5eb4599b50 100644 --- a/frontend/src/metadata/views/kanban/boards/board/header/index.js +++ b/frontend/src/metadata/views/kanban/boards/board/header/index.js @@ -28,7 +28,7 @@ const Header = ({ readonly, haveFreezed, value, groupByColumn, cardsQuantity, ta return event.target.className?.includes('kanban-header-op-btn') || event.target === headerRef.current; }, []); - const handelUnFreezed = useCallback((event) => { + const handleUnFreezed = useCallback((event) => { onUnFreezed(); !keepActive(event) && setActive(false); }, [onUnFreezed, keepActive]); @@ -55,7 +55,7 @@ const Header = ({ readonly, haveFreezed, value, groupByColumn, cardsQuantity, ta {cardsQuantity} - {value && !readonly && } + {value && !readonly && } { + const handleUpdateCurrentDirent = useCallback((record) => { if (!record) return; const recordId = getRecordIdFromRecord(record); const name = getFileNameFromRecord(record); @@ -215,10 +215,10 @@ const Boards = ({ modifyRecord, deleteRecords, modifyColumnData, onCloseSettings const onSelectCard = useCallback((record) => { const recordId = getRecordIdFromRecord(record); if (selectedCard === recordId) return; - handelUpdateCurrentDirent(record); + handleUpdateCurrentDirent(record); onCloseSettings(); showDirentDetail(); - }, [selectedCard, onCloseSettings, showDirentDetail, handelUpdateCurrentDirent]); + }, [selectedCard, onCloseSettings, showDirentDetail, handleUpdateCurrentDirent]); const handleClickOutside = useCallback((event) => { if (isDragging) return; @@ -234,8 +234,8 @@ const Boards = ({ modifyRecord, deleteRecords, modifyColumnData, onCloseSettings event.preventDefault(); if (selectedCard === recordId) return; const record = getRowById(metadata, recordId); - handelUpdateCurrentDirent(record); - }, [metadata, selectedCard, handelUpdateCurrentDirent]); + handleUpdateCurrentDirent(record); + }, [metadata, selectedCard, handleUpdateCurrentDirent]); const onDeleteRecords = useCallback((recordIds) => { deleteRecords(recordIds, { @@ -251,10 +251,10 @@ const Boards = ({ modifyRecord, deleteRecords, modifyColumnData, onCloseSettings success_callback: (operation) => { success_callback && success_callback(operation); const record = getRowById(metadata, rowId); - handelUpdateCurrentDirent(record); + handleUpdateCurrentDirent(record); } }); - }, [metadata, modifyRecord, handelUpdateCurrentDirent]); + }, [metadata, modifyRecord, handleUpdateCurrentDirent]); useEffect(() => { if (!isDirentDetailShow) { diff --git a/frontend/src/metadata/views/kanban/context-menu/index.js b/frontend/src/metadata/views/kanban/context-menu/index.js index c4f82b5d03..61c6675087 100644 --- a/frontend/src/metadata/views/kanban/context-menu/index.js +++ b/frontend/src/metadata/views/kanban/context-menu/index.js @@ -9,7 +9,7 @@ import { gettext } from '../../../../utils/constants'; import { openInNewTab, openParentFolder } from '../../../utils/file'; import { useMetadataView } from '../../../hooks/metadata-view'; import { PRIVATE_COLUMN_KEY } from '../../../constants'; -import { useDownloadFile } from '../../../../hooks/download-file'; +import { useFileOperations } from '../../../../hooks/file-operations'; const CONTEXT_MENU_KEY = { OPEN_IN_NEW_TAB: 'open_in_new_tab', @@ -23,7 +23,7 @@ const KanbanContextMenu = ({ selectedCard, onDelete, onRename }) => { const [isRenameDialogShow, setIsRenameDialogShow] = useState(false); const { metadata } = useMetadataView(); - const { handleDownload: handleDownloadAPI } = useDownloadFile(); + const { handleDownload: handleDownloadAPI } = useFileOperations(); const selectedRecord = useMemo(() => getRowById(metadata, selectedCard), [metadata, selectedCard]); const isDir = useMemo(() => checkIsDir(selectedRecord), [selectedRecord]); diff --git a/frontend/src/metadata/views/kanban/settings/index.js b/frontend/src/metadata/views/kanban/settings/index.js index 777e1e7c21..bd32f3b3f1 100644 --- a/frontend/src/metadata/views/kanban/settings/index.js +++ b/frontend/src/metadata/views/kanban/settings/index.js @@ -65,7 +65,7 @@ const Settings = ({ const displayColumnsConfig = useMemo(() => displayColumns.map(column => ({ key: column.key, shown: column.shown })), [displayColumns]); - const handelUpdateSettings = useCallback((key, value) => { + const handleUpdateSettings = useCallback((key, value) => { modifySettings({ ...settings, [key]: value }); }, [settings, modifySettings]); @@ -74,8 +74,8 @@ const Settings = ({ if (columnConfig.key === key) return { ...columnConfig, shown }; return columnConfig; }); - handelUpdateSettings(KANBAN_SETTINGS_KEYS.COLUMNS, newDisplayColumnsConfig); - }, [displayColumnsConfig, handelUpdateSettings]); + handleUpdateSettings(KANBAN_SETTINGS_KEYS.COLUMNS, newDisplayColumnsConfig); + }, [displayColumnsConfig, handleUpdateSettings]); const onMoveField = useCallback((sourceKey, targetKey) => { const newDisplayColumnsConfig = displayColumnsConfig.slice(0); @@ -84,13 +84,13 @@ const Settings = ({ if (sourceIndex === -1 || targetIndex === -1) return; newDisplayColumnsConfig.splice(sourceIndex, 1, displayColumnsConfig[targetIndex]); newDisplayColumnsConfig.splice(targetIndex, 1, displayColumnsConfig[sourceIndex]); - handelUpdateSettings(KANBAN_SETTINGS_KEYS.COLUMNS, newDisplayColumnsConfig); - }, [displayColumnsConfig, handelUpdateSettings]); + handleUpdateSettings(KANBAN_SETTINGS_KEYS.COLUMNS, newDisplayColumnsConfig); + }, [displayColumnsConfig, handleUpdateSettings]); const onToggleFieldsVisibility = useCallback((visibility) => { const newDisplayColumnsConfig = displayColumnsConfig.map(columnConfig => ({ ...columnConfig, shown: visibility })); - handelUpdateSettings(KANBAN_SETTINGS_KEYS.COLUMNS, newDisplayColumnsConfig); - }, [displayColumnsConfig, handelUpdateSettings]); + handleUpdateSettings(KANBAN_SETTINGS_KEYS.COLUMNS, newDisplayColumnsConfig); + }, [displayColumnsConfig, handleUpdateSettings]); return ( @@ -114,7 +114,7 @@ const Settings = ({ value={settings[KANBAN_SETTINGS_KEYS.GROUP_BY_COLUMN_KEY]} defaultValue={groupByColumnOptions[0]?.value} options={groupByColumnOptions} - onChange={handelUpdateSettings} + onChange={handleUpdateSettings} /> @@ -124,7 +124,7 @@ const Settings = ({ settingKey={KANBAN_SETTINGS_KEYS.TITLE_COLUMN_KEY} value={settings[KANBAN_SETTINGS_KEYS.TITLE_COLUMN_KEY]} options={titleColumnOptions} - onChange={handelUpdateSettings} + onChange={handleUpdateSettings} /> @@ -132,7 +132,7 @@ const Settings = ({ handelUpdateSettings(KANBAN_SETTINGS_KEYS.HIDE_EMPTY_VALUE, !settings[KANBAN_SETTINGS_KEYS.HIDE_EMPTY_VALUE])} + onChange={() => handleUpdateSettings(KANBAN_SETTINGS_KEYS.HIDE_EMPTY_VALUE, !settings[KANBAN_SETTINGS_KEYS.HIDE_EMPTY_VALUE])} /> @@ -140,7 +140,7 @@ const Settings = ({ handelUpdateSettings(KANBAN_SETTINGS_KEYS.SHOW_COLUMN_NAME, !settings[KANBAN_SETTINGS_KEYS.SHOW_COLUMN_NAME])} + onChange={() => handleUpdateSettings(KANBAN_SETTINGS_KEYS.SHOW_COLUMN_NAME, !settings[KANBAN_SETTINGS_KEYS.SHOW_COLUMN_NAME])} /> @@ -148,7 +148,7 @@ const Settings = ({ handelUpdateSettings(KANBAN_SETTINGS_KEYS.TEXT_WRAP, !settings[KANBAN_SETTINGS_KEYS.TEXT_WRAP])} + onChange={() => handleUpdateSettings(KANBAN_SETTINGS_KEYS.TEXT_WRAP, !settings[KANBAN_SETTINGS_KEYS.TEXT_WRAP])} /> diff --git a/frontend/src/metadata/views/table/context-menu.js b/frontend/src/metadata/views/table/context-menu.js index b0f5ef5a76..1dab8dcb0f 100644 --- a/frontend/src/metadata/views/table/context-menu.js +++ b/frontend/src/metadata/views/table/context-menu.js @@ -1,11 +1,9 @@ -import React, { useState, useRef, useCallback, useMemo, useEffect } from 'react'; +import React, { useState, useRef, useCallback, useMemo } from 'react'; import PropTypes from 'prop-types'; import { useMetadataStatus } from '@/hooks'; import { gettext, enableSeafileAI } from '@/utils/constants'; import { Utils } from '@/utils/utils'; import DeleteFolderDialog from '@/components/dialog/delete-folder-dialog'; -import MoveDirentDialog from '@/components/dialog/move-dirent-dialog'; -import { Dirent } from '@/models'; import { useMetadataView } from '../../hooks/metadata-view'; import RowUtils from './utils/row-utils'; import { checkIsDir } from '../../utils/row'; @@ -15,7 +13,6 @@ import { getFileNameFromRecord, getParentDirFromRecord, getRecordIdFromRecord } import ContextMenuComponent from '../../components/context-menu'; import { openInNewTab, openParentFolder } from '../../utils/file'; - const OPERATION = { CLEAR_SELECTED: 'clear-selected', COPY_SELECTED: 'copy-selected', @@ -36,13 +33,12 @@ const OPERATION = { const ContextMenu = ({ isGroupView, selectedRange, selectedPosition, recordMetrics, recordGetterByIndex, onClearSelected, onCopySelected, - getTableContentRect, getTableCanvasContainerRect, deleteRecords, selectNone, moveRecord, addFolder, updateRecordDetails, + getTableContentRect, getTableCanvasContainerRect, deleteRecords, selectNone, updateRecordDetails, updateFaceRecognition, updateRecordDescription, onOCR, generateFileTags }) => { const currentRecord = useRef(null); const [deletedFolderPath, setDeletedFolderPath] = useState(''); - const [isMoveDialogShow, setMoveDialogShow] = useState(false); const { metadata } = useMetadataView(); const { enableOCR } = useMetadataStatus(); @@ -74,11 +70,6 @@ const ContextMenu = ({ setDeletedFolderPath(Utils.joinPath(parentDir, fileName)); }, [deletedFolderPath]); - const toggleMoveDialog = useCallback((record) => { - currentRecord.current = record || null; - setMoveDialogShow(!isMoveDialogShow); - }, [isMoveDialogShow]); - const deleteFolder = useCallback(() => { if (!currentRecord.current) return; const currentRecordId = getRecordIdFromRecord(currentRecord.current); @@ -242,11 +233,6 @@ const ContextMenu = ({ return list; }, [isGroupView, selectedPosition, recordMetrics, selectedRange, metadata, recordGetterByIndex, checkIsDescribableFile, enableOCR, getAbleDeleteRecords]); - const handleMoveRecord = useCallback((...params) => { - selectNone(); - moveRecord && moveRecord(...params); - }, [moveRecord, selectNone]); - const handleOptionClick = useCallback((option, event) => { switch (option.value) { case OPERATION.OPEN_IN_NEW_TAB: { @@ -332,25 +318,14 @@ const ContextMenu = ({ case OPERATION.MOVE: { const { record } = option; if (!record) break; - toggleMoveDialog(record); + window.sfMetadataContext.eventBus.dispatch(EVENT_BUS_TYPE.TOGGLE_MOVE_DIALOG, record); break; } default: { break; } } - }, [repoID, onCopySelected, onClearSelected, updateRecordDescription, generateFileTags, onOCR, deleteRecords, toggleDeleteFolderDialog, selectNone, updateRecordDetails, updateFaceRecognition, toggleMoveDialog]); - - useEffect(() => { - const unsubscribeToggleMoveDialog = window.sfMetadataContext.eventBus.subscribe(EVENT_BUS_TYPE.TOGGLE_MOVE_DIALOG, toggleMoveDialog); - - return () => { - unsubscribeToggleMoveDialog(); - }; - }, [toggleMoveDialog]); - - const currentRecordId = getRecordIdFromRecord(currentRecord.current); - const fileName = getFileNameFromRecord(currentRecord.current); + }, [repoID, onCopySelected, onClearSelected, updateRecordDescription, generateFileTags, onOCR, deleteRecords, toggleDeleteFolderDialog, selectNone, updateRecordDetails, updateFaceRecognition]); const { top, left } = getTableCanvasContainerRect(); const { right, bottom } = getTableContentRect(); @@ -370,17 +345,6 @@ const ContextMenu = ({ toggleDialog={toggleDeleteFolderDialog} /> )} - {isMoveDialogShow && ( - handleMoveRecord(currentRecordId, ...params)} - onCancelMove={toggleMoveDialog} - onAddFolder={addFolder} - /> - )} > ); }; @@ -394,8 +358,6 @@ ContextMenu.propTypes = { getTableContentRect: PropTypes.func, recordGetterByIndex: PropTypes.func, deleteRecords: PropTypes.func, - moveRecord: PropTypes.func, - addFolder: PropTypes.func, }; export default ContextMenu; diff --git a/frontend/src/metadata/views/table/index.js b/frontend/src/metadata/views/table/index.js index ec676c7ea4..c3f96a1b44 100644 --- a/frontend/src/metadata/views/table/index.js +++ b/frontend/src/metadata/views/table/index.js @@ -26,8 +26,6 @@ const Table = () => { modifyColumnWidth, insertColumn, updateFileTags, - moveRecord, - addFolder, updateSelectedRecordIds, updateRecordDetails, updateFaceRecognition, @@ -181,8 +179,6 @@ const Table = () => { generateFileTags={generateFileTags} onGridKeyDown={onHotKey} onGridKeyUp={onHotKeyUp} - moveRecord={moveRecord} - addFolder={addFolder} updateSelectedRecordIds={updateSelectedRecordIds} updateRecordDetails={updateRecordDetails} updateFaceRecognition={updateFaceRecognition} diff --git a/frontend/src/metadata/views/table/table-main/index.js b/frontend/src/metadata/views/table/table-main/index.js index dbd28178b5..23f991b3f9 100644 --- a/frontend/src/metadata/views/table/table-main/index.js +++ b/frontend/src/metadata/views/table/table-main/index.js @@ -48,7 +48,7 @@ const TableMain = ({ modifyRecord && modifyRecord(rowId, updates, oldRowData, originalUpdates, originalOldRowData); }, [modifyRecord]); - const handelInsertColumn = useCallback((name, type, { key, data }) => { + const handleInsertColumn = useCallback((name, type, { key, data }) => { insertColumn && insertColumn(name, type, { key, data }); }, [insertColumn]); @@ -77,7 +77,7 @@ const TableMain = ({ recordGetterById={recordGetterById} recordGetterByIndex={recordGetterByIndex} modifyColumnData={modifyColumnData} - insertColumn={handelInsertColumn} + insertColumn={handleInsertColumn} updateFileTags={updateFileTags} modifyRecords={modifyRecords} {...props} diff --git a/frontend/src/metadata/views/table/table-main/records/body.js b/frontend/src/metadata/views/table/table-main/records/body.js index 9e714dc2bb..5a49010a8e 100644 --- a/frontend/src/metadata/views/table/table-main/records/body.js +++ b/frontend/src/metadata/views/table/table-main/records/body.js @@ -559,7 +559,6 @@ class RecordsBody extends Component { getTableCanvasContainerRect={this.props.getTableCanvasContainerRect} updateFileTags={this.props.updateFileTags} deleteRecords={this.props.deleteRecords} - moveRecord={this.props.moveRecord} generateFileTags={this.props.generateFileTags} /> @@ -628,8 +627,6 @@ RecordsBody.propTypes = { cacheDownloadFilesProps: PropTypes.func, onCellContextMenu: PropTypes.func, getTableCanvasContainerRect: PropTypes.func, - moveRecord: PropTypes.func, - addFolder: PropTypes.func, }; export default RecordsBody; diff --git a/frontend/src/metadata/views/table/table-main/records/index.js b/frontend/src/metadata/views/table/table-main/records/index.js index a9d8767f6a..983b57e83b 100644 --- a/frontend/src/metadata/views/table/table-main/records/index.js +++ b/frontend/src/metadata/views/table/table-main/records/index.js @@ -642,8 +642,6 @@ class Records extends Component { isGroupView={isGroupView} recordGetterByIndex={this.props.recordGetterByIndex} deleteRecords={this.props.deleteRecords} - moveRecord={this.props.moveRecord} - addFolder={this.props.addFolder} selectNone={this.selectNone} updateRecordDetails={this.props.updateRecordDetails} updateFaceRecognition={this.props.updateFaceRecognition} @@ -663,8 +661,6 @@ class Records extends Component { cacheScrollTop: this.storeScrollTop, onCellContextMenu: this.onCellContextMenu, getTableCanvasContainerRect: this.getTableCanvasContainerRect, - moveRecord: this.props.moveRecord, - addFolder: this.props.addFolder }; if (this.props.isGroupView) { return ( @@ -790,8 +786,6 @@ Records.propTypes = { modifyColumnWidth: PropTypes.func, modifyColumnOrder: PropTypes.func, getCopiedRecordsAndColumnsFromRange: PropTypes.func, - moveRecord: PropTypes.func, - addFolder: PropTypes.func, updateSelectedRecordIds: PropTypes.func, }; diff --git a/frontend/src/metadata/views/table/table-main/records/record/cell/operation-btn/file-name-operation-btn/index.js b/frontend/src/metadata/views/table/table-main/records/record/cell/operation-btn/file-name-operation-btn/index.js index 623831453f..f5ec56d571 100644 --- a/frontend/src/metadata/views/table/table-main/records/record/cell/operation-btn/file-name-operation-btn/index.js +++ b/frontend/src/metadata/views/table/table-main/records/record/cell/operation-btn/file-name-operation-btn/index.js @@ -18,7 +18,7 @@ const FileNameOperationBtn = ({ column, record, ...props }) => { const isDir = useMemo(() => checkIsDir(record), [record]); - const handelClick = (event) => { + const handleClick = (event) => { event.stopPropagation(); event.nativeEvent.stopImmediatePropagation(); const repoID = window.sfMetadataContext.getSetting('repoID'); @@ -33,7 +33,7 @@ const FileNameOperationBtn = ({ column, record, ...props }) => { return ( <> - + + @@ -2392,10 +2405,7 @@ class LibContentView extends React.Component { repoTags={this.state.repoTags} selectedDirentList={this.state.selectedDirentList} direntList={direntItemsList} - onItemsMove={this.onMoveItems} - onItemsCopy={this.onCopyItems} onItemsDelete={this.onDeleteItems} - onItemRename={this.onMainPanelItemRename} isRepoOwner={isRepoOwner} currentRepoInfo={this.state.currentRepoInfo} enableDirPrivateShare={enableDirPrivateShare} @@ -2407,7 +2417,6 @@ class LibContentView extends React.Component { showDirentDetail={this.showDirentDetail} currentMode={this.state.currentMode} onItemConvert={this.onConvertItem} - onAddFolder={this.onAddFolder} /> ) ) : ( @@ -2430,8 +2439,6 @@ class LibContentView extends React.Component { toggleTreePanel={this.toggleTreePanel} enableDirPrivateShare={enableDirPrivateShare} showShareBtn={showShareBtn} - onAddFolder={this.onAddFolder} - onAddFile={this.onAddFile} onUploadFile={this.onUploadFile} onUploadFolder={this.onUploadFolder} fullDirentList={this.state.direntList} @@ -2440,8 +2447,8 @@ class LibContentView extends React.Component { repoTags={this.state.repoTags} onItemMove={this.onMoveItem} isDesktop={isDesktop} + eventBus={this.props.eventBus} loadDirentList={this.loadDirentList} - onAddFolderNode={this.onAddFolder} /> )} @@ -2486,8 +2493,6 @@ class LibContentView extends React.Component { onNodeClick={this.onTreeNodeClick} onNodeCollapse={this.onTreeNodeCollapse} onNodeExpanded={this.onTreeNodeExpanded} - onAddFolderNode={this.onAddFolder} - onAddFileNode={this.onAddFile} onRenameNode={this.onRenameTreeNode} onDeleteNode={this.onDeleteTreeNode} isViewFile={this.state.isViewFile} @@ -2509,8 +2514,6 @@ class LibContentView extends React.Component { sortBy={this.state.sortBy} sortOrder={this.state.sortOrder} sortItems={this.sortItems} - onAddFolder={this.onAddFolder} - onAddFile={this.onAddFile} onItemClick={this.onItemClick} onItemSelected={this.onDirentSelected} onItemDelete={this.onMainPanelItemDelete} @@ -2519,7 +2522,6 @@ class LibContentView extends React.Component { renameFileCallback={this.renameItemAjaxCallback} onItemMove={this.onMoveItem} moveFileCallback={this.moveItemsAjaxCallback} - onItemCopy={this.onCopyItem} copyFileCallback={this.copyItemsAjaxCallback} convertFileCallback={this.convertFileAjaxCallback} onItemConvert={this.onConvertItem} @@ -2530,7 +2532,6 @@ class LibContentView extends React.Component { selectedDirentList={this.state.selectedDirentList} onSelectedDirentListUpdate={this.onSelectedDirentListUpdate} onItemsMove={this.onMoveItems} - onItemsCopy={this.onCopyItems} onItemsDelete={this.onDeleteItems} onFileTagChanged={this.onFileTagChanged} showDirentDetail={this.showDirentDetail} @@ -2594,7 +2595,7 @@ class LibContentView extends React.Component { - + ); } } diff --git a/frontend/src/pages/markdown-editor/header-toolbar/header-toolbar.js b/frontend/src/pages/markdown-editor/header-toolbar/header-toolbar.js index a534a7dd47..b35fd42421 100644 --- a/frontend/src/pages/markdown-editor/header-toolbar/header-toolbar.js +++ b/frontend/src/pages/markdown-editor/header-toolbar/header-toolbar.js @@ -8,7 +8,6 @@ import MoreMenu from './more-menu'; import FileInfo from './file-info'; import Icon from '../../../components/icon'; import EmbeddedFileDetails from '../../../components/dirent-detail/embedded-file-details'; -import { TagsProvider } from '../../../tag/hooks'; import { seafileAPI } from '../../../utils/seafile-api'; import { Utils } from '../../../utils/utils'; import Dirent from '../../../../src/models/dirent'; @@ -98,11 +97,7 @@ class HeaderToolbar extends React.Component { eventBus.dispatch(EXTERNAL_EVENTS.ON_ARTICLE_INFO_TOGGLE, this.isFileInfoShow ? null : { component: (props) => { - return ( - - - - ); + return (); }, props: { repoID: repoID, diff --git a/frontend/src/pages/repo-wiki-mode/side-panel.js b/frontend/src/pages/repo-wiki-mode/side-panel.js index 2dfc6021a3..48ebc06923 100644 --- a/frontend/src/pages/repo-wiki-mode/side-panel.js +++ b/frontend/src/pages/repo-wiki-mode/side-panel.js @@ -126,7 +126,6 @@ class SidePanel extends Component { }; onRenameNode = (newName) => { - this.setState({ isRenameDialogShow: !this.state.isRenameDialogShow }); let node = this.state.opNode; this.props.onRenameNode(node, newName); }; diff --git a/frontend/src/shared-dir-view.js b/frontend/src/shared-dir-view.js index 69d43012ab..18a60e1195 100644 --- a/frontend/src/shared-dir-view.js +++ b/frontend/src/shared-dir-view.js @@ -21,7 +21,7 @@ import CopyMoveDirentProgressDialog from './components/dialog/copy-move-dirent-p import RepoInfoBar from './components/repo-info-bar'; import RepoTag from './models/repo-tag'; import { LIST_MODE } from './components/dir-view-mode/constants'; -import { MetadataAIOperationsProvider } from './hooks/metadata-ai-operation'; +import { MetadataAIOperationsProvider } from './hooks'; import ViewModes from './components/view-modes'; import SortMenu from './components/sort-menu'; import { TreeHelper, TreeNode, TreeView } from './components/shared-dir-tree-view'; @@ -76,7 +76,6 @@ class SharedDirView extends React.Component { sortBy: 'name', // 'name' or 'time' or 'size' sortOrder: 'asc', // 'asc' or 'desc' - isZipDialogOpen: false, zipFolderPath: '', usedRepoTags: [], @@ -377,16 +376,15 @@ class SharedDirView extends React.Component { zipDownloadSelectedItems = () => { const { path } = this.state; + let target = this.state.items.filter(item => item.isSelected).map(item => item.file_name || item.folder_name); if (!useGoFileserver) { this.setState({ isZipDialogOpen: true, zipFolderPath: path, - selectedItems: this.state.items.filter(item => item.isSelected) - .map(item => item.file_name || item.folder_name) + selectedItems: target, }); } else { - let target = this.state.items.filter(item => item.isSelected).map(item => item.file_name || item.folder_name); seafileAPI.getShareLinkDirentsZipTask(token, path, target).then((res) => { const zipToken = res.data['zip_token']; location.href = `${fileServerRoot}zip/${zipToken}`; diff --git a/frontend/src/tag/components/dialog/edit-tag-dialog/index.js b/frontend/src/tag/components/dialog/edit-tag-dialog/index.js index d9d1c6b79f..bc4a2df002 100644 --- a/frontend/src/tag/components/dialog/edit-tag-dialog/index.js +++ b/frontend/src/tag/components/dialog/edit-tag-dialog/index.js @@ -62,7 +62,7 @@ const EditTagDialog = ({ tags, tag, title, onSubmit, onToggle }) => { setName(newValue); }, [name]); - const handelColorChange = useCallback((event) => { + const handleColorChange = useCallback((event) => { event.stopPropagation(); event.nativeEvent.stopImmediatePropagation(); const newColor = event.target.value; @@ -83,7 +83,7 @@ const EditTagDialog = ({ tags, tag, title, onSubmit, onToggle }) => { return ( - + { setShowEditTagDialog(false); }, []); - const handelAddTags = useCallback((tag, callback) => { + const handleAddTags = useCallback((tag, callback) => { addTag(tag, callback); }, [addTag]); @@ -96,7 +96,7 @@ const AllTagsOperationToolbar = ({ repoID }) => { {isShowEditTagDialog && ( - + )} {isShowImportLoadingDialog && ( setShowImportLoadingDialog(false)} /> diff --git a/frontend/src/tag/hooks/tag-view.js b/frontend/src/tag/hooks/tag-view.js index 10f4d1ccf4..c9abd072e0 100644 --- a/frontend/src/tag/hooks/tag-view.js +++ b/frontend/src/tag/hooks/tag-view.js @@ -13,16 +13,16 @@ import { getFileById, sortTagFiles } from '../utils/file'; import { getRowById } from '../../components/sf-table/utils/table'; import { getTagFilesLinks } from '../utils/cell'; import { PRIVATE_COLUMN_KEY } from '../constants'; -import URLDecorator from '../../utils/url-decorator'; -import { fileServerRoot, gettext, useGoFileserver } from '../../utils/constants'; +import { gettext } from '../../utils/constants'; import { TAG_FILES_DEFAULT_SORT, TAG_FILES_SORT } from '../constants/sort'; import { TAG_FILES_VIEW_MODE, TAG_FILES_VIEW_MODE_DEFAULT } from '../constants/mode'; +import { useFileOperations } from '../../hooks/file-operations'; // This hook provides content related to seahub interaction, such as whether to enable extended attributes, views data, etc. const TagViewContext = React.createContext(null); export const TagViewProvider = ({ - repoID, tagID, nodeKey, children, moveFileCallback, copyFileCallback, addFolderCallback, deleteFilesCallback, renameFileCallback, convertFileCallback, + repoID, tagID, nodeKey, children, moveFileCallback, copyFileCallback, deleteFilesCallback, renameFileCallback, convertFileCallback, toggleShowDirentToolbar, ...params }) => { const [isLoading, setLoading] = useState(true); @@ -34,6 +34,7 @@ export const TagViewProvider = ({ const [viewMode, setViewMode] = useState(TAG_FILES_VIEW_MODE_DEFAULT); const { tagsData, updateLocalTags } = useTags(); + const { handleDownload, handleMove, handleCopy, handleRename, handleAccessLog, handleShare } = useFileOperations(); const eventBus = useMemo(() => window.sfTagsDataContext?.eventBus, []); const localStorage = useMemo(() => window.sfTagsDataContext?.localStorage, []); @@ -57,19 +58,34 @@ export const TagViewProvider = ({ }, 0); }, [setSelectedFileIds, tagFiles, toggleShowDirentToolbar]); - const moveTagFile = useCallback((targetRepo, dirent, targetParentPath, sourceParentPath, isByDialog) => { - seafileAPI.moveDir(repoID, targetRepo.repo_id, targetParentPath, sourceParentPath, dirent.name).then(res => { - moveFileCallback && moveFileCallback(repoID, targetRepo, dirent, targetParentPath, sourceParentPath, res.data.task_id || null, isByDialog); - updateSelectedFileIds([]); - }); - }, [repoID, moveFileCallback, updateSelectedFileIds]); + const moveTagFile = useCallback(() => { + if (!selectedFileIds || selectedFileIds.length === 0) return null; + const selectedFile = getFileById(tagFiles, selectedFileIds[0]); + const path = selectedFile[TAG_FILE_KEY.PARENT_DIR]; + const dirent = { name: selectedFile[TAG_FILE_KEY.NAME] }; + const callback = (targetRepo, dirent, targetParentPath, sourceParentPath, isByDialog) => { + seafileAPI.moveDir(repoID, targetRepo.repo_id, targetParentPath, sourceParentPath, dirent.name).then(res => { + moveFileCallback && moveFileCallback(repoID, targetRepo, dirent, targetParentPath, sourceParentPath, res.data.task_id || null, isByDialog); + updateSelectedFileIds([]); + }); + }; - const copyTagFile = useCallback((targetRepo, dirent, targetParentPath, sourceParentPath, isByDialog) => { - seafileAPI.copyDir(repoID, targetRepo.repo_id, targetParentPath, sourceParentPath, dirent.name).then(res => { - copyFileCallback && copyFileCallback(repoID, targetRepo, dirent, targetParentPath, sourceParentPath, res.data.task_id || null, isByDialog); - updateSelectedFileIds([]); - }); - }, [repoID, copyFileCallback, updateSelectedFileIds]); + handleMove(path, dirent, false, callback); + }, [repoID, selectedFileIds, tagFiles, moveFileCallback, updateSelectedFileIds, handleMove]); + + const copyTagFile = useCallback(() => { + if (!selectedFileIds || selectedFileIds.length === 0) return null; + const selectedFile = getFileById(tagFiles, selectedFileIds[0]); + const path = selectedFile[TAG_FILE_KEY.PARENT_DIR]; + const dirent = { name: selectedFile[TAG_FILE_KEY.NAME] }; + const callback = (targetRepo, dirent, targetParentPath, sourceParentPath, isByDialog) => { + seafileAPI.copyDir(repoID, targetRepo.repo_id, targetParentPath, sourceParentPath, dirent.name).then(res => { + copyFileCallback && copyFileCallback(repoID, targetRepo, dirent, targetParentPath, sourceParentPath, res.data.task_id || null, isByDialog); + updateSelectedFileIds([]); + }); + }; + handleCopy(path, dirent, false, callback); + }, [repoID, selectedFileIds, tagFiles, copyFileCallback, updateSelectedFileIds, handleCopy]); const deleteTagFiles = useCallback((ids) => { const tagIds = ids?.length ? ids : selectedFileIds; @@ -110,57 +126,80 @@ export const TagViewProvider = ({ }); }, [repoID, tagsData, tagFiles, selectedFileIds, updateLocalTags, deleteFilesCallback, updateSelectedFileIds]); - const getDownloadTarget = useCallback(() => { - if (!selectedFileIds.length) return []; - return selectedFileIds.map(id => { - const file = getFileById(tagFiles, id); - const path = file[TAG_FILE_KEY.PARENT_DIR] === '/' ? file[TAG_FILE_KEY.NAME] : `${file[TAG_FILE_KEY.PARENT_DIR]}/${file[TAG_FILE_KEY.NAME]}`; - return path; - }); - }, [tagFiles, selectedFileIds]); - const downloadTagFiles = useCallback(() => { if (!selectedFileIds.length) return; - if (selectedFileIds.length === 1) { - const file = getFileById(tagFiles, selectedFileIds[0]); - const filePath = Utils.joinPath(file[TAG_FILE_KEY.PARENT_DIR], file[TAG_FILE_KEY.NAME]); - const url = URLDecorator.getUrl({ type: 'download_file_url', repoID, filePath }); - location.href = url; - return; - } - if (!useGoFileserver) { - window.sfTagsDataContext.eventBus.dispatch(EVENT_BUS_TYPE.TOGGLE_ZIP_DIALOG); - return; - } - - const target = getDownloadTarget(); - metadataAPI.zipDownload(repoID, '/', target).then(res => { - const zipToken = res.data['zip_token']; - location.href = `${fileServerRoot}zip/${zipToken}`; - }).catch(error => { - const errMessage = Utils.getErrorMsg(error); - toaster.danger(errMessage); + const direntList = selectedFileIds.map(id => { + const file = getFileById(tagFiles, id); + const name = file[TAG_FILE_KEY.PARENT_DIR] === '/' ? file[TAG_FILE_KEY.NAME] : `${file[TAG_FILE_KEY.PARENT_DIR]}/${file[TAG_FILE_KEY.NAME]}`; + return { name }; }); - }, [repoID, tagFiles, selectedFileIds, getDownloadTarget]); + handleDownload('/', direntList); + }, [tagFiles, selectedFileIds, handleDownload]); - const renameTagFile = useCallback((id, path, newName) => { - seafileAPI.renameFile(repoID, path, newName).then(res => { - renameFileCallback && renameFileCallback(path, newName); - setTagFiles(prevTagFiles => ({ - ...prevTagFiles, - rows: prevTagFiles.rows.map(row => { - if (row[TAG_FILE_KEY.ID] === id) { - return { ...row, [TAG_FILE_KEY.NAME]: newName }; - } - return row; - }) - })); - updateSelectedFileIds([]); - }).catch(error => { - const errMessage = Utils.getErrorMsg(error); + const renameTagFile = useCallback((newName) => { + if (!selectedFileIds || selectedFileIds.length === 0) return null; + const selectedFile = getFileById(tagFiles, selectedFileIds[0]); + const oldName = selectedFile[TAG_FILE_KEY.NAME]; + const path = selectedFile[TAG_FILE_KEY.PARENT_DIR]; + const id = selectedFile[TAG_FILE_KEY.ID]; + const newPath = Utils.joinPath(path, newName); + seafileAPI.getFileInfo(repoID, newPath).then(() => { + let errMessage = gettext('The name "{name}" is already taken. Please choose a different name.'); + errMessage = errMessage.replace('{name}', Utils.HTMLescape(newName)); toaster.danger(errMessage); + }).catch(error => { + if (error && error.response && error.response.status === 404) { + const oldFullPath = Utils.joinPath(path, oldName); + seafileAPI.renameFile(repoID, oldFullPath, newName).then(res => { + renameFileCallback && renameFileCallback(path, newName); + setTagFiles(prevTagFiles => ({ + ...prevTagFiles, + rows: prevTagFiles.rows.map(row => { + if (row[TAG_FILE_KEY.ID] === id) { + return { ...row, [TAG_FILE_KEY.NAME]: newName }; + } + return row; + }) + })); + }).catch(error => { + const errMessage = Utils.getErrorMsg(error); + toaster.danger(errMessage); + }); + } else { + const errMessage = Utils.getErrorMsg(error); + toaster.danger(errMessage); + } }); - }, [repoID, renameFileCallback, updateSelectedFileIds]); + }, [repoID, selectedFileIds, tagFiles, renameFileCallback]); + + const renameTagFileInDialog = useCallback(() => { + if (!selectedFileIds || selectedFileIds.length === 0) return null; + const selectedFile = getFileById(tagFiles, selectedFileIds[0]); + const oldName = selectedFile[TAG_FILE_KEY.NAME]; + const dirent = { name: oldName, type: 'file' }; + handleRename(dirent, [], renameTagFile); + }, [selectedFileIds, tagFiles, handleRename, renameTagFile]); + + const shareTagFile = useCallback(() => { + if (!selectedFileIds || selectedFileIds.length === 0) return null; + const selectedFile = getFileById(tagFiles, selectedFileIds[0]); + const name = selectedFile[TAG_FILE_KEY.NAME]; + const path = Utils.joinPath(selectedFile[TAG_FILE_KEY.PARENT_DIR], name); + const dirent = { + type: 'file', + name, + path, + }; + handleShare(path, dirent); + }, [selectedFileIds, tagFiles, handleShare]); + + const openTagFileAccessLog = useCallback(() => { + if (!selectedFileIds || selectedFileIds.length === 0) return null; + const selectedFile = getFileById(tagFiles, selectedFileIds[0]); + const name = selectedFile[TAG_FILE_KEY.NAME]; + const path = Utils.joinPath(selectedFile[TAG_FILE_KEY.PARENT_DIR], name); + handleAccessLog(path, name); + }, [selectedFileIds, tagFiles, handleAccessLog]); const convertFile = useCallback((path, dstType) => { seafileAPI.convertFile(repoID, path, dstType).then((res) => { @@ -244,16 +283,17 @@ export const TagViewProvider = ({ updateSelectedFileIds, moveTagFile, copyTagFile, - addFolder: addFolderCallback, deleteTagFiles, - getDownloadTarget, downloadTagFiles, + renameTagFileInDialog, renameTagFile, convertFile, sortFiles, sortBy, sortOrder, viewMode, + openTagFileAccessLog, + shareTagFile, }}> {children} diff --git a/frontend/src/tag/tags-tree-view/all-tags/index.js b/frontend/src/tag/tags-tree-view/all-tags/index.js index b30f3ff7fe..05b5d52624 100644 --- a/frontend/src/tag/tags-tree-view/all-tags/index.js +++ b/frontend/src/tag/tags-tree-view/all-tags/index.js @@ -12,7 +12,7 @@ const AllTags = ({ currentPath, selectAllTags }) => { const path = useMemo(() => '/' + PRIVATE_FILE_TYPE.TAGS_PROPERTIES + '/' + ALL_TAGS_ID, []); const isSelected = useMemo(() => currentPath === path, [currentPath, path]); - const handelClick = useCallback(() => { + const handleClick = useCallback(() => { selectAllTags(isSelected); }, [isSelected, selectAllTags]); @@ -20,7 +20,7 @@ const AllTags = ({ currentPath, selectAllTags }) => { return ( {gettext('All tags')} diff --git a/frontend/src/tag/views/all-tags/tags-table/editors/tag-name.js b/frontend/src/tag/views/all-tags/tags-table/editors/tag-name.js index b31aa06ceb..6be2279f07 100644 --- a/frontend/src/tag/views/all-tags/tags-table/editors/tag-name.js +++ b/frontend/src/tag/views/all-tags/tags-table/editors/tag-name.js @@ -14,7 +14,7 @@ const TagNameEditor = forwardRef(({ record, updateTag, onCommitCancel, operation return tagsData?.rows || []; }, [tagsData]); - const handelUpdateTag = useCallback((updates, { success_callback, fail_callback } = {}) => { + const handleUpdateTag = useCallback((updates, { success_callback, fail_callback } = {}) => { const recordId = getRecordIdFromRecord(record); updateTag(recordId, updates, { success_callback, fail_callback }); }, [record, updateTag]); @@ -31,14 +31,7 @@ const TagNameEditor = forwardRef(({ record, updateTag, onCommitCancel, operation } return ( - + ); }); diff --git a/frontend/src/tag/views/all-tags/tags-table/index.js b/frontend/src/tag/views/all-tags/tags-table/index.js index e01339614c..ffab0d8701 100644 --- a/frontend/src/tag/views/all-tags/tags-table/index.js +++ b/frontend/src/tag/views/all-tags/tags-table/index.js @@ -142,7 +142,7 @@ const TagsTable = ({ setIsShowMergeTagsSelector(false); }, []); - const handelAddChildTag = useCallback((tagData, callback) => { + const handleAddChildTag = useCallback((tagData, callback) => { addChildTag(tagData, parentTagIdRef.current, callback); }, [addChildTag]); @@ -333,7 +333,7 @@ const TagsTable = ({ updateSelectedRecordIds={updateSelectedTagIds} /> {isShowNewSubTagDialog && ( - + )} {isShowMergeTagsSelector && ( diff --git a/frontend/src/tag/views/tag-files/grid/index.js b/frontend/src/tag/views/tag-files/grid/index.js index a3ad15d674..8a99d969a9 100644 --- a/frontend/src/tag/views/tag-files/grid/index.js +++ b/frontend/src/tag/views/tag-files/grid/index.js @@ -4,7 +4,7 @@ import { getRecordIdFromRecord } from '../../../../metadata/utils/cell'; import TagFile from './item'; import { hideMenu } from '../../../../components/context-menu/actions'; -const GridView = ({ repoID, openImagePreview, handleRenameTagFile, onTagFileContextMenu }) => { +const GridView = ({ repoID, openImagePreview, onTagFileContextMenu }) => { const [startPoint, setStartPoint] = useState(null); const [endPoint, setEndPoint] = useState(null); const [isMouseDown, setIsMouseDown] = useState(false); @@ -110,7 +110,6 @@ const GridView = ({ repoID, openImagePreview, handleRenameTagFile, onTagFileCont onSelectFile={updateSelectedFileIds} onMultiSelect={onMultiSelect} openImagePreview={openImagePreview} - onRenameFile={handleRenameTagFile} onContextMenu={onTagFileContextMenu} /> ); diff --git a/frontend/src/tag/views/tag-files/grid/item.js b/frontend/src/tag/views/tag-files/grid/item.js index 91d04f44d4..b3ab533ac2 100644 --- a/frontend/src/tag/views/tag-files/grid/item.js +++ b/frontend/src/tag/views/tag-files/grid/item.js @@ -12,8 +12,8 @@ const TagFile = ({ repoID, file, selectedFileIds, onSelectFile, onMultiSelect, o const fileId = useMemo(() => getRecordIdFromRecord(file), [file]); const name = useMemo(() => getFileNameFromRecord(file), [file]); const displayFilename = useMemo(() => { - const convas = document.createElement('canvas'); - const ctx = convas.getContext('2d'); + const canvas = document.createElement('canvas'); + const ctx = canvas.getContext('2d'); ctx.font = '14px Arial'; const metrics = ctx.measureText(name); const textWidth = metrics.width; diff --git a/frontend/src/tag/views/tag-files/index.js b/frontend/src/tag/views/tag-files/index.js index a029691de2..6f60369ca0 100644 --- a/frontend/src/tag/views/tag-files/index.js +++ b/frontend/src/tag/views/tag-files/index.js @@ -1,20 +1,12 @@ -import { useCallback, useState, useRef, useMemo, useEffect } from 'react'; +import React, { useCallback, useState, useRef, useMemo, useEffect } from 'react'; import { useTagView } from '../../hooks'; -import { gettext, username } from '../../../utils/constants'; +import { gettext } from '../../../utils/constants'; import EmptyTip from '../../../components/empty-tip'; import toaster from '../../../components/toast'; import ContextMenu from '../../../components/context-menu/context-menu'; -import MoveDirentDialog from '../../../components/dialog/move-dirent-dialog'; -import CopyDirent from '../../../components/dialog/copy-dirent-dialog'; -import ZipDownloadDialog from '../../../components/dialog/zip-download-dialog'; -import ShareDialog from '../../../components/dialog/share-dialog'; -import FileAccessLog from '../../../components/dialog/file-access-log'; -import Rename from '../../../components/dialog/rename-dirent'; import ImagePreviewer from '../../../metadata/components/cell-formatter/image-previewer'; import TextTranslation from '../../../utils/text-translation'; import { getRecordIdFromRecord } from '../../../metadata/utils/cell'; -import { seafileAPI } from '../../../utils/seafile-api'; -import { TAG_FILE_KEY } from '../../constants/file'; import { Utils } from '../../../utils/utils'; import { getFileById, getFileName, getFileParentDir, getTagFileOperationList } from '../../utils/file'; import { EVENT_BUS_TYPE } from '../../../metadata/constants'; @@ -30,17 +22,11 @@ const TAG_FILE_CONTEXT_MENU_ID = 'tag-files-context-menu'; const TagFiles = () => { const { - tagFiles, repoID, repoInfo, selectedFileIds, updateSelectedFileIds, - moveTagFile, copyTagFile, addFolder, deleteTagFiles, renameTagFile, getDownloadTarget, downloadTagFiles, convertFile, viewMode + tagFiles, repoID, repoInfo, selectedFileIds, updateSelectedFileIds, viewMode, + moveTagFile, copyTagFile, deleteTagFiles, downloadTagFiles, convertFile, shareTagFile, openTagFileAccessLog, + renameTagFileInDialog, renameTagFile, } = useTagView(); - - const [isMoveDialogOpen, setIsMoveDialogOpen] = useState(false); - const [isCopyDialogOpen, setIsCopyDialogOpen] = useState(false); - const [isZipDialogOpen, setIsZipDialogOpen] = useState(false); - const [isShareDialogOpen, setIsShareDialogOpen] = useState(false); - const [isRenameDialogOpen, setIsRenameDialogOpen] = useState(false); const [isImagePreviewerVisible, setImagePreviewerVisible] = useState(false); - const [isFileAccessLogDialogOpen, setIsFileAccessLogDialogOpen] = useState(false); const currentImageRef = useRef(null); @@ -61,26 +47,6 @@ const TagFiles = () => { return selectedFileParentDir && selectedFileName ? Utils.joinPath(selectedFileParentDir, selectedFileName) : ''; }, [selectedFileParentDir, selectedFileName]); - const toggleMoveDialog = useCallback(() => { - setIsMoveDialogOpen(!isMoveDialogOpen); - }, [isMoveDialogOpen]); - - const toggleCopyDialog = useCallback(() => { - setIsCopyDialogOpen(!isCopyDialogOpen); - }, [isCopyDialogOpen]); - - const toggleZipDialog = useCallback(() => { - setIsZipDialogOpen(!isZipDialogOpen); - }, [isZipDialogOpen]); - - const toggleShareDialog = useCallback(() => { - setIsShareDialogOpen(!isShareDialogOpen); - }, [isShareDialogOpen]); - - const toggleRenameDialog = useCallback(() => { - setIsRenameDialogOpen(!isRenameDialogOpen); - }, [isRenameDialogOpen]); - const openImagePreview = useCallback((record) => { currentImageRef.current = record; setImagePreviewerVisible(true); @@ -96,24 +62,6 @@ const TagFiles = () => { updateSelectedFileIds([]); }, [deleteTagFiles, updateSelectedFileIds]); - const handleRenameTagFile = useCallback((newName) => { - const path = selectedFile[TAG_FILE_KEY.PARENT_DIR]; - const newPath = Utils.joinPath(path, newName); - seafileAPI.getFileInfo(repoID, newPath).then(() => { - let errMessage = gettext('The name "{name}" is already taken. Please choose a different name.'); - errMessage = errMessage.replace('{name}', Utils.HTMLescape(newName)); - toaster.danger(errMessage); - }).catch(error => { - if (error && error.response && error.response.status === 404) { - const fullPath = Utils.joinPath(path, selectedFile[TAG_FILE_KEY.NAME]); - renameTagFile(selectedFile[TAG_FILE_KEY.ID], fullPath, newName); - } else { - const errMessage = Utils.getErrorMsg(error); - toaster.danger(errMessage); - } - }); - }, [repoID, selectedFile, renameTagFile]); - const openViaClient = useCallback(() => { let url = URLDecorator.getUrl({ type: 'open_via_client', repoID, selectedFilePath }); location.href = url; @@ -166,25 +114,25 @@ const TagFiles = () => { if (!option) return; switch (option) { case TextTranslation.MOVE.key: - toggleMoveDialog(); + moveTagFile(); break; case TextTranslation.COPY.key: - toggleCopyDialog(); + copyTagFile(); break; case TextTranslation.DELETE.key: handleDeleteTagFiles(); break; case TextTranslation.SHARE.key: - toggleShareDialog(); + shareTagFile(); break; case TextTranslation.DOWNLOAD.key: downloadTagFiles(); break; case TextTranslation.RENAME.key: if (viewMode === LIST_MODE) { - window.sfTagsDataContext && window.sfTagsDataContext.eventBus.dispatch(EVENT_BUS_TYPE.RENAME_TAG_FILE, selectedFileIds[0]); + window.sfTagsDataContext && window.sfTagsDataContext.eventBus.dispatch(EVENT_BUS_TYPE.RENAME_TAG_FILE_IN_SITU, selectedFileIds[0]); } else { - toggleRenameDialog(); + renameTagFileInDialog(selectedFileIds[0]); } break; case TextTranslation.CONVERT_TO_SDOC.key: @@ -210,7 +158,7 @@ const TagFiles = () => { onHistory(); break; case TextTranslation.ACCESS_LOG.key: - setIsFileAccessLogDialogOpen(true); + openTagFileAccessLog(); break; case TextTranslation.OPEN_VIA_CLIENT.key: openViaClient(); @@ -219,7 +167,7 @@ const TagFiles = () => { break; } hideMenu(); - }, [viewMode, toggleRenameDialog, toggleMoveDialog, toggleCopyDialog, handleDeleteTagFiles, downloadTagFiles, selectedFileIds, onConvertFile, exportDocx, exportSdoc, toggleShareDialog, openViaClient, onHistory]); + }, [moveTagFile, copyTagFile, handleDeleteTagFiles, shareTagFile, downloadTagFiles, viewMode, onConvertFile, onHistory, openTagFileAccessLog, openViaClient, selectedFileIds, renameTagFileInDialog, exportDocx, exportSdoc]); const onTagFileContextMenu = useCallback((event, file) => { let menuList = []; @@ -255,14 +203,13 @@ const TagFiles = () => { if (!window.sfTagsDataContext) return; const unsubscribeUnselectFiles = window.sfTagsDataContext.eventBus.subscribe(EVENT_BUS_TYPE.UNSELECT_TAG_FILES, () => updateSelectedFileIds([])); const unsubscribeDeleteTagFiles = window.sfTagsDataContext.eventBus.subscribe(EVENT_BUS_TYPE.DELETE_TAG_FILES, deleteTagFiles); - const unsubScribeMoveTagFile = window.sfTagsDataContext.eventBus.subscribe(EVENT_BUS_TYPE.MOVE_TAG_FILE, toggleMoveDialog); - const unsubScribeCopyTagFile = window.sfTagsDataContext.eventBus.subscribe(EVENT_BUS_TYPE.COPY_TAG_FILE, toggleCopyDialog); - const unsubscribeShareTagFile = window.sfTagsDataContext.eventBus.subscribe(EVENT_BUS_TYPE.SHARE_TAG_FILE, toggleShareDialog); - const unsubscribeRenameTagFile = window.sfTagsDataContext.eventBus.subscribe(EVENT_BUS_TYPE.TOGGLE_RENAME_DIALOG, toggleRenameDialog); + const unsubScribeMoveTagFile = window.sfTagsDataContext.eventBus.subscribe(EVENT_BUS_TYPE.MOVE_TAG_FILE, moveTagFile); + const unsubScribeCopyTagFile = window.sfTagsDataContext.eventBus.subscribe(EVENT_BUS_TYPE.COPY_TAG_FILE, copyTagFile); + const unsubscribeShareTagFile = window.sfTagsDataContext.eventBus.subscribe(EVENT_BUS_TYPE.SHARE_TAG_FILE, shareTagFile); + const unsubscribeRenameTagFile = window.sfTagsDataContext.eventBus.subscribe(EVENT_BUS_TYPE.RENAME_TAG_FILE_IN_DIALOG, renameTagFileInDialog); const unsubscribeDownloadTagFiles = window.sfTagsDataContext.eventBus.subscribe(EVENT_BUS_TYPE.DOWNLOAD_TAG_FILES, downloadTagFiles); - const unsubscribeZipDownload = window.sfTagsDataContext.eventBus.subscribe(EVENT_BUS_TYPE.TOGGLE_ZIP_DIALOG, toggleZipDialog); const unsubscribeFileHistory = window.sfTagsDataContext.eventBus.subscribe(EVENT_BUS_TYPE.FILE_HISTORY, onHistory); - const unsubscribeFileAccessLog = window.sfTagsDataContext.eventBus.subscribe(EVENT_BUS_TYPE.FILE_ACCESS_LOG, () => setIsFileAccessLogDialogOpen(true)); + const unsubscribeFileAccessLog = window.sfTagsDataContext.eventBus.subscribe(EVENT_BUS_TYPE.FILE_ACCESS_LOG, openTagFileAccessLog); const unsubscribeOpenViaClient = window.sfTagsDataContext.eventBus.subscribe(EVENT_BUS_TYPE.OPEN_VIA_CLIENT, openViaClient); const unsubscribeConvertFile = window.sfTagsDataContext.eventBus.subscribe(EVENT_BUS_TYPE.CONVERT_FILE, onConvertFile); const unsubscribeExportDocx = window.sfTagsDataContext.eventBus.subscribe(EVENT_BUS_TYPE.EXPORT_DOCX, exportDocx); @@ -276,7 +223,6 @@ const TagFiles = () => { unsubscribeShareTagFile(); unsubscribeRenameTagFile(); unsubscribeDownloadTagFiles(); - unsubscribeZipDownload(); unsubscribeFileHistory(); unsubscribeFileAccessLog(); unsubscribeOpenViaClient(); @@ -290,28 +236,19 @@ const TagFiles = () => { return (); } - let enableDirPrivateShare = false; - let isRepoOwner = repoInfo.owner_email === username; - let isVirtual = repoInfo.is_virtual; - let isAdmin = repoInfo.is_admin; - if (!isVirtual && (isRepoOwner || isAdmin)) { - enableDirPrivateShare = true; - } - const isGroupOwnedRepo = repoInfo.owner_email.includes('@seafile_group'); return ( <> {viewMode === LIST_MODE ? ( ) : ( )} @@ -331,67 +268,6 @@ const TagFiles = () => { onMenuItemClick={onMenuItemClick} getMenuContainerSize={getMenuContainerSize} /> - {isMoveDialogOpen && ( - - )} - {isCopyDialogOpen && ( - - )} - {isZipDialogOpen && ( - - )} - {isShareDialogOpen && - - } - {isRenameDialogOpen && - {}} - toggleCancel={toggleRenameDialog} - /> - } - {isFileAccessLogDialogOpen && - setIsFileAccessLogDialogOpen(false)} - /> - } > ); }; diff --git a/frontend/src/tag/views/tag-files/list/index.js b/frontend/src/tag/views/tag-files/list/index.js index 61ccb631ff..516f46b7bb 100644 --- a/frontend/src/tag/views/tag-files/list/index.js +++ b/frontend/src/tag/views/tag-files/list/index.js @@ -9,7 +9,7 @@ import TagFile from './item'; import { hideMenu } from '../../../../components/context-menu/actions'; import { EVENT_BUS_TYPE } from '../../../../metadata/constants'; -const ListView = ({ repoID, openImagePreview, handleRenameTagFile, onTagFileContextMenu }) => { +const ListView = ({ repoID, openImagePreview, renameTagFile, onTagFileContextMenu }) => { const [renameTargetId, setRenameTargetId] = useState(null); const { tagsData } = useTags(); @@ -74,8 +74,8 @@ const ListView = ({ repoID, openImagePreview, handleRenameTagFile, onTagFileCont const onRenameConfirm = useCallback((newName) => { onRenameCancel(); - handleRenameTagFile(newName); - }, [onRenameCancel, handleRenameTagFile]); + renameTagFile(newName); + }, [onRenameCancel, renameTagFile]); const onContainerClick = useCallback(() => { hideMenu(); @@ -84,7 +84,7 @@ const ListView = ({ repoID, openImagePreview, handleRenameTagFile, onTagFileCont useEffect(() => { if (!window.sfTagsDataContext) return; - const unsubscribeRenameTagFile = window.sfTagsDataContext.eventBus.subscribe(EVENT_BUS_TYPE.RENAME_TAG_FILE, (id) => setRenameTargetId(id)); + const unsubscribeRenameTagFile = window.sfTagsDataContext.eventBus.subscribe(EVENT_BUS_TYPE.RENAME_TAG_FILE_IN_SITU, (id) => setRenameTargetId(id)); return () => { unsubscribeRenameTagFile && unsubscribeRenameTagFile(); diff --git a/frontend/src/tag/views/tag-files/list/item.js b/frontend/src/tag/views/tag-files/list/item.js index 3472d77a75..d502a3461c 100644 --- a/frontend/src/tag/views/tag-files/list/item.js +++ b/frontend/src/tag/views/tag-files/list/item.js @@ -77,7 +77,7 @@ const TagFile = ({ repoID, file, tagsData, isRenaming, onRenameCancel, onRenameC setIconLoadError(true); }, []); - const handelClickFileName = useCallback((event) => { + const handleClickFileName = useCallback((event) => { event.preventDefault(); event.stopPropagation(); const canPreview = window.sfTagsDataContext.canPreview(); @@ -87,7 +87,7 @@ const TagFile = ({ repoID, file, tagsData, isRenaming, onRenameCancel, onRenameC }); }, [repoID, file, openImagePreview, isRenaming]); - const handelClick = useCallback((event) => { + const handleClick = useCallback((event) => { event.stopPropagation(); if (isRenaming) return; if (event.target.tagName === 'TD' && event.target.closest('td').querySelector('input[type="checkbox"]') === null) { @@ -112,7 +112,7 @@ const TagFile = ({ repoID, file, tagsData, isRenaming, onRenameCancel, onRenameC 'tr-highlight': highlight, 'tr-active': isSelected })} - onClick={handelClick} + onClick={handleClick} onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave} onContextMenu={handleContextMenu} @@ -130,7 +130,7 @@ const TagFile = ({ repoID, file, tagsData, isRenaming, onRenameCancel, onRenameC e.preventDefault()}> - + @@ -142,7 +142,7 @@ const TagFile = ({ repoID, file, tagsData, isRenaming, onRenameCancel, onRenameC onRenameCancel={onRenameCancel} /> ) : ( - {name} + {name} )}
{type === 'file' ? gettext('New file name') : gettext('New folder name')}
{gettext('You can create files quickly')}{' +'}