Skip to content

Commit

Permalink
feat: virtual scroll for grid (#8356)
Browse files Browse the repository at this point in the history
* feat: virtual scroll for grid

* feat: improve virtual scroll

* fix: remove unused expose & ref

* feat: move row ltar helpers to parent level

* fix: use shared composable for useMetas

* fix: column add issue

* fix: reload issue

* feat: move cell state to computed

* chore: lint

* fix: null check for sticky field

* fix: PR requested changes

* fix: shared views

* fix: provide row store calls

* test: avoid all rows selector

* fix: group by

* fix: include isVirtualCol in cellMeta

* fix: split colMeta and cellMeta

* chore: lint

* test: edit column flakiness

* test: renderColumn for dashboard grid

* test: user column test flakiness
  • Loading branch information
mertmit committed Apr 29, 2024
1 parent ba338d3 commit fed1c7b
Show file tree
Hide file tree
Showing 30 changed files with 1,071 additions and 705 deletions.
2 changes: 2 additions & 0 deletions packages/nc-gui/components/shared-view/Calendar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ provide(IsPublicInj, ref(true))
useProvideViewColumns(sharedView, meta, () => reloadEventHook?.trigger(), true)
useProvideSmartsheetLtarHelpers(meta)
useProvideSmartsheetStore(sharedView, meta, true, ref([]), nestedFilters)
useProvideKanbanViewStore(meta, sharedView)
Expand Down
2 changes: 2 additions & 0 deletions packages/nc-gui/components/shared-view/Gallery.vue
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ provide(IsPublicInj, ref(true))
useProvideViewColumns(sharedView, meta, () => reloadEventHook?.trigger(), true)
useProvideSmartsheetLtarHelpers(meta)
useProvideSmartsheetStore(sharedView, meta, true, ref([]), nestedFilters)
useProvideKanbanViewStore(meta, sharedView)
Expand Down
2 changes: 2 additions & 0 deletions packages/nc-gui/components/shared-view/Grid.vue
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ provide(IsLockedInj, isLocked)
useProvideViewColumns(sharedView, meta, () => reloadEventHook?.trigger(), true)
useProvideViewGroupBy(sharedView, meta, xWhere, true)
useProvideSmartsheetLtarHelpers(meta)
if (signedIn.value) {
try {
await loadProject()
Expand Down
2 changes: 2 additions & 0 deletions packages/nc-gui/components/shared-view/Kanban.vue
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ provide(IsPublicInj, ref(true))
useProvideViewColumns(sharedView, meta, () => reloadEventHook?.trigger(), true)
useProvideSmartsheetLtarHelpers(meta)
useProvideSmartsheetStore(sharedView, meta, true, ref([]), nestedFilters)
useProvideKanbanViewStore(meta, sharedView, true)
Expand Down
2 changes: 2 additions & 0 deletions packages/nc-gui/components/shared-view/Map.vue
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ provide(IsPublicInj, ref(true))
useProvideViewColumns(sharedView, meta, () => reloadEventHook?.trigger(), true)
useProvideSmartsheetLtarHelpers(meta)
useProvideSmartsheetStore(sharedView, meta, true, ref([]), nestedFilters)
useProvideMapViewStore(meta, sharedView, true)
Expand Down
121 changes: 43 additions & 78 deletions packages/nc-gui/components/smartsheet/Cell.vue
Original file line number Diff line number Diff line change
Expand Up @@ -154,43 +154,10 @@ const onContextmenu = (e: MouseEvent) => {
e.stopPropagation()
}
}
// Todo: move intersection logic to a separate component or a vue directive
const intersected = ref(false)
const intersectionObserver = ref<IntersectionObserver>()
const elementToObserve = ref<Element>()
// load the cell only when it is in the viewport
function initIntersectionObserver() {
intersectionObserver.value = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
// if the cell is in the viewport, load the cell and disconnect the observer
if (entry.isIntersecting) {
intersected.value = true
intersectionObserver.value?.disconnect()
intersectionObserver.value = undefined
}
})
})
}
// observe the cell when it is mounted
onMounted(() => {
initIntersectionObserver()
intersectionObserver.value?.observe(elementToObserve.value!)
})
// disconnect the observer when the cell is unmounted
onUnmounted(() => {
intersectionObserver.value?.disconnect()
})
</script>

<template>
<div
ref="elementToObserve"
:class="[
`nc-cell-${(column?.uidt || 'default').toLowerCase()}`,
{
Expand All @@ -214,51 +181,49 @@ onUnmounted(() => {
@keydown.shift.enter.exact="navigate(NavigateDir.PREV, $event)"
>
<template v-if="column">
<template v-if="intersected">
<LazyCellTextArea v-if="isTextArea(column)" v-model="vModel" :virtual="props.virtual" />
<LazyCellGeoData v-else-if="isGeoData(column)" v-model="vModel" />
<LazyCellCheckbox v-else-if="isBoolean(column, abstractType)" v-model="vModel" />
<LazyCellAttachment v-else-if="isAttachment(column)" v-model="vModel" :row-index="props.rowIndex" />
<LazyCellSingleSelect
v-else-if="isSingleSelect(column)"
v-model="vModel"
:disable-option-creation="!!isEditColumnMenu"
:row-index="props.rowIndex"
/>
<LazyCellMultiSelect
v-else-if="isMultiSelect(column)"
v-model="vModel"
:disable-option-creation="!!isEditColumnMenu"
:row-index="props.rowIndex"
/>
<LazyCellDatePicker v-else-if="isDate(column, abstractType)" v-model="vModel" :is-pk="isPrimaryKey(column)" />
<LazyCellYearPicker v-else-if="isYear(column, abstractType)" v-model="vModel" :is-pk="isPrimaryKey(column)" />
<LazyCellDateTimePicker
v-else-if="isDateTime(column, abstractType)"
v-model="vModel"
:is-pk="isPrimaryKey(column)"
:is-updated-from-copy-n-paste="currentRow.rowMeta.isUpdatedFromCopyNPaste"
/>
<LazyCellTimePicker v-else-if="isTime(column, abstractType)" v-model="vModel" :is-pk="isPrimaryKey(column)" />
<LazyCellRating v-else-if="isRating(column)" v-model="vModel" />
<LazyCellDuration v-else-if="isDuration(column)" v-model="vModel" />
<LazyCellEmail v-else-if="isEmail(column)" v-model="vModel" />
<LazyCellUrl v-else-if="isURL(column)" v-model="vModel" />
<LazyCellPhoneNumber v-else-if="isPhoneNumber(column)" v-model="vModel" />
<LazyCellPercent v-else-if="isPercent(column)" v-model="vModel" />
<LazyCellCurrency v-else-if="isCurrency(column)" v-model="vModel" @save="emit('save')" />
<LazyCellUser v-else-if="isUser(column)" v-model="vModel" :row-index="props.rowIndex" />
<LazyCellDecimal v-else-if="isDecimal(column)" v-model="vModel" />
<LazyCellFloat v-else-if="isFloat(column, abstractType)" v-model="vModel" />
<LazyCellText v-else-if="isString(column, abstractType)" v-model="vModel" />
<LazyCellInteger v-else-if="isInt(column, abstractType)" v-model="vModel" />
<LazyCellJson v-else-if="isJSON(column)" v-model="vModel" />
<LazyCellText v-else v-model="vModel" />
<div
v-if="((isPublic && readOnly && !isForm) || (isSystemColumn(column) && !isAttachment(column))) && !isTextArea(column)"
class="nc-locked-overlay"
/>
</template>
<LazyCellTextArea v-if="isTextArea(column)" v-model="vModel" :virtual="props.virtual" />
<LazyCellGeoData v-else-if="isGeoData(column)" v-model="vModel" />
<LazyCellCheckbox v-else-if="isBoolean(column, abstractType)" v-model="vModel" />
<LazyCellAttachment v-else-if="isAttachment(column)" v-model="vModel" :row-index="props.rowIndex" />
<LazyCellSingleSelect
v-else-if="isSingleSelect(column)"
v-model="vModel"
:disable-option-creation="!!isEditColumnMenu"
:row-index="props.rowIndex"
/>
<LazyCellMultiSelect
v-else-if="isMultiSelect(column)"
v-model="vModel"
:disable-option-creation="!!isEditColumnMenu"
:row-index="props.rowIndex"
/>
<LazyCellDatePicker v-else-if="isDate(column, abstractType)" v-model="vModel" :is-pk="isPrimaryKey(column)" />
<LazyCellYearPicker v-else-if="isYear(column, abstractType)" v-model="vModel" :is-pk="isPrimaryKey(column)" />
<LazyCellDateTimePicker
v-else-if="isDateTime(column, abstractType)"
v-model="vModel"
:is-pk="isPrimaryKey(column)"
:is-updated-from-copy-n-paste="currentRow.rowMeta.isUpdatedFromCopyNPaste"
/>
<LazyCellTimePicker v-else-if="isTime(column, abstractType)" v-model="vModel" :is-pk="isPrimaryKey(column)" />
<LazyCellRating v-else-if="isRating(column)" v-model="vModel" />
<LazyCellDuration v-else-if="isDuration(column)" v-model="vModel" />
<LazyCellEmail v-else-if="isEmail(column)" v-model="vModel" />
<LazyCellUrl v-else-if="isURL(column)" v-model="vModel" />
<LazyCellPhoneNumber v-else-if="isPhoneNumber(column)" v-model="vModel" />
<LazyCellPercent v-else-if="isPercent(column)" v-model="vModel" />
<LazyCellCurrency v-else-if="isCurrency(column)" v-model="vModel" @save="emit('save')" />
<LazyCellUser v-else-if="isUser(column)" v-model="vModel" :row-index="props.rowIndex" />
<LazyCellDecimal v-else-if="isDecimal(column)" v-model="vModel" />
<LazyCellFloat v-else-if="isFloat(column, abstractType)" v-model="vModel" />
<LazyCellText v-else-if="isString(column, abstractType)" v-model="vModel" />
<LazyCellInteger v-else-if="isInt(column, abstractType)" v-model="vModel" />
<LazyCellJson v-else-if="isJSON(column)" v-model="vModel" />
<LazyCellText v-else v-model="vModel" />
<div
v-if="((isPublic && readOnly && !isForm) || (isSystemColumn(column) && !isAttachment(column))) && !isTextArea(column)"
class="nc-locked-overlay"
/>
</template>
</div>
</template>
Expand Down
1 change: 0 additions & 1 deletion packages/nc-gui/components/smartsheet/Form.vue
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,6 @@ reloadEventHook.on(async () => {
const { fields, showAll, hideAll } = useViewColumnsOrThrow()
const { state, row } = useProvideSmartsheetRowStore(
meta,
ref({
row: formState.value,
oldRow: {},
Expand Down
17 changes: 1 addition & 16 deletions packages/nc-gui/components/smartsheet/Row.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
<script lang="ts" setup>
import type { Ref } from 'vue'
import type { TableType } from 'nocodb-sdk'
import type { Row } from '#imports'
import {
ReloadRowDataHookInj,
Expand All @@ -10,7 +8,6 @@ import {
provide,
toRef,
useProvideSmartsheetRowStore,
useSmartsheetStoreOrThrow,
} from '#imports'
const props = defineProps<{
Expand All @@ -19,12 +16,7 @@ const props = defineProps<{
const currentRow = toRef(props, 'row')
const { meta } = useSmartsheetStoreOrThrow()
const { isNew, state, syncLTARRefs, clearLTARCell, addLTARRef, cleaMMCell } = useProvideSmartsheetRowStore(
meta as Ref<TableType>,
currentRow,
)
const { isNew, state } = useProvideSmartsheetRowStore(currentRow)
const reloadViewDataTrigger = inject(ReloadViewDataHookInj)!
Expand All @@ -39,13 +31,6 @@ reloadHook.on((params) => {
})
provide(ReloadRowDataHookInj, reloadHook)
defineExpose({
syncLTARRefs,
clearLTARCell,
addLTARRef,
cleaMMCell,
})
</script>

<template>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
<script lang="ts" setup>
import type { ColumnType, TableType } from 'nocodb-sdk'
import type { Ref } from 'vue'
import { isVirtualCol } from 'nocodb-sdk'
import {
ActiveViewInj,
Expand Down Expand Up @@ -52,7 +49,7 @@ provide(ReloadRowDataHookInj, reloadViewDataHook!)
const currentRow = toRef(props, 'row')
useProvideSmartsheetRowStore(meta as Ref<TableType>, currentRow)
useProvideSmartsheetRowStore(currentRow)
</script>

<template>
Expand Down
4 changes: 1 addition & 3 deletions packages/nc-gui/components/smartsheet/TableDataCell.vue
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
<script lang="ts" setup>
import { CellClickHookInj, CurrentCellInj, createEventHook, ref } from '#imports'
const el = ref<HTMLTableDataCellElement>()
const el = ref<HTMLElement>()
const cellClickHook = createEventHook()
provide(CellClickHookInj, cellClickHook)
provide(CurrentCellInj, el)
defineExpose({ el })
</script>

<template>
Expand Down
61 changes: 13 additions & 48 deletions packages/nc-gui/components/smartsheet/VirtualCell.vue
Original file line number Diff line number Diff line change
Expand Up @@ -62,43 +62,10 @@ function onNavigate(dir: NavigateDir, e: KeyboardEvent) {
if (!isForm.value) e.stopImmediatePropagation()
}
// Todo: move intersection logic to a separate component or a vue directive
const intersected = ref(false)
const intersectionObserver = ref<IntersectionObserver>()
const elementToObserve = ref<Element>()
// load the cell only when it is in the viewport
function initIntersectionObserver() {
intersectionObserver.value = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
// if the cell is in the viewport, load the cell and disconnect the observer
if (entry.isIntersecting) {
intersected.value = true
intersectionObserver.value?.disconnect()
intersectionObserver.value = undefined
}
})
})
}
// observe the cell when it is mounted
onMounted(() => {
initIntersectionObserver()
intersectionObserver.value?.observe(elementToObserve.value!)
})
// disconnect the observer when the cell is unmounted
onUnmounted(() => {
intersectionObserver.value?.disconnect()
})
</script>

<template>
<div
ref="elementToObserve"
class="nc-virtual-cell w-full flex items-center"
:class="{
'text-right justify-end': isGrid && !isForm && isRollup(column) && !isExpandedForm,
Expand All @@ -107,21 +74,19 @@ onUnmounted(() => {
@keydown.enter.exact="onNavigate(NavigateDir.NEXT, $event)"
@keydown.shift.enter.exact="onNavigate(NavigateDir.PREV, $event)"
>
<template v-if="intersected">
<LazyVirtualCellLinks v-if="isLink(column)" />
<LazyVirtualCellHasMany v-else-if="isHm(column)" />
<LazyVirtualCellManyToMany v-else-if="isMm(column)" />
<LazyVirtualCellBelongsTo v-else-if="isBt(column)" />
<LazyVirtualCellOneToOne v-else-if="isOo(column)" />
<LazyVirtualCellRollup v-else-if="isRollup(column)" />
<LazyVirtualCellFormula v-else-if="isFormula(column)" />
<LazyVirtualCellQrCode v-else-if="isQrCode(column)" />
<LazyVirtualCellBarcode v-else-if="isBarcode(column)" />
<LazyVirtualCellCount v-else-if="isCount(column)" />
<LazyVirtualCellLookup v-else-if="isLookup(column)" />
<LazyCellReadOnlyDateTimePicker v-else-if="isCreatedOrLastModifiedTimeCol(column)" :model-value="modelValue" />
<LazyCellReadOnlyUser v-else-if="isCreatedOrLastModifiedByCol(column)" :model-value="modelValue" />
</template>
<LazyVirtualCellLinks v-if="isLink(column)" />
<LazyVirtualCellHasMany v-else-if="isHm(column)" />
<LazyVirtualCellManyToMany v-else-if="isMm(column)" />
<LazyVirtualCellBelongsTo v-else-if="isBt(column)" />
<LazyVirtualCellOneToOne v-else-if="isOo(column)" />
<LazyVirtualCellRollup v-else-if="isRollup(column)" />
<LazyVirtualCellFormula v-else-if="isFormula(column)" />
<LazyVirtualCellQrCode v-else-if="isQrCode(column)" />
<LazyVirtualCellBarcode v-else-if="isBarcode(column)" />
<LazyVirtualCellCount v-else-if="isCount(column)" />
<LazyVirtualCellLookup v-else-if="isLookup(column)" />
<LazyCellReadOnlyDateTimePicker v-else-if="isCreatedOrLastModifiedTimeCol(column)" :model-value="modelValue" />
<LazyCellReadOnlyUser v-else-if="isCreatedOrLastModifiedByCol(column)" :model-value="modelValue" />
</div>
</template>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ const props = defineProps<{
}>()
const emits = defineEmits(['update:value'])
const meta = inject(MetaInj, ref())
provide(EditColumnInj, ref(true))
const vModel = useVModel(props, 'value', emits)
Expand All @@ -20,7 +18,7 @@ const rowRef = ref({
},
})
useProvideSmartsheetRowStore(meta, rowRef)
useProvideSmartsheetRowStore(rowRef)
const cdfValue = ref<string | null>(null)
Expand Down
2 changes: 2 additions & 0 deletions packages/nc-gui/components/smartsheet/expanded-form/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,8 @@ const duplicatingRowInProgress = ref(false)
useProvideSmartsheetStore(ref({}) as Ref<ViewType>, meta)
useProvideSmartsheetLtarHelpers(meta)
watch(
state,
() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ const pagination = computed(() => {
:hide-header="true"
:pagination="pagination"
:disable-skeleton="true"
:disable-virtual-y="true"
/>
</template>

Expand Down

0 comments on commit fed1c7b

Please sign in to comment.