From 5688497f195cac50fdb594b72459e5c63b1ab7a6 Mon Sep 17 00:00:00 2001 From: Josh <37798644+joshuaellis@users.noreply.github.com> Date: Wed, 8 Feb 2023 16:33:38 +0000 Subject: [PATCH] feat: implement fractional indexing to reduce payload for relations feat(wip): add fractional _temp_keys_ on reordering feat(wip): add temp keys to loaded relations --- jest.base-config.front.js | 2 +- .../EditViewDataManagerProvider/reducer.js | 48 ++- .../tests/reducer.test.js | 284 ++++++++++++++- .../utils/cleanData.js | 24 +- .../utils/tests/cleanData.test.js | 324 ++++++++++++++++-- packages/core/admin/package.json | 1 + yarn.lock | 7 + 7 files changed, 619 insertions(+), 71 deletions(-) diff --git a/jest.base-config.front.js b/jest.base-config.front.js index a401eed823..f7be2198f0 100644 --- a/jest.base-config.front.js +++ b/jest.base-config.front.js @@ -75,7 +75,7 @@ module.exports = { '/fileTransformer.js', }, transformIgnorePatterns: [ - 'node_modules/(?!(react-dnd|dnd-core|react-dnd-html5-backend|@strapi/design-system|@strapi/icons)/)', + 'node_modules/(?!(react-dnd|dnd-core|react-dnd-html5-backend|@strapi/design-system|@strapi/icons|fractional-indexing)/)', ], testMatch: ['/**/tests/**/?(*.)+(spec|test).[jt]s?(x)'], testEnvironmentOptions: { diff --git a/packages/core/admin/admin/src/content-manager/components/EditViewDataManagerProvider/reducer.js b/packages/core/admin/admin/src/content-manager/components/EditViewDataManagerProvider/reducer.js index a194094cdc..ffc0670e1d 100644 --- a/packages/core/admin/admin/src/content-manager/components/EditViewDataManagerProvider/reducer.js +++ b/packages/core/admin/admin/src/content-manager/components/EditViewDataManagerProvider/reducer.js @@ -8,6 +8,7 @@ import uniqBy from 'lodash/uniqBy'; import merge from 'lodash/merge'; import castArray from 'lodash/castArray'; import isNil from 'lodash/isNil'; +import { generateNKeysBetween } from 'fractional-indexing'; import { findLeafByPathAndReplace, @@ -155,17 +156,33 @@ const reducer = (state, action) => const initialDataRelations = get(state, initialDataPath); const modifiedDataRelations = get(state, modifiedDataPath); - /** - * Check if the values we're loading are already in initial - * data if they are then we don't need to load them at all - */ const valuesToLoad = value.filter((relation) => { return !initialDataRelations.some((initialDataRelation) => { return initialDataRelation.id === relation.id; }); }); - set(draftState, initialDataPath, uniqBy([...valuesToLoad, ...initialDataRelations], 'id')); + const keys = generateNKeysBetween( + null, + modifiedDataRelations[0]?.__temp_key__, + valuesToLoad.length + ); + + /** + * Check if the values we're loading are already in initial + * data if they are then we don't need to load them at all + */ + + const valuesWithKeys = valuesToLoad.map((relation, index) => ({ + ...relation, + __temp_key__: keys[index], + })); + + set( + draftState, + initialDataPath, + uniqBy([...valuesWithKeys, ...initialDataRelations], 'id') + ); /** * We need to set the value also on modifiedData, because initialData @@ -175,7 +192,7 @@ const reducer = (state, action) => set( draftState, modifiedDataPath, - uniqBy([...valuesToLoad, ...modifiedDataRelations], 'id') + uniqBy([...valuesWithKeys, ...modifiedDataRelations], 'id') ); break; @@ -192,7 +209,9 @@ const reducer = (state, action) => set(draftState, path, [value]); } else { const modifiedDataRelations = get(state, path); - const newRelations = [...modifiedDataRelations, value]; + const [key] = generateNKeysBetween(modifiedDataRelations.at(-1)?.__temp_key__, null, 1); + + const newRelations = [...modifiedDataRelations, { ...value, __temp_key__: key }]; set(draftState, path, newRelations); } @@ -219,8 +238,19 @@ const reducer = (state, action) => const newRelations = [...modifiedDataRelations]; - newRelations.splice(oldIndex, 1); - newRelations.splice(newIndex, 0, currentItem); + if (action.type === 'REORDER_RELATION') { + const [newKey] = generateNKeysBetween( + modifiedDataRelations[newIndex - 1]?.__temp_key__, + modifiedDataRelations[newIndex]?.__temp_key__, + 1 + ); + + newRelations.splice(oldIndex, 1); + newRelations.splice(newIndex, 0, { ...currentItem, __temp_key__: newKey }); + } else { + newRelations.splice(oldIndex, 1); + newRelations.splice(newIndex, 0, currentItem); + } set(draftState, path, newRelations); diff --git a/packages/core/admin/admin/src/content-manager/components/EditViewDataManagerProvider/tests/reducer.test.js b/packages/core/admin/admin/src/content-manager/components/EditViewDataManagerProvider/tests/reducer.test.js index 4a590203c4..29230ebc25 100644 --- a/packages/core/admin/admin/src/content-manager/components/EditViewDataManagerProvider/tests/reducer.test.js +++ b/packages/core/admin/admin/src/content-manager/components/EditViewDataManagerProvider/tests/reducer.test.js @@ -869,7 +869,7 @@ describe('CONTENT MANAGER | COMPONENTS | EditViewDataManagerProvider | reducer', componentsDataStructure: {}, initialData: {}, modifiedData: { - relation: [{ id: 1 }], + relation: [{ id: 1, __temp_key__: 'a0' }], }, }; @@ -882,6 +882,74 @@ describe('CONTENT MANAGER | COMPONENTS | EditViewDataManagerProvider | reducer', expect(reducer(state, action)).toEqual(expected); }); + it('should set a temp key every time a relation is connected', () => { + const state = { + ...initialState, + + initialData: { + relation: [ + { id: 1, __temp_key__: 'a0' }, + { id: 2, __temp_key__: 'a1' }, + ], + }, + modifiedData: { + relation: [ + { id: 1, __temp_key__: 'a0' }, + { id: 2, __temp_key__: 'a1' }, + ], + }, + }; + + const nextState = reducer(state, { + type: 'CONNECT_RELATION', + keys: ['relation'], + value: { id: 3 }, + }); + + expect(nextState).toStrictEqual({ + ...initialState, + componentsDataStructure: {}, + initialData: { + relation: [ + { id: 1, __temp_key__: 'a0' }, + { id: 2, __temp_key__: 'a1' }, + ], + }, + modifiedData: { + relation: [ + { id: 1, __temp_key__: 'a0' }, + { id: 2, __temp_key__: 'a1' }, + { id: 3, __temp_key__: 'a2' }, + ], + }, + }); + + expect( + reducer(nextState, { + type: 'CONNECT_RELATION', + keys: ['relation'], + value: { id: 4 }, + }) + ).toStrictEqual({ + ...initialState, + componentsDataStructure: {}, + initialData: { + relation: [ + { id: 1, __temp_key__: 'a0' }, + { id: 2, __temp_key__: 'a1' }, + ], + }, + modifiedData: { + relation: [ + { id: 1, __temp_key__: 'a0' }, + { id: 2, __temp_key__: 'a1' }, + { id: 3, __temp_key__: 'a2' }, + { id: 4, __temp_key__: 'a3' }, + ], + }, + }); + }); + it('should overwrite existing data, when toOneRelation is set to true', () => { const state = { ...initialState, @@ -953,10 +1021,10 @@ describe('CONTENT MANAGER | COMPONENTS | EditViewDataManagerProvider | reducer', expect(nextState).toStrictEqual({ ...initialState, initialData: { - relation: [{ id: 1 }], + relation: [{ id: 1, __temp_key__: 'a0' }], }, modifiedData: { - relation: [{ id: 1 }], + relation: [{ id: 1, __temp_key__: 'a0' }], }, }); @@ -970,10 +1038,16 @@ describe('CONTENT MANAGER | COMPONENTS | EditViewDataManagerProvider | reducer', ).toStrictEqual({ ...initialState, initialData: { - relation: [{ id: 2 }, { id: 1 }], + relation: [ + { id: 2, __temp_key__: 'Zz' }, + { id: 1, __temp_key__: 'a0' }, + ], }, modifiedData: { - relation: [{ id: 2 }, { id: 1 }], + relation: [ + { id: 2, __temp_key__: 'Zz' }, + { id: 1, __temp_key__: 'a0' }, + ], }, }); }); @@ -1002,10 +1076,10 @@ describe('CONTENT MANAGER | COMPONENTS | EditViewDataManagerProvider | reducer', expect(nextState).toStrictEqual({ ...initialState, initialData: { - relation: [{ id: 1 }], + relation: [{ id: 1, __temp_key__: 'a0' }], }, modifiedData: { - relation: [{ id: 1 }], + relation: [{ id: 1, __temp_key__: 'a0' }], }, }); @@ -1019,10 +1093,103 @@ describe('CONTENT MANAGER | COMPONENTS | EditViewDataManagerProvider | reducer', ).toStrictEqual({ ...initialState, initialData: { - relation: [{ id: 1 }], + relation: [{ id: 1, __temp_key__: 'a0' }], }, modifiedData: { - relation: [{ id: 1 }], + relation: [{ id: 1, __temp_key__: 'a0' }], + }, + }); + }); + + it('should add a temp key for all the relations added', () => { + const state = { + ...initialState, + initialData: { + relation: [], + }, + modifiedData: { + relation: [], + }, + }; + + const initialDataPath = ['initialData', 'relation']; + const modifiedDataPath = ['modifiedData', 'relation']; + + let nextState = reducer(state, { + type: 'LOAD_RELATION', + initialDataPath, + modifiedDataPath, + value: [{ id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }, { id: 5 }], + }); + + expect(nextState).toStrictEqual({ + ...initialState, + initialData: { + relation: [ + { id: 1, __temp_key__: 'a0' }, + { id: 2, __temp_key__: 'a1' }, + { id: 3, __temp_key__: 'a2' }, + { id: 4, __temp_key__: 'a3' }, + { id: 5, __temp_key__: 'a4' }, + ], + }, + modifiedData: { + relation: [ + { id: 1, __temp_key__: 'a0' }, + { id: 2, __temp_key__: 'a1' }, + { id: 3, __temp_key__: 'a2' }, + { id: 4, __temp_key__: 'a3' }, + { id: 5, __temp_key__: 'a4' }, + ], + }, + }); + }); + + it('should add a temp key working backwards on every new load because of how relations are shown in the UI', () => { + const state = { + ...initialState, + initialData: { + relation: [], + }, + modifiedData: { + relation: [], + }, + }; + + const initialDataPath = ['initialData', 'relation']; + const modifiedDataPath = ['modifiedData', 'relation']; + + let nextState = reducer(state, { + type: 'LOAD_RELATION', + initialDataPath, + modifiedDataPath, + value: [{ id: 1 }, { id: 2 }], + }); + + expect( + reducer(nextState, { + type: 'LOAD_RELATION', + initialDataPath, + modifiedDataPath, + value: [{ id: 3 }, { id: 4 }], + }) + ).toStrictEqual({ + ...initialState, + initialData: { + relation: [ + { id: 3, __temp_key__: 'Zy' }, + { id: 4, __temp_key__: 'Zz' }, + { id: 1, __temp_key__: 'a0' }, + { id: 2, __temp_key__: 'a1' }, + ], + }, + modifiedData: { + relation: [ + { id: 3, __temp_key__: 'Zy' }, + { id: 4, __temp_key__: 'Zz' }, + { id: 1, __temp_key__: 'a0' }, + { id: 2, __temp_key__: 'a1' }, + ], }, }); }); @@ -2397,10 +2564,10 @@ describe('CONTENT MANAGER | COMPONENTS | EditViewDataManagerProvider | reducer', field1: { field2: { relation: [ - { name: 'first' }, - { name: 'second' }, - { name: 'third' }, - { name: 'fourth' }, + { name: 'first', __temp_key__: 'a0' }, + { name: 'second', __temp_key__: 'a1' }, + { name: 'third', __temp_key__: 'a2' }, + { name: 'fourth', __temp_key__: 'a3' }, ], }, }, @@ -2421,10 +2588,10 @@ describe('CONTENT MANAGER | COMPONENTS | EditViewDataManagerProvider | reducer', field1: { field2: { relation: [ - { name: 'first' }, - { name: 'fourth' }, - { name: 'second' }, - { name: 'third' }, + { name: 'first', __temp_key__: 'a0' }, + { name: 'fourth', __temp_key__: 'a0V' }, + { name: 'second', __temp_key__: 'a1' }, + { name: 'third', __temp_key__: 'a2' }, ], }, }, @@ -2433,6 +2600,89 @@ describe('CONTENT MANAGER | COMPONENTS | EditViewDataManagerProvider | reducer', expect(reducer(state, action)).toEqual(expected); }); + + it('should move many components many times and have the correct temp keys', () => { + const state = { + ...initialState, + modifiedData: { + relation: [ + { name: 'first', __temp_key__: 'a0' }, + { name: 'second', __temp_key__: 'a1' }, + { name: 'third', __temp_key__: 'a2' }, + { name: 'fourth', __temp_key__: 'a3' }, + ], + }, + }; + + const generateAction = (newIndex, oldIndex) => ({ + type: 'REORDER_RELATION', + newIndex, + oldIndex, + keys: ['relation'], + }); + + const generateExpected = (relation = []) => ({ + ...initialState, + modifiedData: { + relation, + }, + }); + + const nextState1 = reducer(state, generateAction(1, 3)); + + expect(nextState1).toEqual( + generateExpected([ + { name: 'first', __temp_key__: 'a0' }, + { name: 'fourth', __temp_key__: 'a0V' }, + { name: 'second', __temp_key__: 'a1' }, + { name: 'third', __temp_key__: 'a2' }, + ]) + ); + + const nextState2 = reducer(nextState1, generateAction(1, 2)); + + expect(nextState2).toEqual( + generateExpected([ + { name: 'first', __temp_key__: 'a0' }, + { name: 'second', __temp_key__: 'a0G' }, + { name: 'fourth', __temp_key__: 'a0V' }, + { name: 'third', __temp_key__: 'a2' }, + ]) + ); + + const nextState3 = reducer(nextState2, generateAction(0, 3)); + + expect(nextState3).toEqual( + generateExpected([ + { name: 'third', __temp_key__: 'Zz' }, + { name: 'first', __temp_key__: 'a0' }, + { name: 'second', __temp_key__: 'a0G' }, + { name: 'fourth', __temp_key__: 'a0V' }, + ]) + ); + + const nextState4 = reducer(nextState3, generateAction(3, 1)); + + expect(nextState4).toEqual( + generateExpected([ + { name: 'third', __temp_key__: 'Zz' }, + { name: 'second', __temp_key__: 'a0G' }, + { name: 'fourth', __temp_key__: 'a0V' }, + { name: 'first', __temp_key__: 'a0O' }, + ]) + ); + + const nextState5 = reducer(nextState4, generateAction(1, 2)); + + expect(nextState5).toEqual( + generateExpected([ + { name: 'third', __temp_key__: 'Zz' }, + { name: 'fourth', __temp_key__: 'a0' }, + { name: 'second', __temp_key__: 'a0G' }, + { name: 'first', __temp_key__: 'a0O' }, + ]) + ); + }); }); describe('SET_DEFAULT_DATA_STRUCTURES', () => { diff --git a/packages/core/admin/admin/src/content-manager/components/EditViewDataManagerProvider/utils/cleanData.js b/packages/core/admin/admin/src/content-manager/components/EditViewDataManagerProvider/utils/cleanData.js index 003a4116c7..e33eff09fe 100644 --- a/packages/core/admin/admin/src/content-manager/components/EditViewDataManagerProvider/utils/cleanData.js +++ b/packages/core/admin/admin/src/content-manager/components/EditViewDataManagerProvider/utils/cleanData.js @@ -95,28 +95,22 @@ const cleanData = ({ browserState, serverState }, currentSchema, componentsSchem */ let actualOldValue = get(rootServerState, trueInitialDataPath, []); - const valuesWithPositions = value.map((relation, index, allRelations) => { - const nextRelation = allRelations[index + 1]; - - if (nextRelation) { - return { ...relation, position: { before: nextRelation.id } }; - } - - return { ...relation, position: { end: true } }; - }); - /** * Instead of the full relation object, we only want to send its ID * connectedRelations are the items that are in the browserState * array but not in the serverState */ - const connectedRelations = valuesWithPositions.reduce((acc, relation, currentIndex) => { - const indexOfRelationOnServer = actualOldValue.findIndex( + const connectedRelations = value.reduce((acc, relation, currentIndex, array) => { + const relationOnServer = actualOldValue.find( (oldRelation) => oldRelation.id === relation.id ); - if (indexOfRelationOnServer === -1 || indexOfRelationOnServer !== currentIndex) { - return [...acc, { id: relation.id, position: relation.position }]; + const relationInFront = array[currentIndex + 1]; + + if (!relationOnServer || relationOnServer.__temp_key__ !== relation.__temp_key__) { + const position = relationInFront ? { before: relationInFront.id } : { end: true }; + + return [...acc, { id: relation.id, position }]; } return acc; @@ -127,7 +121,7 @@ const cleanData = ({ browserState, serverState }, currentSchema, componentsSchem * are no longer in the browserState */ const disconnectedRelations = actualOldValue.reduce((acc, relation) => { - if (!valuesWithPositions.find((newRelation) => newRelation.id === relation.id)) { + if (!value.find((newRelation) => newRelation.id === relation.id)) { return [...acc, { id: relation.id }]; } diff --git a/packages/core/admin/admin/src/content-manager/components/EditViewDataManagerProvider/utils/tests/cleanData.test.js b/packages/core/admin/admin/src/content-manager/components/EditViewDataManagerProvider/utils/tests/cleanData.test.js index 9843566b52..c10d73b266 100644 --- a/packages/core/admin/admin/src/content-manager/components/EditViewDataManagerProvider/utils/tests/cleanData.test.js +++ b/packages/core/admin/admin/src/content-manager/components/EditViewDataManagerProvider/utils/tests/cleanData.test.js @@ -407,10 +407,10 @@ describe('CM || components || EditViewDataManagerProvider || utils || cleanData' const result = cleanData( { browserState: { - relation: [{ id: 1, something: true }], + relation: [{ id: 1, __temp_key__: 'a1', something: true }], }, serverState: { - relation: [{ id: 2, something: true }], + relation: [{ id: 2, __temp_key__: 'a0', something: true }], }, }, schema, @@ -429,7 +429,7 @@ describe('CM || components || EditViewDataManagerProvider || utils || cleanData' const result = cleanData( { browserState: { - relation: [{ id: 1, something: true }], + relation: [{ id: 1, __temp_key__: 'a0', something: true }], }, serverState: { relation: [], @@ -454,7 +454,7 @@ describe('CM || components || EditViewDataManagerProvider || utils || cleanData' relation: [], }, serverState: { - relation: [{ id: 1, something: true }], + relation: [{ id: 1, __temp_key__: 'a0', something: true }], }, }, schema, @@ -477,13 +477,13 @@ describe('CM || components || EditViewDataManagerProvider || utils || cleanData' { id: 1, relation_component: { - relation: [{ id: 1 }], + relation: [{ id: 1, __temp_key__: 'a0' }], }, }, { id: 2, relation_component: { - relation: [{ id: 2 }], + relation: [{ id: 2, __temp_key__: 'a0' }], }, }, ], @@ -541,7 +541,7 @@ describe('CM || components || EditViewDataManagerProvider || utils || cleanData' { id: 2, relation_component: { - relation: [{ id: 2 }], + relation: [{ id: 2, __temp_key__: 'a0' }], }, }, ], @@ -551,13 +551,13 @@ describe('CM || components || EditViewDataManagerProvider || utils || cleanData' { id: 1, relation_component: { - relation: [{ id: 1 }], + relation: [{ id: 1, __temp_key__: 'a0' }], }, }, { id: 2, relation_component: { - relation: [{ id: 2 }], + relation: [{ id: 2, __temp_key__: 'a0' }], }, }, ], @@ -599,13 +599,13 @@ describe('CM || components || EditViewDataManagerProvider || utils || cleanData' { __component: 'basic.relation', id: 1, - relation: [{ id: 1 }], + relation: [{ id: 1, __temp_key__: 'a0' }], }, { __component: 'basic.nested-relation', id: 2, relation_component: { - relation: [{ id: 2 }], + relation: [{ id: 2, __temp_key__: 'a0' }], }, }, { @@ -615,7 +615,7 @@ describe('CM || components || EditViewDataManagerProvider || utils || cleanData' { __component: 'basic.relation', id: 1, - relation: [{ id: 3 }], + relation: [{ id: 3, __temp_key__: 'a0' }], }, ], }, @@ -715,7 +715,7 @@ describe('CM || components || EditViewDataManagerProvider || utils || cleanData' { __component: 'basic.relation', id: 1, - relation: [{ id: 3 }], + relation: [{ id: 3, __temp_key__: 'a0' }], }, { __component: 'basic.relation', @@ -731,13 +731,13 @@ describe('CM || components || EditViewDataManagerProvider || utils || cleanData' { __component: 'basic.relation', id: 1, - relation: [{ id: 1 }], + relation: [{ id: 1, __temp_key__: 'a0' }], }, { __component: 'basic.nested-relation', id: 2, relation_component: { - relation: [{ id: 2 }], + relation: [{ id: 2, __temp_key__: 'a0' }], }, }, { @@ -747,12 +747,12 @@ describe('CM || components || EditViewDataManagerProvider || utils || cleanData' { __component: 'basic.relation', id: 1, - relation: [{ id: 3 }], + relation: [{ id: 3, __temp_key__: 'a0' }], }, { __component: 'basic.relation', id: 2, - relation: [{ id: 4 }], + relation: [{ id: 4, __temp_key__: 'a0' }], }, ], }, @@ -813,10 +813,16 @@ describe('CM || components || EditViewDataManagerProvider || utils || cleanData' const result = cleanData( { browserState: { - relation: [{ id: 1 }, { id: 2 }], + relation: [ + { id: 1, __temp_key__: 'Zz' }, + { id: 2, __temp_key__: 'a0' }, + ], }, serverState: { - relation: [{ id: 2 }, { id: 1 }], + relation: [ + { id: 2, __temp_key__: 'a0' }, + { id: 1, __temp_key__: 'a1' }, + ], }, }, schema, @@ -825,10 +831,7 @@ describe('CM || components || EditViewDataManagerProvider || utils || cleanData' expect(result).toStrictEqual({ relation: { - connect: [ - { id: 2, position: { end: true } }, - { id: 1, position: { before: 2 } }, - ], + connect: [{ id: 1, position: { before: 2 } }], disconnect: [], }, }); @@ -838,10 +841,17 @@ describe('CM || components || EditViewDataManagerProvider || utils || cleanData' const result = cleanData( { browserState: { - relation: [{ id: 3 }, { id: 1 }, { id: 2 }], + relation: [ + { id: 3, __temp_key__: 'Zz' }, + { id: 1, __temp_key__: 'a0' }, + { id: 2, __temp_key__: 'a1' }, + ], }, serverState: { - relation: [{ id: 1 }, { id: 2 }], + relation: [ + { id: 1, __temp_key__: 'a0' }, + { id: 2, __temp_key__: 'a1' }, + ], }, }, schema, @@ -851,8 +861,6 @@ describe('CM || components || EditViewDataManagerProvider || utils || cleanData' expect(result).toStrictEqual({ relation: { connect: [ - { id: 2, position: { end: true } }, - { id: 1, position: { before: 2 } }, { id: 3, position: { before: 1 }, @@ -867,10 +875,18 @@ describe('CM || components || EditViewDataManagerProvider || utils || cleanData' const result = cleanData( { browserState: { - relation: [{ id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }], + relation: [ + { id: 1, __temp_key__: 'a0' }, + { id: 2, __temp_key__: 'a1' }, + { id: 3, __temp_key__: 'a2' }, + { id: 4, __temp_key__: 'a3' }, + ], }, serverState: { - relation: [{ id: 1 }, { id: 2 }], + relation: [ + { id: 1, __temp_key__: 'a0' }, + { id: 2, __temp_key__: 'a1' }, + ], }, }, schema, @@ -893,5 +909,255 @@ describe('CM || components || EditViewDataManagerProvider || utils || cleanData' }, }); }); + + test('given a complicated list of reorderd relations it should only contain the items that moved', () => { + const result = cleanData( + { + browserState: { + relation: [ + { + id: 3, + __temp_key__: 'Zw', + }, + { + id: 2, + __temp_key__: 'Zx', + }, + { + id: 4, + __temp_key__: 'Zwl', + }, + { + id: 1, + __temp_key__: 'ZwV', + }, + { + id: 5, + __temp_key__: 'Zy', + }, + { + id: 7, + __temp_key__: 'Zz', + }, + { + id: 10, + __temp_key__: 'ZzV', + }, + { + id: 6, + __temp_key__: 'a0', + }, + { + id: 9, + __temp_key__: 'a0G', + }, + { + id: 8, + __temp_key__: 'a0V', + }, + ], + }, + serverState: { + relation: [ + { + id: 1, + __temp_key__: 'Zv', + }, + { + id: 3, + __temp_key__: 'Zw', + }, + { + id: 2, + __temp_key__: 'Zx', + }, + { + id: 5, + __temp_key__: 'Zy', + }, + { + id: 4, + __temp_key__: 'Zz', + }, + { + id: 6, + __temp_key__: 'a0', + }, + { + id: 7, + __temp_key__: 'a1', + }, + { + id: 8, + __temp_key__: 'a2', + }, + { + id: 9, + __temp_key__: 'a3', + }, + { + id: 10, + __temp_key__: 'a4', + }, + ], + }, + }, + schema, + componentsSchema + ); + + expect(result).toStrictEqual({ + relation: { + connect: [ + { + id: 8, + position: { + end: true, + }, + }, + { + id: 9, + position: { + before: 8, + }, + }, + { + id: 10, + position: { + before: 6, + }, + }, + { + id: 7, + position: { + before: 10, + }, + }, + { + id: 1, + position: { + before: 5, + }, + }, + { + id: 4, + position: { + before: 1, + }, + }, + ], + disconnect: [], + }, + }); + }); + + test('given a long list of relations and i move the first to the last, only that item should be in the payload', () => { + const result = cleanData( + { + browserState: { + relation: [ + { + id: 10, + __temp_key__: 'Zu', + }, + { + id: 1, + __temp_key__: 'Zv', + }, + { + id: 3, + __temp_key__: 'Zw', + }, + { + id: 2, + __temp_key__: 'Zx', + }, + { + id: 5, + __temp_key__: 'Zy', + }, + { + id: 4, + __temp_key__: 'Zz', + }, + { + id: 6, + __temp_key__: 'a0', + }, + { + id: 7, + __temp_key__: 'a1', + }, + { + id: 8, + __temp_key__: 'a2', + }, + { + id: 9, + __temp_key__: 'a3', + }, + ], + }, + serverState: { + relation: [ + { + id: 1, + __temp_key__: 'Zv', + }, + { + id: 3, + __temp_key__: 'Zw', + }, + { + id: 2, + __temp_key__: 'Zx', + }, + { + id: 5, + __temp_key__: 'Zy', + }, + { + id: 4, + __temp_key__: 'Zz', + }, + { + id: 6, + __temp_key__: 'a0', + }, + { + id: 7, + __temp_key__: 'a1', + }, + { + id: 8, + __temp_key__: 'a2', + }, + { + id: 9, + __temp_key__: 'a3', + }, + { + id: 10, + __temp_key__: 'a4', + }, + ], + }, + }, + schema, + componentsSchema + ); + + expect(result).toStrictEqual({ + relation: { + connect: [ + { + id: 10, + position: { before: 1 }, + }, + ], + disconnect: [], + }, + }); + }); }); }); diff --git a/packages/core/admin/package.json b/packages/core/admin/package.json index 426e475de0..f433383824 100644 --- a/packages/core/admin/package.json +++ b/packages/core/admin/package.json @@ -73,6 +73,7 @@ "find-root": "1.1.0", "fork-ts-checker-webpack-plugin": "7.2.1", "formik": "^2.2.6", + "fractional-indexing": "3.2.0", "fs-extra": "10.0.0", "highlight.js": "^10.4.1", "history": "^4.9.0", diff --git a/yarn.lock b/yarn.lock index 984f00e3d9..b96bcc4aef 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12018,6 +12018,11 @@ forwarded@0.2.0: resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== +fractional-indexing@3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/fractional-indexing/-/fractional-indexing-3.2.0.tgz#1193e63d54ff4e0cbe0c79a9ed6cfbab25d91628" + integrity sha512-PcOxmqwYCW7O2ovKRU8OoQQj2yqTfEB/yeTYk4gPid6dN5ODRfU1hXd9tTVZzax/0NkO7AxpHykvZnT1aYp/BQ== + fragment-cache@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" @@ -17867,6 +17872,8 @@ path-case@^2.1.0: version "2.1.1" resolved "https://registry.yarnpkg.com/path-case/-/path-case-2.1.1.tgz#94b8037c372d3fe2906e465bb45e25d226e8eea5" integrity sha1-lLgDfDctP+KQbkZbtF4l0ibo7qU= + dependencies: + no-case "^2.2.0" path-dirname@^1.0.0: version "1.0.2"