Browse Source

fix: gallery view fixes

pull/9799/head
DarkPhoenix2704 2 weeks ago
parent
commit
dae06d31f9
  1. 166
      packages/nc-gui/components/smartsheet/Gallery.vue

166
packages/nc-gui/components/smartsheet/Gallery.vue

@ -1,4 +1,4 @@
<script lang="ts" setup> <script setup lang="ts">
import { UITypes, ViewTypes, isVirtualCol } from 'nocodb-sdk' import { UITypes, ViewTypes, isVirtualCol } from 'nocodb-sdk'
import type { Attachment } from '../../lib/types' import type { Attachment } from '../../lib/types'
import type { Row as RowType } from '#imports' import type { Row as RowType } from '#imports'
@ -27,8 +27,8 @@ provide(IsGalleryInj, ref(true))
provide(IsGridInj, ref(false)) provide(IsGridInj, ref(false))
provide(IsCalendarInj, ref(false)) provide(IsCalendarInj, ref(false))
provide(RowHeightInj, ref(1 as const)) provide(RowHeightInj, ref(1 as const))
// provide view data reload hook as fallback to row data reload
provide(ReloadRowDataHookInj, reloadViewDataHook!) provide(ReloadRowDataHookInj, reloadViewDataHook!)
const { const {
fetchChunk, fetchChunk,
loadGalleryData, loadGalleryData,
@ -54,7 +54,6 @@ const coverImageColumn: any = computed(() =>
const coverImageObjectFitClass = computed(() => { const coverImageObjectFitClass = computed(() => {
const fk_cover_image_object_fit = parseProp(galleryData.value?.meta)?.fk_cover_image_object_fit || CoverImageObjectFit.FIT const fk_cover_image_object_fit = parseProp(galleryData.value?.meta)?.fk_cover_image_object_fit || CoverImageObjectFit.FIT
if (fk_cover_image_object_fit === CoverImageObjectFit.FIT) return '!object-contain' if (fk_cover_image_object_fit === CoverImageObjectFit.FIT) return '!object-contain'
if (fk_cover_image_object_fit === CoverImageObjectFit.COVER) return '!object-cover' if (fk_cover_image_object_fit === CoverImageObjectFit.COVER) return '!object-cover'
}) })
@ -158,8 +157,6 @@ const handleClick = (col, event) => {
openNewRecordFormHook?.on(openNewRecordFormHookHandler) openNewRecordFormHook?.on(openNewRecordFormHookHandler)
// remove openNewRecordFormHookHandler before unmounting
// so that it won't be triggered multiple times
onBeforeUnmount(() => openNewRecordFormHook.off(openNewRecordFormHookHandler)) onBeforeUnmount(() => openNewRecordFormHook.off(openNewRecordFormHookHandler))
const reloadAttachments = ref(false) const reloadAttachments = ref(false)
@ -190,7 +187,7 @@ const scrollTop = ref(0)
const rowSlice = reactive({ const rowSlice = reactive({
start: 0, start: 0,
end: 100, end: 12,
}) })
const { width: scrollContainerWidth } = useElementSize(scrollContainer) const { width: scrollContainerWidth } = useElementSize(scrollContainer)
@ -255,34 +252,35 @@ const updateVisibleRows = async () => {
clearCache(Math.max(0, start - BUFFER_SIZE), Math.min(totalRows.value, end + BUFFER_SIZE)) clearCache(Math.max(0, start - BUFFER_SIZE), Math.min(totalRows.value, end + BUFFER_SIZE))
} }
const containerTransformY = ref(0)
const calculateSlices = () => { const calculateSlices = () => {
if (!scrollContainer.value || cardHeight.value === 0) { if (!scrollContainer.value) {
setTimeout(calculateSlices, 50) setTimeout(calculateSlices, 50)
return return
} }
const { clientHeight, scrollHeight } = scrollContainer.value const { clientHeight } = scrollContainer.value
const scrollTopRow = Math.floor(scrollTop.value / (cardHeight.value + 12)) const visibleRowStart = Math.floor(scrollTop.value / (cardHeight.value + 12))
const rowsVisible = Math.ceil((clientHeight - 12) / (cardHeight.value + 12))
const visibleRows = Math.ceil(clientHeight / (cardHeight.value + 12))
const BUFFER_ROWS = 2 const BUFFER_ROWS = 2
const startRow = Math.max(0, scrollTopRow - BUFFER_ROWS) * columnsPerRow.value
const endRow = Math.min(totalRows.value, (scrollTopRow + visibleRows + BUFFER_ROWS) * columnsPerRow.value)
const isNearBottom = scrollTop.value + clientHeight >= scrollHeight - cardHeight.value * 2 const startRecordIndex = Math.max(0, visibleRowStart - BUFFER_ROWS) * columnsPerRow.value
const endRecordIndex = Math.min((visibleRowStart + rowsVisible + BUFFER_ROWS) * columnsPerRow.value, totalRows.value)
rowSlice.start = startRecordIndex
rowSlice.end = endRecordIndex
rowSlice.start = startRow const val = Math.ceil(rowSlice.start / columnsPerRow.value) * (cardHeight.value + 12)
rowSlice.end = isNearBottom ? endRow + columnsPerRow.value * 2 : endRow
containerTransformY.value = val
updateVisibleRows() updateVisibleRows()
} }
const containerTransformY = computed(() => {
const rowStartIndex = Math.floor(rowSlice.start / columnsPerRow.value)
return rowStartIndex * (cardHeight.value + 12)
})
const containerHeight = computed(() => { const containerHeight = computed(() => {
const numberOfRows = Math.ceil(totalRows.value / columnsPerRow.value) const numberOfRows = Math.ceil(totalRows.value / columnsPerRow.value)
return numberOfRows * cardHeight.value + (numberOfRows - 1) * 12 return numberOfRows * cardHeight.value + (numberOfRows - 1) * 12
@ -293,10 +291,9 @@ let scrollRaf = false
useScroll(scrollContainer, { useScroll(scrollContainer, {
onScroll: (e) => { onScroll: (e) => {
if (scrollRaf) return if (scrollRaf) return
scrollRaf = true scrollRaf = true
requestAnimationFrame(() => { requestAnimationFrame(() => {
scrollTop.value = e.target?.scrollTop scrollTop.value = e.target?.scrollTop || 0
calculateSlices() calculateSlices()
scrollRaf = false scrollRaf = false
}) })
@ -327,21 +324,20 @@ watch(
}, },
) )
const { width, height } = useWindowSize() const placeholderAboveHeight = computed(() => {
const visibleRowStart = Math.floor(scrollTop.value / (cardHeight.value + 12))
const startRecordIndex = Math.max(0, visibleRowStart - 2)
return startRecordIndex * (cardHeight.value + 12)
})
let resizeTimeout: number | null = null const { width, height } = useWindowSize()
watch( watch(
[width, height, cardHeight, columnsPerRow], [() => width.value, () => height.value, () => columnsPerRow.value],
() => { () => {
if (resizeTimeout) { calculateSlices()
clearTimeout(resizeTimeout)
}
resizeTimeout = setTimeout(() => {
calculateSlices()
resizeTimeout = null
}, 150) as unknown as number
}, },
{ {
immediate: true, immediate: true,
@ -350,50 +346,52 @@ watch(
reloadViewDataHook?.on(async () => { reloadViewDataHook?.on(async () => {
clearCache(Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY) clearCache(Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY)
await syncCount()
calculateSlices() calculateSlices()
await updateVisibleRows()
}) })
</script> </script>
<template> <template>
<NcDropdown <div
v-model:visible="contextMenu" ref="scrollContainer"
:disabled="contextMenuTarget === null" data-testid="nc-gallery-wrapper"
:trigger="isSqlView ? [] : ['contextmenu']" class="flex flex-col w-full nc-gallery select-none relative nc-scrollbar-md bg-gray-50 h-[calc(100svh-93px)]"
overlay-class-name="nc-dropdown-grid-context-menu"
> >
<template #overlay> <NcDropdown
<NcMenu @click="contextMenu = false"> v-model:visible="contextMenu"
<NcMenuItem v-if="contextMenuTarget" @click="expandForm(contextMenuTarget.row)"> :disabled="contextMenuTarget === null"
<div v-e="['a:row:expand-record']" class="flex items-center gap-2"> :trigger="isSqlView ? [] : ['contextmenu']"
<component :is="iconMap.expand" class="flex" /> overlay-class-name="nc-dropdown-grid-context-menu"
{{ $t('activity.expandRecord') }}
</div>
</NcMenuItem>
<NcDivider />
<NcMenuItem
v-if="contextMenuTarget?.index !== undefined"
class="!text-red-600 !hover:bg-red-50"
@click="deleteRow(contextMenuTarget.index)"
>
<div v-e="['a:row:delete']" class="flex items-center gap-2">
<component :is="iconMap.delete" class="flex" />
<!-- Delete Row -->
{{ $t('activity.deleteRow') }}
</div>
</NcMenuItem>
</NcMenu>
</template>
<div
ref="scrollContainer"
data-testid="nc-gallery-wrapper"
class="flex flex-col w-full nc-gallery select-none relative nc-scrollbar-md bg-gray-50 h-[calc(100svh-93px)]"
> >
<div> <template #overlay>
<div :style="{ height: `${containerHeight}px` }"> <NcMenu @click="contextMenu = false">
<div :style="{ transform: `translateY(${containerTransformY}px)` }" class="nc-gallery-container grid gap-3 p-3"> <NcMenuItem v-if="contextMenuTarget" @click="expandForm(contextMenuTarget.row)">
<div v-e="['a:row:expand-record']" class="flex items-center gap-2">
<component :is="iconMap.expand" class="flex" />
{{ $t('activity.expandRecord') }}
</div>
</NcMenuItem>
<NcDivider />
<NcMenuItem
v-if="contextMenuTarget?.index !== undefined"
class="!text-red-600 !hover:bg-red-50"
@click="deleteRow(contextMenuTarget.index)"
>
<div v-e="['a:row:delete']" class="flex items-center gap-2">
<component :is="iconMap.delete" class="flex" />
{{ $t('activity.deleteRow') }}
</div>
</NcMenuItem>
</NcMenu>
</template>
<div
:class="{
'h-full': totalRows < 30,
}"
>
<div :key="containerHeight" class="relative" :style="{ height: `${containerHeight}px` }">
<div :style="{ height: `${placeholderAboveHeight}px` }"></div>
<div class="nc-gallery-container grid gap-3 p-3">
<div <div
v-for="(record, rowIndex) in visibleRows" v-for="(record, rowIndex) in visibleRows"
:key="`record-${record.rowMeta.rowIndex}`" :key="`record-${record.rowMeta.rowIndex}`"
@ -420,7 +418,6 @@ reloadViewDataHook?.on(async () => {
</div> </div>
</a> </a>
</template> </template>
<template #prevArrow> <template #prevArrow>
<div class="z-10 arrow"> <div class="z-10 arrow">
<NcButton <NcButton
@ -432,7 +429,6 @@ reloadViewDataHook?.on(async () => {
</NcButton> </NcButton>
</div> </div>
</template> </template>
<template #nextArrow> <template #nextArrow>
<div class="z-10 arrow"> <div class="z-10 arrow">
<NcButton <NcButton
@ -444,7 +440,6 @@ reloadViewDataHook?.on(async () => {
</NcButton> </NcButton>
</div> </div>
</template> </template>
<template v-for="(attachment, index) in attachments(record)"> <template v-for="(attachment, index) in attachments(record)">
<LazyCellAttachmentPreviewImage <LazyCellAttachmentPreviewImage
v-if="isImage(attachment.title, attachment.mimetype ?? attachment.type)" v-if="isImage(attachment.title, attachment.mimetype ?? attachment.type)"
@ -470,7 +465,6 @@ reloadViewDataHook?.on(async () => {
:column="displayField" :column="displayField"
:row="record" :row="record"
/> />
<LazySmartsheetCell <LazySmartsheetCell
v-else v-else
v-model="record.row[displayField.title]" v-model="record.row[displayField.title]"
@ -482,7 +476,6 @@ reloadViewDataHook?.on(async () => {
</template> </template>
<template v-else> - </template> <template v-else> - </template>
</h2> </h2>
<div <div
v-for="col in fieldsWithoutDisplay" v-for="col in fieldsWithoutDisplay"
:key="`record-${record.rowMeta.rowIndex}-${col.id}`" :key="`record-${record.rowMeta.rowIndex}-${col.id}`"
@ -495,11 +488,9 @@ reloadViewDataHook?.on(async () => {
<div class="flex flex-row w-full justify-start"> <div class="flex flex-row w-full justify-start">
<div class="nc-card-col-header w-full !children:text-gray-500"> <div class="nc-card-col-header w-full !children:text-gray-500">
<LazySmartsheetHeaderVirtualCell v-if="isVirtualCol(col)" :column="col" :hide-menu="true" /> <LazySmartsheetHeaderVirtualCell v-if="isVirtualCol(col)" :column="col" :hide-menu="true" />
<LazySmartsheetHeaderCell v-else :column="col" :hide-menu="true" /> <LazySmartsheetHeaderCell v-else :column="col" :hide-menu="true" />
</div> </div>
</div> </div>
<div <div
v-if="!isRowEmpty(record, col)" v-if="!isRowEmpty(record, col)"
class="flex flex-row w-full text-gray-800 items-center justify-start min-h-7 py-1" class="flex flex-row w-full text-gray-800 items-center justify-start min-h-7 py-1"
@ -511,7 +502,6 @@ reloadViewDataHook?.on(async () => {
:row="record" :row="record"
class="!text-gray-800" class="!text-gray-800"
/> />
<LazySmartsheetCell <LazySmartsheetCell
v-else v-else
v-model="record.row[col.title]" v-model="record.row[col.title]"
@ -535,17 +525,16 @@ reloadViewDataHook?.on(async () => {
</div> </div>
</div> </div>
</div> </div>
<div class="sticky bottom-4"> </NcDropdown>
<NcButton v-if="isUIAllowed('dataInsert')" size="xs" type="secondary" class="ml-4" @click="openNewRecordFormHook.trigger"> <div class="sticky bottom-4">
<div class="flex items-center gap-2"> <NcButton v-if="isUIAllowed('dataInsert')" size="xs" type="secondary" class="ml-4" @click="openNewRecordFormHook.trigger">
<component :is="iconMap.plus" class="" /> <div class="flex items-center gap-2">
{{ $t('activity.newRecord') }} <component :is="iconMap.plus" class="" />
</div> {{ $t('activity.newRecord') }}
</NcButton> </div>
</div> </NcButton>
</div> </div>
</NcDropdown> </div>
<Suspense> <Suspense>
<LazySmartsheetExpandedForm <LazySmartsheetExpandedForm
v-if="expandedFormRow && expandedFormDlg" v-if="expandedFormRow && expandedFormDlg"
@ -557,7 +546,6 @@ reloadViewDataHook?.on(async () => {
:view="view" :view="view"
/> />
</Suspense> </Suspense>
<Suspense> <Suspense>
<LazySmartsheetExpandedForm <LazySmartsheetExpandedForm
v-if="expandedFormOnRowIdDlg && meta?.id" v-if="expandedFormOnRowIdDlg && meta?.id"

Loading…
Cancel
Save