commit
a3c7611633
|
@ -426,7 +426,11 @@ const PhotoFrame = ({
|
||||||
handleSelect(filteredData[index].id, index)(!checked);
|
handleSelect(filteredData[index].id, index)(!checked);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const getThumbnail = (files: EnteFile[], index: number) =>
|
const getThumbnail = (
|
||||||
|
files: EnteFile[],
|
||||||
|
index: number,
|
||||||
|
isScrolling: boolean
|
||||||
|
) =>
|
||||||
files[index] ? (
|
files[index] ? (
|
||||||
<PreviewCard
|
<PreviewCard
|
||||||
key={`tile-${files[index].id}-selected-${
|
key={`tile-${files[index].id}-selected-${
|
||||||
|
@ -450,6 +454,7 @@ const PhotoFrame = ({
|
||||||
(index >= currentHover && index <= rangeStart)
|
(index >= currentHover && index <= rangeStart)
|
||||||
}
|
}
|
||||||
activeCollection={activeCollection}
|
activeCollection={activeCollection}
|
||||||
|
showPlaceholder={isScrolling}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<></>
|
<></>
|
||||||
|
|
|
@ -24,7 +24,6 @@ import { GalleryContext } from 'pages/gallery';
|
||||||
import { SpecialPadding } from 'styles/SpecialPadding';
|
import { SpecialPadding } from 'styles/SpecialPadding';
|
||||||
|
|
||||||
const A_DAY = 24 * 60 * 60 * 1000;
|
const A_DAY = 24 * 60 * 60 * 1000;
|
||||||
const NO_OF_PAGES = 2;
|
|
||||||
const FOOTER_HEIGHT = 90;
|
const FOOTER_HEIGHT = 90;
|
||||||
|
|
||||||
export enum ITEM_TYPE {
|
export enum ITEM_TYPE {
|
||||||
|
@ -153,7 +152,11 @@ interface Props {
|
||||||
width: number;
|
width: number;
|
||||||
filteredData: EnteFile[];
|
filteredData: EnteFile[];
|
||||||
showAppDownloadBanner: boolean;
|
showAppDownloadBanner: boolean;
|
||||||
getThumbnail: (files: EnteFile[], index: number) => JSX.Element;
|
getThumbnail: (
|
||||||
|
files: EnteFile[],
|
||||||
|
index: number,
|
||||||
|
isScrolling?: boolean
|
||||||
|
) => JSX.Element;
|
||||||
activeCollection: number;
|
activeCollection: number;
|
||||||
resetFetching: () => void;
|
resetFetching: () => void;
|
||||||
}
|
}
|
||||||
|
@ -512,10 +515,6 @@ export function PhotoList({
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const extraRowsToRender = Math.ceil(
|
|
||||||
(NO_OF_PAGES * height) / IMAGE_CONTAINER_MAX_HEIGHT
|
|
||||||
);
|
|
||||||
|
|
||||||
const generateKey = (index) => {
|
const generateKey = (index) => {
|
||||||
switch (timeStampList[index].itemType) {
|
switch (timeStampList[index].itemType) {
|
||||||
case ITEM_TYPE.FILE:
|
case ITEM_TYPE.FILE:
|
||||||
|
@ -527,7 +526,10 @@ export function PhotoList({
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderListItem = (listItem: TimeStampListItem) => {
|
const renderListItem = (
|
||||||
|
listItem: TimeStampListItem,
|
||||||
|
isScrolling: boolean
|
||||||
|
) => {
|
||||||
switch (listItem.itemType) {
|
switch (listItem.itemType) {
|
||||||
case ITEM_TYPE.TIME:
|
case ITEM_TYPE.TIME:
|
||||||
return listItem.dates ? (
|
return listItem.dates ? (
|
||||||
|
@ -556,7 +558,8 @@ export function PhotoList({
|
||||||
const ret = listItem.items.map((item, idx) =>
|
const ret = listItem.items.map((item, idx) =>
|
||||||
getThumbnail(
|
getThumbnail(
|
||||||
filteredDataCopy,
|
filteredDataCopy,
|
||||||
listItem.itemStartIndex + idx
|
listItem.itemStartIndex + idx,
|
||||||
|
isScrolling
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
if (listItem.groups) {
|
if (listItem.groups) {
|
||||||
|
@ -587,14 +590,15 @@ export function PhotoList({
|
||||||
width={width}
|
width={width}
|
||||||
itemCount={timeStampList.length}
|
itemCount={timeStampList.length}
|
||||||
itemKey={generateKey}
|
itemKey={generateKey}
|
||||||
overscanCount={extraRowsToRender}>
|
overscanCount={0}
|
||||||
{({ index, style }) => (
|
useIsScrolling>
|
||||||
|
{({ index, style, isScrolling }) => (
|
||||||
<ListItem style={style}>
|
<ListItem style={style}>
|
||||||
<ListContainer
|
<ListContainer
|
||||||
columns={columns}
|
columns={columns}
|
||||||
shrinkRatio={shrinkRatio}
|
shrinkRatio={shrinkRatio}
|
||||||
groups={timeStampList[index].groups}>
|
groups={timeStampList[index].groups}>
|
||||||
{renderListItem(timeStampList[index])}
|
{renderListItem(timeStampList[index], isScrolling)}
|
||||||
</ListContainer>
|
</ListContainer>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import React, { useContext, useLayoutEffect, useRef, useState } from 'react';
|
import React, { useContext, useLayoutEffect, useState } from 'react';
|
||||||
import { EnteFile } from 'types/file';
|
import { EnteFile } from 'types/file';
|
||||||
import { styled } from '@mui/material';
|
import { styled } from '@mui/material';
|
||||||
import PlayCircleOutlineOutlinedIcon from '@mui/icons-material/PlayCircleOutlineOutlined';
|
import PlayCircleOutlineOutlinedIcon from '@mui/icons-material/PlayCircleOutlineOutlined';
|
||||||
|
@ -18,18 +18,18 @@ import { formatDateRelative } from 'utils/time';
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
file: EnteFile;
|
file: EnteFile;
|
||||||
updateURL?: (url: string) => EnteFile;
|
updateURL: (url: string) => EnteFile;
|
||||||
onClick?: () => void;
|
onClick: () => void;
|
||||||
forcedEnable?: boolean;
|
selectable: boolean;
|
||||||
selectable?: boolean;
|
selected: boolean;
|
||||||
selected?: boolean;
|
onSelect: (checked: boolean) => void;
|
||||||
onSelect?: (checked: boolean) => void;
|
onHover: () => void;
|
||||||
onHover?: () => void;
|
onRangeSelect: () => void;
|
||||||
onRangeSelect?: () => void;
|
isRangeSelectActive: boolean;
|
||||||
isRangeSelectActive?: boolean;
|
selectOnClick: boolean;
|
||||||
selectOnClick?: boolean;
|
isInsSelectRange: boolean;
|
||||||
isInsSelectRange?: boolean;
|
activeCollection: number;
|
||||||
activeCollection?: number;
|
showPlaceholder: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Check = styled('input')<{ active: boolean }>`
|
const Check = styled('input')<{ active: boolean }>`
|
||||||
|
@ -203,7 +203,6 @@ export default function PreviewCard(props: IProps) {
|
||||||
file,
|
file,
|
||||||
onClick,
|
onClick,
|
||||||
updateURL,
|
updateURL,
|
||||||
forcedEnable,
|
|
||||||
selectable,
|
selectable,
|
||||||
selected,
|
selected,
|
||||||
onSelect,
|
onSelect,
|
||||||
|
@ -213,14 +212,13 @@ export default function PreviewCard(props: IProps) {
|
||||||
isRangeSelectActive,
|
isRangeSelectActive,
|
||||||
isInsSelectRange,
|
isInsSelectRange,
|
||||||
} = props;
|
} = props;
|
||||||
const isMounted = useRef(true);
|
|
||||||
const publicCollectionGalleryContext = useContext(
|
const publicCollectionGalleryContext = useContext(
|
||||||
PublicCollectionGalleryContext
|
PublicCollectionGalleryContext
|
||||||
);
|
);
|
||||||
const deduplicateContext = useContext(DeduplicateContext);
|
const deduplicateContext = useContext(DeduplicateContext);
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
if (file && !file.msrc) {
|
if (file && !file.msrc && !props.showPlaceholder) {
|
||||||
const main = async () => {
|
const main = async () => {
|
||||||
try {
|
try {
|
||||||
let url;
|
let url;
|
||||||
|
@ -236,18 +234,14 @@ export default function PreviewCard(props: IProps) {
|
||||||
} else {
|
} else {
|
||||||
url = await DownloadManager.getThumbnail(file);
|
url = await DownloadManager.getThumbnail(file);
|
||||||
}
|
}
|
||||||
if (isMounted.current) {
|
|
||||||
setImgSrc(url);
|
setImgSrc(url);
|
||||||
thumbs.set(file.id, url);
|
thumbs.set(file.id, url);
|
||||||
if (updateURL) {
|
|
||||||
const newFile = updateURL(url);
|
const newFile = updateURL(url);
|
||||||
file.msrc = newFile.msrc;
|
file.msrc = newFile.msrc;
|
||||||
file.html = newFile.html;
|
file.html = newFile.html;
|
||||||
file.src = newFile.src;
|
file.src = newFile.src;
|
||||||
file.w = newFile.w;
|
file.w = newFile.w;
|
||||||
file.h = newFile.h;
|
file.h = newFile.h;
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logError(e, 'preview card useEffect failed');
|
logError(e, 'preview card useEffect failed');
|
||||||
// no-op
|
// no-op
|
||||||
|
@ -262,12 +256,7 @@ export default function PreviewCard(props: IProps) {
|
||||||
main();
|
main();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}, [file, props.showPlaceholder]);
|
||||||
return () => {
|
|
||||||
// cool cool cool
|
|
||||||
isMounted.current = false;
|
|
||||||
};
|
|
||||||
}, [file]);
|
|
||||||
|
|
||||||
const handleClick = () => {
|
const handleClick = () => {
|
||||||
if (selectOnClick) {
|
if (selectOnClick) {
|
||||||
|
@ -300,10 +289,10 @@ export default function PreviewCard(props: IProps) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Cont
|
<Cont
|
||||||
id={`thumb-${file?.id}`}
|
id={`thumb-${file?.id}-${props.showPlaceholder}`}
|
||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
onMouseEnter={handleHover}
|
onMouseEnter={handleHover}
|
||||||
disabled={!forcedEnable && !file?.msrc && !imgSrc}
|
disabled={!file?.msrc && !imgSrc}
|
||||||
{...(selectable ? useLongPress(longPressCallback, 500) : {})}>
|
{...(selectable ? useLongPress(longPressCallback, 500) : {})}>
|
||||||
{selectable && (
|
{selectable && (
|
||||||
<Check
|
<Check
|
||||||
|
|
|
@ -13,11 +13,19 @@ import { logError } from 'utils/sentry';
|
||||||
import { FILE_TYPE } from 'constants/file';
|
import { FILE_TYPE } from 'constants/file';
|
||||||
import { CustomError } from 'utils/error';
|
import { CustomError } from 'utils/error';
|
||||||
import { openThumbnailCache } from './cacheService';
|
import { openThumbnailCache } from './cacheService';
|
||||||
|
import QueueProcessor, { PROCESSING_STRATEGY } from './queueProcessor';
|
||||||
|
|
||||||
|
const MAX_PARALLEL_DOWNLOADS = 10;
|
||||||
|
|
||||||
class DownloadManager {
|
class DownloadManager {
|
||||||
private fileObjectURLPromise = new Map<string, Promise<string[]>>();
|
private fileObjectURLPromise = new Map<string, Promise<string[]>>();
|
||||||
private thumbnailObjectURLPromise = new Map<number, Promise<string>>();
|
private thumbnailObjectURLPromise = new Map<number, Promise<string>>();
|
||||||
|
|
||||||
|
private thumbnailDownloadRequestsProcessor = new QueueProcessor<any>(
|
||||||
|
MAX_PARALLEL_DOWNLOADS,
|
||||||
|
PROCESSING_STRATEGY.LIFO
|
||||||
|
);
|
||||||
|
|
||||||
public async getThumbnail(file: EnteFile) {
|
public async getThumbnail(file: EnteFile) {
|
||||||
try {
|
try {
|
||||||
const token = getToken();
|
const token = getToken();
|
||||||
|
@ -34,7 +42,10 @@ class DownloadManager {
|
||||||
if (cacheResp) {
|
if (cacheResp) {
|
||||||
return URL.createObjectURL(await cacheResp.blob());
|
return URL.createObjectURL(await cacheResp.blob());
|
||||||
}
|
}
|
||||||
const thumb = await this.downloadThumb(token, file);
|
const thumb =
|
||||||
|
await this.thumbnailDownloadRequestsProcessor.queueUpRequest(
|
||||||
|
() => this.downloadThumb(token, file)
|
||||||
|
).promise;
|
||||||
const thumbBlob = new Blob([thumb]);
|
const thumbBlob = new Blob([thumb]);
|
||||||
|
|
||||||
thumbnailCache
|
thumbnailCache
|
||||||
|
|
|
@ -14,11 +14,14 @@ import { EnteFile } from 'types/file';
|
||||||
import { logError } from 'utils/sentry';
|
import { logError } from 'utils/sentry';
|
||||||
import { FILE_TYPE } from 'constants/file';
|
import { FILE_TYPE } from 'constants/file';
|
||||||
import { CustomError } from 'utils/error';
|
import { CustomError } from 'utils/error';
|
||||||
|
import QueueProcessor from './queueProcessor';
|
||||||
|
|
||||||
class PublicCollectionDownloadManager {
|
class PublicCollectionDownloadManager {
|
||||||
private fileObjectURLPromise = new Map<string, Promise<string[]>>();
|
private fileObjectURLPromise = new Map<string, Promise<string[]>>();
|
||||||
private thumbnailObjectURLPromise = new Map<number, Promise<string>>();
|
private thumbnailObjectURLPromise = new Map<number, Promise<string>>();
|
||||||
|
|
||||||
|
private thumbnailDownloadRequestsProcessor = new QueueProcessor<any>(5);
|
||||||
|
|
||||||
public async getThumbnail(
|
public async getThumbnail(
|
||||||
file: EnteFile,
|
file: EnteFile,
|
||||||
token: string,
|
token: string,
|
||||||
|
@ -46,11 +49,10 @@ class PublicCollectionDownloadManager {
|
||||||
if (cacheResp) {
|
if (cacheResp) {
|
||||||
return URL.createObjectURL(await cacheResp.blob());
|
return URL.createObjectURL(await cacheResp.blob());
|
||||||
}
|
}
|
||||||
const thumb = await this.downloadThumb(
|
const thumb =
|
||||||
token,
|
await this.thumbnailDownloadRequestsProcessor.queueUpRequest(
|
||||||
passwordToken,
|
() => this.downloadThumb(token, passwordToken, file)
|
||||||
file
|
).promise;
|
||||||
);
|
|
||||||
const thumbBlob = new Blob([thumb]);
|
const thumbBlob = new Blob([thumb]);
|
||||||
try {
|
try {
|
||||||
await thumbnailCache?.put(
|
await thumbnailCache?.put(
|
||||||
|
|
|
@ -8,6 +8,11 @@ interface RequestQueueItem {
|
||||||
canceller: { exec: () => void };
|
canceller: { exec: () => void };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum PROCESSING_STRATEGY {
|
||||||
|
FIFO,
|
||||||
|
LIFO,
|
||||||
|
}
|
||||||
|
|
||||||
export interface RequestCanceller {
|
export interface RequestCanceller {
|
||||||
exec: () => void;
|
exec: () => void;
|
||||||
}
|
}
|
||||||
|
@ -17,7 +22,10 @@ export default class QueueProcessor<T> {
|
||||||
|
|
||||||
private requestInProcessing = 0;
|
private requestInProcessing = 0;
|
||||||
|
|
||||||
constructor(private maxParallelProcesses: number) {}
|
constructor(
|
||||||
|
private maxParallelProcesses: number,
|
||||||
|
private processingStrategy = PROCESSING_STRATEGY.FIFO
|
||||||
|
) {}
|
||||||
|
|
||||||
public queueUpRequest(
|
public queueUpRequest(
|
||||||
request: (canceller?: RequestCanceller) => Promise<T>
|
request: (canceller?: RequestCanceller) => Promise<T>
|
||||||
|
@ -52,7 +60,10 @@ export default class QueueProcessor<T> {
|
||||||
|
|
||||||
private async processQueue() {
|
private async processQueue() {
|
||||||
while (this.requestQueue.length > 0) {
|
while (this.requestQueue.length > 0) {
|
||||||
const queueItem = this.requestQueue.shift();
|
const queueItem =
|
||||||
|
this.processingStrategy === PROCESSING_STRATEGY.LIFO
|
||||||
|
? this.requestQueue.pop()
|
||||||
|
: this.requestQueue.shift();
|
||||||
let response = null;
|
let response = null;
|
||||||
|
|
||||||
if (queueItem.isCanceled.status) {
|
if (queueItem.isCanceled.status) {
|
||||||
|
|
Loading…
Reference in a new issue