redesign fileinfo completed

This commit is contained in:
Abhinav 2022-11-24 18:29:57 +05:30
parent 2edeeb0371
commit 7ea4a26e45
14 changed files with 363 additions and 182 deletions

10
src/components/Chip.tsx Normal file
View file

@ -0,0 +1,10 @@
import { Box, styled } from '@mui/material';
import { CSSProperties } from 'react';
export const Chip = styled(Box)(({ theme }) => ({
...(theme.typography.body2 as CSSProperties),
padding: '8px 12px',
borderRadius: '4px',
backgroundColor: theme.palette.fill.dark,
fontWeight: 'bold',
}));

View file

@ -0,0 +1,11 @@
import { Drawer } from '@mui/material';
import styled from 'styled-components';
export const EnteDrawer = styled(Drawer)(({ theme }) => ({
'& .MuiPaper-root': {
maxWidth: '375px',
width: '100%',
scrollbarWidth: 'thin',
padding: theme.spacing(1),
},
}));

View file

@ -31,6 +31,7 @@ import { CustomError } from 'utils/error';
import { User } from 'types/user'; import { User } from 'types/user';
import { getData, LS_KEYS } from 'utils/storage/localStorage'; import { getData, LS_KEYS } from 'utils/storage/localStorage';
import { useMemo } from 'react'; import { useMemo } from 'react';
import { Collection } from 'types/collection';
const Container = styled('div')` const Container = styled('div')`
display: block; display: block;
@ -49,6 +50,7 @@ const PHOTOSWIPE_HASH_SUFFIX = '&opened';
interface Props { interface Props {
files: EnteFile[]; files: EnteFile[];
collections?: Collection[];
syncWithRemote: () => Promise<void>; syncWithRemote: () => Promise<void>;
favItemIds?: Set<number>; favItemIds?: Set<number>;
archivedCollections?: Set<number>; archivedCollections?: Set<number>;
@ -76,6 +78,7 @@ type SourceURL = {
const PhotoFrame = ({ const PhotoFrame = ({
files, files,
collections,
syncWithRemote, syncWithRemote,
favItemIds, favItemIds,
archivedCollections, archivedCollections,
@ -184,6 +187,23 @@ const PhotoFrame = ({
}); });
}, [files, deletedFileIds, search, activeCollection]); }, [files, deletedFileIds, search, activeCollection]);
const fileToCollectionsMap = useMemo(() => {
const fileToCollectionsMap = new Map<number, number[]>();
files.forEach((file) => {
if (!fileToCollectionsMap.get(file.id)) {
fileToCollectionsMap.set(file.id, []);
}
fileToCollectionsMap.get(file.id).push(file.collectionID);
});
return fileToCollectionsMap;
}, [files]);
const collectionNameMap = useMemo(() => {
return new Map<number, string>(
collections.map((collection) => [collection.id, collection.name])
);
}, [collections]);
useEffect(() => { useEffect(() => {
const currentURL = new URL(window.location.href); const currentURL = new URL(window.location.href);
const end = currentURL.hash.lastIndexOf('&'); const end = currentURL.hash.lastIndexOf('&');
@ -600,6 +620,8 @@ const PhotoFrame = ({
isTrashCollection={activeCollection === TRASH_SECTION} isTrashCollection={activeCollection === TRASH_SECTION}
enableDownload={enableDownload} enableDownload={enableDownload}
isSourceLoaded={isSourceLoaded} isSourceLoaded={isSourceLoaded}
fileToCollectionsMap={fileToCollectionsMap}
collectionNameMap={collectionNameMap}
/> />
</Container> </Container>
)} )}

View file

@ -1,73 +1,70 @@
import React, { useState } from 'react'; import React from 'react';
import constants from 'utils/strings/constants'; import constants from 'utils/strings/constants';
import { RenderInfoItem } from './RenderInfoItem'; import { Stack, styled, Typography } from '@mui/material';
import { LegendContainer } from '../styledComponents/LegendContainer'; import { FileInfoSidebar } from '.';
import { Pre } from '../styledComponents/Pre'; import Titlebar from 'components/Titlebar';
import { import { Box } from '@mui/system';
Checkbox, import CopyButton from 'components/CodeBlock/CopyButton';
FormControlLabel,
FormGroup,
Typography,
} from '@mui/material';
export function ExifData(props: { exif: any }) { const ExifItem = styled(Box)`
const { exif } = props; padding-left: 8px;
const [showAll, setShowAll] = useState(false); padding-right: 8px;
display: flex;
flex-direction: column;
gap: 4px;
`;
const changeHandler = (e: React.ChangeEvent<HTMLInputElement>) => { function parseExifValue(value: any) {
setShowAll(e.target.checked); switch (typeof value) {
case 'string':
case 'number':
return value;
case 'object':
if (value instanceof Date) {
return value.toString();
}
break;
default:
return JSON.stringify(value);
}
}
export function ExifData(props: {
exif: any;
open: boolean;
onClose: () => void;
filename: string;
onInfoClose: () => void;
}) {
const { exif, open, onClose, filename, onInfoClose } = props;
if (!exif) {
return <></>;
}
const handleRootClose = () => {
onClose();
onInfoClose();
}; };
const renderAllValues = () => <Pre>{exif.raw}</Pre>;
const renderSelectedValues = () => (
<>
{exif?.Make &&
exif?.Model &&
RenderInfoItem(constants.DEVICE, `${exif.Make} ${exif.Model}`)}
{exif?.ImageWidth &&
exif?.ImageHeight &&
RenderInfoItem(
constants.IMAGE_SIZE,
`${exif.ImageWidth} x ${exif.ImageHeight}`
)}
{exif?.Flash && RenderInfoItem(constants.FLASH, exif.Flash)}
{exif?.FocalLength &&
RenderInfoItem(
constants.FOCAL_LENGTH,
exif.FocalLength.toString()
)}
{exif?.ApertureValue &&
RenderInfoItem(
constants.APERTURE,
exif.ApertureValue.toString()
)}
{exif?.ISOSpeedRatings &&
RenderInfoItem(constants.ISO, exif.ISOSpeedRatings.toString())}
</>
);
return ( return (
<> <FileInfoSidebar open={open} onClose={onClose}>
<LegendContainer> <Titlebar
<Typography variant="subtitle" mb={1}> onClose={onClose}
{constants.EXIF} title={constants.EXIF}
caption={filename}
onRootClose={handleRootClose}
actionButton={<CopyButton code={exif} color={'secondary'} />}
/>
<Stack py={3} px={1} spacing={2}>
{[...Object.entries(exif)].map(([key, value]) => (
<ExifItem key={key}>
<Typography variant="body2" color={'text.secondary'}>
{key}
</Typography> </Typography>
<FormGroup> <Typography>{parseExifValue(value)}</Typography>
<FormControlLabel </ExifItem>
control={ ))}
<Checkbox </Stack>
size="small" </FileInfoSidebar>
onChange={changeHandler}
color="accent"
/>
}
label={constants.SHOW_ALL}
/>
</FormGroup>
</LegendContainer>
{showAll ? renderAllValues() : renderSelectedValues()}
</>
); );
} }

View file

@ -26,27 +26,29 @@ export default function InfoItem({
children, children,
}: Iprops): JSX.Element { }: Iprops): JSX.Element {
return ( return (
<FlexWrapper height={48} justifyContent="space-between"> <FlexWrapper justifyContent="space-between">
<FlexWrapper gap={0.5} pr={1}> <Box display={'flex'} alignItems="flex-start" gap={0.5} pr={1}>
<IconButton <IconButton
color="secondary" color="secondary"
sx={{ '&&': { cursor: 'default' } }} sx={{ '&&': { cursor: 'default', m: 0.5 } }}
disableRipple> disableRipple>
{icon} {icon}
</IconButton> </IconButton>
<Box> <Box py={0.5}>
{children ? ( {children ? (
children children
) : ( ) : (
<> <>
<Typography>{title}</Typography> <Typography sx={{ wordBreak: 'break-all' }}>
{title}
</Typography>
<Typography variant="body2" color="text.secondary"> <Typography variant="body2" color="text.secondary">
{caption} {caption}
</Typography> </Typography>
</> </>
)} )}
</Box> </Box>
</FlexWrapper> </Box>
{customEndButton {customEndButton
? customEndButton ? customEndButton
: !hideEditOption && ( : !hideEditOption && (

View file

@ -1,4 +1,4 @@
import React, { useEffect, useState } from 'react'; import React, { useState } from 'react';
import { updateFilePublicMagicMetadata } from 'services/fileService'; import { updateFilePublicMagicMetadata } from 'services/fileService';
import { EnteFile } from 'types/file'; import { EnteFile } from 'types/file';
import { changeCaption, updateExistingFilePubMetadata } from 'utils/file'; import { changeCaption, updateExistingFilePubMetadata } from 'utils/file';
@ -35,9 +35,6 @@ export function RenderCaption({
setIsInEditMode(false); setIsInEditMode(false);
}; };
useEffect(() => {
console.log(isInEditMode);
}, [isInEditMode]);
const saveEdits = async (newCaption: string) => { const saveEdits = async (newCaption: string) => {
try { try {
if (file) { if (file) {

View file

@ -13,6 +13,7 @@ import { FILE_TYPE } from 'constants/file';
import { PhotoOutlined, VideoFileOutlined } from '@mui/icons-material'; import { PhotoOutlined, VideoFileOutlined } from '@mui/icons-material';
import InfoItem from './InfoItem'; import InfoItem from './InfoItem';
import { makeHumanReadableStorage } from 'utils/billing'; import { makeHumanReadableStorage } from 'utils/billing';
import Box from '@mui/material/Box';
const getFileTitle = (filename, extension) => { const getFileTitle = (filename, extension) => {
if (extension) { if (extension) {
@ -22,14 +23,14 @@ const getFileTitle = (filename, extension) => {
} }
}; };
const getCaption = (file: EnteFile, exif) => { const getCaption = (file: EnteFile, parsedExifData) => {
const cameraMP = exif?.['megaPixels']; const megaPixels = parsedExifData?.['megaPixels'];
const resolution = exif?.['resolution']; const resolution = parsedExifData?.['resolution'];
const fileSize = file.info?.fileSize; const fileSize = file.info?.fileSize;
const captionParts = []; const captionParts = [];
if (cameraMP) { if (megaPixels) {
captionParts.push(`${cameraMP} MP`); captionParts.push(megaPixels);
} }
if (resolution) { if (resolution) {
captionParts.push(resolution); captionParts.push(resolution);
@ -37,16 +38,22 @@ const getCaption = (file: EnteFile, exif) => {
if (fileSize) { if (fileSize) {
captionParts.push(makeHumanReadableStorage(fileSize)); captionParts.push(makeHumanReadableStorage(fileSize));
} }
return captionParts.join(' '); return (
<FlexWrapper gap={1}>
{captionParts.map((caption) => (
<Box key={caption}> {caption}</Box>
))}
</FlexWrapper>
);
}; };
export function RenderFileName({ export function RenderFileName({
exif, parsedExifData,
shouldDisableEdits, shouldDisableEdits,
file, file,
scheduleUpdate, scheduleUpdate,
}: { }: {
exif: Record<string, any>; parsedExifData: Record<string, any>;
shouldDisableEdits: boolean; shouldDisableEdits: boolean;
file: EnteFile; file: EnteFile;
scheduleUpdate: () => void; scheduleUpdate: () => void;
@ -93,9 +100,8 @@ export function RenderFileName({
) )
} }
title={getFileTitle(filename, extension)} title={getFileTitle(filename, extension)}
caption={getCaption(file, exif)} caption={getCaption(file, parsedExifData)}
openEditor={openEditMode} openEditor={openEditMode}
loading={false}
hideEditOption={shouldDisableEdits || isInEditMode} hideEditOption={shouldDisableEdits || isInEditMode}
/> />
) : ( ) : (

View file

@ -1,72 +1,100 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import constants from 'utils/strings/constants'; import constants from 'utils/strings/constants';
import { RenderFileName } from './RenderFileName'; import { RenderFileName } from './RenderFileName';
// import { ExifData } from './ExifData';
import { RenderCreationTime } from './RenderCreationTime'; import { RenderCreationTime } from './RenderCreationTime';
import { DialogProps, Drawer, Link, Stack, styled } from '@mui/material'; import { Box, DialogProps, Link, Stack, styled } from '@mui/material';
import { Location, Metadata } from 'types/upload'; import { Location } from 'types/upload';
import Photoswipe from 'photoswipe';
import { getEXIFLocation } from 'services/upload/exifService'; import { getEXIFLocation } from 'services/upload/exifService';
import { RenderCaption } from './RenderCaption'; import { RenderCaption } from './RenderCaption';
import { import {
BackupOutlined, BackupOutlined,
CameraOutlined,
FolderOutlined, FolderOutlined,
LocationOnOutlined, LocationOnOutlined,
TextSnippetOutlined, TextSnippetOutlined,
} from '@mui/icons-material'; } from '@mui/icons-material';
import CopyButton from 'components/CodeBlock/CopyButton'; import CopyButton from 'components/CodeBlock/CopyButton';
import { formatDateTime } from 'utils/time'; import { formatDateMedium, formatTime } from 'utils/time';
import { Badge } from 'components/Badge';
import Titlebar from 'components/Titlebar'; import Titlebar from 'components/Titlebar';
import InfoItem from './InfoItem'; import InfoItem from './InfoItem';
import { FlexWrapper } from 'components/Container';
import EnteSpinner from 'components/EnteSpinner';
import { EnteFile } from 'types/file';
import { Chip } from 'components/Chip';
import LinkButton from 'components/pages/gallery/LinkButton';
import { ExifData } from './ExifData';
import { EnteDrawer } from 'components/EnteDrawer';
const FileInfoSidebar = styled((props: DialogProps) => ( export const FileInfoSidebar = styled((props: DialogProps) => (
<Drawer {...props} anchor="right" /> <EnteDrawer {...props} anchor="right" />
))(({ theme }) => ({ ))({
zIndex: 1501, zIndex: 1501,
'& .MuiPaper-root': { '& .MuiPaper-root': {
maxWidth: '375px', padding: 8,
width: '100%',
scrollbarWidth: 'thin',
padding: theme.spacing(1),
}, },
})); });
interface Iprops { interface Iprops {
shouldDisableEdits: boolean; shouldDisableEdits: boolean;
showInfo: boolean; showInfo: boolean;
handleCloseInfo: () => void; handleCloseInfo: () => void;
items: any[]; file: EnteFile;
photoSwipe: Photoswipe<Photoswipe.Options>;
metadata: Metadata;
exif: any; exif: any;
scheduleUpdate: () => void; scheduleUpdate: () => void;
refreshPhotoswipe: () => void; refreshPhotoswipe: () => void;
fileToCollectionsMap: Map<number, number[]>;
collectionNameMap: Map<number, string>;
}
function BasicDeviceCamera({
parsedExifData,
}: {
parsedExifData: Record<string, any>;
}) {
return (
<FlexWrapper gap={1}>
<Box>{parsedExifData['fNumber']}</Box>
<Box>{parsedExifData['exposureTime']}</Box>
<Box>{parsedExifData['ISO']}</Box>
</FlexWrapper>
);
}
function getOpenStreetMapLink(location: {
latitude: number;
longitude: number;
}) {
return `https://www.openstreetmap.org/?mlat=${location.latitude}&mlon=${location.longitude}#map=15/${location.latitude}/${location.longitude}`;
} }
export function FileInfo({ export function FileInfo({
shouldDisableEdits, shouldDisableEdits,
showInfo, showInfo,
handleCloseInfo, handleCloseInfo,
items, file,
photoSwipe,
metadata,
exif, exif,
scheduleUpdate, scheduleUpdate,
refreshPhotoswipe, refreshPhotoswipe,
fileToCollectionsMap,
collectionNameMap,
}: Iprops) { }: Iprops) {
const [location, setLocation] = useState<Location>(null); const [location, setLocation] = useState<Location>(null);
const [parsedExifData, setParsedExifData] = useState<Record<string, any>>();
const [showExif, setShowExif] = useState(false);
const openExif = () => setShowExif(true);
const closeExif = () => setShowExif(false);
useEffect(() => { useEffect(() => {
if (!location && metadata) { if (!location && file && file.metadata) {
if (metadata.longitude || metadata.longitude === 0) { if (file.metadata.longitude || file.metadata.longitude === 0) {
setLocation({ setLocation({
latitude: metadata.latitude, latitude: file.metadata.latitude,
longitude: metadata.longitude, longitude: file.metadata.longitude,
}); });
} }
} }
}, [metadata]); }, [file]);
useEffect(() => { useEffect(() => {
if (!location && exif) { if (!location && exif) {
@ -77,80 +105,175 @@ export function FileInfo({
} }
}, [exif]); }, [exif]);
if (!metadata) { useEffect(() => {
if (!exif) {
return;
}
const parsedExifData = {};
if (exif['fNumber']) {
parsedExifData['fNumber'] = `f/${Math.ceil(exif['FNumber'])}`;
} else if (exif['ApertureValue'] && exif['FocalLength']) {
parsedExifData['fNumber'] = `f/${Math.ceil(
exif['FocalLength'] / exif['ApertureValue']
)}`;
}
const imageWidth = exif['ImageWidth'] ?? exif['ExifImageWidth'];
const imageHeight = exif['ImageHeight'] ?? exif['ExifImageHeight'];
if (imageWidth && imageHeight) {
parsedExifData['resolution'] = `${imageWidth} x ${imageHeight}`;
parsedExifData['megaPixels'] = `${Math.round(
(imageWidth * imageHeight) / 1000000
)}MP`;
}
if (exif['Make'] && exif['Model']) {
parsedExifData[
'takenOnDevice'
] = `${exif['Make']} ${exif['Model']}`;
}
if (exif['ExposureTime']) {
parsedExifData['exposureTime'] = exif['ExposureTime'];
}
if (exif['ISO']) {
parsedExifData['ISO'] = `ISO${exif['ISO']}`;
}
setParsedExifData(parsedExifData);
}, [exif]);
if (!file) {
return <></>; return <></>;
} }
return ( return (
<FileInfoSidebar open={showInfo} onClose={handleCloseInfo}> <FileInfoSidebar open={showInfo} onClose={handleCloseInfo}>
<Titlebar onClose={handleCloseInfo} title={constants.INFO} /> <Titlebar
onClose={handleCloseInfo}
title={constants.INFO}
backIsClose
/>
<Stack pt={1} pb={3} spacing={'20px'}> <Stack pt={1} pb={3} spacing={'20px'}>
<RenderCaption <RenderCaption
shouldDisableEdits={shouldDisableEdits} shouldDisableEdits={shouldDisableEdits}
file={items[photoSwipe?.getCurrentIndex()]} file={file}
scheduleUpdate={scheduleUpdate} scheduleUpdate={scheduleUpdate}
refreshPhotoswipe={refreshPhotoswipe} refreshPhotoswipe={refreshPhotoswipe}
/> />
<RenderCreationTime <RenderCreationTime
shouldDisableEdits={shouldDisableEdits} shouldDisableEdits={shouldDisableEdits}
file={items[photoSwipe?.getCurrentIndex()]} file={file}
scheduleUpdate={scheduleUpdate} scheduleUpdate={scheduleUpdate}
/> />
<RenderFileName <RenderFileName
exif={exif} parsedExifData={parsedExifData}
shouldDisableEdits={shouldDisableEdits} shouldDisableEdits={shouldDisableEdits}
file={items[photoSwipe?.getCurrentIndex()]} file={file}
scheduleUpdate={scheduleUpdate} scheduleUpdate={scheduleUpdate}
/> />
{parsedExifData && parsedExifData['takenOnDevice'] && (
<InfoItem
icon={<CameraOutlined />}
title={parsedExifData['takenOnDevice']}
caption={
<BasicDeviceCamera
parsedExifData={parsedExifData}
/>
}
hideEditOption
/>
)}
{location && ( {/* {location && ( */}
<InfoItem <InfoItem
icon={<LocationOnOutlined />} icon={<LocationOnOutlined />}
title={constants.LOCATION} title={constants.LOCATION}
caption={ caption={
<Link <Link
href={`https://www.openstreetmap.org/?mlat=${metadata.latitude}&mlon=${metadata.longitude}#map=15/${metadata.latitude}/${metadata.longitude}`}> href={getOpenStreetMapLink({
latitude: file.metadata.latitude,
longitude: file.metadata.longitude,
})}
target="_blank">
{constants.SHOW_ON_MAP} {constants.SHOW_ON_MAP}
</Link> </Link>
} }
customEndButton={ customEndButton={
<CopyButton <CopyButton
code={`https://www.openstreetmap.org/?mlat=${metadata.latitude}&mlon=${metadata.longitude}#map=15/${metadata.latitude}/${metadata.longitude}`} code={getOpenStreetMapLink({
latitude: file.metadata.latitude,
longitude: file.metadata.longitude,
})}
color="secondary" color="secondary"
size="medium" size="medium"
/> />
} }
/> />
)} {/* )} */}
<InfoItem <InfoItem
icon={<TextSnippetOutlined />} icon={<TextSnippetOutlined />}
title={constants.DETAILS} title={constants.DETAILS}
caption={constants.VIEW_EXIF} caption={
typeof exif === 'undefined' ? (
<EnteSpinner size={11.33} />
) : exif !== null ? (
<LinkButton
onClick={openExif}
sx={{
textDecoration: 'none',
color: 'text.secondary',
}}>
{constants.VIEW_EXIF}
</LinkButton>
) : (
constants.NO_EXIF
)
}
hideEditOption hideEditOption
/> />
<InfoItem <InfoItem
icon={<BackupOutlined />} icon={<BackupOutlined />}
title={formatDateTime(metadata.modificationTime / 1000)} title={formatDateMedium(
caption={formatDateTime(metadata.modificationTime / 1000)} file.metadata.modificationTime / 1000
)}
caption={formatTime(file.metadata.modificationTime / 1000)}
hideEditOption hideEditOption
/> />
<InfoItem icon={<FolderOutlined />} hideEditOption> <InfoItem icon={<FolderOutlined />} hideEditOption>
<Stack spacing={1} direction="row"> <Box
<Badge>abc</Badge> display={'flex'}
<Badge>DEF</Badge> gap={1}
<Badge>GHI</Badge> flexWrap="wrap"
</Stack> justifyContent={'flex-start'}
</InfoItem> alignItems={'flex-start'}>
{fileToCollectionsMap
{/* {exif && ( .get(file.id)
.map((collectionID) => (
<> <>
<ExifData exif={exif} /> <Chip key={collectionID}>
{collectionNameMap.get(collectionID)}
</Chip>
<Chip key={collectionID}>
{collectionNameMap.get(collectionID)}
</Chip>
<Chip key={collectionID}>
{collectionNameMap.get(collectionID)}
</Chip>
<Chip key={collectionID}>
{collectionNameMap.get(collectionID)}
</Chip>
</> </>
)} */} ))}
</Box>
</InfoItem>
</Stack> </Stack>
<ExifData
exif={exif}
open={showExif}
onClose={closeExif}
onInfoClose={handleCloseInfo}
filename={file.metadata.title}
/>
</FileInfoSidebar> </FileInfoSidebar>
); );
} }

View file

@ -10,7 +10,6 @@ import { EnteFile } from 'types/file';
import constants from 'utils/strings/constants'; import constants from 'utils/strings/constants';
import exifr from 'exifr'; import exifr from 'exifr';
import { downloadFile } from 'utils/file'; import { downloadFile } from 'utils/file';
import { prettyPrintExif } from 'utils/exif';
import { livePhotoBtnHTML } from 'components/LivePhotoBtn'; import { livePhotoBtnHTML } from 'components/LivePhotoBtn';
import { logError } from 'utils/sentry'; import { logError } from 'utils/sentry';
@ -61,6 +60,8 @@ interface Iprops {
isTrashCollection: boolean; isTrashCollection: boolean;
enableDownload: boolean; enableDownload: boolean;
isSourceLoaded: boolean; isSourceLoaded: boolean;
fileToCollectionsMap: Map<number, number[]>;
collectionNameMap: Map<number, string>;
} }
function PhotoViewer(props: Iprops) { function PhotoViewer(props: Iprops) {
@ -71,7 +72,6 @@ function PhotoViewer(props: Iprops) {
const { isOpen, items, isSourceLoaded } = props; const { isOpen, items, isSourceLoaded } = props;
const [isFav, setIsFav] = useState(false); const [isFav, setIsFav] = useState(false);
const [showInfo, setShowInfo] = useState(false); const [showInfo, setShowInfo] = useState(false);
const [metadata, setMetaData] = useState<EnteFile['metadata']>(null);
const [exif, setExif] = useState<any>(null); const [exif, setExif] = useState<any>(null);
const [livePhotoBtnOptions, setLivePhotoBtnOptions] = useState( const [livePhotoBtnOptions, setLivePhotoBtnOptions] = useState(
defaultLivePhotoDefaultOptions defaultLivePhotoDefaultOptions
@ -318,8 +318,10 @@ function PhotoViewer(props: Iprops) {
} }
}; };
const checkExifAvailable = async () => { const checkExifAvailable = async (force?: boolean) => {
setExif(null); if (exif || !force) {
return;
}
await sleep(100); await sleep(100);
try { try {
const img: HTMLImageElement = document.querySelector( const img: HTMLImageElement = document.querySelector(
@ -327,11 +329,11 @@ function PhotoViewer(props: Iprops) {
); );
if (img) { if (img) {
const exifData = await exifr.parse(img); const exifData = await exifr.parse(img);
if (!exifData) { if (exifData) {
return;
}
exifData.raw = prettyPrintExif(exifData);
setExif(exifData); setExif(exifData);
} else {
setExif(null);
}
} }
} catch (e) { } catch (e) {
logError(e, 'exifr parsing failed'); logError(e, 'exifr parsing failed');
@ -340,10 +342,9 @@ function PhotoViewer(props: Iprops) {
function updateInfo() { function updateInfo() {
const file: EnteFile = this?.currItem; const file: EnteFile = this?.currItem;
if (file?.metadata) { if (file) {
setMetaData(file.metadata); setExif(undefined);
setExif(null); checkExifAvailable(true);
checkExifAvailable();
} }
} }
@ -493,12 +494,12 @@ function PhotoViewer(props: Iprops) {
shouldDisableEdits={props.isSharedCollection} shouldDisableEdits={props.isSharedCollection}
showInfo={showInfo} showInfo={showInfo}
handleCloseInfo={handleCloseInfo} handleCloseInfo={handleCloseInfo}
items={items} file={photoSwipe?.currItem as EnteFile}
photoSwipe={photoSwipe}
metadata={metadata}
exif={exif} exif={exif}
scheduleUpdate={scheduleUpdate} scheduleUpdate={scheduleUpdate}
refreshPhotoswipe={refreshPhotoswipe} refreshPhotoswipe={refreshPhotoswipe}
fileToCollectionsMap={props.fileToCollectionsMap}
collectionNameMap={props.collectionNameMap}
/> />
</> </>
); );

View file

@ -1,11 +1,9 @@
import { Drawer, styled } from '@mui/material'; import { styled } from '@mui/material';
import CircleIcon from '@mui/icons-material/Circle'; import CircleIcon from '@mui/icons-material/Circle';
import { EnteDrawer } from 'components/EnteDrawer';
export const DrawerSidebar = styled(Drawer)(({ theme }) => ({ export const DrawerSidebar = styled(EnteDrawer)(({ theme }) => ({
'& .MuiPaper-root': { '& .MuiPaper-root': {
maxWidth: '375px',
width: '100%',
scrollbarWidth: 'thin',
padding: theme.spacing(1.5), padding: theme.spacing(1.5),
}, },
})); }));

View file

@ -1,30 +1,55 @@
import { Close } from '@mui/icons-material'; import { ArrowBack, Close } from '@mui/icons-material';
import { Box, IconButton, Typography } from '@mui/material'; import { Box, IconButton, Typography } from '@mui/material';
import React from 'react'; import React from 'react';
import { FlexWrapper } from './Container';
interface Iprops { interface Iprops {
title: string; title: string;
caption?: string; caption?: string;
onClose: () => void; onClose: () => void;
backIsClose?: boolean;
onRootClose?: () => void;
actionButton?: JSX.Element;
} }
export default function Titlebar({ export default function Titlebar({
title, title,
caption, caption,
onClose, onClose,
backIsClose,
actionButton,
onRootClose,
}: Iprops): JSX.Element { }: Iprops): JSX.Element {
return ( return (
<> <>
<Box display={'flex'} height={48} alignItems={'center'}> <FlexWrapper
<IconButton onClick={onClose} color="secondary"> height={48}
alignItems={'center'}
justifyContent="space-between">
<IconButton
onClick={onClose}
color={backIsClose ? 'secondary' : 'primary'}>
{backIsClose ? <Close /> : <ArrowBack />}
</IconButton>
<Box display={'flex'} gap="4px">
{actionButton && actionButton}
{!backIsClose && (
<IconButton onClick={onRootClose} color={'secondary'}>
<Close /> <Close />
</IconButton> </IconButton>
)}
</Box> </Box>
</FlexWrapper>
<Box py={0.5} px={2} height={54}> <Box py={0.5} px={2} height={54}>
<Typography variant="h3" fontWeight={'bold'}> <Typography variant="h3" fontWeight={'bold'}>
{title} {title}
</Typography> </Typography>
<Typography variant="body2">{caption}</Typography> <Typography
variant="body2"
color="text.secondary"
sx={{ wordBreak: 'break-all' }}>
{caption}
</Typography>
</Box> </Box>
</> </>
); );

View file

@ -666,6 +666,7 @@ export default function Gallery() {
/> />
<PhotoFrame <PhotoFrame
files={files} files={files}
collections={collections}
syncWithRemote={syncWithRemote} syncWithRemote={syncWithRemote}
favItemIds={favItemIds} favItemIds={favItemIds}
archivedCollections={archivedCollections} archivedCollections={archivedCollections}

View file

@ -1,13 +0,0 @@
export function prettyPrintExif(exifData: Object) {
let strPretty = '';
for (const [tagName, tagValue] of Object.entries(exifData)) {
if (tagValue instanceof Uint8Array) {
strPretty += tagName + ' : ' + '[' + tagValue + ']' + '\r\n';
} else if (tagValue instanceof Date) {
strPretty += tagName + ' : ' + tagValue.toDateString() + '\r\n';
} else {
strPretty += tagName + ' : ' + tagValue + '\r\n';
}
}
return strPretty;
}

View file

@ -443,6 +443,7 @@ const englishConstants = {
SHOW_ON_MAP: 'View on OpenStreetMap', SHOW_ON_MAP: 'View on OpenStreetMap',
DETAILS: 'Details', DETAILS: 'Details',
VIEW_EXIF: 'View all EXIF data', VIEW_EXIF: 'View all EXIF data',
NO_EXIF: 'No EXIF data',
EXIF: 'Exif', EXIF: 'Exif',
DEVICE: 'Device', DEVICE: 'Device',
IMAGE_SIZE: 'Image size', IMAGE_SIZE: 'Image size',