hunk.code_window: 	const showSize =
   		showBytesInGridView &&
   		!isEphemeralLocation &&
   		!isFolder &&
   		!location &&
  
hunk.labels: keep keep keep replace replace keep
hunk.file_path: interface/app/$libraryId/Explorer/View/GridView.tsx
hunk.type: replace
file: import {
  	createContext,
  	useCallback,
  	useContext,
  	useEffect,
  	useMemo,
  	useRef,
  	useState,
  	type ReactNode
  } from 'react';
  import { useMatch } from 'react-router';
  import Selecto from 'react-selecto';
  import { useKey } from 'rooks';
  import { type ExplorerItem } from '@sd/client';
  import { GridList, useGridList } from '~/components';
  import { useMouseNavigate, useOperatingSystem } from '~/hooks';
  
  import { useExplorerContext } from '../Context';
  import { getQuickPreviewStore } from '../QuickPreview/store';
  import { getExplorerStore, isCut, useExplorerStore } from '../store';
  import { uniqueId } from '../util';
  import { useExplorerViewContext } from '../ViewContext';
  
  const SelectoContext = createContext<{
  	selecto: React.RefObject<Selecto>;
  	selectoUnSelected: React.MutableRefObject<Set<string>>;
  } | null>(null);
  
  type RenderItem = (item: { item: ExplorerItem; selected: boolean; cut: boolean }) => ReactNode;
  
  const GridListItem = (props: {
  	index: number;
  	item: ExplorerItem;
  	children: RenderItem;
  	onMouseDown: (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => void;
  }) => {
  	const explorer = useExplorerContext();
  	const explorerStore = useExplorerStore();
  	const explorerView = useExplorerViewContext();
  
  	const selecto = useContext(SelectoContext);
  
  	const cut = isCut(props.item, explorerStore.cutCopyState);
  
  	const selected = useMemo(
  		// Even though this checks object equality, it should still be safe since `selectedItems`
  		// will be re-calculated before this memo runs.
  		() => explorer.selectedItems.has(props.item),
  		[explorer.selectedItems, props.item]
  	);
  
  	const itemId = uniqueId(props.item);
  
  	useEffect(() => {
  		if (!selecto?.selecto.current || !selecto.selectoUnSelected.current.has(itemId)) return;
  
  		if (!selected) {
  			selecto.selectoUnSelected.current.delete(itemId);
  			return;
  		}
  
  		const element = document.querySelector(`[data-selectable-id="${itemId}"]`);
  
  		if (!element) return;
  
  		selecto.selectoUnSelected.current.delete(itemId);
  		selecto.selecto.current.setSelectedTargets([
  			...selecto.selecto.current.getSelectedTargets(),
  			element as HTMLElement
  		]);
  
  		// eslint-disable-next-line react-hooks/exhaustive-deps
  	}, []);
  
  	useEffect(() => {
  		if (!selecto) return;
  
  		return () => {
  			const element = document.querySelector(`[data-selectable-id="${itemId}"]`);
  			if (selected && !element) selecto.selectoUnSelected.current.add(itemId);
  		};
  
  		// eslint-disable-next-line react-hooks/exhaustive-deps
  	}, [selected]);
  
  	return (
  		<div
  			className="h-full w-full"
  			data-selectable=""
  			data-selectable-index={props.index}
  			data-selectable-id={itemId}
  			onMouseDown={props.onMouseDown}
  			onContextMenu={(e) => {
  				if (explorerView.selectable && !explorer.selectedItems.has(props.item)) {
  					explorer.resetSelectedItems([props.item]);
  					selecto?.selecto.current?.setSelectedTargets([e.currentTarget]);
  				}
  			}}
  		>
  			{props.children({ item: props.item, selected, cut })}
  		</div>
  	);
  };
  
  const CHROME_REGEX = /Chrome/;
  
  export default ({ children }: { children: RenderItem }) => {
  	const os = useOperatingSystem();
  	const realOS = useOperatingSystem(true);
  
  	const isChrome = CHROME_REGEX.test(navigator.userAgent);
  	const isEphemeralLocation = useMatch('/:libraryId/ephemeral/:ephemeralId');
  
  	const explorer = useExplorerContext();
  	const settings = explorer.useSettingsSnapshot();
  	const explorerStore = useExplorerStore();
  	const explorerView = useExplorerViewContext();
  
  	const selecto = useRef<Selecto>(null);
  	const selectoUnSelected = useRef<Set<string>>(new Set());
  	const selectoFirstColumn = useRef<number | undefined>();
  	const selectoLastColumn = useRef<number | undefined>();
  
  	const [dragFromThumbnail, setDragFromThumbnail] = useState(false);
  	const mouseNavigate = useMouseNavigate();
  
  	const itemDetailsHeight = 44 + (settings.showBytesInGridView && !isEphemeralLocation ? 20 : 0);
  	const itemHeight = settings.gridItemSize + itemDetailsHeight;
  
  	const padding = settings.layoutMode === 'grid' ? 12 : 0;
  
  	const grid = useGridList({
  		ref: explorerView.ref,
  		count: explorer.items?.length ?? 0,
  		totalCount: explorer.count,
  		overscan: explorer.overscan,
  		onLoadMore: explorer.loadMore,
  		size:
  			settings.layoutMode === 'grid'
  				? { width: settings.gridItemSize, height: itemHeight }
  				: undefined,
  		columns: settings.layoutMode === 'media' ? settings.mediaColumns : undefined,
  		getItemId: useCallback(
  			(index: number) => {
  				const item = explorer.items?.[index];
  				return item ? uniqueId(item) : undefined;
  			},
  			[explorer.items]
  		),
  		getItemData: useCallback((index: number) => explorer.items?.[index], [explorer.items]),
  		padding: {
  			...explorerView.padding,
  			bottom: explorerView.bottom
  				? (explorerView.padding?.bottom ?? padding) + explorerView.bottom
  				: undefined,
  			x: padding,
  			y: padding
  		},
  		gap:
  			explorerView.gap ||
  			(settings.layoutMode === 'grid' ? explorerStore.gridGap : undefined),
  		top: explorerView.top
  	});
  
  	function getElementId(element: Element) {
  		return element.getAttribute('data-selectable-id');
  	}
  
  	function getElementIndex(element: Element) {
  		const index = element.getAttribute('data-selectable-index');
  		return index ? Number(index) : null;
  	}
  
  	function getElementItem(element: Element) {
  		const index = getElementIndex(element);
  		if (index === null) return null;
  
  		return grid.getItem(index) ?? null;
  	}
  
  	function getActiveItem(elements: Element[]) {
  		// Get selected item with least index.
  		// Might seem kinda weird but it's the same behaviour as Finder.
  		const activeItem =
  			elements.reduce(
  				(least, current) => {
  					const currentItem = getElementItem(current);
  					if (!currentItem) return least;
  
  					if (!least) return currentItem;
  
  					return currentItem.index < least.index ? currentItem : least;
  				},
  				null as ReturnType<typeof getElementItem>
  			)?.data ?? null;
  
  		return activeItem;
  	}
  
  	useEffect(
  		() => {
  			const element = explorer.scrollRef.current;
  			if (!element) return;
  
  			const handleScroll = () => {
  				selecto.current?.checkScroll();
  				selecto.current?.findSelectableTargets();
  			};
  
  			element.addEventListener('scroll', handleScroll);
  			return () => element.removeEventListener('scroll', handleScroll);
  		},
  		// explorer.scrollRef is a stable reference so this only actually runs once
  		[explorer.scrollRef]
  	);
  
  	useEffect(() => {
  		if (!selecto.current) return;
  
  		const set = new Set(explorer.selectedItemHashes.value);
  		if (set.size === 0) return;
  
  		const items = [...document.querySelectorAll('[data-selectable]')].filter((item) => {
  			const id = getElementId(item);
  			if (id === null) return;
  
  			const selected = set.has(id);
  			if (selected) set.delete(id);
  
  			return selected;
  		});
  
  		selectoUnSelected.current = set;
  		selecto.current.setSelectedTargets(items as HTMLElement[]);
  
  		activeItem.current = getActiveItem(items);
  
  		// eslint-disable-next-line react-hooks/exhaustive-deps
  	}, [grid.columnCount, explorer.items]);
  
  	// The item that further selection will move from (shift + arrow for example).
  	// This used to be calculated from the last item of selectedItems,
  	// but Set ordering isn't reliable.
  	// Ref bc we never actually render this.
  	const activeItem = useRef<ExplorerItem | null>(null);
  
  	useEffect(() => {
  		if (explorer.selectedItems.size !== 0) return;
  
  		selectoUnSelected.current = new Set();
  		// Accessing refs during render is bad
  		activeItem.current = null;
  	}, [explorer.selectedItems]);
  
  	useKey(['ArrowUp', 'ArrowDown', 'ArrowRight', 'ArrowLeft', 'Escape'], (e) => {
  		if (!explorerView.selectable) return;
  
  		if (e.key === 'Escape') {
  			explorer.resetSelectedItems([]);
  			selecto.current?.setSelectedTargets([]);
  			return;
  		}
  
  		if (e.key === 'ArrowDown' && explorer.selectedItems.size === 0) {
  			const item = grid.getItem(0);
  			if (!item?.data) return;
  
  			const id = uniqueId(item.data);
  
  			const selectedItemDom = document.querySelector(
  				`[data-selectable-id="${realOS === 'windows' ? id.replaceAll('\\', '\\\\') : id}"]`
  			);
  
  			if (selectedItemDom) {
  				explorer.resetSelectedItems([item.data]);
  				selecto.current?.setSelectedTargets([selectedItemDom as HTMLElement]);
  				activeItem.current = item.data;
  			}
  			return;
  		}
  
  		if (explorer.selectedItems.size > 0) e.preventDefault();
  
  		const lastItem = activeItem.current;
  		if (!lastItem) return;
  
  		const lastItemIndex = explorer.items?.findIndex((item) => item === lastItem);
  		if (lastItemIndex === undefined || lastItemIndex === -1) return;
  
  		const gridItem = grid.getItem(lastItemIndex);
  		if (!gridItem) return;
  
  		const currentIndex = gridItem.index;
  		let newIndex = currentIndex;
  
  		switch (e.key) {
  			case 'ArrowUp':
  				newIndex -= grid.columnCount;
  				break;
  			case 'ArrowDown':
  				newIndex += grid.columnCount;
  				break;
  			case 'ArrowRight':
  				newIndex += 1;
  				break;
  			case 'ArrowLeft':
  				newIndex -= 1;
  				break;
  		}
  
  		const newSelectedItem = grid.getItem(newIndex);
  		if (!newSelectedItem?.data) return;
  		if (!explorer.allowMultiSelect) explorer.resetSelectedItems([newSelectedItem.data]);
  		else {
  			const id = uniqueId(newSelectedItem.data);
  
  			const selectedItemDom = document.querySelector(
  				`[data-selectable-id="${realOS === 'windows' ? id.replaceAll('\\', '\\\\') : id}"]`
  			);
  			if (!selectedItemDom) return;
  
  			if (e.shiftKey && !getQuickPreviewStore().open) {
  				if (!explorer.selectedItems.has(newSelectedItem.data)) {
  					explorer.addSelectedItem(newSelectedItem.data);
  					selecto.current?.setSelectedTargets([
  						...(selecto.current?.getSelectedTargets() || []),
  						selectedItemDom as HTMLElement
  					]);
  				}
  			} else {
  				explorer.resetSelectedItems([newSelectedItem.data]);
  				selecto.current?.setSelectedTargets([selectedItemDom as HTMLElement]);
  				if (selectoUnSelected.current.size > 0) selectoUnSelected.current = new Set();
  			}
  		}
  
  		activeItem.current = newSelectedItem.data;
  
  		if (
  			explorer.scrollRef.current &&
  			explorerView.ref.current &&
  			newSelectedItem.row !== gridItem.row
  		) {
  			const viewRect = explorerView.ref.current.getBoundingClientRect();
  
  			const itemRect = newSelectedItem.rect;
  			const itemTop = itemRect.top + viewRect.top;
  			const itemBottom = itemRect.bottom + viewRect.top;
  
  			const scrollRect = explorer.scrollRef.current.getBoundingClientRect();
  			const scrollTop =
  				(explorerView.top ??
  					parseInt(getComputedStyle(explorer.scrollRef.current).paddingTop)) + 1;
  			const scrollBottom = scrollRect.height - (os !== 'windows' && os !== 'browser' ? 2 : 1);
  
  			if (itemTop < scrollTop) {
  				explorer.scrollRef.current.scrollBy({
  					top:
  						itemTop -
  						scrollTop -
  						(newSelectedItem.row === 0 ? grid.padding.top : grid.gap.y / 2),
  					behavior: 'smooth'
  				});
  			} else if (itemBottom > scrollBottom - (explorerView.bottom ?? 0)) {
  				explorer.scrollRef.current.scrollBy({
  					top:
  						itemBottom -
  						scrollBottom +
  						(explorerView.bottom ?? 0) +
  						(newSelectedItem.row === grid.rowCount - 1
  							? grid.padding.bottom
  							: grid.gap.y / 2),
  					behavior: 'smooth'
  				});
  			}
  		}
  	});
  
  	return (
  		<SelectoContext.Provider value={selecto.current ? { selecto, selectoUnSelected } : null}>
  			{explorer.allowMultiSelect && (
  				<Selecto
  					ref={selecto}
  					boundContainer={
  						explorerView.ref.current
  							? {
  									element: explorerView.ref.current,
  									top: false,
  									bottom: false
  							  }
  							: undefined
  					}
  					selectableTargets={['[data-selectable]']}
  					toggleContinueSelect="shift"
  					hitRate={0}
  					// selectFromInside={explorerStore.layoutMode === 'media'}
  					onDragStart={(e) => {
  						getExplorerStore().isDragging = true;
  						if ((e.inputEvent as MouseEvent).target instanceof HTMLImageElement) {
  							setDragFromThumbnail(true);
  						}
  					}}
  					onDragEnd={() => {
  						getExplorerStore().isDragging = false;
  						selectoFirstColumn.current = undefined;
  						selectoLastColumn.current = undefined;
  						setDragFromThumbnail(false);
  
  						const allSelected = selecto.current?.getSelectedTargets() ?? [];
  						activeItem.current = getActiveItem(allSelected);
  					}}
  					onScroll={({ direction }) => {
  						selecto.current?.findSelectableTargets();
  						explorer.scrollRef.current?.scrollBy(
  							(direction[0] || 0) * 10,
  							(direction[1] || 0) * 10
  						);
  					}}
  					scrollOptions={{
  						container: { current: explorer.scrollRef.current },
  						throttleTime: isChrome || dragFromThumbnail ? 30 : 10000
  					}}
  					onSelect={(e) => {
  						const inputEvent = e.inputEvent as MouseEvent;
  
  						if (inputEvent.type === 'mousedown') {
  							const el = inputEvent.shiftKey
  								? e.added[0] || e.removed[0]
  								: e.selected[0];
  
  							if (!el) return;
  
  							const item = getElementItem(el);
  
  							if (!item?.data) return;
  
  							if (!inputEvent.shiftKey) {
  								if (explorer.selectedItems.has(item.data)) {
  									selecto.current?.setSelectedTargets(e.beforeSelected);
  								} else {
  									selectoUnSelected.current = new Set();
  									explorer.resetSelectedItems([item.data]);
  								}
  
  								return;
  							}
  
  							if (e.added[0]) explorer.addSelectedItem(item.data);
  							else explorer.removeSelectedItem(item.data);
  						} else if (inputEvent.type === 'mousemove') {
  							const unselectedItems: string[] = [];
  
  							e.added.forEach((el) => {
  								const item = getElementItem(el);
  
  								if (!item?.data) return;
  
  								explorer.addSelectedItem(item.data);
  							});
  
  							e.removed.forEach((el) => {
  								const item = getElementItem(el);
  
  								if (!item?.data || typeof item.id === 'number') return;
  
  								if (document.contains(el)) explorer.removeSelectedItem(item.data);
  								else unselectedItems.push(item.id);
  							});
  
  							const dragDirection = {
  								x: inputEvent.x === e.rect.left ? 'left' : 'right',
  								y: inputEvent.y === e.rect.bottom ? 'down' : 'up'
  							} as const;
  
  							const dragStart = {
  								x: dragDirection.x === 'right' ? e.rect.left : e.rect.right,
  								y: dragDirection.y === 'down' ? e.rect.top : e.rect.bottom
  							};
  
  							const dragEnd = { x: inputEvent.x, y: inputEvent.y };
  
  							const columns = new Set<number>();
  
  							const elements = [...e.added, ...e.removed];
  
  							const items = elements.reduce(
  								(items, el) => {
  									const item = getElementItem(el);
  
  									if (!item) return items;
  
  									columns.add(item.column);
  									return [...items, item];
  								},
  								[] as NonNullable<ReturnType<typeof getElementItem>>[]
  							);
  
  							if (columns.size > 1) {
  								items.sort((a, b) => a.column - b.column);
  
  								const firstItem =
  									dragDirection.x === 'right'
  										? items[0]
  										: items[items.length - 1];
  
  								const lastItem =
  									dragDirection.x === 'right'
  										? items[items.length - 1]
  										: items[0];
  
  								if (firstItem && lastItem) {
  									selectoFirstColumn.current = firstItem.column;
  									selectoLastColumn.current = lastItem.column;
  								}
  							} else if (columns.size === 1) {
  								const column = [...columns.values()][0]!;
  
  								items.sort((a, b) => a.row - b.row);
  
  								const itemRect = elements[0]?.getBoundingClientRect();
  
  								const inDragArea =
  									itemRect &&
  									(dragDirection.x === 'right'
  										? dragEnd.x >= itemRect.left
  										: dragEnd.x <= itemRect.right);
  
  								if (
  									column !== selectoLastColumn.current ||
  									(column === selectoLastColumn.current && !inDragArea)
  								) {
  									const firstItem =
  										dragDirection.y === 'down'
  											? items[0]
  											: items[items.length - 1];
  
  									if (firstItem) {
  										const viewRectTop =
  											explorerView.ref.current?.getBoundingClientRect().top ??
  											0;
  
  										const itemTop = firstItem.rect.top + viewRectTop;
  										const itemBottom = firstItem.rect.bottom + viewRectTop;
  
  										if (
  											dragDirection.y === 'down'
  												? dragStart.y < itemTop
  												: dragStart.y > itemBottom
  										) {
  											const dragHeight = Math.abs(
  												dragStart.y -
  													(dragDirection.y === 'down'
  														? itemTop
  														: itemBottom)
  											);
  
  											let itemsInDragCount =
  												(dragHeight - grid.gap.y) /
  												(grid.virtualItemHeight + grid.gap.y);
  
  											if (itemsInDragCount > 1) {
  												itemsInDragCount = Math.ceil(itemsInDragCount);
  											} else {
  												itemsInDragCount = Math.round(itemsInDragCount);
  											}
  
  											[...Array(itemsInDragCount)].forEach((_, i) => {
  												const index =
  													dragDirection.y === 'down'
  														? itemsInDragCount - i
  														: i + 1;
  
  												const itemIndex =
  													firstItem.index +
  													(dragDirection.y === 'down' ? -index : index) *
  														grid.columnCount;
  
  												const item = explorer.items?.[itemIndex];
  
  												if (item) {
  													if (inputEvent.shiftKey) {
  														if (explorer.selectedItems.has(item))
  															explorer.removeSelectedItem(item);
  														else {
  															explorer.addSelectedItem(item);
  															if (inDragArea)
  																unselectedItems.push(
  																	uniqueId(item)
  																);
  														}
  													} else if (!inDragArea)
  														explorer.removeSelectedItem(item);
  													else {
  														explorer.addSelectedItem(item);
  														if (inDragArea)
  															unselectedItems.push(uniqueId(item));
  													}
  												}
  											});
  										}
  									}
  
  									if (!inDragArea && column === selectoFirstColumn.current) {
  										selectoFirstColumn.current = undefined;
  										selectoLastColumn.current = undefined;
  									} else {
  										selectoLastColumn.current = column;
  										if (selectoFirstColumn.current === undefined) {
  											selectoFirstColumn.current = column;
  										}
  									}
  								}
  							}
  
  							if (unselectedItems.length > 0) {
  								selectoUnSelected.current = new Set([
  									...selectoUnSelected.current,
  									...unselectedItems
  								]);
  							}
  						}
  					}}
  				/>
  			)}
  
  			<GridList grid={grid} scrollRef={explorer.scrollRef}>
  				{(index) => {
  					const item = explorer.items?.[index];
  					if (!item) return null;
  
  					return (
  						<GridListItem
  							index={index}
  							item={item}
  							onMouseDown={(e) => {
  								e.stopPropagation();
  
  								mouseNavigate(e);
  
  								if (!explorerView.selectable) return;
  
  								const item = grid.getItem(index);
  
  								if (!item?.data) return;
  
  								if (!explorer.allowMultiSelect) {
  									explorer.resetSelectedItems([item.data]);
  								} else {
  									selectoFirstColumn.current = item.column;
  									selectoLastColumn.current = item.column;
  								}
  
  								activeItem.current = item.data;
  							}}
  						>
  							{children}
  						</GridListItem>
  					);
  				}}
  			</GridList>
  		</SelectoContext.Provider>
  	);
  };
  