Skip to content

Commit

Permalink
feat(web): infinite scroll
Browse files Browse the repository at this point in the history
  • Loading branch information
LukeWasTakenn committed Oct 10, 2023
1 parent 44f9a3b commit 4c88fc7
Show file tree
Hide file tree
Showing 4 changed files with 59 additions and 7 deletions.
2 changes: 1 addition & 1 deletion web/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ debugData([
rightInventory: {
id: 'shop',
type: 'crafting',
slots: 50,
slots: 5000,
label: 'Bob Smith',
weight: 3000,
maxWeight: 5000,
Expand Down
18 changes: 15 additions & 3 deletions web/src/components/inventory/InventoryGrid.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,28 @@
import React from 'react';
import React, { useRef } from 'react';
import { Inventory } from '../../typings';
import WeightBar from '../utils/WeightBar';
import InventorySlot from './InventorySlot';
import { getTotalWeight } from '../../helpers';
import { useAppSelector } from '../../store';
import { useIntersection } from '../../hooks/useIntersection';

const PAGE_SIZE = 30;

const InventoryGrid: React.FC<{ inventory: Inventory }> = ({ inventory }) => {
const weight = React.useMemo(
() => (inventory.maxWeight !== undefined ? Math.floor(getTotalWeight(inventory.items) * 1000) / 1000 : 0),
[inventory.maxWeight, inventory.items]
);
const [page, setPage] = React.useState(0);
const containerRef = useRef(null);
const { ref, entry } = useIntersection({ threshold: 0.5 });
const isBusy = useAppSelector((state) => state.inventory.isBusy);

React.useEffect(() => {
if (entry && entry.isIntersecting) {
setPage((prev) => ++prev);
}
}, [entry]);
return (
<>
<div className="inventory-grid-wrapper" style={{ pointerEvents: isBusy ? 'none' : 'auto' }}>
Expand All @@ -26,12 +37,13 @@ const InventoryGrid: React.FC<{ inventory: Inventory }> = ({ inventory }) => {
</div>
<WeightBar percent={inventory.maxWeight ? (weight / inventory.maxWeight) * 100 : 0} />
</div>
<div className="inventory-grid-container">
<div className="inventory-grid-container" ref={containerRef}>
<>
{inventory.items.map((item) => (
{inventory.items.slice(0, (page + 1) * PAGE_SIZE).map((item, index) => (
<InventorySlot
key={`${inventory.type}-${inventory.id}-${item.slot}`}
item={item}
ref={index === (page + 1) * PAGE_SIZE - 1 ? ref : null}
inventoryType={inventory.type}
inventoryGroups={inventory.groups}
inventoryId={inventory.id}
Expand Down
12 changes: 9 additions & 3 deletions web/src/components/inventory/InventorySlot.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import useNuiEvent from '../../hooks/useNuiEvent';
import { ItemsPayload } from '../../reducers/refreshSlots';
import { closeTooltip, openTooltip } from '../../store/tooltip';
import { openContextMenu } from '../../store/contextMenu';
import { useMergeRefs } from '@floating-ui/react';

interface SlotProps {
inventoryId: Inventory['id'];
Expand All @@ -22,7 +23,10 @@ interface SlotProps {
item: Slot;
}

const InventorySlot: React.FC<SlotProps> = ({ item, inventoryId, inventoryType, inventoryGroups }) => {
const InventorySlot: React.ForwardRefRenderFunction<HTMLDivElement, SlotProps> = (
{ item, inventoryId, inventoryType, inventoryGroups },
ref
) => {
const manager = useDragDropManager();
const dispatch = useAppDispatch();
const timerRef = useRef<NodeJS.Timer | null>(null);
Expand Down Expand Up @@ -111,9 +115,11 @@ const InventorySlot: React.FC<SlotProps> = ({ item, inventoryId, inventoryType,
}
};

const refs = useMergeRefs([connectRef, ref]);

return (
<div
ref={connectRef}
ref={refs}
onContextMenu={handleContext}
onClick={handleClick}
className="inventory-slot"
Expand Down Expand Up @@ -214,4 +220,4 @@ const InventorySlot: React.FC<SlotProps> = ({ item, inventoryId, inventoryType,
);
};

export default React.memo(InventorySlot);
export default React.memo(React.forwardRef(InventorySlot));
34 changes: 34 additions & 0 deletions web/src/hooks/useIntersection.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// https://github.com/mantinedev/mantine/blob/master/src/mantine-hooks/src/use-intersection/use-intersection.ts

import { useCallback, useRef, useState } from 'react';

export function useIntersection<T extends HTMLElement = any>(
options?: ConstructorParameters<typeof IntersectionObserver>[1]
) {
const [entry, setEntry] = useState<IntersectionObserverEntry | null>(null);

const observer = useRef<IntersectionObserver | null>(null);

const ref = useCallback(
(element: T | null) => {
if (observer.current) {
observer.current.disconnect();
observer.current = null;
}

if (element === null) {
setEntry(null);
return;
}

observer.current = new IntersectionObserver(([_entry]) => {
setEntry(_entry);
}, options);

observer.current.observe(element);
},
[options?.rootMargin, options?.root, options?.threshold]
);

return { ref, entry };
}

0 comments on commit 4c88fc7

Please sign in to comment.