Skip to content

Commit 0250f6a

Browse files
authored
Media Info Dialog #263 (#30)
2 parents 91ccb2a + ddaa4f6 commit 0250f6a

File tree

5 files changed

+229
-48
lines changed

5 files changed

+229
-48
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,3 +46,5 @@ yarn-debug.log*
4646
yarn-error.log*
4747
.eslintcache
4848
.vscode/settings.json
49+
/.vs
50+
/.vscode

src/components/Preview.tsx

Lines changed: 214 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import ChevronRightIcon from '@mui/icons-material/ChevronRight';
33
import CloseIcon from '@mui/icons-material/Close';
44
import FavoriteIcon from '@mui/icons-material/Favorite';
55
import HeartBrokenIcon from '@mui/icons-material/HeartBroken';
6+
import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined';
67
import ZoomInIcon from '@mui/icons-material/ZoomIn';
78
import {
89
Box,
@@ -12,6 +13,7 @@ import {
1213
Dialog,
1314
DialogActions,
1415
DialogContent,
16+
Divider,
1517
Tooltip,
1618
Typography,
1719
} from '@mui/material';
@@ -27,6 +29,12 @@ interface PreviewProps {
2729
dateCreated: string;
2830
mediaType?: string;
2931
isFavorite?: boolean;
32+
dateMediaTaken?: string;
33+
dateMediaCreated?: string;
34+
filename?: string;
35+
sizeInBytes?: number;
36+
width?: number;
37+
height?: number;
3038
};
3139
handlePrev: () => void;
3240
handleNext: () => void;
@@ -36,6 +44,56 @@ interface PreviewProps {
3644
handleSingleFavorites?: (id: string, actionAdd: boolean) => void;
3745
}
3846

47+
/** Format bytes → human-readable string (e.g. "3.2 MB") */
48+
const formatBytes = (bytes: number): string => {
49+
if (bytes < 1024) return `${bytes} B`;
50+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
51+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
52+
};
53+
54+
/** Format an ISO date string → readable local date+time */
55+
const formatDate = (iso: string): string => {
56+
try {
57+
return new Date(iso).toLocaleString(undefined, {
58+
year: 'numeric',
59+
month: 'long',
60+
day: 'numeric',
61+
hour: '2-digit',
62+
minute: '2-digit',
63+
});
64+
} catch {
65+
return iso;
66+
}
67+
};
68+
69+
// ── Info pane row ─────────────────────────────────────────────────────────────
70+
71+
interface InfoRowProps {
72+
label: string;
73+
value: string;
74+
}
75+
76+
const InfoRow = ({ label, value }: InfoRowProps) => (
77+
<Box sx={{ py: 1.5 }}>
78+
<Typography
79+
variant="caption"
80+
sx={{
81+
color: 'text.secondary',
82+
textTransform: 'uppercase',
83+
letterSpacing: '0.08em',
84+
fontSize: '0.68rem',
85+
}}
86+
>
87+
{label}
88+
</Typography>
89+
<Typography variant="body2" sx={{ mt: 0.3, wordBreak: 'break-all' }}>
90+
{value}
91+
</Typography>
92+
</Box>
93+
);
94+
95+
// ── Main component ─────────────────────────────────────────────────────────────
96+
3997
const Preview = ({
4098
isOpen,
4199
media,
@@ -51,9 +109,11 @@ const Preview = ({
51109
queryFn: () => getPhoto(media.id),
52110
});
53111

112+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
54113
const date = new Date(media.dateCreated).toDateString();
55114

56115
const [zoom, setZoom] = useState(false);
116+
const [infoOpen, setInfoOpen] = useState(false);
57117

58118
const handleZoomIn = () => {
59119
setZoom(true);
@@ -67,6 +127,11 @@ const Preview = ({
67127
handleSingleFavorites?.(media.id, !media.isFavorite);
68128
};
69129

130+
// Close info pane when media changes
131+
useEffect(() => {
132+
setInfoOpen(false);
133+
}, [media.id]);
134+
70135
useEffect(() => {
71136
const handleKeyLeft = (e: KeyboardEvent) => {
72137
if (e.key === 'ArrowLeft') {
@@ -89,6 +154,17 @@ const Preview = ({
89154
};
90155
}, [media]);
91156

157+
// Shared toolbar button style
158+
const toolbarBtnSx = {
159+
minWidth: 32,
160+
p: '4px',
161+
color: 'gray',
162+
'&:hover': {
163+
backgroundColor: 'transparent',
164+
color: 'currentColor',
165+
},
166+
};
167+
92168
return (
93169
<>
94170
{zoom && (
@@ -119,6 +195,7 @@ const Preview = ({
119195
</Container>
120196
)}
121197
<Dialog open={isOpen} onClose={onClose} fullScreen>
198+
{/* ── Top toolbar ── */}
122199
<Box
123200
sx={{
124201
width: '100%',
@@ -129,39 +206,9 @@ const Preview = ({
129206
gap: 0.5, // reduce space between icons
130207
}}
131208
>
132-
{media.mediaType !== 'video' && (
133-
<Button
134-
onClick={handleZoomIn}
135-
disableRipple
136-
sx={{
137-
minWidth: 32,
138-
p: '4px',
139-
color: 'gray',
140-
'&:hover': {
141-
backgroundColor: 'transparent',
142-
color: 'currentColor',
143-
},
144-
}}
145-
>
146-
<Tooltip title="Zoom In">
147-
<ZoomInIcon />
148-
</Tooltip>
149-
</Button>
150-
)}
209+
{/* HeartBroken / Favorite */}
151210
{handleSingleFavorites && (
152-
<Button
153-
onClick={handleFavorite}
154-
disableRipple
155-
sx={{
156-
minWidth: 32,
157-
p: '4px',
158-
color: 'gray',
159-
'&:hover': {
160-
backgroundColor: 'transparent',
161-
color: 'currentColor',
162-
},
163-
}}
164-
>
211+
<Button onClick={handleFavorite} disableRipple sx={toolbarBtnSx}>
165212
{media.isFavorite ? (
166213
<Tooltip title="Remove from Favorites">
167214
<HeartBrokenIcon />
@@ -173,31 +220,57 @@ const Preview = ({
173220
)}
174221
</Button>
175222
)}
223+
224+
{/* ── Info button (between HeartBroken and ZoomIn) ── */}
176225
<Button
177-
onClick={onClose}
226+
onClick={() => setInfoOpen((prev) => !prev)}
178227
disableRipple
179228
sx={{
180-
minWidth: 32,
181-
p: '4px',
182-
color: 'gray',
183-
'&:hover': {
184-
backgroundColor: 'transparent',
185-
color: 'currentColor',
186-
},
229+
...toolbarBtnSx,
230+
color: infoOpen ? 'primary.main' : 'gray',
187231
}}
188232
>
233+
<Tooltip title="Info">
234+
<InfoOutlinedIcon />
235+
</Tooltip>
236+
</Button>
237+
238+
{/* Zoom In */}
239+
{media.mediaType !== 'video' && (
240+
<Button onClick={handleZoomIn} disableRipple sx={toolbarBtnSx}>
241+
<Tooltip title="Zoom In">
242+
<ZoomInIcon />
243+
</Tooltip>
244+
</Button>
245+
)}
246+
247+
{/* Close */}
248+
<Button onClick={onClose} disableRipple sx={toolbarBtnSx}>
189249
<Tooltip title="Close Preview">
190250
<CloseIcon />
191251
</Tooltip>
192252
</Button>
193253
</Box>
194-
<DialogContent sx={{ p: 0 }}>
254+
255+
{/* ── Main content area ── */}
256+
<DialogContent
257+
sx={{
258+
p: 0,
259+
overflow: 'hidden',
260+
position: 'relative',
261+
display: 'flex',
262+
}}
263+
>
264+
{/* Media area — shrinks when info pane opens */}
195265
<Box
196266
sx={{
197267
display: 'flex',
198268
justifyContent: 'space-between',
199269
alignItems: 'center',
200270
height: '100%',
271+
flex: 1,
272+
minWidth: 0,
273+
transition: 'all 0.3s ease',
201274
}}
202275
>
203276
<Button
@@ -231,13 +304,15 @@ const Preview = ({
231304
height: '100%',
232305
display: 'flex',
233306
justifyContent: 'center',
307+
flex: 1,
308+
minWidth: 0,
234309
}}
235310
>
236311
{isLoading ? (
237312
<CircularProgress sx={{ my: 'auto' }} />
238313
) : (
239314
<Box
240-
onClick={handleZoomIn}
315+
onClick={media.mediaType !== 'video' ? handleZoomIn : undefined}
241316
sx={{
242317
display: 'flex',
243318
justifyContent: 'center',
@@ -302,6 +377,101 @@ const Preview = ({
302377
</Tooltip>
303378
</Button>
304379
</Box>
380+
381+
{/* ── Sliding Info Pane — absolutely positioned, slides in over the right edge ── */}
382+
<Box
383+
sx={{
384+
position: 'absolute',
385+
top: 0,
386+
right: 0,
387+
height: '100%',
388+
width: 300,
389+
transform: infoOpen ? 'translateX(0)' : 'translateX(100%)',
390+
transition: 'transform 0.3s ease',
391+
borderLeft: '1px solid',
392+
borderColor: 'divider',
393+
bgcolor: 'background.paper',
394+
boxSizing: 'border-box',
395+
px: 2,
396+
py: 2,
397+
overflowY: 'auto',
398+
overflowX: 'hidden',
399+
zIndex: 10,
400+
}}
401+
>
402+
{/* Pane header */}
403+
<Box
404+
sx={{
405+
display: 'flex',
406+
alignItems: 'center',
407+
justifyContent: 'space-between',
408+
mb: 1,
409+
}}
410+
>
411+
<Typography variant="subtitle1" fontWeight={600}>
412+
Info
413+
</Typography>
414+
<Button
415+
onClick={() => setInfoOpen(false)}
416+
disableRipple
417+
sx={{
418+
minWidth: 32,
419+
p: '4px',
420+
color: 'gray',
421+
'&:hover': { backgroundColor: 'transparent' },
422+
}}
423+
>
424+
<CloseIcon fontSize="small" />
425+
</Button>
426+
</Box>
427+
428+
<Divider sx={{ mb: 1 }} />
429+
430+
{/* Info rows — only rendered when data is present */}
431+
{media.filename && (
432+
<InfoRow label="Filename" value={media.filename} />
433+
)}
434+
{media.dateMediaTaken && (
435+
<>
436+
<InfoRow
437+
label="Date Taken"
438+
value={formatDate(media.dateMediaTaken)}
439+
/>
440+
<Divider />
441+
</>
442+
)}
443+
{media.dateMediaCreated && (
444+
<>
445+
<InfoRow
446+
label="Date Created"
447+
value={formatDate(media.dateMediaCreated)}
448+
/>
449+
<Divider />
450+
</>
451+
)}
452+
{(media.width || media.height) && (
453+
<>
454+
<InfoRow
455+
label="Dimensions"
456+
value={
457+
[
458+
media.width && `${media.width}`,
459+
media.height && `${media.height}`,
460+
]
461+
.filter(Boolean)
462+
.join(' × ') + ' px'
463+
}
464+
/>
465+
<Divider />
466+
</>
467+
)}
468+
{media.sizeInBytes != null && (
469+
<InfoRow
470+
label="File Size"
471+
value={formatBytes(media.sizeInBytes)}
472+
/>
473+
)}
474+
</Box>
305475
</DialogContent>
306476
<DialogActions>
307477
<Box
@@ -313,9 +483,9 @@ const Preview = ({
313483
px: 2,
314484
}}
315485
>
316-
<Typography color="text.secondary" fontSize={'small'}>
486+
{/* <Typography color="text.secondary" fontSize={'small'}>
317487
Date Created: {date}
318-
</Typography>
488+
</Typography> */}
319489
</Box>
320490
</DialogActions>
321491
</Dialog>

src/pages/SettingsPage.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { ChangePassword } from '../components/Users/ChangePassword';
22
import { DeleteAccount } from '../components/Users/DeleteAccount';
3+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
34
import { VideoConversionSettings } from '../components/Users/VideoConversionSettings';
45
import MainLayout from '../layout/MainLayout';
56

@@ -8,7 +9,7 @@ const SettingsPage = () => {
89
<MainLayout title="Settings">
910
<ChangePassword />
1011
<DeleteAccount />
11-
<VideoConversionSettings />
12+
{/* <VideoConversionSettings /> */}
1213
</MainLayout>
1314
);
1415
};

0 commit comments

Comments
 (0)