Merge pull request #716 from ente-io/thumbnail-scroll

Thumbnail scroll
This commit is contained in:
Neeraj Gupta 2022-09-22 14:18:46 +05:30 committed by GitHub
commit a3c7611633
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 78 additions and 56 deletions

View file

@ -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}
/>
) : (
<></>

View file

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

View file

@ -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;
}
}
setImgSrc(url);
thumbs.set(file.id, url);
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

View file

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

View file

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

View file

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