diff --git a/src/app/globals.css b/src/app/globals.css index 9a9ee11..0055f51 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -66,6 +66,11 @@ body { background-color: var(--color-background); } +main { + width: 100%; + height: max(100vh, 100%); +} + .dark body { background-color: var(--color-background); } diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 8293abd..c1e0b84 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -48,7 +48,7 @@ export default async function RootLayout({ -
+
{children} diff --git a/src/app/page.tsx b/src/app/page.tsx index 0c7a16b..32a7acb 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -5,7 +5,7 @@ import { useLiveQuery } from "dexie-react-hooks"; import { addProject, db, deleteProject } from "@/lib/db"; import { Label } from "@/components/ui/label"; import { Button } from "@/components/ui/button"; -import { ALargeSmallIcon, ArrowDownAZIcon, ArrowDownIcon, ArrowUpAZIcon, ArrowUpIcon, CalendarArrowDownIcon, CalendarArrowUpIcon, CalendarIcon, ClockArrowDownIcon, ClockArrowUpIcon, ClockIcon, CopyIcon, EditIcon, EllipsisIcon, Grid2X2CheckIcon, InfoIcon, ListCheckIcon, PencilIcon, PlusIcon, TrashIcon } from "lucide-react"; +import { ALargeSmallIcon, ArrowDownAZIcon, ArrowDownIcon, ArrowUpAZIcon, ArrowUpIcon, CalendarArrowDownIcon, CalendarArrowUpIcon, CalendarIcon, ClockArrowDownIcon, ClockArrowUpIcon, ClockIcon, CopyIcon, EditIcon, EllipsisIcon, Grid2X2CheckIcon, Grid2x2X, Grid2x2XIcon, InfoIcon, ListCheckIcon, PencilIcon, PlusIcon, TrashIcon } from "lucide-react"; import { Toggle } from "@/components/ui/toggle"; import Search from "@/components/search"; import { useIsMobile } from "@/hooks/use-mobile"; @@ -33,6 +33,7 @@ import { Separator } from "@/components/ui/separator"; import StaticSidebarTrigger from "@/components/static-sidebar-trigger"; import SidebarTriggerAdjustable from "@/components/sidebar-trigger-adjustable"; import ScrollFadingTitle from "@/components/scroll-fading-title"; +import { fa } from "zod/v4/locales"; type SortingType = "byCreationDate" | "byEditDate" @@ -190,14 +191,12 @@ const DeleteProjectDialog = ({ project }: { project: Project }) => { const ProjectDropdown = ({ project, - selected, - setSelected + selected }: { project: Project, - selected: boolean, - setSelected: Dispatch> + selected: boolean }): ReactNode => { - const { selecting, setSelecting } = useSelectContext(); + const { selecting, setSelecting, selectedProjects, setSelectedProjects } = useSelectContext(); const [renameDialogOpen, setRenameDialogOpen] = useState(false); const [deleteAlertOpen, setDeleteAlertOpen] = useState(false); const isMobile = useIsMobile(); @@ -227,8 +226,18 @@ const ProjectDropdown = ({ }; const handleSelect = () => { - if (!selecting) setSelecting(true); - setSelected(!selected); + if (!selecting) { + setSelecting(true); + setSelectedProjects([]); + } + + const index = selectedProjects.indexOf(project.uuid); + if (index >= 0) { + selectedProjects.splice(index, 1); + setSelectedProjects([...selectedProjects]); + } else { + setSelectedProjects([...selectedProjects, project.uuid]); + } }; if (isMobile) { @@ -383,42 +392,26 @@ const ProjectContainer = ({ project: Project }): ReactNode => { const { selecting, selectedProjects, setSelectedProjects } = useSelectContext(); - const [selected, setSelected] = useState(false); - const [mounted, setMounted] = useState(false); - - if (!selecting && selected) setSelected(false); const date = new Date(project.editDate); // TODO: data-selectable is a really dirty way of deciding whether to trigger selection or not should be reworked in the future - const handleClick = (e: React.MouseEvent) => { + const handleCheck = (e: React.MouseEvent) => { if ((e.target as HTMLDivElement).getAttribute("data-selectable") != "true") return; e.stopPropagation(); if (selecting) { - setSelected(!selected); + const index = selectedProjects.indexOf(project.uuid); + if (index >= 0) { + selectedProjects.splice(index, 1); + setSelectedProjects([...selectedProjects]); + } else { + setSelectedProjects([...selectedProjects, project.uuid]); + } } }; - // Automatically add UUID to the selectedProjects - // useEffect is required here because we can't update SelectContext while rendering - useEffect(() => { - if (mounted) { - if (selected) { - setSelectedProjects([...selectedProjects, project.uuid]); - } else { - const newSelectedProject = selectedProjects; - const index = selectedProjects.indexOf(project.uuid); - if (index >= 0) { - newSelectedProject.splice(newSelectedProject.indexOf(project.uuid), 1); - setSelectedProjects(newSelectedProject); - } - } - } - setMounted(true); - }, [selected]); - return ( - +
@@ -429,12 +422,12 @@ const ProjectContainer = ({
{!selecting && } - +
{selecting && (
- setSelected(checked as boolean)} /> + handleCheck} />
)}
@@ -450,6 +443,7 @@ export default function Home(): ReactNode { const [selectedProjects, setSelectedProjects] = useState([]); const [sortingType, setSortingType] = useState(defaultSortingType); const [descendingSort, setDescendingSort] = useState(false); + const [showDeleteSelectedAlert, setShowDeleteSelectedAlert] = useState(false); const projects = useLiveQuery(() => ( db.projects.toArray() @@ -480,6 +474,12 @@ export default function Home(): ReactNode { } as Project); }; + const handleDeleteSelected = () => { + selectedProjects.map((uuid) => deleteProject(uuid)); + setSelectedProjects([]); + setSelecting(false); + }; + const context: SelectContextData = { selecting, setSelecting, @@ -489,94 +489,148 @@ export default function Home(): ReactNode { return ( -
-
- - -

Project Library

- {projects && } -
-
-
- -
- - - - - - - - Create New Project - - - Fill in the information about your project. You can change it at any time later. - - -
- - - - - - - - - - - - -
-
- setSearch(e.target.value)} className={isMobile ? "w-full" : "w-60"} /> -
-
-
-
- - - - - - Sort Projects - - - - - - - - - - setDescendingSort(pressed)}> - {descendingSort ? : } - - - - {descendingSort ? "Descending" : "Ascending"} - - -
- setSelecting(pressed)}> - {isMobile ? "Select" : "Select Projects"} - +
+
+
+ + +

Project Library

+ {projects && } +
-
-
- {filteredProjects && filteredProjects.map((project) => )} -
- {(projects != undefined && projects.length == 0) && ( -
- +
+ +
+ + + + + + + + Create New Project + + + Fill in the information about your project. You can change it at any time later. + + +
+ + + + + + + + + + + + +
+
+ setSearch(e.target.value)} className={isMobile ? "w-full" : "w-60"} /> +
+
+
+
+ + + + + + Sort Projects + + + + + + + + + + setDescendingSort(pressed)}> + {descendingSort ? : } + + + + {descendingSort ? "Descending" : "Ascending"} + + +
+ setSelecting(pressed)}> + {isMobile ? "Select" : "Select Projects"} + +
+
+ {filteredProjects && filteredProjects.map((project) => )} +
+ {(projects != undefined && projects.length == 0) && ( +
+ +
+ )} +
+ {selecting && ( + <> +
+
+
+ +
+ +
+ + + + + + + filteredProjects && (selectedProjects.length == 0 ? setSelectedProjects(filteredProjects.map((project) => project.uuid)) : setSelectedProjects([]))}> + {selectedProjects.length == 0 ? "Select All" : "Deselect All"} + + + + +
+
+
+ )}
+ + + + Delete {selectedProjects.length} Projects + + This action cannot be undone. + + + + + + + + + + + + ); }; diff --git a/src/app/settings/page.tsx b/src/app/settings/page.tsx index 2605396..d64387f 100644 --- a/src/app/settings/page.tsx +++ b/src/app/settings/page.tsx @@ -7,6 +7,7 @@ import { InfoIcon } from "lucide-react"; import { ReactNode, useEffect, useState } from "react"; import { usePersistenceContext } from "../persistence-provider"; import StaticSidebarTrigger from "@/components/static-sidebar-trigger"; +import ScrollFadingTitle from "@/components/scroll-fading-title"; function PersistentStorageControl({ status @@ -31,7 +32,9 @@ export default function Settings(): ReactNode {
-

Settings

+ +

Settings

+

Storage

diff --git a/src/components/static-sidebar-trigger/StaticSidebarTrigger.tsx b/src/components/static-sidebar-trigger/StaticSidebarTrigger.tsx index 54df90d..caff4b3 100644 --- a/src/components/static-sidebar-trigger/StaticSidebarTrigger.tsx +++ b/src/components/static-sidebar-trigger/StaticSidebarTrigger.tsx @@ -1,14 +1,12 @@ "use client"; -import { useEffect, useState } from "react"; -import { SidebarTrigger, useSidebar } from "../ui/sidebar"; +import { SidebarTrigger } from "../ui/sidebar"; export const StaticSidebarTrigger = () => { - const { open } = useSidebar(); return ( <>
- +
);