From ec6c703c60ead5f362606d76badb782c157db0d8 Mon Sep 17 00:00:00 2001 From: Yousif Yassi Date: Fri, 1 Dec 2023 12:21:33 -0500 Subject: [PATCH] fix: DIA-743: [FE] Blank space is displayed instead of the records on scroll up/down --- src/components/CellViews/ImageCell.js | 13 +++- src/components/DataManager/DataManager.js | 27 ++++---- src/components/Label/Label.js | 77 ++++++++++----------- src/providers/ImageProvider.js | 81 +++++++++++++++++++++++ 4 files changed, 148 insertions(+), 50 deletions(-) create mode 100644 src/providers/ImageProvider.js diff --git a/src/components/CellViews/ImageCell.js b/src/components/CellViews/ImageCell.js index 1c9b498d..2f8db7dc 100644 --- a/src/components/CellViews/ImageCell.js +++ b/src/components/CellViews/ImageCell.js @@ -1,6 +1,8 @@ import { getRoot } from "mobx-state-tree"; import { FF_LSDV_4711, isFF } from "../../utils/feature-flags"; import { AnnotationPreview } from "../Common/AnnotationPreview/AnnotationPreview"; +import { useRef } from "react"; +import { useImageProvider } from "../../providers/ImageProvider"; const imgDefaultProps = {}; @@ -13,23 +15,32 @@ export const ImageCell = (column) => { column: { alias }, } = column; const root = getRoot(original); + const imgRef = useRef(); + const { getImage } = useImageProvider(); const renderImagePreview = original.total_annotations === 0 || !root.showPreviews; const imgSrc = Array.isArray(value) ? value[0] : value; if (!imgSrc) return null; + getImage(imgSrc).then((loadedImage) => { + if (imgRef.current && loadedImage.loaded && loadedImage.url) { + imgRef.current.setAttribute("src", loadedImage.url); + imgRef.current.style.display = ""; + } + }); return renderImagePreview ? ( Data ) : ( diff --git a/src/components/DataManager/DataManager.js b/src/components/DataManager/DataManager.js index 71899ab3..90911bc5 100644 --- a/src/components/DataManager/DataManager.js +++ b/src/components/DataManager/DataManager.js @@ -11,6 +11,7 @@ import { FiltersSidebar } from "../Filters/FiltersSidebar/FilterSidebar"; import { DataView } from "../MainView"; import "./DataManager.styl"; import { Toolbar } from "./Toolbar/Toolbar"; +import { ImageProvider } from "../../providers/ImageProvider"; const injector = inject(({ store }) => { const { sidebarEnabled, sidebarVisible } = store.viewsStore ?? {}; @@ -127,19 +128,21 @@ const TabsSwitch = switchInjector(observer(({ sdk, views, tabs, selectedKey }) = export const DataManager = injector(({ shrinkWidth }) => { return ( - - - - - + + + + + + - - - + + + - - - - + + + + + ); }); diff --git a/src/components/Label/Label.js b/src/components/Label/Label.js index 942769f5..aeca3ceb 100644 --- a/src/components/Label/Label.js +++ b/src/components/Label/Label.js @@ -11,6 +11,7 @@ import { Resizer } from "../Common/Resizer/Resizer"; import { Space } from "../Common/Space/Space"; import { DataView } from "../MainView"; import "./Label.styl"; +import { ImageProvider } from "../../providers/ImageProvider"; const LabelingHeader = ({ SDK, onClick, isExplorerMode }) => { return ( @@ -99,45 +100,47 @@ export const Labeling = injector(observer(({ const outlinerEnabled = isFF(FF_DEV_1170); return ( - - {SDK.interfaceEnabled("labelingHeader") && ( - - )} - - - {isExplorerMode && ( - - - - - + + + {SDK.interfaceEnabled("labelingHeader") && ( + )} - - {loading && } - + + {isExplorerMode && ( + + + + + + )} + + + {loading && } + + - - + + ); })); diff --git a/src/providers/ImageProvider.js b/src/providers/ImageProvider.js new file mode 100644 index 00000000..d345faa1 --- /dev/null +++ b/src/providers/ImageProvider.js @@ -0,0 +1,81 @@ +import React, { useCallback, useContext, useMemo, useRef } from "react"; + +export const ImageContext = React.createContext({}); +ImageContext.displayName = "ImageContext"; + +export const ImageProvider = ({ children }) => { + const loadedImagesRef = useRef(new Map()); + const getImage = useCallback((imgSrc) => { + const loadedImages = loadedImagesRef.current; + + if (loadedImages.has(imgSrc)) { + const loadedImage = loadedImages.get(imgSrc); + + return loadedImage.promise; + } + const imageFetchPromise = fetch(imgSrc) + .then((response) => { + const reader = response.body.getReader(); + + return new ReadableStream({ + start(controller) { + return pump(); + function pump() { + return reader.read().then(({ done, value }) => { + // When no more data needs to be consumed, close the stream + if (done) { + controller.close(); + return; + } + // Enqueue the next data chunk into our target stream + controller.enqueue(value); + return pump(); + }); + } + }, + }); + }) + // Create a new response out of the stream + .then((stream) => new Response(stream)) + // Create an object URL for the response + .then((response) => response.blob()) + .then((blob) => URL.createObjectURL(blob)) + // Update image + .then((url) => { + loadedImages.set(imgSrc, { + url, + promise: imageFetchPromise, + loaded: true, + }); + return loadedImages.get(imgSrc); + }) + .catch((err) => { + console.error(err); + return loadedImages.get(imgSrc); + }); + + loadedImages.set(imgSrc, { + url: imgSrc, + promise: imageFetchPromise, + loaded: false, + }); + + return imageFetchPromise; + }, []); + const contextValue = useMemo(() => { + return { + loadedImages: loadedImagesRef.current, + getImage, + }; + }, [getImage]); + + return ( + + {children} + + ); +}; + +export const useImageProvider = () => { + return useContext(ImageContext) ?? {}; +}; \ No newline at end of file