Browse Source

Nc feat: Grid view custom pagesize option (#8298)

* feat(nc-gui): custom page size in grid view

* fix(nc-gui): persist grid view page size value across browser

* chore(nc-gui): lint

* fix(nc-gui): small changes

* fix(nc-gui): on change pagesize offset issue

* fix(test): small changes
pull/8300/head
Ramesh Mane 3 months ago committed by GitHub
parent
commit
6db2b67c5b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 243
      packages/nc-gui/components/nc/Pagination.vue
  2. 19
      packages/nc-gui/components/smartsheet/Pagination.vue
  3. 16
      packages/nc-gui/components/smartsheet/PlainCell.vue
  4. 11
      packages/nc-gui/components/smartsheet/grid/Table.vue
  5. 2
      packages/nc-gui/lib/constants.ts
  6. 2
      tests/playwright/pages/Dashboard/Grid/index.ts

243
packages/nc-gui/components/nc/Pagination.vue

@ -1,5 +1,6 @@
<script setup lang="ts">
import NcTooltip from '~/components/nc/Tooltip.vue'
import { NC_GRID_VIEW_PAGE_SIZE_KEY } from '#imports'
const props = defineProps<{
current: number
@ -11,19 +12,41 @@ const props = defineProps<{
nextPageTooltip?: string
firstPageTooltip?: string
lastPageTooltip?: string
showSizeChanger?: boolean
}>()
const emits = defineEmits(['update:current', 'update:pageSize'])
const { total } = toRefs(props)
const { total, showSizeChanger } = toRefs(props)
const current = useVModel(props, 'current', emits)
const pageSize = useVModel(props, 'pageSize', emits)
const localPageSize = computed({
get: () => {
if (!showSizeChanger.value) return pageSize.value
const storedPageSize = !isNaN(parseInt(localStorage.getItem(NC_GRID_VIEW_PAGE_SIZE_KEY) ?? ''))
? parseInt(localStorage.getItem(NC_GRID_VIEW_PAGE_SIZE_KEY) ?? '')
: 25
if (pageSize.value !== storedPageSize) {
pageSize.value = storedPageSize
}
return pageSize.value
},
set: (val) => {
localStorage.setItem(NC_GRID_VIEW_PAGE_SIZE_KEY, `${val}`)
pageSize.value = val
},
})
const entityName = computed(() => props.entityName || 'item')
const totalPages = computed(() => Math.max(Math.ceil(total.value / pageSize.value), 1))
const totalPages = computed(() => Math.max(Math.ceil(total.value / localPageSize.value), 1))
const { isMobileMode } = useGlobal()
@ -53,94 +76,164 @@ const pagesList = computed(() => {
label: i + 1,
}))
})
const pageSizeOptions = [
{
value: 25,
label: '25 / page',
},
{
value: 50,
label: '50 / page',
},
{
value: 75,
label: '75 / page',
},
{
value: 100,
label: '100 / page',
},
]
const pageListRef = ref()
const pageSizeRef = ref()
const pageListDropdownVisibleChange = (value: boolean) => {
if (!value && pageListRef.value) {
pageListRef.value?.blur()
}
}
const pageSizeDropdownVisibleChange = (value: boolean) => {
if (!value && pageSizeRef.value) {
pageSizeRef.value?.blur()
}
}
</script>
<template>
<div v-if="totalPages > 1" class="nc-pagination flex flex-row items-center gap-x-2">
<component :is="props.firstPageTooltip && mode === 'full' ? NcTooltip : 'div'" v-if="mode === 'full'">
<template v-if="props.firstPageTooltip" #title>
{{ props.firstPageTooltip }}
</template>
<NcButton
v-e="[`a:pagination:${entityName}:first-page`]"
class="first-page"
type="secondary"
size="small"
:disabled="current === 1"
@click="goToFirstPage"
>
<GeneralIcon icon="doubleLeftArrow" />
</NcButton>
</component>
<component :is="props.prevPageTooltip && mode === 'full' ? NcTooltip : 'div'">
<template v-if="props.prevPageTooltip" #title>
{{ props.prevPageTooltip }}
</template>
<NcButton
v-e="[`a:pagination:${entityName}:prev-page`]"
class="prev-page"
type="secondary"
<div class="nc-pagination flex flex-row items-center gap-x-2">
<template v-if="totalPages > 1">
<component :is="props.firstPageTooltip && mode === 'full' ? NcTooltip : 'div'" v-if="mode === 'full'">
<template v-if="props.firstPageTooltip" #title>
{{ props.firstPageTooltip }}
</template>
<NcButton
v-e="[`a:pagination:${entityName}:first-page`]"
class="first-page"
type="secondary"
size="xsmall"
:disabled="current === 1"
@click="goToFirstPage"
>
<GeneralIcon icon="doubleLeftArrow" class="nc-pagination-icon" />
</NcButton>
</component>
<component :is="props.prevPageTooltip && mode === 'full' ? NcTooltip : 'div'">
<template v-if="props.prevPageTooltip" #title>
{{ props.prevPageTooltip }}
</template>
<NcButton
v-e="[`a:pagination:${entityName}:prev-page`]"
class="prev-page"
type="secondary"
size="xsmall"
:disabled="current === 1"
@click="changePage({ increase: false })"
>
<GeneralIcon icon="arrowLeft" class="nc-pagination-icon" />
</NcButton>
</component>
<div v-if="!isMobileMode" class="text-gray-500">
<a-select
ref="pageListRef"
v-model:value="current"
class="!mr-[2px]"
:options="pagesList"
size="small"
dropdown-class-name="nc-pagination-dropdown"
@dropdown-visible-change="pageListDropdownVisibleChange"
>
<template #suffixIcon>
<GeneralIcon icon="arrowDown" class="text-gray-500 nc-select-expand-btn" />
</template>
</a-select>
<span class="mx-1"> {{ mode !== 'full' ? '/' : 'of' }} </span>
<span class="total">
{{ totalPages }}
</span>
</div>
<component :is="props.nextPageTooltip && mode === 'full' ? NcTooltip : 'div'">
<template v-if="props.nextPageTooltip" #title>
{{ props.nextPageTooltip }}
</template>
<NcButton
v-e="[`a:pagination:${entityName}:next-page`]"
class="next-page"
type="secondary"
size="xsmall"
:disabled="current === totalPages"
@click="changePage({ increase: true })"
>
<GeneralIcon icon="arrowRight" class="nc-pagination-icon" />
</NcButton>
</component>
<component :is="props.lastPageTooltip && mode === 'full' ? NcTooltip : 'div'" v-if="mode === 'full'">
<template v-if="props.lastPageTooltip" #title>
{{ props.lastPageTooltip }}
</template>
<NcButton
v-e="[`a:pagination:${entityName}:last-page`]"
class="last-page"
type="secondary"
size="xsmall"
:disabled="current === totalPages"
@click="goToLastPage"
>
<GeneralIcon icon="doubleRightArrow" class="nc-pagination-icon" />
</NcButton>
</component>
</template>
<div v-if="showSizeChanger && !isMobileMode" class="text-gray-500">
<a-select
ref="pageSizeRef"
v-model:value="localPageSize"
class="!min-w-[110px]"
:options="pageSizeOptions"
size="small"
:disabled="current === 1"
@click="changePage({ increase: false })"
dropdown-class-name="nc-pagination-dropdown"
@dropdown-visible-change="pageSizeDropdownVisibleChange"
>
<GeneralIcon icon="arrowLeft" />
</NcButton>
</component>
<div v-if="!isMobileMode" class="text-gray-600">
<a-select v-model:value="current" class="!mr-[2px]" :options="pagesList">
<template #suffixIcon>
<GeneralIcon icon="arrowDown" class="text-gray-500 nc-select-expand-btn" />
<GeneralIcon icon="arrowDown" class="text-gray-500 nc-select-page-size-expand-btn" />
</template>
</a-select>
<span class="mx-1"> {{ mode !== 'full' ? '/' : 'of' }} </span>
<span class="total">
{{ totalPages }}
</span>
</div>
<component :is="props.nextPageTooltip && mode === 'full' ? NcTooltip : 'div'">
<template v-if="props.nextPageTooltip" #title>
{{ props.nextPageTooltip }}
</template>
<NcButton
v-e="[`a:pagination:${entityName}:next-page`]"
class="next-page"
type="secondary"
size="small"
:disabled="current === totalPages"
@click="changePage({ increase: true })"
>
<GeneralIcon icon="arrowRight" />
</NcButton>
</component>
<component :is="props.lastPageTooltip && mode === 'full' ? NcTooltip : 'div'" v-if="mode === 'full'">
<template v-if="props.lastPageTooltip" #title>
{{ props.lastPageTooltip }}
</template>
<NcButton
v-e="[`a:pagination:${entityName}:last-page`]"
class="last-page"
type="secondary"
size="small"
:disabled="current === totalPages"
@click="goToLastPage"
>
<GeneralIcon icon="doubleRightArrow" />
</NcButton>
</component>
</div>
</template>
<style lang="scss" scoped>
:deep(.ant-select-selector) {
@apply !border-gray-200 !rounded-lg;
@apply !border-gray-200 !rounded-lg !h-[25px];
}
.nc-pagination-icon {
@apply w-4 h-4;
}
:deep(.ant-select-dropdown) {
@apply !rounded-lg !overflow-hidden;
:deep(.nc-button:not(:disabled)) {
.nc-pagination-icon {
@apply !text-gray-500;
}
}
</style>
<style lang="scss">
.nc-pagination-dropdown {
@apply !rounded-lg;
}
</style>

19
packages/nc-gui/components/smartsheet/Pagination.vue

@ -15,6 +15,7 @@ interface Props {
extraStyle?: string
showApiTiming?: boolean
alignLeft?: boolean
showSizeChanger?: boolean
}
const props = defineProps<Props>()
@ -43,8 +44,6 @@ const { isLeftSidebarOpen } = storeToRefs(useSidebarStore())
const count = computed(() => vPaginationData.value?.totalRows ?? Infinity)
const size = computed(() => vPaginationData.value?.pageSize ?? 25)
const page = computed({
get: () => vPaginationData?.value?.page ?? 1,
set: async (p) => {
@ -61,6 +60,21 @@ const page = computed({
},
})
const size = computed({
get: () => vPaginationData.value?.pageSize ?? 25,
set: (size: number) => {
if (vPaginationData.value) {
vPaginationData.value.pageSize = size
if (vPaginationData.value.totalRows && page.value * size < vPaginationData.value.totalRows) {
changePage?.(page.value)
} else {
changePage?.(1)
}
}
},
})
const isRTLLanguage = computed(() => isRtlLang(locale.value as keyof typeof Language))
const renderAltOrOptlKey = () => {
@ -117,6 +131,7 @@ const tempPageVal = ref(page.value)
:next-page-tooltip="`${renderAltOrOptlKey()}+→`"
:first-page-tooltip="`${renderAltOrOptlKey()}+↓`"
:last-page-tooltip="`${renderAltOrOptlKey()}+↑`"
:show-size-changer="showSizeChanger"
/>
<div v-else class="mx-auto flex items-center mt-n1" style="max-width: 250px">
<span class="text-xs" style="white-space: nowrap"> Change page:</span>

16
packages/nc-gui/components/smartsheet/PlainCell.vue

@ -10,19 +10,7 @@ import {
timeFormats,
} from 'nocodb-sdk'
import dayjs from 'dayjs'
import {
computed,
isBoolean,
isDate,
isDateTime,
isInt,
parseProp,
ref,
storeToRefs,
useAttachment,
useBase,
useMetas,
} from '#imports'
import { computed, isBoolean, isDate, isDateTime, isInt, parseProp, ref, storeToRefs, useBase, useMetas } from '#imports'
interface Props {
column: ColumnType
@ -50,8 +38,6 @@ const { basesUser } = storeToRefs(basesStore)
const { isXcdbBase, isMssql, isMysql } = useBase()
const { getPossibleAttachmentSrc } = useAttachment()
const sqlUi = ref(column.value?.source_id ? sqlUis.value[column.value?.source_id] : Object.values(sqlUis.value)[0])
const abstractType = computed(() => column.value && sqlUi.value.getAbstractType(column.value))

11
packages/nc-gui/components/smartsheet/grid/Table.vue

@ -1200,7 +1200,15 @@ useEventListener(document, 'keyup', async (e: KeyboardEvent) => {
const cmdOrCtrl = isMac() ? e.metaKey : e.ctrlKey
if (isKeyDown.value && !isRichModalOpen && !activeDropdownEl && !isDrawerOrModalExist() && !cmdOrCtrl && !e.shiftKey && !e.altKey) {
if (
isKeyDown.value &&
!isRichModalOpen &&
!activeDropdownEl &&
!isDrawerOrModalExist() &&
!cmdOrCtrl &&
!e.shiftKey &&
!e.altKey
) {
if (
(e.key === 'Tab' && activeCell.row === dataRef.value.length - 1 && activeCell.col === fields.value?.length - 1) ||
(e.key === 'ArrowDown' &&
@ -1965,6 +1973,7 @@ onKeyStroke('ArrowDown', onDown)
:hide-sidebars="paginationStyleRef?.hideSidebars === true"
:fixed-size="paginationStyleRef?.fixedSize"
:extra-style="paginationStyleRef?.extraStyle"
:show-size-changer="!isGroupBy"
>
<template #add-record>
<div v-if="isAddingEmptyRowAllowed && !showSkeleton" class="flex ml-1">

2
packages/nc-gui/lib/constants.ts

@ -18,3 +18,5 @@ export const GROUP_BY_VARS = {
__nc_false__: 'Unchecked',
} as Record<string, string>,
}
export const NC_GRID_VIEW_PAGE_SIZE_KEY = 'ncGridViewPageSize'

2
tests/playwright/pages/Dashboard/Grid/index.ts

@ -362,7 +362,7 @@ export class GridPage extends BasePage {
return;
}
await expect(this.get().locator(`.nc-pagination .ant-select-selection-item`)).toHaveText(pageNumber);
await expect(this.get().locator(`.nc-pagination .ant-select-selection-item`).first()).toHaveText(pageNumber);
}
async waitLoading() {

Loading…
Cancel
Save