rc-1
This commit is contained in:
@@ -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>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user