Skip to content
Merged
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
117 changes: 94 additions & 23 deletions frontend/src/App.css
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,12 @@
--transition-fast: 150ms ease-in-out;
--transition-normal: 250ms ease-in-out;
--transition-slow: 350ms ease-in-out;

/* Image View Layout */
--view-header-height: 50px;
--view-image-reserve: 55px;
--view-header-height-mobile: 40px;
--view-image-reserve-mobile: 45px;
}

/* Global Styles */
Expand Down Expand Up @@ -125,6 +131,64 @@ body {
border-bottom: 1px solid var(--border-medium);
}

/* Compact View Header for Image View */
.view-header-compact {
background: var(--bg-primary);
border-bottom: 1px solid var(--border-light);
box-shadow: var(--shadow-sm);
padding: var(--space-2) var(--space-4);
position: sticky;
top: 0;
z-index: 100;
}

.view-header-content {
max-width: 1200px;
margin: 0 auto;
display: grid;
grid-template-columns: auto 1fr auto;
align-items: center;
gap: var(--space-3);
}

.view-filename {
font-size: 0.875rem;
font-weight: 500;
color: var(--gray-800);
text-align: center;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
padding: 0 var(--space-2);
}

.view-user-info {
font-size: 0.75rem;
color: var(--gray-500);
white-space: nowrap;
}

/* Responsive design for compact header */
@media (max-width: 768px) {
.view-header-compact {
padding: var(--space-2) var(--space-3);
}

.view-header-content {
grid-template-columns: auto 1fr;
gap: var(--space-2);
}

.view-filename {
font-size: 0.8125rem;
text-align: left;
}

.view-user-info {
display: none;
}
}

.header-content {
max-width: 1200px;
margin: 0 auto;
Expand Down Expand Up @@ -1488,16 +1552,16 @@ ul li:hover {

.compact-classifications-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
align-items: center;
justify-content: center;
gap: var(--space-3);
}

.classifications-buttons {
display: flex;
flex-wrap: wrap;
gap: var(--space-2);
flex: 1;
justify-content: center;
}

.compact-class-btn {
Expand Down Expand Up @@ -1681,17 +1745,17 @@ ul li:hover {
/* Responsive adjustments for compact classifications */
@media (max-width: 768px) {
.compact-classifications-header {
flex-direction: column;
align-items: stretch;
flex-wrap: wrap;
justify-content: center;
gap: var(--space-2);
}

.classifications-buttons {
justify-content: center;
}

.compact-help-controls {
align-self: center;
margin-top: var(--space-2);
order: 2;
}

.help-sections {
Expand Down Expand Up @@ -1905,13 +1969,15 @@ ul li:hover {
#image-display {
display: flex;
justify-content: center;
align-items: center;
min-height: 400px;
align-items: flex-start;
Copy link

Copilot AI Sep 19, 2025

Choose a reason for hiding this comment

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

[nitpick] The hardcoded CSS variable --view-header-height (50px) creates tight coupling between the header height and image display calculation. If the header height changes, both the CSS variable and the calculation need to be updated. Consider using a more flexible approach or add a comment explaining this dependency.

Suggested change
align-items: flex-start;
align-items: flex-start;
/* The height calculation below depends on --view-header-height.
If you change the header height, update --view-header-height in :root accordingly. */

Copilot uses AI. Check for mistakes.

/* The height calculation below depends on --view-header-height.
If you change the header height, update --view-header-height in :root accordingly. */
height: calc(100vh - var(--view-header-height));
background: var(--bg-primary);
border: 1px solid var(--border-light);
border-radius: var(--radius-lg);
margin: var(--space-6) 0;
padding: var(--space-4);
margin: 0;
padding: var(--space-1);
box-shadow: var(--shadow-md);
overflow: auto;
position: relative;
Expand All @@ -1927,7 +1993,8 @@ ul li:hover {

#image-display .view-image {
max-width: 100%;
max-height: 80vh;
max-height: calc(100vh - var(--view-image-reserve));
width: auto;
height: auto;
border-radius: var(--radius-md);
box-shadow: var(--shadow-lg);
Expand Down Expand Up @@ -1960,24 +2027,26 @@ ul li:hover {
.image-navigation {
display: flex;
justify-content: center;
gap: var(--space-4);
margin: var(--space-6) 0;
gap: var(--space-2);
margin: var(--space-3) 0;
}

.navigation-btn {
min-width: 120px;
min-width: 80px;
padding: var(--space-2) var(--space-3);
font-size: 0.875rem;
}

/* Responsive adjustments for image display */
@media (max-width: 768px) {
#image-display {
min-height: 300px;
margin: var(--space-4) 0;
padding: var(--space-3);
height: calc(100vh - var(--view-header-height-mobile));
margin: 0;
padding: var(--space-1);
}

#image-display .view-image {
max-height: 60vh;
max-height: calc(100vh - var(--view-image-reserve-mobile));
}

.image-controls {
Expand All @@ -1991,7 +2060,9 @@ ul li:hover {
}

.navigation-btn {
min-width: 100px;
min-width: 70px;
padding: var(--space-2);
font-size: 0.8125rem;
}
}

Expand Down
75 changes: 33 additions & 42 deletions frontend/src/ImageView.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,10 +86,16 @@ function ImageView() {
throw new Error('Invalid server response: expected an array of images');
}

setProjectImages(images);

// Find the index of the current image
const index = images.findIndex(img => img.id === imageId);
// Sort images by date (newest first) to match the gallery default sorting
// Use spread operator to avoid mutating the original array
const sortedImages = [...images].sort((a, b) => {
return new Date(b.created_at || '1970-01-01') - new Date(a.created_at || '1970-01-01');
});

setProjectImages(sortedImages);

// Find the index of the current image in the sorted array
const index = sortedImages.findIndex(img => img.id === imageId);
setCurrentImageIndex(index);

} catch (error) {
Expand Down Expand Up @@ -195,25 +201,23 @@ function ImageView() {
}, [currentImageIndex, projectImages.length, navigateToNextImage, navigateToPreviousImage]);

return (
<div className="App" style={{ maxWidth: '100%', padding: '10px' }}>
<header className="App-header">
<div id="view-header">
<button
className="btn btn-secondary"
<div className="App" style={{ maxWidth: '100%', padding: '0' }}>
<header className="view-header-compact">
<div className="view-header-content">
<button
className="btn btn-secondary btn-small"
onClick={() => navigate(`/project/${projectId}`)}
>
&lt; Back to Project
Back
</button>
<h1>{image ? image.filename : 'Loading image...'}</h1>
<span className="view-filename">{image ? image.filename : 'Loading...'}</span>
{currentUser && (
<div className="user-info">
<span>Logged in as: {currentUser.email}</span>
</div>
<span className="view-user-info">{currentUser.email}</span>
)}
</div>
</header>

<div className="container" style={{ maxWidth: '100%' }}>
<div className="container" style={{ maxWidth: '100%', padding: 'var(--space-4)' }}>
{error && (
<div className="alert alert-error">
{error}
Expand All @@ -227,39 +231,26 @@ function ImageView() {
)}

<div className="image-view-container">
<div className="image-navigation">
<button
className="btn btn-secondary navigation-btn"
onClick={navigateToPreviousImage}
disabled={currentImageIndex <= 0}
>
&lt; Previous
</button>
<button
className="btn btn-secondary navigation-btn"
onClick={navigateToNextImage}
disabled={currentImageIndex >= projectImages.length - 1 || currentImageIndex === -1}
>
Next &gt;
</button>
</div>

{/* Compact classification buttons above image */}
<CompactImageClassifications
imageId={imageId}
classes={classes}
loading={loading}
setLoading={setLoading}
setError={setError}
<CompactImageClassifications
imageId={imageId}
classes={classes}
loading={loading}
setLoading={setLoading}
setError={setError}
/>
<ImageDisplay
imageId={imageId}
image={image}

<ImageDisplay
imageId={imageId}
image={image}
isTransitioning={isTransitioning}
projectId={projectId}
setImage={setImage}
refreshProjectImages={loadProjectImages}
navigateToPreviousImage={navigateToPreviousImage}
navigateToNextImage={navigateToNextImage}
currentImageIndex={currentImageIndex}
projectImages={projectImages}
/>

<ImageComments
Expand Down
5 changes: 4 additions & 1 deletion frontend/src/components/CompactImageClassifications.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ function CompactImageClassifications({ imageId, classes, loading, setLoading, se
const hotkeyMap = new Map();
const priorityKeys = ['a', 's', 'd', 'f', 'q', 'w', 'e', 'r']; // Home row + top row
const allKeys = 'abcdefghijklmnopqrstuvwxyz1234567890'.split('');


// Reserve 'h' for help functionality
usedKeys.add('h');

// First pass: try first letter of class name
classList.forEach(cls => {
const firstLetter = cls.name.toLowerCase().charAt(0);
Expand Down
Loading