This commit is contained in:
2026-05-23 17:17:56 -07:00
commit 448f2e33ef
135 changed files with 11817 additions and 0 deletions
@@ -0,0 +1,99 @@
import { useEffect, useRef, useState } from 'react'
import type { BackendArt } from '../../functions/BackendTypes'
import { useScrollRoot } from '../../functions/Context'
import { routeIntercept } from '../../functions/Route'
import { CDN_BASE } from '../../functions/Backend'
import './styles/LayoutBrowser.css'
interface PropsForLayoutBrowser {
items: BackendArt[]
position: number
onEndReached?: () => void
}
export interface RecoverForLayoutBrowser {
position: number
items: BackendArt[]
}
export default function LayoutBrowser({ items, position, onEndReached }: PropsForLayoutBrowser) {
const [columnCount, setColumnCount] = useState(3)
const containerRef = useRef<HTMLDivElement>(null)
const scrollRoot = useScrollRoot()
const didRestore = useRef(false)
// Endless Scrolling
useEffect(() => {
if (!onEndReached || !scrollRoot) return
const onScroll = () => {
const { scrollTop, scrollHeight, clientHeight } = scrollRoot
if (scrollTop + clientHeight >= scrollHeight - 100) {
onEndReached()
}
}
scrollRoot.addEventListener('scroll', onScroll)
return () => scrollRoot.removeEventListener('scroll', onScroll)
}, [onEndReached, scrollRoot])
// Restore Scrolling
useEffect(() => {
if (!scrollRoot || didRestore.current) return
// avoid race conditions
const raf = requestAnimationFrame(() => {
scrollRoot.scrollTo({ top: position })
didRestore.current = true
})
return () => {
cancelAnimationFrame(raf)
}
}, [scrollRoot, position, items])
// Calculate Column Count
useEffect(() => {
const el = containerRef.current
if (!el) return
const ro = new ResizeObserver(([entry]) => {
setColumnCount(Math.max(1, Math.floor(entry.contentRect.width / 160)))
})
ro.observe(el)
return () => ro.disconnect()
}, [])
const columns: BackendArt[][] = Array.from({ length: columnCount }, () => [])
items.forEach((item, i) => columns[i % columnCount].push(item))
return (
<div className="layout-browser" ref={containerRef}>
{columns.map((column, columnIdx) => (
<div key={columnIdx} className="column">
{column.map((item, itemIdx) => {
const animationOrder = Math.min(columnIdx + itemIdx * columnCount, 25)
const animationDelay = `calc(${animationOrder} * var(--animation-step-delay))`
return (
<a
className="item"
href={`/art/${item.id}`}
onClick={(e) =>
routeIntercept(e, item, {
position: scrollRoot?.scrollTop ?? 0,
items: items,
} as RecoverForLayoutBrowser)
}>
<img
style={{ animationDelay }}
className="preview animation-fall-in"
src={`${CDN_BASE}/${item.id}/preview.avif`}
/>
<div className="metadata">
<div className="title">{item.title}</div>
</div>
</a>
)
})}
</div>
))}
</div>
)
}