Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 41 additions & 22 deletions src/components/common/VirtualGrid.vue
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
<template>
<div ref="container" class="scroll-container">
<div :style="{ height: `${(state.start / cols) * itemHeight}px` }" />
<div :style="gridStyle">
<div v-for="item in renderedItems" :key="item.key" data-virtual-grid-item>
<div
ref="container"
class="h-full overflow-y-auto scrollbar-thin scrollbar-track-transparent scrollbar-thumb-(--dialog-surface)"
>
<div :style="topSpacerStyle" />
<div :style="mergedGridStyle">
<div
v-for="item in renderedItems"
:key="item.key"
class="transition-[width] duration-150 ease-out"
data-virtual-grid-item
>
<slot name="item" :item="item" />
</div>
</div>
<div
:style="{
height: `${((items.length - state.end) / cols) * itemHeight}px`
}"
/>
<div :style="bottomSpacerStyle" />
</div>
</template>

Expand All @@ -28,19 +32,22 @@ type GridState = {

const {
items,
gridStyle,
bufferRows = 1,
scrollThrottle = 64,
resizeDebounce = 64,
defaultItemHeight = 200,
defaultItemWidth = 200
defaultItemWidth = 200,
maxColumns = Infinity
} = defineProps<{
items: (T & { key: string })[]
gridStyle: Partial<CSSProperties>
gridStyle: CSSProperties
bufferRows?: number
scrollThrottle?: number
resizeDebounce?: number
defaultItemHeight?: number
defaultItemWidth?: number
maxColumns?: number
}>()

const emit = defineEmits<{
Expand All @@ -59,7 +66,18 @@ const { y: scrollY } = useScroll(container, {
eventListenerOptions: { passive: true }
})

const cols = computed(() => Math.floor(width.value / itemWidth.value) || 1)
const cols = computed(() =>
Math.min(Math.floor(width.value / itemWidth.value) || 1, maxColumns)
)

const mergedGridStyle = computed<CSSProperties>(() => {
if (maxColumns === Infinity) return gridStyle
return {
...gridStyle,
gridTemplateColumns: `repeat(${maxColumns}, minmax(0, 1fr))`
}
})

const viewRows = computed(() => Math.ceil(height.value / itemHeight.value))
const offsetRows = computed(() => Math.floor(scrollY.value / itemHeight.value))
const isValidGrid = computed(() => height.value && width.value && items?.length)
Expand All @@ -83,6 +101,16 @@ const renderedItems = computed(() =>
isValidGrid.value ? items.slice(state.value.start, state.value.end) : []
)

function rowsToHeight(rows: number): string {
return `${(rows / cols.value) * itemHeight.value}px`
}
const topSpacerStyle = computed<CSSProperties>(() => ({
height: rowsToHeight(state.value.start)
}))
const bottomSpacerStyle = computed<CSSProperties>(() => ({
height: rowsToHeight(items.length - state.value.end)
}))
Comment on lines +104 to +112
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Consider renaming rowsToHeight for clarity.

The function accepts an item count (not rows), converts to rows by dividing by cols, then computes pixel height. The name rowsToHeight is misleading.

♻️ Suggested rename
-function rowsToHeight(rows: number): string {
-  return `${(rows / cols.value) * itemHeight.value}px`
+function itemCountToHeight(itemCount: number): string {
+  return `${(itemCount / cols.value) * itemHeight.value}px`
 }
 const topSpacerStyle = computed<CSSProperties>(() => ({
-  height: rowsToHeight(state.value.start)
+  height: itemCountToHeight(state.value.start)
 }))
 const bottomSpacerStyle = computed<CSSProperties>(() => ({
-  height: rowsToHeight(items.length - state.value.end)
+  height: itemCountToHeight(items.length - state.value.end)
 }))
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function rowsToHeight(rows: number): string {
return `${(rows / cols.value) * itemHeight.value}px`
}
const topSpacerStyle = computed<CSSProperties>(() => ({
height: rowsToHeight(state.value.start)
}))
const bottomSpacerStyle = computed<CSSProperties>(() => ({
height: rowsToHeight(items.length - state.value.end)
}))
function itemCountToHeight(itemCount: number): string {
return `${(itemCount / cols.value) * itemHeight.value}px`
}
const topSpacerStyle = computed<CSSProperties>(() => ({
height: itemCountToHeight(state.value.start)
}))
const bottomSpacerStyle = computed<CSSProperties>(() => ({
height: itemCountToHeight(items.length - state.value.end)
}))
🤖 Prompt for AI Agents
In `@src/components/common/VirtualGrid.vue` around lines 104 - 112, The helper
rowsToHeight is misleading because it takes an item count and divides by cols;
rename it (e.g., itemsCountToHeight or countToHeight) and its parameter from
rows to count to make intent clear, then update both call sites in
topSpacerStyle and bottomSpacerStyle to use the new name; ensure the function
signature (e.g., function countToHeight(count: number): string) and its internal
formula remain the same and adjust any type annotations if needed.


whenever(
() => state.value.isNearEnd,
() => {
Expand All @@ -109,15 +137,6 @@ const onResize = debounce(updateItemSize, resizeDebounce)
watch([width, height], onResize, { flush: 'post' })
whenever(() => items, updateItemSize, { flush: 'post' })
onBeforeUnmount(() => {
onResize.cancel() // Clear pending debounced calls
onResize.cancel()
})
</script>

<style scoped>
.scroll-container {
height: 100%;
overflow-y: auto;
scrollbar-width: thin;
scrollbar-color: var(--dialog-surface) transparent;
}
</style>
19 changes: 17 additions & 2 deletions src/platform/assets/components/AssetGrid.vue
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,10 @@
<VirtualGrid
v-else
:items="assetsWithKey"
:grid-style="gridStyle"
:grid-style
:default-item-height="320"
:default-item-width="240"
:max-columns
>
<template #item="{ item }">
<AssetCard
Expand All @@ -43,6 +44,7 @@
</template>

<script setup lang="ts">
import { breakpointsTailwind, useBreakpoints } from '@vueuse/core'
import type { CSSProperties } from 'vue'
import { computed } from 'vue'

Expand All @@ -64,7 +66,20 @@ const assetsWithKey = computed(() =>
assets.map((asset) => ({ ...asset, key: asset.id }))
)

const gridStyle: Partial<CSSProperties> = {
const breakpoints = useBreakpoints(breakpointsTailwind)
const is2Xl = breakpoints.greaterOrEqual('2xl')
const isXl = breakpoints.greaterOrEqual('xl')
const isLg = breakpoints.greaterOrEqual('lg')
const isMd = breakpoints.greaterOrEqual('md')
const maxColumns = computed(() => {
if (is2Xl.value) return 5
if (isXl.value) return 4
if (isLg.value) return 3
if (isMd.value) return 2
return 1
})

const gridStyle: CSSProperties = {
display: 'grid',
gridTemplateColumns: 'repeat(auto-fill, minmax(15rem, 1fr))',
gap: '1rem',
Expand Down
Loading