Skip to content

Commit

Permalink
Fix DataTableColumn drag and drop
Browse files Browse the repository at this point in the history
I think this got broken with the id changes. Before the Id started with DataTableColumn, but this is not sure because the id is only needed to be unique but can be fully custom. We should only work with the type for such things.
  • Loading branch information
ivy-lli committed Dec 9, 2024
1 parent 663b8bc commit 2089ba2
Show file tree
Hide file tree
Showing 4 changed files with 74 additions and 39 deletions.
2 changes: 1 addition & 1 deletion packages/editor/src/editor/canvas/ComponentBlock.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ type ComponentBlockProps = Omit<DropZoneProps, 'id'> & {
};

export const ComponentBlock = ({ component, preId, ...props }: ComponentBlockProps) => (
<DropZone id={component.cid} preId={preId} {...props}>
<DropZone id={component.cid} type={component.type} preId={preId} {...props}>
<Draggable config={componentByName(component.type)} data={component} />
</DropZone>
);
Expand Down
7 changes: 4 additions & 3 deletions packages/editor/src/editor/canvas/DropZone.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,17 @@ import './DropZone.css';
import type { ComponentProps } from 'react';
import { isDropZoneDisabled } from './drag-data';
import { cn } from '@axonivy/ui-components';
import type { ComponentType } from '@axonivy/form-editor-protocol';

export type DropZoneProps = ComponentProps<'div'> & {
id: string;
type?: ComponentType;
preId?: string;
dragHint?: boolean;
};

export const DropZone = ({ id, preId, className, children }: DropZoneProps) => {
export const DropZone = ({ id, type, preId, className, children }: DropZoneProps) => {
const dnd = useDndContext();
const { isOver, setNodeRef } = useDroppable({ id, disabled: isDropZoneDisabled(id, dnd.active, preId) });
const { isOver, setNodeRef } = useDroppable({ id, disabled: isDropZoneDisabled(id, type, dnd.active, preId) });
return (
<div ref={setNodeRef} className={cn('drop-zone', isOver && 'is-drop-target', className)}>
<div className='drop-zone-block' />
Expand Down
56 changes: 41 additions & 15 deletions packages/editor/src/editor/canvas/drag-data.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,46 +2,72 @@ import type { FormData } from '@axonivy/form-editor-protocol';
import { dragData, isDropZoneDisabled, type DragData } from './drag-data';
import type { Active } from '@dnd-kit/core';
import type { DeepPartial } from '../../types/types';
import { STRUCTURE_DROPZONE_ID_PREFIX, TABLE_DROPZONE_ID_PREFIX } from '../../data/data';

describe('dragData', () => {
test('normal', () => {
const data = filledData();
expectDragData(dragData(data.components[0]), []);
expect(dragData(data.components[0])).toEqual<DragData>({ componentType: 'Input', disabledIds: [] });
});

test('layout', () => {
const data = filledData();
expectDragData(dragData(data.components[2]), ['31', '32', '33']);
expect(dragData(data.components[2])).toEqual<DragData>({ componentType: 'Layout', disabledIds: ['31', '32', '33'] });
});

test('fieldset', () => {
const data = filledData();
expectDragData(dragData(data.components[3]), ['41', '42', '43']);
expect(dragData(data.components[3])).toEqual<DragData>({ componentType: 'Fieldset', disabledIds: ['41', '42', '43'] });
});

test('panel', () => {
const data = filledData();
expectDragData(dragData(data.components[4]), ['51', '52', '53']);
expect(dragData(data.components[4])).toEqual<DragData>({ componentType: 'Panel', disabledIds: ['51', '52', '53'] });
});
});

describe('isDragZoneDisabled', () => {
test('false', () => {
describe('isDropZoneDisabled', () => {
test('inactive draggable', () => {
expect(isDropZoneDisabled('1', undefined, null)).toBeFalsy();
expect(isDropZoneDisabled('1', undefined, undefined)).toBeFalsy();
});

test('drop zone from draggable', () => {
const active: Partial<Active> = { id: '1', data: { current: undefined } };
expect(isDropZoneDisabled('', active as Active)).toBeFalsy();
expect(isDropZoneDisabled('2', active as Active)).toBeFalsy();
expect(isDropZoneDisabled('1', undefined, active as Active)).toBeTruthy();
});

test('true', () => {
test('drop zone from pre element', () => {
const active: Partial<Active> = { id: '1', data: { current: undefined } };
expect(isDropZoneDisabled('1', active as Active)).toBeTruthy();
expect(isDropZoneDisabled('2', active as Active, '1')).toBeTruthy();
expect(isDropZoneDisabled('2', undefined, active as Active, '1')).toBeTruthy();
});
});

const expectDragData = (data: DragData, ids: Array<string>) => {
expect(data).to.deep.equals({ disabledIds: ids });
};
test('empty drop zone from structure element', () => {
const active: Partial<Active> = { id: '1', data: { current: undefined } };
expect(isDropZoneDisabled(`${STRUCTURE_DROPZONE_ID_PREFIX}1`, undefined, active as Active)).toBeTruthy();
});

test('empty drop zone from table element', () => {
const active: Partial<Active> = { id: '1', data: { current: undefined } };
expect(isDropZoneDisabled(`${TABLE_DROPZONE_ID_PREFIX}1`, undefined, active as Active)).toBeTruthy();
});

test('disable all children for structure', () => {
const active: Partial<Active> = { id: '0', data: { current: { componentType: 'Layout', disabledIds: ['2', '3'] } } };
expect(isDropZoneDisabled('1', undefined, active as Active)).toBeFalsy();
expect(isDropZoneDisabled('2', undefined, active as Active)).toBeTruthy();
expect(isDropZoneDisabled('3', undefined, active as Active)).toBeTruthy();
});

test('disable columns', () => {
let active: Partial<Active> = { id: '0', data: { current: { componentType: 'Input', disabledIds: [] } } };
expect(isDropZoneDisabled('1', 'Input', active as Active)).toBeFalsy();
expect(isDropZoneDisabled('2', 'DataTableColumn', active as Active)).toBeTruthy();
active = { id: '0', data: { current: { componentType: 'DataTableColumn', disabledIds: [] } } };
expect(isDropZoneDisabled('1', 'Input', active as Active)).toBeTruthy();
expect(isDropZoneDisabled('2', 'DataTableColumn', active as Active)).toBeFalsy();
});
});

const filledData = () => {
const prefilledData: DeepPartial<FormData> = {
Expand Down
48 changes: 28 additions & 20 deletions packages/editor/src/editor/canvas/drag-data.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { isStructure, isTable, type Component, type ComponentData } from '@axonivy/form-editor-protocol';
import { isStructure, isTable, type Component, type ComponentData, type ComponentType } from '@axonivy/form-editor-protocol';
import type { Active } from '@dnd-kit/core';
import { STRUCTURE_DROPZONE_ID_PREFIX, TABLE_DROPZONE_ID_PREFIX } from '../../data/data';

export type DragData = { disabledIds: Array<string> };
export type DragData = {
componentType: ComponentType;
disabledIds: Array<string>;
};

const disabledIds = (data: Component | ComponentData): Array<string> => {
if (isStructure(data) || isTable(data)) {
Expand All @@ -17,40 +20,45 @@ const disabledIds = (data: Component | ComponentData): Array<string> => {
};

export const dragData = (data: Component | ComponentData): DragData => {
return { disabledIds: disabledIds(data) };
return { componentType: data.type, disabledIds: disabledIds(data) };
};

export const isDragData = (data: unknown): data is DragData => {
return typeof data === 'object' && data !== null && 'disabledIds' in data;
return typeof data === 'object' && data !== null && 'componentType' in data && 'disabledIds' in data;
};

export const isDropZoneDisabled = (id: string, active: Active | null, preId?: string) => {
const dropZoneId = active?.id;

if (disableDropZoneDataTableColumn(id, dropZoneId?.toString())) {
return true;
export const isDropZoneDisabled = (dropId: string, dropType?: ComponentType, active?: Active | null, preDropId?: string) => {
if (active === undefined || active === null) {
return false;
}
const dragId = active.id;
if (
dropZoneId === id ||
`${STRUCTURE_DROPZONE_ID_PREFIX}${dropZoneId}` === id ||
`${TABLE_DROPZONE_ID_PREFIX}${dropZoneId}` === id ||
dropZoneId === preId
dragId === dropId ||
`${STRUCTURE_DROPZONE_ID_PREFIX}${dragId}` === dropId ||
`${TABLE_DROPZONE_ID_PREFIX}${dragId}` === dropId ||
dragId === preDropId
) {
return true;
}
const data = active?.data.current;

const data = active.data.current;
if (isDragData(data)) {
return data.disabledIds.includes(id);
if (disableDataTableColumnDropZone(dropType, data.componentType)) {
return true;
}
return data.disabledIds.includes(dropId);
}
return false;
};

const disableDropZoneDataTableColumn = (id: string, dropZoneId: string | undefined) => {
const isColumn = id.startsWith('DataTableColumn');
const isColumnTarget = dropZoneId ? dropZoneId.includes('DataTableColumn') : false;

const disableDataTableColumnDropZone = (dropType: ComponentType | undefined, dragType: ComponentType) => {
const isColumn = dragType === 'DataTableColumn';
const isColumnTarget = dropType === 'DataTableColumn';
if (isColumn) {
return !isColumnTarget;
}
return isColumnTarget;
if (isColumnTarget) {
return !isColumn;
}
return false;
};

0 comments on commit 2089ba2

Please sign in to comment.