Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 29 additions & 13 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

33 changes: 28 additions & 5 deletions frontend/src/components/Media/MediaInfoPanel.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react';
import React, { useEffect, useState } from 'react';
import { open } from '@tauri-apps/plugin-shell';
import {
X,
Expand Down Expand Up @@ -26,6 +26,19 @@ export const MediaInfoPanel: React.FC<MediaInfoPanelProps> = ({
currentIndex,
totalImages,
}) => {
const [isVisible, setIsVisible] = useState(show);

useEffect(() => {
if (show) {
setIsVisible(true);
}
}, [show]);

const handleClose = () => {
setIsVisible(false);
setTimeout(onClose, 300); // must match animation duration
};

const getFormattedDate = () => {
if (currentImage?.metadata?.date_created) {
return new Date(currentImage.metadata.date_created).toLocaleDateString(
Expand Down Expand Up @@ -58,19 +71,29 @@ export const MediaInfoPanel: React.FC<MediaInfoPanelProps> = ({
}
};

if (!show) return null;
if (!show && !isVisible) return null;


return (
<div className="animate-in slide-in-from-left absolute top-10 left-6 z-50 w-[350px] rounded-xl border border-white/10 bg-black/60 p-6 shadow-xl backdrop-blur-lg transition-all duration-300">
<div
className={`
absolute top-10 left-6 z-50 w-[350px]
rounded-xl border border-white/10 bg-black/60 p-6
shadow-xl backdrop-blur-lg
transform transition-all duration-300 ease-in-out
${isVisible ? 'opacity-100 translate-x-0' : 'opacity-0 -translate-x-6'}
`}
>
<div className="mb-4 flex items-center justify-between border-b border-white/10 pb-3">
<h3 className="text-xl font-medium text-white">Image Details</h3>
<button
onClick={onClose}
onClick={handleClose}
className="text-white/70 hover:text-white"
aria-label="Close info panel"
>
<X className="h-5 w-5" />
</button>

</div>

<div className="space-y-4 text-sm">
Expand Down Expand Up @@ -106,7 +129,7 @@ export const MediaInfoPanel: React.FC<MediaInfoPanelProps> = ({
<div className="min-w-0 flex-1">
<p className="text-xs text-white/50">Location</p>
{currentImage?.metadata?.latitude &&
currentImage?.metadata?.longitude ? (
currentImage?.metadata?.longitude ? (
<button
type="button"
onClick={handleLocationClick}
Expand Down
57 changes: 57 additions & 0 deletions frontend/src/components/ScrollToTopButton/ScrollToTopButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { useEffect, useState } from 'react';

type ScrollToTopButtonProps = {
scrollContainerRef?: React.RefObject<HTMLElement | null>;
threshold?: number;
};

export const ScrollToTopButton = ({
scrollContainerRef,
threshold = 300,
}: ScrollToTopButtonProps) => {
const [visible, setVisible] = useState(false);

useEffect(() => {
const scroller = scrollContainerRef?.current || window;

const handleScroll = () => {
const scrollTop =
scroller instanceof Window
? window.scrollY
: scroller.scrollTop;

setVisible(scrollTop > threshold);
};

scroller.addEventListener('scroll', handleScroll);
return () => scroller.removeEventListener('scroll', handleScroll);
}, [scrollContainerRef, threshold]);
Comment on lines +14 to +28
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Check initial scroll position on mount.

The button visibility is only updated on scroll events. If the page is already scrolled past the threshold when the component mounts (e.g., user navigates back via browser history), the button won't appear until the user scrolls again.

🔧 Proposed fix to check initial scroll position
  useEffect(() => {
    const scroller = scrollContainerRef?.current || window;

    const handleScroll = () => {
      const scrollTop =
        scroller instanceof Window
          ? window.scrollY
          : scroller.scrollTop;

      setVisible(scrollTop > threshold);
    };

+   // Check initial scroll position
+   handleScroll();
+
    scroller.addEventListener('scroll', handleScroll);
    return () => scroller.removeEventListener('scroll', handleScroll);
  }, [scrollContainerRef, threshold]);
🤖 Prompt for AI Agents
In @frontend/src/components/ScrollToTopButton/ScrollToTopButton.tsx around lines
14 - 28, The effect that attaches the scroll listener (useEffect) only updates
visibility on scroll; call the same handler once on mount (and whenever
scrollContainerRef or threshold changes) to set initial visibility immediately.
Locate the useEffect, the scroller/handleScroll function, and the setVisible
call, and invoke handleScroll() after registering the event listener (or before
returning the cleanup) so the component checks the current scrollTop against
threshold on mount and when dependencies change.


const scrollToTop = () => {
const scroller = scrollContainerRef?.current;

if (scroller) {
scroller.scrollTo({ top: 0, behavior: 'smooth' });
} else {
window.scrollTo({ top: 0, behavior: 'smooth' });
}
};

if (!visible) return null;

return (
<button
onClick={scrollToTop}
aria-label="Scroll to top"
className="
fixed top-35 right-15 z-50
rounded-full bg-primary p-3
text-white shadow-lg
transition-all duration-300
hover:scale-110 hover:shadow-xl
"
Comment on lines +46 to +52
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🌐 Web query:

Tailwind CSS v4 default spacing scale and position utilities top right

💡 Result:

  • Default spacing unit: 1 = 0.25rem (4px). The v4 spacing scale is derived from this and includes common values (name → rem → px): 0 → 0px; px → 1px; 0.5 → 0.125rem (2px); 1 → 0.25rem (4px); 1.5 → 0.375rem (6px); 2 → 0.5rem (8px); 2.5 → 0.625rem (10px); 3 → 0.75rem (12px); 3.5 → 0.875rem (14px); 4 → 1rem (16px); 5 → 1.25rem (20px); 6 → 1.5rem (24px); 7 → 1.75rem (28px); 8 → 2rem (32px); 9 → 2.25rem (36px); 10 → 2.5rem (40px); 11 → 2.75rem (44px); 12 → 3rem (48px); 14 → 3.5rem (56px); 16 → 4rem (64px); 20 → 5rem (80px); 24 → 6rem (96px); 28 → 7rem (112px); 32 → 8rem (128px); 36 → 9rem (144px); 40 → 10rem (160px); 44 → 11rem (176px); 48 → 12rem (192px); 52 → 13rem (208px); 56 → 14rem (224px); 60 → 15rem (240px); 64 → 16rem (256px); 72 → 18rem (288px); 80 → 20rem (320px); 96 → 24rem (384px). [1]

  • Position utilities (top/right/etc.): v4 provides top-, right-, inset-, inset-x/inset-y, negative variants, and arbitrary/custom-value forms (e.g. top-0, right-0 to pin to the corner; inset-[3px], inset-(--var) for custom/CSS-variable). Example to pin an absolutely positioned element to the top-right corner: class="absolute top-0 right-0". [2]

Sources:
[1] Tailwind docs — Customizing spacing / default spacing scale.
[2] Tailwind docs — top / right / bottom / left (position/inset utilities).


🏁 Script executed:

# Find Tailwind configuration in the repository
fd -e "tailwind.config" -o -e "tailwind.config.js" -o -e "tailwind.config.ts" -o -e "tailwind.config.json"

Repository: AOSSIE-Org/PictoPy

Length of output: 178


🏁 Script executed:

# Search for any Tailwind config references
rg -i "tailwind" --type json --type js --type ts -A 5 --max-count 20

Repository: AOSSIE-Org/PictoPy

Length of output: 11629


🏁 Script executed:

find frontend -name "tailwind.config*" -type f

Repository: AOSSIE-Org/PictoPy

Length of output: 44


🏁 Script executed:

# Check if there's any tailwind config in frontend directory
ls -la frontend/ | grep -i tailwind

Repository: AOSSIE-Org/PictoPy

Length of output: 44


🏁 Script executed:

# Search for any custom spacing configuration in the codebase
rg "spacing|top-35|right-15" frontend/ --type ts --type js --type json -B 2 -A 2

Repository: AOSSIE-Org/PictoPy

Length of output: 2063


🏁 Script executed:

# Check if there's a tailwind.config file anywhere in frontend or if it's using defaults
find frontend -type f \( -name "*.config.*" -o -name "tailwind.css" -o -name "globals.css" \) | head -20

Repository: AOSSIE-Org/PictoPy

Length of output: 135


🏁 Script executed:

# Check the App.css and index.css for any custom spacing definitions
cat frontend/src/App.css 2>/dev/null || echo "App.css not found"

Repository: AOSSIE-Org/PictoPy

Length of output: 7941


🏁 Script executed:

# Search for CSS custom properties or theme overrides
rg "@layer|theme|--spacing|spacing:" frontend/src --type css -B 2 -A 2

Repository: AOSSIE-Org/PictoPy

Length of output: 1700


Use valid Tailwind v4 spacing values or arbitrary value syntax for positioning.

top-35 and right-15 do not exist in Tailwind v4's default spacing scale (which includes only specific values: 0, px, 0.5, 1–12, 14, 16, 20, 24, 28, 32, 36, 40, 44, 48, 52, 56, 60, 64, 72, 80, 96). The project does not define custom spacing for these values. Use valid scale values like top-36 (9rem) or arbitrary values like top-[35px] and right-[15px] instead.

🤖 Prompt for AI Agents
In @frontend/src/components/ScrollToTopButton/ScrollToTopButton.tsx around lines
46 - 52, The Tailwind classes in ScrollToTopButton's className use invalid
spacing keys (top-35, right-15); update the positioning to use valid scale
values or arbitrary value syntax: replace top-35/right-15 in the className
string of the ScrollToTopButton component with either a nearest valid scale like
top-36/right-16 or explicit arbitrary values like top-[35px] and right-[15px] so
Tailwind v4 compiles correctly.

>
</button>
);
};
6 changes: 5 additions & 1 deletion frontend/src/pages/Home/Home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { fetchAllImages } from '@/api/api-functions';
import { RootState } from '@/app/store';
import { EmptyGalleryState } from '@/components/EmptyStates/EmptyGalleryState';
import { useMutationFeedback } from '@/hooks/useMutationFeedback';
import { ScrollToTopButton } from '@/components/ScrollToTopButton/ScrollToTopButton';

export const Home = () => {
const dispatch = useDispatch();
Expand Down Expand Up @@ -48,7 +49,7 @@ export const Home = () => {
const title =
isSearchActive && images.length > 0
? `Face Search Results (${images.length} found)`
: 'Image Gallery';
: 'Image Gallery';

return (
<div className="relative flex h-full flex-col pr-6">
Expand Down Expand Up @@ -78,6 +79,9 @@ export const Home = () => {
className="absolute top-0 right-0 h-full w-4"
/>
)}

<ScrollToTopButton scrollContainerRef={scrollableRef} />
</div>

);
};