commit
d5ba497fc0
37
src/components/EnteDateTimePicker.tsx
Normal file
37
src/components/EnteDateTimePicker.tsx
Normal file
|
@ -0,0 +1,37 @@
|
|||
import React from 'react';
|
||||
import {
|
||||
MIN_EDITED_CREATION_TIME,
|
||||
MAX_EDITED_CREATION_TIME,
|
||||
ALL_TIME,
|
||||
} from 'services/fileService';
|
||||
|
||||
import DatePicker from 'react-datepicker';
|
||||
import 'react-datepicker/dist/react-datepicker.css';
|
||||
|
||||
const isSameDay = (first, second) =>
|
||||
first.getFullYear() === second.getFullYear() &&
|
||||
first.getMonth() === second.getMonth() &&
|
||||
first.getDate() === second.getDate();
|
||||
|
||||
const EnteDateTimePicker = ({ isInEditMode, pickedTime, handleChange }) => (
|
||||
<DatePicker
|
||||
open={isInEditMode}
|
||||
selected={pickedTime}
|
||||
onChange={handleChange}
|
||||
timeInputLabel="Time:"
|
||||
dateFormat="dd/MM/yyyy h:mm aa"
|
||||
showTimeSelect
|
||||
autoFocus
|
||||
minDate={MIN_EDITED_CREATION_TIME}
|
||||
maxDate={MAX_EDITED_CREATION_TIME}
|
||||
maxTime={
|
||||
isSameDay(pickedTime, new Date())
|
||||
? MAX_EDITED_CREATION_TIME
|
||||
: ALL_TIME
|
||||
}
|
||||
minTime={MIN_EDITED_CREATION_TIME}
|
||||
fixedHeight
|
||||
withPortal></DatePicker>
|
||||
);
|
||||
|
||||
export default EnteDateTimePicker;
|
|
@ -1,172 +0,0 @@
|
|||
import constants from 'utils/strings/constants';
|
||||
import MessageDialog from './MessageDialog';
|
||||
import React, { useContext, useEffect, useState } from 'react';
|
||||
import { ProgressBar, Button } from 'react-bootstrap';
|
||||
import { ComfySpan } from './ExportInProgress';
|
||||
import { updateCreationTimeWithExif } from 'services/updateCreationTimeWithExif';
|
||||
import { GalleryContext } from 'pages/gallery';
|
||||
import { File } from 'services/fileService';
|
||||
export interface FixCreationTimeAttributes {
|
||||
files: File[];
|
||||
}
|
||||
|
||||
interface Props {
|
||||
isOpen: boolean;
|
||||
show: () => void;
|
||||
hide: () => void;
|
||||
attributes: FixCreationTimeAttributes;
|
||||
}
|
||||
export enum FIX_STATE {
|
||||
NOT_STARTED,
|
||||
RUNNING,
|
||||
COMPLETED,
|
||||
COMPLETED_WITH_ERRORS,
|
||||
}
|
||||
function Message(props: { fixState: FIX_STATE }) {
|
||||
let message = null;
|
||||
switch (props.fixState) {
|
||||
case FIX_STATE.NOT_STARTED:
|
||||
message = constants.UPDATE_CREATION_TIME_NOT_STARTED();
|
||||
break;
|
||||
case FIX_STATE.COMPLETED:
|
||||
message = constants.UPDATE_CREATION_TIME_COMPLETED();
|
||||
break;
|
||||
case FIX_STATE.COMPLETED_WITH_ERRORS:
|
||||
message = constants.UPDATE_CREATION_TIME_COMPLETED_WITH_ERROR();
|
||||
break;
|
||||
}
|
||||
return message ? <div>{message}</div> : <></>;
|
||||
}
|
||||
export default function FixCreationTime(props: Props) {
|
||||
const [fixState, setFixState] = useState(FIX_STATE.NOT_STARTED);
|
||||
const [progressTracker, setProgressTracker] = useState({
|
||||
current: 0,
|
||||
total: 0,
|
||||
});
|
||||
const galleryContext = useContext(GalleryContext);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
props.attributes &&
|
||||
props.isOpen &&
|
||||
fixState !== FIX_STATE.RUNNING
|
||||
) {
|
||||
setFixState(FIX_STATE.NOT_STARTED);
|
||||
}
|
||||
}, [props.isOpen]);
|
||||
|
||||
const startFix = async () => {
|
||||
setFixState(FIX_STATE.RUNNING);
|
||||
const completedWithoutError = await updateCreationTimeWithExif(
|
||||
props.attributes.files,
|
||||
setProgressTracker
|
||||
);
|
||||
if (!completedWithoutError) {
|
||||
setFixState(FIX_STATE.COMPLETED);
|
||||
} else {
|
||||
setFixState(FIX_STATE.COMPLETED_WITH_ERRORS);
|
||||
}
|
||||
await galleryContext.syncWithRemote();
|
||||
};
|
||||
if (!props.attributes) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
return (
|
||||
<MessageDialog
|
||||
show={props.isOpen}
|
||||
onHide={props.hide}
|
||||
attributes={{
|
||||
title:
|
||||
fixState === FIX_STATE.RUNNING
|
||||
? constants.FIX_CREATION_TIME_IN_PROGRESS
|
||||
: constants.FIX_CREATION_TIME,
|
||||
staticBackdrop: true,
|
||||
nonClosable: true,
|
||||
}}>
|
||||
<div
|
||||
style={{
|
||||
marginBottom: '10px',
|
||||
padding: '0 5%',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
flexDirection: 'column',
|
||||
}}>
|
||||
<Message fixState={fixState} />
|
||||
|
||||
{fixState === FIX_STATE.RUNNING && (
|
||||
<>
|
||||
<div style={{ marginBottom: '10px' }}>
|
||||
<ComfySpan>
|
||||
{' '}
|
||||
{progressTracker.current} /{' '}
|
||||
{progressTracker.total}{' '}
|
||||
</ComfySpan>{' '}
|
||||
<span style={{ marginLeft: '10px' }}>
|
||||
{' '}
|
||||
{constants.CREATION_TIME_UPDATED}
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
width: '100%',
|
||||
marginTop: '10px',
|
||||
marginBottom: '20px',
|
||||
}}>
|
||||
<ProgressBar
|
||||
now={Math.round(
|
||||
(progressTracker.current * 100) /
|
||||
progressTracker.total
|
||||
)}
|
||||
animated={true}
|
||||
variant="upload-progress-bar"
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{fixState !== FIX_STATE.RUNNING && (
|
||||
<div
|
||||
style={{
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
marginTop: '30px',
|
||||
justifyContent: 'space-around',
|
||||
}}>
|
||||
{(fixState === FIX_STATE.NOT_STARTED ||
|
||||
fixState === FIX_STATE.COMPLETED_WITH_ERRORS) && (
|
||||
<Button
|
||||
block
|
||||
variant={'outline-secondary'}
|
||||
onClick={() => {
|
||||
props.hide();
|
||||
}}>
|
||||
{constants.CANCEL}
|
||||
</Button>
|
||||
)}
|
||||
{fixState === FIX_STATE.COMPLETED && (
|
||||
<Button
|
||||
block
|
||||
variant={'outline-secondary'}
|
||||
onClick={props.hide}>
|
||||
{constants.CLOSE}
|
||||
</Button>
|
||||
)}
|
||||
{(fixState === FIX_STATE.NOT_STARTED ||
|
||||
fixState === FIX_STATE.COMPLETED_WITH_ERRORS) && (
|
||||
<>
|
||||
<div style={{ width: '30px' }} />
|
||||
|
||||
<Button
|
||||
block
|
||||
variant={'outline-success'}
|
||||
onClick={startFix}>
|
||||
{constants.FIX_CREATION_TIME}
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</MessageDialog>
|
||||
);
|
||||
}
|
55
src/components/FixCreationTime/footer.tsx
Normal file
55
src/components/FixCreationTime/footer.tsx
Normal file
|
@ -0,0 +1,55 @@
|
|||
import React from 'react';
|
||||
import { Button } from 'react-bootstrap';
|
||||
import { FIX_STATE } from '.';
|
||||
import constants from 'utils/strings/constants';
|
||||
|
||||
export default function FixCreationTimeFooter({
|
||||
fixState,
|
||||
startFix,
|
||||
...props
|
||||
}) {
|
||||
return (
|
||||
fixState !== FIX_STATE.RUNNING && (
|
||||
<div
|
||||
style={{
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
marginTop: '30px',
|
||||
justifyContent: 'space-around',
|
||||
}}>
|
||||
{(fixState === FIX_STATE.NOT_STARTED ||
|
||||
fixState === FIX_STATE.COMPLETED_WITH_ERRORS) && (
|
||||
<Button
|
||||
block
|
||||
variant={'outline-secondary'}
|
||||
onClick={() => {
|
||||
props.hide();
|
||||
}}>
|
||||
{constants.CANCEL}
|
||||
</Button>
|
||||
)}
|
||||
{fixState === FIX_STATE.COMPLETED && (
|
||||
<Button
|
||||
block
|
||||
variant={'outline-secondary'}
|
||||
onClick={props.hide}>
|
||||
{constants.CLOSE}
|
||||
</Button>
|
||||
)}
|
||||
{(fixState === FIX_STATE.NOT_STARTED ||
|
||||
fixState === FIX_STATE.COMPLETED_WITH_ERRORS) && (
|
||||
<>
|
||||
<div style={{ width: '30px' }} />
|
||||
|
||||
<Button
|
||||
block
|
||||
variant={'outline-success'}
|
||||
onClick={startFix}>
|
||||
{constants.FIX_CREATION_TIME}
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
153
src/components/FixCreationTime/index.tsx
Normal file
153
src/components/FixCreationTime/index.tsx
Normal file
|
@ -0,0 +1,153 @@
|
|||
import constants from 'utils/strings/constants';
|
||||
import MessageDialog from '../MessageDialog';
|
||||
import React, { useContext, useEffect, useState } from 'react';
|
||||
import { updateCreationTimeWithExif } from 'services/updateCreationTimeWithExif';
|
||||
import { GalleryContext } from 'pages/gallery';
|
||||
import { File } from 'services/fileService';
|
||||
import FixCreationTimeRunning from './running';
|
||||
import FixCreationTimeFooter from './footer';
|
||||
import { Formik } from 'formik';
|
||||
|
||||
import FixCreationTimeOptions from './options';
|
||||
export interface FixCreationTimeAttributes {
|
||||
files: File[];
|
||||
}
|
||||
|
||||
interface Props {
|
||||
isOpen: boolean;
|
||||
show: () => void;
|
||||
hide: () => void;
|
||||
attributes: FixCreationTimeAttributes;
|
||||
}
|
||||
export enum FIX_STATE {
|
||||
NOT_STARTED,
|
||||
RUNNING,
|
||||
COMPLETED,
|
||||
COMPLETED_WITH_ERRORS,
|
||||
}
|
||||
|
||||
export enum FIX_OPTIONS {
|
||||
DATE_TIME_ORIGINAL,
|
||||
DATE_TIME_DIGITIZED,
|
||||
CUSTOM_TIME,
|
||||
}
|
||||
|
||||
interface formValues {
|
||||
option: FIX_OPTIONS;
|
||||
customTime: Date;
|
||||
}
|
||||
|
||||
function Message(props: { fixState: FIX_STATE }) {
|
||||
let message = null;
|
||||
switch (props.fixState) {
|
||||
case FIX_STATE.NOT_STARTED:
|
||||
message = constants.UPDATE_CREATION_TIME_NOT_STARTED();
|
||||
break;
|
||||
case FIX_STATE.COMPLETED:
|
||||
message = constants.UPDATE_CREATION_TIME_COMPLETED();
|
||||
break;
|
||||
case FIX_STATE.COMPLETED_WITH_ERRORS:
|
||||
message = constants.UPDATE_CREATION_TIME_COMPLETED_WITH_ERROR();
|
||||
break;
|
||||
}
|
||||
return message ? <div>{message}</div> : <></>;
|
||||
}
|
||||
export default function FixCreationTime(props: Props) {
|
||||
const [fixState, setFixState] = useState(FIX_STATE.NOT_STARTED);
|
||||
const [progressTracker, setProgressTracker] = useState({
|
||||
current: 0,
|
||||
total: 0,
|
||||
});
|
||||
const galleryContext = useContext(GalleryContext);
|
||||
useEffect(() => {
|
||||
if (
|
||||
props.attributes &&
|
||||
props.isOpen &&
|
||||
fixState !== FIX_STATE.RUNNING
|
||||
) {
|
||||
setFixState(FIX_STATE.NOT_STARTED);
|
||||
}
|
||||
}, [props.isOpen]);
|
||||
|
||||
const startFix = async (option: FIX_OPTIONS, customTime: Date) => {
|
||||
setFixState(FIX_STATE.RUNNING);
|
||||
const completedWithoutError = await updateCreationTimeWithExif(
|
||||
props.attributes.files,
|
||||
option,
|
||||
customTime,
|
||||
setProgressTracker
|
||||
);
|
||||
if (!completedWithoutError) {
|
||||
setFixState(FIX_STATE.COMPLETED);
|
||||
} else {
|
||||
setFixState(FIX_STATE.COMPLETED_WITH_ERRORS);
|
||||
}
|
||||
await galleryContext.syncWithRemote();
|
||||
};
|
||||
if (!props.attributes) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
const onSubmit = (values: formValues) => {
|
||||
console.log(values);
|
||||
startFix(Number(values.option), new Date(values.customTime));
|
||||
};
|
||||
|
||||
return (
|
||||
<MessageDialog
|
||||
show={props.isOpen}
|
||||
onHide={props.hide}
|
||||
attributes={{
|
||||
title:
|
||||
fixState === FIX_STATE.RUNNING
|
||||
? constants.FIX_CREATION_TIME_IN_PROGRESS
|
||||
: constants.FIX_CREATION_TIME,
|
||||
staticBackdrop: true,
|
||||
nonClosable: true,
|
||||
}}>
|
||||
<div
|
||||
style={{
|
||||
marginBottom: '10px',
|
||||
padding: '0 5%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
...(fixState === FIX_STATE.RUNNING
|
||||
? { alignItems: 'center' }
|
||||
: {}),
|
||||
}}>
|
||||
<Message fixState={fixState} />
|
||||
|
||||
{fixState === FIX_STATE.RUNNING && (
|
||||
<FixCreationTimeRunning progressTracker={progressTracker} />
|
||||
)}
|
||||
<Formik<formValues>
|
||||
initialValues={{
|
||||
option: FIX_OPTIONS.DATE_TIME_ORIGINAL,
|
||||
customTime: new Date(),
|
||||
}}
|
||||
validateOnBlur={false}
|
||||
onSubmit={onSubmit}>
|
||||
{({ values, handleChange, handleSubmit }) => (
|
||||
<>
|
||||
{(fixState === FIX_STATE.NOT_STARTED ||
|
||||
fixState ===
|
||||
FIX_STATE.COMPLETED_WITH_ERRORS) && (
|
||||
<div style={{ marginTop: '10px' }}>
|
||||
<FixCreationTimeOptions
|
||||
handleChange={handleChange}
|
||||
values={values}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<FixCreationTimeFooter
|
||||
fixState={fixState}
|
||||
startFix={handleSubmit}
|
||||
hide={props.hide}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</Formik>
|
||||
</div>
|
||||
</MessageDialog>
|
||||
);
|
||||
}
|
79
src/components/FixCreationTime/options.tsx
Normal file
79
src/components/FixCreationTime/options.tsx
Normal file
|
@ -0,0 +1,79 @@
|
|||
import React, { ChangeEvent } from 'react';
|
||||
import { FIX_OPTIONS } from '.';
|
||||
import { Form } from 'react-bootstrap';
|
||||
import EnteDateTimePicker from 'components/EnteDateTimePicker';
|
||||
import { Row, Value } from 'components/Container';
|
||||
import constants from 'utils/strings/constants';
|
||||
|
||||
const Option = ({
|
||||
value,
|
||||
selected,
|
||||
onChange,
|
||||
label,
|
||||
}: {
|
||||
value: FIX_OPTIONS;
|
||||
selected: FIX_OPTIONS;
|
||||
onChange: (e: string | ChangeEvent<any>) => void;
|
||||
label: string;
|
||||
}) => (
|
||||
<Form.Check
|
||||
name="group1"
|
||||
style={{
|
||||
margin: '5px 0',
|
||||
color: value !== Number(selected) ? '#aaa' : '#fff',
|
||||
}}>
|
||||
<Form.Check.Input
|
||||
id={value.toString()}
|
||||
type="radio"
|
||||
value={value}
|
||||
checked={value === Number(selected)}
|
||||
onChange={onChange}
|
||||
/>
|
||||
<Form.Check.Label
|
||||
style={{ cursor: 'pointer' }}
|
||||
htmlFor={value.toString()}>
|
||||
{label}
|
||||
</Form.Check.Label>
|
||||
</Form.Check>
|
||||
);
|
||||
|
||||
export default function FixCreationTimeOptions({ handleChange, values }) {
|
||||
return (
|
||||
<Form noValidate>
|
||||
<Option
|
||||
value={FIX_OPTIONS.DATE_TIME_ORIGINAL}
|
||||
onChange={handleChange('option')}
|
||||
label={constants.DATE_TIME_ORIGINAL}
|
||||
selected={Number(values.option)}
|
||||
/>
|
||||
|
||||
<Option
|
||||
value={FIX_OPTIONS.DATE_TIME_DIGITIZED}
|
||||
onChange={handleChange('option')}
|
||||
label={constants.DATE_TIME_DIGITIZED}
|
||||
selected={Number(values.option)}
|
||||
/>
|
||||
<Row style={{ margin: '0' }}>
|
||||
<Value width="50%">
|
||||
<Option
|
||||
value={FIX_OPTIONS.CUSTOM_TIME}
|
||||
onChange={handleChange('option')}
|
||||
label={constants.CUSTOM_TIME}
|
||||
selected={Number(values.option)}
|
||||
/>
|
||||
</Value>
|
||||
{Number(values.option) === FIX_OPTIONS.CUSTOM_TIME && (
|
||||
<Value width="40%">
|
||||
<EnteDateTimePicker
|
||||
isInEditMode
|
||||
pickedTime={new Date(values.customTime)}
|
||||
handleChange={(x: Date) =>
|
||||
handleChange('customTime')(x.toUTCString())
|
||||
}
|
||||
/>
|
||||
</Value>
|
||||
)}
|
||||
</Row>
|
||||
</Form>
|
||||
);
|
||||
}
|
35
src/components/FixCreationTime/running.tsx
Normal file
35
src/components/FixCreationTime/running.tsx
Normal file
|
@ -0,0 +1,35 @@
|
|||
import constants from 'utils/strings/constants';
|
||||
import { ComfySpan } from 'components/ExportInProgress';
|
||||
import React from 'react';
|
||||
import { ProgressBar } from 'react-bootstrap';
|
||||
|
||||
export default function FixCreationTimeRunning({ progressTracker }) {
|
||||
return (
|
||||
<>
|
||||
<div style={{ marginBottom: '10px' }}>
|
||||
<ComfySpan>
|
||||
{' '}
|
||||
{progressTracker.current} / {progressTracker.total}{' '}
|
||||
</ComfySpan>{' '}
|
||||
<span style={{ marginLeft: '10px' }}>
|
||||
{' '}
|
||||
{constants.CREATION_TIME_UPDATED}
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
width: '100%',
|
||||
marginTop: '10px',
|
||||
marginBottom: '20px',
|
||||
}}>
|
||||
<ProgressBar
|
||||
now={Math.round(
|
||||
(progressTracker.current * 100) / progressTracker.total
|
||||
)}
|
||||
animated={true}
|
||||
variant="upload-progress-bar"
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -8,11 +8,8 @@ import {
|
|||
removeFromFavorites,
|
||||
} from 'services/collectionService';
|
||||
import {
|
||||
ALL_TIME,
|
||||
File,
|
||||
MAX_EDITED_FILE_NAME_LENGTH,
|
||||
MAX_EDITED_CREATION_TIME,
|
||||
MIN_EDITED_CREATION_TIME,
|
||||
updatePublicMagicMetadata,
|
||||
} from 'services/fileService';
|
||||
import constants from 'utils/strings/constants';
|
||||
|
@ -41,14 +38,13 @@ import {
|
|||
} from 'components/Container';
|
||||
import { logError } from 'utils/sentry';
|
||||
|
||||
import DatePicker from 'react-datepicker';
|
||||
import 'react-datepicker/dist/react-datepicker.css';
|
||||
import CloseIcon from 'components/icons/CloseIcon';
|
||||
import TickIcon from 'components/icons/TickIcon';
|
||||
import { FreeFlowText } from 'components/RecoveryKeyModal';
|
||||
import { Formik } from 'formik';
|
||||
import * as Yup from 'yup';
|
||||
import EnteSpinner from 'components/EnteSpinner';
|
||||
import EnteDateTimePicker from 'components/EnteDateTimePicker';
|
||||
|
||||
interface Iprops {
|
||||
isOpen: boolean;
|
||||
|
@ -87,11 +83,6 @@ const renderInfoItem = (label: string, value: string | JSX.Element) => (
|
|||
</Row>
|
||||
);
|
||||
|
||||
const isSameDay = (first, second) =>
|
||||
first.getFullYear() === second.getFullYear() &&
|
||||
first.getMonth() === second.getMonth() &&
|
||||
first.getDate() === second.getDate();
|
||||
|
||||
function RenderCreationTime({
|
||||
file,
|
||||
scheduleUpdate,
|
||||
|
@ -145,24 +136,11 @@ function RenderCreationTime({
|
|||
<Label width="30%">{constants.CREATION_TIME}</Label>
|
||||
<Value width={isInEditMode ? '50%' : '60%'}>
|
||||
{isInEditMode ? (
|
||||
<DatePicker
|
||||
open={isInEditMode}
|
||||
selected={pickedTime}
|
||||
onChange={handleChange}
|
||||
timeInputLabel="Time:"
|
||||
dateFormat="dd/MM/yyyy h:mm aa"
|
||||
showTimeSelect
|
||||
autoFocus
|
||||
minDate={MIN_EDITED_CREATION_TIME}
|
||||
maxDate={MAX_EDITED_CREATION_TIME}
|
||||
maxTime={
|
||||
isSameDay(pickedTime, new Date())
|
||||
? MAX_EDITED_CREATION_TIME
|
||||
: ALL_TIME
|
||||
}
|
||||
minTime={MIN_EDITED_CREATION_TIME}
|
||||
fixedHeight
|
||||
withPortal></DatePicker>
|
||||
<EnteDateTimePicker
|
||||
isInEditMode={isInEditMode}
|
||||
pickedTime={pickedTime}
|
||||
handleChange={handleChange}
|
||||
/>
|
||||
) : (
|
||||
formatDateTime(pickedTime)
|
||||
)}
|
||||
|
|
|
@ -19,7 +19,10 @@ import RemoveIcon from 'components/icons/RemoveIcon';
|
|||
import RestoreIcon from 'components/icons/RestoreIcon';
|
||||
import ClockIcon from 'components/icons/ClockIcon';
|
||||
import { getData, LS_KEYS } from 'utils/storage/localStorage';
|
||||
import { FIX_CREATION_TIME_USER_ID, User } from 'services/userService';
|
||||
import {
|
||||
FIX_CREATION_TIME_VISIBLE_TO_USER_IDS,
|
||||
User,
|
||||
} from 'services/userService';
|
||||
|
||||
interface Props {
|
||||
addToCollectionHelper: (collection: Collection) => void;
|
||||
|
@ -86,7 +89,8 @@ const SelectedFileOptions = ({
|
|||
const [showFixCreationTime, setShowFixCreationTime] = useState(false);
|
||||
useEffect(() => {
|
||||
const user: User = getData(LS_KEYS.USER);
|
||||
const showFixCreationTime = user?.id === FIX_CREATION_TIME_USER_ID;
|
||||
const showFixCreationTime =
|
||||
FIX_CREATION_TIME_VISIBLE_TO_USER_IDS.includes(user?.id);
|
||||
setShowFixCreationTime(showFixCreationTime);
|
||||
}, []);
|
||||
const addToCollection = () =>
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { FIX_OPTIONS } from 'components/FixCreationTime';
|
||||
import { SetProgressTracker } from 'components/FixLargeThumbnail';
|
||||
import CryptoWorker from 'utils/crypto';
|
||||
import {
|
||||
|
@ -8,11 +9,13 @@ import {
|
|||
import { logError } from 'utils/sentry';
|
||||
import downloadManager from './downloadManager';
|
||||
import { File, FILE_TYPE, updatePublicMagicMetadata } from './fileService';
|
||||
import { getExifData } from './upload/exifService';
|
||||
import { getRawExif, getUNIXTime } from './upload/exifService';
|
||||
import { getFileType } from './upload/readFileService';
|
||||
|
||||
export async function updateCreationTimeWithExif(
|
||||
filesToBeUpdated: File[],
|
||||
fixOption: FIX_OPTIONS,
|
||||
customTime: Date,
|
||||
setProgressTracker: SetProgressTracker
|
||||
) {
|
||||
let completedWithError = false;
|
||||
|
@ -26,18 +29,30 @@ export async function updateCreationTimeWithExif(
|
|||
if (file.metadata.fileType !== FILE_TYPE.IMAGE) {
|
||||
continue;
|
||||
}
|
||||
const fileURL = await downloadManager.getFile(file);
|
||||
const fileObject = await getFileFromURL(fileURL);
|
||||
const worker = await new CryptoWorker();
|
||||
const fileTypeInfo = await getFileType(worker, fileObject);
|
||||
const exifData = await getExifData(fileObject, fileTypeInfo);
|
||||
let correctCreationTime: number;
|
||||
if (fixOption === FIX_OPTIONS.CUSTOM_TIME) {
|
||||
correctCreationTime = getUNIXTime(customTime);
|
||||
} else {
|
||||
const fileURL = await downloadManager.getFile(file);
|
||||
const fileObject = await getFileFromURL(fileURL);
|
||||
const worker = await new CryptoWorker();
|
||||
const fileTypeInfo = await getFileType(worker, fileObject);
|
||||
const exifData = await getRawExif(fileObject, fileTypeInfo);
|
||||
if (fixOption === FIX_OPTIONS.DATE_TIME_ORIGINAL) {
|
||||
correctCreationTime = getUNIXTime(
|
||||
exifData?.DateTimeOriginal
|
||||
);
|
||||
} else {
|
||||
correctCreationTime = getUNIXTime(exifData?.CreateDate);
|
||||
}
|
||||
}
|
||||
if (
|
||||
exifData?.creationTime &&
|
||||
exifData?.creationTime !== file.metadata.creationTime
|
||||
correctCreationTime &&
|
||||
correctCreationTime !== file.metadata.creationTime
|
||||
) {
|
||||
let updatedFile = await changeFileCreationTime(
|
||||
file,
|
||||
exifData.creationTime
|
||||
correctCreationTime
|
||||
);
|
||||
updatedFile = (
|
||||
await updatePublicMagicMetadata([updatedFile])
|
||||
|
|
|
@ -13,6 +13,15 @@ const EXIF_TAGS_NEEDED = [
|
|||
'GPSLatitudeRef',
|
||||
'GPSLongitudeRef',
|
||||
];
|
||||
interface Exif {
|
||||
DateTimeOriginal?: Date;
|
||||
CreateDate?: Date;
|
||||
ModifyDate?: Date;
|
||||
GPSLatitude?: number;
|
||||
GPSLongitude?: number;
|
||||
GPSLatitudeRef?: number;
|
||||
GPSLongitudeRef?: number;
|
||||
}
|
||||
interface ParsedEXIFData {
|
||||
location: Location;
|
||||
creationTime: number;
|
||||
|
@ -22,7 +31,26 @@ export async function getExifData(
|
|||
receivedFile: globalThis.File,
|
||||
fileTypeInfo: FileTypeInfo
|
||||
): Promise<ParsedEXIFData> {
|
||||
let exifData;
|
||||
const exifData = await getRawExif(receivedFile, fileTypeInfo);
|
||||
if (!exifData) {
|
||||
return { location: NULL_LOCATION, creationTime: null };
|
||||
}
|
||||
const parsedEXIFData = {
|
||||
location: getEXIFLocation(exifData),
|
||||
creationTime: getUNIXTime(
|
||||
exifData.DateTimeOriginal ??
|
||||
exifData.CreateDate ??
|
||||
exifData.ModifyDate
|
||||
),
|
||||
};
|
||||
return parsedEXIFData;
|
||||
}
|
||||
|
||||
export async function getRawExif(
|
||||
receivedFile: File,
|
||||
fileTypeInfo: FileTypeInfo
|
||||
) {
|
||||
let exifData: Exif;
|
||||
try {
|
||||
exifData = await exifr.parse(receivedFile, EXIF_TAGS_NEEDED);
|
||||
} catch (e) {
|
||||
|
@ -31,20 +59,10 @@ export async function getExifData(
|
|||
});
|
||||
// ignore exif parsing errors
|
||||
}
|
||||
if (!exifData) {
|
||||
return { location: NULL_LOCATION, creationTime: null };
|
||||
}
|
||||
const parsedEXIFData = {
|
||||
location: getEXIFLocation(exifData),
|
||||
creationTime: getUNIXTime(exifData),
|
||||
};
|
||||
return parsedEXIFData;
|
||||
return exifData;
|
||||
}
|
||||
|
||||
function getUNIXTime(exifData: any) {
|
||||
const dateTime: Date =
|
||||
exifData.DateTimeOriginal ?? exifData.CreateDate ?? exifData.ModifyDate;
|
||||
|
||||
export function getUNIXTime(dateTime: Date) {
|
||||
if (!dateTime) {
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -28,7 +28,7 @@ const ENDPOINT = getEndpoint();
|
|||
|
||||
const HAS_SET_KEYS = 'hasSetKeys';
|
||||
|
||||
export const FIX_CREATION_TIME_USER_ID = 341;
|
||||
export const FIX_CREATION_TIME_VISIBLE_TO_USER_IDS = [1, 125, 341];
|
||||
|
||||
export interface User {
|
||||
id: number;
|
||||
|
|
|
@ -601,7 +601,7 @@ const englishConstants = {
|
|||
CREATION_TIME_UPDATED: `file time updated`,
|
||||
|
||||
UPDATE_CREATION_TIME_NOT_STARTED: () => (
|
||||
<>do you want to fix time with the values found in EXIF</>
|
||||
<>select the option you want to use</>
|
||||
),
|
||||
UPDATE_CREATION_TIME_COMPLETED: () => <>successfully updated all files</>,
|
||||
|
||||
|
@ -609,6 +609,10 @@ const englishConstants = {
|
|||
<>file time updation failed for some files, please retry</>
|
||||
),
|
||||
FILE_NAME_CHARACTER_LIMIT: '100 characters max',
|
||||
|
||||
DATE_TIME_ORIGINAL: 'EXIF:DateTimeOriginal',
|
||||
DATE_TIME_DIGITIZED: 'EXIF:DateTimeDigitized',
|
||||
CUSTOM_TIME: 'custom time',
|
||||
};
|
||||
|
||||
export default englishConstants;
|
||||
|
|
Loading…
Reference in a new issue