2025-04-02 12:52:56 +00:00
|
|
|
import { useDraggable, useDroppable } from "@dnd-kit/core";
|
2025-06-23 08:13:43 +00:00
|
|
|
import classNames from "classnames";
|
|
|
|
|
import { useEffect } from "react";
|
|
|
|
|
import { useShallow } from "zustand/react/shallow";
|
2025-01-21 09:04:17 +00:00
|
|
|
|
2025-03-29 08:53:41 +00:00
|
|
|
import { FFS_DRAG_FILE, FFS_DRAG_FOLDER } from "src/assets/constants";
|
2025-04-02 12:52:56 +00:00
|
|
|
import { useExplorer } from "src/hooks/useExplorer";
|
2025-06-23 08:13:43 +00:00
|
|
|
import { ExplorerStore } from "src/store";
|
2025-05-02 14:47:35 +00:00
|
|
|
import { pluralize } from "src/utils";
|
2025-01-12 08:28:25 +00:00
|
|
|
|
2025-06-23 08:13:43 +00:00
|
|
|
import FolderContent, { FolderProps } from "./Content";
|
|
|
|
|
import FolderExpandIcon from "./ExpandIcon";
|
|
|
|
|
|
|
|
|
|
|
2025-03-11 10:34:28 +00:00
|
|
|
type Props = FolderProps & {
|
2025-03-24 07:45:36 +00:00
|
|
|
onOpenFilesPane: () => void;
|
2025-03-11 10:34:28 +00:00
|
|
|
disableDrag?: boolean;
|
2025-04-09 03:44:49 +00:00
|
|
|
hideExpandIcon?: boolean;
|
2025-04-12 09:44:48 +00:00
|
|
|
disableHoverIndent?: boolean;
|
2025-03-11 10:34:28 +00:00
|
|
|
};
|
2025-01-13 09:42:27 +00:00
|
|
|
const Folder = ({
|
2025-01-21 09:04:17 +00:00
|
|
|
folder,
|
2025-03-24 07:45:36 +00:00
|
|
|
onOpenFilesPane,
|
2025-03-08 08:55:53 +00:00
|
|
|
hideExpandIcon = false,
|
2025-03-11 10:34:28 +00:00
|
|
|
disableDrag = false,
|
2025-04-12 09:44:48 +00:00
|
|
|
disableHoverIndent = false,
|
2025-03-11 10:34:28 +00:00
|
|
|
}: Props) => {
|
2025-05-02 14:30:20 +00:00
|
|
|
const { useExplorerStore, plugin } = useExplorer();
|
2025-05-03 09:23:32 +00:00
|
|
|
const { language } = plugin;
|
2025-03-11 07:50:28 +00:00
|
|
|
|
2025-01-21 09:04:17 +00:00
|
|
|
const {
|
|
|
|
|
setFocusedFolder,
|
2025-03-29 08:53:41 +00:00
|
|
|
expandFolder,
|
|
|
|
|
collapseFolder,
|
2025-01-21 11:30:45 +00:00
|
|
|
expandedFolderPaths,
|
2025-04-09 03:44:49 +00:00
|
|
|
focusedFolder,
|
|
|
|
|
hasFolderChildren,
|
2025-05-02 14:30:20 +00:00
|
|
|
getFilesinFolder,
|
|
|
|
|
getFoldersByParent,
|
2025-05-24 08:25:43 +00:00
|
|
|
changeViewMode,
|
2025-04-02 11:58:54 +00:00
|
|
|
} = useExplorerStore(
|
|
|
|
|
useShallow((store: ExplorerStore) => ({
|
2025-03-05 06:52:03 +00:00
|
|
|
setFocusedFolder: store.setFocusedFolder,
|
2025-03-11 06:37:12 +00:00
|
|
|
hasFolderChildren: store.hasFolderChildren,
|
2025-01-21 11:30:45 +00:00
|
|
|
expandedFolderPaths: store.expandedFolderPaths,
|
2025-03-29 08:53:41 +00:00
|
|
|
expandFolder: store.expandFolder,
|
|
|
|
|
collapseFolder: store.collapseFolder,
|
2025-04-09 03:44:49 +00:00
|
|
|
focusedFolder: store.focusedFolder,
|
2025-05-02 14:30:20 +00:00
|
|
|
getFilesinFolder: store.getFilesInFolder,
|
|
|
|
|
getFoldersByParent: store.getFoldersByParent,
|
2025-05-24 08:25:43 +00:00
|
|
|
changeViewMode: store.changeViewMode,
|
2025-01-21 09:04:17 +00:00
|
|
|
}))
|
|
|
|
|
);
|
|
|
|
|
|
2025-05-02 14:30:20 +00:00
|
|
|
const isFocused = folder.path == focusedFolder?.path;
|
2025-04-11 07:48:33 +00:00
|
|
|
const isRoot = folder.isRoot();
|
|
|
|
|
|
2025-03-29 08:53:41 +00:00
|
|
|
const {
|
|
|
|
|
setNodeRef: setDragRef,
|
|
|
|
|
attributes,
|
|
|
|
|
listeners,
|
|
|
|
|
isDragging,
|
|
|
|
|
} = useDraggable({
|
|
|
|
|
id: folder.path,
|
|
|
|
|
data: { type: FFS_DRAG_FOLDER, item: folder },
|
2025-04-12 09:39:05 +00:00
|
|
|
disabled: disableDrag,
|
2025-03-29 08:53:41 +00:00
|
|
|
});
|
2025-03-11 06:37:12 +00:00
|
|
|
|
2025-03-29 08:53:41 +00:00
|
|
|
const { setNodeRef: setDropRef, isOver } = useDroppable({
|
|
|
|
|
id: `drop-${folder.path}`,
|
|
|
|
|
data: {
|
|
|
|
|
accepts: [FFS_DRAG_FILE, FFS_DRAG_FOLDER],
|
|
|
|
|
item: folder,
|
|
|
|
|
},
|
|
|
|
|
disabled: disableDrag,
|
|
|
|
|
});
|
2025-03-11 06:37:12 +00:00
|
|
|
|
|
|
|
|
useEffect(() => {
|
2025-03-29 08:53:41 +00:00
|
|
|
if (!isOver) return;
|
|
|
|
|
const timer = setTimeout(() => {
|
|
|
|
|
if (!expandedFolderPaths.includes(folder.path)) {
|
|
|
|
|
expandFolder(folder);
|
|
|
|
|
}
|
|
|
|
|
}, 800);
|
|
|
|
|
return () => clearTimeout(timer);
|
|
|
|
|
}, [isOver]);
|
2025-03-11 06:37:12 +00:00
|
|
|
|
2025-03-24 07:45:36 +00:00
|
|
|
useEffect(() => {
|
|
|
|
|
window.addEventListener("dblclick", onOpenFilesPane);
|
|
|
|
|
return () => {
|
|
|
|
|
window.removeEventListener("dblclick", onOpenFilesPane);
|
|
|
|
|
};
|
|
|
|
|
}, [onOpenFilesPane]);
|
|
|
|
|
|
2025-03-29 08:53:41 +00:00
|
|
|
const onToggleExpandState = (): void => {
|
|
|
|
|
const isFolderExpanded = expandedFolderPaths.includes(folder.path);
|
|
|
|
|
if (isFolderExpanded) {
|
|
|
|
|
collapseFolder(folder);
|
|
|
|
|
} else {
|
|
|
|
|
expandFolder(folder);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2025-04-09 03:44:49 +00:00
|
|
|
const maybeRenderExpandIcon = () => {
|
2025-04-11 07:48:33 +00:00
|
|
|
if (isRoot || hideExpandIcon) return null;
|
2025-04-12 09:22:37 +00:00
|
|
|
return <FolderExpandIcon folder={folder} />;
|
2025-04-09 03:44:49 +00:00
|
|
|
};
|
|
|
|
|
|
2025-05-02 14:30:20 +00:00
|
|
|
const getAriaLabel = () => {
|
2025-05-02 14:47:35 +00:00
|
|
|
const { name } = folder;
|
2025-05-02 14:30:20 +00:00
|
|
|
const filesCount = getFilesinFolder(folder).length;
|
|
|
|
|
const foldersCount = getFoldersByParent(folder).length;
|
|
|
|
|
|
2025-05-02 14:47:35 +00:00
|
|
|
const filesCountInfo = pluralize(filesCount, "file");
|
|
|
|
|
const foldersCountInfo = pluralize(foldersCount, "folder");
|
2025-05-02 14:30:20 +00:00
|
|
|
|
2025-05-03 09:23:32 +00:00
|
|
|
if (language === "zh") {
|
|
|
|
|
return `${name}\n${filesCount} 条笔记,${foldersCount} 个文件夹`;
|
2025-05-02 14:47:35 +00:00
|
|
|
}
|
2025-05-03 09:23:32 +00:00
|
|
|
return `${name}\n${filesCountInfo}, ${foldersCountInfo}`;
|
2025-05-02 14:30:20 +00:00
|
|
|
};
|
|
|
|
|
|
2025-05-02 14:33:21 +00:00
|
|
|
const getIndentStyle = () => {
|
|
|
|
|
if (disableHoverIndent) return undefined;
|
|
|
|
|
const folderLevel = isRoot ? 0 : folder.path.split("/").length - 1;
|
|
|
|
|
return {
|
|
|
|
|
marginInlineStart: -17 * folderLevel,
|
|
|
|
|
paddingInlineStart: 24 + 17 * folderLevel,
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
2025-05-03 09:23:32 +00:00
|
|
|
const getClassNames = () => {
|
|
|
|
|
return classNames(
|
|
|
|
|
"ffs__folder-container tree-item-self nav-folder-title is-clickable",
|
|
|
|
|
{
|
|
|
|
|
"mod-collapsible": hasFolderChildren(folder),
|
|
|
|
|
"is-active": isFocused,
|
|
|
|
|
"ffs__is-over": isOver && !disableDrag,
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
2025-01-12 08:28:25 +00:00
|
|
|
return (
|
2025-04-09 03:44:49 +00:00
|
|
|
<div
|
2025-05-03 09:23:32 +00:00
|
|
|
className={getClassNames()}
|
2025-04-09 03:44:49 +00:00
|
|
|
ref={setDropRef}
|
2025-05-24 08:25:43 +00:00
|
|
|
onClick={async () => {
|
2025-05-22 10:39:44 +00:00
|
|
|
onToggleExpandState();
|
|
|
|
|
setFocusedFolder(folder);
|
2025-05-24 08:25:43 +00:00
|
|
|
await changeViewMode("folder");
|
2025-05-22 10:39:44 +00:00
|
|
|
}}
|
2025-05-02 14:33:21 +00:00
|
|
|
style={getIndentStyle()}
|
2025-05-02 14:30:20 +00:00
|
|
|
data-tooltip-position="right"
|
|
|
|
|
aria-label={getAriaLabel()}
|
2025-04-09 03:44:49 +00:00
|
|
|
>
|
|
|
|
|
{maybeRenderExpandIcon()}
|
2025-04-07 07:54:14 +00:00
|
|
|
<div
|
2025-04-09 03:44:49 +00:00
|
|
|
className="ffs__draggable-container tree-item-inner nav-folder-title-content"
|
2025-04-12 09:36:54 +00:00
|
|
|
style={{ opacity: isDragging ? 0.5 : 1 }}
|
2025-03-29 08:53:41 +00:00
|
|
|
ref={setDragRef}
|
|
|
|
|
{...attributes}
|
|
|
|
|
{...listeners}
|
|
|
|
|
>
|
|
|
|
|
<FolderContent
|
|
|
|
|
folder={folder}
|
|
|
|
|
onToggleExpandState={onToggleExpandState}
|
|
|
|
|
/>
|
2025-04-07 07:54:14 +00:00
|
|
|
</div>
|
|
|
|
|
</div>
|
2025-01-12 08:28:25 +00:00
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export default Folder;
|