Skip to content

Commit

Permalink
bump file tree bug for now
Browse files Browse the repository at this point in the history
  • Loading branch information
BankkRoll authored Jun 22, 2024
1 parent d175fc6 commit f49b4ef
Show file tree
Hide file tree
Showing 2 changed files with 298 additions and 35 deletions.
177 changes: 177 additions & 0 deletions website/src/components/create/file-tree.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
// src/components/create/file-tree.tsx

import { Fragment, useCallback, useState } from "react";

import { Checkbox } from "@/components/ui/checkbox";
import { Minus } from "lucide-react";

export enum CheckboxState {
UNCHECKED,
CHECKED,
INDETERMINATE,
}

export type ItemState = {
id: number;
state: CheckboxState;
};

export type Item = {
id: number;
name: string;
parentId: number;
};

type CheckboxListProps = {
items: Item[];
idsToRender?: number[];
indentLevel?: number;
onClick?: (id: number) => void;
getStateForId: (id: number) => CheckboxState;
};

export const updateItemStates = (
oldState: ItemState[],
items: Item[],
clickedId: number,
) => {
const newState = oldState.map((i) => ({ ...i }));

const getItemState = (id: number) => {
return newState.find((i) => i.id === id)!.state;
};

const updateParent = (id: number) => {
const item = items.find((i) => i.id === id);
const parent = items.find((i) => i.id === item!.parentId);
if (!parent) return;
const childIds = items
.filter((i) => i.parentId === parent.id)
.map((i) => i.id);
const childStates = childIds.map((childId) => getItemState(childId));
if (childStates.every((state) => state === CheckboxState.CHECKED)) {
newState.find((i) => i.id === parent.id)!.state = CheckboxState.CHECKED;
} else if (
childStates.every((state) => state === CheckboxState.UNCHECKED)
) {
newState.find((i) => i.id === parent.id)!.state = CheckboxState.UNCHECKED;
} else {
newState.find((i) => i.id === parent.id)!.state =
CheckboxState.INDETERMINATE;
}
updateParent(parent.id);
};

const setUnchecked = (id: number) => {
newState.find((i) => i.id === id)!.state = CheckboxState.UNCHECKED;
items
.filter((i) => i.parentId === id)
.forEach((child) => setUnchecked(child.id));
updateParent(id);
};

const setChecked = (id: number) => {
newState.find((i) => i.id === id)!.state = CheckboxState.CHECKED;
items
.filter((i) => i.parentId === id)
.forEach((child) => setChecked(child.id));
updateParent(id);
};

const itemState = getItemState(clickedId);
if (itemState === CheckboxState.CHECKED) {
setUnchecked(clickedId);
} else {
setChecked(clickedId);
}
return newState;
};

function CheckboxList({
items,
getStateForId,
idsToRender = [],
indentLevel = 0,
onClick = () => {},
}: CheckboxListProps) {
if (!idsToRender.length) {
idsToRender = items.filter((i) => !i.parentId).map((i) => i.id);
}

const getChildNodes = (parentId: number) => {
const nodeItems = items.filter((i) => i.parentId === parentId);
if (!nodeItems.length) return null;
return (
<CheckboxList
items={items}
idsToRender={nodeItems.map((i) => i.id)}
indentLevel={indentLevel + 1}
onClick={onClick}
getStateForId={getStateForId}
key={parentId} // Ensuring unique key for each parent
/>
);
};

return (
<ul style={{ paddingLeft: indentLevel * 20 }}>
{idsToRender.map((id) => {
const item = items.find((i) => i.id === id);
const checkboxState = getStateForId(id);
const isIndeterminate = checkboxState === CheckboxState.INDETERMINATE;
const isChecked = checkboxState === CheckboxState.CHECKED;
return (
<Fragment key={item!.id}>
<li className="flex items-center gap-2 py-1">
{isIndeterminate ? (
<div className="w-fit rounded bg-black p-[1px]">
<Minus className="h-[14px] w-[14px] text-white" />
</div>
) : (
<Checkbox
id={String(item!.id)}
onClick={() => onClick(item!.id)}
checked={isChecked}
/>
)}
<label
htmlFor={String(item!.id)}
className="hover:cursor-pointer"
>
{item!.name}
</label>
</li>
{getChildNodes(item!.id)}
</Fragment>
);
})}
</ul>
);
}

function Tree({
data,
getStateForId,
onClick,
}: {
data: Item[];
getStateForId: (id: number) => CheckboxState;
onClick: (id: number) => void;
}) {
const defaultItemStates: ItemState[] = data.map((i) => ({
id: i.id,
state: CheckboxState.UNCHECKED,
}));

const [itemStates, setItemStates] = useState<ItemState[]>(defaultItemStates);

return (
<CheckboxList
items={data}
onClick={onClick}
getStateForId={getStateForId}
/>
);
}

export { Tree, CheckboxList };
156 changes: 121 additions & 35 deletions website/src/pages/create.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
// src/pages/create.tsx
import { SetStateAction, useEffect, useState } from "react";
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { useEffect, useState } from "react";

import { AnimatedBeamer } from "@/components/ui/beams/animated-beamer";
import { Button } from "@/components/ui/button";
import { Checkbox } from "@/components/ui/checkbox";
import FileSelectionDialog from "@/components/create/file-selector";
import GitHubAuth from "@/components/create/github-auth";
import { GitHubLogoIcon } from "@radix-ui/react-icons";
import { Input } from "@/components/ui/input";
import PDFPreview from "@/components/create/pdf-preview";
import { ScrollArea } from "@/components/ui/scroll-area";
import { convertToPDF } from "@/utils/pdf-converter";
import { toast } from "sonner";
import { useRouter } from "next/router";
Expand Down Expand Up @@ -77,7 +84,8 @@ export default function Create() {
setRepoFiles(repoFiles);
setSelectedFiles(
new Set(repoFiles.map((file: { path: any }) => file.path)),
);
); // Default to select all
setSelectAll(true);
setDialogOpen(true);
return { repoUrl };
})
Expand Down Expand Up @@ -136,53 +144,131 @@ export default function Create() {
}
};

const handleLogout = () => {
setToken(null);
setUsername(null);
setRepoFiles([]);
setSelectedFiles(new Set());
setSelectAll(false);
setDialogOpen(false);
toast.success("Logged out successfully");
};

return (
<main className="flex flex-col md:flex-row justify-between p-4">
<div className="flex flex-col w-full md:w-1/3 p-4">
<h1 className="text-4xl font-bold mb-6">Create</h1>
<Input
placeholder="Enter GitHub Repo URL"
value={repoUrl}
onChange={(e: { target: { value: SetStateAction<string> } }) =>
setRepoUrl(e.target.value)
}
onChange={(e) => setRepoUrl(e.target.value)}
className="mb-4"
/>
<GitHubAuth
token={token}
username={username}
onSignIn={handleSignInWithGitHub}
onCloneRepo={handleCloneRepo}
onLogout={handleLogout}
isLoading={isLoading}
repoUrl={repoUrl}
/>
<div className="flex flex-col pb-6 gap-4">
<div className="flex items-center gap-2">
<Checkbox disabled id="lineNumbers" />
<label
htmlFor="lineNumbers"
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
>
Add Line Numbers
</label>
</div>
<div className="flex items-center gap-2">
<Checkbox disabled id="pageNumbers" />
<label
htmlFor="pageNumbers"
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
>
Add Page Numbers
</label>
</div>
</div>
{!token ? (
<div className="flex flex-col">
<Button variant="secondary" onClick={handleCloneRepo}>
Connect
<GitHubLogoIcon className="ml-2 w-5 h-5" />
</Button>
<p className="text-sm text-center text-muted-foreground italic">
Your github token is NEVER STORED and used only in auth callback
to clone and convert the repository.
</p>
</div>
) : (
<div className="flex flex-col">
<Button
onClick={handleCloneRepo}
disabled={!repoUrl || isLoading}
className="mb-4"
>
Fetch Files
</Button>
<Button
variant="secondary"
className="hover:bg-secondary cursor-default hover:cursor-default"
>
{username ? `${username}` : "Loading..."}
<GitHubLogoIcon className="ml-2 w-5 h-5" />
</Button>
</div>
)}
{repoFiles.length > 0 && (
<>
<FileSelectionDialog
dialogOpen={dialogOpen}
setDialogOpen={setDialogOpen}
repoFiles={repoFiles}
selectedFiles={selectedFiles}
handleFileSelection={handleFileSelection}
/>
<Dialog open={dialogOpen} onOpenChange={setDialogOpen}>
<DialogTrigger>
<Button className="w-full my-4">
Select Files ({selectedFiles.size})
</Button>
</DialogTrigger>
<DialogContent>
<ScrollArea className="max-h-[60svh]">
<DialogHeader>
<DialogTitle>Select Files</DialogTitle>
</DialogHeader>
<div className="flex flex-col gap-2">
<div className="flex items-center gap-2">
<Checkbox
id="selectAll"
checked={selectAll}
onCheckedChange={handleSelectAll}
/>
<label htmlFor="selectAll" className="text-sm">
Select All
</label>
</div>
{repoFiles.map((file) => (
<div key={file.path} className="flex items-center gap-2">
<Checkbox
id={file.path}
checked={selectedFiles.has(file.path)}
onCheckedChange={() => handleFileSelection(file.path)}
/>
<label htmlFor={file.path} className="text-sm">
{file.path}
</label>
</div>
))}
</div>
</ScrollArea>
</DialogContent>
</Dialog>
<Button onClick={handleConvertToPDF} disabled={isLoading}>
Convert ({selectedFiles.size}) Files to PDF
</Button>
</>
)}
</div>
<PDFPreview pdfUrl={pdfUrl} isLoading={isLoading} />

<div className="flex flex-col w-full md:w-2/3 p-4 items-center">
{!pdfUrl ? (
<iframe
src={"/repo2pdf-web.pdf"}
title="PDF Preview"
className="min-h-[400px] md:min-h-[80svh] w-full h-full border rounded-lg"
/>
) : isLoading ? (
<div className="w-full h-full flex items-center justify-center">
<AnimatedBeamer />
</div>
) : (
<iframe
src={pdfUrl}
title="PDF Preview"
className="min-h-[400px] md:min-h-[80svh] w-full h-full border rounded-lg"
/>
)}
</div>
</main>
);
}

0 comments on commit f49b4ef

Please sign in to comment.