Skip to content

Commit

Permalink
Add support for move file/folder
Browse files Browse the repository at this point in the history
  • Loading branch information
thomasgauvin committed May 27, 2024
1 parent 75fcc5c commit 79c9635
Show file tree
Hide file tree
Showing 11 changed files with 653 additions and 337 deletions.
368 changes: 205 additions & 163 deletions package-lock.json

Large diffs are not rendered by default.

96 changes: 91 additions & 5 deletions src/components/FileSystemAdapters/FileSystem/FileSystemItem.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useState } from "react";
import { useRef, useState } from "react";
import DirectoryNode from "../../../models/DirectoryNode";
import { ChevronDown, ChevronRight, FilePlus, FolderPlus } from "lucide-react";
import RightClickMenu from "./RightClickMenu";
Expand All @@ -7,28 +7,108 @@ import * as ContextMenu from "@radix-ui/react-context-menu";
export function FileSystemItem({
node,
depth,
forceRerenderCounter,
setForceRerenderCounter,
setSelectedFile,
handleFileSelect,
currentlySelectedFile,
handleDeleteFile,
handleCreateFile,
handleCreateFolder,
handleRenameFolder,
handleRenameFile,
draggable
}: {
node: DirectoryNode;
depth: number;
forceRerenderCounter: number;
setForceRerenderCounter: (counter: number) => void;
setSelectedFile: (file: DirectoryNode | null) => void;
handleFileSelect: (file: DirectoryNode) => void;
currentlySelectedFile: DirectoryNode | undefined;
handleDeleteFile: (node: DirectoryNode) => void;
handleCreateFile: (node: DirectoryNode) => void;
handleCreateFolder: (node: DirectoryNode) => void;
handleRenameFolder: (node: DirectoryNode) => void;
handleRenameFile: (node: DirectoryNode) => void;
draggable: boolean
}) {
const [expanded, setExpanded] = useState(depth === 0);
const [isDragOver, setIsDragOver] = useState(false);
const dragEnterCount = useRef(0);

const handleOnDragStart = (e: React.DragEvent<HTMLDivElement>, node: DirectoryNode) => {
e.dataTransfer.setData("node_id", node.getId());
console.log(e.dataTransfer.getData("node_id"));
e.stopPropagation();
};

const handleOnDragDrop = (e: React.DragEvent<HTMLDivElement>, newParent: DirectoryNode) => {
console.log(e.dataTransfer.getData("node_id"));
console.log('dropping');
console.log(newParent);

const droppedNodeId = e.dataTransfer.getData("node_id");


//get root node
const rootNode = node.getRootNode();
//get dropped node by id from rootNode
const droppedNode = rootNode.getNodeById(droppedNodeId);

try{
droppedNode?.moveNodeToNewParent(newParent);
setForceRerenderCounter(forceRerenderCounter + 1);
console.log("forced rerender");
}
catch(e){
console.log(e);
alert("Could not move file due to conflicting file names in destination.")
}

setIsDragOver(false);
dragEnterCount.current = 0;
e.preventDefault();
e.stopPropagation();
};

const handleOnDragOver = (e: React.DragEvent<HTMLDivElement>) => {
e.preventDefault();
};

const handleOnDragEnter = (e: React.DragEvent<HTMLDivElement>) => {
setIsDragOver(true);

dragEnterCount.current += 1;

e.preventDefault();
e.stopPropagation();
};

const handleOnDragLeave = (e: React.DragEvent<HTMLDivElement>) => {

dragEnterCount.current -= 1;

if (dragEnterCount.current <= 0) {
setIsDragOver(false);
}

e.preventDefault();
e.stopPropagation();
};

return (
<div key={node.getName()} className="select-none">
<div
key={node.getName()}
className="select-none outline-zinc-400 rounded outline-dashed outline-0"
draggable={draggable}
onDragStart={draggable ? (e) => handleOnDragStart(e, node) : undefined}
onDrop={(e) => handleOnDragDrop(e, node)}
onDragOver={handleOnDragOver}
onDragEnter={handleOnDragEnter}
onDragLeave={handleOnDragLeave}
style={isDragOver? { outlineWidth: "1px" } : {}}
>
{depth === 0 ? null : (
<div
className="p-0.5 hover:bg-zinc-200/70 rounded flex items-center "
Expand Down Expand Up @@ -96,6 +176,8 @@ export function FileSystemItem({
: ""
}
`}
draggable={true}
onDragStart={(e) => handleOnDragStart(e, child)}
onClick={() => handleFileSelect(child)}
>
<div className="pl-1">
Expand All @@ -111,13 +193,17 @@ export function FileSystemItem({
<FileSystemItem
node={child}
depth={depth + 1}
forceRerenderCounter={forceRerenderCounter}
setForceRerenderCounter={setForceRerenderCounter}
setSelectedFile={setSelectedFile}
handleFileSelect={handleFileSelect}
currentlySelectedFile={currentlySelectedFile}
handleDeleteFile={handleDeleteFile}
handleCreateFile={handleCreateFile}
handleCreateFolder={handleCreateFolder}
handleRenameFolder={handleRenameFolder}
handleRenameFile={handleRenameFile}
draggable={true}
/> // Recursively render directory
)}
</ContextMenu.Trigger>
Expand All @@ -134,9 +220,9 @@ export function FileSystemItem({
}
onDelete={() => handleDeleteFile(child)}
onRename={
child.isDirectory() ?
() => handleRenameFolder(child) :
() => handleRenameFile(child)
child.isDirectory()
? () => handleRenameFolder(child)
: () => handleRenameFile(child)
}
/>
</ContextMenu.Root>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import DirectoryNode, { createDirectoryNode } from "../../../models/DirectoryNode";
import { Button } from "../../ui/Button";
import * as Separator from "@radix-ui/react-separator";

export function GettingStartedHelper({
selectedFile,
selectedDirectory,
setSelectedDirectory
}: {
selectedFile: DirectoryNode | null;
selectedDirectory: DirectoryNode | null;
setSelectedDirectory: (node: DirectoryNode | null) => void;
}) {

const handleDirectorySelect = async () => {
try {
//@ts-expect-error
const directoryHandle = await window.showDirectoryPicker();
if (!directoryHandle) return;

setSelectedDirectory(
await createDirectoryNode(directoryHandle, undefined)
);
} catch (error) {
console.error("Error selecting directory:", error);
}
};

return (
<div
className="z-20 flex fixed top-[50%] left-[50%]
transform translate-x-[-10%] translate-y-[-35%]
"
>
<div className="bg-white rounded shadow-md">
<div className="mx-8 mt-8 mb-4">
<div className="flex flex-col space-y-1.5 ">
<div className="text-center ">
<h1 className="font-semibold text-lg leading-none tracking-tight mb-0.5">
Paginary
</h1>
<p className="text-muted-foreground text-sm">
A local-first open-source note taking app.
</p>
</div>
<div className="my-2">
<Separator.Root className="bg-zinc-200 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full my-[15px]" />
</div>

<div className="text-center">
<div className="mb-2">
<Button variant={"outline"} className="rounded w-full">
Create a new note
</Button>
</div>

<div className="">
<Button
variant={"link"}
className="rounded w-full font-normal "
>
Select a location to save your notes
</Button>
</div>
<div className="">
<Button
variant={"link"}
className="rounded w-full font-normal"
onClick={handleDirectorySelect}
>
Open an existing folder
</Button>
</div>
</div>
</div>
</div>
</div>
</div>
);
}
30 changes: 25 additions & 5 deletions src/components/FileSystemAdapters/FileSystem/LocalFileSystem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@
// and pass them down to the Folder component. The Folder component is then responsible for rendering
// and calling them

import { useEffect } from "react";
import { useEffect, useReducer, useState } from "react";
import DirectoryNode, {
createDirectoryNode,
} from "../../../models/DirectoryNode";
import { FileSystemItem } from "./FileSystemItem";
import { FilePlus, FolderPlus, X } from "lucide-react";
import { FilePlus, FolderPlus, LucideFile, LucideFilePlus, LucideFolderPlus, X } from "lucide-react";
import { set } from "remirror";
import { Button } from "../../ui/Button";

//sample react function
export const LocalFileSystem = ({
Expand All @@ -27,6 +29,9 @@ export const LocalFileSystem = ({
setPanelIsOpen: (isOpen: boolean) => void;
panelIsOpen: boolean;
}) => {

const [forceRerenderCounter, setForceRerenderCounter] = useState(0);

useEffect(() => {
const fetchEntries = async () => {
if (selectedDirectory?.directoryHandle) {
Expand Down Expand Up @@ -80,6 +85,8 @@ export const LocalFileSystem = ({
} else {
setSelectedFile(null);
}

setForceRerenderCounter(forceRerenderCounter + 1);
};

const createFileHandlingDuplicates = async (parent: DirectoryNode) => {
Expand All @@ -98,6 +105,8 @@ export const LocalFileSystem = ({
}
}

setForceRerenderCounter(forceRerenderCounter + 1);

throw new Error("Could not create file");
};

Expand All @@ -112,6 +121,8 @@ export const LocalFileSystem = ({
"Error creating file, too many conflicts when trying to create the file."
);
}

setForceRerenderCounter(forceRerenderCounter + 1);
};

const createFolderHandlingDuplicates = async (parent: DirectoryNode) => {
Expand All @@ -129,6 +140,8 @@ export const LocalFileSystem = ({
}
}

setForceRerenderCounter(forceRerenderCounter + 1);

throw new Error("Could not create folder");
};

Expand All @@ -148,6 +161,7 @@ export const LocalFileSystem = ({
if (newName) {
await node.renameFolder(newName);
setSelectedFile(selectedFile?.getCopy() || null);
setForceRerenderCounter(forceRerenderCounter + 1);
}
};

Expand All @@ -159,6 +173,7 @@ export const LocalFileSystem = ({
if (newName) {
await node.renameFile(newName);
setSelectedFile(selectedFile?.getCopy() || null);
setForceRerenderCounter(forceRerenderCounter + 1);
}
};

Expand All @@ -183,9 +198,7 @@ export const LocalFileSystem = ({

return (
<div
className={`bg-zinc-50 overflow-y-scroll ${
hidden ? "hidden" : ""
}
className={`bg-zinc-50 overflow-y-scroll ${hidden ? "hidden" : ""}
w-full h-full scrollbar scrollbar-thumb-zinc-200 scrollbar-track-zinc-100 scrollbar-thin scrollbar-thumb-rounded-full scrollbar-track-rounded-full
`}
>
Expand Down Expand Up @@ -230,15 +243,22 @@ export const LocalFileSystem = ({
</div>
<div className="font-normal p-1">
<FileSystemItem
key={forceRerenderCounter} //react does a shallow compare on an object to determine if it needs to rerender. since we are updating the object directly when
//updating name/moving files, etc. and we don't want to create copies of the root selected directory unnecessarily (causes rerenders of file, remirror editor, etc.)
//we are incrementing a counter that we can use to force the rerender of this. by setting it as the key to the filesystemitem, we can force the update for this
node={selectedDirectory}
depth={0}
forceRerenderCounter={forceRerenderCounter}
setForceRerenderCounter={setForceRerenderCounter}
setSelectedFile={setSelectedFile}
handleFileSelect={handleFileSelect}
currentlySelectedFile={selectedFile}
handleDeleteFile={handleDeleteFile}
handleCreateFile={handleCreateFile}
handleCreateFolder={handleCreateFolder}
handleRenameFolder={handleRenameFolder}
handleRenameFile={handleRenameFile}
draggable={false}
/>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ const RightClickMenu = ({ onCreateFile, onCreateFolder, onDelete, onRename }:
{
onCreateFile && (
<ContextMenu.Item
className={`text-sm leading-none rounded-md
className={`text-sm leading-none rounded-md cursor-pointer
flex items-center h-[25px] px-[5px] relative select-none outline-none
data-[disabled]:pointer-events-none
data-[highlighted]:bg-zinc-100`}
Expand All @@ -35,7 +35,7 @@ const RightClickMenu = ({ onCreateFile, onCreateFolder, onDelete, onRename }:
{
onCreateFolder && (
<ContextMenu.Item
className={`text-sm leading-none rounded-md
className={`text-sm leading-none rounded-md cursor-pointer
flex items-center h-[25px] px-[5px] relative select-none outline-none
data-[disabled]:pointer-events-none
data-[highlighted]:bg-zinc-100`}
Expand All @@ -49,9 +49,10 @@ const RightClickMenu = ({ onCreateFile, onCreateFolder, onDelete, onRename }:
)
}
<ContextMenu.Item
className={`text-sm leading-none rounded-md
className={`text-sm leading-none rounded-md cursor-pointer
flex items-center h-[25px] px-[5px] relative select-none outline-none
data-[disabled]:pointer-events-none
cursor-pointer
data-[highlighted]:bg-zinc-100`}
onClick={() => {
onRename();
Expand All @@ -61,7 +62,7 @@ const RightClickMenu = ({ onCreateFile, onCreateFolder, onDelete, onRename }:
<div>Rename</div>
</ContextMenu.Item>
<ContextMenu.Item
className={`text-sm leading-none rounded-md
className={`text-sm leading-none rounded-md cursor-pointer
flex items-center h-[25px] px-[5px] relative select-none outline-none
data-[disabled]:pointer-events-none
data-[highlighted]:bg-zinc-100`}
Expand Down
Loading

0 comments on commit 79c9635

Please sign in to comment.