From 8623c4af81dc976999902171e2acb3d5f674d3e7 Mon Sep 17 00:00:00 2001 From: NriotHrreion Date: Tue, 6 Aug 2024 18:20:47 +0800 Subject: [PATCH] fix: Star list doesn't update when starring folder through context menu --- components/explorer/sidebar.tsx | 32 ++++++--------- components/explorer/starred-item.tsx | 60 ++++++++++++++++++++++++++++ hooks/useEmitter.ts | 26 ++++++++++++ hooks/useFolder.ts | 3 ++ hooks/useForceUpdate.ts | 7 ++++ lib/emitter.ts | 3 ++ 6 files changed, 111 insertions(+), 20 deletions(-) create mode 100644 components/explorer/starred-item.tsx create mode 100644 hooks/useEmitter.ts create mode 100644 hooks/useForceUpdate.ts create mode 100644 lib/emitter.ts diff --git a/components/explorer/sidebar.tsx b/components/explorer/sidebar.tsx index ab68802..98a1299 100644 --- a/components/explorer/sidebar.tsx +++ b/components/explorer/sidebar.tsx @@ -1,5 +1,4 @@ "use-client"; -import path from "path"; import React, { useEffect, useState } from "react"; import { Card } from "@nextui-org/card"; @@ -7,12 +6,14 @@ import { Button } from "@nextui-org/button"; import { Tooltip } from "@nextui-org/tooltip"; import { FilePlus2, FileUp, FolderPlus, Star } from "lucide-react"; -import { getFolderIcon } from "./explorer-item"; import SidebarWidget from "./sidebar-widget"; +import StarredItem from "./starred-item"; -import { parseStringPath, useExplorer } from "@/hooks/useExplorer"; +import { useExplorer } from "@/hooks/useExplorer"; import { useDialog } from "@/hooks/useDialog"; import { useFolder } from "@/hooks/useFolder"; +import { useEmitter } from "@/hooks/useEmitter"; +import { useForceUpdate } from "@/hooks/useForceUpdate"; import { storage } from "@/lib/storage"; import { starListStorageKey } from "@/lib/global"; @@ -22,6 +23,7 @@ const Sidebar: React.FC = () => { const folder = useFolder(explorer.stringifyPath()); const [starred, setStarred] = useState(folder.getIsStarred()); + const forceUpdate = useForceUpdate(); const handleCreateFolder = () => { dialog.open("createFolder", { path: explorer.stringifyPath() }); @@ -44,6 +46,10 @@ const Sidebar: React.FC = () => { setStarred(folder.getIsStarred()); }); + useEmitter([ + ["star-list-change", () => forceUpdate()] + ]); + return (
@@ -82,23 +88,9 @@ const Sidebar: React.FC = () => {
{ - (JSON.parse(storage.getItem(starListStorageKey, JSON.stringify([]))) as string[]).map((item, index) => { - const itemName = path.basename(item); - - return ( -
- - - -
- ); - }) + (JSON.parse(storage.getItem(starListStorageKey, JSON.stringify([]))) as string[]).map((item, index) => ( + + )) }
diff --git a/components/explorer/starred-item.tsx b/components/explorer/starred-item.tsx new file mode 100644 index 0000000..281b28f --- /dev/null +++ b/components/explorer/starred-item.tsx @@ -0,0 +1,60 @@ +"use client"; + +import path from "path"; + +import React from "react"; +import { Button } from "@nextui-org/button"; +import { Tooltip } from "@nextui-org/tooltip"; +import { ContextMenuItem, useContextMenu } from "use-context-menu"; + +import { getFolderIcon } from "./explorer-item"; + +import { parseStringPath, useExplorer } from "@/hooks/useExplorer"; +import { useFolder } from "@/hooks/useFolder"; + +interface StarredItemProps { + itemPath: string +} + +const StarredItem: React.FC = ({ itemPath }) => { + const explorer = useExplorer(); + const itemName = path.basename(itemPath); + const folder = useFolder(itemPath.replace(explorer.disk, "")); + + const handleOpen = () => { + explorer.setPath(parseStringPath(itemPath)); + }; + + const handleUnstar = () => { + folder.toggleStar(); + }; + + const { contextMenu, onContextMenu } = useContextMenu( + <> + handleOpen()}>打开 + handleUnstar()}>取消星标 + + ); + + return ( +
+ + + + + {contextMenu} +
+ ); +} + +export default StarredItem; diff --git a/hooks/useEmitter.ts b/hooks/useEmitter.ts new file mode 100644 index 0000000..560b558 --- /dev/null +++ b/hooks/useEmitter.ts @@ -0,0 +1,26 @@ +import { useEffect } from "react"; + +import { emitter } from "@/lib/emitter"; + +type EmitterInstance = [string, (...args: any[]) => any]; + +/** + * Create an event listener with `EventEmitter` + * + * @example + * ```ts + * useEmitter([ + * ["foo", () => console.log("bar")] + * ]); + * + * // in somewhere... + * new Emitter().emit("foo"); // bar + * ``` + */ +export function useEmitter(instances: EmitterInstance[]) { + useEffect(() => { + instances.forEach((instance: EmitterInstance) => { + emitter.on(instance[0], (...args: any[]) => instance[1](...args)); + }); + }, []); +} diff --git a/hooks/useFolder.ts b/hooks/useFolder.ts index e69d7b2..d7470ea 100644 --- a/hooks/useFolder.ts +++ b/hooks/useFolder.ts @@ -8,6 +8,7 @@ import { useExplorer } from "./useExplorer"; import { storage } from "@/lib/storage"; import { starListStorageKey } from "@/lib/global"; +import { emitter } from "@/lib/emitter"; interface FolderOperations extends DirectoryItemOperations { create: (name: string, type: "folder" | "file") => Promise @@ -171,6 +172,8 @@ export function useFolder(fullPath: string): FolderOperations { } else { storage.setItem(starListStorageKey, JSON.stringify([...starList, targetPath])); } + + emitter.emit("star-list-change"); }, getIsStarred: () => { if(typeof window === "undefined") return false; diff --git a/hooks/useForceUpdate.ts b/hooks/useForceUpdate.ts new file mode 100644 index 0000000..b9a7abd --- /dev/null +++ b/hooks/useForceUpdate.ts @@ -0,0 +1,7 @@ +import { useState } from "react"; + +export function useForceUpdate() { + const [, setCount] = useState(0); + + return () => setCount((prev) => prev + 1); +} diff --git a/lib/emitter.ts b/lib/emitter.ts new file mode 100644 index 0000000..679f37d --- /dev/null +++ b/lib/emitter.ts @@ -0,0 +1,3 @@ +import EventEmitter from "events"; + +export const emitter = new EventEmitter();