diff --git a/src/components/ItemTree/CloseDir/index.jsx b/src/components/ItemTree/CloseDir/index.jsx new file mode 100644 index 0000000..7d3c09e --- /dev/null +++ b/src/components/ItemTree/CloseDir/index.jsx @@ -0,0 +1,15 @@ +import React from 'react'; +import {Menu} from 'antd'; +import {useDispatch} from "react-redux"; +import {dirRemove} from "../../../redux/dirMessage_reducer"; + +function CloseDir (prop) { + console.log("prop",prop) + const dispatch = useDispatch() + const closeDir = ()=>{ + dispatch(dirRemove({selectDirKey: prop.filePath})) + prop.closeMenu() + } + return 关闭目录 +} +export default CloseDir; \ No newline at end of file diff --git a/src/components/ItemTree/DirAddFile/index.jsx b/src/components/ItemTree/DirAddFile/index.jsx new file mode 100644 index 0000000..fd766f2 --- /dev/null +++ b/src/components/ItemTree/DirAddFile/index.jsx @@ -0,0 +1,56 @@ +import React, {useRef, useState} from 'react'; +import {Input, message, Modal} from 'antd'; +import {useDispatch} from "react-redux"; +import {newFile} from "../../../utils/File"; +import {dirFileAdd} from "../../../redux/dirMessage_reducer"; +import {addTableBarItem} from "../../../redux/tableBarItem_reducer"; +import {isEmpty} from "../../../utils/ObjectUtils"; + +const DirAddFile = (prop) => { + console.log("prop",prop) + const [isModalOpen, setIsModalOpen] = useState(false); + const dispatch = useDispatch(); + const inputValue = useRef(null); + const [messageApi, contextHolder] = message.useMessage(); + + const showModal = () => { + setIsModalOpen(true); + }; + const handleOk = () => { + console.log("inputValue",inputValue.current.input.value) + // 如果为空则提示 + if (isEmpty(inputValue.current.input.value)){ + let messageType = messageApi.open({ + type: 'error', + content: '文件名不能为空', + }); + return + } + // 新建文件 + let fileName = prop.filePath+"/"+inputValue.current.input.value+".lexical" + newFile(fileName) + // 更新树 + dispatch(dirFileAdd({"filePath":prop.filePath,fileName})) + // 选中key,添加bar + dispatch(addTableBarItem({ + label: inputValue.current.input.value+".lexical", + children: fileName, + key: fileName, + activeKey: fileName + })) + setIsModalOpen(false); + prop.closeMenu() + }; + const handleCancel = () => { + setIsModalOpen(false); + prop.closeMenu() + }; + return <> + {contextHolder} + 添加文件 + + + + ; +}; +export default DirAddFile; \ No newline at end of file diff --git a/src/components/ItemTree/DirDeleteFile/index.jsx b/src/components/ItemTree/DirDeleteFile/index.jsx new file mode 100644 index 0000000..c5152e6 --- /dev/null +++ b/src/components/ItemTree/DirDeleteFile/index.jsx @@ -0,0 +1,55 @@ +import React from 'react'; +import {Popconfirm} from 'antd'; +import {deleteFileAndDir} from "../../../utils/File"; +import {useDispatch, useSelector} from "react-redux"; +import {dirFileRemove} from "../../../redux/dirMessage_reducer"; +import {removeTableBarItem, setActiveKey} from "../../../redux/tableBarItem_reducer"; +const DirDeleteFile = (prop) => { + console.log("prop",prop) + const dispatch= useDispatch() + const activeKey=useSelector(state => state.tableBarItem.activeKey); + const items = useSelector(state => state.tableBarItem.data) + + const deleteFile = () => { + // 删除文件 + deleteFileAndDir(prop.filePath) + // 更新树 + dispatch(dirFileRemove({"filePath":prop.filePath})) + // 更新bar + let targetKey = prop.filePath + let newActiveKey = activeKey; + let lastIndex = -1; + items.forEach((item, i) => { + if (item.key === targetKey) { + lastIndex = i - 1; + } + }); + dispatch(removeTableBarItem(targetKey)); + const newPanes = items.filter((item) => item.key !== targetKey); + if (newPanes.length && newActiveKey === targetKey) { + if (lastIndex >= 0) { + newActiveKey = newPanes[lastIndex].key; + } else { + newActiveKey = newPanes[0].key; + } + } + dispatch(setActiveKey({"activeKey":newActiveKey})); + prop.closeMenu() + }; + const cancelDeleteFile = () => { + + prop.closeMenu() + }; + return + 删除文件 + ; +}; +export default DirDeleteFile; \ No newline at end of file diff --git a/src/components/ItemTree/RefreshDir/index.jsx b/src/components/ItemTree/RefreshDir/index.jsx new file mode 100644 index 0000000..fc43820 --- /dev/null +++ b/src/components/ItemTree/RefreshDir/index.jsx @@ -0,0 +1,19 @@ +import React, {useRef, useState} from 'react'; +import {Button, Input, Menu, Modal} from 'antd'; +import {readDir} from "../../../utils/File"; +import {nextDirAdd} from "../../../redux/dirMessage_reducer"; +import {useDispatch} from "react-redux"; + +const RefreshDir = (prop) => { + console.log("prop",prop) + const dispatch = useDispatch() + const refreshDir = () => { + prop.refreshDir(prop.filePath) + prop.closeMenu() + }; + + return ( + 更新目录 + ); +}; +export default RefreshDir; \ No newline at end of file diff --git a/src/components/ItemTree/UpdateFileName/index.jsx b/src/components/ItemTree/UpdateFileName/index.jsx new file mode 100644 index 0000000..8088680 --- /dev/null +++ b/src/components/ItemTree/UpdateFileName/index.jsx @@ -0,0 +1,48 @@ +import React, {useRef, useState} from 'react'; +import {Input, Menu, Modal} from 'antd'; +import {updateFileName} from "../../../utils/File"; +import {useDispatch} from "react-redux"; +import {updateFileName as updateFileNameRedux} from "../../../redux/dirMessage_reducer"; +import {updateFileName as updateFileNameBar} from "../../../redux/tableBarItem_reducer"; + +const UpdateFileName = (prop) => { + console.log("prop",prop) + const [isModalOpen, setIsModalOpen] = useState(false); + const dispatch = useDispatch(); + const inputValue = useRef(null); + const showModal = () => { + setIsModalOpen(true); + }; + const handleOk = () => { + setIsModalOpen(false); + console.log("inputValue",inputValue.current.input.value) + // 如果和原始名称一样,关闭菜单直接返回 + // 如果不一样,修改文件名并且更新树,同步条目 + if (prop.fileName!==inputValue.current.input.value){ + // 修改文件名 + let first = prop.filePath.lastIndexOf("/") + let second = prop.filePath.lastIndexOf(".") + let newFilePath= prop.filePath.substring(0,first+1)+inputValue.current.input.value+prop.filePath.substring(second) + updateFileName(prop.filePath,newFilePath) + // 更新树 + dispatch(updateFileNameRedux({"oldFilePath":prop.filePath,"newFilePath":newFilePath})) + // 同步条目 + dispatch(updateFileNameBar({"oldFilePath":prop.filePath,"newFilePath":newFilePath})) + + } + prop.closeMenu() + }; + const handleCancel = () => { + setIsModalOpen(false); + prop.closeMenu() + }; + return ( + <> + 修改文件名 + + + + + ); +}; +export default UpdateFileName; \ No newline at end of file diff --git a/src/components/ItemTree/index.jsx b/src/components/ItemTree/index.jsx index 5856aa8..1281472 100644 --- a/src/components/ItemTree/index.jsx +++ b/src/components/ItemTree/index.jsx @@ -1,16 +1,19 @@ import React, {useEffect, useMemo, useState} from 'react'; import {Input, Menu, Tree} from 'antd'; import {FolderOutlined, FileMarkdownOutlined, FileOutlined, DeleteOutlined, RedoOutlined} from '@ant-design/icons'; -import {message, Popconfirm} from 'antd'; import "./index.less" - +import {getFileNameByPath} from "../../utils/File"; const {Search} = Input; import {useSelector, useDispatch} from "react-redux"; import {addExpandedKeys, addTableBarItem, setExpandedKeys} from "../../redux/tableBarItem_reducer"; import {readDir} from "../../utils/File"; import {nextDirAdd} from "../../redux/dirMessage_reducer"; -import Icon from "antd/lib/icon"; import {isEmpty} from "../../utils/ObjectUtils"; +import UpdateFileName from "./UpdateFileName"; +import RefreshDir from "./RefreshDir"; +import CloseDir from "./CloseDir"; +import DirAddFile from "./DirAddFile"; +import DirDeleteFile from "./DirDeleteFile"; // const defaultData = []; // 将树平铺用于查找 const dataList = []; @@ -54,7 +57,7 @@ function generateChildList(fileList) { } result.push({ "key": filePath, - "title": titleExtended(fileName,dirFlag,filePath), + "title": titleExtended(fileName, dirFlag, filePath), "icon": dirFlag ? : fileName.endsWith(".md") ? : , "dirFlag": dirFlag, "children": childListM @@ -63,40 +66,8 @@ function generateChildList(fileList) { return result; } -const titleExtended = (fileName, dirFlag,filePath) => { - - const confirm = (e) => { - console.log(e); - message.success('Click on Yes'); - }; - const cancel = (e) => { - console.log(e); - message.error('Click on No'); - }; - return {fileName} - {dirFlag && - - - - - - - } - +const titleExtended = (fileName, dirFlag, filePath) => { + return {fileName} } /** * 将文件信息改为树信息 @@ -117,7 +88,7 @@ const flushTree = (fileDirDate) => { defaultValueStateSet.push({ "key": filePath, // 修改属性后此处也需要修改 - "title": titleExtended(fileName.substring(fileName.lastIndexOf("/")+1), dirFlag,filePath), + "title": titleExtended(getFileNameByPath(fileName), dirFlag, filePath), "icon": , "dirFlag": dirFlag, "children": childListM @@ -171,30 +142,38 @@ const ItemTree = (prop) => { setSearchValue(value); setAutoExpandParent(true); }; + + const refreshDir = (filePath) => { + readDir(filePath).then(fileStateList => { + if (Array.isArray(fileStateList[0].childList) && fileStateList[0].childList.length > 0) { + dispatch(nextDirAdd({selectDirKey: filePath, fileStateList})) + // 添加下级节点 + addChildNode(defaultValueState, flushTree(fileStateList)) + const result = [...defaultValueState] + console.log("[...defaultValueState]:", result) + setDefaultValueState(result) + // 打开当前目录 + dispatch(addExpandedKeys([filePath])); + setAutoExpandParent(false); + } + }) + } const onSelect = (selectedKeys, e) => { if (e.selected) { console.log('onSelect.selectedKeys', selectedKeys, e) if (e.node.dirFlag) { // 加载目录下一级文件信息 if (!Array.isArray(e.node.children) || e.node.children.length === 0) { - readDir(e.node.key).then(fileStateList => { - if (Array.isArray(fileStateList[0].childList) && fileStateList[0].childList.length > 0) { - dispatch(nextDirAdd({selectDirKey: e.node.key, fileStateList})) - // 添加下级节点 - addChildNode(defaultValueState, flushTree(fileStateList)) - const result = [...defaultValueState] - console.log("[...defaultValueState]:", result) - setDefaultValueState(result) - } - }) + refreshDir(e.node.key) + } else { + // 打开当前目录 + dispatch(addExpandedKeys([e.node.key])); + setAutoExpandParent(false); } - // 打开当前目录 - dispatch(addExpandedKeys([e.node.key])); - setAutoExpandParent(false); } else { // 打开文件 dispatch(addTableBarItem({ - label: e.node.title.props.children[0], + label: e.node.title.props.children, children: e.node.key, key: e.node.key, activeKey: e.node.key @@ -232,35 +211,59 @@ const ItemTree = (prop) => { // }); // return loop(defaultData); // }, [searchValue]); - const treeNodeOnRightClick=(e,node)=> { - console.log("e,node",node) + const treeNodeOnRightClick = (e) => { + console.log("e,node", e) setState({ rightClickNodeTreeItem: { pageX: e.event.pageX, pageY: e.event.pageY, - id: e.node.key + key: e.node.key, + dirFlag: e.node.dirFlag, + title: getFileNameByPath(e.node.key), } }); } - const getNodeTreeRightClickMenu=()=> { - console.log("state",state,isEmpty(state)) - if (isEmpty(state)){ + const getNodeTreeRightClickMenu = () => { + console.log("state", state, isEmpty(state)) + if (isEmpty(state)) { return } - const {pageX, pageY} = {...state.rightClickNodeTreeItem}; + const {pageX, pageY, title, dirFlag, key} = {...state.rightClickNodeTreeItem}; const tmpStyle = { position: 'absolute', left: `${pageX}px`, top: `${pageY}px`, - popupBg:"#ec0f0f", - "z-index":"9999" + popupBg: "#ec0f0f", + zIndex: "9999" }; + const menuItem = [ + !dirFlag && + + setState("")}/> + , + dirFlag && + + setState("")}/> + , + dirFlag && + + setState("")}/> + , + !dirFlag && + + setState("")}> + , + dirFlag && + + setState("")}/> + , + { + setState("") + }}>关闭菜单 + ] return ( - - {'修改文件名'} - {'添加文件'} - {'修改'} - {'删除目录'} + + {menuItem} ) } diff --git a/src/redux/dirMessage_reducer.js b/src/redux/dirMessage_reducer.js index c247687..0987462 100644 --- a/src/redux/dirMessage_reducer.js +++ b/src/redux/dirMessage_reducer.js @@ -46,6 +46,21 @@ export const dirMessageSlice = createSlice({ } } }, + dirRemove:(state,action)=>{ + console.log("dirMessage:dirRemove", state, action) + // 获取当前选中的key + let selectDirKey = action.payload.selectDirKey; + state.data = state.data.filter(file=>{ + if (file.filePath === selectDirKey && file.dirFlag) { + return false; + } else if (file.childList.length > 0 && selectDirKey.startsWith(file.filePath)) { + file.childList = filterChild(file.childList, selectDirKey) + return true; + }else { + return true; + } + }) + }, nextDirAdd: (state, action) => { console.log("dirMessage:nextDirAdd", state, action) // 获取当前选中的key @@ -59,10 +74,99 @@ export const dirMessageSlice = createSlice({ findChild(file.childList, action, selectDirKey) } }) + }, + updateFileName:(state,action)=>{ + console.log("dirMessage:updateFileName", state, action) + let newFilePath = action.payload.newFilePath + let oldFilePath = action.payload.oldFilePath + // 查找旧文件并且修改文件信息 + state.data.forEach(file => { + if (file.filePath === oldFilePath) { + file.filePath = newFilePath + file.fileName = newFilePath.substring(newFilePath.lastIndexOf("/")+1) + } else if (file.childList.length > 0 && oldFilePath.startsWith(file.filePath)) { + updateFileNameChild(file.childList, oldFilePath, newFilePath) + } + }) + }, + dirFileAdd:(state,action)=>{ + console.log("dirMessage:dirFileAdd", state, action) + let filePath = action.payload.filePath + let fileName = action.payload.fileName + let fileMessage = { + "fileName": fileName.replace(filePath+"/",""), + "filePath": fileName, + "dirFlag": false, + "childList": [] + } + // 查找旧文件并且修改文件信息 + state.data.forEach(file => { + if (file.filePath === filePath) { + if (Array.isArray(file.children)){ + file.children.push(fileMessage) + }else { + file.children=[fileMessage] + } + } else if (file.childList.length > 0 && file.filePath.startsWith(filePath)) { + dirFileAddChild(file.childList, filePath, fileMessage) + } + }) + }, + dirFileRemove:(state,action)=>{ + let filePath = action.payload.filePath; + state.data = state.data.filter(file=>{ + if (file.filePath === filePath && !file.dirFlag) { + return false; + } else if (file.childList.length > 0 && filePath.startsWith(file.filePath)) { + file.childList = dirFileRemoveChild(file.childList, filePath) + return true; + }else { + return true; + } + }) } } }) +function dirFileRemoveChild(fileList, selectDirKey) { + return fileList.filter(file => { + if (file.filePath === selectDirKey && !file.dirFlag) { + return false + }else if (file.dirFlag && selectDirKey.startsWith(file.filePath) && Array.isArray(file.childList) && file.childList.length > 0) { + dirFileRemoveChild(file.childList, selectDirKey) + return true + }else { + return true + } + }) +} + +function dirFileAddChild(fileList, filePath, fileMessage){ + fileList.forEach(file => { + if (file.filePath === filePath) { + if (Array.isArray(file.children)){ + file.children.push(fileMessage) + }else { + file.children=[fileMessage] + } + } else if (file.childList.length > 0 && file.filePath.startsWith(filePath)) { + dirFileAddChild(file.childList, filePath, fileMessage) + } + }) +} +function filterChild(fileList, selectDirKey) { + return fileList.filter(file => { + if (file.filePath === selectDirKey && file.dirFlag) { + return false + }else if (file.dirFlag && selectDirKey.startsWith(file.filePath) && Array.isArray(file.childList) && file.childList.length > 0) { + filterChild(file.childList, selectDirKey) + return true + }else { + return true + } + }) +} + function findChild(fileList, action, selectDirKey) { fileList.forEach(file => { if (file.filePath === selectDirKey && file.dirFlag && @@ -76,9 +180,25 @@ function findChild(fileList, action, selectDirKey) { }) } +function updateFileNameChild(fileList, oldFilePath, newFilePath) { + fileList.forEach(file => { + if (file.filePath === oldFilePath) { + file.filePath = newFilePath + file.fileName = newFilePath.substring(newFilePath.lastIndexOf("/")+1) + return + }else if (Array.isArray(file.childList) && file.childList.length > 0 && oldFilePath.startsWith(file.filePath)) { + updateFileNameChild(file.childList, oldFilePath, newFilePath) + } + }) +} + export const { dirAdd, - nextDirAdd + nextDirAdd, + dirRemove, + updateFileName, + dirFileAdd, + dirFileRemove, } = dirMessageSlice.actions export default dirMessageSlice.reducer diff --git a/src/redux/tableBarItem_reducer.js b/src/redux/tableBarItem_reducer.js index 424a236..5504c6b 100644 --- a/src/redux/tableBarItem_reducer.js +++ b/src/redux/tableBarItem_reducer.js @@ -40,6 +40,22 @@ export const tableBarItemSlice = createSlice({ state.activeKey=action.payload.activeKey } }, + updateFileName:(state,action)=>{ + console.log("tableBarItemSlice:updateFileName", state, action) + let newFilePath = action.payload.newFilePath + let oldFilePath = action.payload.oldFilePath + // 查找旧文件并且修改文件信息 + state.data.forEach(file => { + if (file.key === oldFilePath) { + file.key = newFilePath + file.children= newFilePath + file.label = newFilePath.substring(newFilePath.lastIndexOf("/")+1) + } + }) + if (state.activeKey===oldFilePath){ + state.activeKey = newFilePath + } + }, updatedSavedFile:(state, action)=>{ console.log("tableBarItemSlice:updatedSavedFile",action.payload) // 如果其中包含了相同的key,则是文件覆盖。 @@ -75,6 +91,7 @@ export const { addTableBarItem, updatedSavedFile, setExpandedKeys, removeExpandedKeys, - addExpandedKeys + addExpandedKeys, + updateFileName } = tableBarItemSlice.actions export default tableBarItemSlice.reducer diff --git a/src/utils/File/index.jsx b/src/utils/File/index.jsx index 94046da..e4593da 100644 --- a/src/utils/File/index.jsx +++ b/src/utils/File/index.jsx @@ -35,6 +35,28 @@ export async function overWriteFile(filePath,jsonText) { await fs.writeFile(filePath,jsonText,{"encoding":"utf-8"}) } +export async function newFile(filePath) { + await fs.writeFile(filePath,"",{"encoding":"utf-8"}) +} + +export async function deleteFileAndDir(filePath) { + await fs.rm(filePath) +} + +export async function updateFileName(oldFileName,newFileName){ + await fs.rename(oldFileName,newFileName) +} + export async function saveFileWithName(){ return ipcRenderer.invoke("saveFileWithName" ) +} + +export function getFileNameByPath(fileName){ + // 获取文件名 + let fileFullName = fileName.substring(fileName.lastIndexOf("/")+1); + let number = fileFullName.lastIndexOf("."); + if (number>0){ + return fileFullName.substring(0,number); + } + return fileFullName } \ No newline at end of file