Merge pull request #600 from ente-io/date-time-picker

Date time picker
This commit is contained in:
Abhinav Kumar 2022-06-15 03:41:41 +05:30 committed by GitHub
commit 1c1f25af7d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 178 additions and 110 deletions

View file

@ -13,11 +13,13 @@
"prepare": "husky install"
},
"dependencies": {
"@date-io/date-fns": "^2.14.0",
"@ente-io/next-with-workbox": "^1.0.3",
"@mui/icons-material": "^5.6.2",
"@mui/material": "^5.6.2",
"@mui/styled-engine": "npm:@mui/styled-engine-sc@latest",
"@mui/styled-engine-sc": "^5.6.1",
"@mui/x-date-pickers": "^5.0.0-alpha.6",
"@sentry/nextjs": "^6.7.1",
"@stripe/stripe-js": "^1.13.2",
"@typescript-eslint/eslint-plugin": "^4.25.0",
@ -49,7 +51,6 @@
"piexifjs": "^1.0.6",
"react": "^17.0.2",
"react-bootstrap": "^1.3.0",
"react-datepicker": "^4.3.0",
"react-dom": "^17.0.2",
"react-dropzone": "^11.2.4",
"react-otp-input": "^2.3.1",

View file

@ -7,6 +7,7 @@ import { ALL_SECTION } from 'constants/collection';
import CollectionShare from 'components/Collections/CollectionShare';
import { SetCollectionNamerAttributes } from 'components/Collections/CollectionNamer';
import { ITEM_TYPE, TimeStampListItem } from 'components/PhotoList';
import { hasNonEmptyCollections } from 'utils/collection';
interface Iprops {
collections: Collection[];
@ -35,7 +36,8 @@ export default function Collections(props: Iprops) {
const collectionsMap = useRef<Map<number, Collection>>(new Map());
const activeCollection = useRef<Collection>(null);
const shouldBeHidden = isInSearchMode || collectionSummaries?.size <= 3;
const shouldBeHidden =
isInSearchMode || hasNonEmptyCollections(collectionSummaries);
useEffect(() => {
collectionsMap.current = new Map(

View file

@ -15,6 +15,7 @@ export const CollectionListBarWrapper = styled(PaddedContainer)`
`;
export const CollectionInfoBarWrapper = styled(Box)`
width: 100%;
margin-bottom: 24px;
`;

View file

@ -39,6 +39,7 @@ export const IconButton = styled('button')`
`;
export const Row = styled('div')`
min-height: 32px;
display: flex;
align-items: center;
margin-bottom: ${({ theme }) => theme.spacing(2)};

View file

@ -1,50 +1,68 @@
import React from 'react';
import React, { useState } from 'react';
import DatePicker from 'react-datepicker';
import 'react-datepicker/dist/react-datepicker.css';
import {
MIN_EDITED_CREATION_TIME,
MAX_EDITED_CREATION_TIME,
ALL_TIME,
} from 'constants/file';
const isSameDay = (first, second) =>
first.getFullYear() === second.getFullYear() &&
first.getMonth() === second.getMonth() &&
first.getDate() === second.getDate();
import { TextField } from '@mui/material';
import {
LocalizationProvider,
MobileDateTimePicker,
} from '@mui/x-date-pickers';
import { AdapterDateFns } from '@mui/x-date-pickers/AdapterDateFns';
interface Props {
loading?: boolean;
isInEditMode: boolean;
pickedTime: Date;
handleChange: (date: Date) => void;
initialValue?: Date;
disabled?: boolean;
label?: string;
onSubmit: (date: Date) => void;
onClose?: () => void;
}
const EnteDateTimePicker = ({
loading,
isInEditMode,
pickedTime,
handleChange,
}: Props) => (
<DatePicker
disabled={loading}
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>
);
initialValue,
disabled,
onSubmit,
onClose,
}: Props) => {
const [open, setOpen] = useState(true);
const [value, setValue] = useState(initialValue ?? new Date());
const handleClose = () => {
setOpen(false);
onClose?.();
};
return (
<LocalizationProvider dateAdapter={AdapterDateFns}>
<MobileDateTimePicker
value={value}
onChange={setValue}
open={open}
onClose={handleClose}
onOpen={() => setOpen(true)}
maxDateTime={MAX_EDITED_CREATION_TIME}
minDateTime={MIN_EDITED_CREATION_TIME}
disabled={disabled}
onAccept={onSubmit}
DialogProps={{
sx: {
zIndex: '1502',
'.MuiPickersToolbar-penIconButton': {
display: 'none',
},
},
}}
renderInput={(params) => (
<TextField
{...params}
hiddenLabel
margin="none"
variant="standard"
/>
)}
/>
</LocalizationProvider>
);
};
export default EnteDateTimePicker;

View file

@ -69,9 +69,7 @@ export default function FixCreationTimeOptions({ handleChange, values }) {
{Number(values.option) === FIX_OPTIONS.CUSTOM_TIME && (
<Value width="40%">
<EnteDateTimePicker
isInEditMode
pickedTime={new Date(values.customTime)}
handleChange={(x: Date) =>
onSubmit={(x: Date) =>
handleChange('customTime')(x.toUTCString())
}
/>

View file

@ -107,6 +107,7 @@ const FooterContainer = styled(ListItemContainer)`
}
color: #979797;
text-align: center;
justify-content: center;
align-items: flex-end;
margin-top: calc(2rem + 20px);
`;
@ -163,7 +164,11 @@ export function PhotoList({
};
useEffect(() => {
let timeStampList: TimeStampListItem[] = [getPhotoListHeader()];
let timeStampList: TimeStampListItem[] = [];
if (galleryContext.photoListHeader) {
timeStampList.push(getPhotoListHeader());
}
if (deduplicateContext.isOnDeduplicatePage) {
skipMerge = true;
groupByFileSize(timeStampList);

View file

@ -10,10 +10,8 @@ import {
import EditIcon from 'components/icons/EditIcon';
import { IconButton, Label, Row, Value } from 'components/Container';
import { logError } from 'utils/sentry';
import CloseIcon from '@mui/icons-material/Close';
import TickIcon from '@mui/icons-material/Done';
import EnteDateTimePicker from 'components/EnteDateTimePicker';
import { SmallLoadingSpinner } from '../styledComponents/SmallLoadingSpinner';
import EnteDateTimePicker from 'components/EnteDateTimePicker';
export function RenderCreationTime({
shouldDisableEdits,
@ -28,12 +26,10 @@ export function RenderCreationTime({
const originalCreationTime = new Date(file?.metadata.creationTime / 1000);
const [isInEditMode, setIsInEditMode] = useState(false);
const [pickedTime, setPickedTime] = useState(originalCreationTime);
const openEditMode = () => setIsInEditMode(true);
const closeEditMode = () => setIsInEditMode(false);
const saveEdits = async () => {
const saveEdits = async (pickedTime: Date) => {
try {
setLoading(true);
if (isInEditMode && file) {
@ -59,59 +55,38 @@ export function RenderCreationTime({
setLoading(false);
}
};
const discardEdits = () => {
setPickedTime(originalCreationTime);
closeEditMode();
};
const handleChange = (newDate: Date) => {
if (newDate instanceof Date) {
setPickedTime(newDate);
}
};
return (
<>
<Row>
<Label width="30%">{constants.CREATION_TIME}</Label>
<Value
width={
!shouldDisableEdits
? isInEditMode
? '50%'
: '60%'
: '70%'
!shouldDisableEdits ? !isInEditMode && '60%' : '70%'
}>
{isInEditMode ? (
<EnteDateTimePicker
loading={loading}
isInEditMode={isInEditMode}
pickedTime={pickedTime}
handleChange={handleChange}
initialValue={originalCreationTime}
disabled={loading}
onSubmit={saveEdits}
onClose={closeEditMode}
/>
) : (
formatDateTime(pickedTime)
formatDateTime(originalCreationTime)
)}
</Value>
{!shouldDisableEdits && (
{!shouldDisableEdits && !isInEditMode && (
<Value
width={isInEditMode ? '20%' : '10%'}
width={'10%'}
style={{ cursor: 'pointer', marginLeft: '10px' }}>
{!isInEditMode ? (
{loading ? (
<IconButton>
<SmallLoadingSpinner />
</IconButton>
) : (
<IconButton onClick={openEditMode}>
<EditIcon />
</IconButton>
) : (
<>
<IconButton onClick={saveEdits}>
{loading ? (
<SmallLoadingSpinner />
) : (
<TickIcon />
)}
</IconButton>
<IconButton onClick={discardEdits}>
<CloseIcon />
</IconButton>
</>
)}
</Value>
)}

View file

@ -1,6 +1,5 @@
export const MIN_EDITED_CREATION_TIME = new Date(1800, 0, 1);
export const MAX_EDITED_CREATION_TIME = new Date();
export const ALL_TIME = new Date(1800, 0, 1, 23, 59, 59);
export const MAX_EDITED_FILE_NAME_LENGTH = 100;
export const MAX_TRASH_BATCH_SIZE = 1000;

View file

@ -75,6 +75,7 @@ import {
getSelectedCollection,
isFavoriteCollection,
getArchivedCollections,
hasNonEmptyCollections,
} from 'utils/collection';
import { logError } from 'utils/sentry';
import {
@ -687,7 +688,7 @@ export default function Gallery() {
setUploadInProgress={setUploadInProgress}
fileRejections={fileRejections}
setFiles={setFiles}
isFirstUpload={collectionSummaries?.size === 0}
isFirstUpload={hasNonEmptyCollections(collectionSummaries)}
electronFiles={electronFiles}
setElectronFiles={setElectronFiles}
uploadTypeSelectorView={uploadTypeSelectorView}

View file

@ -14,7 +14,11 @@ import { User } from 'types/user';
import { getData, LS_KEYS } from 'utils/storage/localStorage';
import { logError } from 'utils/sentry';
import constants from 'utils/strings/constants';
import { Collection, CollectionMagicMetadataProps } from 'types/collection';
import {
Collection,
CollectionMagicMetadataProps,
CollectionSummaries,
} from 'types/collection';
import { CollectionType } from 'constants/collection';
import { getAlbumSiteHost } from 'constants/pages';
import { getUnixTimeInMicroSecondsWithDelta } from 'utils/time';
@ -189,3 +193,9 @@ export const changeCollectionVisibility = async (
export const getArchivedCollections = (collections: Collection[]) => {
return collections.filter(IsArchived).map((collection) => collection.id);
};
export const hasNonEmptyCollections = (
collectionSummaries: CollectionSummaries
) => {
return collectionSummaries?.size <= 3;
};

View file

@ -202,7 +202,7 @@ export function formatDateTime(date: number | Date) {
day: 'numeric',
});
const timeFormat = new Intl.DateTimeFormat('en-IN', {
timeStyle: 'medium',
timeStyle: 'short',
});
return `${dateTimeFormat.format(date)} ${timeFormat.format(date)}`;
}

View file

@ -904,6 +904,39 @@
"@babel/helper-validator-identifier" "^7.14.9"
to-fast-properties "^2.0.0"
"@date-io/core@^2.14.0":
version "2.14.0"
resolved "https://registry.yarnpkg.com/@date-io/core/-/core-2.14.0.tgz#03e9b9b9fc8e4d561c32dd324df0f3ccd967ef14"
integrity sha512-qFN64hiFjmlDHJhu+9xMkdfDG2jLsggNxKXglnekUpXSq8faiqZgtHm2lsHCUuaPDTV6wuXHcCl8J1GQ5wLmPw==
"@date-io/date-fns@^2.14.0":
version "2.14.0"
resolved "https://registry.yarnpkg.com/@date-io/date-fns/-/date-fns-2.14.0.tgz#92ab150f488f294c135c873350d154803cebdbea"
integrity sha512-4fJctdVyOd5cKIKGaWUM+s3MUXMuzkZaHuTY15PH70kU1YTMrCoauA7hgQVx9qj0ZEbGrH9VSPYJYnYro7nKiA==
dependencies:
"@date-io/core" "^2.14.0"
"@date-io/dayjs@^2.14.0":
version "2.14.0"
resolved "https://registry.yarnpkg.com/@date-io/dayjs/-/dayjs-2.14.0.tgz#8d4e93e1d473bb5f25210866204dc33384ca4c20"
integrity sha512-4fRvNWaOh7AjvOyJ4h6FYMS7VHLQnIEeAV5ahv6sKYWx+1g1UwYup8h7+gPuoF+sW2hTScxi7PVaba2Jk/U8Og==
dependencies:
"@date-io/core" "^2.14.0"
"@date-io/luxon@^2.14.0":
version "2.14.0"
resolved "https://registry.yarnpkg.com/@date-io/luxon/-/luxon-2.14.0.tgz#cd1641229e00a899625895de3a31e3aaaf66629f"
integrity sha512-KmpBKkQFJ/YwZgVd0T3h+br/O0uL9ZdE7mn903VPAG2ZZncEmaUfUdYKFT7v7GyIKJ4KzCp379CRthEbxevEVg==
dependencies:
"@date-io/core" "^2.14.0"
"@date-io/moment@^2.14.0":
version "2.14.0"
resolved "https://registry.yarnpkg.com/@date-io/moment/-/moment-2.14.0.tgz#8300abd6ae8c55d8edee90d118db3cef0b1d4f58"
integrity sha512-VsoLXs94GsZ49ecWuvFbsa081zEv2xxG7d+izJsqGa2L8RPZLlwk27ANh87+SNnOUpp+qy2AoCAf0mx4XXhioA==
dependencies:
"@date-io/core" "^2.14.0"
"@emotion/cache@^11.4.0":
version "11.4.0"
resolved "https://registry.npmjs.org/@emotion/cache/-/cache-11.4.0.tgz"
@ -1126,6 +1159,17 @@
resolved "https://registry.yarnpkg.com/@mui/types/-/types-7.1.3.tgz#d7636f3046110bcccc63e6acfd100e2ad9ca712a"
integrity sha512-DDF0UhMBo4Uezlk+6QxrlDbchF79XG6Zs0zIewlR4c0Dt6GKVFfUtzPtHCH1tTbcSlq/L2bGEdiaoHBJ9Y1gSA==
"@mui/utils@^5.4.1":
version "5.8.0"
resolved "https://registry.yarnpkg.com/@mui/utils/-/utils-5.8.0.tgz#4b1d19cbcf70773283375e763b7b3552b84cb58f"
integrity sha512-7LgUtCvz78676iC0wpTH7HizMdCrTphhBmRWimIMFrp5Ph6JbDFVuKS1CwYnWWxRyYKL0QzXrDL0lptAU90EXg==
dependencies:
"@babel/runtime" "^7.17.2"
"@types/prop-types" "^15.7.5"
"@types/react-is" "^16.7.1 || ^17.0.0"
prop-types "^15.8.1"
react-is "^17.0.2"
"@mui/utils@^5.6.1":
version "5.6.1"
resolved "https://registry.yarnpkg.com/@mui/utils/-/utils-5.6.1.tgz#4ab79a21bd481555d9a588f4b18061b3c28ea5db"
@ -1137,6 +1181,22 @@
prop-types "^15.7.2"
react-is "^17.0.2"
"@mui/x-date-pickers@^5.0.0-alpha.6":
version "5.0.0-alpha.6"
resolved "https://registry.yarnpkg.com/@mui/x-date-pickers/-/x-date-pickers-5.0.0-alpha.6.tgz#90fdb5730e0411862d7a7edd18af44f03ab8d889"
integrity sha512-2JeagDwwa/V2XPj243cZg5ReZ2553OzukUAfbdxXwj9gGGLeXjBa95NP4kPOBOze4tJq1y/4aYt/aK50aZWElQ==
dependencies:
"@babel/runtime" "^7.17.2"
"@date-io/date-fns" "^2.14.0"
"@date-io/dayjs" "^2.14.0"
"@date-io/luxon" "^2.14.0"
"@date-io/moment" "^2.14.0"
"@mui/utils" "^5.4.1"
clsx "^1.1.1"
prop-types "^15.7.2"
react-transition-group "^4.4.2"
rifm "^0.12.1"
"@next/bundle-analyzer@^9.5.3":
version "9.5.5"
resolved "https://registry.npmjs.org/@next/bundle-analyzer/-/bundle-analyzer-9.5.5.tgz"
@ -1524,7 +1584,7 @@
resolved "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.4.tgz"
integrity sha512-rZ5drC/jWjrArrS8BR6SIr4cWpW09RNTYt9AMZo3Jwwif+iacXAqgVjm0B0Bv/S1jhDXKHqRVNCbACkJ89RAnQ==
"@types/prop-types@^15.7.4":
"@types/prop-types@^15.7.4", "@types/prop-types@^15.7.5":
version "15.7.5"
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.5.tgz#5f19d2b85a98e9558036f6a3cacc8819420f05cf"
integrity sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==
@ -2234,7 +2294,7 @@ cipher-base@^1.0.1, cipher-base@^1.0.3:
inherits "^2.0.1"
safe-buffer "^5.0.1"
classnames@^2.2.6, classnames@^2.3.1:
classnames@^2.3.1:
version "2.3.1"
resolved "https://registry.npmjs.org/classnames/-/classnames-2.3.1.tgz"
integrity sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA==
@ -2475,7 +2535,7 @@ damerau-levenshtein@^1.0.6:
resolved "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.7.tgz"
integrity sha512-VvdQIPGdWP0SqFXghj79Wf/5LArmreyMsGLa6FG6iC4t3j7j5s71TrwWmT/4akbDQIqjfACkLZmjXhA7g2oUZw==
date-fns@^2.0.1, date-fns@^2.24.0:
date-fns@^2.0.1:
version "2.25.0"
resolved "https://registry.npmjs.org/date-fns/-/date-fns-2.25.0.tgz"
integrity sha512-ovYRFnTrbGPD4nqaEqescPEv1mNwvt+UTqI3Ay9SzNtey9NZnYu6E2qCcBBgJ6/2VF1zGGygpyTDITqpQQ5e+w==
@ -4571,6 +4631,15 @@ prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.2, prop-types@^15.7.2:
object-assign "^4.1.1"
react-is "^16.8.1"
prop-types@^15.8.1:
version "15.8.1"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
dependencies:
loose-envify "^1.4.0"
object-assign "^4.1.1"
react-is "^16.13.1"
property-expr@^2.0.2:
version "2.0.4"
resolved "https://registry.npmjs.org/property-expr/-/property-expr-2.0.4.tgz"
@ -4649,18 +4718,6 @@ react-bootstrap@^1.3.0:
uncontrollable "^7.2.1"
warning "^4.0.3"
react-datepicker@^4.3.0:
version "4.3.0"
resolved "https://registry.npmjs.org/react-datepicker/-/react-datepicker-4.3.0.tgz"
integrity sha512-cg+gp4YnPcZc6iZ0+v7VuRgcFG22KL7izHYrCxXeSLOn5VGkyxTfcu5qA/cGIsngurCt/u0NtgVTlHB1Fwap1g==
dependencies:
"@popperjs/core" "^2.9.2"
classnames "^2.2.6"
date-fns "^2.24.0"
prop-types "^15.7.2"
react-onclickoutside "^6.12.0"
react-popper "^2.2.5"
react-dom@^17.0.2:
version "17.0.2"
resolved "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz"
@ -4701,7 +4758,7 @@ react-input-autosize@^3.0.0:
dependencies:
prop-types "^15.5.8"
react-is@^16.3.2, react-is@^16.7.0, react-is@^16.8.1:
react-is@^16.13.1, react-is@^16.3.2, react-is@^16.7.0, react-is@^16.8.1:
version "16.13.1"
resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
@ -4716,11 +4773,6 @@ react-lifecycles-compat@^3.0.4:
resolved "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz"
integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==
react-onclickoutside@^6.12.0:
version "6.12.0"
resolved "https://registry.npmjs.org/react-onclickoutside/-/react-onclickoutside-6.12.0.tgz"
integrity sha512-oPlOTYcISLHfpMog2lUZMFSbqOs4LFcA4+vo7fpfevB5v9Z0D5VBDBkfeO5lv+hpEcGoaGk67braLT+QT+eICA==
react-otp-input@^2.3.1:
version "2.4.0"
resolved "https://registry.npmjs.org/react-otp-input/-/react-otp-input-2.4.0.tgz"
@ -4958,6 +5010,11 @@ reusify@^1.0.4:
resolved "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz"
integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==
rifm@^0.12.1:
version "0.12.1"
resolved "https://registry.yarnpkg.com/rifm/-/rifm-0.12.1.tgz#8fa77f45b7f1cda2a0068787ac821f0593967ac4"
integrity sha512-OGA1Bitg/dSJtI/c4dh90svzaUPt228kzFsUkJbtA2c964IqEAwWXeL9ZJi86xWv3j5SMqRvGULl7bA6cK0Bvg==
rimraf@^2.6.3:
version "2.7.1"
resolved "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz"