Ver Fonte

Optimize gallery for frist load.

Pushkar Anand há 4 anos atrás
pai
commit
5520b94b57

+ 80 - 84
src/components/PhotoFrame.tsx

@@ -2,12 +2,13 @@ import router from 'next/router';
 import {
     DeadCenter,
     FILE_TYPE,
+    GalleryContext,
     Search,
     SetFiles,
     setSearchStats,
 } from 'pages/gallery';
 import PreviewCard from 'pages/gallery/components/PreviewCard';
-import React, {useEffect, useState} from 'react';
+import React, {useContext, useEffect, useState} from 'react';
 import {Button} from 'react-bootstrap';
 import {File} from 'services/fileService';
 import styled from 'styled-components';
@@ -30,6 +31,8 @@ interface TimeStampListItem {
     itemStartIndex?: number;
     date?: string;
     banner?: any;
+    id?: string;
+    height?: number;
 }
 
 const Container = styled.div`
@@ -76,6 +79,14 @@ const DateContainer = styled.div`
     padding-top: 15px;
 `;
 
+const BannerContainer = styled.div`
+    color: #979797;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    text-align: center;
+`;
+
 const EmptyScreen = styled.div`
     display: flex;
     justify-content: center;
@@ -100,7 +111,6 @@ interface Props {
     setFiles: SetFiles;
     syncWithRemote: () => Promise<void>;
     favItemIds: Set<number>;
-    sinceTime: number;
     setSelected;
     selected;
     isFirstLoad;
@@ -116,7 +126,6 @@ const PhotoFrame = ({
     setFiles,
     syncWithRemote,
     favItemIds,
-    sinceTime,
     setSelected,
     selected,
     isFirstLoad,
@@ -130,6 +139,7 @@ const PhotoFrame = ({
     const [currentIndex, setCurrentIndex] = useState<number>(0);
     const fetching: { [k: number]: boolean } = {};
     const startTime = Date.now();
+    const galleryContext = useContext(GalleryContext);
 
     useEffect(() => {
         if (searchMode) {
@@ -220,7 +230,13 @@ const PhotoFrame = ({
 
     const getSlideData = async (instance: any, index: number, item: File) => {
         if (!item.msrc) {
-            const url = await DownloadManager.getPreview(item);
+            let url;
+            if (galleryContext.thumbs.has(item.id)) {
+                url = galleryContext.thumbs.get(item.id);
+            } else {
+                url = await DownloadManager.getPreview(item);
+                galleryContext.thumbs.set(item.id, url);
+            }
             updateUrl(item.dataIndex)(url);
             item.msrc = url;
             if (!item.src) {
@@ -237,7 +253,13 @@ const PhotoFrame = ({
         }
         if (!fetching[item.dataIndex]) {
             fetching[item.dataIndex] = true;
-            const url = await DownloadManager.getFile(item);
+            let url;
+            if (galleryContext.files.has(item.id)) {
+                url = galleryContext.files.get(item.id);
+            } else {
+                url = await DownloadManager.getFile(item);
+                galleryContext.files.set(item.id, url);
+            }
             updateSrcUrl(item.dataIndex, url);
             if (item.metadata.fileType === FILE_TYPE.VIDEO) {
                 item.html = `
@@ -368,6 +390,7 @@ const PhotoFrame = ({
                                                 dateTimeFormat.format(
                                                     currentDate,
                                                 ),
+                                        id: currentDate.toString(),
                                     });
                                     timeStampList.push({
                                         itemType: ITEM_TYPE.TILE,
@@ -389,106 +412,79 @@ const PhotoFrame = ({
                                     });
                                 }
                             });
-                            files.length < 30 &&
-                                !searchMode &&
+                            files.length < 30 && !searchMode &&
                                 timeStampList.push({
                                     itemType: ITEM_TYPE.BANNER,
                                     banner: (
-                                        <div
-                                            style={{
-                                                color: '#979797',
-                                                display: 'flex',
-                                                alignItems: 'center',
-                                                justifyContent: 'center',
-                                                textAlign: 'center',
-                                            }}
-                                        >
+                                        <BannerContainer>
                                             {constants.INSTALL_MOBILE_APP()}
-                                        </div>
+                                        </BannerContainer>
                                     ),
+                                    id: 'install-banner',
+                                    height: 48,
                                 });
                             const extraRowsToRender = Math.ceil(
                                 (NO_OF_PAGES * height) / IMAGE_CONTAINER_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 getItemSize = (index) => {
+                                switch (timeStampList[index].itemType) {
+                                case ITEM_TYPE.TIME:
+                                    return DATE_CONTAINER_HEIGHT;
+                                case ITEM_TYPE.TILE:
+                                    return IMAGE_CONTAINER_HEIGHT;
+                                default:
+                                    return timeStampList[index].height;
+                                }
+                            };
+
+                            const renderListItem = (listItem) => {
+                                switch (listItem.itemType) {
+                                case ITEM_TYPE.TIME:
+                                    return (
+                                        <DateContainer>
+                                            {listItem.date}
+                                        </DateContainer>
+                                    );
+                                case ITEM_TYPE.BANNER:
+                                    return listItem.banner;
+                                default:
+                                    return (listItem.items.map(
+                                        (item, idx) => getThumbnail(
+                                            filteredData,
+                                            listItem.itemStartIndex + idx,
+                                        ),
+                                    ));
+                                }
+                            };
+
                             return (
                                 <List
-                                    itemSize={(index) => (timeStampList[index].itemType ===
-                                        ITEM_TYPE.TILE ?
-                                        IMAGE_CONTAINER_HEIGHT :
-                                        DATE_CONTAINER_HEIGHT)}
+                                    itemSize={getItemSize}
                                     height={height}
                                     width={width}
                                     itemCount={timeStampList.length}
-                                    key={`${router.query.collection}-${columns}-${sinceTime}`}
+                                    itemKey={generateKey}
                                     overscanCount={extraRowsToRender}
                                 >
                                     {({index, style}) => (
-                                        <ListItem
-                                            style={
-                                                timeStampList[index]
-                                                    .itemType ===
-                                                    ITEM_TYPE.BANNER ?
-                                                    {
-                                                        ...style,
-                                                        top: Math.max(
-                                                            Number(
-                                                                style.top,
-                                                            ),
-                                                            height - 45,
-                                                        ),
-                                                        height:
-                                                                  width < 450 ?
-                                                                      Number(
-                                                                          style.height,
-                                                                      ) * 2 :
-                                                                      style.height,
-                                                    } :
-                                                    style
-                                            }
-                                        >
+                                        <ListItem style={style}>
                                             <ListContainer
                                                 columns={
-                                                    timeStampList[index]
-                                                        .itemType ===
-                                                        ITEM_TYPE.TILE ?
-                                                        columns :
-                                                        1
+                                                    timeStampList[index].itemType === ITEM_TYPE.TILE ?
+                                                        columns :1
                                                 }
                                             >
-                                                {timeStampList[index]
-                                                    .itemType ===
-                                                    ITEM_TYPE.TIME ? (
-                                                        <DateContainer>
-                                                            {
-                                                                timeStampList[
-                                                                    index
-                                                                ].date
-                                                            }
-                                                        </DateContainer>
-                                                    ) : timeStampList[index]
-                                                        .itemType ===
-                                                      ITEM_TYPE.BANNER ? (
-                                                            <>
-                                                                {
-                                                                    timeStampList[
-                                                                        index
-                                                                    ].banner
-                                                                }
-                                                            </>
-                                                        ) : (
-                                                            timeStampList[
-                                                                index
-                                                            ].items.map(
-                                                                (item, idx) => getThumbnail(
-                                                                    filteredData,
-                                                                    timeStampList[
-                                                                        index
-                                                                    ]
-                                                                        .itemStartIndex +
-                                                                        idx,
-                                                                ),
-                                                            )
-                                                        )}
+                                                {renderListItem(timeStampList[index])}
                                             </ListContainer>
                                         </ListItem>
                                     )}

+ 13 - 3
src/pages/gallery/components/PreviewCard.tsx

@@ -1,9 +1,10 @@
-import React, {useEffect, useRef, useState} from 'react';
+import React, {useContext, useLayoutEffect, useRef, useState} from 'react';
 import {File} from 'services/fileService';
 import styled from 'styled-components';
 import PlayCircleOutline from 'components/PlayCircleOutline';
 import DownloadManager from 'services/downloadManager';
 import useLongPress from 'utils/common/useLongPress';
+import {GalleryContext} from '..';
 
 interface IProps {
     file: File;
@@ -103,6 +104,7 @@ const Cont = styled.div<{ disabled: boolean; selected: boolean }>`
 
 export default function PreviewCard(props: IProps) {
     const [imgSrc, setImgSrc] = useState<string>();
+    const {thumbs} = useContext(GalleryContext);
     const {
         file,
         onClick,
@@ -114,17 +116,25 @@ export default function PreviewCard(props: IProps) {
         selectOnClick,
     } = props;
     const isMounted = useRef(true);
-    useEffect(() => {
+    useLayoutEffect(() => {
         if (file && !file.msrc) {
             const main = async () => {
                 const url = await DownloadManager.getPreview(file);
                 if (isMounted.current) {
                     setImgSrc(url);
+                    thumbs.set(file.id, url);
                     file.msrc = url;
                     updateUrl(url);
                 }
             };
-            main();
+
+            if (thumbs.has(file.id)) {
+                const thumbImgSrc = thumbs.get(file.id);
+                setImgSrc(thumbImgSrc);
+                file.msrc = thumbImgSrc;
+            } else {
+                main();
+            }
         }
         return () => {
             isMounted.current = false;

+ 126 - 116
src/pages/gallery/index.tsx

@@ -1,4 +1,4 @@
-import React, {useEffect, useRef, useState} from 'react';
+import React, {createContext, useEffect, useRef, useState} from 'react';
 import {useRouter} from 'next/router';
 import {clearKeys, getKey, SESSION_KEYS} from 'utils/storage/sessionStorage';
 import {
@@ -95,6 +95,18 @@ export interface SearchStats {
     timeTaken: number;
 }
 
+type GalleryContextType = {
+    thumbs: Map<number, string>;
+    files: Map<number, string>
+}
+
+const defaultGalleryContext: GalleryContextType = {
+    thumbs: new Map(),
+    files: new Map(),
+};
+
+export const GalleryContext = createContext<GalleryContextType>(defaultGalleryContext);
+
 export default function Gallery() {
     const router = useRouter();
     const [collections, setCollections] = useState<Collection[]>([]);
@@ -102,7 +114,6 @@ export default function Gallery() {
     const [files, setFiles] = useState<File[]>(null);
     const [favItemIds, setFavItemIds] = useState<Set<number>>();
     const [bannerMessage, setBannerMessage] = useState<string>(null);
-    const [sinceTime, setSinceTime] = useState(0);
     const [isFirstLoad, setIsFirstLoad] = useState(false);
     const [isFirstFetch, setIsFirstFetch] = useState(false);
     const [selected, setSelected] = useState<selectedState>({count: 0});
@@ -190,7 +201,7 @@ export default function Gallery() {
             await billingService.updatePlans();
             await billingService.syncSubscription();
             const collections = await syncCollections();
-            const {files, isUpdated} = await syncFiles(collections);
+            const {files} = await syncFiles(collections, setFiles);
             const nonEmptyCollections = getNonEmptyCollections(
                 collections,
                 files,
@@ -201,12 +212,11 @@ export default function Gallery() {
             );
             const favItemIds = await getFavItemIds(files);
             setCollections(nonEmptyCollections);
-            if (isUpdated) {
-                setFiles(files);
-            }
+            // if (isUpdated) {
+            //     setFiles(files);
+            // }
             setCollectionsAndTheirLatestFile(collectionAndItsLatestFile);
             setFavItemIds(favItemIds);
-            setSinceTime(new Date().getTime());
         } catch (e) {
             switch (e.message) {
             case errorCodes.ERR_SESSION_EXPIRED:
@@ -283,123 +293,123 @@ export default function Gallery() {
 
     const updateSearch = (search: Search) => {
         setSearch(search);
-        setSinceTime(new Date().getTime());
         setSearchStats(null);
     };
     return (
-        <FullScreenDropZone
-            getRootProps={getRootProps}
-            getInputProps={getInputProps}
-            showCollectionSelector={setCollectionSelectorView.bind(null, true)}
-        >
-            {loading && (
-                <LoadingOverlay>
-                    <EnteSpinner />
-                </LoadingOverlay>
-            )}
-            <LoadingBar color="#2dc262" ref={loadingBar} />
-            {isFirstLoad && (
-                <AlertContainer>
-                    {constants.INITIAL_LOAD_DELAY_WARNING}
-                </AlertContainer>
-            )}
-            <PlanSelector
-                modalView={planModalView}
-                closeModal={() => setPlanModalView(false)}
-                setDialogMessage={setDialogMessage}
-                setLoading={setLoading}
-            />
-            <AlertBanner bannerMessage={bannerMessage} />
-            <MessageDialog
-                size="lg"
-                show={dialogView}
-                onHide={() => setDialogView(false)}
-                attributes={dialogMessage}
-            />
-            <SearchBar
-                isOpen={searchMode}
-                setOpen={setSearchMode}
-                loadingBar={loadingBar}
-                isFirstFetch={isFirstFetch}
-                setCollections={setCollections}
-                setSearch={updateSearch}
-                files={files}
-                searchStats={searchStats}
-            />
-            <Collections
-                collections={collections}
-                searchMode={searchMode}
-                selected={Number(router.query.collection)}
-                selectCollection={selectCollection}
-                syncWithRemote={syncWithRemote}
-                setDialogMessage={setDialogMessage}
-                setCollectionNamerAttributes={setCollectionNamerAttributes}
-                startLoadingBar={loadingBar.current?.continuousStart}
-            />
-            <CollectionNamer
-                show={collectionNamerView}
-                onHide={setCollectionNamerView.bind(null, false)}
-                attributes={collectionNamerAttributes}
-            />
-            <CollectionSelector
-                show={collectionSelectorView}
-                onHide={setCollectionSelectorView.bind(null, false)}
-                collectionsAndTheirLatestFile={collectionsAndTheirLatestFile}
-                directlyShowNextModal={
-                    collectionsAndTheirLatestFile?.length === 0
-                }
-                attributes={collectionSelectorAttributes}
-            />
-            <Upload
-                syncWithRemote={syncWithRemote}
-                setBannerMessage={setBannerMessage}
-                acceptedFiles={acceptedFiles}
-                existingFiles={files}
-                setCollectionSelectorAttributes={
-                    setCollectionSelectorAttributes
-                }
-                closeCollectionSelector={setCollectionSelectorView.bind(
-                    null,
-                    false,
+        <GalleryContext.Provider value={defaultGalleryContext}>
+            <FullScreenDropZone
+                getRootProps={getRootProps}
+                getInputProps={getInputProps}
+                showCollectionSelector={setCollectionSelectorView.bind(null, true)}
+            >
+                {loading && (
+                    <LoadingOverlay>
+                        <EnteSpinner />
+                    </LoadingOverlay>
+                )}
+                <LoadingBar color="#2dc262" ref={loadingBar} />
+                {isFirstLoad && (
+                    <AlertContainer>
+                        {constants.INITIAL_LOAD_DELAY_WARNING}
+                    </AlertContainer>
                 )}
-                setLoading={setLoading}
-                setCollectionNamerAttributes={setCollectionNamerAttributes}
-                setDialogMessage={setDialogMessage}
-                setUploadInProgress={setUploadInProgress}
-            />
-            <Sidebar
-                files={files}
-                collections={collections}
-                setDialogMessage={setDialogMessage}
-                showPlanSelectorModal={() => setPlanModalView(true)}
-            />
-            <UploadButton openFileUploader={openFileUploader} />
-            <PhotoFrame
-                files={files}
-                setFiles={setFiles}
-                syncWithRemote={syncWithRemote}
-                favItemIds={favItemIds}
-                sinceTime={sinceTime}
-                setSelected={setSelected}
-                selected={selected}
-                isFirstLoad={isFirstLoad}
-                openFileUploader={openFileUploader}
-                loadingBar={loadingBar}
-                searchMode={searchMode}
-                search={search}
-                setSearchStats={setSearchStats}
-            />
-            {selected.count > 0 && (
-                <SelectedFileOptions
-                    addToCollectionHelper={addToCollectionHelper}
-                    showCreateCollectionModal={showCreateCollectionModal}
+                <PlanSelector
+                    modalView={planModalView}
+                    closeModal={() => setPlanModalView(false)}
+                    setDialogMessage={setDialogMessage}
+                    setLoading={setLoading}
+                />
+                <AlertBanner bannerMessage={bannerMessage} />
+                <MessageDialog
+                    size="lg"
+                    show={dialogView}
+                    onHide={() => setDialogView(false)}
+                    attributes={dialogMessage}
+                />
+                <SearchBar
+                    isOpen={searchMode}
+                    setOpen={setSearchMode}
+                    loadingBar={loadingBar}
+                    isFirstFetch={isFirstFetch}
+                    setCollections={setCollections}
+                    setSearch={updateSearch}
+                    files={files}
+                    searchStats={searchStats}
+                />
+                <Collections
+                    collections={collections}
+                    searchMode={searchMode}
+                    selected={Number(router.query.collection)}
+                    selectCollection={selectCollection}
+                    syncWithRemote={syncWithRemote}
                     setDialogMessage={setDialogMessage}
+                    setCollectionNamerAttributes={setCollectionNamerAttributes}
+                    startLoadingBar={loadingBar.current?.continuousStart}
+                />
+                <CollectionNamer
+                    show={collectionNamerView}
+                    onHide={setCollectionNamerView.bind(null, false)}
+                    attributes={collectionNamerAttributes}
+                />
+                <CollectionSelector
+                    show={collectionSelectorView}
+                    onHide={setCollectionSelectorView.bind(null, false)}
+                    collectionsAndTheirLatestFile={collectionsAndTheirLatestFile}
+                    directlyShowNextModal={
+                        collectionsAndTheirLatestFile?.length === 0
+                    }
+                    attributes={collectionSelectorAttributes}
+                />
+                <Upload
+                    syncWithRemote={syncWithRemote}
+                    setBannerMessage={setBannerMessage}
+                    acceptedFiles={acceptedFiles}
+                    existingFiles={files}
                     setCollectionSelectorAttributes={
                         setCollectionSelectorAttributes
                     }
-                    deleteFileHelper={deleteFileHelper}
+                    closeCollectionSelector={setCollectionSelectorView.bind(
+                        null,
+                        false,
+                    )}
+                    setLoading={setLoading}
+                    setCollectionNamerAttributes={setCollectionNamerAttributes}
+                    setDialogMessage={setDialogMessage}
+                    setUploadInProgress={setUploadInProgress}
+                />
+                <Sidebar
+                    files={files}
+                    collections={collections}
+                    setDialogMessage={setDialogMessage}
+                    showPlanSelectorModal={() => setPlanModalView(true)}
                 />
-            )}
-        </FullScreenDropZone>
+                <UploadButton openFileUploader={openFileUploader} />
+                <PhotoFrame
+                    files={files}
+                    setFiles={setFiles}
+                    syncWithRemote={syncWithRemote}
+                    favItemIds={favItemIds}
+                    setSelected={setSelected}
+                    selected={selected}
+                    isFirstLoad={isFirstLoad}
+                    openFileUploader={openFileUploader}
+                    loadingBar={loadingBar}
+                    searchMode={searchMode}
+                    search={search}
+                    setSearchStats={setSearchStats}
+                />
+                {selected.count > 0 && (
+                    <SelectedFileOptions
+                        addToCollectionHelper={addToCollectionHelper}
+                        showCreateCollectionModal={showCreateCollectionModal}
+                        setDialogMessage={setDialogMessage}
+                        setCollectionSelectorAttributes={
+                            setCollectionSelectorAttributes
+                        }
+                        deleteFileHelper={deleteFileHelper}
+                    />
+                )}
+            </FullScreenDropZone>
+        </GalleryContext.Provider>
     );
 }

+ 6 - 1
src/services/fileService.ts

@@ -43,7 +43,7 @@ export const getLocalFiles = async () => {
     return files;
 };
 
-export const syncFiles = async (collections: Collection[]) => {
+export const syncFiles = async (collections: Collection[], setFiles: (files: File[]) => void) => {
     const localFiles = await getLocalFiles();
     let isUpdated = false;
     let files = await removeDeletedCollectionFiles(collections, localFiles);
@@ -88,6 +88,11 @@ export const syncFiles = async (collections: Collection[]) => {
             `${collection.id}-time`,
             collection.updationTime,
         );
+        setFiles(files.map((item) => ({
+            ...item,
+            w: window.innerWidth,
+            h: window.innerHeight,
+        })));
     }
     return {
         files: files.map((item) => ({

+ 2 - 2
src/utils/strings/englishConstants.tsx

@@ -159,7 +159,7 @@ const englishConstants = {
     ERROR: 'error',
     MESSAGE: 'message',
     INSTALL_MOBILE_APP: () => (
-        <div>
+        <>
         install our{' '}
             <a
                 href="https://play.google.com/store/apps/details?id=io.ente.photos"
@@ -180,7 +180,7 @@ const englishConstants = {
                 {' '}
             </a>
         to automatically backup all your photos
-        </div>
+        </>
     ),
     DOWNLOAD_APP_MESSAGE: () => (
         <>