Merge branch 'master' into update-file-title

This commit is contained in:
Abhinav 2021-11-05 13:26:12 +05:30
commit 91825db617
14 changed files with 623 additions and 562 deletions

View file

@ -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}
</Button>
</EmptyScreen>
) : filteredData.length ? (
) : (
<Container>
<AutoSizer>
{({ 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,
});
}
});
if (!skipMerge) {
timeStampList = mergeTimeStampList(
timeStampList,
columns
);
}
const getItemSize = (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 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: (
<BannerContainer span={columns}>
<p>
{constants.INSTALL_MOBILE_APP()}
</p>
</BannerContainer>
),
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) => (
<>
<DateContainer
key={item.date}
span={item.span}>
{item.date}
</DateContainer>
<div />
</>
))
) : (
<DateContainer span={columns}>
{listItem.date}
</DateContainer>
);
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, <div />);
sum += 1;
}
}
return ret;
}
}
};
return (
<List
key={`${columns}-${listItemHeight}-${activeCollection}`}
ref={listRef}
itemSize={getItemSize}
height={height}
{({ height, width }) => (
<PhotoList
width={width}
itemCount={timeStampList.length}
itemKey={generateKey}
overscanCount={extraRowsToRender}>
{({ index, style }) => (
<ListItem style={style}>
<ListContainer
columns={columns}
groups={
timeStampList[index].groups
}>
{renderListItem(
timeStampList[index]
height={height}
getThumbnail={getThumbnail}
filteredData={filteredData}
activeCollection={activeCollection}
showBanner={
files.length < 30 && !isInSearchMode
}
resetFetching={resetFetching}
/>
)}
</ListContainer>
</ListItem>
)}
</List>
);
}}
</AutoSizer>
<PhotoSwipe
isOpen={open}
@ -815,10 +452,6 @@ const PhotoFrame = ({
isTrashCollection={activeCollection === TRASH_SECTION}
/>
</Container>
) : (
<DeadCenter>
<div>{constants.NOTHING_HERE}</div>
</DeadCenter>
)}
</>
);

View file

@ -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: (
<NothingContainer span={columns}>
<div>{constants.NOTHING_HERE}</div>
</NothingContainer>
),
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: (
<BannerContainer span={columns}>
<p>{constants.INSTALL_MOBILE_APP()}</p>
</BannerContainer>
),
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) => (
<>
<DateContainer key={item.date} span={item.span}>
{item.date}
</DateContainer>
<div />
</>
))
) : (
<DateContainer span={columns}>
{listItem.date}
</DateContainer>
);
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, <div />);
sum += 1;
}
}
return ret;
}
}
};
return (
<List
key={`${activeCollection}`}
ref={listRef}
itemSize={getItemSize(timeStampList)}
height={height}
width={width}
itemCount={timeStampList.length}
itemKey={generateKey}
overscanCount={extraRowsToRender}>
{({ index, style }) => (
<ListItem style={style}>
<ListContainer
columns={columns}
groups={timeStampList[index].groups}>
{renderListItem(timeStampList[index])}
</ListContainer>
</ListItem>
)}
</List>
);
}

View file

@ -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) => (
</Row>
);
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>
<ButtonContainer
style={{
marginLeft: 'auto',
width: '200px',
justifyContent: 'flex-end',
padding: '5px 10px ',
}}>
<Button
style={{ marginRight: '20px' }}
variant="outline-secondary"
onClick={discardEdits}>
{constants.CANCEL}
</Button>
<Button
variant="outline-success"
onClick={saveEdits}>
{constants.SAVE}
</Button>
</ButtonContainer>
</DatePicker>
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>
) : (
formatDateTime(pickedTime)
)}
@ -186,9 +172,20 @@ function RenderCreationTime({
<Value
width={isInEditMode ? '20%' : '10%'}
style={{ cursor: 'pointer', marginLeft: '10px' }}>
{!isInEditMode ? (
<IconButton onClick={openEditMode}>
<EditIcon />
</IconButton>
) : (
<>
<IconButton onClick={saveEdits}>
<TickIcon />
</IconButton>
<IconButton onClick={discardEdits}>
<CloseIcon />
</IconButton>
</>
)}
</Value>
</Row>
</>

View file

@ -14,7 +14,7 @@ export default function TickIcon(props) {
}
TickIcon.defaultProps = {
height: 28,
height: 20,
width: 20,
viewBox: '0 0 24 24',
};

View file

@ -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 =
(
<MenuItem>
<Row>
<Label width="20px">
<Value width="20px">
{activeSortBy === props.sortBy && (
<TickWrapper>
<TickIcon />
</TickWrapper>
)}
</Label>
</Value>
<Value width="165px">
<MenuLink
onClick={() => setCollectionSortBy(props.sortBy)}

View file

@ -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?.();
}

View file

@ -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`

View file

@ -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<File> = (await localForage.getItem<File[]>(FILES)) || [];
const files: Array<File> =
(await localForage.getItem<File[]>(FILES_TABLE)) || [];
return files;
};
export const setLocalFiles = async (files: File[]) => {
await localForage.setItem(FILES_TABLE, files);
};
const getCollectionLastSyncTime = async (collection: Collection) =>
(await localForage.getItem<number>(`${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<number>(`${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<File[]> => {
try {
const decryptedFiles: File[] = [];
let time =
sinceTime ||
(await localForage.getItem<number>(`${collection.id}-time`)) ||
0;
let time = sinceTime;
let resp;
do {
const token = getToken();

View file

@ -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;
}

View file

@ -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,
}))
)
);
}

View file

@ -1,6 +1,5 @@
import exifr from 'exifr';
import { logError } from 'utils/sentry';
import { NULL_LOCATION, Location } from './metadataService';
const EXIF_TAGS_NEEDED = [
@ -20,7 +19,6 @@ interface ParsedEXIFData {
export async function getExifData(
receivedFile: globalThis.File
): Promise<ParsedEXIFData> {
try {
const exifData = await exifr.parse(receivedFile, EXIF_TAGS_NEEDED);
if (!exifData) {
return { location: NULL_LOCATION, creationTime: null };
@ -30,10 +28,6 @@ export async function getExifData(
creationTime: getUNIXTime(exifData),
};
return parsedEXIFData;
} catch (e) {
logError(e, 'error reading exif data');
// ignore exif parsing errors
}
}
function getUNIXTime(exifData: any) {

View file

@ -34,7 +34,14 @@ export async function extractMetadata(
) {
let exifData = null;
if (fileTypeInfo.fileType === FILE_TYPE.IMAGE) {
try {
exifData = await getExifData(receivedFile);
} catch (e) {
logError(e, 'file missing exif data ', {
fileType: fileTypeInfo.exactType,
});
// ignore exif parsing errors
}
}
const extractedMetadata: MetadataObject = {

View file

@ -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);

View file

@ -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,