Skip to content

Commit

Permalink
Lazy load images in library
Browse files Browse the repository at this point in the history
  • Loading branch information
undyingwraith committed Jan 2, 2025
1 parent 49793e5 commit 7d64a2d
Show file tree
Hide file tree
Showing 4 changed files with 78 additions and 13 deletions.
24 changes: 15 additions & 9 deletions packages/ui/src/components/molecules/FileGridItem/FileGridItem.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { Button, Card, CardActions, CardHeader, CardMedia } from '@mui/material';
import { ReadonlySignal, useComputed } from '@preact/signals-react';
import { IFileInfo, isIVideoFile, isPosterFeature, isTitleFeature, isYearFeature } from 'ipmc-interfaces';
import React from 'react';
import { useFileUrl } from '../../../hooks';
import { useTranslation } from '../../../hooks/useTranslation';
import React, { useRef } from 'react';
import { useFileUrl, useIsVisible, useTranslation } from '../../../hooks';
import { PinButton } from '../../atoms/PinButton';
import { Display } from '../../pages/LibraryManager';
import posterFallback from './no-poster.png';
Expand All @@ -13,11 +12,18 @@ import thumbFallback from './no-thumbnail.png';
export function FileGridItem(props: { file: IFileInfo; onOpen: () => void; display: ReadonlySignal<Display>; }) {
const { file, display, onOpen } = props;
const _t = useTranslation();
const imgRef = useRef<HTMLDivElement>(null);
const visible = useIsVisible(imgRef);

const posterUrl = useFileUrl(isPosterFeature(file) && file.posters.length > 0 ? file.posters[0]?.cid : undefined, posterFallback);
const thumbUrl = useFileUrl(isIVideoFile(file) && file.thumbnails.length > 0 ? file.thumbnails[0]?.cid : undefined, thumbFallback);
const width = useComputed(() => display.value == Display.Poster ? 240 : 640);
const height = useComputed(() => display.value == Display.Poster ? 360 : 360);
const posterUrl = useFileUrl(isPosterFeature(file) && file.posters.length > 0 ? file.posters[0]?.cid : undefined, visible, posterFallback);
const thumbUrl = useFileUrl(isIVideoFile(file) && file.thumbnails.length > 0 ? file.thumbnails[0]?.cid : undefined, visible, thumbFallback);
const size = useComputed<{ width: number; height: number; }>(() => display.value == Display.Poster ? {
width: 240,
height: 360,
} : {
width: 640,
height: 360,
});
const url = useComputed(() => {
switch (display.value) {
case Display.Poster:
Expand All @@ -30,8 +36,8 @@ export function FileGridItem(props: { file: IFileInfo; onOpen: () => void; displ
});

return useComputed(() => (
<Card sx={{ width: width.value }}>
<CardMedia image={url.value} sx={{ height: height.value, width: width.value }} />
<Card sx={{ width: size.value.width }} ref={imgRef}>
<CardMedia image={url.value} sx={{ height: size.value.height, width: size.value.width }} />
<CardHeader title={isTitleFeature(file) ? file.title : file.name} subheader={isYearFeature(file) ? file.year : undefined}></CardHeader>
<CardActions>
<PinButton cid={file.cid} />
Expand Down
1 change: 1 addition & 0 deletions packages/ui/src/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export { useFileUrl } from './useFileUrl';
export { useHotkey } from './useHotkey';
export { useIsVisible } from './useIsVisible';
export { PinStatus, usePinManager } from './usePinManager';
export { useTitle } from './useTitle';
export { useTranslation } from './useTranslation';
Expand Down
8 changes: 4 additions & 4 deletions packages/ui/src/hooks/useFileUrl.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
import { IIpfsService, IIpfsServiceSymbol } from 'ipmc-interfaces';
import { useService } from '../context/AppContext';
import { ReadonlySignal, useComputed, useSignal, useSignalEffect } from '@preact/signals-react';
import { ReadonlySignal, Signal, useComputed, useSignal, useSignalEffect } from '@preact/signals-react';
import { useLinkedSignal } from './useLinkedSignal';
import { fileTypeFromBuffer } from 'file-type';

export function useFileUrl(cid?: string, fallback?: string): ReadonlySignal<string | undefined> {
export function useFileUrl(cid?: string, visible?: Signal<boolean>, fallback?: string): ReadonlySignal<string | undefined> {
const result = useSignal<string | undefined>(undefined);
const heliaService = useService<IIpfsService>(IIpfsServiceSymbol);
const cidSig = useLinkedSignal(cid);

useSignalEffect(() => {
const controller = new AbortController();
const cid = cidSig.value;
if (cid !== undefined) {

const currentlyVisible = visible?.value ?? false;
if (cid !== undefined && currentlyVisible) {
(async () => {
const data = await heliaService.fetch(cid);
const fileType = await fileTypeFromBuffer(data);
Expand Down
58 changes: 58 additions & 0 deletions packages/ui/src/hooks/useIsVisible.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { Signal, useSignal } from '@preact/signals-react';
import { RefObject, useEffect } from 'react';


let listenerCallbacks = new WeakMap();

let observer: IntersectionObserver;

function handleIntersections(entries: any[]) {
entries.forEach(entry => {
if (listenerCallbacks.has(entry.target)) {
let cb = listenerCallbacks.get(entry.target);

if (entry.isIntersecting || entry.intersectionRatio > 0) {
observer.unobserve(entry.target);
listenerCallbacks.delete(entry.target);
cb();
}
}
});
}

function getIntersectionObserver() {
if (observer === undefined) {
observer = new IntersectionObserver(handleIntersections, {
rootMargin: '100px',
threshold: 0.15,
});
}
return observer;
}

export function useIsVisible(elem: RefObject<HTMLDivElement | null>): Signal<boolean> {
const visible = useSignal<boolean>(false);

useEffect(() => {
let target = elem?.current;
if (target !== null) {
let observer = getIntersectionObserver();
listenerCallbacks.set(target, () => {
visible.value = true;
});
observer.observe(target);

return () => {
listenerCallbacks.delete(target);
observer.unobserve(target);
};
} else {
return () => {
// NOOP
};
}
}, []);

return visible;
}

0 comments on commit 7d64a2d

Please sign in to comment.