diff --git a/src/components/PhotoFrame.tsx b/src/components/PhotoFrame.tsx
index 58618384e..ffd4bf8ad 100644
--- a/src/components/PhotoFrame.tsx
+++ b/src/components/PhotoFrame.tsx
@@ -1,5 +1,4 @@
import {
- DeadCenter,
GalleryContext,
Search,
SelectedState,
@@ -14,18 +13,9 @@ import styled from 'styled-components';
import DownloadManager from 'services/downloadManager';
import constants from 'utils/strings/constants';
import AutoSizer from 'react-virtualized-auto-sizer';
-import { VariableSizeList as List } from 'react-window';
import PhotoSwipe from 'components/PhotoSwipe/PhotoSwipe';
import { isInsideBox, isSameDay as isSameDayAnyYear } from 'utils/search';
import { SetDialogMessage } from './MessageDialog';
-import {
- GAP_BTW_TILES,
- DATE_CONTAINER_HEIGHT,
- IMAGE_CONTAINER_MAX_HEIGHT,
- IMAGE_CONTAINER_MAX_WIDTH,
- MIN_COLUMNS,
- SPACE_BTW_DATES,
-} from 'types';
import { fileIsArchived, formatDateRelative } from 'utils/file';
import {
ALL_SECTION,
@@ -34,24 +24,7 @@ import {
} from './pages/gallery/Collections';
import { isSharedFile } from 'utils/file';
import { isPlaybackPossible } from 'utils/photoFrame';
-
-const NO_OF_PAGES = 2;
-const A_DAY = 24 * 60 * 60 * 1000;
-
-interface TimeStampListItem {
- itemType: ITEM_TYPE;
- items?: File[];
- itemStartIndex?: number;
- date?: string;
- dates?: {
- date: string;
- span: number;
- }[];
- groups?: number[];
- banner?: any;
- id?: string;
- height?: number;
-}
+import { PhotoList } from './PhotoList';
const Container = styled.div`
display: block;
@@ -66,60 +39,6 @@ const Container = styled.div`
}
`;
-const ListItem = styled.div`
- display: flex;
- justify-content: center;
-`;
-
-const getTemplateColumns = (columns: number, groups?: number[]): string => {
- if (groups) {
- const sum = groups.reduce((acc, item) => acc + item, 0);
- if (sum < columns) {
- groups[groups.length - 1] += columns - sum;
- }
- return groups
- .map((x) => `repeat(${x}, 1fr)`)
- .join(` ${SPACE_BTW_DATES}px `);
- } else {
- return `repeat(${columns}, 1fr)`;
- }
-};
-
-const ListContainer = styled.div<{ columns: number; groups?: number[] }>`
- user-select: none;
- display: grid;
- grid-template-columns: ${({ columns, groups }) =>
- getTemplateColumns(columns, groups)};
- grid-column-gap: ${GAP_BTW_TILES}px;
- padding: 0 24px;
- width: 100%;
- color: #fff;
-
- @media (max-width: ${IMAGE_CONTAINER_MAX_WIDTH * 4}px) {
- padding: 0 4px;
- }
-`;
-
-const DateContainer = styled.div<{ span: number }>`
- user-select: none;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- grid-column: span ${(props) => props.span};
- display: flex;
- align-items: center;
- height: ${DATE_CONTAINER_HEIGHT}px;
-`;
-
-const BannerContainer = styled.div<{ span: number }>`
- color: #979797;
- text-align: center;
- grid-column: span ${(props) => props.span};
- display: flex;
- justify-content: center;
- align-items: flex-end;
-`;
-
const EmptyScreen = styled.div`
display: flex;
justify-content: center;
@@ -133,12 +52,6 @@ const EmptyScreen = styled.div`
}
`;
-enum ITEM_TYPE {
- TIME = 'TIME',
- TILE = 'TILE',
- BANNER = 'BANNER',
-}
-
interface Props {
files: File[];
setFiles: SetFiles;
@@ -182,11 +95,11 @@ const PhotoFrame = ({
const [fetching, setFetching] = useState<{ [k: number]: boolean }>({});
const startTime = Date.now();
const galleryContext = useContext(GalleryContext);
- const listRef = useRef(null);
const [rangeStart, setRangeStart] = useState(null);
const [currentHover, setCurrentHover] = useState(null);
const [isShiftKeyPressed, setIsShiftKeyPressed] = useState(false);
-
+ const filteredDataRef = useRef([]);
+ const filteredData = filteredDataRef?.current ?? [];
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === 'Shift') {
@@ -223,10 +136,9 @@ const PhotoFrame = ({
}
}, [search]);
- useEffect(() => {
- listRef.current?.resetAfterIndex(0);
+ const resetFetching = () => {
setFetching({});
- }, [files, search, deleted]);
+ };
useEffect(() => {
if (selected.count === 0) {
@@ -234,6 +146,71 @@ const PhotoFrame = ({
}
}, [selected]);
+ useEffect(() => {
+ const idSet = new Set();
+ filteredDataRef.current = files
+ .map((item, index) => ({
+ ...item,
+ dataIndex: index,
+ ...(item.deleteBy && {
+ title: constants.AUTOMATIC_BIN_DELETE_MESSAGE(
+ formatDateRelative(item.deleteBy / 1000)
+ ),
+ }),
+ }))
+ .filter((item) => {
+ if (deleted.includes(item.id)) {
+ return false;
+ }
+ if (
+ search.date &&
+ !isSameDayAnyYear(search.date)(
+ new Date(item.metadata.creationTime / 1000)
+ )
+ ) {
+ return false;
+ }
+ if (
+ search.location &&
+ !isInsideBox(item.metadata, search.location)
+ ) {
+ return false;
+ }
+ if (activeCollection === ALL_SECTION && fileIsArchived(item)) {
+ return false;
+ }
+ if (
+ activeCollection === ARCHIVE_SECTION &&
+ !fileIsArchived(item)
+ ) {
+ return false;
+ }
+
+ if (isSharedFile(item) && !isSharedCollection) {
+ return false;
+ }
+ if (activeCollection === TRASH_SECTION && !item.isTrashed) {
+ return false;
+ }
+ if (activeCollection !== TRASH_SECTION && item.isTrashed) {
+ return false;
+ }
+ if (!idSet.has(item.id)) {
+ if (
+ activeCollection === ALL_SECTION ||
+ activeCollection === ARCHIVE_SECTION ||
+ activeCollection === TRASH_SECTION ||
+ activeCollection === item.collectionID
+ ) {
+ idSet.add(item.id);
+ return true;
+ }
+ return false;
+ }
+ return false;
+ });
+ }, [files, deleted, search, activeCollection]);
+
const updateUrl = (index: number) => (url: string) => {
files[index] = {
...files[index],
@@ -310,9 +287,7 @@ const PhotoFrame = ({
if (selected.collectionID !== activeCollection) {
setSelected({ count: 0, collectionID: 0 });
}
- if (rangeStart || rangeStart === 0) {
- setRangeStart(null);
- } else if (checked) {
+ if (checked) {
setRangeStart(index);
}
@@ -332,11 +307,11 @@ const PhotoFrame = ({
let leftEnd = -1;
let rightEnd = -1;
if (index < rangeStart) {
- leftEnd = index;
- rightEnd = rangeStart;
+ leftEnd = index + 1;
+ rightEnd = rangeStart - 1;
} else {
- leftEnd = rangeStart;
- rightEnd = index;
+ leftEnd = rangeStart + 1;
+ rightEnd = index - 1;
}
for (let i = leftEnd; i <= rightEnd; i++) {
handleSelect(filteredData[i].id)(true);
@@ -364,8 +339,8 @@ const PhotoFrame = ({
isShiftKeyPressed && (rangeStart || rangeStart === 0)
}
isInsSelectRange={
- (index >= rangeStart + 1 && index <= currentHover) ||
- (index >= currentHover && index <= rangeStart - 1)
+ (index >= rangeStart && index <= currentHover) ||
+ (index >= currentHover && index <= rangeStart)
}
/>
);
@@ -426,149 +401,6 @@ const PhotoFrame = ({
}
};
- const idSet = new Set();
- const filteredData = files
- .map((item, index) => ({
- ...item,
- dataIndex: index,
- ...(item.deleteBy && {
- title: constants.AUTOMATIC_BIN_DELETE_MESSAGE(
- formatDateRelative(item.deleteBy / 1000)
- ),
- }),
- }))
- .filter((item) => {
- if (deleted.includes(item.id)) {
- return false;
- }
- if (
- search.date &&
- !isSameDayAnyYear(search.date)(
- new Date(item.metadata.creationTime / 1000)
- )
- ) {
- return false;
- }
- if (
- search.location &&
- !isInsideBox(item.metadata, search.location)
- ) {
- return false;
- }
- if (activeCollection === ALL_SECTION && fileIsArchived(item)) {
- return false;
- }
- if (activeCollection === ARCHIVE_SECTION && !fileIsArchived(item)) {
- return false;
- }
-
- if (isSharedFile(item) && !isSharedCollection) {
- return false;
- }
- if (activeCollection === TRASH_SECTION && !item.isTrashed) {
- return false;
- }
- if (activeCollection !== TRASH_SECTION && item.isTrashed) {
- return false;
- }
- if (!idSet.has(item.id)) {
- if (
- activeCollection === ALL_SECTION ||
- activeCollection === ARCHIVE_SECTION ||
- activeCollection === TRASH_SECTION ||
- activeCollection === item.collectionID
- ) {
- idSet.add(item.id);
- return true;
- }
- return false;
- }
- return false;
- });
-
- const isSameDay = (first, second) =>
- first.getFullYear() === second.getFullYear() &&
- first.getMonth() === second.getMonth() &&
- first.getDate() === second.getDate();
-
- /**
- * Checks and merge multiple dates into a single row.
- *
- * @param items
- * @param columns
- * @returns
- */
- const mergeTimeStampList = (
- items: TimeStampListItem[],
- columns: number
- ): TimeStampListItem[] => {
- const newList: TimeStampListItem[] = [];
- let index = 0;
- let newIndex = 0;
- while (index < items.length) {
- const currItem = items[index];
- // If the current item is of type time, then it is not part of an ongoing date.
- // So, there is a possibility of merge.
- if (currItem.itemType === ITEM_TYPE.TIME) {
- // If new list pointer is not at the end of list then
- // we can add more items to the same list.
- if (newList[newIndex]) {
- // Check if items can be added to same list
- if (
- newList[newIndex + 1].items.length +
- items[index + 1].items.length <=
- columns
- ) {
- newList[newIndex].dates.push({
- date: currItem.date,
- span: items[index + 1].items.length,
- });
- newList[newIndex + 1].items = newList[
- newIndex + 1
- ].items.concat(items[index + 1].items);
- index += 2;
- } else {
- // Adding items would exceed the number of columns.
- // So, move new list pointer to the end. Hence, in next iteration,
- // items will be added to a new list.
- newIndex += 2;
- }
- } else {
- // New list pointer was at the end of list so simply add new items to the list.
- newList.push({
- ...currItem,
- date: null,
- dates: [
- {
- date: currItem.date,
- span: items[index + 1].items.length,
- },
- ],
- });
- newList.push(items[index + 1]);
- index += 2;
- }
- } else {
- // Merge cannot happen. Simply add all items to new list
- // and set new list point to the end of list.
- newList.push(currItem);
- index++;
- newIndex = newList.length;
- }
- }
- for (let i = 0; i < newList.length; i++) {
- const currItem = newList[i];
- const nextItem = newList[i + 1];
- if (currItem.itemType === ITEM_TYPE.TIME) {
- if (currItem.dates.length > 1) {
- currItem.groups = currItem.dates.map((item) => item.span);
- nextItem.groups = currItem.groups;
- }
- }
- }
- return newList;
- };
-
return (
<>
{!isFirstLoad && files.length === 0 && !isInSearchMode ? (
@@ -591,217 +423,22 @@ const PhotoFrame = ({
{constants.UPLOAD_FIRST_PHOTO}
- ) : filteredData.length ? (
+ ) : (
- {({ height, width }) => {
- let columns = Math.floor(
- width / IMAGE_CONTAINER_MAX_WIDTH
- );
- let listItemHeight = IMAGE_CONTAINER_MAX_HEIGHT;
- let skipMerge = false;
- if (columns < MIN_COLUMNS) {
- columns = MIN_COLUMNS;
- listItemHeight = width / MIN_COLUMNS;
- skipMerge = true;
- }
-
- let timeStampList: TimeStampListItem[] = [];
- let listItemIndex = 0;
- let currentDate = -1;
- filteredData.forEach((item, index) => {
- if (
- !isSameDay(
- new Date(
- item.metadata.creationTime / 1000
- ),
- new Date(currentDate)
- )
- ) {
- currentDate =
- item.metadata.creationTime / 1000;
- const dateTimeFormat =
- new Intl.DateTimeFormat('en-IN', {
- weekday: 'short',
- year: 'numeric',
- month: 'short',
- day: 'numeric',
- });
- timeStampList.push({
- itemType: ITEM_TYPE.TIME,
- date: isSameDay(
- new Date(currentDate),
- new Date()
- )
- ? 'Today'
- : isSameDay(
- new Date(currentDate),
- new Date(Date.now() - A_DAY)
- )
- ? 'Yesterday'
- : dateTimeFormat.format(
- currentDate
- ),
- id: currentDate.toString(),
- });
- timeStampList.push({
- itemType: ITEM_TYPE.TILE,
- items: [item],
- itemStartIndex: index,
- });
- listItemIndex = 1;
- } else if (listItemIndex < columns) {
- timeStampList[
- timeStampList.length - 1
- ].items.push(item);
- listItemIndex++;
- } else {
- listItemIndex = 1;
- timeStampList.push({
- itemType: ITEM_TYPE.TILE,
- items: [item],
- itemStartIndex: index,
- });
+ {({ height, width }) => (
+ {
- switch (timeStampList[index].itemType) {
- case ITEM_TYPE.TIME:
- return DATE_CONTAINER_HEIGHT;
- case ITEM_TYPE.TILE:
- return listItemHeight;
- default:
- return timeStampList[index].height;
- }
- };
-
- const photoFrameHeight = (() => {
- let sum = 0;
- for (let i = 0; i < timeStampList.length; i++) {
- sum += getItemSize(i);
- }
- return sum;
- })();
- files.length < 30 &&
- !isInSearchMode &&
- timeStampList.push({
- itemType: ITEM_TYPE.BANNER,
- banner: (
-
-
- {constants.INSTALL_MOBILE_APP()}
-
-
- ),
- id: 'install-banner',
- height: Math.max(
- 48,
- height - photoFrameHeight
- ),
- });
- const extraRowsToRender = Math.ceil(
- (NO_OF_PAGES * height) /
- IMAGE_CONTAINER_MAX_HEIGHT
- );
-
- const generateKey = (index) => {
- switch (timeStampList[index].itemType) {
- case ITEM_TYPE.TILE:
- return `${
- timeStampList[index].items[0].id
- }-${
- timeStampList[index].items.slice(
- -1
- )[0].id
- }`;
- default:
- return `${timeStampList[index].id}-${index}`;
- }
- };
-
- const renderListItem = (
- listItem: TimeStampListItem
- ) => {
- switch (listItem.itemType) {
- case ITEM_TYPE.TIME:
- return listItem.dates ? (
- listItem.dates.map((item) => (
- <>
-
- {item.date}
-
-
- >
- ))
- ) : (
-
- {listItem.date}
-
- );
- case ITEM_TYPE.BANNER:
- return listItem.banner;
- default: {
- const ret = listItem.items.map(
- (item, idx) =>
- getThumbnail(
- filteredData,
- listItem.itemStartIndex +
- idx
- )
- );
- if (listItem.groups) {
- let sum = 0;
- for (
- let i = 0;
- i < listItem.groups.length - 1;
- i++
- ) {
- sum = sum + listItem.groups[i];
- ret.splice(sum, 0,
);
- sum += 1;
- }
- }
- return ret;
- }
- }
- };
-
- return (
-
- {({ index, style }) => (
-
-
- {renderListItem(
- timeStampList[index]
- )}
-
-
- )}
-
- );
- }}
+ resetFetching={resetFetching}
+ />
+ )}
- ) : (
-
- {constants.NOTHING_HERE}
-
)}
>
);
diff --git a/src/components/PhotoList.tsx b/src/components/PhotoList.tsx
new file mode 100644
index 000000000..f8dce46ab
--- /dev/null
+++ b/src/components/PhotoList.tsx
@@ -0,0 +1,409 @@
+import React, { useRef, useEffect } from 'react';
+import { VariableSizeList as List } from 'react-window';
+import styled from 'styled-components';
+import { File } from 'services/fileService';
+import {
+ IMAGE_CONTAINER_MAX_WIDTH,
+ IMAGE_CONTAINER_MAX_HEIGHT,
+ MIN_COLUMNS,
+ DATE_CONTAINER_HEIGHT,
+ GAP_BTW_TILES,
+ SPACE_BTW_DATES,
+} from 'types';
+import constants from 'utils/strings/constants';
+
+const A_DAY = 24 * 60 * 60 * 1000;
+const NO_OF_PAGES = 2;
+
+enum ITEM_TYPE {
+ TIME = 'TIME',
+ TILE = 'TILE',
+ BANNER = 'BANNER',
+}
+
+interface TimeStampListItem {
+ itemType: ITEM_TYPE;
+ items?: File[];
+ itemStartIndex?: number;
+ date?: string;
+ dates?: {
+ date: string;
+ span: number;
+ }[];
+ groups?: number[];
+ banner?: any;
+ id?: string;
+ height?: number;
+}
+
+const ListItem = styled.div`
+ display: flex;
+ justify-content: center;
+`;
+
+const getTemplateColumns = (columns: number, groups?: number[]): string => {
+ if (groups) {
+ const sum = groups.reduce((acc, item) => acc + item, 0);
+ if (sum < columns) {
+ groups[groups.length - 1] += columns - sum;
+ }
+ return groups
+ .map((x) => `repeat(${x}, 1fr)`)
+ .join(` ${SPACE_BTW_DATES}px `);
+ } else {
+ return `repeat(${columns}, 1fr)`;
+ }
+};
+
+const ListContainer = styled.div<{ columns: number; groups?: number[] }>`
+ user-select: none;
+ display: grid;
+ grid-template-columns: ${({ columns, groups }) =>
+ getTemplateColumns(columns, groups)};
+ grid-column-gap: ${GAP_BTW_TILES}px;
+ padding: 0 24px;
+ width: 100%;
+ color: #fff;
+
+ @media (max-width: ${IMAGE_CONTAINER_MAX_WIDTH * 4}px) {
+ padding: 0 4px;
+ }
+`;
+
+const DateContainer = styled.div<{ span: number }>`
+ user-select: none;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ grid-column: span ${(props) => props.span};
+ display: flex;
+ align-items: center;
+ height: ${DATE_CONTAINER_HEIGHT}px;
+`;
+
+const BannerContainer = styled.div<{ span: number }>`
+ color: #979797;
+ text-align: center;
+ grid-column: span ${(props) => props.span};
+ display: flex;
+ justify-content: center;
+ align-items: flex-end;
+`;
+
+const NothingContainer = styled.div<{ span: number }>`
+ color: #979797;
+ text-align: center;
+ grid-column: span ${(props) => props.span};
+ display: flex;
+ justify-content: center;
+ align-items: center;
+`;
+
+interface Props {
+ height: number;
+ width: number;
+ filteredData: File[];
+ showBanner: boolean;
+ getThumbnail: (file: File[], index: number) => JSX.Element;
+ activeCollection: number;
+ resetFetching: () => void;
+}
+
+export function PhotoList({
+ height,
+ width,
+ filteredData,
+ showBanner,
+ getThumbnail,
+ activeCollection,
+ resetFetching,
+}: Props) {
+ const timeStampListRef = useRef([]);
+ const timeStampList = timeStampListRef?.current ?? [];
+ const filteredDataCopyRef = useRef([]);
+ const filteredDataCopy = filteredDataCopyRef.current ?? [];
+ const listRef = useRef(null);
+
+ let columns = Math.floor(width / IMAGE_CONTAINER_MAX_WIDTH);
+ let listItemHeight = IMAGE_CONTAINER_MAX_HEIGHT;
+
+ let skipMerge = false;
+ if (columns < MIN_COLUMNS) {
+ columns = MIN_COLUMNS;
+ listItemHeight = width / MIN_COLUMNS;
+ skipMerge = true;
+ }
+
+ const refreshList = () => {
+ listRef.current?.resetAfterIndex(0);
+ resetFetching();
+ };
+
+ useEffect(() => {
+ let timeStampList: TimeStampListItem[] = [];
+ let listItemIndex = 0;
+ let currentDate = -1;
+
+ filteredData.forEach((item, index) => {
+ if (
+ !isSameDay(
+ new Date(item.metadata.creationTime / 1000),
+ new Date(currentDate)
+ )
+ ) {
+ currentDate = item.metadata.creationTime / 1000;
+ const dateTimeFormat = new Intl.DateTimeFormat('en-IN', {
+ weekday: 'short',
+ year: 'numeric',
+ month: 'short',
+ day: 'numeric',
+ });
+ timeStampList.push({
+ itemType: ITEM_TYPE.TIME,
+ date: isSameDay(new Date(currentDate), new Date())
+ ? 'Today'
+ : isSameDay(
+ new Date(currentDate),
+ new Date(Date.now() - A_DAY)
+ )
+ ? 'Yesterday'
+ : dateTimeFormat.format(currentDate),
+ id: currentDate.toString(),
+ });
+ timeStampList.push({
+ itemType: ITEM_TYPE.TILE,
+ items: [item],
+ itemStartIndex: index,
+ });
+ listItemIndex = 1;
+ } else if (listItemIndex < columns) {
+ timeStampList[timeStampList.length - 1].items.push(item);
+ listItemIndex++;
+ } else {
+ listItemIndex = 1;
+ timeStampList.push({
+ itemType: ITEM_TYPE.TILE,
+ items: [item],
+ itemStartIndex: index,
+ });
+ }
+ });
+
+ if (!skipMerge) {
+ timeStampList = mergeTimeStampList(timeStampList, columns);
+ }
+ if (timeStampList.length === 0) {
+ timeStampList.push(getEmptyListItem());
+ }
+ if (showBanner) {
+ timeStampList.push(getBannerItem(timeStampList));
+ }
+
+ timeStampListRef.current = timeStampList;
+ filteredDataCopyRef.current = filteredData;
+ refreshList();
+ }, [width, height, filteredData, showBanner]);
+
+ const isSameDay = (first, second) =>
+ first.getFullYear() === second.getFullYear() &&
+ first.getMonth() === second.getMonth() &&
+ first.getDate() === second.getDate();
+
+ const getEmptyListItem = () => {
+ return {
+ itemType: ITEM_TYPE.BANNER,
+ banner: (
+
+ {constants.NOTHING_HERE}
+
+ ),
+ id: 'empty-list-banner',
+ height: height - 48,
+ };
+ };
+
+ const getBannerItem = (timeStampList) => {
+ const photoFrameHeight = (() => {
+ let sum = 0;
+ const getCurrentItemSize = getItemSize(timeStampList);
+ for (let i = 0; i < timeStampList.length; i++) {
+ sum += getCurrentItemSize(i);
+ }
+ return sum;
+ })();
+ return {
+ itemType: ITEM_TYPE.BANNER,
+ banner: (
+
+ {constants.INSTALL_MOBILE_APP()}
+
+ ),
+ id: 'install-banner',
+ height: Math.max(48, height - photoFrameHeight),
+ };
+ };
+ /**
+ * Checks and merge multiple dates into a single row.
+ *
+ * @param items
+ * @param columns
+ * @returns
+ */
+ const mergeTimeStampList = (
+ items: TimeStampListItem[],
+ columns: number
+ ): TimeStampListItem[] => {
+ const newList: TimeStampListItem[] = [];
+ let index = 0;
+ let newIndex = 0;
+ while (index < items.length) {
+ const currItem = items[index];
+ // If the current item is of type time, then it is not part of an ongoing date.
+ // So, there is a possibility of merge.
+ if (currItem.itemType === ITEM_TYPE.TIME) {
+ // If new list pointer is not at the end of list then
+ // we can add more items to the same list.
+ if (newList[newIndex]) {
+ // Check if items can be added to same list
+ if (
+ newList[newIndex + 1].items.length +
+ items[index + 1].items.length <=
+ columns
+ ) {
+ newList[newIndex].dates.push({
+ date: currItem.date,
+ span: items[index + 1].items.length,
+ });
+ newList[newIndex + 1].items = newList[
+ newIndex + 1
+ ].items.concat(items[index + 1].items);
+ index += 2;
+ } else {
+ // Adding items would exceed the number of columns.
+ // So, move new list pointer to the end. Hence, in next iteration,
+ // items will be added to a new list.
+ newIndex += 2;
+ }
+ } else {
+ // New list pointer was at the end of list so simply add new items to the list.
+ newList.push({
+ ...currItem,
+ date: null,
+ dates: [
+ {
+ date: currItem.date,
+ span: items[index + 1].items.length,
+ },
+ ],
+ });
+ newList.push(items[index + 1]);
+ index += 2;
+ }
+ } else {
+ // Merge cannot happen. Simply add all items to new list
+ // and set new list point to the end of list.
+ newList.push(currItem);
+ index++;
+ newIndex = newList.length;
+ }
+ }
+ for (let i = 0; i < newList.length; i++) {
+ const currItem = newList[i];
+ const nextItem = newList[i + 1];
+ if (currItem.itemType === ITEM_TYPE.TIME) {
+ if (currItem.dates.length > 1) {
+ currItem.groups = currItem.dates.map((item) => item.span);
+ nextItem.groups = currItem.groups;
+ }
+ }
+ }
+ return newList;
+ };
+
+ const getItemSize = (timeStampList) => (index) => {
+ switch (timeStampList[index].itemType) {
+ case ITEM_TYPE.TIME:
+ return DATE_CONTAINER_HEIGHT;
+ case ITEM_TYPE.TILE:
+ return listItemHeight;
+ default:
+ return timeStampList[index].height;
+ }
+ };
+
+ const extraRowsToRender = Math.ceil(
+ (NO_OF_PAGES * height) / IMAGE_CONTAINER_MAX_HEIGHT
+ );
+
+ const generateKey = (index) => {
+ switch (timeStampList[index].itemType) {
+ case ITEM_TYPE.TILE:
+ return `${timeStampList[index].items[0].id}-${
+ timeStampList[index].items.slice(-1)[0].id
+ }`;
+ default:
+ return `${timeStampList[index].id}-${index}`;
+ }
+ };
+
+ const renderListItem = (listItem: TimeStampListItem) => {
+ switch (listItem.itemType) {
+ case ITEM_TYPE.TIME:
+ return listItem.dates ? (
+ listItem.dates.map((item) => (
+ <>
+
+ {item.date}
+
+
+ >
+ ))
+ ) : (
+
+ {listItem.date}
+
+ );
+ case ITEM_TYPE.BANNER:
+ return listItem.banner;
+ default: {
+ const ret = listItem.items.map((item, idx) =>
+ getThumbnail(
+ filteredDataCopy,
+ listItem.itemStartIndex + idx
+ )
+ );
+ if (listItem.groups) {
+ let sum = 0;
+ for (let i = 0; i < listItem.groups.length - 1; i++) {
+ sum = sum + listItem.groups[i];
+ ret.splice(sum, 0,
);
+ sum += 1;
+ }
+ }
+ return ret;
+ }
+ }
+ };
+
+ return (
+
+ {({ index, style }) => (
+
+
+ {renderListItem(timeStampList[index])}
+
+
+ )}
+
+ );
+}
diff --git a/src/components/PhotoSwipe/PhotoSwipe.tsx b/src/components/PhotoSwipe/PhotoSwipe.tsx
index 27c7e495f..e7e39c2b0 100644
--- a/src/components/PhotoSwipe/PhotoSwipe.tsx
+++ b/src/components/PhotoSwipe/PhotoSwipe.tsx
@@ -8,8 +8,10 @@ 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';
@@ -40,8 +42,8 @@ import { logError } from 'utils/sentry';
import DatePicker from 'react-datepicker';
import 'react-datepicker/dist/react-datepicker.css';
-import TickIcon from 'components/icons/TickIcon';
import CloseIcon from 'components/icons/CloseIcon';
+import TickIcon from 'components/icons/TickIcon';
interface Iprops {
isOpen: boolean;
@@ -73,11 +75,6 @@ const Pre = styled.pre`
padding: 7px 15px;
`;
-const ButtonContainer = styled.div`
- margin-left: auto;
- width: 200px;
- padding: 5px 10px;
-`;
const WarningMessage = styled.div`
width: 100%;
margin-top: 0.25rem;
@@ -92,6 +89,11 @@ const renderInfoItem = (label: string, value: string | JSX.Element) => (
);
+const isSameDay = (first, second) =>
+ first.getFullYear() === second.getFullYear() &&
+ first.getMonth() === second.getMonth() &&
+ first.getDate() === second.getDate();
+
function RenderCreationTime({
file,
scheduleUpdate,
@@ -106,6 +108,7 @@ function RenderCreationTime({
const openEditMode = () => setIsInEditMode(true);
const closeEditMode = () => setIsInEditMode(false);
+
const saveEdits = async () => {
try {
if (isInEditMode && file) {
@@ -150,35 +153,18 @@ function RenderCreationTime({
onChange={handleChange}
timeInputLabel="Time:"
dateFormat="dd/MM/yyyy h:mm aa"
- showTimeInput
+ showTimeSelect
autoFocus
- shouldCloseOnSelect={false}
- onClickOutside={discardEdits}
- minDate={new Date(MIN_EDITED_CREATION_TIME)}
- maxDate={new Date()}
- showYearDropdown
- showMonthDropdown
- withPortal>
-
-
- {constants.CANCEL}
-
-
- {constants.SAVE}
-
-
-
+ 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>
) : (
formatDateTime(pickedTime)
)}
@@ -186,9 +172,20 @@ function RenderCreationTime({
-
-
-
+ {!isInEditMode ? (
+
+
+
+ ) : (
+ <>
+
+
+
+
+
+
+ >
+ )}
>
diff --git a/src/components/icons/TickIcon.tsx b/src/components/icons/TickIcon.tsx
index 8bd76968f..633d869a2 100644
--- a/src/components/icons/TickIcon.tsx
+++ b/src/components/icons/TickIcon.tsx
@@ -14,7 +14,7 @@ export default function TickIcon(props) {
}
TickIcon.defaultProps = {
- height: 28,
+ height: 20,
width: 20,
viewBox: '0 0 24 24',
};
diff --git a/src/components/pages/gallery/CollectionSortOptions.tsx b/src/components/pages/gallery/CollectionSortOptions.tsx
index 73241c145..aff226140 100644
--- a/src/components/pages/gallery/CollectionSortOptions.tsx
+++ b/src/components/pages/gallery/CollectionSortOptions.tsx
@@ -1,4 +1,4 @@
-import { Label, Value } from 'components/Container';
+import { Value } from 'components/Container';
import TickIcon from 'components/icons/TickIcon';
import React from 'react';
import { ListGroup, Popover, Row } from 'react-bootstrap';
@@ -23,13 +23,13 @@ const SortByOptionCreator =
(
-
+
{activeSortBy === props.sortBy && (
)}
-
+
setCollectionSortBy(props.sortBy)}
diff --git a/src/components/pages/gallery/PreviewCard.tsx b/src/components/pages/gallery/PreviewCard.tsx
index f3dcd4a31..b04963826 100644
--- a/src/components/pages/gallery/PreviewCard.tsx
+++ b/src/components/pages/gallery/PreviewCard.tsx
@@ -217,9 +217,8 @@ export default function PreviewCard(props: IProps) {
if (selectOnClick) {
if (isRangeSelectActive) {
onRangeSelect();
- } else {
- onSelect?.(!selected);
}
+ onSelect?.(!selected);
} else if (file?.msrc || imgSrc) {
onClick?.();
}
diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx
index 332e30bac..b324f1eea 100644
--- a/src/pages/_app.tsx
+++ b/src/pages/_app.tsx
@@ -413,6 +413,39 @@ const GlobalStyles = createGlobalStyle`
.react-datepicker__input-container > input {
width:100%;
}
+ .react-datepicker__navigation{
+ top:14px;
+ }
+ .react-datepicker, .react-datepicker__header,.react-datepicker__time-container .react-datepicker__time,.react-datepicker-time__header{
+ background-color: #202020;
+ color:#fff;
+ border-color: #444;
+ }
+ .react-datepicker__current-month,.react-datepicker__day-name, .react-datepicker__day, .react-datepicker__time-name{
+ color:#fff;
+ }
+ .react-datepicker__day--disabled{
+ color:#5b5656;
+ }
+ .react-datepicker__time-container .react-datepicker__time .react-datepicker__time-box ul.react-datepicker__time-list li.react-datepicker__time-list-item:hover{
+ background-color:#686868
+ }
+ .react-datepicker__time-container .react-datepicker__time .react-datepicker__time-box ul.react-datepicker__time-list li.react-datepicker__time-list-item--disabled :hover{
+ background-color: #202020;
+ }
+
+ .react-datepicker__time-container .react-datepicker__time .react-datepicker__time-box ul.react-datepicker__time-list li.react-datepicker__time-list-item--disabled{
+ color:#5b5656;
+ }
+ .react-datepicker{
+ padding-bottom:10px;
+ }
+ .react-datepicker__day:hover {
+ background-color:#686868
+ }
+ .react-datepicker__day--disabled:hover {
+ background-color: #202020;
+ }
`;
export const LogoImage = styled.img`
diff --git a/src/services/fileService.ts b/src/services/fileService.ts
index 522f61a8c..c6add81a3 100644
--- a/src/services/fileService.ts
+++ b/src/services/fileService.ts
@@ -10,19 +10,16 @@ import {
import { Collection } from './collectionService';
import HTTPService from './HTTPService';
import { logError } from 'utils/sentry';
-import {
- appendPhotoSwipeProps,
- decryptFile,
- mergeMetadata,
- sortFiles,
-} from 'utils/file';
+import { decryptFile, mergeMetadata, sortFiles } from 'utils/file';
import CryptoWorker from 'utils/crypto';
const ENDPOINT = getEndpoint();
-const FILES = 'files';
+const FILES_TABLE = 'files';
-export const MIN_EDITED_CREATION_TIME = '1800-01-01T00:00:00.000Z';
+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;
@@ -133,10 +130,18 @@ interface TrashRequestItems {
collectionID: number;
}
export const getLocalFiles = async () => {
- const files: Array = (await localForage.getItem(FILES)) || [];
+ const files: Array =
+ (await localForage.getItem(FILES_TABLE)) || [];
return files;
};
+export const setLocalFiles = async (files: File[]) => {
+ await localForage.setItem(FILES_TABLE, files);
+};
+
+const getCollectionLastSyncTime = async (collection: Collection) =>
+ (await localForage.getItem(`${collection.id}-time`)) ?? 0;
+
export const syncFiles = async (
collections: Collection[],
setFiles: (files: File[]) => void
@@ -144,15 +149,14 @@ export const syncFiles = async (
const localFiles = await getLocalFiles();
let files = await removeDeletedCollectionFiles(collections, localFiles);
if (files.length !== localFiles.length) {
- await localForage.setItem('files', files);
+ await setLocalFiles(files);
setFiles(sortFiles(mergeMetadata(files)));
}
for (const collection of collections) {
if (!getToken()) {
continue;
}
- const lastSyncTime =
- (await localForage.getItem(`${collection.id}-time`)) ?? 0;
+ const lastSyncTime = await getCollectionLastSyncTime(collection);
if (collection.updationTime === lastSyncTime) {
continue;
}
@@ -177,15 +181,14 @@ export const syncFiles = async (
}
files.push(file);
}
- await localForage.setItem('files', files);
+ await setLocalFiles(files);
await localForage.setItem(
`${collection.id}-time`,
collection.updationTime
);
- files = sortFiles(mergeMetadata(appendPhotoSwipeProps(files)));
- setFiles(files);
+ setFiles(sortFiles(mergeMetadata(files)));
}
- return mergeMetadata(appendPhotoSwipeProps(files));
+ return mergeMetadata(files);
};
export const getFiles = async (
@@ -196,10 +199,7 @@ export const getFiles = async (
): Promise => {
try {
const decryptedFiles: File[] = [];
- let time =
- sinceTime ||
- (await localForage.getItem(`${collection.id}-time`)) ||
- 0;
+ let time = sinceTime;
let resp;
do {
const token = getToken();
diff --git a/src/services/migrateThumbnailService.ts b/src/services/migrateThumbnailService.ts
index a6a16e349..2eb769707 100644
--- a/src/services/migrateThumbnailService.ts
+++ b/src/services/migrateThumbnailService.ts
@@ -10,6 +10,7 @@ import uploadHttpClient from 'services/upload/uploadHttpClient';
import { EncryptionResult, UploadURL } from 'services/upload/uploadService';
import { SetProgressTracker } from 'components/FixLargeThumbnail';
import { getFileType } from './upload/readFileService';
+import { getLocalTrash, getTrashedFiles } from './trashService';
const ENDPOINT = getEndpoint();
const REPLACE_THUMBNAIL_THRESHOLD = 500 * 1024; // 500KB
@@ -43,10 +44,14 @@ export async function replaceThumbnail(
const token = getToken();
const worker = await new CryptoWorker();
const files = await getLocalFiles();
- const largeThumbnailFiles = files.filter((file) =>
+ const trash = await getLocalTrash();
+ const trashFiles = getTrashedFiles(trash);
+ const largeThumbnailFiles = [...files, ...trashFiles].filter((file) =>
largeThumbnailFileIDs.has(file.id)
);
-
+ if (largeThumbnailFileIDs.size !== largeThumbnailFiles.length) {
+ logError(Error(), 'all large thumbnail files not found locally');
+ }
if (largeThumbnailFiles.length === 0) {
return completedWithError;
}
diff --git a/src/services/trashService.ts b/src/services/trashService.ts
index 882d6abc1..11cc3933a 100644
--- a/src/services/trashService.ts
+++ b/src/services/trashService.ts
@@ -1,12 +1,7 @@
import { SetFiles } from 'pages/gallery';
import { getEndpoint } from 'utils/common/apiUtil';
import { getToken } from 'utils/common/key';
-import {
- appendPhotoSwipeProps,
- decryptFile,
- mergeMetadata,
- sortFiles,
-} from 'utils/file';
+import { decryptFile, mergeMetadata, sortFiles } from 'utils/file';
import { logError } from 'utils/sentry';
import localForage from 'utils/storage/localForage';
import { Collection, getCollection } from './collectionService';
@@ -167,14 +162,12 @@ function removeRestoredOrDeletedTrashItems(trash: Trash) {
export function getTrashedFiles(trash: Trash) {
return mergeMetadata(
- appendPhotoSwipeProps(
- trash.map((trashedFile) => ({
- ...trashedFile.file,
- updationTime: trashedFile.updatedAt,
- isTrashed: true,
- deleteBy: trashedFile.deleteBy,
- }))
- )
+ trash.map((trashedFile) => ({
+ ...trashedFile.file,
+ updationTime: trashedFile.updatedAt,
+ isTrashed: true,
+ deleteBy: trashedFile.deleteBy,
+ }))
);
}
diff --git a/src/services/upload/exifService.ts b/src/services/upload/exifService.ts
index cacd81f2c..004958b6f 100644
--- a/src/services/upload/exifService.ts
+++ b/src/services/upload/exifService.ts
@@ -1,6 +1,5 @@
import exifr from 'exifr';
-import { logError } from 'utils/sentry';
import { NULL_LOCATION, Location } from './metadataService';
const EXIF_TAGS_NEEDED = [
@@ -20,20 +19,15 @@ interface ParsedEXIFData {
export async function getExifData(
receivedFile: globalThis.File
): Promise {
- try {
- const exifData = await exifr.parse(receivedFile, EXIF_TAGS_NEEDED);
- if (!exifData) {
- return { location: NULL_LOCATION, creationTime: null };
- }
- const parsedEXIFData = {
- location: getEXIFLocation(exifData),
- creationTime: getUNIXTime(exifData),
- };
- return parsedEXIFData;
- } catch (e) {
- logError(e, 'error reading exif data');
- // ignore exif parsing errors
+ const exifData = await exifr.parse(receivedFile, EXIF_TAGS_NEEDED);
+ if (!exifData) {
+ return { location: NULL_LOCATION, creationTime: null };
}
+ const parsedEXIFData = {
+ location: getEXIFLocation(exifData),
+ creationTime: getUNIXTime(exifData),
+ };
+ return parsedEXIFData;
}
function getUNIXTime(exifData: any) {
diff --git a/src/services/upload/metadataService.ts b/src/services/upload/metadataService.ts
index 530f77084..fab027ba6 100644
--- a/src/services/upload/metadataService.ts
+++ b/src/services/upload/metadataService.ts
@@ -34,7 +34,14 @@ export async function extractMetadata(
) {
let exifData = null;
if (fileTypeInfo.fileType === FILE_TYPE.IMAGE) {
- exifData = await getExifData(receivedFile);
+ try {
+ exifData = await getExifData(receivedFile);
+ } catch (e) {
+ logError(e, 'file missing exif data ', {
+ fileType: fileTypeInfo.exactType,
+ });
+ // ignore exif parsing errors
+ }
}
const extractedMetadata: MetadataObject = {
diff --git a/src/services/upload/uploadManager.ts b/src/services/upload/uploadManager.ts
index 7cb91377b..1c67cbb6d 100644
--- a/src/services/upload/uploadManager.ts
+++ b/src/services/upload/uploadManager.ts
@@ -1,4 +1,4 @@
-import { File, getLocalFiles } from '../fileService';
+import { File, getLocalFiles, setLocalFiles } from '../fileService';
import { Collection, getLocalCollections } from '../collectionService';
import { SetFiles } from 'pages/gallery';
import { ComlinkWorker, getDedicatedCryptoWorker } from 'utils/crypto';
@@ -8,7 +8,6 @@ import {
removeUnnecessaryFileProps,
} from 'utils/file';
import { logError } from 'utils/sentry';
-import localForage from 'utils/storage/localForage';
import {
getMetadataMapKey,
ParsedMetaDataJSON,
@@ -185,8 +184,7 @@ class UploadManager {
if (fileUploadResult === FileUploadResults.UPLOADED) {
this.existingFiles.push(file);
this.existingFiles = sortFiles(this.existingFiles);
- await localForage.setItem(
- 'files',
+ await setLocalFiles(
removeUnnecessaryFileProps(this.existingFiles)
);
this.setFiles(this.existingFiles);
diff --git a/src/utils/file/index.ts b/src/utils/file/index.ts
index 9efd84447..221fda554 100644
--- a/src/utils/file/index.ts
+++ b/src/utils/file/index.ts
@@ -420,13 +420,6 @@ export function mergeMetadata(files: File[]): File[] {
},
}));
}
-export function appendPhotoSwipeProps(files: File[]) {
- return files.map((file) => ({
- ...file,
- w: window.innerWidth,
- h: window.innerHeight,
- })) as File[];
-}
export function updateExistingFilePubMetadata(
existingFile: File,