diff --git a/web/apps/photos/src/components/PhotoFrame.tsx b/web/apps/photos/src/components/PhotoFrame.tsx index 835bdeb3a..524b555ef 100644 --- a/web/apps/photos/src/components/PhotoFrame.tsx +++ b/web/apps/photos/src/components/PhotoFrame.tsx @@ -17,12 +17,16 @@ import DownloadManager, { LivePhotoSourceURL, SourceURLs, } from "services/download"; +import { + handleSelectCreator, + updateFileMsrcProps, + updateFileSrcProps, +} from "utils/photoFrame"; import { EnteFile } from "types/file"; import { SelectedState, SetFilesDownloadProgressAttributesCreator, } from "types/gallery"; -import { updateFileMsrcProps, updateFileSrcProps } from "utils/photoFrame"; import { PhotoList } from "./PhotoList"; import { DedupePhotoList } from "./PhotoList/dedupe"; import PreviewCard from "./pages/gallery/PreviewCard"; @@ -227,52 +231,12 @@ const PhotoFrame = ({ setIsPhotoSwipeOpen?.(true); }; - const handleSelect = - (id: number, isOwnFile: boolean, index?: number) => - (checked: boolean) => { - if (typeof index !== "undefined") { - if (checked) { - setRangeStart(index); - } else { - setRangeStart(undefined); - } - } - setSelected((selected) => { - if (selected.collectionID !== activeCollectionID) { - selected = { ownCount: 0, count: 0, collectionID: 0 }; - } + const handleSelect = handleSelectCreator( + setSelected, + activeCollectionID, + setRangeStart + ); - const handleCounterChange = (count: number) => { - if (selected[id] === checked) { - return count; - } - if (checked) { - return count + 1; - } else { - return count - 1; - } - }; - - const handleAllCounterChange = () => { - if (isOwnFile) { - return { - ownCount: handleCounterChange(selected.ownCount), - count: handleCounterChange(selected.count), - }; - } else { - return { - count: handleCounterChange(selected.count), - }; - } - }; - return { - ...selected, - [id]: checked, - collectionID: activeCollectionID, - ...handleAllCounterChange(), - }; - }); - }; const onHoverOver = (index: number) => () => { setCurrentHover(index); }; diff --git a/web/apps/photos/src/components/PhotoList/index.tsx b/web/apps/photos/src/components/PhotoList/index.tsx index 4d0624215..a75adaf33 100644 --- a/web/apps/photos/src/components/PhotoList/index.tsx +++ b/web/apps/photos/src/components/PhotoList/index.tsx @@ -1,8 +1,7 @@ import { FlexWrapper } from "@ente/shared/components/Container"; import { ENTE_WEBSITE_LINK } from "@ente/shared/constants/urls"; -import { formatDate } from "@ente/shared/time/format"; import { convertBytesToHumanReadable } from "@ente/shared/utils/size"; -import { Box, Link, Typography, styled } from "@mui/material"; +import { Box, Link, Typography,Checkbox, styled } from "@mui/material"; import { DATE_CONTAINER_HEIGHT, GAP_BTW_TILES, @@ -23,8 +22,10 @@ import { ListChildComponentProps, areEqual, } from "react-window"; -import { EnteFile } from "types/file"; import { PublicCollectionGalleryContext } from "utils/publicCollectionGallery"; +import { formatDate, getDate, isSameDay } from "@ente/shared/time/format"; +import { handleSelectCreator } from "utils/photoFrame"; +import { EnteFile } from "types/file"; const A_DAY = 24 * 60 * 60 * 1000; const FOOTER_HEIGHT = 90; @@ -185,6 +186,9 @@ const NothingContainer = styled(ListItemContainer)` justify-content: center; `; +const SelectAllCheckBoxContainer = styled(Checkbox)<{ margin: number }>` + margin-left: ${(props) => props.margin}px; +`; interface Props { height: number; width: number; @@ -265,6 +269,8 @@ export function PhotoList({ const shouldRefresh = useRef(false); const listRef = useRef(null); + const [checkedDates, setCheckedDates] = useState({}); + const fittableColumns = getFractionFittableColumns(width); let columns = Math.floor(fittableColumns); @@ -473,14 +479,6 @@ export function PhotoList({ }); }; - const isSameDay = (first, second) => { - return ( - first.getFullYear() === second.getFullYear() && - first.getMonth() === second.getMonth() && - first.getDate() === second.getDate() - ); - }; - const getPhotoListHeader = (photoListHeader) => { return { ...photoListHeader, @@ -722,6 +720,62 @@ export function PhotoList({ } }; + useEffect(() => { + const notSelectedFiles = displayFiles?.filter( + (item) => !galleryContext.selectedFile[item.id] + ); + const unselectedDates = [ + ...new Set(notSelectedFiles?.map((item) => getDate(item))), // to get file's date which were manually unselected + ]; + + const localSelectedFiles = displayFiles.filter( + // to get files which were manually selected + (item) => !unselectedDates.includes(getDate(item)) + ); + + const localSelectedDates = [ + ...new Set(localSelectedFiles?.map((item) => getDate(item))), + ]; // to get file's date which were manually selected + + unselectedDates.forEach((date) => { + setCheckedDates((prev) => ({ + ...prev, + [date]: false, + })); // To uncheck select all checkbox if any of the file on the date is unselected + }); + + localSelectedDates.map((date) => { + setCheckedDates((prev) => ({ + ...prev, + [date]: true, + })); + // To check select all checkbox if all of the files on the date is selected manually + }); + }, [galleryContext.selectedFile]); + + const handleSelect = handleSelectCreator( + galleryContext.setSelectedFiles, + activeCollectionID + ); + + const onChangeSelectAllCheckBox = (date: string) => { + const dates = { ...checkedDates, [date]: !checkedDates[date] }; + const isDateSelected = !checkedDates[date]; + + setCheckedDates(dates); + + const filesOnADay = displayFiles?.filter( + (item) => getDate(item) === date + ); // all files on a checked/unchecked day + + filesOnADay.forEach((file) => { + handleSelect( + file.id, + file.ownerID === galleryContext?.user?.id + )(isDateSelected); + }); + }; + const renderListItem = ( listItem: TimeStampListItem, isScrolling: boolean, @@ -733,6 +787,15 @@ export function PhotoList({ .map((item) => [ {item.date} + + onChangeSelectAllCheckBox(item.date) + } + margin={columns} + /> ,
, ]) @@ -740,6 +803,15 @@ export function PhotoList({ ) : ( {listItem.date} + + onChangeSelectAllCheckBox(listItem.date) + } + margin={columns} + /> ); case ITEM_TYPE.SIZE_AND_COUNT: diff --git a/web/apps/photos/src/pages/gallery/index.tsx b/web/apps/photos/src/pages/gallery/index.tsx index dc043a7da..46dd87c89 100644 --- a/web/apps/photos/src/pages/gallery/index.tsx +++ b/web/apps/photos/src/pages/gallery/index.tsx @@ -163,6 +163,8 @@ const defaultGalleryContext: GalleryContextType = { emailList: null, openHiddenSection: () => null, isClipSearchResult: null, + selectedFile: null, + setSelectedFiles: () => null, }; export const GalleryContext = createContext( @@ -1013,8 +1015,9 @@ export default function Gallery() { emailList, openHiddenSection, isClipSearchResult, - }} - > + selectedFile: selected, + setSelectedFiles: setSelected, + }}> diff --git a/web/apps/photos/src/types/gallery/index.ts b/web/apps/photos/src/types/gallery/index.ts index a2e77f0b6..0e003761c 100644 --- a/web/apps/photos/src/types/gallery/index.ts +++ b/web/apps/photos/src/types/gallery/index.ts @@ -11,6 +11,9 @@ export type SelectedState = { count: number; collectionID: number; }; +export type SetSelectedState = React.Dispatch< + React.SetStateAction +>; export type SetFiles = React.Dispatch>; export type SetCollections = React.Dispatch>; export type SetLoading = React.Dispatch>; @@ -54,6 +57,8 @@ export type GalleryContextType = { emailList: string[]; openHiddenSection: (callback?: () => void) => void; isClipSearchResult: boolean; + setSelectedFiles: (value) => void; + selectedFile: SelectedState; }; export enum CollectionSelectorIntent { diff --git a/web/apps/photos/src/utils/photoFrame/index.ts b/web/apps/photos/src/utils/photoFrame/index.ts index 6e59524c5..4396c78b9 100644 --- a/web/apps/photos/src/utils/photoFrame/index.ts +++ b/web/apps/photos/src/utils/photoFrame/index.ts @@ -1,7 +1,8 @@ -import { logError } from "@ente/shared/sentry"; import { FILE_TYPE } from "constants/file"; -import { LivePhotoSourceURL, SourceURLs } from "services/download"; import { EnteFile } from "types/file"; +import { logError } from "@ente/shared/sentry"; +import { LivePhotoSourceURL, SourceURLs } from "services/download"; +import { SetSelectedState } from "types/gallery"; const WAIT_FOR_VIDEO_PLAYBACK = 1 * 1000; @@ -129,3 +130,55 @@ export async function updateFileSrcProps( file.src = url as string; } } + +export const handleSelectCreator = + ( + setSelected: SetSelectedState, + activeCollectionID: number, + setRangeStart? + ) => + (id: number, isOwnFile: boolean, index?: number) => + (checked: boolean) => { + if (typeof index !== 'undefined') { + if (checked) { + setRangeStart(index); + } else { + setRangeStart(undefined); + } + } + setSelected((selected) => { + if (selected.collectionID !== activeCollectionID) { + selected = { ownCount: 0, count: 0, collectionID: 0 }; + } + + const handleCounterChange = (count: number) => { + if (selected[id] === checked) { + return count; + } + if (checked) { + return count + 1; + } else { + return count - 1; + } + }; + + const handleAllCounterChange = () => { + if (isOwnFile) { + return { + ownCount: handleCounterChange(selected.ownCount), + count: handleCounterChange(selected.count), + }; + } else { + return { + count: handleCounterChange(selected.count), + }; + } + }; + return { + ...selected, + [id]: checked, + collectionID: activeCollectionID, + ...handleAllCounterChange(), + }; + }); + }; diff --git a/web/packages/shared/time/format.ts b/web/packages/shared/time/format.ts index 0e2dc68b5..1fc7ef089 100644 --- a/web/packages/shared/time/format.ts +++ b/web/packages/shared/time/format.ts @@ -1,5 +1,7 @@ import i18n, { t } from "i18next"; +const A_DAY = 24 * 60 * 60 * 1000; + const dateTimeFullFormatter1 = new Intl.DateTimeFormat(i18n.language, { weekday: "short", month: "short", @@ -76,3 +78,22 @@ export function formatDateRelative(date: number) { u as Intl.RelativeTimeFormatUnit, ); } + +export const isSameDay = (first, second) => { + return ( + first.getFullYear() === second.getFullYear() && + first.getMonth() === second.getMonth() && + first.getDate() === second.getDate() + ); +}; + +export const getDate = (item) => { + const currentDate = item.metadata.creationTime / 1000; + const date = isSameDay(new Date(currentDate), new Date()) + ? t('TODAY') + : isSameDay(new Date(currentDate), new Date(Date.now() - A_DAY)) + ? t('YESTERDAY') + : formatDate(currentDate); + + return date; +};