feat:菜单功能

This commit is contained in:
shixiaohua 2024-02-19 16:11:55 +08:00
parent 9037bfe7c9
commit fe07a2f982
9 changed files with 424 additions and 69 deletions

View File

@ -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 <span onClick={closeDir}>关闭目录</span>
}
export default CloseDir;

View File

@ -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}))
// keybar
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}
<span onClick={showModal}>添加文件</span>
<Modal title="新建文件名" open={isModalOpen} onOk={handleOk} onCancel={handleCancel}>
<Input ref={inputValue} placeholder="文件名不能为空" />
</Modal>
</>;
};
export default DirAddFile;

View File

@ -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 <Popconfirm
placement="right"
title="确认删除文件"
description="文件删除后将从磁盘删除"
onConfirm={deleteFile}
onCancel={cancelDeleteFile}
okText="确认"
cancelText="取消"
>
<span>删除文件</span>
</Popconfirm>;
};
export default DirDeleteFile;

View File

@ -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 (
<span onClick={refreshDir}>更新目录</span>
);
};
export default RefreshDir;

View File

@ -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 (
<>
<span onClick={showModal}>修改文件名</span>
<Modal title="修改文件名" open={isModalOpen} onOk={handleOk} onCancel={handleCancel}>
<Input ref={inputValue} placeholder={prop.fileName} />
</Modal>
</>
);
};
export default UpdateFileName;

View File

@ -1,16 +1,19 @@
import React, {useEffect, useMemo, useState} from 'react'; import React, {useEffect, useMemo, useState} from 'react';
import {Input, Menu, Tree} from 'antd'; import {Input, Menu, Tree} from 'antd';
import {FolderOutlined, FileMarkdownOutlined, FileOutlined, DeleteOutlined, RedoOutlined} from '@ant-design/icons'; import {FolderOutlined, FileMarkdownOutlined, FileOutlined, DeleteOutlined, RedoOutlined} from '@ant-design/icons';
import {message, Popconfirm} from 'antd';
import "./index.less" import "./index.less"
import {getFileNameByPath} from "../../utils/File";
const {Search} = Input; const {Search} = Input;
import {useSelector, useDispatch} from "react-redux"; import {useSelector, useDispatch} from "react-redux";
import {addExpandedKeys, addTableBarItem, setExpandedKeys} from "../../redux/tableBarItem_reducer"; import {addExpandedKeys, addTableBarItem, setExpandedKeys} from "../../redux/tableBarItem_reducer";
import {readDir} from "../../utils/File"; import {readDir} from "../../utils/File";
import {nextDirAdd} from "../../redux/dirMessage_reducer"; import {nextDirAdd} from "../../redux/dirMessage_reducer";
import Icon from "antd/lib/icon";
import {isEmpty} from "../../utils/ObjectUtils"; 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 defaultData = [];
// //
const dataList = []; const dataList = [];
@ -54,7 +57,7 @@ function generateChildList(fileList) {
} }
result.push({ result.push({
"key": filePath, "key": filePath,
"title": titleExtended(fileName,dirFlag,filePath), "title": titleExtended(fileName, dirFlag, filePath),
"icon": dirFlag ? <FolderOutlined/> : fileName.endsWith(".md") ? <FileMarkdownOutlined/> : <FileOutlined/>, "icon": dirFlag ? <FolderOutlined/> : fileName.endsWith(".md") ? <FileMarkdownOutlined/> : <FileOutlined/>,
"dirFlag": dirFlag, "dirFlag": dirFlag,
"children": childListM "children": childListM
@ -63,40 +66,8 @@ function generateChildList(fileList) {
return result; return result;
} }
const titleExtended = (fileName, dirFlag,filePath) => { const titleExtended = (fileName, dirFlag, filePath) => {
return <span title={filePath}>{fileName}</span>
const confirm = (e) => {
console.log(e);
message.success('Click on Yes');
};
const cancel = (e) => {
console.log(e);
message.error('Click on No');
};
return <span title = {filePath}>{fileName}
{dirFlag && <span style={{float: "right"}}>
<Popconfirm
title="更新当前目录"
description="更新当前目录内容"
onConfirm={confirm}
onCancel={cancel}
okText="确认"
cancelText="取消"
>
<RedoOutlined/>
</Popconfirm>
<Popconfirm
title="关闭当前目录"
description="从列表中移除目录并不会删除本地文件"
onConfirm={confirm}
onCancel={cancel}
okText="确认"
cancelText="取消"
>
<DeleteOutlined/>
</Popconfirm>
</span>}
</span>
} }
/** /**
* 将文件信息改为树信息 * 将文件信息改为树信息
@ -117,7 +88,7 @@ const flushTree = (fileDirDate) => {
defaultValueStateSet.push({ defaultValueStateSet.push({
"key": filePath, "key": filePath,
// //
"title": titleExtended(fileName.substring(fileName.lastIndexOf("/")+1), dirFlag,filePath), "title": titleExtended(getFileNameByPath(fileName), dirFlag, filePath),
"icon": <FolderOutlined/>, "icon": <FolderOutlined/>,
"dirFlag": dirFlag, "dirFlag": dirFlag,
"children": childListM "children": childListM
@ -171,30 +142,38 @@ const ItemTree = (prop) => {
setSearchValue(value); setSearchValue(value);
setAutoExpandParent(true); 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) => { const onSelect = (selectedKeys, e) => {
if (e.selected) { if (e.selected) {
console.log('onSelect.selectedKeys', selectedKeys, e) console.log('onSelect.selectedKeys', selectedKeys, e)
if (e.node.dirFlag) { if (e.node.dirFlag) {
// //
if (!Array.isArray(e.node.children) || e.node.children.length === 0) { if (!Array.isArray(e.node.children) || e.node.children.length === 0) {
readDir(e.node.key).then(fileStateList => { refreshDir(e.node.key)
if (Array.isArray(fileStateList[0].childList) && fileStateList[0].childList.length > 0) { } else {
dispatch(nextDirAdd({selectDirKey: e.node.key, fileStateList})) //
// dispatch(addExpandedKeys([e.node.key]));
addChildNode(defaultValueState, flushTree(fileStateList)) setAutoExpandParent(false);
const result = [...defaultValueState]
console.log("[...defaultValueState]:", result)
setDefaultValueState(result)
}
})
} }
//
dispatch(addExpandedKeys([e.node.key]));
setAutoExpandParent(false);
} else { } else {
// //
dispatch(addTableBarItem({ dispatch(addTableBarItem({
label: e.node.title.props.children[0], label: e.node.title.props.children,
children: e.node.key, children: e.node.key,
key: e.node.key, key: e.node.key,
activeKey: e.node.key activeKey: e.node.key
@ -232,35 +211,59 @@ const ItemTree = (prop) => {
// }); // });
// return loop(defaultData); // return loop(defaultData);
// }, [searchValue]); // }, [searchValue]);
const treeNodeOnRightClick=(e,node)=> { const treeNodeOnRightClick = (e) => {
console.log("e,node",node) console.log("e,node", e)
setState({ setState({
rightClickNodeTreeItem: { rightClickNodeTreeItem: {
pageX: e.event.pageX, pageX: e.event.pageX,
pageY: e.event.pageY, pageY: e.event.pageY,
id: e.node.key key: e.node.key,
dirFlag: e.node.dirFlag,
title: getFileNameByPath(e.node.key),
} }
}); });
} }
const getNodeTreeRightClickMenu=()=> { const getNodeTreeRightClickMenu = () => {
console.log("state",state,isEmpty(state)) console.log("state", state, isEmpty(state))
if (isEmpty(state)){ if (isEmpty(state)) {
return return
} }
const {pageX, pageY} = {...state.rightClickNodeTreeItem}; const {pageX, pageY, title, dirFlag, key} = {...state.rightClickNodeTreeItem};
const tmpStyle = { const tmpStyle = {
position: 'absolute', position: 'absolute',
left: `${pageX}px`, left: `${pageX}px`,
top: `${pageY}px`, top: `${pageY}px`,
popupBg:"#ec0f0f", popupBg: "#ec0f0f",
"z-index":"9999" zIndex: "9999"
}; };
const menuItem = [
!dirFlag &&
<Menu.Item key='1'>
<UpdateFileName fileName={title} filePath={key} closeMenu={() => setState("")}/>
</Menu.Item>,
dirFlag &&
<Menu.Item key='2'>
<RefreshDir filePath={key} refreshDir={refreshDir} closeMenu={() => setState("")}/>
</Menu.Item>,
dirFlag &&
<Menu.Item key='3'>
<DirAddFile filePath={key} closeMenu={() => setState("")}/>
</Menu.Item>,
!dirFlag &&
<Menu.Item key='4'>
<DirDeleteFile filePath={key} closeMenu={() => setState("")}></DirDeleteFile>
</Menu.Item>,
dirFlag &&
<Menu.Item key='5'>
<CloseDir filePath={key} closeMenu={() => setState("")}/>
</Menu.Item>,
<Menu.Item key='6' onClick={() => {
setState("")
}}>关闭菜单</Menu.Item>
]
return ( return (
<Menu style={tmpStyle}> <Menu style={tmpStyle}>
<Menu.Item key='1'><Icon type='plus-circle'/>{'修改文件名'}</Menu.Item> {menuItem}
<Menu.Item key='2'><Icon type='plus-circle-o'/>{'添加文件'}</Menu.Item>
<Menu.Item key='4'><Icon type='edit'/>{'修改'}</Menu.Item>
<Menu.Item key='3'><Icon type='minus-circle-o'/>{'删除目录'}</Menu.Item>
</Menu> </Menu>
) )
} }

View File

@ -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) => { nextDirAdd: (state, action) => {
console.log("dirMessage:nextDirAdd", state, action) console.log("dirMessage:nextDirAdd", state, action)
// 获取当前选中的key // 获取当前选中的key
@ -59,10 +74,99 @@ export const dirMessageSlice = createSlice({
findChild(file.childList, action, selectDirKey) 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) { function findChild(fileList, action, selectDirKey) {
fileList.forEach(file => { fileList.forEach(file => {
if (file.filePath === selectDirKey && file.dirFlag && 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 { export const {
dirAdd, dirAdd,
nextDirAdd nextDirAdd,
dirRemove,
updateFileName,
dirFileAdd,
dirFileRemove,
} = dirMessageSlice.actions } = dirMessageSlice.actions
export default dirMessageSlice.reducer export default dirMessageSlice.reducer

View File

@ -40,6 +40,22 @@ export const tableBarItemSlice = createSlice({
state.activeKey=action.payload.activeKey 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)=>{ updatedSavedFile:(state, action)=>{
console.log("tableBarItemSlice:updatedSavedFile",action.payload) console.log("tableBarItemSlice:updatedSavedFile",action.payload)
// 如果其中包含了相同的key则是文件覆盖。 // 如果其中包含了相同的key则是文件覆盖。
@ -75,6 +91,7 @@ export const { addTableBarItem,
updatedSavedFile, updatedSavedFile,
setExpandedKeys, setExpandedKeys,
removeExpandedKeys, removeExpandedKeys,
addExpandedKeys addExpandedKeys,
updateFileName
} = tableBarItemSlice.actions } = tableBarItemSlice.actions
export default tableBarItemSlice.reducer export default tableBarItemSlice.reducer

View File

@ -35,6 +35,28 @@ export async function overWriteFile(filePath,jsonText) {
await fs.writeFile(filePath,jsonText,{"encoding":"utf-8"}) 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(){ export async function saveFileWithName(){
return ipcRenderer.invoke("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
}