diff --git a/frontend/index.html b/frontend/index.html index ff93803bb..ac397ffdb 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -4,7 +4,7 @@ - Tauri + React + Typescript + PictoPy - AI-Powered Photo Manager diff --git a/frontend/src/components/Media/ChronologicalGallery.tsx b/frontend/src/components/Media/ChronologicalGallery.tsx index f033e35a0..a5a4584ae 100644 --- a/frontend/src/components/Media/ChronologicalGallery.tsx +++ b/frontend/src/components/Media/ChronologicalGallery.tsx @@ -6,6 +6,7 @@ import { groupImagesByYearMonthFromMetadata } from '@/utils/dateUtils'; import { setCurrentViewIndex } from '@/features/imageSlice'; import { MediaView } from './MediaView'; import { selectIsImageViewOpen } from '@/features/imageSelectors'; +import { useSidebar } from '@/components/ui/sidebar'; export type MonthMarker = { offset: number; @@ -34,6 +35,11 @@ export const ChronologicalGallery = ({ const monthHeaderRefs = useRef>(new Map()); const galleryRef = useRef(null); const isImageViewOpen = useSelector(selectIsImageViewOpen); + const { open: sidebarOpen } = useSidebar(); + + // Adjust grid minmax based on sidebar state + // When sidebar is collapsed, reduce min size to fit more images per row + const gridMinSize = sidebarOpen ? '224px' : '200px'; // Optimized grouping with proper date handling const grouped = useMemo( @@ -157,7 +163,12 @@ export const ChronologicalGallery = ({ {/* Images Grid */} -
+
{imgs.map((img) => { const chronologicalIndex = imageIndexMap.get(img.id) ?? -1; diff --git a/frontend/src/components/Navigation/Sidebar/AppSidebar.tsx b/frontend/src/components/Navigation/Sidebar/AppSidebar.tsx index 3fa8cd3d7..0b12cfe74 100644 --- a/frontend/src/components/Navigation/Sidebar/AppSidebar.tsx +++ b/frontend/src/components/Navigation/Sidebar/AppSidebar.tsx @@ -7,6 +7,7 @@ import { SidebarMenuButton, SidebarMenuItem, SidebarSeparator, + useSidebar, } from '@/components/ui/sidebar'; import { Bolt, @@ -16,15 +17,23 @@ import { Video, BookImage, ClockFading, + ChevronLeft, } from 'lucide-react'; import { useLocation, Link } from 'react-router'; import { ROUTES } from '@/constants/routes'; import { getVersion } from '@tauri-apps/api/app'; import { useEffect, useState } from 'react'; +import { Button } from '@/components/ui/button'; +import { + Tooltip, + TooltipContent, + TooltipTrigger, +} from '@/components/ui/tooltip'; export function AppSidebar() { const location = useLocation(); const [version, setVersion] = useState('1.0.0'); + const { open, toggleSidebar } = useSidebar(); useEffect(() => { getVersion().then((version) => { @@ -59,7 +68,7 @@ export function AppSidebar() {
.
@@ -73,7 +82,7 @@ export function AppSidebar() { asChild isActive={isActive(item.path)} tooltip={item.name} - className="rounded-sm" + className="rounded-sm transition-all duration-200" > @@ -85,9 +94,44 @@ export function AppSidebar() { -
-
PictoPy v{version}
-
© {new Date().getFullYear()} PictoPy
+
+
+
+ PictoPy v{version} +
+
+ © {new Date().getFullYear()} PictoPy +
+
+
+ + + + + +

{open ? 'Collapse sidebar' : 'Expand sidebar'}

+
+
+
diff --git a/frontend/src/components/ui/sidebar.tsx b/frontend/src/components/ui/sidebar.tsx index 0a3c6ab02..b24456fef 100644 --- a/frontend/src/components/ui/sidebar.tsx +++ b/frontend/src/components/ui/sidebar.tsx @@ -68,9 +68,24 @@ function SidebarProvider({ const [openMobile, setOpenMobile] = React.useState(false); // This is the internal state of the sidebar. - // We use openProp and setOpenProp for control from outside the component. const [_open, _setOpen] = React.useState(defaultOpen); const open = openProp ?? _open; + + // Restore sidebar state from cookie on mount + React.useEffect(() => { + try { + const cookies = document.cookie + .split('; ') + .find((row) => row.startsWith(SIDEBAR_COOKIE_NAME + '=')); + if (cookies) { + const savedState = cookies.split('=')[1] === 'true'; + _setOpen(savedState); + } + } catch { + // Ignore cookie errors + } + }, []); + const setOpen = React.useCallback( (value: boolean | ((value: boolean) => boolean)) => { const openState = typeof value === 'function' ? value(open) : value; @@ -81,7 +96,9 @@ function SidebarProvider({ } // This sets the cookie to keep the sidebar state. - document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`; + try { + document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`; + } catch {} }, [setOpenProp, open], ); @@ -107,8 +124,6 @@ function SidebarProvider({ return () => window.removeEventListener('keydown', handleKeyDown); }, [toggleSidebar]); - // We add a state so that we can do data-state="expanded" or "collapsed". - // This makes it easier to style the sidebar with Tailwind classes. const state = open ? 'expanded' : 'collapsed'; const contextValue = React.useMemo( @@ -231,7 +246,7 @@ function Sidebar({ side === 'left' ? 'left-0 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]' : 'right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]', - // Adjust the padding for floating and inset variants. + variant === 'floating' || variant === 'inset' ? 'p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4))+2px)]' : 'group-data-[collapsible=icon]:w-(--sidebar-width-icon) group-data-[side=left]:border-r group-data-[side=right]:border-l', @@ -425,7 +440,7 @@ function SidebarGroupAction({ data-sidebar="group-action" className={cn( 'text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground absolute top-3.5 right-3 flex aspect-square w-5 items-center justify-center rounded-md p-0 outline-hidden transition-transform focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0', - // Increases the hit area of the button on mobile. + 'after:absolute after:-inset-2 md:after:hidden', 'group-data-[collapsible=icon]:hidden', className, @@ -560,7 +575,7 @@ function SidebarMenuAction({ data-sidebar="menu-action" className={cn( 'text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground peer-hover/menu-button:text-sidebar-accent-foreground absolute top-1.5 right-1 flex aspect-square w-5 items-center justify-center rounded-md p-0 outline-hidden transition-transform focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0', - // Increases the hit area of the button on mobile. + 'after:absolute after:-inset-2 md:after:hidden', 'peer-data-[size=sm]/menu-button:top-1', 'peer-data-[size=default]/menu-button:top-1.5', @@ -604,7 +619,6 @@ function SidebarMenuSkeleton({ }: React.ComponentProps<'div'> & { showIcon?: boolean; }) { - // Random width between 50 to 90%. const width = React.useMemo(() => { return `${Math.floor(Math.random() * 40) + 50}%`; }, []); diff --git a/frontend/src/pages/Home/MyFav.tsx b/frontend/src/pages/Home/MyFav.tsx index 2afcb70e8..41e60adb0 100644 --- a/frontend/src/pages/Home/MyFav.tsx +++ b/frontend/src/pages/Home/MyFav.tsx @@ -48,7 +48,7 @@ export const MyFav = () => { }, [data, isSuccess, dispatch, isSearchActive]); const favouriteImages = useMemo( - () => images.filter((image) => image.isFavourite === true), + () => images.filter((image) => image.isFavourite), [images], ); diff --git a/landing-page/src/Pages/Landing page/Home1.tsx b/landing-page/src/Pages/Landing page/Home1.tsx index a60532686..e192e33c6 100644 --- a/landing-page/src/Pages/Landing page/Home1.tsx +++ b/landing-page/src/Pages/Landing page/Home1.tsx @@ -82,7 +82,7 @@ const shuffle = (array: (typeof squareData)[0][]) => { let currentIndex = array.length, randomIndex; - while (currentIndex != 0) { + while (currentIndex !== 0) { randomIndex = Math.floor(Math.random() * currentIndex); currentIndex--;