Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
95 lines
3.9 KiB
TypeScript
95 lines
3.9 KiB
TypeScript
import type { Ref } from 'vue'
|
|
import { useWindowSize } from '@vueuse/core'
|
|
|
|
// Spiegelt das Upload-Grid aus index.vue:
|
|
// grid-cols-4 sm:6 md:8 lg:12, auto-rows 6.5rem / sm:7rem / lg:7.5rem, gap-2
|
|
const PAGINATION_RESERVE_PX = 60 // Pagination (~36px) + pt-4, knapp bemessen damit die letzte Zeile nicht wegfällt
|
|
const FALLBACK_TOP_PX = 150 // SSR-Schätzung: Header + Padding + Filterzeile
|
|
const DEFAULT_SSR_WIDTH = 1920
|
|
const DEFAULT_SSR_HEIGHT = 1080
|
|
|
|
interface PageSizeOptions {
|
|
// [base, sm (>=640px), lg (>=1024px)] - Zeilenhöhe in rem ohne Grid-Gap
|
|
rowHeights?: [number, number, number]
|
|
// Abstand zwischen Anker-Unterkante und Grid-Oberkante in px (mb-* der Zeile)
|
|
gapBelowAnchorPx?: number
|
|
// Zusätzlicher Platzbedarf unter dem Grid in px (Seiten-Padding, Spacer)
|
|
bottomReservePx?: number
|
|
}
|
|
|
|
export function usePageSize(anchor: Ref<HTMLElement | null | undefined>, options: PageSizeOptions = {}) {
|
|
const { rowHeights = [6.5, 7, 7.5], gapBelowAnchorPx = 8, bottomReservePx = 0 } = options
|
|
const savedVp = useCookie<{ w: number, h: number }>('vp-size', {
|
|
default: () => ({ w: DEFAULT_SSR_WIDTH, h: DEFAULT_SSR_HEIGHT }),
|
|
sameSite: 'lax',
|
|
})
|
|
const { width, height } = useWindowSize({
|
|
initialWidth: savedVp.value?.w || DEFAULT_SSR_WIDTH,
|
|
initialHeight: savedVp.value?.h || DEFAULT_SSR_HEIGHT,
|
|
})
|
|
if (import.meta.client) {
|
|
watch([width, height], ([w, h]) => {
|
|
if (Number.isFinite(w) && Number.isFinite(h) && w > 0 && h > 0) {
|
|
if (savedVp.value?.w !== w || savedVp.value?.h !== h) {
|
|
savedVp.value = { w, h }
|
|
}
|
|
}
|
|
}, { immediate: true })
|
|
}
|
|
|
|
const cols = computed(() => {
|
|
const w = Number.isFinite(width.value) ? width.value : (savedVp.value?.w || DEFAULT_SSR_WIDTH)
|
|
return w >= 1024 ? 12 : w >= 768 ? 8 : w >= 640 ? 6 : 4
|
|
})
|
|
|
|
// Zell-Budget: Wie viele 1x1-Zellen passen ohne Scrollen in den Viewport.
|
|
// Die Grid-Oberkante wird am DOM-Element (Filterzeile) gemessen statt
|
|
// Header/Padding zu schätzen.
|
|
// Während einer laufenden Route-/View-Transition ist in SiteBreadcrumb
|
|
// noch route.path === '/t/...' aktiv, bis NuxtPage den neuen View fertig
|
|
// eingehängt hat - die Breadcrumb-Zeile (~36 px) schiebt das Anchor-Element
|
|
// dabei temporär nach unten und kostet sonst genau eine Grid-Zeile.
|
|
// Daher zusätzlich die Breadcrumb-Höhe abziehen, wenn wir auf '/' sind.
|
|
const route = useRoute()
|
|
const anchorBottom = ref(FALLBACK_TOP_PX)
|
|
function measureAnchor() {
|
|
const el = anchor.value
|
|
if (!el) return
|
|
const scroller = document.getElementById('main-scroll')
|
|
const scrollerScrollTop = scroller?.scrollTop ?? 0
|
|
let bottom = el.getBoundingClientRect().bottom + scrollerScrollTop
|
|
if (route.path === '/') {
|
|
const bc = scroller?.querySelector('[data-slot="breadcrumb"]') as HTMLElement | null
|
|
if (bc) bottom -= bc.getBoundingClientRect().height
|
|
}
|
|
anchorBottom.value = bottom
|
|
}
|
|
watch([anchor, () => route.path], () => {
|
|
measureAnchor()
|
|
nextTick(measureAnchor)
|
|
}, { flush: 'post' })
|
|
const ready = ref(false)
|
|
onMounted(() => {
|
|
measureAnchor()
|
|
requestAnimationFrame(() => {
|
|
measureAnchor()
|
|
ready.value = true
|
|
})
|
|
})
|
|
watch([width, height], measureAnchor, { flush: 'post' })
|
|
|
|
const cells = computed(() => {
|
|
const w = Number.isFinite(width.value) ? width.value : (savedVp.value?.w || DEFAULT_SSR_WIDTH)
|
|
const h = Number.isFinite(height.value) ? height.value : (savedVp.value?.h || DEFAULT_SSR_HEIGHT)
|
|
const rem = import.meta.client
|
|
? Number.parseFloat(getComputedStyle(document.documentElement).fontSize) || 16
|
|
: 16
|
|
const rowH = (w >= 1024 ? rowHeights[2] : w >= 640 ? rowHeights[1] : rowHeights[0]) * rem + 8 // + grid gap
|
|
const top = anchorBottom.value + gapBelowAnchorPx
|
|
const rows = Math.max(1, Math.floor((h - top - PAGINATION_RESERVE_PX - bottomReservePx) / rowH))
|
|
return cols.value * rows
|
|
})
|
|
|
|
return { cells, cols, ready }
|
|
}
|