🦣🦣🦣 Table virtualization (#14743)

This big PR implements table virtualization with an offset paging,
allowing a way more fluid UX.

It is a v1 that should be improved in the future with partial data
loading and optimization of the browser display performance of a row.

But with this PR we have the solid enough technical foundation, both
frontend and backend, to get to a smooth table UX.

Fixes and improvements after first successful round of development
(needed to have main clean) :

- [x] Delete should refresh virtualized portion only and reset all table
- [x] Fix add new : top and bottom
- [x] Table empty shouldn’t show when first loading
- [x] Fix d&d
- [x]  Fix sorts
- [x] Fix drag when scrolling after a full virtual page (it throws an
error)
- [x] Si update mais qu’on a un sort ou filter, alors il faut trigger le
refresh
- [x]  Reset scroll position between tables
- [x]  Reset scroll shadows between tables
- [x] Setup d&n for virtual list :
https://github.com/hello-pangea/dnd/blob/main/docs/patterns/virtual-lists.md
- [x]  Full table re-render when entering edit mode
- [x] Clean code and prepare for merge

Fixes https://github.com/twentyhq/core-team-issues/issues/1613 that
contains other bugs to be fixed before merge

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
Lucas Bordeau
2025-10-11 12:08:10 +02:00
committed by GitHub
co-authored by Charles Bochet
parent 720e93a68a
commit 68c86871dd
150 changed files with 3615 additions and 1712 deletions
@@ -129,6 +129,10 @@ export class GraphqlQueryFindManyResolverService extends GraphqlQueryBaseResolve
objectMetadataMaps,
});
if (isDefined(executionArgs.args.offset)) {
queryBuilder.skip(executionArgs.args.offset);
}
const objectRecords = (await queryBuilder
.setFindOptions({
select: columnsToSelect,
@@ -38,6 +38,7 @@ export interface FindManyResolverArgs<
after?: string;
filter?: Filter;
orderBy?: OrderBy;
offset?: number;
}
export interface FindOneResolverArgs<Filter = ObjectRecordFilter> {
@@ -19,6 +19,7 @@ describe('getResolverArgs', () => {
isNullable: true,
isArray: true,
},
offset: { type: GraphQLInt, isNullable: true },
},
findOne: {
filter: { kind: GqlInputTypeDefinitionKind.Filter, isNullable: false },
@@ -20,6 +20,10 @@ export const getResolverArgs = (
type: GraphQLInt,
isNullable: true,
},
offset: {
type: GraphQLInt,
isNullable: true,
},
before: {
type: GraphQLString,
isNullable: true,