refactor: align files tab with content tab design (#5621)
* fix: files.vue bugs before styling changes * feat: move files tab to shared layout structure * fix: qa * fix: qa * fix: bugs * fix: lint * fix: admonition cleanup with progress + actions * fix: cleanup * fix: modals * fix: admon title * fix: i18n standard * fix: lint + i18n pass * fix: remove transition * fix: type errors * feat: files tab in app * fix: qa * fix: backup item minmax * fix: use ContentPageHeader for server panel * fix: lint * fix: lint * fix: lint * feat: page leave safety * fix: lint * fix: cargo fmt fix * fix: blank in prod * fix: content card table stuff * Revert "fix: blank in prod" This reverts commit 74758fe185cf85a4a20355857f889cb091b97ace. * fix: import * feat: browse worlds/servers flow * fix: worlds tab parity with content tab * fix: perf bug + shader filter pill copy * feat: singleplayer filter * fix: ordering * fix: breadcrumbs * fix: lint * fix: qa * feat: store server proj id when adding to a non-linked instance * fix: lint * fix: i18n + qa * fix: conflict * qa: already installed modal + placeholders not server-specific * fix: qa * fix: add + edit server modals * fix: qa * fix: security * fix: devin flags * fix: lint * chore: change file to break build cache * fix: admon * fix: import path stuff * feat: qa * fix: fmt fmt idiot --------- Signed-off-by: Calum H. <calum@modrinth.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import type { Ref } from 'vue'
|
||||
import { computed, ref, watchEffect } from 'vue'
|
||||
import { computed, ref, watch, watchEffect } from 'vue'
|
||||
|
||||
export interface VirtualScrollOptions {
|
||||
itemHeight: number
|
||||
@@ -16,6 +16,7 @@ export function useVirtualScroll<T>(items: Ref<T[]>, options: VirtualScrollOptio
|
||||
const scrollContainer = ref<HTMLElement | Window | null>(null)
|
||||
const scrollTop = ref(0)
|
||||
const viewportHeight = ref(0)
|
||||
const containerOffset = ref(0)
|
||||
|
||||
const totalHeight = computed(() => items.value.length * itemHeight)
|
||||
|
||||
@@ -41,13 +42,25 @@ export function useVirtualScroll<T>(items: Ref<T[]>, options: VirtualScrollOptio
|
||||
return container instanceof Window ? window.innerHeight : container.clientHeight
|
||||
}
|
||||
|
||||
function getContainerOffset(listEl: HTMLElement, container: HTMLElement | Window): number {
|
||||
function updateContainerOffset() {
|
||||
const listEl = listContainer.value
|
||||
const container = scrollContainer.value
|
||||
if (!listEl || !container) return
|
||||
|
||||
if (container instanceof Window) {
|
||||
return listEl.getBoundingClientRect().top + window.scrollY
|
||||
containerOffset.value = listEl.getBoundingClientRect().top + window.scrollY
|
||||
} else {
|
||||
const listRect = listEl.getBoundingClientRect()
|
||||
const containerRect = container.getBoundingClientRect()
|
||||
containerOffset.value = listRect.top - containerRect.top + container.scrollTop
|
||||
}
|
||||
const listRect = listEl.getBoundingClientRect()
|
||||
const containerRect = container.getBoundingClientRect()
|
||||
return listRect.top - containerRect.top + container.scrollTop
|
||||
}
|
||||
|
||||
function syncScrollState() {
|
||||
if (!scrollContainer.value) return
|
||||
scrollTop.value = getScrollTop(scrollContainer.value)
|
||||
viewportHeight.value = getViewportHeight(scrollContainer.value)
|
||||
updateContainerOffset()
|
||||
}
|
||||
|
||||
const visibleRange = computed(() => {
|
||||
@@ -57,15 +70,17 @@ export function useVirtualScroll<T>(items: Ref<T[]>, options: VirtualScrollOptio
|
||||
|
||||
if (!listContainer.value || !scrollContainer.value) return { start: 0, end: 0 }
|
||||
|
||||
const containerOffset = getContainerOffset(listContainer.value, scrollContainer.value)
|
||||
const relativeScrollTop = Math.max(0, scrollTop.value - containerOffset)
|
||||
const relativeScrollTop = Math.max(0, scrollTop.value - containerOffset.value)
|
||||
|
||||
const start = Math.floor(relativeScrollTop / itemHeight)
|
||||
const visibleCount = Math.ceil(viewportHeight.value / itemHeight)
|
||||
|
||||
const rangeStart = Math.max(0, start - bufferSize)
|
||||
const rangeEnd = Math.min(items.value.length, start + visibleCount + bufferSize * 2)
|
||||
|
||||
return {
|
||||
start: Math.max(0, start - bufferSize),
|
||||
end: Math.min(items.value.length, start + visibleCount + bufferSize * 2),
|
||||
start: Math.min(rangeStart, rangeEnd),
|
||||
end: rangeEnd,
|
||||
}
|
||||
})
|
||||
|
||||
@@ -91,16 +106,20 @@ export function useVirtualScroll<T>(items: Ref<T[]>, options: VirtualScrollOptio
|
||||
function handleScroll() {
|
||||
if (scrollContainer.value) {
|
||||
scrollTop.value = getScrollTop(scrollContainer.value)
|
||||
updateContainerOffset()
|
||||
}
|
||||
checkNearEnd()
|
||||
}
|
||||
|
||||
function handleResize() {
|
||||
if (scrollContainer.value) {
|
||||
viewportHeight.value = getViewportHeight(scrollContainer.value)
|
||||
}
|
||||
syncScrollState()
|
||||
}
|
||||
|
||||
// Re-sync scroll state when items change to avoid stale scrollTop/offset
|
||||
watch(items, () => {
|
||||
syncScrollState()
|
||||
})
|
||||
|
||||
watchEffect((onCleanup) => {
|
||||
if (typeof window === 'undefined') return
|
||||
|
||||
@@ -109,15 +128,24 @@ export function useVirtualScroll<T>(items: Ref<T[]>, options: VirtualScrollOptio
|
||||
|
||||
const container = findScrollableAncestor(listEl)
|
||||
scrollContainer.value = container
|
||||
viewportHeight.value = getViewportHeight(container)
|
||||
scrollTop.value = getScrollTop(container)
|
||||
syncScrollState()
|
||||
|
||||
container.addEventListener('scroll', handleScroll, { passive: true })
|
||||
window.addEventListener('resize', handleResize, { passive: true })
|
||||
|
||||
// Use ResizeObserver for element scroll containers
|
||||
let resizeObserver: ResizeObserver | undefined
|
||||
if (!(container instanceof Window)) {
|
||||
resizeObserver = new ResizeObserver(() => {
|
||||
syncScrollState()
|
||||
})
|
||||
resizeObserver.observe(container)
|
||||
}
|
||||
|
||||
onCleanup(() => {
|
||||
container.removeEventListener('scroll', handleScroll)
|
||||
window.removeEventListener('resize', handleResize)
|
||||
resizeObserver?.disconnect()
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user