diff --git a/web/apps/photos/src/components/FileList.tsx b/web/apps/photos/src/components/FileList.tsx index 132c518e5d..07ff92236d 100644 --- a/web/apps/photos/src/components/FileList.tsx +++ b/web/apps/photos/src/components/FileList.tsx @@ -309,47 +309,31 @@ export const FileList: React.FC = ({ ), ); } else { - let listItemIndex = 0; - let lastCreationTime: number | undefined; - for (const [index, af] of annotatedFiles.entries()) { - const creationTime = fileCreationTime(af.file) / 1000; - if ( - !lastCreationTime || - !isSameDay( - new Date(creationTime), - new Date(lastCreationTime), - ) - ) { - lastCreationTime = creationTime; - - items.push({ - height: dateListItemHeight, - tag: "date", - date: af.timelineDateString, - }); - items.push({ - height: fileItemHeight, - tag: "file", - items: [af], - itemStartIndex: index, - }); - listItemIndex = 1; - } else if (listItemIndex < columns) { - items[items.length - 1]!.items!.push(af); - listItemIndex++; - } else { - listItemIndex = 1; - items.push({ - height: fileItemHeight, - tag: "file", - items: [af], - itemStartIndex: index, - }); - } - } - if (!isSmallerLayout) { - items = mergeRowsWherePossible(items, columns); + let i = 0; + for (const split of splitByDate(annotatedFiles)) { + items.push({ + height: dateListItemHeight, + tag: "date", + dGroups: [ + { + date: split[0]!.timelineDateString, + dateSpan: split.length, + }, + ], + }); + items.push({ + height: fileItemHeight, + tag: "file", + fGroups: [ + { annotatedFiles: split, annotatedFilesStartIndex: i }, + ], + }); + i += split.length; } + // TODO(RE): + // if (!isSmallerLayout) { + // items = mergeRowsWherePossible(items, columns); + // } } if (!annotatedFiles.length) { @@ -540,6 +524,38 @@ export const FileList: React.FC = ({ const haveSelection = selected.count > 0; switch (listItem.tag) { case "date": + if (listItem.dGroups) { + return intersperseWithGaps( + listItem.dGroups, + ({ date, dateSpan }) => ( + + {haveSelection && ( + + onChangeSelectAllCheckBox(date) + } + size="small" + sx={{ pl: 0 }} + /> + )} + {date} + + ), + ({ date }) =>
, + ); + } return listItem.dates ? ( listItem.dates .map((item) => [ @@ -833,6 +849,22 @@ export const FileList: React.FC = ({ ); }; +/** + * Return a new array of splits, each split containing {@link annotatedFiles} + * which have the same {@link timelineDateString}. + */ +const splitByDate = (annotatedFiles: FileListAnnotatedFile[]) => + annotatedFiles.reduce( + (splits, annotatedFile) => ( + splits.at(-1)?.at(0)?.timelineDateString == + annotatedFile.timelineDateString + ? splits.at(-1)?.push(annotatedFile) + : splits.push([annotatedFile]), + splits + ), + new Array(), + ); + /** * Merge multiple dates into a single row. */ @@ -913,6 +945,16 @@ const mergeRowsWherePossible = ( return newList; }; +/** + * For each element of {@link xs}, obtain an element by applying {@link f}, + * then obtain a gap element by applying {@link g}. Return a flattened array + * containing all of these, except the trailing gap. + */ +const intersperseWithGaps = (xs: T[], f: (x: T) => U, g: (x: T) => U) => { + const ys = xs.map((x) => [f(x), g(x)]).flat(); + return ys.slice(0, ys.length - 1); +}; + /** * A list item container that spans the full width. */