redesign fileinfo completed
This commit is contained in:
parent
2edeeb0371
commit
7ea4a26e45
10
src/components/Chip.tsx
Normal file
10
src/components/Chip.tsx
Normal 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',
|
||||
}));
|
11
src/components/EnteDrawer.tsx
Normal file
11
src/components/EnteDrawer.tsx
Normal 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),
|
||||
},
|
||||
}));
|
|
@ -31,6 +31,7 @@ import { CustomError } from 'utils/error';
|
|||
import { User } from 'types/user';
|
||||
import { getData, LS_KEYS } from 'utils/storage/localStorage';
|
||||
import { useMemo } from 'react';
|
||||
import { Collection } from 'types/collection';
|
||||
|
||||
const Container = styled('div')`
|
||||
display: block;
|
||||
|
@ -49,6 +50,7 @@ const PHOTOSWIPE_HASH_SUFFIX = '&opened';
|
|||
|
||||
interface Props {
|
||||
files: EnteFile[];
|
||||
collections?: Collection[];
|
||||
syncWithRemote: () => Promise<void>;
|
||||
favItemIds?: Set<number>;
|
||||
archivedCollections?: Set<number>;
|
||||
|
@ -76,6 +78,7 @@ type SourceURL = {
|
|||
|
||||
const PhotoFrame = ({
|
||||
files,
|
||||
collections,
|
||||
syncWithRemote,
|
||||
favItemIds,
|
||||
archivedCollections,
|
||||
|
@ -184,6 +187,23 @@ const PhotoFrame = ({
|
|||
});
|
||||
}, [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(() => {
|
||||
const currentURL = new URL(window.location.href);
|
||||
const end = currentURL.hash.lastIndexOf('&');
|
||||
|
@ -600,6 +620,8 @@ const PhotoFrame = ({
|
|||
isTrashCollection={activeCollection === TRASH_SECTION}
|
||||
enableDownload={enableDownload}
|
||||
isSourceLoaded={isSourceLoaded}
|
||||
fileToCollectionsMap={fileToCollectionsMap}
|
||||
collectionNameMap={collectionNameMap}
|
||||
/>
|
||||
</Container>
|
||||
)}
|
||||
|
|
|
@ -1,73 +1,70 @@
|
|||
import React, { useState } from 'react';
|
||||
import React from 'react';
|
||||
import constants from 'utils/strings/constants';
|
||||
|
||||
import { RenderInfoItem } from './RenderInfoItem';
|
||||
import { LegendContainer } from '../styledComponents/LegendContainer';
|
||||
import { Pre } from '../styledComponents/Pre';
|
||||
import {
|
||||
Checkbox,
|
||||
FormControlLabel,
|
||||
FormGroup,
|
||||
Typography,
|
||||
} from '@mui/material';
|
||||
import { Stack, styled, Typography } from '@mui/material';
|
||||
import { FileInfoSidebar } from '.';
|
||||
import Titlebar from 'components/Titlebar';
|
||||
import { Box } from '@mui/system';
|
||||
import CopyButton from 'components/CodeBlock/CopyButton';
|
||||
|
||||
export function ExifData(props: { exif: any }) {
|
||||
const { exif } = props;
|
||||
const [showAll, setShowAll] = useState(false);
|
||||
const ExifItem = styled(Box)`
|
||||
padding-left: 8px;
|
||||
padding-right: 8px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
`;
|
||||
|
||||
const changeHandler = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setShowAll(e.target.checked);
|
||||
function parseExifValue(value: any) {
|
||||
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 (
|
||||
<>
|
||||
<LegendContainer>
|
||||
<Typography variant="subtitle" mb={1}>
|
||||
{constants.EXIF}
|
||||
<FileInfoSidebar open={open} onClose={onClose}>
|
||||
<Titlebar
|
||||
onClose={onClose}
|
||||
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>
|
||||
<FormGroup>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
size="small"
|
||||
onChange={changeHandler}
|
||||
color="accent"
|
||||
/>
|
||||
}
|
||||
label={constants.SHOW_ALL}
|
||||
/>
|
||||
</FormGroup>
|
||||
</LegendContainer>
|
||||
{showAll ? renderAllValues() : renderSelectedValues()}
|
||||
</>
|
||||
<Typography>{parseExifValue(value)}</Typography>
|
||||
</ExifItem>
|
||||
))}
|
||||
</Stack>
|
||||
</FileInfoSidebar>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -26,27 +26,29 @@ export default function InfoItem({
|
|||
children,
|
||||
}: Iprops): JSX.Element {
|
||||
return (
|
||||
<FlexWrapper height={48} justifyContent="space-between">
|
||||
<FlexWrapper gap={0.5} pr={1}>
|
||||
<FlexWrapper justifyContent="space-between">
|
||||
<Box display={'flex'} alignItems="flex-start" gap={0.5} pr={1}>
|
||||
<IconButton
|
||||
color="secondary"
|
||||
sx={{ '&&': { cursor: 'default' } }}
|
||||
sx={{ '&&': { cursor: 'default', m: 0.5 } }}
|
||||
disableRipple>
|
||||
{icon}
|
||||
</IconButton>
|
||||
<Box>
|
||||
<Box py={0.5}>
|
||||
{children ? (
|
||||
children
|
||||
) : (
|
||||
<>
|
||||
<Typography>{title}</Typography>
|
||||
<Typography sx={{ wordBreak: 'break-all' }}>
|
||||
{title}
|
||||
</Typography>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
{caption}
|
||||
</Typography>
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
</FlexWrapper>
|
||||
</Box>
|
||||
{customEndButton
|
||||
? customEndButton
|
||||
: !hideEditOption && (
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import { updateFilePublicMagicMetadata } from 'services/fileService';
|
||||
import { EnteFile } from 'types/file';
|
||||
import { changeCaption, updateExistingFilePubMetadata } from 'utils/file';
|
||||
|
@ -35,9 +35,6 @@ export function RenderCaption({
|
|||
setIsInEditMode(false);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
console.log(isInEditMode);
|
||||
}, [isInEditMode]);
|
||||
const saveEdits = async (newCaption: string) => {
|
||||
try {
|
||||
if (file) {
|
||||
|
|
|
@ -13,6 +13,7 @@ import { FILE_TYPE } from 'constants/file';
|
|||
import { PhotoOutlined, VideoFileOutlined } from '@mui/icons-material';
|
||||
import InfoItem from './InfoItem';
|
||||
import { makeHumanReadableStorage } from 'utils/billing';
|
||||
import Box from '@mui/material/Box';
|
||||
|
||||
const getFileTitle = (filename, extension) => {
|
||||
if (extension) {
|
||||
|
@ -22,14 +23,14 @@ const getFileTitle = (filename, extension) => {
|
|||
}
|
||||
};
|
||||
|
||||
const getCaption = (file: EnteFile, exif) => {
|
||||
const cameraMP = exif?.['megaPixels'];
|
||||
const resolution = exif?.['resolution'];
|
||||
const getCaption = (file: EnteFile, parsedExifData) => {
|
||||
const megaPixels = parsedExifData?.['megaPixels'];
|
||||
const resolution = parsedExifData?.['resolution'];
|
||||
const fileSize = file.info?.fileSize;
|
||||
|
||||
const captionParts = [];
|
||||
if (cameraMP) {
|
||||
captionParts.push(`${cameraMP} MP`);
|
||||
if (megaPixels) {
|
||||
captionParts.push(megaPixels);
|
||||
}
|
||||
if (resolution) {
|
||||
captionParts.push(resolution);
|
||||
|
@ -37,16 +38,22 @@ const getCaption = (file: EnteFile, exif) => {
|
|||
if (fileSize) {
|
||||
captionParts.push(makeHumanReadableStorage(fileSize));
|
||||
}
|
||||
return captionParts.join(' ');
|
||||
return (
|
||||
<FlexWrapper gap={1}>
|
||||
{captionParts.map((caption) => (
|
||||
<Box key={caption}> {caption}</Box>
|
||||
))}
|
||||
</FlexWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export function RenderFileName({
|
||||
exif,
|
||||
parsedExifData,
|
||||
shouldDisableEdits,
|
||||
file,
|
||||
scheduleUpdate,
|
||||
}: {
|
||||
exif: Record<string, any>;
|
||||
parsedExifData: Record<string, any>;
|
||||
shouldDisableEdits: boolean;
|
||||
file: EnteFile;
|
||||
scheduleUpdate: () => void;
|
||||
|
@ -93,9 +100,8 @@ export function RenderFileName({
|
|||
)
|
||||
}
|
||||
title={getFileTitle(filename, extension)}
|
||||
caption={getCaption(file, exif)}
|
||||
caption={getCaption(file, parsedExifData)}
|
||||
openEditor={openEditMode}
|
||||
loading={false}
|
||||
hideEditOption={shouldDisableEdits || isInEditMode}
|
||||
/>
|
||||
) : (
|
||||
|
|
|
@ -1,72 +1,100 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import constants from 'utils/strings/constants';
|
||||
import { RenderFileName } from './RenderFileName';
|
||||
// import { ExifData } from './ExifData';
|
||||
import { RenderCreationTime } from './RenderCreationTime';
|
||||
import { DialogProps, Drawer, Link, Stack, styled } from '@mui/material';
|
||||
import { Location, Metadata } from 'types/upload';
|
||||
import Photoswipe from 'photoswipe';
|
||||
import { Box, DialogProps, Link, Stack, styled } from '@mui/material';
|
||||
import { Location } from 'types/upload';
|
||||
import { getEXIFLocation } from 'services/upload/exifService';
|
||||
import { RenderCaption } from './RenderCaption';
|
||||
import {
|
||||
BackupOutlined,
|
||||
CameraOutlined,
|
||||
FolderOutlined,
|
||||
LocationOnOutlined,
|
||||
TextSnippetOutlined,
|
||||
} from '@mui/icons-material';
|
||||
import CopyButton from 'components/CodeBlock/CopyButton';
|
||||
import { formatDateTime } from 'utils/time';
|
||||
import { Badge } from 'components/Badge';
|
||||
import { formatDateMedium, formatTime } from 'utils/time';
|
||||
import Titlebar from 'components/Titlebar';
|
||||
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) => (
|
||||
<Drawer {...props} anchor="right" />
|
||||
))(({ theme }) => ({
|
||||
export const FileInfoSidebar = styled((props: DialogProps) => (
|
||||
<EnteDrawer {...props} anchor="right" />
|
||||
))({
|
||||
zIndex: 1501,
|
||||
'& .MuiPaper-root': {
|
||||
maxWidth: '375px',
|
||||
width: '100%',
|
||||
scrollbarWidth: 'thin',
|
||||
padding: theme.spacing(1),
|
||||
padding: 8,
|
||||
},
|
||||
}));
|
||||
});
|
||||
|
||||
interface Iprops {
|
||||
shouldDisableEdits: boolean;
|
||||
showInfo: boolean;
|
||||
handleCloseInfo: () => void;
|
||||
items: any[];
|
||||
photoSwipe: Photoswipe<Photoswipe.Options>;
|
||||
metadata: Metadata;
|
||||
file: EnteFile;
|
||||
exif: any;
|
||||
scheduleUpdate: () => 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({
|
||||
shouldDisableEdits,
|
||||
showInfo,
|
||||
handleCloseInfo,
|
||||
items,
|
||||
photoSwipe,
|
||||
metadata,
|
||||
file,
|
||||
exif,
|
||||
scheduleUpdate,
|
||||
refreshPhotoswipe,
|
||||
fileToCollectionsMap,
|
||||
collectionNameMap,
|
||||
}: Iprops) {
|
||||
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(() => {
|
||||
if (!location && metadata) {
|
||||
if (metadata.longitude || metadata.longitude === 0) {
|
||||
if (!location && file && file.metadata) {
|
||||
if (file.metadata.longitude || file.metadata.longitude === 0) {
|
||||
setLocation({
|
||||
latitude: metadata.latitude,
|
||||
longitude: metadata.longitude,
|
||||
latitude: file.metadata.latitude,
|
||||
longitude: file.metadata.longitude,
|
||||
});
|
||||
}
|
||||
}
|
||||
}, [metadata]);
|
||||
}, [file]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!location && exif) {
|
||||
|
@ -77,80 +105,175 @@ export function FileInfo({
|
|||
}
|
||||
}, [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 (
|
||||
<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'}>
|
||||
<RenderCaption
|
||||
shouldDisableEdits={shouldDisableEdits}
|
||||
file={items[photoSwipe?.getCurrentIndex()]}
|
||||
file={file}
|
||||
scheduleUpdate={scheduleUpdate}
|
||||
refreshPhotoswipe={refreshPhotoswipe}
|
||||
/>
|
||||
|
||||
<RenderCreationTime
|
||||
shouldDisableEdits={shouldDisableEdits}
|
||||
file={items[photoSwipe?.getCurrentIndex()]}
|
||||
file={file}
|
||||
scheduleUpdate={scheduleUpdate}
|
||||
/>
|
||||
|
||||
<RenderFileName
|
||||
exif={exif}
|
||||
parsedExifData={parsedExifData}
|
||||
shouldDisableEdits={shouldDisableEdits}
|
||||
file={items[photoSwipe?.getCurrentIndex()]}
|
||||
file={file}
|
||||
scheduleUpdate={scheduleUpdate}
|
||||
/>
|
||||
{parsedExifData && parsedExifData['takenOnDevice'] && (
|
||||
<InfoItem
|
||||
icon={<CameraOutlined />}
|
||||
title={parsedExifData['takenOnDevice']}
|
||||
caption={
|
||||
<BasicDeviceCamera
|
||||
parsedExifData={parsedExifData}
|
||||
/>
|
||||
}
|
||||
hideEditOption
|
||||
/>
|
||||
)}
|
||||
|
||||
{location && (
|
||||
{/* {location && ( */}
|
||||
<InfoItem
|
||||
icon={<LocationOnOutlined />}
|
||||
title={constants.LOCATION}
|
||||
caption={
|
||||
<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}
|
||||
</Link>
|
||||
}
|
||||
customEndButton={
|
||||
<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"
|
||||
size="medium"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{/* )} */}
|
||||
<InfoItem
|
||||
icon={<TextSnippetOutlined />}
|
||||
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
|
||||
/>
|
||||
<InfoItem
|
||||
icon={<BackupOutlined />}
|
||||
title={formatDateTime(metadata.modificationTime / 1000)}
|
||||
caption={formatDateTime(metadata.modificationTime / 1000)}
|
||||
title={formatDateMedium(
|
||||
file.metadata.modificationTime / 1000
|
||||
)}
|
||||
caption={formatTime(file.metadata.modificationTime / 1000)}
|
||||
hideEditOption
|
||||
/>
|
||||
|
||||
<InfoItem icon={<FolderOutlined />} hideEditOption>
|
||||
<Stack spacing={1} direction="row">
|
||||
<Badge>abc</Badge>
|
||||
<Badge>DEF</Badge>
|
||||
<Badge>GHI</Badge>
|
||||
</Stack>
|
||||
</InfoItem>
|
||||
|
||||
{/* {exif && (
|
||||
<Box
|
||||
display={'flex'}
|
||||
gap={1}
|
||||
flexWrap="wrap"
|
||||
justifyContent={'flex-start'}
|
||||
alignItems={'flex-start'}>
|
||||
{fileToCollectionsMap
|
||||
.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>
|
||||
<ExifData
|
||||
exif={exif}
|
||||
open={showExif}
|
||||
onClose={closeExif}
|
||||
onInfoClose={handleCloseInfo}
|
||||
filename={file.metadata.title}
|
||||
/>
|
||||
</FileInfoSidebar>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -10,7 +10,6 @@ import { EnteFile } from 'types/file';
|
|||
import constants from 'utils/strings/constants';
|
||||
import exifr from 'exifr';
|
||||
import { downloadFile } from 'utils/file';
|
||||
import { prettyPrintExif } from 'utils/exif';
|
||||
import { livePhotoBtnHTML } from 'components/LivePhotoBtn';
|
||||
import { logError } from 'utils/sentry';
|
||||
|
||||
|
@ -61,6 +60,8 @@ interface Iprops {
|
|||
isTrashCollection: boolean;
|
||||
enableDownload: boolean;
|
||||
isSourceLoaded: boolean;
|
||||
fileToCollectionsMap: Map<number, number[]>;
|
||||
collectionNameMap: Map<number, string>;
|
||||
}
|
||||
|
||||
function PhotoViewer(props: Iprops) {
|
||||
|
@ -71,7 +72,6 @@ function PhotoViewer(props: Iprops) {
|
|||
const { isOpen, items, isSourceLoaded } = props;
|
||||
const [isFav, setIsFav] = useState(false);
|
||||
const [showInfo, setShowInfo] = useState(false);
|
||||
const [metadata, setMetaData] = useState<EnteFile['metadata']>(null);
|
||||
const [exif, setExif] = useState<any>(null);
|
||||
const [livePhotoBtnOptions, setLivePhotoBtnOptions] = useState(
|
||||
defaultLivePhotoDefaultOptions
|
||||
|
@ -318,8 +318,10 @@ function PhotoViewer(props: Iprops) {
|
|||
}
|
||||
};
|
||||
|
||||
const checkExifAvailable = async () => {
|
||||
setExif(null);
|
||||
const checkExifAvailable = async (force?: boolean) => {
|
||||
if (exif || !force) {
|
||||
return;
|
||||
}
|
||||
await sleep(100);
|
||||
try {
|
||||
const img: HTMLImageElement = document.querySelector(
|
||||
|
@ -327,11 +329,11 @@ function PhotoViewer(props: Iprops) {
|
|||
);
|
||||
if (img) {
|
||||
const exifData = await exifr.parse(img);
|
||||
if (!exifData) {
|
||||
return;
|
||||
}
|
||||
exifData.raw = prettyPrintExif(exifData);
|
||||
if (exifData) {
|
||||
setExif(exifData);
|
||||
} else {
|
||||
setExif(null);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
logError(e, 'exifr parsing failed');
|
||||
|
@ -340,10 +342,9 @@ function PhotoViewer(props: Iprops) {
|
|||
|
||||
function updateInfo() {
|
||||
const file: EnteFile = this?.currItem;
|
||||
if (file?.metadata) {
|
||||
setMetaData(file.metadata);
|
||||
setExif(null);
|
||||
checkExifAvailable();
|
||||
if (file) {
|
||||
setExif(undefined);
|
||||
checkExifAvailable(true);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -493,12 +494,12 @@ function PhotoViewer(props: Iprops) {
|
|||
shouldDisableEdits={props.isSharedCollection}
|
||||
showInfo={showInfo}
|
||||
handleCloseInfo={handleCloseInfo}
|
||||
items={items}
|
||||
photoSwipe={photoSwipe}
|
||||
metadata={metadata}
|
||||
file={photoSwipe?.currItem as EnteFile}
|
||||
exif={exif}
|
||||
scheduleUpdate={scheduleUpdate}
|
||||
refreshPhotoswipe={refreshPhotoswipe}
|
||||
fileToCollectionsMap={props.fileToCollectionsMap}
|
||||
collectionNameMap={props.collectionNameMap}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
import { Drawer, styled } from '@mui/material';
|
||||
import { styled } from '@mui/material';
|
||||
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': {
|
||||
maxWidth: '375px',
|
||||
width: '100%',
|
||||
scrollbarWidth: 'thin',
|
||||
padding: theme.spacing(1.5),
|
||||
},
|
||||
}));
|
||||
|
|
|
@ -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 React from 'react';
|
||||
import { FlexWrapper } from './Container';
|
||||
|
||||
interface Iprops {
|
||||
title: string;
|
||||
caption?: string;
|
||||
onClose: () => void;
|
||||
backIsClose?: boolean;
|
||||
onRootClose?: () => void;
|
||||
actionButton?: JSX.Element;
|
||||
}
|
||||
|
||||
export default function Titlebar({
|
||||
title,
|
||||
caption,
|
||||
onClose,
|
||||
backIsClose,
|
||||
actionButton,
|
||||
onRootClose,
|
||||
}: Iprops): JSX.Element {
|
||||
return (
|
||||
<>
|
||||
<Box display={'flex'} height={48} alignItems={'center'}>
|
||||
<IconButton onClick={onClose} color="secondary">
|
||||
<FlexWrapper
|
||||
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 />
|
||||
</IconButton>
|
||||
)}
|
||||
</Box>
|
||||
</FlexWrapper>
|
||||
<Box py={0.5} px={2} height={54}>
|
||||
<Typography variant="h3" fontWeight={'bold'}>
|
||||
{title}
|
||||
</Typography>
|
||||
<Typography variant="body2">{caption}</Typography>
|
||||
<Typography
|
||||
variant="body2"
|
||||
color="text.secondary"
|
||||
sx={{ wordBreak: 'break-all' }}>
|
||||
{caption}
|
||||
</Typography>
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -666,6 +666,7 @@ export default function Gallery() {
|
|||
/>
|
||||
<PhotoFrame
|
||||
files={files}
|
||||
collections={collections}
|
||||
syncWithRemote={syncWithRemote}
|
||||
favItemIds={favItemIds}
|
||||
archivedCollections={archivedCollections}
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -443,6 +443,7 @@ const englishConstants = {
|
|||
SHOW_ON_MAP: 'View on OpenStreetMap',
|
||||
DETAILS: 'Details',
|
||||
VIEW_EXIF: 'View all EXIF data',
|
||||
NO_EXIF: 'No EXIF data',
|
||||
EXIF: 'Exif',
|
||||
DEVICE: 'Device',
|
||||
IMAGE_SIZE: 'Image size',
|
||||
|
|
Loading…
Reference in a new issue