fixed project renaming

This commit is contained in:
corgifist 2025-07-31 21:18:21 +03:00
parent f871d4d8d7
commit 7c5bfbddc9
3 changed files with 45 additions and 31 deletions

View File

@ -11,7 +11,7 @@ export default async function RootLayout({
return ( return (
<SidebarProvider> <SidebarProvider>
<Dashboard /> <Dashboard />
<main className="relative h-screen"> <main className="relative h-screen isolate">
<PersistenceProvider> <PersistenceProvider>
{children} {children}
</PersistenceProvider> </PersistenceProvider>

View File

@ -1,5 +1,5 @@
"use client"; "use client";
import { createContext, Dispatch, forwardRef, ReactNode, SetStateAction, useContext, useState } from "react"; import React, { createContext, Dispatch, forwardRef, ReactNode, SetStateAction, useContext, useId, useState } from "react";
import { useLiveQuery } from "dexie-react-hooks"; import { useLiveQuery } from "dexie-react-hooks";
import { addProject, db, deleteProject } from "@/lib/db"; import { addProject, db, deleteProject } from "@/lib/db";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
@ -24,8 +24,7 @@ import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent,
import { useDebounce } from "use-debounce"; import { useDebounce } from "use-debounce";
import { Checkbox } from "@/components/ui/checkbox"; import { Checkbox } from "@/components/ui/checkbox";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { Tooltip, TooltipContent } from "@/components/ui/tooltip"; import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
import { TooltipTrigger } from "@radix-ui/react-tooltip";
import { Sheet, SheetClose, SheetContent, SheetDescription, SheetHeader, SheetTitle, SheetTrigger } from "@/components/ui/sheet"; import { Sheet, SheetClose, SheetContent, SheetDescription, SheetHeader, SheetTitle, SheetTrigger } from "@/components/ui/sheet";
import { Separator } from "@/components/ui/separator"; import { Separator } from "@/components/ui/separator";
import StaticSidebarTrigger from "@/components/static-sidebar-trigger"; import StaticSidebarTrigger from "@/components/static-sidebar-trigger";
@ -103,7 +102,9 @@ const ProjectInfoFormSchema = z.object({
description: z.string().or(z.literal("")) description: z.string().or(z.literal(""))
}); });
const ProjectInfoForm = ({ form }: { form: UseFormReturn<z.infer<typeof ProjectInfoFormSchema>> }) => ( type ProjectInfoForm = z.infer<typeof ProjectInfoFormSchema>;
const ProjectInfoFormControls = ({ form }: { form: UseFormReturn<ProjectInfoForm> }) => (
<> <>
<FormField control={form.control} name="title" render={({ field }) => ( <FormField control={form.control} name="title" render={({ field }) => (
<FormItem> <FormItem>
@ -118,7 +119,7 @@ const ProjectInfoForm = ({ form }: { form: UseFormReturn<z.infer<typeof ProjectI
<FormItem> <FormItem>
<FormLabel>Description</FormLabel> <FormLabel>Description</FormLabel>
<FormControl> <FormControl>
<Textarea autoComplete="off" placeholder="Tell something about your project" className="resize-y" {...field} /> <Textarea {...field} autoComplete="off" placeholder="Tell something about your project" className="resize-y" />
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
</FormItem> </FormItem>
@ -127,16 +128,21 @@ const ProjectInfoForm = ({ form }: { form: UseFormReturn<z.infer<typeof ProjectI
); );
const RenameProjectDialog = ({ project }: { project: Project }) => { const RenameProjectDialog = ({ project }: { project: Project }) => {
const renameForm = useForm<z.infer<typeof ProjectInfoFormSchema>>({ const formId = useId();
const renameForm = useForm<ProjectInfoForm>({
resolver: zodResolver(ProjectInfoFormSchema), resolver: zodResolver(ProjectInfoFormSchema),
defaultValues: { defaultValues: {
title: project.title, title: project.title,
description: project.description description: project.description
} },
mode: "onChange"
}); });
const handleRenameSubmit = async (data: z.infer<typeof ProjectInfoFormSchema>) => { const handleRenameSubmit = async (data: ProjectInfoForm) => {
await db.projects.update(project.uuid, { console.log(data);
console.log(project.uuid);
console.log(data.title);
db.projects.update(project.uuid, {
title: data.title, title: data.title,
description: data.description, description: data.description,
editDate: Date.now() editDate: Date.now()
@ -147,17 +153,19 @@ const RenameProjectDialog = ({ project }: { project: Project }) => {
<DialogContent> <DialogContent>
<DialogHeader> <DialogHeader>
<DialogTitle>Rename Project</DialogTitle> <DialogTitle>Rename Project</DialogTitle>
<DialogDescription>Change the name of your project.</DialogDescription> <DialogDescription>Change information of your project.</DialogDescription>
</DialogHeader> </DialogHeader>
<Form {...renameForm}> <Form {...renameForm}>
<form onSubmit={renameForm.handleSubmit(handleRenameSubmit)} className="grid gap-3"> <form id={formId} onSubmit={renameForm.handleSubmit(handleRenameSubmit)} className="grid gap-3">
<ProjectInfoForm form={renameForm} /> <ProjectInfoFormControls form={renameForm} />
<DialogFooter> <DialogFooter>
<DialogClose asChild> <DialogClose asChild>
<Button variant="outline">Cancel</Button> <Button type="button" variant="outline">Cancel</Button>
</DialogClose> </DialogClose>
<DialogClose asChild> <DialogClose onClick={(e: React.MouseEvent<HTMLButtonElement>) => {
<Button type="submit">Rename</Button> if (renameForm.formState.errors.title) e.preventDefault();
}} asChild>
<Button type="submit" form={formId}>Rename</Button>
</DialogClose> </DialogClose>
</DialogFooter> </DialogFooter>
</form> </form>
@ -263,7 +271,7 @@ const ProjectDropdown = ({
<Separator /> <Separator />
<SheetClose asChild> <SheetClose asChild>
<Button variant="ghost" className="justify-start w-full" onClick={handleSelect}> <Button variant="ghost" className="justify-start w-full" onClick={handleSelect}>
{selected ? <Grid2X2XIcon /> : <Grid2X2CheckIcon /> }{selected ? "Deselect" : "Select"} {selected ? <Grid2X2XIcon /> : <Grid2X2CheckIcon />}{selected ? "Deselect" : "Select"}
</Button> </Button>
</SheetClose> </SheetClose>
<SheetClose asChild> <SheetClose asChild>
@ -308,14 +316,17 @@ const ProjectDropdown = ({
<DropdownMenuContent align={isMobile ? "end" : "start"} className="min-w-48"> <DropdownMenuContent align={isMobile ? "end" : "start"} className="min-w-48">
<div className="flex flex-row items-center justify-between w-full"> <div className="flex flex-row items-center justify-between w-full">
<DropdownMenuLabel className="font-semibold">{project.title}</DropdownMenuLabel> <DropdownMenuLabel className="font-semibold">{project.title}</DropdownMenuLabel>
<DropdownMenuItem onClick={() => setRenameDialogOpen(true)}> <DropdownMenuItem onClick={(e: React.MouseEvent<HTMLDivElement>) => {
e.preventDefault();
setRenameDialogOpen(true)
}}>
<PencilIcon /><span className="sr-only">Rename</span> <PencilIcon /><span className="sr-only">Rename</span>
</DropdownMenuItem> </DropdownMenuItem>
</div> </div>
<DropdownMenuSeparator /> <DropdownMenuSeparator />
<DropdownMenuGroup> <DropdownMenuGroup>
<DropdownMenuItem onClick={handleSelect}> <DropdownMenuItem onClick={handleSelect}>
{selected ? <Grid2X2XIcon className="mr-2" /> : <Grid2X2CheckIcon className="mr-2" /> } {selected ? "Deselect" : "Select"} {selected ? <Grid2X2XIcon className="mr-2" /> : <Grid2X2CheckIcon className="mr-2" />} {selected ? "Deselect" : "Select"}
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem onClick={() => console.log("Edit Project")}> <DropdownMenuItem onClick={() => console.log("Edit Project")}>
<EditIcon className="mr-2" /> Edit <EditIcon className="mr-2" /> Edit
@ -398,7 +409,7 @@ const ProjectContainer = ({
// TODO: data-selectable is a really dirty way of deciding whether to trigger selection or not should be reworked in the future // TODO: data-selectable is a really dirty way of deciding whether to trigger selection or not should be reworked in the future
const handleCheck = (e: React.MouseEvent<HTMLDivElement>) => { const handleCheck = (e: React.MouseEvent<HTMLDivElement>) => {
if ((e.target as HTMLDivElement).getAttribute("data-selectable") != "true") return; if ((e.target as HTMLDivElement).getAttribute("data-selectable") != "true") return;
e.stopPropagation(); e.preventDefault();
if (selecting) { if (selecting) {
const index = selectedProjects.indexOf(project.uuid); const index = selectedProjects.indexOf(project.uuid);
if (index >= 0) { if (index >= 0) {
@ -418,7 +429,7 @@ const ProjectContainer = ({
<div data-selectable="true"> <div data-selectable="true">
<h3 className="text-sm sm:text-sm md:text-md lg:text-lg font-semibold line-clamp-1" data-selectable="true">{project.title}</h3> <h3 className="text-sm sm:text-sm md:text-md lg:text-lg font-semibold line-clamp-1" data-selectable="true">{project.title}</h3>
{project.description && <p className="text-sm text-secondary-foreground line-clamp-1" data-selectable="true">{project.description}</p>} {project.description && <p className="text-sm text-secondary-foreground line-clamp-1" data-selectable="true">{project.description}</p>}
{project.editDate && <p className="text-sm text-secondary-foreground" data-selectable="true">Last Edit Date: {date.toLocaleDateString()}, {date.toLocaleTimeString()}</p>} {project.editDate && <p className="text-sm text-secondary-foreground" data-selectable="true">Last Edit Date: {date.toLocaleDateString()}, {date.toLocaleTimeString([], { timeStyle: "short" })}</p>}
</div> </div>
<div className="flex flex-col lg:xl:flex-row items-center gap-1" data-selectable="true"> <div className="flex flex-col lg:xl:flex-row items-center gap-1" data-selectable="true">
{!selecting && <ProjectDescription project={project} />} {!selecting && <ProjectDescription project={project} />}
@ -427,14 +438,14 @@ const ProjectContainer = ({
</div> </div>
{selecting && ( {selecting && (
<div className="absolute top-0 right-0 p-5" data-selectable="true"> <div className="absolute top-0 right-0 p-5" data-selectable="true">
<Checkbox checked={selectedProjects.includes(project.uuid)} data-selectable="true"/> <Checkbox checked={selectedProjects.includes(project.uuid)} data-selectable="true" />
</div> </div>
)} )}
</AscendingCard> </AscendingCard>
</AspectRatio> </AspectRatio>
); );
return selecting return true
? projectComponent ? projectComponent
: (<Link href={`/editor/${project.uuid}`} onClick={(e: React.MouseEvent<HTMLAnchorElement>) => { : (<Link href={`/editor/${project.uuid}`} onClick={(e: React.MouseEvent<HTMLAnchorElement>) => {
if ((e.target as HTMLDivElement).getAttribute("data-selectable") != "true") e.preventDefault(); if ((e.target as HTMLDivElement).getAttribute("data-selectable") != "true") e.preventDefault();
@ -456,11 +467,12 @@ export default function Home(): ReactNode {
)); ));
const filteredProjects = projects && ( const filteredProjects = projects && (
projects.filter((project) => project.title.toLowerCase().includes(debouncedSearch.toLowerCase())) projects
.filter((project) => project.title.toLowerCase().includes(debouncedSearch.toLowerCase()))
.sort((a, b) => sortProjects(a, b, sortingType) * (descendingSort ? -1 : 1)) .sort((a, b) => sortProjects(a, b, sortingType) * (descendingSort ? -1 : 1))
); );
const newProjectForm = useForm<z.infer<typeof ProjectInfoFormSchema>>({ const newProjectForm = useForm<ProjectInfoForm>({
resolver: zodResolver(ProjectInfoFormSchema), resolver: zodResolver(ProjectInfoFormSchema),
defaultValues: { defaultValues: {
title: "New ClipFusion Project", title: "New ClipFusion Project",
@ -468,7 +480,7 @@ export default function Home(): ReactNode {
} }
}); });
const newProjectSubmit = async (data: z.infer<typeof ProjectInfoFormSchema>) => { const newProjectSubmit = async (data: ProjectInfoForm) => {
const date = Date.now(); const date = Date.now();
addProject({ addProject({
uuid: generateUUID(), uuid: generateUUID(),
@ -524,12 +536,14 @@ export default function Home(): ReactNode {
</DialogHeader> </DialogHeader>
<Form {...newProjectForm}> <Form {...newProjectForm}>
<form onSubmit={newProjectForm.handleSubmit(newProjectSubmit)} className="grid gap-3"> <form onSubmit={newProjectForm.handleSubmit(newProjectSubmit)} className="grid gap-3">
<ProjectInfoForm form={newProjectForm} /> <ProjectInfoFormControls form={newProjectForm} />
<DialogFooter> <DialogFooter>
<DialogClose asChild> <DialogClose asChild>
<Button variant="outline">Cancel</Button> <Button type="button" variant="outline">Cancel</Button>
</DialogClose> </DialogClose>
<DialogClose asChild> <DialogClose onClick={(e: React.MouseEvent<HTMLButtonElement>) => {
if (newProjectForm.formState.errors.title) e.preventDefault();
}} asChild>
<Button type="submit">Create</Button> <Button type="submit">Create</Button>
</DialogClose> </DialogClose>
</DialogFooter> </DialogFooter>
@ -578,7 +592,7 @@ export default function Home(): ReactNode {
<div className={cn("grid grid-cols-1 sm:grid-cols-2 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4 mt-2")}> <div className={cn("grid grid-cols-1 sm:grid-cols-2 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4 mt-2")}>
{filteredProjects && filteredProjects.map((project) => <ProjectContainer key={project.uuid} project={project} />)} {filteredProjects && filteredProjects.map((project) => <ProjectContainer key={project.uuid} project={project} />)}
</div> </div>
{(projects != undefined && projects.length == 0) && ( {(filteredProjects != undefined && filteredProjects.length == 0) && (
<div className="w-full h-full flex justify-center items-center"> <div className="w-full h-full flex justify-center items-center">
<Label className="text-muted-foreground">Nothing to Show</Label> <Label className="text-muted-foreground">Nothing to Show</Label>
</div> </div>

View File

@ -37,7 +37,7 @@ export default async function RootLayout({
children: ReactNode children: ReactNode
}>) { }>) {
return ( return (
<main className="relative h-screen"> <main className="relative w-screen h-screen isolate">
<PersistenceProvider> <PersistenceProvider>
{children} {children}
</PersistenceProvider> </PersistenceProvider>