refactor: improve droptarget component

This commit is contained in:
mathuo 2023-02-01 22:03:12 +07:00
parent c34e03f158
commit c41158cea6
No known key found for this signature in database
GPG Key ID: C6EEDEFD6CA07281
9 changed files with 154 additions and 134 deletions

View File

@ -10,7 +10,10 @@ describe('groupPanelApi', () => {
title: 'test_title',
};
const accessor: Partial<DockviewComponent> = {};
const accessor: Partial<DockviewComponent> = {
onDidAddPanel: jest.fn(),
onDidRemovePanel: jest.fn(),
};
const groupViewPanel = new GroupPanel(
<DockviewComponent>accessor,
'',
@ -44,7 +47,10 @@ describe('groupPanelApi', () => {
id: 'test_id',
};
const accessor: Partial<DockviewComponent> = {};
const accessor: Partial<DockviewComponent> = {
onDidAddPanel: jest.fn(),
onDidRemovePanel: jest.fn(),
};
const groupViewPanel = new GroupPanel(
<DockviewComponent>accessor,
'',

View File

@ -2,15 +2,15 @@ import { Droptarget, Position } from '../../dnd/droptarget';
import { fireEvent } from '@testing-library/dom';
function createOffsetDragOverEvent(params: {
offsetX: number;
offsetY: number;
clientX: number;
clientY: number;
}): Event {
const event = new Event('dragover', {
bubbles: true,
cancelable: true,
});
Object.defineProperty(event, 'offsetX', { get: () => params.offsetX });
Object.defineProperty(event, 'offsetY', { get: () => params.offsetY });
Object.defineProperty(event, 'clientX', { get: () => params.clientX });
Object.defineProperty(event, 'clientY', { get: () => params.clientY });
return event;
}
@ -32,7 +32,7 @@ describe('droptarget', () => {
droptarget = new Droptarget(element, {
canDisplayOverlay: () => true,
validOverlays: 'none',
acceptedTargetZones: ['center'],
});
droptarget.onDrop((event) => {
@ -54,7 +54,7 @@ describe('droptarget', () => {
droptarget = new Droptarget(element, {
canDisplayOverlay: () => true,
validOverlays: 'all',
acceptedTargetZones: ['top', 'left', 'right', 'bottom', 'center'],
});
droptarget.onDrop((event) => {
@ -73,7 +73,10 @@ describe('droptarget', () => {
fireEvent(
target,
createOffsetDragOverEvent({ offsetX: 19, offsetY: 0 })
createOffsetDragOverEvent({
clientX: 19,
clientY: 0,
})
);
expect(position).toBeUndefined();
@ -84,7 +87,7 @@ describe('droptarget', () => {
test('default', () => {
droptarget = new Droptarget(element, {
canDisplayOverlay: () => true,
validOverlays: 'all',
acceptedTargetZones: ['top', 'left', 'right', 'bottom', 'center'],
});
expect(droptarget.state).toBeUndefined();
@ -106,56 +109,91 @@ describe('droptarget', () => {
fireEvent(
target,
createOffsetDragOverEvent({ offsetX: 19, offsetY: 0 })
createOffsetDragOverEvent({ clientX: 19, clientY: 0 })
);
viewQuery = element.querySelectorAll(
'.drop-target > .drop-target-dropzone > .drop-target-selection.left'
'.drop-target > .drop-target-dropzone > .drop-target-selection'
);
expect(viewQuery.length).toBe(1);
expect(droptarget.state).toBe(Position.Left);
expect(
(
element
.getElementsByClassName('drop-target-selection')
.item(0) as HTMLDivElement
).style.transform
).toBe('translateX(-25%) scaleX(0.5)');
fireEvent(
target,
createOffsetDragOverEvent({ offsetX: 40, offsetY: 19 })
createOffsetDragOverEvent({ clientX: 40, clientY: 19 })
);
viewQuery = element.querySelectorAll(
'.drop-target > .drop-target-dropzone > .drop-target-selection.top'
'.drop-target > .drop-target-dropzone > .drop-target-selection'
);
expect(viewQuery.length).toBe(1);
expect(droptarget.state).toBe(Position.Top);
expect(
(
element
.getElementsByClassName('drop-target-selection')
.item(0) as HTMLDivElement
).style.transform
).toBe('translateY(-25%) scaleY(0.5)');
fireEvent(
target,
createOffsetDragOverEvent({ offsetX: 160, offsetY: 81 })
createOffsetDragOverEvent({ clientX: 160, clientY: 81 })
);
viewQuery = element.querySelectorAll(
'.drop-target > .drop-target-dropzone > .drop-target-selection.bottom'
'.drop-target > .drop-target-dropzone > .drop-target-selection'
);
expect(viewQuery.length).toBe(1);
expect(droptarget.state).toBe(Position.Bottom);
expect(
(
element
.getElementsByClassName('drop-target-selection')
.item(0) as HTMLDivElement
).style.transform
).toBe('translateY(25%) scaleY(0.5)');
fireEvent(
target,
createOffsetDragOverEvent({ offsetX: 161, offsetY: 0 })
createOffsetDragOverEvent({ clientX: 161, clientY: 0 })
);
viewQuery = element.querySelectorAll(
'.drop-target > .drop-target-dropzone > .drop-target-selection.right'
'.drop-target > .drop-target-dropzone > .drop-target-selection'
);
expect(viewQuery.length).toBe(1);
expect(droptarget.state).toBe(Position.Right);
expect(
(
element
.getElementsByClassName('drop-target-selection')
.item(0) as HTMLDivElement
).style.transform
).toBe('translateX(25%) scaleX(0.5)');
fireEvent(
target,
createOffsetDragOverEvent({ offsetX: 100, offsetY: 50 })
createOffsetDragOverEvent({ clientX: 100, clientY: 50 })
);
expect(droptarget.state).toBe(Position.Center);
expect(
(
element
.getElementsByClassName('drop-target-selection')
.item(0) as HTMLDivElement
).style.transform
).toBe('');
fireEvent.dragLeave(target);
expect(droptarget.state).toBeUndefined();
expect(droptarget.state).toBe(Position.Center);
viewQuery = element.querySelectorAll('.drop-target');
expect(viewQuery.length).toBe(0);
});

View File

@ -4,7 +4,10 @@ import { GroupPanel } from '../../groupview/groupviewPanel';
describe('gridviewPanel', () => {
test('get panel', () => {
const accessorMock = jest.fn<DockviewComponent, []>(() => {
return {} as any;
return {
onDidAddPanel: jest.fn(),
onDidRemovePanel: jest.fn(),
} as any;
});
const accessor = new accessorMock();

View File

@ -221,6 +221,8 @@ describe('groupview', () => {
id: 'dockview-1',
removePanel: removePanelMock,
removeGroup: removeGroupMock,
onDidAddPanel: jest.fn(),
onDidRemovePanel: jest.fn(),
}) as DockviewComponent;
options = {
@ -612,6 +614,8 @@ describe('groupview', () => {
showDndOverlay: jest.fn(),
},
getPanel: jest.fn(),
onDidAddPanel: jest.fn(),
onDidRemovePanel: jest.fn(),
};
});
const accessor = new accessorMock() as DockviewComponent;
@ -667,6 +671,8 @@ describe('groupview', () => {
},
getPanel: jest.fn(),
doSetGroupActive: jest.fn(),
onDidAddPanel: jest.fn(),
onDidRemovePanel: jest.fn(),
};
});
const accessor = new accessorMock() as DockviewComponent;
@ -729,6 +735,8 @@ describe('groupview', () => {
},
getPanel: jest.fn(),
doSetGroupActive: jest.fn(),
onDidAddPanel: jest.fn(),
onDidRemovePanel: jest.fn(),
};
});
const accessor = new accessorMock() as DockviewComponent;
@ -792,6 +800,8 @@ describe('groupview', () => {
},
getPanel: jest.fn(),
doSetGroupActive: jest.fn(),
onDidAddPanel: jest.fn(),
onDidRemovePanel: jest.fn(),
};
});
const accessor = new accessorMock() as DockviewComponent;

View File

@ -12,7 +12,10 @@ import { TestPanel } from '../groupview.spec';
describe('tabsContainer', () => {
test('that an external event does not render a drop target and calls through to the group mode', () => {
const accessorMock = jest.fn<Partial<DockviewComponent>, []>(() => {
return {};
return {
onDidAddPanel: jest.fn(),
onDidRemovePanel: jest.fn(),
};
});
const groupviewMock = jest.fn<Partial<Groupview>, []>(() => {
return {
@ -62,6 +65,8 @@ describe('tabsContainer', () => {
const accessorMock = jest.fn<Partial<DockviewComponent>, []>(() => {
return {
id: 'testcomponentid',
onDidAddPanel: jest.fn(),
onDidRemovePanel: jest.fn(),
};
});
const groupviewMock = jest.fn<Partial<Groupview>, []>(() => {
@ -125,6 +130,8 @@ describe('tabsContainer', () => {
const accessorMock = jest.fn<Partial<DockviewComponent>, []>(() => {
return {
id: 'testcomponentid',
onDidAddPanel: jest.fn(),
onDidRemovePanel: jest.fn(),
};
});
const groupviewMock = jest.fn<Partial<Groupview>, []>(() => {
@ -185,6 +192,8 @@ describe('tabsContainer', () => {
const accessorMock = jest.fn<Partial<DockviewComponent>, []>(() => {
return {
id: 'testcomponentid',
onDidAddPanel: jest.fn(),
onDidRemovePanel: jest.fn(),
};
});
const groupviewMock = jest.fn<Partial<Groupview>, []>(() => {
@ -245,6 +254,8 @@ describe('tabsContainer', () => {
const accessorMock = jest.fn<Partial<DockviewComponent>, []>(() => {
return {
id: 'testcomponentid',
onDidAddPanel: jest.fn(),
onDidRemovePanel: jest.fn(),
};
});
const groupviewMock = jest.fn<Partial<Groupview>, []>(() => {

View File

@ -54,10 +54,11 @@ export class Droptarget extends CompositeDisposable {
canDisplayOverlay: CanDisplayOverlay;
acceptedTargetZones: DropTargetDirections[];
overlayModel?: {
units?: 'pixels' | 'percentage';
type?: 'modal' | 'line';
cover?: number;
directionalThreshold?: number;
size?: { value: number; type: 'pixels' | 'percentage' };
activationSize?: {
value: number;
type: 'pixels' | 'percentage';
};
};
}
) {
@ -122,14 +123,7 @@ export class Droptarget extends CompositeDisposable {
return;
}
const isSmallX =
this.options.overlayModel?.type === 'line' ||
width < 100;
const isSmallY =
this.options.overlayModel?.type === 'line' ||
height < 100;
this.toggleClasses(quadrant, isSmallX, isSmallY);
this.toggleClasses(quadrant, width, height);
this.setState(quadrant);
},
@ -161,33 +155,50 @@ export class Droptarget extends CompositeDisposable {
private toggleClasses(
quadrant: Quadrant | null,
isSmallX: boolean,
isSmallY: boolean
width: number,
height: number
) {
if (!this.overlay) {
return;
}
const isSmallX = width < 100;
const isSmallY = height < 100;
const isLeft = quadrant === 'left';
const isRight = quadrant === 'right';
const isTop = quadrant === 'top';
const isBottom = quadrant === 'bottom';
const size =
typeof this.options.overlayModel?.cover === 'number'
? clamp(this.options.overlayModel?.cover, 0, 1)
: 0.5;
const rightClass = !isSmallX && isRight;
const leftClass = !isSmallX && isLeft;
const topClass = !isSmallY && isTop;
const bottomClass = !isSmallY && isBottom;
let size = 0.5;
if (this.options.overlayModel?.size?.type === 'percentage') {
size = clamp(this.options.overlayModel.size.value, 0, 100) / 100;
}
if (this.options.overlayModel?.size?.type === 'pixels') {
if (rightClass || leftClass) {
size =
clamp(0, this.options.overlayModel.size.value, width) /
width;
}
if (topClass || bottomClass) {
size =
clamp(0, this.options.overlayModel.size.value, height) /
height;
}
}
const translate = (1 - size) / 2;
const scale = size;
let transform = '';
const rightClass = !isSmallX && isRight;
const leftClass = !isSmallX && isLeft;
const topClass = !isSmallY && isTop;
const bottomClass = !isSmallY && isBottom;
if (rightClass) {
transform = `translateX(${100 * translate}%) scaleX(${scale})`;
} else if (leftClass) {
@ -202,11 +213,6 @@ export class Droptarget extends CompositeDisposable {
this.overlay.style.transform = transform;
// toggleClass(this.overlay, 'right', !isSmallX && isRight);
// toggleClass(this.overlay, 'left', !isSmallX && isLeft);
// toggleClass(this.overlay, 'top', !isSmallY && isTop);
// toggleClass(this.overlay, 'bottom', !isSmallY && isBottom);
toggleClass(this.overlay, 'small-right', isSmallX && isRight);
toggleClass(this.overlay, 'small-left', isSmallX && isLeft);
toggleClass(this.overlay, 'small-top', isSmallY && isTop);
@ -240,20 +246,23 @@ export class Droptarget extends CompositeDisposable {
width: number,
height: number
): Quadrant | null | undefined {
if (
!this.options.overlayModel?.units ||
this.options.overlayModel?.units === 'percentage'
) {
const isPercentage =
this.options.overlayModel?.activationSize === undefined ||
this.options.overlayModel?.activationSize?.type === 'percentage';
const value =
typeof this.options.overlayModel?.activationSize?.value === 'number'
? this.options.overlayModel?.activationSize?.value
: 20;
if (isPercentage) {
return calculateQuadrant_Percentage(
overlayType,
x,
y,
width,
height,
typeof this.options.overlayModel?.directionalThreshold ===
'number'
? this.options.overlayModel?.directionalThreshold
: 20
value
);
}
@ -263,14 +272,11 @@ export class Droptarget extends CompositeDisposable {
y,
width,
height,
typeof this.options.overlayModel?.directionalThreshold === 'number'
? this.options.overlayModel?.directionalThreshold
: 20
value
);
}
private removeDropTarget() {
console.log('remove');
if (this.target) {
this._state = undefined;
this.element.removeChild(this.target);
@ -305,36 +311,6 @@ function calculateQuadrant_Percentage(
return 'bottom';
}
// switch (overlayType) {
// case 'all':
// case 'nocenter':
// if (xp < threshold) {
// return 'left';
// }
// if (xp > 100 - threshold) {
// return 'right';
// }
// if (yp < threshold) {
// return 'top';
// }
// if (yp > 100 - threshold) {
// return 'bottom';
// }
// break;
// case 'vertical':
// if (yp < 50) {
// return 'top';
// }
// return 'bottom';
// case 'horizontal':
// if (xp < 50) {
// return 'left';
// }
// return 'right';
// }
if (!overlayType.includes('center')) {
return undefined;
}
@ -363,35 +339,6 @@ function calculateQuadrant_Pixels(
return 'bottom';
}
// switch (overlayType) {
// case 'all':
// case 'nocenter':
// if (x < threshold) {
// return 'left';
// }
// if (x > width - threshold) {
// return 'right';
// }
// if (y < threshold) {
// return 'top';
// }
// if (y > height - threshold) {
// return 'bottom';
// }
// break;
// case 'vertical':
// if (x < width / 2) {
// return 'top';
// }
// return 'bottom';
// case 'horizontal':
// if (y < height / 2) {
// return 'left';
// }
// return 'right';
// }
if (!overlayType.includes('center')) {
return undefined;
}

View File

@ -232,14 +232,11 @@ export class DockviewComponent
return true
},
acceptedTargetZones: ['top', 'bottom', 'left', 'right'],
overlayModel:{
units: 'pixels',
type: 'line',
directionalThreshold: 5,
cover: 0.1
}
overlayModel:{
activationSize: { type: 'pixels', value: 10 },
size: { type:'pixels', value: 20 }
}
)
})
this.addDisposables(
dropTarget,
@ -254,13 +251,17 @@ export class DockviewComponent
case Position.Top:
case Position.Bottom:
if(this.gridview.orientation === Orientation.HORIZONTAL) {
this.gridview.flipOrientation();
// we need to add to a vertical splitview but the current root is a horizontal splitview.
// insert a vertical splitview at the root level and add the existing view as a child
this.gridview.insertOrthogonalSplitviewAtRoot();
}
break;
case Position.Left:
case Position.Right:
if(this.gridview.orientation === Orientation.VERTICAL) {
this.gridview.flipOrientation();
// we need to add to a horizontal splitview but the current root is a vertical splitview.
// insert a horiziontal splitview at the root level and add the existing view as a child
this.gridview.insertOrthogonalSplitviewAtRoot();
}
break;
default:
@ -270,11 +271,13 @@ export class DockviewComponent
switch(event.position) {
case Position.Top:
case Position.Left:
this.createGroupAtLocation([0]);
const verticalGroup = this.createGroupAtLocation([0]); // insert into first position
this.moveGroupOrPanel(verticalGroup, data.groupId, data.panelId || undefined, Position.Center);
break;
case Position.Bottom:
case Position.Right:
this.createGroupAtLocation([this.gridview.length]);
const horizontalGroup = this.createGroupAtLocation([this.gridview.length]); // insert into last position
this.moveGroupOrPanel(horizontalGroup, data.groupId, data.panelId || undefined, Position.Center);
}
}
));

View File

@ -418,7 +418,7 @@ export class Gridview implements IDisposable {
* If the root is orientated as a VERTICAL node then nest the existing root within a new HORIZIONTAL root node
* If the root is orientated as a HORIZONTAL node then nest the existing root within a new VERITCAL root node
*/
public flipOrientation(): void {
public insertOrthogonalSplitviewAtRoot(): void {
if (!this._root) {
return;
}

View File

@ -71,7 +71,9 @@ export abstract class DraggablePaneviewPanel extends PaneviewPanel {
this.target = new Droptarget(this.element, {
acceptedTargetZones: ['top', 'bottom'],
threshold: 50,
overlayModel: {
activationSize: { type: 'percentage', value: 50 },
},
canDisplayOverlay: (event) => {
const data = getPaneData();