Favorites Drag and Drop Implementation (#8979)

Adds drag and drop functionality for favorites management, allowing
users to:

- Move favorites between folders
- Move favorites from folders to orphan section
- Move orphan favorites into folders

Known Issues:
Drop detection at folder boundaries requires spacing workaround
This commit is contained in:
nitin
2024-12-17 17:16:58 +05:30
committed by GitHub
parent 4fe3250e81
commit 582530ef1e
19 changed files with 992 additions and 516 deletions

View File

@ -0,0 +1,17 @@
import { FAVORITE_DROPPABLE_IDS } from '@/favorites/constants/FavoriteDroppableIds';
import { createFolderDroppableId } from '../createFolderDroppableId';
describe('createFolderDroppableId', () => {
it('should create a valid folder droppable id', () => {
const folderId = '123-456';
const result = createFolderDroppableId(folderId);
expect(result).toBe(`${FAVORITE_DROPPABLE_IDS.FOLDER_PREFIX}${folderId}`);
});
it('should work with empty string', () => {
const result = createFolderDroppableId('');
expect(result).toBe(FAVORITE_DROPPABLE_IDS.FOLDER_PREFIX);
});
});

View File

@ -0,0 +1,19 @@
import { FAVORITE_DROPPABLE_IDS } from '@/favorites/constants/FavoriteDroppableIds';
import { createFolderHeaderDroppableId } from '../createFolderHeaderDroppableId';
describe('createFolderHeaderDroppableId', () => {
it('should create a valid folder header droppable id', () => {
const folderId = '123-456';
const result = createFolderHeaderDroppableId(folderId);
expect(result).toBe(
`${FAVORITE_DROPPABLE_IDS.FOLDER_HEADER_PREFIX}${folderId}`,
);
});
it('should work with empty string', () => {
const result = createFolderHeaderDroppableId('');
expect(result).toBe(FAVORITE_DROPPABLE_IDS.FOLDER_HEADER_PREFIX);
});
});

View File

@ -0,0 +1,47 @@
import { FAVORITE_DROPPABLE_IDS } from '@/favorites/constants/FavoriteDroppableIds';
import { validateAndExtractFolderId } from '../validateAndExtractFolderId';
describe('validateAndExtractFolderId', () => {
it('should return null for orphan favorites', () => {
const result = validateAndExtractFolderId(
FAVORITE_DROPPABLE_IDS.ORPHAN_FAVORITES,
);
expect(result).toBeNull();
});
it('should extract folder id from folder droppable id', () => {
const folderId = '123-456';
const droppableId = `${FAVORITE_DROPPABLE_IDS.FOLDER_PREFIX}${folderId}`;
const result = validateAndExtractFolderId(droppableId);
expect(result).toBe(folderId);
});
it('should extract folder id from folder header droppable id', () => {
const folderId = '123-456';
const droppableId = `${FAVORITE_DROPPABLE_IDS.FOLDER_HEADER_PREFIX}${folderId}`;
const result = validateAndExtractFolderId(droppableId);
expect(result).toBe(folderId);
});
it('should throw error for invalid droppable id format', () => {
expect(() => {
validateAndExtractFolderId('invalid-id');
}).toThrow('Invalid droppable ID format: invalid-id');
});
it('should throw error for empty folder id in folder format', () => {
expect(() => {
validateAndExtractFolderId(FAVORITE_DROPPABLE_IDS.FOLDER_PREFIX);
}).toThrow(`Invalid folder ID: ${FAVORITE_DROPPABLE_IDS.FOLDER_PREFIX}`);
});
it('should throw error for empty folder id in folder header format', () => {
expect(() => {
validateAndExtractFolderId(FAVORITE_DROPPABLE_IDS.FOLDER_HEADER_PREFIX);
}).toThrow(
`Invalid folder header ID: ${FAVORITE_DROPPABLE_IDS.FOLDER_HEADER_PREFIX}`,
);
});
});

View File

@ -0,0 +1,6 @@
import { FAVORITE_DROPPABLE_IDS } from '@/favorites/constants/FavoriteDroppableIds';
import { FavoriteDroppableId } from '@/favorites/types/FavoriteDroppableId';
export const createFolderDroppableId = (
folderId: string,
): FavoriteDroppableId => `${FAVORITE_DROPPABLE_IDS.FOLDER_PREFIX}${folderId}`;

View File

@ -0,0 +1,7 @@
import { FAVORITE_DROPPABLE_IDS } from '@/favorites/constants/FavoriteDroppableIds';
import { FavoriteDroppableId } from '@/favorites/types/FavoriteDroppableId';
export const createFolderHeaderDroppableId = (
folderId: string,
): FavoriteDroppableId =>
`${FAVORITE_DROPPABLE_IDS.FOLDER_HEADER_PREFIX}${folderId}`;

View File

@ -0,0 +1,29 @@
import { FAVORITE_DROPPABLE_IDS } from '@/favorites/constants/FavoriteDroppableIds';
export const validateAndExtractFolderId = (
droppableId: string,
): string | null => {
if (droppableId === FAVORITE_DROPPABLE_IDS.ORPHAN_FAVORITES) {
return null;
}
if (droppableId.startsWith(FAVORITE_DROPPABLE_IDS.FOLDER_HEADER_PREFIX)) {
const folderId = droppableId.replace(
FAVORITE_DROPPABLE_IDS.FOLDER_HEADER_PREFIX,
'',
);
if (!folderId) throw new Error(`Invalid folder header ID: ${droppableId}`);
return folderId;
}
if (droppableId.startsWith(FAVORITE_DROPPABLE_IDS.FOLDER_PREFIX)) {
const folderId = droppableId.replace(
FAVORITE_DROPPABLE_IDS.FOLDER_PREFIX,
'',
);
if (!folderId) throw new Error(`Invalid folder ID: ${droppableId}`);
return folderId;
}
throw new Error(`Invalid droppable ID format: ${droppableId}`);
};