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