Refactor command menu items deprecated code (#19508)
- Removes the intermediate `CommandMenuItemConfig` / `CommandConfigContext` / `CommandMenuItemDisplay` abstraction layers, replacing them with a single `CommandMenuItemRenderer` that renders directly from the command menu items from the backend - Eliminates the server-items/ subdirectory by moving its contents (hooks/, contexts/, states/, display/, edit/) up into the parent command-menu-item/ module, removing an unnecessary nesting level.
This commit is contained in:
+39
@@ -0,0 +1,39 @@
|
||||
import { computeInsertPositionFromBounds } from '@/command-menu-item/edit/utils/computeInsertPositionFromBounds';
|
||||
|
||||
describe('computeInsertPositionFromBounds', () => {
|
||||
it('returns midpoint when both bounds are defined', () => {
|
||||
expect(computeInsertPositionFromBounds(2, 4)).toBe(3);
|
||||
});
|
||||
|
||||
it('returns midpoint for non-integer result', () => {
|
||||
expect(computeInsertPositionFromBounds(1, 2)).toBe(1.5);
|
||||
});
|
||||
|
||||
it('returns previous - 1 when only next is undefined', () => {
|
||||
expect(computeInsertPositionFromBounds(5, undefined)).toBe(6);
|
||||
});
|
||||
|
||||
it('returns next - 1 when only previous is undefined', () => {
|
||||
expect(computeInsertPositionFromBounds(undefined, 3)).toBe(2);
|
||||
});
|
||||
|
||||
it('returns 0 when both bounds are undefined', () => {
|
||||
expect(computeInsertPositionFromBounds(undefined, undefined)).toBe(0);
|
||||
});
|
||||
|
||||
it('returns previous - 1 when bounds are equal', () => {
|
||||
expect(computeInsertPositionFromBounds(5, 5)).toBe(4);
|
||||
});
|
||||
|
||||
it('handles negative positions', () => {
|
||||
expect(computeInsertPositionFromBounds(-4, -2)).toBe(-3);
|
||||
});
|
||||
|
||||
it('handles zero as previous position', () => {
|
||||
expect(computeInsertPositionFromBounds(0, 2)).toBe(1);
|
||||
});
|
||||
|
||||
it('handles zero as next position', () => {
|
||||
expect(computeInsertPositionFromBounds(-2, 0)).toBe(-1);
|
||||
});
|
||||
});
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
import { getPositionBoundsAtInsertionPoint } from '@/command-menu-item/edit/utils/getPositionBoundsAtInsertionPoint';
|
||||
|
||||
const makeItems = (positions: number[]) =>
|
||||
positions.map((position, index) => ({
|
||||
id: `item-${index}`,
|
||||
position,
|
||||
}));
|
||||
|
||||
describe('getPositionBoundsAtInsertionPoint', () => {
|
||||
const items = makeItems([10, 20, 30, 40, 50]);
|
||||
|
||||
describe('insert before', () => {
|
||||
it('returns previous=undefined and next=10 when inserting before the first item', () => {
|
||||
expect(
|
||||
getPositionBoundsAtInsertionPoint('item-0', 'before', items),
|
||||
).toEqual({
|
||||
previousPosition: undefined,
|
||||
nextPosition: 10,
|
||||
});
|
||||
});
|
||||
|
||||
it('returns previous=10 and next=20 when inserting before the second item', () => {
|
||||
expect(
|
||||
getPositionBoundsAtInsertionPoint('item-1', 'before', items),
|
||||
).toEqual({
|
||||
previousPosition: 10,
|
||||
nextPosition: 20,
|
||||
});
|
||||
});
|
||||
|
||||
it('returns previous=40 and next=50 when inserting before the last item', () => {
|
||||
expect(
|
||||
getPositionBoundsAtInsertionPoint('item-4', 'before', items),
|
||||
).toEqual({
|
||||
previousPosition: 40,
|
||||
nextPosition: 50,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('insert after', () => {
|
||||
it('returns previous=10 and next=20 when inserting after the first item', () => {
|
||||
expect(
|
||||
getPositionBoundsAtInsertionPoint('item-0', 'after', items),
|
||||
).toEqual({
|
||||
previousPosition: 10,
|
||||
nextPosition: 20,
|
||||
});
|
||||
});
|
||||
|
||||
it('returns previous=50 and next=undefined when inserting after the last item', () => {
|
||||
expect(
|
||||
getPositionBoundsAtInsertionPoint('item-4', 'after', items),
|
||||
).toEqual({
|
||||
previousPosition: 50,
|
||||
nextPosition: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it('returns previous=30 and next=40 when inserting after the middle item', () => {
|
||||
expect(
|
||||
getPositionBoundsAtInsertionPoint('item-2', 'after', items),
|
||||
).toEqual({
|
||||
previousPosition: 30,
|
||||
nextPosition: 40,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('edge cases', () => {
|
||||
it('returns undefined when anchor item is not found', () => {
|
||||
expect(
|
||||
getPositionBoundsAtInsertionPoint('nonexistent', 'before', items),
|
||||
).toBeUndefined();
|
||||
});
|
||||
|
||||
it('handles single-item list inserting before', () => {
|
||||
const singleItem = makeItems([5]);
|
||||
|
||||
expect(
|
||||
getPositionBoundsAtInsertionPoint('item-0', 'before', singleItem),
|
||||
).toEqual({
|
||||
previousPosition: undefined,
|
||||
nextPosition: 5,
|
||||
});
|
||||
});
|
||||
|
||||
it('handles single-item list inserting after', () => {
|
||||
const singleItem = makeItems([5]);
|
||||
|
||||
expect(
|
||||
getPositionBoundsAtInsertionPoint('item-0', 'after', singleItem),
|
||||
).toEqual({
|
||||
previousPosition: 5,
|
||||
nextPosition: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it('returns undefined for empty list', () => {
|
||||
expect(
|
||||
getPositionBoundsAtInsertionPoint('item-0', 'before', []),
|
||||
).toBeUndefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
export const computeInsertPositionFromBounds = (
|
||||
previousPosition: number | undefined,
|
||||
nextPosition: number | undefined,
|
||||
): number => {
|
||||
if (!isDefined(previousPosition) && isDefined(nextPosition)) {
|
||||
return nextPosition - 1;
|
||||
}
|
||||
|
||||
if (isDefined(previousPosition) && !isDefined(nextPosition)) {
|
||||
return previousPosition + 1;
|
||||
}
|
||||
|
||||
if (isDefined(previousPosition) && isDefined(nextPosition)) {
|
||||
if (previousPosition === nextPosition) {
|
||||
return previousPosition - 1;
|
||||
}
|
||||
|
||||
return (previousPosition + nextPosition) / 2;
|
||||
}
|
||||
|
||||
return 0;
|
||||
};
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
type PositionedItem = { id: string; position: number };
|
||||
|
||||
export const getPositionBoundsAtInsertionPoint = (
|
||||
anchorItemId: string,
|
||||
insertionSide: 'before' | 'after',
|
||||
sectionItems: PositionedItem[],
|
||||
) => {
|
||||
const anchorIndex = sectionItems.findIndex(
|
||||
(item) => item.id === anchorItemId,
|
||||
);
|
||||
|
||||
if (anchorIndex === -1) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const insertIndex =
|
||||
insertionSide === 'before' ? anchorIndex : anchorIndex + 1;
|
||||
|
||||
return {
|
||||
previousPosition: sectionItems[insertIndex - 1]?.position,
|
||||
nextPosition: sectionItems[insertIndex]?.position,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,42 @@
|
||||
import { MAIN_CONTEXT_STORE_INSTANCE_ID } from '@/context-store/constants/MainContextStoreInstanceId';
|
||||
import { contextStoreCurrentObjectMetadataItemIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataItemIdComponentState';
|
||||
import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState';
|
||||
import { objectMetadataItemsSelector } from '@/object-metadata/states/objectMetadataItemsSelector';
|
||||
import { getRecordIndexIdFromObjectNamePluralAndViewId } from '@/object-record/utils/getRecordIndexIdFromObjectNamePluralAndViewId';
|
||||
import type { useStore } from 'jotai';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
export const getRecordIndexId = (
|
||||
store: ReturnType<typeof useStore>,
|
||||
): string | null => {
|
||||
const objectMetadataItemId = store.get(
|
||||
contextStoreCurrentObjectMetadataItemIdComponentState.atomFamily({
|
||||
instanceId: MAIN_CONTEXT_STORE_INSTANCE_ID,
|
||||
}),
|
||||
);
|
||||
|
||||
const viewId = store.get(
|
||||
contextStoreCurrentViewIdComponentState.atomFamily({
|
||||
instanceId: MAIN_CONTEXT_STORE_INSTANCE_ID,
|
||||
}),
|
||||
);
|
||||
|
||||
if (!isDefined(objectMetadataItemId) || !isDefined(viewId)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const objectMetadataItems = store.get(objectMetadataItemsSelector.atom);
|
||||
|
||||
const objectMetadataItem = objectMetadataItems.find(
|
||||
(item) => item.id === objectMetadataItemId,
|
||||
);
|
||||
|
||||
if (!isDefined(objectMetadataItem)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return getRecordIndexIdFromObjectNamePluralAndViewId(
|
||||
objectMetadataItem.namePlural,
|
||||
viewId,
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user