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 ? (
) : (
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