mirror of
https://github.com/hanzoai/dataroom.git
synced 2026-10-30 18:41:07 +00:00
322 lines
8.0 KiB
TypeScript
322 lines
8.0 KiB
TypeScript
import { useRouter } from "next/router";
|
|
|
|
import { memo, useMemo } from "react";
|
|
import type { CSSProperties } from "react";
|
|
|
|
import { DataroomFolder } from "@prisma/client";
|
|
import { HomeIcon } from "lucide-react";
|
|
|
|
import { cn } from "@/lib/utils";
|
|
import {
|
|
HIERARCHICAL_DISPLAY_STYLE,
|
|
getHierarchicalDisplayName,
|
|
} from "@/lib/utils/hierarchical-display";
|
|
|
|
import { FileTree } from "@/components/ui/nextra-filetree";
|
|
import { useViewerSurfaceTheme } from "@/components/view/viewer/viewer-surface-theme";
|
|
|
|
import { buildNestedFolderStructureWithDocs } from "./utils";
|
|
|
|
const ViewerDocumentFileItem = memo(
|
|
({
|
|
document,
|
|
dataroomIndexEnabled,
|
|
}: {
|
|
document: DataroomDocumentWithVersion;
|
|
dataroomIndexEnabled?: boolean;
|
|
}) => {
|
|
const documentDisplayName = getHierarchicalDisplayName(
|
|
document.name,
|
|
document.hierarchicalIndex,
|
|
dataroomIndexEnabled || false,
|
|
);
|
|
|
|
return (
|
|
<FileTree.File
|
|
name={documentDisplayName}
|
|
// onToggle={() => router.push(`/documents/${document.id}`)}
|
|
/>
|
|
);
|
|
},
|
|
);
|
|
ViewerDocumentFileItem.displayName = "ViewerDocumentFileItem";
|
|
|
|
type DataroomDocumentWithVersion = {
|
|
dataroomDocumentId: string;
|
|
folderId: string | null;
|
|
id: string;
|
|
name: string;
|
|
hierarchicalIndex: string | null;
|
|
versions: {
|
|
id: string;
|
|
versionNumber: number;
|
|
hasPages: boolean;
|
|
}[];
|
|
};
|
|
|
|
type DataroomFolderWithDocuments = DataroomFolder & {
|
|
childFolders: DataroomFolderWithDocuments[];
|
|
documents: {
|
|
dataroomDocumentId: string;
|
|
folderId: string | null;
|
|
id: string;
|
|
name: string;
|
|
hierarchicalIndex: string | null;
|
|
}[];
|
|
};
|
|
|
|
type FolderPath = Set<string> | null;
|
|
|
|
function findFolderPath(
|
|
folder: DataroomFolderWithDocuments,
|
|
folderId: string,
|
|
currentPath: Set<string> = new Set<string>(),
|
|
): FolderPath {
|
|
if (folder.id === folderId) {
|
|
return currentPath.add(folder.id);
|
|
}
|
|
|
|
for (const child of folder.childFolders) {
|
|
const path = findFolderPath(child, folderId, currentPath.add(folder.id));
|
|
if (path) {
|
|
return path;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
const FolderComponent = memo(
|
|
({
|
|
folder,
|
|
folderId,
|
|
setFolderId,
|
|
folderPath,
|
|
dataroomIndexEnabled,
|
|
}: {
|
|
folder: DataroomFolderWithDocuments;
|
|
folderId: string | null;
|
|
setFolderId: React.Dispatch<React.SetStateAction<string | null>>;
|
|
folderPath: Set<string> | null;
|
|
dataroomIndexEnabled?: boolean;
|
|
}) => {
|
|
const router = useRouter();
|
|
|
|
// Get hierarchical display name for the folder
|
|
const folderDisplayName = getHierarchicalDisplayName(
|
|
folder.name,
|
|
folder.hierarchicalIndex,
|
|
dataroomIndexEnabled || false,
|
|
);
|
|
|
|
// Memoize the rendering of the current folder's documents
|
|
const documents = useMemo(
|
|
() =>
|
|
folder.documents.map((doc) => (
|
|
<ViewerDocumentFileItem
|
|
key={doc.id}
|
|
document={{
|
|
...doc,
|
|
versions: [], // Not needed for display
|
|
}}
|
|
dataroomIndexEnabled={dataroomIndexEnabled}
|
|
/>
|
|
)),
|
|
[folder.documents, dataroomIndexEnabled],
|
|
);
|
|
|
|
// Recursively render child folders if they exist
|
|
const childFolders = useMemo(
|
|
() =>
|
|
folder.childFolders.map((childFolder) => (
|
|
<FolderComponent
|
|
key={childFolder.id}
|
|
folder={childFolder}
|
|
folderId={folderId}
|
|
setFolderId={setFolderId}
|
|
folderPath={folderPath}
|
|
dataroomIndexEnabled={dataroomIndexEnabled}
|
|
/>
|
|
)),
|
|
[
|
|
folder.childFolders,
|
|
folderId,
|
|
setFolderId,
|
|
folderPath,
|
|
dataroomIndexEnabled,
|
|
],
|
|
);
|
|
|
|
const isActive = folder.id === folderId;
|
|
const isChildActive =
|
|
folderPath?.has(folder.id) ||
|
|
folder.childFolders.some((childFolder) => childFolder.id === folderId);
|
|
|
|
return (
|
|
<div
|
|
onClick={(e) => {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
setFolderId(folder.id);
|
|
}}
|
|
>
|
|
<FileTree.Folder
|
|
name={folderDisplayName}
|
|
key={folder.id}
|
|
active={isActive}
|
|
childActive={isChildActive}
|
|
onToggle={() => setFolderId(folder.id)}
|
|
>
|
|
{childFolders}
|
|
{documents}
|
|
</FileTree.Folder>
|
|
</div>
|
|
);
|
|
},
|
|
);
|
|
FolderComponent.displayName = "FolderComponent";
|
|
|
|
const HomeLink = memo(
|
|
({
|
|
folderId,
|
|
setFolderId,
|
|
}: {
|
|
folderId: string | null;
|
|
setFolderId: React.Dispatch<React.SetStateAction<string | null>>;
|
|
}) => {
|
|
const { usesLightText, palette } = useViewerSurfaceTheme();
|
|
|
|
return (
|
|
<li
|
|
className={cn(
|
|
"flex list-none",
|
|
"rounded-md transition-all duration-200 ease-in-out",
|
|
usesLightText
|
|
? "text-[var(--viewer-text)] hover:bg-[var(--viewer-control-bg)]"
|
|
: "text-foreground hover:bg-gray-100 hover:shadow-sm hover:dark:bg-muted",
|
|
"px-3 py-1.5 leading-6",
|
|
folderId === null &&
|
|
(usesLightText
|
|
? "bg-[var(--viewer-panel-active)] font-semibold"
|
|
: "bg-gray-100 font-semibold dark:bg-muted"),
|
|
)}
|
|
style={
|
|
usesLightText
|
|
? ({
|
|
"--viewer-text": palette.textColor,
|
|
"--viewer-control-bg": palette.controlBgColor,
|
|
"--viewer-panel-active": palette.panelActiveBgColor,
|
|
} as CSSProperties)
|
|
: undefined
|
|
}
|
|
>
|
|
<span
|
|
className="inline-flex w-full cursor-pointer items-center"
|
|
onClick={(e) => {
|
|
e.preventDefault();
|
|
setFolderId(null);
|
|
}}
|
|
>
|
|
<HomeIcon className="h-5 w-5 shrink-0" aria-hidden="true" />
|
|
<span className="ml-2 w-fit truncate" title="Home">
|
|
Dataroom Home
|
|
</span>
|
|
</span>
|
|
</li>
|
|
);
|
|
},
|
|
);
|
|
HomeLink.displayName = "HomeLink";
|
|
|
|
const SidebarFolders = ({
|
|
folders,
|
|
documents,
|
|
folderId,
|
|
setFolderId,
|
|
dataroomIndexEnabled,
|
|
}: {
|
|
folders: DataroomFolder[];
|
|
documents: DataroomDocumentWithVersion[];
|
|
folderId: string | null;
|
|
setFolderId: React.Dispatch<React.SetStateAction<string | null>>;
|
|
dataroomIndexEnabled?: boolean;
|
|
}) => {
|
|
const { usesLightText, palette } = useViewerSurfaceTheme();
|
|
|
|
const nestedFolders = useMemo(() => {
|
|
if (folders) {
|
|
return buildNestedFolderStructureWithDocs(folders, documents);
|
|
}
|
|
return [];
|
|
}, [folders, documents]);
|
|
|
|
const folderPath = useMemo(() => {
|
|
if (!folderId) {
|
|
return null;
|
|
}
|
|
|
|
for (let i = 0; i < nestedFolders.length; i++) {
|
|
const path = findFolderPath(nestedFolders[i], folderId);
|
|
if (path) {
|
|
return path;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}, [nestedFolders, folderId]);
|
|
|
|
return (
|
|
<FileTree
|
|
prefersLightText={usesLightText}
|
|
style={
|
|
usesLightText
|
|
? ({
|
|
"--viewer-text": palette.textColor,
|
|
"--viewer-muted-text": palette.mutedTextColor,
|
|
"--viewer-control-bg": palette.controlBgColor,
|
|
"--viewer-panel-active": palette.panelActiveBgColor,
|
|
} as CSSProperties)
|
|
: undefined
|
|
}
|
|
>
|
|
<HomeLink folderId={folderId} setFolderId={setFolderId} />
|
|
{nestedFolders.map((folder) => (
|
|
<FolderComponent
|
|
key={folder.id}
|
|
folder={folder}
|
|
folderId={folderId}
|
|
setFolderId={setFolderId}
|
|
folderPath={folderPath}
|
|
dataroomIndexEnabled={dataroomIndexEnabled}
|
|
/>
|
|
))}
|
|
</FileTree>
|
|
);
|
|
};
|
|
|
|
export function ViewFolderTree({
|
|
folders,
|
|
documents,
|
|
setFolderId,
|
|
folderId,
|
|
dataroomIndexEnabled,
|
|
}: {
|
|
folders: DataroomFolder[];
|
|
documents: DataroomDocumentWithVersion[];
|
|
setFolderId: React.Dispatch<React.SetStateAction<string | null>>;
|
|
folderId: string | null;
|
|
dataroomIndexEnabled?: boolean;
|
|
}) {
|
|
if (!folders) return null;
|
|
|
|
return (
|
|
<SidebarFolders
|
|
folders={folders}
|
|
documents={documents}
|
|
setFolderId={setFolderId}
|
|
folderId={folderId}
|
|
dataroomIndexEnabled={dataroomIndexEnabled}
|
|
/>
|
|
);
|
|
}
|