From 844ae2aabb9b1cfa3d38460945a91a259a24d0ed Mon Sep 17 00:00:00 2001 From: halodong Date: Sun, 13 Aug 2023 00:18:15 +0800 Subject: [PATCH] feat: delete file --- .../src/components/Explorer/index.tsx | 5 +- .../src/components/Explorer/styles.ts | 1 + .../src/components/FileTree/FileNode.tsx | 13 +++- .../FileTree/FileNodeContextMenu.tsx | 7 ++ .../src/components/FileTree/FileTree.tsx | 42 +++++++++--- .../src/components/FileTree/styles.ts | 23 ++++++- apps/linebyline/src/stores/useEditorStore.ts | 65 ++++++++++++++++--- src-tauri/src/cmd.rs | 16 ++++- src-tauri/src/fc.rs | 20 +++--- src-tauri/src/main.rs | 2 + 10 files changed, 157 insertions(+), 37 deletions(-) diff --git a/apps/linebyline/src/components/Explorer/index.tsx b/apps/linebyline/src/components/Explorer/index.tsx index 83e90498..376442e7 100644 --- a/apps/linebyline/src/components/Explorer/index.tsx +++ b/apps/linebyline/src/components/Explorer/index.tsx @@ -10,7 +10,6 @@ import { useGlobalCacheData, useOpen } from '@/hooks' import { CacheManager } from '@/helper' import { Empty, FileTree, List, Popper } from '@/components' import styled from 'styled-components' -import SideBarHeader from '../SideBar/SideBarHeader' const RecentListBottom = styled.div` padding: 8px; @@ -63,8 +62,8 @@ const Explorer: FC = (props) => { const containerCLs = classNames(props.className) return ( - - + e.preventDefault()}> + {/* */}
{folderData && folderData.length > 0 ? ( = ({ item, level = 0, activeId, onSelect, open = false }) => { const [isOpen, setIsOpen] = useState(open) const newInputRef = useRef(null) const { contextMenuNode, setContextMenuNode } = useFileTreeContextMenuNode() + const { deleteFile } = useEditorStore() const isActived = activeId === item.id const isFolder = item.kind === 'dir' @@ -25,12 +27,19 @@ const FileNode: FC = ({ item, level = 0, activeId, onSelect, open newInputRef.current?.show({ fileNode: contextMenuNode }) } + const delFileHandler = () => { + if (!contextMenuNode || contextMenuNode.id !== item.id) return + deleteFile(contextMenuNode) + } + bus.on('SIDEBAR:show-new-input', newFileHandler) + bus.on('SIDEBAR:delete-file', delFileHandler) return () => { bus.detach('SIDEBAR:show-new-input', newFileHandler) + bus.detach('SIDEBAR:delete-file', delFileHandler) } - }, [contextMenuNode, item]) + }, [contextMenuNode, item, deleteFile]) const handleClick: MouseEventHandler = useCallback( (e) => { @@ -47,8 +56,8 @@ const FileNode: FC = ({ item, level = 0, activeId, onSelect, open const handleContextMenu = useCallback( (e: React.MouseEvent, fileNode?: IFile) => { - e.stopPropagation() const node = fileNode || item + e.stopPropagation() setContextMenuNode(node) }, [item, setContextMenuNode], diff --git a/apps/linebyline/src/components/FileTree/FileNodeContextMenu.tsx b/apps/linebyline/src/components/FileTree/FileNodeContextMenu.tsx index 947fb25c..07038af2 100644 --- a/apps/linebyline/src/components/FileTree/FileNodeContextMenu.tsx +++ b/apps/linebyline/src/components/FileTree/FileNodeContextMenu.tsx @@ -28,6 +28,13 @@ export const FileNodeContextMenu = memo((props: ContextMenuProps) => { bus.emit('SIDEBAR:show-new-input') }, }, + { + key: 'delete_file', + label: 'Delete File', + handler: () => { + bus.emit("SIDEBAR:delete-file") + } + } ] return ( diff --git a/apps/linebyline/src/components/FileTree/FileTree.tsx b/apps/linebyline/src/components/FileTree/FileTree.tsx index 6067d165..7f64b861 100644 --- a/apps/linebyline/src/components/FileTree/FileTree.tsx +++ b/apps/linebyline/src/components/FileTree/FileTree.tsx @@ -1,16 +1,17 @@ import classNames from 'classnames' import type { FC } from 'react' -import { memo, useCallback } from 'react' +import { memo, useCallback, useState } from 'react' import FileNode from './FileNode' import type { IFile } from '@/helper/filesys' import { FileNodeContextMenu } from './FileNodeContextMenu' import useFileTreeContextMenu from '@/hooks/useContextMenu' +import { RootFolderTab } from './styles' const FileTree: FC = (props) => { const { data, activeId, onSelect, className } = props const { setOpen, points, setPoints } = useFileTreeContextMenu() - - const containerCls = classNames('w-full overflow-hidden', className) + const rootFolderNode = data?.[0]?.kind === 'dir' ? data[0] : null + const [folderVisible, setFolderVisible] = useState(true) const handleContextMenu = useCallback( (e: React.MouseEvent) => { @@ -24,12 +25,37 @@ const FileTree: FC = (props) => { [setOpen, setPoints], ) + const containerCls = classNames('w-full overflow-hidden', className) + + const rootFolderTabIconCls = classNames('arrow-icon', { + 'arrow-icon__down': folderVisible, + }) + return ( -
- {/* Currently, folderData only has root file, so open it. */} - {data?.map((item) => ( - - ))} +
+ {rootFolderNode ? ( + setFolderVisible((prev) => !prev)}> +
+ +
+ {rootFolderNode.name} +
+ ) : null} + +
+ {folderVisible + ? (rootFolderNode?.children || data)?.map((item) => ( + + )) + : null} +
+
) diff --git a/apps/linebyline/src/components/FileTree/styles.ts b/apps/linebyline/src/components/FileTree/styles.ts index 67ba4213..06743822 100644 --- a/apps/linebyline/src/components/FileTree/styles.ts +++ b/apps/linebyline/src/components/FileTree/styles.ts @@ -32,9 +32,28 @@ export const FileNodeStyled = styled.div` flex-shrink: 0; } - .newfile-input { margin: 0 8px; - border: 1px solid ${props => props.theme.accentColor}; + border: 1px solid ${(props) => props.theme.accentColor}; + } +` + +export const RootFolderTab = styled.div` + padding: 4px 2px; + display: flex; + align-items: center; + cursor: pointer; + font-size: 1rem; + color: ${(props) => props.theme.tipsFontColor}; + + .arrow-icon { + display: inline-block; + font-size: 1.4rem; + color: ${(props) => props.theme.primaryFontColor}; + transition: all 0.3s; + + &__down { + transform: rotate(90deg); + } } ` diff --git a/apps/linebyline/src/stores/useEditorStore.ts b/apps/linebyline/src/stores/useEditorStore.ts index 79e8e9a9..ad25b39e 100644 --- a/apps/linebyline/src/stores/useEditorStore.ts +++ b/apps/linebyline/src/stores/useEditorStore.ts @@ -22,8 +22,14 @@ const findParentNode = (fileNode: IFile, rootFile: IFile) => { return dfs(rootFile) } -const hasSameFile = (fileNodeList: IFile[], target: { name: string; kind: IFile['kind'] }) => { - return !!fileNodeList.find((file) => file.name === target.name && file.kind === target.kind) +type BaseIFile = { name: string; kind: IFile['kind'] } + +function sameFile(current: BaseIFile, target: BaseIFile): boolean { + return current.name === target.name && current.kind === target.kind +} + +const hasSameFile = (fileNodeList: IFile[], target: BaseIFile) => { + return !!fileNodeList.find((file) => sameFile(file, target)) } const useEditorStore = create((set, get) => { @@ -52,14 +58,51 @@ const useEditorStore = create((set, get) => { parent.children!.push(targetFile) addOpenedFile(targetFile.id) - set((state) => { - return { - ...state, - activeId: targetFile.id, - folderData: [...(state.folderData || [])], + invoke('write_file', { filePath: targetFile.path, content: targetFile.content }).then( + () => { + set((state) => { + return { + ...state, + activeId: targetFile.id, + folderData: [...(state.folderData || [])], + } + }) + }, + ) + } + }, + + deleteFile: (fileNode) => { + const { folderData, activeId, delOpenedFile, opened } = get() + const parent = findParentNode(fileNode, folderData![0]) + let targetFile: IFile | undefined + + if (parent?.children) { + const newChildren: IFile[] = [] + for (let i = 0; i < parent.children.length; i++) { + const child = parent.children[i] + if (sameFile(child, fileNode)) { + targetFile = child + } else { + newChildren.push(child) } + } + if (!targetFile) return false + + parent.children = newChildren + + invoke(targetFile.kind === 'dir' ? 'delete_folder' : 'delete_file', { + filePath: targetFile.path, + }).then(() => { + delOpenedFile(targetFile!.id) + set((state) => { + return { + ...state, + activeId: activeId === targetFile!.id ? opened[opened.length - 1] : activeId, + folderData: [...(state.folderData || [])], + } + }) }) - invoke('write_file', { filePath: targetFile.path, content: targetFile.content }) } }, @@ -119,13 +162,17 @@ const useEditorStore = create((set, get) => { } }) -interface EditorStore { +type EditorStore = { opened: string[] activeId?: string + /** + * folderData only has root file. + */ folderData: null | IFile[] editorCtxMap: Map> setActiveId: (id: string) => void addFile: (file: IFile, target: { name: string; kind: IFile['kind'] }) => boolean | void + deleteFile: (file: IFile) => void addOpenedFile: (id: string) => void delOpenedFile: (id: string) => void setFolderData: (folderData: IFile[]) => void diff --git a/src-tauri/src/cmd.rs b/src-tauri/src/cmd.rs index 9b5859a7..cfa43ae9 100644 --- a/src-tauri/src/cmd.rs +++ b/src-tauri/src/cmd.rs @@ -1,6 +1,4 @@ -use crate::{ - fc, -}; +use crate::fc; // Learn more about Tauri commands at https://tauri.app/v1/guides/features/command #[tauri::command] @@ -20,3 +18,15 @@ pub fn write_file(file_path: &str, content: &str) -> String { fc::write_file(file_path, content); String::from("OK") } + +#[tauri::command] +pub fn delete_file(file_path: &str) -> String { + fc::remove_file(file_path); + String::from("OK") +} + +#[tauri::command] +pub fn delete_folder(file_path: &str) -> String { + fc::remove_folder(file_path); + String::from("OK") +} diff --git a/src-tauri/src/fc.rs b/src-tauri/src/fc.rs index c70f9cd0..aa0f0153 100644 --- a/src-tauri/src/fc.rs +++ b/src-tauri/src/fc.rs @@ -114,14 +114,14 @@ pub fn create_file>(filename: P) -> AnyResult<()> { // Ok(()) // } -// pub fn remove_file(path: &str) -> SerdeResult<()> { -// let file_path = Path::new(path); -// fs::remove_file(file_path); -// Ok(()) -// } +pub fn remove_file(path: &str) -> AnyResult<()> { + let file_path = Path::new(path); + fs::remove_file(file_path)?; + Ok(()) +} -// pub fn remove_folder(path: &str) -> SerdeResult<()> { -// let folder_path = Path::new(path); -// fs::remove_dir_all(folder_path); -// Ok(()) -// } +pub fn remove_folder(path: &str) -> AnyResult<()> { + let folder_path = Path::new(path); + fs::remove_dir_all(folder_path)?; + Ok(()) +} diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index eb0a9518..43d8f9f7 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -17,6 +17,8 @@ fn main() { cmd::open_folder, cmd::get_file_content, cmd::write_file, + cmd::delete_file, + cmd::delete_folder, conf::cmd::get_app_conf_path, conf::cmd::get_app_conf, conf::cmd::reset_app_conf,