Browse Source

fix(nc-gui): Added lazy rendering in selection option in adding an option

pull/6628/head
Muhammed Mustafa 1 year ago
parent
commit
1477962301
  1. 114
      packages/nc-gui/components/smartsheet/column/SelectOptions.vue

114
packages/nc-gui/components/smartsheet/column/SelectOptions.vue

@ -27,10 +27,16 @@ const { setAdditionalValidations, validateInfos, isMysql, isPg } = useColumnCrea
const { optionsMagic: _optionsMagic } = useNocoEe() const { optionsMagic: _optionsMagic } = useNocoEe()
const optionsWrapperDomRef = ref<HTMLElement>()
const options = ref<(Option & { status?: 'remove' })[]>([]) const options = ref<(Option & { status?: 'remove' })[]>([])
const isAddingOption = ref(false)
// TODO: Implement proper top and bottom virtual scrolling
const OPTIONS_PAGE_COUNT = 20 const OPTIONS_PAGE_COUNT = 20
const loadedOptionCount = ref(OPTIONS_PAGE_COUNT) const loadedOptionAnchor = ref(OPTIONS_PAGE_COUNT)
const isReverseLazyLoad = ref(false)
const renderedOptions = ref<(Option & { status?: 'remove' })[]>([]) const renderedOptions = ref<(Option & { status?: 'remove' })[]>([])
const savedDefaultOption = ref<Option | null>(null) const savedDefaultOption = ref<Option | null>(null)
@ -40,7 +46,6 @@ const colorMenus = ref<any>({})
const colors = ref(enumColor.light) const colors = ref(enumColor.light)
const inputs = ref()
const defaultOption = ref() const defaultOption = ref()
const isKanban = inject(IsKanbanInj, ref(false)) const isKanban = inject(IsKanbanInj, ref(false))
@ -89,11 +94,13 @@ onMounted(() => {
} }
} }
isReverseLazyLoad.value = false
options.value = vModel.value.colOptions.options options.value = vModel.value.colOptions.options
loadedOptionCount.value = Math.min(loadedOptionCount.value, options.value.length) loadedOptionAnchor.value = Math.min(loadedOptionAnchor.value, options.value.length)
renderedOptions.value = [...options.value].slice(0, loadedOptionCount.value) renderedOptions.value = [...options.value].slice(0, loadedOptionAnchor.value)
// Support for older options // Support for older options
for (const op of options.value.filter((el) => el.order === null)) { for (const op of options.value.filter((el) => el.order === null)) {
@ -129,19 +136,36 @@ const getNextColor = () => {
} }
const addNewOption = () => { const addNewOption = () => {
isAddingOption.value = true
const tempOption = { const tempOption = {
title: '', title: '',
color: getNextColor(), color: getNextColor(),
} }
options.value.push(tempOption) options.value.push(tempOption)
loadedOptionCount.value = options.value.length isReverseLazyLoad.value = true
renderedOptions.value = [...options.value]
loadedOptionAnchor.value = options.value.length - OPTIONS_PAGE_COUNT * 2
loadedOptionAnchor.value = Math.max(loadedOptionAnchor.value, 0)
renderedOptions.value = options.value.slice(loadedOptionAnchor.value, options.value.length)
optionsWrapperDomRef.value!.scrollTop = optionsWrapperDomRef.value!.scrollHeight
nextTick(() => { nextTick(() => {
if (inputs.value?.$el) { // Last child doesnt work for query selector
inputs.value.$el.focus() setTimeout(() => {
} const doms = document.querySelectorAll(`.nc-col-option-select-option .nc-select-col-option-select-option`)
const dom = doms[doms.length - 1] as HTMLInputElement
if (dom) {
dom.focus()
}
}, 150)
optionsWrapperDomRef.value!.scrollTop = optionsWrapperDomRef.value!.scrollHeight
isAddingOption.value = false
}) })
} }
@ -181,34 +205,87 @@ const undoRemoveRenderedOption = (index: number) => {
} }
} }
const loadListData = async ($state: any) => { // focus last created input
if (loadedOptionCount.value === options.value.length) { // watch(inputs, () => {
// if (inputs.value?.$el) {
// inputs.value.$el.focus()
// }
// })
// Removes the Select Option from cdf if the option is removed
watch(vModel.value, (next) => {
const cdfs = (next.cdf ?? '').split(',')
const values = (next.colOptions.options ?? []).map((col) => {
return col.title.replace(/^'/, '').replace(/'$/, '')
})
const newCdf = cdfs.filter((c: string) => values.includes(c)).join(',')
next.cdf = newCdf.length === 0 ? null : newCdf
})
const loadListDataReverse = async ($state: any) => {
if (isAddingOption.value) return
if (loadedOptionAnchor.value === 0) {
$state.complete() $state.complete()
return return
} }
$state.loading() $state.loading()
loadedOptionCount.value += OPTIONS_PAGE_COUNT loadedOptionAnchor.value -= OPTIONS_PAGE_COUNT
loadedOptionCount.value = Math.min(loadedOptionCount.value, options.value.length) loadedOptionAnchor.value = Math.max(loadedOptionAnchor.value, 0)
renderedOptions.value = options.value.slice(loadedOptionAnchor.value, options.value.length)
renderedOptions.value = options.value.slice(0, loadedOptionCount.value) optionsWrapperDomRef.value!.scrollTop = optionsWrapperDomRef.value!.scrollTop + 100
if (loadedOptionCount.value === options.value.length) { if (loadedOptionAnchor.value === 0) {
$state.complete() $state.complete()
return return
} }
$state.loaded() $state.loaded()
} }
const loadListData = async ($state: any) => {
if (isAddingOption.value) return
if (loadedOptionAnchor.value === options.value.length) {
return $state.complete()
}
$state.loading()
loadedOptionAnchor.value += OPTIONS_PAGE_COUNT
loadedOptionAnchor.value = Math.min(loadedOptionAnchor.value, options.value.length)
renderedOptions.value = options.value.slice(0, loadedOptionAnchor.value)
if (loadedOptionAnchor.value === options.value.length) {
return $state.complete()
}
$state.loaded()
}
</script> </script>
<template> <template>
<div class="w-full"> <div class="w-full">
<div <div
class="overflow-x-auto scrollbar-thin-dull" ref="optionsWrapperDomRef"
class="nc-col-option-select-option overflow-x-auto scrollbar-thin-dull"
:style="{ :style="{
maxHeight: 'calc(min(30vh, 250px))', maxHeight: 'calc(min(30vh, 250px))',
}" }"
> >
<InfiniteLoading v-if="isReverseLazyLoad" v-bind="$attrs" @infinite="loadListDataReverse">
<template #spinner>
<div class="flex flex-row w-full justify-center mt-2">
<GeneralLoader />
</div>
</template>
<template #complete>
<span></span>
</template>
</InfiniteLoading>
<Draggable :list="renderedOptions" item-key="id" handle=".nc-child-draggable-icon" @change="syncOptions"> <Draggable :list="renderedOptions" item-key="id" handle=".nc-child-draggable-icon" @change="syncOptions">
<template #item="{ element, index }"> <template #item="{ element, index }">
<div class="flex py-1 items-center nc-select-option"> <div class="flex py-1 items-center nc-select-option">
@ -245,9 +322,8 @@ const loadListData = async ($state: any) => {
</a-dropdown> </a-dropdown>
<a-input <a-input
ref="inputs"
v-model:value="element.title" v-model:value="element.title"
class="caption !rounded-lg" class="caption !rounded-lg nc-select-col-option-select-option"
:data-testid="`select-column-option-input-${index}`" :data-testid="`select-column-option-input-${index}`"
:disabled="element.status === 'remove'" :disabled="element.status === 'remove'"
@keydown.enter.prevent="element.title?.trim() && addNewOption()" @keydown.enter.prevent="element.title?.trim() && addNewOption()"
@ -273,7 +349,7 @@ const loadListData = async ($state: any) => {
</div> </div>
</template> </template>
</Draggable> </Draggable>
<InfiniteLoading v-bind="$attrs" @infinite="loadListData"> <InfiniteLoading v-if="!isReverseLazyLoad" v-bind="$attrs" @infinite="loadListData">
<template #spinner> <template #spinner>
<div class="flex flex-row w-full justify-center mt-2"> <div class="flex flex-row w-full justify-center mt-2">
<GeneralLoader /> <GeneralLoader />

Loading…
Cancel
Save