refactor: simplify dnd control

This commit is contained in:
mathuo 2023-02-14 22:19:29 +07:00
parent 321f78ec0e
commit 3052857dfc
No known key found for this signature in database
GPG Key ID: C6EEDEFD6CA07281
15 changed files with 157 additions and 142 deletions

View File

@ -3,9 +3,7 @@ import {
calculateQuadrantAsPixels, calculateQuadrantAsPixels,
directionToPosition, directionToPosition,
Droptarget, Droptarget,
DropTargetDirections,
Position, Position,
Quadrant,
} from '../../dnd/droptarget'; } from '../../dnd/droptarget';
import { fireEvent } from '@testing-library/dom'; import { fireEvent } from '@testing-library/dom';
@ -36,11 +34,11 @@ describe('droptarget', () => {
}); });
test('directionToPosition', () => { test('directionToPosition', () => {
expect(directionToPosition('above')).toBe(Position.Top); expect(directionToPosition('above')).toBe('top');
expect(directionToPosition('below')).toBe(Position.Bottom); expect(directionToPosition('below')).toBe('bottom');
expect(directionToPosition('left')).toBe(Position.Left); expect(directionToPosition('left')).toBe('left');
expect(directionToPosition('right')).toBe(Position.Right); expect(directionToPosition('right')).toBe('right');
expect(directionToPosition('within')).toBe(Position.Center); expect(directionToPosition('within')).toBe('center');
expect(() => directionToPosition('bad_input' as any)).toThrow( expect(() => directionToPosition('bad_input' as any)).toThrow(
"invalid direction 'bad_input'" "invalid direction 'bad_input'"
); );
@ -65,7 +63,7 @@ describe('droptarget', () => {
'.drop-target-dropzone' '.drop-target-dropzone'
) as HTMLElement; ) as HTMLElement;
fireEvent.drop(target); fireEvent.drop(target);
expect(position).toBe(Position.Center); expect(position).toBe('center');
}); });
test('drop', () => { test('drop', () => {
@ -100,7 +98,7 @@ describe('droptarget', () => {
expect(position).toBeUndefined(); expect(position).toBeUndefined();
fireEvent.drop(target); fireEvent.drop(target);
expect(position).toBe(Position.Left); expect(position).toBe('left');
}); });
test('default', () => { test('default', () => {
@ -135,7 +133,7 @@ describe('droptarget', () => {
'.drop-target > .drop-target-dropzone > .drop-target-selection' '.drop-target > .drop-target-dropzone > .drop-target-selection'
); );
expect(viewQuery.length).toBe(1); expect(viewQuery.length).toBe(1);
expect(droptarget.state).toBe(Position.Left); expect(droptarget.state).toBe('left');
expect( expect(
( (
element element
@ -153,7 +151,7 @@ describe('droptarget', () => {
'.drop-target > .drop-target-dropzone > .drop-target-selection' '.drop-target > .drop-target-dropzone > .drop-target-selection'
); );
expect(viewQuery.length).toBe(1); expect(viewQuery.length).toBe(1);
expect(droptarget.state).toBe(Position.Top); expect(droptarget.state).toBe('top');
expect( expect(
( (
element element
@ -171,7 +169,7 @@ describe('droptarget', () => {
'.drop-target > .drop-target-dropzone > .drop-target-selection' '.drop-target > .drop-target-dropzone > .drop-target-selection'
); );
expect(viewQuery.length).toBe(1); expect(viewQuery.length).toBe(1);
expect(droptarget.state).toBe(Position.Bottom); expect(droptarget.state).toBe('bottom');
expect( expect(
( (
element element
@ -189,7 +187,7 @@ describe('droptarget', () => {
'.drop-target > .drop-target-dropzone > .drop-target-selection' '.drop-target > .drop-target-dropzone > .drop-target-selection'
); );
expect(viewQuery.length).toBe(1); expect(viewQuery.length).toBe(1);
expect(droptarget.state).toBe(Position.Right); expect(droptarget.state).toBe('right');
expect( expect(
( (
element element
@ -202,7 +200,7 @@ describe('droptarget', () => {
target, target,
createOffsetDragOverEvent({ clientX: 100, clientY: 50 }) createOffsetDragOverEvent({ clientX: 100, clientY: 50 })
); );
expect(droptarget.state).toBe(Position.Center); expect(droptarget.state).toBe('center');
expect( expect(
( (
element element
@ -212,7 +210,7 @@ describe('droptarget', () => {
).toBe(''); ).toBe('');
fireEvent.dragLeave(target); fireEvent.dragLeave(target);
expect(droptarget.state).toBe(Position.Center); expect(droptarget.state).toBe('center');
viewQuery = element.querySelectorAll('.drop-target'); viewQuery = element.querySelectorAll('.drop-target');
expect(viewQuery.length).toBe(0); expect(viewQuery.length).toBe(0);
}); });
@ -220,10 +218,10 @@ describe('droptarget', () => {
describe('calculateQuadrantAsPercentage', () => { describe('calculateQuadrantAsPercentage', () => {
test('variety of cases', () => { test('variety of cases', () => {
const inputs: Array<{ const inputs: Array<{
directions: DropTargetDirections[]; directions: Position[];
x: number; x: number;
y: number; y: number;
result: Quadrant | null | undefined; result: Position | null;
}> = [ }> = [
{ directions: ['left', 'right'], x: 19, y: 50, result: 'left' }, { directions: ['left', 'right'], x: 19, y: 50, result: 'left' },
{ {
@ -248,13 +246,13 @@ describe('droptarget', () => {
directions: ['left', 'right', 'top', 'bottom', 'center'], directions: ['left', 'right', 'top', 'bottom', 'center'],
x: 50, x: 50,
y: 50, y: 50,
result: null, result: 'center',
}, },
{ {
directions: ['left', 'right', 'top', 'bottom'], directions: ['left', 'right', 'top', 'bottom'],
x: 50, x: 50,
y: 50, y: 50,
result: undefined, result: null,
}, },
]; ];
@ -276,10 +274,10 @@ describe('droptarget', () => {
describe('calculateQuadrantAsPixels', () => { describe('calculateQuadrantAsPixels', () => {
test('variety of cases', () => { test('variety of cases', () => {
const inputs: Array<{ const inputs: Array<{
directions: DropTargetDirections[]; directions: Position[];
x: number; x: number;
y: number; y: number;
result: Quadrant | null | undefined; result: Position | null;
}> = [ }> = [
{ directions: ['left', 'right'], x: 19, y: 50, result: 'left' }, { directions: ['left', 'right'], x: 19, y: 50, result: 'left' },
{ {
@ -304,13 +302,13 @@ describe('droptarget', () => {
directions: ['left', 'right', 'top', 'bottom', 'center'], directions: ['left', 'right', 'top', 'bottom', 'center'],
x: 50, x: 50,
y: 50, y: 50,
result: null, result: 'center',
}, },
{ {
directions: ['left', 'right', 'top', 'bottom'], directions: ['left', 'right', 'top', 'bottom'],
x: 50, x: 50,
y: 50, y: 50,
result: undefined, result: null,
}, },
]; ];

View File

@ -253,9 +253,9 @@ describe('dockviewComponent', () => {
const panel4 = dockview.getGroupPanel('panel4'); const panel4 = dockview.getGroupPanel('panel4');
const group1 = panel1!.group; const group1 = panel1!.group;
dockview.moveGroupOrPanel(group1, group1.id, 'panel1', Position.Right); dockview.moveGroupOrPanel(group1, group1.id, 'panel1', 'right');
const group2 = panel1!.group; const group2 = panel1!.group;
dockview.moveGroupOrPanel(group2, group1.id, 'panel3', Position.Center); dockview.moveGroupOrPanel(group2, group1.id, 'panel3', 'center');
expect(dockview.activeGroup).toBe(group2); expect(dockview.activeGroup).toBe(group2);
expect(dockview.activeGroup!.model.activePanel).toBe(panel3); expect(dockview.activeGroup!.model.activePanel).toBe(panel3);
@ -305,9 +305,9 @@ describe('dockviewComponent', () => {
const panel1 = dockview.getGroupPanel('panel1')!; const panel1 = dockview.getGroupPanel('panel1')!;
const panel2 = dockview.getGroupPanel('panel2')!; const panel2 = dockview.getGroupPanel('panel2')!;
const group1 = panel1.group; const group1 = panel1.group;
dockview.moveGroupOrPanel(group1, group1.id, 'panel1', Position.Right); dockview.moveGroupOrPanel(group1, group1.id, 'panel1', 'right');
const group2 = panel1.group; const group2 = panel1.group;
dockview.moveGroupOrPanel(group2, group1.id, 'panel3', Position.Center); dockview.moveGroupOrPanel(group2, group1.id, 'panel3', 'center');
expect(dockview.size).toBe(2); expect(dockview.size).toBe(2);
expect(dockview.totalPanels).toBe(4); expect(dockview.totalPanels).toBe(4);
@ -370,9 +370,9 @@ describe('dockviewComponent', () => {
expect(panel4.api.isActive).toBeFalsy(); expect(panel4.api.isActive).toBeFalsy();
const group1 = panel1.group; const group1 = panel1.group;
dockview.moveGroupOrPanel(group1, group1.id, 'panel1', Position.Right); dockview.moveGroupOrPanel(group1, group1.id, 'panel1', 'right');
const group2 = panel1.group; const group2 = panel1.group;
dockview.moveGroupOrPanel(group2, group1.id, 'panel3', Position.Center); dockview.moveGroupOrPanel(group2, group1.id, 'panel3', 'center');
expect(dockview.size).toBe(2); expect(dockview.size).toBe(2);
expect(panel1.group).toBe(panel3.group); expect(panel1.group).toBe(panel3.group);
@ -439,7 +439,7 @@ describe('dockviewComponent', () => {
expect(group.model.indexOf(panel1)).toBe(0); expect(group.model.indexOf(panel1)).toBe(0);
expect(group.model.indexOf(panel2)).toBe(1); expect(group.model.indexOf(panel2)).toBe(1);
dockview.moveGroupOrPanel(group, group.id, 'panel1', Position.Right); dockview.moveGroupOrPanel(group, group.id, 'panel1', 'right');
expect(dockview.size).toBe(2); expect(dockview.size).toBe(2);
expect(dockview.totalPanels).toBe(2); expect(dockview.totalPanels).toBe(2);
@ -489,7 +489,7 @@ describe('dockviewComponent', () => {
expect(viewQuery.length).toBe(1); expect(viewQuery.length).toBe(1);
const group = dockview.getGroupPanel('panel1')!.group; const group = dockview.getGroupPanel('panel1')!.group;
dockview.moveGroupOrPanel(group, group.id, 'panel1', Position.Right); dockview.moveGroupOrPanel(group, group.id, 'panel1', 'right');
viewQuery = container.querySelectorAll( viewQuery = container.querySelectorAll(
'.branch-node > .split-view-container > .view-container > .view' '.branch-node > .split-view-container > .view-container > .view'
@ -974,7 +974,7 @@ describe('dockviewComponent', () => {
panel2.group!, panel2.group!,
panel5.group!.id, panel5.group!.id,
panel5.id, panel5.id,
Position.Center 'center'
); );
expect(events).toEqual([ expect(events).toEqual([
{ type: 'REMOVE_PANEL', panel: panel5 }, { type: 'REMOVE_PANEL', panel: panel5 },
@ -993,7 +993,7 @@ describe('dockviewComponent', () => {
panel2.group!, panel2.group!,
panel4.group!.id, panel4.group!.id,
panel4.id, panel4.id,
Position.Center 'center'
); );
expect(events).toEqual([ expect(events).toEqual([
@ -1313,7 +1313,7 @@ describe('dockviewComponent', () => {
panel1.group, panel1.group,
panel2.group.id, panel2.group.id,
'panel2', 'panel2',
Position.Left 'left'
); );
expect(panel1Spy).not.toHaveBeenCalled(); expect(panel1Spy).not.toHaveBeenCalled();
@ -1354,7 +1354,7 @@ describe('dockviewComponent', () => {
panel1.group, panel1.group,
panel2.group.id, panel2.group.id,
'panel2', 'panel2',
Position.Center 'center'
); );
expect(panel1Spy).not.toHaveBeenCalled(); expect(panel1Spy).not.toHaveBeenCalled();
@ -1393,7 +1393,7 @@ describe('dockviewComponent', () => {
panel1.group, panel1.group,
panel1.group.id, panel1.group.id,
'panel1', 'panel1',
Position.Center, 'center',
0 0
); );
@ -1554,7 +1554,7 @@ describe('dockviewComponent', () => {
panel3.group, panel3.group,
panel1.group.id, panel1.group.id,
undefined, undefined,
Position.Center 'center'
); );
expect(dockview.groups.length).toBe(1); expect(dockview.groups.length).toBe(1);

View File

@ -730,7 +730,7 @@ describe('groupview', () => {
).toBe(0); ).toBe(0);
}); });
test('that should allow drop when not dropping on self for same component id', () => { test('that should not allow drop when dropping on self for same component id', () => {
const accessorMock = jest.fn<Partial<DockviewComponent>, []>(() => { const accessorMock = jest.fn<Partial<DockviewComponent>, []>(() => {
return { return {
id: 'testcomponentid', id: 'testcomponentid',
@ -792,7 +792,7 @@ describe('groupview', () => {
expect( expect(
element.getElementsByClassName('drop-target-dropzone').length element.getElementsByClassName('drop-target-dropzone').length
).toBe(1); ).toBe(0);
}); });
test('that should not allow drop when not dropping for different component id', () => { test('that should not allow drop when not dropping for different component id', () => {

View File

@ -9,44 +9,29 @@ function numberOrFallback(maybeNumber: any, fallback: number): number {
return typeof maybeNumber === 'number' ? maybeNumber : fallback; return typeof maybeNumber === 'number' ? maybeNumber : fallback;
} }
export enum Position {
Top = 'Top',
Left = 'Left',
Bottom = 'Bottom',
Right = 'Right',
Center = 'Center',
}
export function directionToPosition(direction: Direction): Position { export function directionToPosition(direction: Direction): Position {
switch (direction) { switch (direction) {
case 'above': case 'above':
return Position.Top; return 'top';
case 'below': case 'below':
return Position.Bottom; return 'bottom';
case 'left': case 'left':
return Position.Left; return 'left';
case 'right': case 'right':
return Position.Right; return 'right';
case 'within': case 'within':
return Position.Center; return 'center';
default: default:
throw new Error(`invalid direction '${direction}'`); throw new Error(`invalid direction '${direction}'`);
} }
} }
export type Quadrant = 'top' | 'bottom' | 'left' | 'right';
export interface DroptargetEvent { export interface DroptargetEvent {
readonly position: Position; readonly position: Position;
readonly nativeEvent: DragEvent; readonly nativeEvent: DragEvent;
} }
export type DropTargetDirections = export type Position = 'top' | 'bottom' | 'left' | 'right' | 'center';
| 'top'
| 'bottom'
| 'left'
| 'right'
| 'center';
function isBooleanValue( function isBooleanValue(
canDisplayOverlay: CanDisplayOverlay canDisplayOverlay: CanDisplayOverlay
@ -56,7 +41,7 @@ function isBooleanValue(
export type CanDisplayOverlay = export type CanDisplayOverlay =
| boolean | boolean
| ((dragEvent: DragEvent, state: Quadrant | null) => boolean); | ((dragEvent: DragEvent, state: Position) => boolean);
export class Droptarget extends CompositeDisposable { export class Droptarget extends CompositeDisposable {
private target: HTMLElement | undefined; private target: HTMLElement | undefined;
@ -74,7 +59,7 @@ export class Droptarget extends CompositeDisposable {
private readonly element: HTMLElement, private readonly element: HTMLElement,
private readonly options: { private readonly options: {
canDisplayOverlay: CanDisplayOverlay; canDisplayOverlay: CanDisplayOverlay;
acceptedTargetZones: DropTargetDirections[]; acceptedTargetZones: Position[];
overlayModel?: { overlayModel?: {
size?: { value: number; type: 'pixels' | 'percentage' }; size?: { value: number; type: 'pixels' | 'percentage' };
activationSize?: { activationSize?: {
@ -117,7 +102,7 @@ export class Droptarget extends CompositeDisposable {
height height
); );
if (quadrant === undefined) { if (quadrant === null) {
this.removeDropTarget(); this.removeDropTarget();
return; return;
} }
@ -135,7 +120,7 @@ export class Droptarget extends CompositeDisposable {
this.target.className = 'drop-target-dropzone'; this.target.className = 'drop-target-dropzone';
this.overlay = document.createElement('div'); this.overlay = document.createElement('div');
this.overlay.className = 'drop-target-selection'; this.overlay.className = 'drop-target-selection';
this._state = Position.Center; this._state = 'center';
this.target.appendChild(this.overlay); this.target.appendChild(this.overlay);
this.element.classList.add('drop-target'); this.element.classList.add('drop-target');
@ -181,7 +166,7 @@ export class Droptarget extends CompositeDisposable {
} }
private toggleClasses( private toggleClasses(
quadrant: Quadrant | null, quadrant: Position,
width: number, width: number,
height: number height: number
): void { ): void {
@ -246,33 +231,33 @@ export class Droptarget extends CompositeDisposable {
toggleClass(this.overlay, 'small-bottom', isSmallY && isBottom); toggleClass(this.overlay, 'small-bottom', isSmallY && isBottom);
} }
private setState(quadrant: Quadrant | null): void { private setState(quadrant: Position): void {
switch (quadrant) { switch (quadrant) {
case 'top': case 'top':
this._state = Position.Top; this._state = 'top';
break; break;
case 'left': case 'left':
this._state = Position.Left; this._state = 'left';
break; break;
case 'bottom': case 'bottom':
this._state = Position.Bottom; this._state = 'bottom';
break; break;
case 'right': case 'right':
this._state = Position.Right; this._state = 'right';
break; break;
default: case 'center':
this._state = Position.Center; this._state = 'center';
break; break;
} }
} }
private calculateQuadrant( private calculateQuadrant(
overlayType: Set<DropTargetDirections>, overlayType: Set<Position>,
x: number, x: number,
y: number, y: number,
width: number, width: number,
height: number height: number
): Quadrant | null | undefined { ): Position | null {
const isPercentage = const isPercentage =
this.options.overlayModel?.activationSize === undefined || this.options.overlayModel?.activationSize === undefined ||
this.options.overlayModel?.activationSize?.type === 'percentage'; this.options.overlayModel?.activationSize?.type === 'percentage';
@ -315,13 +300,13 @@ export class Droptarget extends CompositeDisposable {
} }
export function calculateQuadrantAsPercentage( export function calculateQuadrantAsPercentage(
overlayType: Set<DropTargetDirections>, overlayType: Set<Position>,
x: number, x: number,
y: number, y: number,
width: number, width: number,
height: number, height: number,
threshold: number threshold: number
): Quadrant | null | undefined { ): Position | null {
const xp = (100 * x) / width; const xp = (100 * x) / width;
const yp = (100 * y) / height; const yp = (100 * y) / height;
@ -339,20 +324,20 @@ export function calculateQuadrantAsPercentage(
} }
if (!overlayType.has('center')) { if (!overlayType.has('center')) {
return undefined;
}
return null; return null;
} }
return 'center';
}
export function calculateQuadrantAsPixels( export function calculateQuadrantAsPixels(
overlayType: Set<DropTargetDirections>, overlayType: Set<Position>,
x: number, x: number,
y: number, y: number,
width: number, width: number,
height: number, height: number,
threshold: number threshold: number
): Quadrant | null | undefined { ): Position | null {
if (overlayType.has('left') && x < threshold) { if (overlayType.has('left') && x < threshold) {
return 'left'; return 'left';
} }
@ -367,8 +352,8 @@ export function calculateQuadrantAsPixels(
} }
if (!overlayType.has('center')) { if (!overlayType.has('center')) {
return undefined;
}
return null; return null;
} }
return 'center';
}

View File

@ -46,6 +46,7 @@ import {
import { GroupPanel, IGroupviewPanel } from '../groupview/groupviewPanel'; import { GroupPanel, IGroupviewPanel } from '../groupview/groupviewPanel';
import { DefaultGroupPanelView } from './defaultGroupPanelView'; import { DefaultGroupPanelView } from './defaultGroupPanelView';
import { getPanelData } from '../dnd/dataTransfer'; import { getPanelData } from '../dnd/dataTransfer';
import { DockviewDropTargets } from '../groupview/dnd';
export interface PanelReference { export interface PanelReference {
update: (event: { params: { [key: string]: any } }) => void; update: (event: { params: { [key: string]: any } }) => void;
@ -235,8 +236,26 @@ export class DockviewComponent
} }
const dropTarget = new Droptarget(this.element, { const dropTarget = new Droptarget(this.element, {
canDisplayOverlay: () => { canDisplayOverlay: (event, position) => {
const data = getPanelData();
if (data) {
if (data.viewId !== this.id) {
return false;
}
return true; return true;
}
if (this.options.showDndOverlay) {
return this.options.showDndOverlay({
nativeEvent: event,
position: position,
target: DockviewDropTargets.Edge,
getData: getPanelData,
});
}
return false;
}, },
acceptedTargetZones: ['top', 'bottom', 'left', 'right'], acceptedTargetZones: ['top', 'bottom', 'left', 'right'],
overlayModel: { overlayModel: {
@ -250,16 +269,14 @@ export class DockviewComponent
dropTarget.onDrop((event) => { dropTarget.onDrop((event) => {
const data = getPanelData(); const data = getPanelData();
if (!data) { if (data) {
return;
}
this.moveGroupOrPanel( this.moveGroupOrPanel(
this.orthogonalize(event.position), this.orthogonalize(event.position),
data.groupId, data.groupId,
data.panelId || undefined, data.panelId || undefined,
Position.Center 'center'
); );
}
}) })
); );
@ -268,16 +285,16 @@ export class DockviewComponent
private orthogonalize(position: Position): GroupPanel { private orthogonalize(position: Position): GroupPanel {
switch (position) { switch (position) {
case Position.Top: case 'top':
case Position.Bottom: case 'bottom':
if (this.gridview.orientation === Orientation.HORIZONTAL) { if (this.gridview.orientation === Orientation.HORIZONTAL) {
// we need to add to a vertical splitview but the current root is a horizontal splitview. // 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 // insert a vertical splitview at the root level and add the existing view as a child
this.gridview.insertOrthogonalSplitviewAtRoot(); this.gridview.insertOrthogonalSplitviewAtRoot();
} }
break; break;
case Position.Left: case 'left':
case Position.Right: case 'right':
if (this.gridview.orientation === Orientation.VERTICAL) { if (this.gridview.orientation === Orientation.VERTICAL) {
// we need to add to a horizontal splitview but the current root is a vertical splitview. // 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 // insert a horiziontal splitview at the root level and add the existing view as a child
@ -289,11 +306,11 @@ export class DockviewComponent
} }
switch (position) { switch (position) {
case Position.Top: case 'top':
case Position.Left: case 'left':
return this.createGroupAtLocation([0]); // insert into first position return this.createGroupAtLocation([0]); // insert into first position
case Position.Bottom: case 'bottom':
case Position.Right: case 'right':
return this.createGroupAtLocation([this.gridview.length]); // insert into last position return this.createGroupAtLocation([this.gridview.length]); // insert into last position
default: default:
throw new Error(`unsupported position ${position}`); throw new Error(`unsupported position ${position}`);
@ -543,7 +560,7 @@ export class DockviewComponent
const target = toTarget( const target = toTarget(
<Direction>options.position?.direction || 'within' <Direction>options.position?.direction || 'within'
); );
if (target === Position.Center) { if (target === 'center') {
panel = this.createPanel(options, referenceGroup); panel = this.createPanel(options, referenceGroup);
referenceGroup.model.openPanel(panel); referenceGroup.model.openPanel(panel);
} else { } else {
@ -703,7 +720,7 @@ export class DockviewComponent
return; return;
} }
if (!target || target === Position.Center) { if (!target || target === 'center') {
const groupItem: IDockviewPanel | undefined = const groupItem: IDockviewPanel | undefined =
sourceGroup?.model.removePanel(itemId) || sourceGroup?.model.removePanel(itemId) ||
this.panels.find((panel) => panel.id === itemId); this.panels.find((panel) => panel.id === itemId);
@ -782,7 +799,7 @@ export class DockviewComponent
target: Position target: Position
): void { ): void {
if (sourceGroup) { if (sourceGroup) {
if (!target || target === Position.Center) { if (!target || target === 'center') {
const activePanel = sourceGroup.activePanel; const activePanel = sourceGroup.activePanel;
const panels = [...sourceGroup.panels].map((p) => const panels = [...sourceGroup.panels].map((p) =>
sourceGroup.model.removePanel(p.id) sourceGroup.model.removePanel(p.id)

View File

@ -14,6 +14,7 @@ import { FrameworkFactory } from '../types';
import { DockviewDropTargets } from '../groupview/dnd'; import { DockviewDropTargets } from '../groupview/dnd';
import { PanelTransfer } from '../dnd/dataTransfer'; import { PanelTransfer } from '../dnd/dataTransfer';
import { IGroupControlRenderer } from '../react/dockview/groupControlsRenderer'; import { IGroupControlRenderer } from '../react/dockview/groupControlsRenderer';
import { Position } from '../dnd/droptarget';
export interface GroupPanelFrameworkComponentFactory { export interface GroupPanelFrameworkComponentFactory {
content: FrameworkFactory<IContentRenderer>; content: FrameworkFactory<IContentRenderer>;
@ -54,7 +55,8 @@ export interface ViewFactoryData {
export interface DockviewDndOverlayEvent { export interface DockviewDndOverlayEvent {
nativeEvent: DragEvent; nativeEvent: DragEvent;
target: DockviewDropTargets; target: DockviewDropTargets;
group: GroupPanel; position: Position;
group?: GroupPanel;
getData: () => PanelTransfer | undefined; getData: () => PanelTransfer | undefined;
} }

View File

@ -15,19 +15,19 @@ const nextLayoutId = sequentialNumberGenerator();
export type Direction = 'left' | 'right' | 'above' | 'below' | 'within'; export type Direction = 'left' | 'right' | 'above' | 'below' | 'within';
export function toTarget(direction: Direction) { export function toTarget(direction: Direction): Position {
switch (direction) { switch (direction) {
case 'left': case 'left':
return Position.Left; return 'left';
case 'right': case 'right':
return Position.Right; return 'right';
case 'above': case 'above':
return Position.Top; return 'top';
case 'below': case 'below':
return Position.Bottom; return 'bottom';
case 'within': case 'within':
default: default:
return Position.Center; return 'center';
} }
} }

View File

@ -9,13 +9,13 @@ import {
Orientation, Orientation,
Sizing, Sizing,
} from '../splitview/core/splitview'; } from '../splitview/core/splitview';
import { Position } from '../dnd/droptarget';
import { tail } from '../array'; import { tail } from '../array';
import { LeafNode } from './leafNode'; import { LeafNode } from './leafNode';
import { BranchNode } from './branchNode'; import { BranchNode } from './branchNode';
import { Node } from './types'; import { Node } from './types';
import { Emitter, Event } from '../events'; import { Emitter, Event } from '../events';
import { IDisposable, MutableDisposable } from '../lifecycle'; import { IDisposable, MutableDisposable } from '../lifecycle';
import { Position } from '../dnd/droptarget';
function findLeaf(candiateNode: Node, last: boolean): LeafNode { function findLeaf(candiateNode: Node, last: boolean): LeafNode {
if (candiateNode instanceof LeafNode) { if (candiateNode instanceof LeafNode) {
@ -132,22 +132,19 @@ export function getRelativeLocation(
const [rest, _index] = tail(location); const [rest, _index] = tail(location);
let index = _index; let index = _index;
if (direction === Position.Right || direction === Position.Bottom) { if (direction === 'right' || direction === 'bottom') {
index += 1; index += 1;
} }
return [...rest, index]; return [...rest, index];
} else { } else {
const index = const index = direction === 'right' || direction === 'bottom' ? 1 : 0;
direction === Position.Right || direction === Position.Bottom
? 1
: 0;
return [...location, index]; return [...location, index];
} }
} }
export function getDirectionOrientation(direction: Position): Orientation { export function getDirectionOrientation(direction: Position): Orientation {
return direction === Position.Top || direction === Position.Bottom return direction === 'top' || direction === 'bottom'
? Orientation.VERTICAL ? Orientation.VERTICAL
: Orientation.HORIZONTAL; : Orientation.HORIZONTAL;
} }

View File

@ -3,7 +3,6 @@ import {
SerializedGridObject, SerializedGridObject,
getGridLocation, getGridLocation,
} from './gridview'; } from './gridview';
import { Position } from '../dnd/droptarget';
import { tail, sequenceEquals } from '../array'; import { tail, sequenceEquals } from '../array';
import { CompositeDisposable } from '../lifecycle'; import { CompositeDisposable } from '../lifecycle';
import { IPanelDeserializer } from '../dockview/deserializer'; import { IPanelDeserializer } from '../dockview/deserializer';
@ -25,6 +24,7 @@ import { BaseComponentOptions } from '../panel/types';
import { Orientation, Sizing } from '../splitview/core/splitview'; import { Orientation, Sizing } from '../splitview/core/splitview';
import { createComponent } from '../panel/componentFactory'; import { createComponent } from '../panel/componentFactory';
import { Emitter, Event } from '../events'; import { Emitter, Event } from '../events';
import { Position } from '../dnd/droptarget';
export interface SerializedGridview { export interface SerializedGridview {
grid: { grid: {
@ -265,7 +265,7 @@ export class GridviewComponent
} }
const target = toTarget(options.direction); const target = toTarget(options.direction);
if (target === Position.Center) { if (target === 'center') {
throw new Error(`${target} not supported as an option`); throw new Error(`${target} not supported as an option`);
} else { } else {
const location = getGridLocation(referenceGroup.element); const location = getGridLocation(referenceGroup.element);
@ -294,7 +294,7 @@ export class GridviewComponent
} }
const target = toTarget(options.position.direction); const target = toTarget(options.position.direction);
if (target === Position.Center) { if (target === 'center') {
throw new Error(`${target} not supported as an option`); throw new Error(`${target} not supported as an option`);
} else { } else {
const location = getGridLocation(referenceGroup.element); const location = getGridLocation(referenceGroup.element);

View File

@ -2,4 +2,5 @@ export enum DockviewDropTargets {
Tab, Tab,
Panel, Panel,
TabContainer, TabContainer,
Edge,
} }

View File

@ -110,7 +110,11 @@ export interface IGroupview extends IDisposable, IGridPanelView {
panel?: IDockviewPanel; panel?: IDockviewPanel;
suppressRoll?: boolean; suppressRoll?: boolean;
}): void; }): void;
canDisplayOverlay(event: DragEvent, target: DockviewDropTargets): boolean; canDisplayOverlay(
event: DragEvent,
position: Position,
target: DockviewDropTargets
): boolean;
} }
export class Groupview extends CompositeDisposable implements IGroupview { export class Groupview extends CompositeDisposable implements IGroupview {
@ -240,18 +244,24 @@ export class Groupview extends CompositeDisposable implements IGroupview {
this.dropTarget = new Droptarget(this.contentContainer.element, { this.dropTarget = new Droptarget(this.contentContainer.element, {
acceptedTargetZones: ['top', 'bottom', 'left', 'right', 'center'], acceptedTargetZones: ['top', 'bottom', 'left', 'right', 'center'],
canDisplayOverlay: (event, quadrant) => { canDisplayOverlay: (event, position) => {
if (this.locked && !quadrant) { if (this.locked && position === 'center') {
return false; return false;
} }
const data = getPanelData(); const data = getPanelData();
if (data && data.viewId === this.accessor.id) { if (data && data.viewId === this.accessor.id) {
if (data.panelId === null && data.groupId === this.id) { if (data.groupId === this.id) {
// don't allow group move to drop on self if (position === 'center') {
// don't allow to drop on self for center position
return false; return false;
} }
if (data.panelId === null) {
// don't allow group move to drop anywhere on self
return false;
}
}
const groupHasOnePanelAndIsActiveDragElement = const groupHasOnePanelAndIsActiveDragElement =
this._panels.length === 1 && data.groupId === this.id; this._panels.length === 1 && data.groupId === this.id;
@ -259,7 +269,11 @@ export class Groupview extends CompositeDisposable implements IGroupview {
return !groupHasOnePanelAndIsActiveDragElement; return !groupHasOnePanelAndIsActiveDragElement;
} }
return this.canDisplayOverlay(event, DockviewDropTargets.Panel); return this.canDisplayOverlay(
event,
position,
DockviewDropTargets.Panel
);
}, },
}); });
@ -279,7 +293,7 @@ export class Groupview extends CompositeDisposable implements IGroupview {
this._onDidRemovePanel, this._onDidRemovePanel,
this._onDidActivePanelChange, this._onDidActivePanelChange,
this.tabsContainer.onDrop((event) => { this.tabsContainer.onDrop((event) => {
this.handleDropEvent(event.event, Position.Center, event.index); this.handleDropEvent(event.event, 'center', event.index);
}), }),
this.contentContainer.onDidFocus(() => { this.contentContainer.onDidFocus(() => {
this.accessor.doSetGroupActive(this.groupPanel, true); this.accessor.doSetGroupActive(this.groupPanel, true);
@ -673,13 +687,18 @@ export class Groupview extends CompositeDisposable implements IGroupview {
} }
} }
canDisplayOverlay(event: DragEvent, target: DockviewDropTargets): boolean { canDisplayOverlay(
event: DragEvent,
position: Position,
target: DockviewDropTargets
): boolean {
// custom overlay handler // custom overlay handler
if (this.accessor.options.showDndOverlay) { if (this.accessor.options.showDndOverlay) {
return this.accessor.options.showDndOverlay({ return this.accessor.options.showDndOverlay({
nativeEvent: event, nativeEvent: event,
target, target,
group: this.accessor.getPanel(this.id)!, group: this.accessor.getPanel(this.id)!,
position,
getData: getPanelData, getData: getPanelData,
}); });
} }

View File

@ -98,7 +98,7 @@ export class Tab extends CompositeDisposable implements ITab {
this.droptarget = new Droptarget(this._element, { this.droptarget = new Droptarget(this._element, {
acceptedTargetZones: ['center'], acceptedTargetZones: ['center'],
canDisplayOverlay: (event) => { canDisplayOverlay: (event, position) => {
if (this.group.locked) { if (this.group.locked) {
return false; return false;
} }
@ -119,6 +119,7 @@ export class Tab extends CompositeDisposable implements ITab {
return this.group.model.canDisplayOverlay( return this.group.model.canDisplayOverlay(
event, event,
position,
DockviewDropTargets.Tab DockviewDropTargets.Tab
); );
}, },

View File

@ -42,7 +42,7 @@ export class VoidContainer extends CompositeDisposable {
this.voidDropTarget = new Droptarget(this._element, { this.voidDropTarget = new Droptarget(this._element, {
acceptedTargetZones: ['center'], acceptedTargetZones: ['center'],
canDisplayOverlay: (event) => { canDisplayOverlay: (event, position) => {
const data = getPanelData(); const data = getPanelData();
if (data && this.accessor.id === data.viewId) { if (data && this.accessor.id === data.viewId) {
@ -60,6 +60,7 @@ export class VoidContainer extends CompositeDisposable {
return group.model.canDisplayOverlay( return group.model.canDisplayOverlay(
event, event,
position,
DockviewDropTargets.Panel DockviewDropTargets.Panel
); );
}, },

View File

@ -27,7 +27,7 @@ export * from './react'; // TODO: should be conditional on whether user wants th
export { Event } from './events'; export { Event } from './events';
export { IDisposable } from './lifecycle'; export { IDisposable } from './lifecycle';
export { Position } from './dnd/droptarget'; export { Position as DropTargetDirections } from './dnd/droptarget';
export { export {
FocusEvent, FocusEvent,
PanelDimensionChangeEvent, PanelDimensionChangeEvent,

View File

@ -4,7 +4,7 @@ import {
LocalSelectionTransfer, LocalSelectionTransfer,
PaneTransfer, PaneTransfer,
} from '../dnd/dataTransfer'; } from '../dnd/dataTransfer';
import { Droptarget, DroptargetEvent, Position } from '../dnd/droptarget'; import { Droptarget, DroptargetEvent } from '../dnd/droptarget';
import { Emitter } from '../events'; import { Emitter } from '../events';
import { IDisposable } from '../lifecycle'; import { IDisposable } from '../lifecycle';
import { Orientation } from '../splitview/core/splitview'; import { Orientation } from '../splitview/core/splitview';
@ -142,16 +142,10 @@ export abstract class DraggablePaneviewPanel extends PaneviewPanel {
const fromIndex = allPanels.indexOf(existingPanel); const fromIndex = allPanels.indexOf(existingPanel);
let toIndex = containerApi.panels.indexOf(this); let toIndex = containerApi.panels.indexOf(this);
if ( if (event.position === 'left' || event.position === 'top') {
event.position === Position.Left ||
event.position === Position.Top
) {
toIndex = Math.max(0, toIndex - 1); toIndex = Math.max(0, toIndex - 1);
} }
if ( if (event.position === 'right' || event.position === 'bottom') {
event.position === Position.Right ||
event.position === Position.Bottom
) {
if (fromIndex > toIndex) { if (fromIndex > toIndex) {
toIndex++; toIndex++;
} }