mirror of
https://github.com/mathuo/dockview
synced 2025-02-09 01:45:45 +00:00
commit
bce106217a
@ -3,6 +3,7 @@ import {
|
|||||||
CompositeDisposable,
|
CompositeDisposable,
|
||||||
GridviewApi,
|
GridviewApi,
|
||||||
IGridviewPanelProps,
|
IGridviewPanelProps,
|
||||||
|
DockviewDropTarget,
|
||||||
} from 'dockview';
|
} from 'dockview';
|
||||||
import './activitybar.scss';
|
import './activitybar.scss';
|
||||||
import { useLayoutRegistry } from './registry';
|
import { useLayoutRegistry } from './registry';
|
||||||
@ -48,6 +49,10 @@ export const Activitybar = (props: IGridviewPanelProps) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="activity-bar" onClick={onOpenSidebar}>
|
<div className="activity-bar" onClick={onOpenSidebar}>
|
||||||
|
<DockviewDropTarget
|
||||||
|
validOverlays={'vertical'}
|
||||||
|
canDisplayOverlay={true}
|
||||||
|
>
|
||||||
<div className="activity-bar-item">
|
<div className="activity-bar-item">
|
||||||
<ActivitybarImage
|
<ActivitybarImage
|
||||||
url={
|
url={
|
||||||
@ -55,6 +60,7 @@ export const Activitybar = (props: IGridviewPanelProps) => {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
</DockviewDropTarget>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -8,20 +8,20 @@ export const ControlCenter = () => {
|
|||||||
|
|
||||||
const dragRef = React.useRef<HTMLDivElement>();
|
const dragRef = React.useRef<HTMLDivElement>();
|
||||||
|
|
||||||
React.useEffect(() => {
|
// React.useEffect(() => {
|
||||||
const api = registry.get<DockviewApi>('dockview');
|
// const api = registry.get<DockviewApi>('dockview');
|
||||||
const target = api.createDragTarget(
|
// const target = api.createDragTarget(
|
||||||
{ element: dragRef.current, content: 'drag me' },
|
// { element: dragRef.current, content: 'drag me' },
|
||||||
() => ({
|
// () => ({
|
||||||
id: 'yellow',
|
// id: 'yellow',
|
||||||
component: 'test_component',
|
// component: 'test_component',
|
||||||
})
|
// })
|
||||||
);
|
// );
|
||||||
|
|
||||||
return () => {
|
// return () => {
|
||||||
target.dispose();
|
// target.dispose();
|
||||||
};
|
// };
|
||||||
}, []);
|
// }, []);
|
||||||
|
|
||||||
const onDragStart = (event: React.DragEvent) => {
|
const onDragStart = (event: React.DragEvent) => {
|
||||||
event.dataTransfer.setData('text/plain', 'Panel2');
|
event.dataTransfer.setData('text/plain', 'Panel2');
|
||||||
|
@ -304,26 +304,26 @@ export const TestGrid = (props: IGridviewPanelProps) => {
|
|||||||
_api.current = event.api;
|
_api.current = event.api;
|
||||||
registry.register('dockview', api);
|
registry.register('dockview', api);
|
||||||
|
|
||||||
api.addDndHandle('text/plain', (ev) => {
|
// api.addDndHandle('text/plain', (ev) => {
|
||||||
const { event } = ev;
|
// const { event } = ev;
|
||||||
|
|
||||||
return {
|
// return {
|
||||||
id: 'yellow',
|
// id: 'yellow',
|
||||||
component: 'test_component',
|
// component: 'test_component',
|
||||||
};
|
// };
|
||||||
});
|
// });
|
||||||
|
|
||||||
api.addDndHandle('Files', (ev) => {
|
// api.addDndHandle('Files', (ev) => {
|
||||||
const { event } = ev;
|
// const { event } = ev;
|
||||||
|
|
||||||
ev.event.event.preventDefault();
|
// ev.event.event.preventDefault();
|
||||||
|
|
||||||
return {
|
// return {
|
||||||
id: Date.now().toString(),
|
// id: Date.now().toString(),
|
||||||
title: event.event.dataTransfer.files[0].name,
|
// title: event.event.dataTransfer.files[0].name,
|
||||||
component: 'test_component',
|
// component: 'test_component',
|
||||||
};
|
// };
|
||||||
});
|
// });
|
||||||
|
|
||||||
const state = localStorage.getItem('dockview');
|
const state = localStorage.getItem('dockview');
|
||||||
if (state) {
|
if (state) {
|
||||||
|
@ -6,6 +6,7 @@ import {
|
|||||||
IPaneviewPanelProps,
|
IPaneviewPanelProps,
|
||||||
CompositeDisposable,
|
CompositeDisposable,
|
||||||
PaneviewApi,
|
PaneviewApi,
|
||||||
|
PaneviewDropEvent,
|
||||||
} from 'dockview';
|
} from 'dockview';
|
||||||
import { ControlCenter } from './controlCenter';
|
import { ControlCenter } from './controlCenter';
|
||||||
import { toggleClass } from '../dom';
|
import { toggleClass } from '../dom';
|
||||||
@ -183,6 +184,10 @@ export const Sidebar = (props: IGridviewPanelProps) => {
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const onDidDrop = React.useCallback((event: PaneviewDropEvent) => {
|
||||||
|
console.log('drop', event);
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
@ -194,6 +199,7 @@ export const Sidebar = (props: IGridviewPanelProps) => {
|
|||||||
headerComponents={headerComponents}
|
headerComponents={headerComponents}
|
||||||
components={components}
|
components={components}
|
||||||
onReady={onReady}
|
onReady={onReady}
|
||||||
|
onDidDrop={onDidDrop}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -26,17 +26,16 @@ describe('droptarget', () => {
|
|||||||
let position: Position | undefined = undefined;
|
let position: Position | undefined = undefined;
|
||||||
|
|
||||||
droptarget = new Droptarget(element, {
|
droptarget = new Droptarget(element, {
|
||||||
isDisabled: () => false,
|
canDisplayOverlay: () => true,
|
||||||
isDirectional: false,
|
validOverlays: 'none',
|
||||||
id: 'test-dnd',
|
|
||||||
enableExternalDragEvents: true,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
droptarget.onDidChange((event) => {
|
droptarget.onDrop((event) => {
|
||||||
position = event.position;
|
position = event.position;
|
||||||
});
|
});
|
||||||
|
|
||||||
fireEvent.dragEnter(element);
|
fireEvent.dragEnter(element);
|
||||||
|
fireEvent.dragOver(element);
|
||||||
|
|
||||||
const target = element.querySelector(
|
const target = element.querySelector(
|
||||||
'.drop-target-dropzone'
|
'.drop-target-dropzone'
|
||||||
@ -49,17 +48,16 @@ describe('droptarget', () => {
|
|||||||
let position: Position | undefined = undefined;
|
let position: Position | undefined = undefined;
|
||||||
|
|
||||||
droptarget = new Droptarget(element, {
|
droptarget = new Droptarget(element, {
|
||||||
isDisabled: () => false,
|
canDisplayOverlay: () => true,
|
||||||
isDirectional: true,
|
validOverlays: 'all',
|
||||||
id: 'test-dnd',
|
|
||||||
enableExternalDragEvents: true,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
droptarget.onDidChange((event) => {
|
droptarget.onDrop((event) => {
|
||||||
position = event.position;
|
position = event.position;
|
||||||
});
|
});
|
||||||
|
|
||||||
fireEvent.dragEnter(element);
|
fireEvent.dragEnter(element);
|
||||||
|
fireEvent.dragOver(element);
|
||||||
|
|
||||||
const target = element.querySelector(
|
const target = element.querySelector(
|
||||||
'.drop-target-dropzone'
|
'.drop-target-dropzone'
|
||||||
@ -80,15 +78,14 @@ describe('droptarget', () => {
|
|||||||
|
|
||||||
test('default', () => {
|
test('default', () => {
|
||||||
droptarget = new Droptarget(element, {
|
droptarget = new Droptarget(element, {
|
||||||
isDisabled: () => false,
|
canDisplayOverlay: () => true,
|
||||||
isDirectional: true,
|
validOverlays: 'all',
|
||||||
id: 'test-dnd',
|
|
||||||
enableExternalDragEvents: true,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(droptarget.state).toBeUndefined();
|
expect(droptarget.state).toBeUndefined();
|
||||||
|
|
||||||
fireEvent.dragEnter(element);
|
fireEvent.dragEnter(element);
|
||||||
|
fireEvent.dragOver(element);
|
||||||
|
|
||||||
let viewQuery = element.querySelectorAll(
|
let viewQuery = element.querySelectorAll(
|
||||||
'.drop-target > .drop-target-dropzone > .drop-target-selection'
|
'.drop-target > .drop-target-dropzone > .drop-target-selection'
|
||||||
|
@ -9,6 +9,7 @@ import { Orientation } from '../../splitview/core/splitview';
|
|||||||
import { ReactPanelDeserialzier } from '../../react/deserializer';
|
import { ReactPanelDeserialzier } from '../../react/deserializer';
|
||||||
import { Position } from '../../dnd/droptarget';
|
import { Position } from '../../dnd/droptarget';
|
||||||
import { GroupviewPanel } from '../../groupview/groupviewPanel';
|
import { GroupviewPanel } from '../../groupview/groupviewPanel';
|
||||||
|
import { IGroupPanel } from '../../groupview/groupPanel';
|
||||||
class PanelContentPartTest implements IContentRenderer {
|
class PanelContentPartTest implements IContentRenderer {
|
||||||
element: HTMLElement = document.createElement('div');
|
element: HTMLElement = document.createElement('div');
|
||||||
|
|
||||||
@ -357,6 +358,7 @@ describe('dockviewComponent', () => {
|
|||||||
data: {
|
data: {
|
||||||
views: ['panel1'],
|
views: ['panel1'],
|
||||||
id: 'group-1',
|
id: 'group-1',
|
||||||
|
activeView: 'panel1',
|
||||||
},
|
},
|
||||||
size: 500,
|
size: 500,
|
||||||
},
|
},
|
||||||
|
@ -16,7 +16,7 @@ import { fireEvent } from '@testing-library/dom';
|
|||||||
import { LocalSelectionTransfer } from '../../dnd/dataTransfer';
|
import { LocalSelectionTransfer } from '../../dnd/dataTransfer';
|
||||||
import { Position } from '../../dnd/droptarget';
|
import { Position } from '../../dnd/droptarget';
|
||||||
import { GroupviewPanel } from '../../groupview/groupviewPanel';
|
import { GroupviewPanel } from '../../groupview/groupviewPanel';
|
||||||
import { GroupOptions, GroupDropEvent } from '../../groupview/groupview';
|
import { GroupOptions } from '../../groupview/groupview';
|
||||||
import { DockviewPanelApi } from '../../api/groupPanelApi';
|
import { DockviewPanelApi } from '../../api/groupPanelApi';
|
||||||
import {
|
import {
|
||||||
DefaultGroupPanelView,
|
DefaultGroupPanelView,
|
||||||
@ -294,44 +294,44 @@ describe('groupview', () => {
|
|||||||
expect(viewQuery).toBeTruthy();
|
expect(viewQuery).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('dnd', () => {
|
// test('dnd', () => {
|
||||||
const panel1 = new TestPanel('panel1', jest.fn() as any);
|
// const panel1 = new TestPanel('panel1', jest.fn() as any);
|
||||||
const panel2 = new TestPanel('panel2', jest.fn() as any);
|
// const panel2 = new TestPanel('panel2', jest.fn() as any);
|
||||||
|
|
||||||
groupview.model.openPanel(panel1);
|
// groupview.model.openPanel(panel1);
|
||||||
groupview.model.openPanel(panel2);
|
// groupview.model.openPanel(panel2);
|
||||||
|
|
||||||
const events: GroupDropEvent[] = [];
|
// const events: GroupDropEvent[] = [];
|
||||||
|
|
||||||
groupview.model.onDrop((event) => {
|
// groupview.model.onDrop((event) => {
|
||||||
events.push(event);
|
// events.push(event);
|
||||||
});
|
// });
|
||||||
|
|
||||||
const viewQuery = groupview.element.querySelectorAll(
|
// const viewQuery = groupview.element.querySelectorAll(
|
||||||
'.groupview > .tabs-and-actions-container > .tabs-container > .tab'
|
// '.groupview > .tabs-and-actions-container > .tabs-container > .tab'
|
||||||
);
|
// );
|
||||||
expect(viewQuery.length).toBe(2);
|
// expect(viewQuery.length).toBe(2);
|
||||||
|
|
||||||
LocalSelectionTransfer.getInstance().setData([], 'dockview-1');
|
// LocalSelectionTransfer.getInstance().setData([], 'dockview-1');
|
||||||
|
|
||||||
fireEvent.dragEnter(viewQuery[0]);
|
// fireEvent.dragEnter(viewQuery[0]);
|
||||||
|
|
||||||
let dropTarget = viewQuery[0].querySelector('.drop-target-dropzone');
|
// let dropTarget = viewQuery[0].querySelector('.drop-target-dropzone');
|
||||||
fireEvent.dragOver(dropTarget);
|
// fireEvent.dragOver(dropTarget);
|
||||||
fireEvent.drop(dropTarget);
|
// fireEvent.drop(dropTarget);
|
||||||
|
|
||||||
expect(events.length).toBe(1);
|
// expect(events.length).toBe(1);
|
||||||
expect(events[0].target).toBe(Position.Center);
|
// expect(events[0].target).toBe(Position.Center);
|
||||||
expect(events[0].index).toBe(0);
|
// expect(events[0].index).toBe(0);
|
||||||
|
|
||||||
fireEvent.dragEnter(viewQuery[1]);
|
// fireEvent.dragEnter(viewQuery[1]);
|
||||||
|
|
||||||
dropTarget = viewQuery[1].querySelector('.drop-target-dropzone');
|
// dropTarget = viewQuery[1].querySelector('.drop-target-dropzone');
|
||||||
fireEvent.dragOver(dropTarget);
|
// fireEvent.dragOver(dropTarget);
|
||||||
fireEvent.drop(dropTarget);
|
// fireEvent.drop(dropTarget);
|
||||||
|
|
||||||
expect(events.length).toBe(2);
|
// expect(events.length).toBe(2);
|
||||||
expect(events[1].target).toBe(Position.Center);
|
// expect(events[1].target).toBe(Position.Center);
|
||||||
expect(events[1].index).toBe(1);
|
// expect(events[1].index).toBe(1);
|
||||||
});
|
// });
|
||||||
});
|
});
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
IDockviewComponent,
|
IDockviewComponent,
|
||||||
LayoutDropEvent,
|
|
||||||
SerializedDockview,
|
SerializedDockview,
|
||||||
} from '../dockview/dockviewComponent';
|
} from '../dockview/dockviewComponent';
|
||||||
import {
|
import {
|
||||||
@ -339,19 +338,19 @@ export class DockviewApi {
|
|||||||
return this.component.addPanel(options);
|
return this.component.addPanel(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
addDndHandle(type: string, cb: (event: LayoutDropEvent) => PanelOptions) {
|
// addDndHandle(type: string, cb: (event: LayoutDropEvent) => PanelOptions) {
|
||||||
return this.component.addDndHandle(type, cb);
|
// return this.component.addDndHandle(type, cb);
|
||||||
}
|
// }
|
||||||
|
|
||||||
createDragTarget(
|
// createDragTarget(
|
||||||
target: {
|
// target: {
|
||||||
element: HTMLElement;
|
// element: HTMLElement;
|
||||||
content: string;
|
// content: string;
|
||||||
},
|
// },
|
||||||
options: (() => PanelOptions) | PanelOptions
|
// options: (() => PanelOptions) | PanelOptions
|
||||||
) {
|
// ) {
|
||||||
return this.component.createDragTarget(target, options);
|
// return this.component.createDragTarget(target, options);
|
||||||
}
|
// }
|
||||||
|
|
||||||
addEmptyGroup(options?: AddGroupOptions) {
|
addEmptyGroup(options?: AddGroupOptions) {
|
||||||
return this.component.addEmptyGroup(options);
|
return this.component.addEmptyGroup(options);
|
||||||
|
100
packages/dockview/src/dnd/abstractDragHandler.ts
Normal file
100
packages/dockview/src/dnd/abstractDragHandler.ts
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
import { getElementsByTagName } from '../dom';
|
||||||
|
import { addDisposableListener, Emitter } from '../events';
|
||||||
|
import { focusedElement } from '../focusedElement';
|
||||||
|
import { CompositeDisposable, IDisposable } from '../lifecycle';
|
||||||
|
import { DATA_KEY, LocalSelectionTransfer } from './dataTransfer';
|
||||||
|
|
||||||
|
export abstract class DragHandler extends CompositeDisposable {
|
||||||
|
private iframes: HTMLElement[] = [];
|
||||||
|
|
||||||
|
private readonly _onDragStart = new Emitter<void>();
|
||||||
|
readonly onDragStart = this._onDragStart.event;
|
||||||
|
|
||||||
|
// private activeDrag: { id: string } | undefined;
|
||||||
|
|
||||||
|
// get isDragging() {
|
||||||
|
// return !!this.activeDrag;
|
||||||
|
// }
|
||||||
|
|
||||||
|
private disposable: IDisposable | undefined;
|
||||||
|
|
||||||
|
constructor(private readonly el: HTMLElement) {
|
||||||
|
super();
|
||||||
|
this.configure();
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract getData(): IDisposable;
|
||||||
|
|
||||||
|
private configure() {
|
||||||
|
this.addDisposables(
|
||||||
|
addDisposableListener(this.el, 'dragstart', (event) => {
|
||||||
|
this.iframes = [
|
||||||
|
...getElementsByTagName('iframe'),
|
||||||
|
...getElementsByTagName('webview'),
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const iframe of this.iframes) {
|
||||||
|
iframe.style.pointerEvents = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
this.el.classList.add('dragged');
|
||||||
|
setTimeout(() => this.el.classList.remove('dragged'), 0);
|
||||||
|
|
||||||
|
// this.activeDrag = this.getData();
|
||||||
|
this.disposable?.dispose();
|
||||||
|
this.disposable = this.getData();
|
||||||
|
|
||||||
|
// if (event.dataTransfer) {
|
||||||
|
// event.dataTransfer.setData(DATA_KEY, stringifiedData);
|
||||||
|
// event.dataTransfer.effectAllowed = 'move';
|
||||||
|
// }
|
||||||
|
}),
|
||||||
|
addDisposableListener(this.el, 'dragend', (ev) => {
|
||||||
|
for (const iframe of this.iframes) {
|
||||||
|
iframe.style.pointerEvents = 'auto';
|
||||||
|
}
|
||||||
|
this.iframes = [];
|
||||||
|
|
||||||
|
this.disposable?.dispose();
|
||||||
|
this.disposable = undefined;
|
||||||
|
|
||||||
|
// drop events fire before dragend so we can remove this safely
|
||||||
|
// LocalSelectionTransfer.getInstance().clearData(this.activeDrag);
|
||||||
|
// this.activeDrag = undefined;
|
||||||
|
}),
|
||||||
|
addDisposableListener(this.el, 'mousedown', (event) => {
|
||||||
|
if (event.defaultPrevented) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* TODO: alternative to stopPropagation
|
||||||
|
*
|
||||||
|
* I need to stop the event propagation here since otherwise it'll be intercepted by event handlers
|
||||||
|
* on the tabs-container. I cannot use event.preventDefault() since I need the on DragStart event to occur
|
||||||
|
*/
|
||||||
|
event.stopPropagation();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* //TODO mousedown focusing with draggable element (is there a better approach?)
|
||||||
|
*
|
||||||
|
* this mousedown event wants to focus the tab itself but if we call preventDefault()
|
||||||
|
* this would also prevent the dragStart event from firing. To get around this we propagate
|
||||||
|
* the onChanged event during the next tick of the event-loop, allowing the tab element to become
|
||||||
|
* focused on this tick and ensuring the dragstart event is not interrupted
|
||||||
|
*/
|
||||||
|
|
||||||
|
const oldFocus = focusedElement.element as HTMLElement;
|
||||||
|
setTimeout(() => {
|
||||||
|
oldFocus.focus();
|
||||||
|
// this._onChanged.fire({ kind: MouseEventKind.CLICK, event });
|
||||||
|
}, 0);
|
||||||
|
}),
|
||||||
|
addDisposableListener(this.el, 'contextmenu', (event) => {
|
||||||
|
// this._onChanged.fire({
|
||||||
|
// kind: MouseEventKind.CONTEXT_MENU,
|
||||||
|
// event,
|
||||||
|
// });
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,6 @@
|
|||||||
import { PanelOptions } from '../dockview/options';
|
import { PanelOptions } from '../dockview/options';
|
||||||
import { tryParseJSON } from '../json';
|
import { tryParseJSON } from '../json';
|
||||||
|
import { PanelTransfer, PaneTransfer } from './droptarget';
|
||||||
|
|
||||||
export const DATA_KEY = 'splitview/transfer';
|
export const DATA_KEY = 'splitview/transfer';
|
||||||
|
|
||||||
@ -12,7 +13,7 @@ export const isPanelTransferEvent = (event: DragEvent) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export enum DragType {
|
export enum DragType {
|
||||||
ITEM = 'group_drag',
|
DOCKVIEW_TAB = 'dockview_tab',
|
||||||
EXTERNAL = 'external_group_drag',
|
EXTERNAL = 'external_group_drag',
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -30,7 +31,7 @@ export type DataObject = DragItem | ExternalDragItem;
|
|||||||
* dragging a tab component
|
* dragging a tab component
|
||||||
*/
|
*/
|
||||||
export const isTabDragEvent = (data: any): data is DragItem => {
|
export const isTabDragEvent = (data: any): data is DragItem => {
|
||||||
return data.type === DragType.ITEM;
|
return data.type === DragType.DOCKVIEW_TAB;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -102,3 +103,25 @@ export class LocalSelectionTransfer<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getPanelData(): PanelTransfer | undefined {
|
||||||
|
const panelTransfer = LocalSelectionTransfer.getInstance<PanelTransfer>();
|
||||||
|
const isPanelEvent = panelTransfer.hasData(PanelTransfer.prototype);
|
||||||
|
|
||||||
|
if (!isPanelEvent) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return panelTransfer.getData(PanelTransfer.prototype)![0];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getPaneData(): PaneTransfer | undefined {
|
||||||
|
const paneTransfer = LocalSelectionTransfer.getInstance<PaneTransfer>();
|
||||||
|
const isPanelEvent = paneTransfer.hasData(PaneTransfer.prototype);
|
||||||
|
|
||||||
|
if (!isPanelEvent) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return paneTransfer.getData(PaneTransfer.prototype)![0];
|
||||||
|
}
|
||||||
|
181
packages/dockview/src/dnd/dnd.ts
Normal file
181
packages/dockview/src/dnd/dnd.ts
Normal file
@ -0,0 +1,181 @@
|
|||||||
|
import { addDisposableListener, Emitter } from '../events';
|
||||||
|
import { CompositeDisposable, IDisposable } from '../lifecycle';
|
||||||
|
import { LocalSelectionTransfer } from './dataTransfer';
|
||||||
|
|
||||||
|
export interface IDragAndDropObserverCallbacks {
|
||||||
|
onDragEnter: (e: DragEvent) => void;
|
||||||
|
onDragLeave: (e: DragEvent) => void;
|
||||||
|
onDrop: (e: DragEvent) => void;
|
||||||
|
onDragEnd: (e: DragEvent) => void;
|
||||||
|
|
||||||
|
onDragOver?: (e: DragEvent) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class DragAndDropObserver extends CompositeDisposable {
|
||||||
|
// A helper to fix issues with repeated DRAG_ENTER / DRAG_LEAVE
|
||||||
|
// calls see https://github.com/microsoft/vscode/issues/14470
|
||||||
|
// when the element has child elements where the events are fired
|
||||||
|
// repeadedly.
|
||||||
|
private counter = 0;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private element: HTMLElement,
|
||||||
|
private callbacks: IDragAndDropObserverCallbacks
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.registerListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
private registerListeners(): void {
|
||||||
|
this.addDisposables(
|
||||||
|
addDisposableListener(this.element, 'dragenter', (e: DragEvent) => {
|
||||||
|
this.counter++;
|
||||||
|
|
||||||
|
this.callbacks.onDragEnter(e);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
this.addDisposables(
|
||||||
|
addDisposableListener(this.element, 'dragover', (e: DragEvent) => {
|
||||||
|
e.preventDefault(); // needed so that the drop event fires (https://stackoverflow.com/questions/21339924/drop-event-not-firing-in-chrome)
|
||||||
|
|
||||||
|
if (this.callbacks.onDragOver) {
|
||||||
|
this.callbacks.onDragOver(e);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
this.addDisposables(
|
||||||
|
addDisposableListener(this.element, 'dragleave', (e: DragEvent) => {
|
||||||
|
this.counter--;
|
||||||
|
|
||||||
|
if (this.counter === 0) {
|
||||||
|
this.callbacks.onDragLeave(e);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
this.addDisposables(
|
||||||
|
addDisposableListener(this.element, 'dragend', (e: DragEvent) => {
|
||||||
|
this.counter = 0;
|
||||||
|
this.callbacks.onDragEnd(e);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
this.addDisposables(
|
||||||
|
addDisposableListener(this.element, 'drop', (e: DragEvent) => {
|
||||||
|
this.counter = 0;
|
||||||
|
this.callbacks.onDrop(e);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IDraggedCompositeData {
|
||||||
|
eventData: DragEvent;
|
||||||
|
dragAndDropData: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ICompositeDragAndDropObserverCallbacks {
|
||||||
|
onDragEnter?: (e: IDraggedCompositeData) => void;
|
||||||
|
onDragLeave?: (e: IDraggedCompositeData) => void;
|
||||||
|
onDrop?: (e: IDraggedCompositeData) => void;
|
||||||
|
onDragOver?: (e: IDraggedCompositeData) => void;
|
||||||
|
onDragStart?: (e: IDraggedCompositeData) => void;
|
||||||
|
onDragEnd?: (e: IDraggedCompositeData) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
class DockviewIdentifier<T = {}> {
|
||||||
|
constructor(private readonly data: T) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class DragAndDrop extends CompositeDisposable {
|
||||||
|
private _onDragStart = new Emitter<any>();
|
||||||
|
private _onDragEnd = new Emitter<any>();
|
||||||
|
private static _instance: DragAndDrop | undefined;
|
||||||
|
static get INSTANCE(): DragAndDrop {
|
||||||
|
if (!DragAndDrop._instance) {
|
||||||
|
DragAndDrop._instance = new DragAndDrop();
|
||||||
|
}
|
||||||
|
return DragAndDrop._instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
private transferData =
|
||||||
|
LocalSelectionTransfer.getInstance<DockviewIdentifier>();
|
||||||
|
|
||||||
|
private constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.addDisposables(this._onDragStart, this._onDragEnd);
|
||||||
|
}
|
||||||
|
|
||||||
|
registerTarget(
|
||||||
|
element: HTMLElement,
|
||||||
|
callbacks: ICompositeDragAndDropObserverCallbacks
|
||||||
|
): IDisposable {
|
||||||
|
const disposables = new CompositeDisposable();
|
||||||
|
|
||||||
|
disposables.addDisposables(
|
||||||
|
new DragAndDropObserver(element, {
|
||||||
|
onDragEnd: (e) => {
|
||||||
|
// no-op
|
||||||
|
},
|
||||||
|
onDragEnter: (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
},
|
||||||
|
onDragLeave: (e) => {
|
||||||
|
//
|
||||||
|
},
|
||||||
|
onDrop: (e) => {
|
||||||
|
//
|
||||||
|
},
|
||||||
|
onDragOver: (e) => {
|
||||||
|
//
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
return disposables;
|
||||||
|
}
|
||||||
|
|
||||||
|
registerDraggable(
|
||||||
|
element: HTMLElement,
|
||||||
|
draggedItemProvider: () => { type: string; id: string },
|
||||||
|
callbacks: ICompositeDragAndDropObserverCallbacks
|
||||||
|
): IDisposable {
|
||||||
|
element.draggable = true;
|
||||||
|
|
||||||
|
const disposables = new CompositeDisposable();
|
||||||
|
|
||||||
|
disposables.addDisposables(
|
||||||
|
addDisposableListener(element, 'dragstart', (e) => {
|
||||||
|
this._onDragStart.fire({ event: e });
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
disposables.addDisposables(
|
||||||
|
new DragAndDropObserver(element, {
|
||||||
|
onDragEnd: (e) => {
|
||||||
|
// no-op
|
||||||
|
},
|
||||||
|
onDragEnter: (e) => {
|
||||||
|
//
|
||||||
|
},
|
||||||
|
onDragLeave: (e) => {
|
||||||
|
//
|
||||||
|
},
|
||||||
|
onDrop: (e) => {
|
||||||
|
//
|
||||||
|
},
|
||||||
|
onDragOver: (e) => {
|
||||||
|
//
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
return disposables;
|
||||||
|
}
|
||||||
|
}
|
@ -12,6 +12,7 @@
|
|||||||
> .drop-target-selection {
|
> .drop-target-selection {
|
||||||
position: relative;
|
position: relative;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
|
box-sizing: border-box;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
background-color: var(--dv-drag-over-background-color);
|
background-color: var(--dv-drag-over-background-color);
|
||||||
@ -33,6 +34,19 @@
|
|||||||
&.bottom {
|
&.bottom {
|
||||||
height: 50%;
|
height: 50%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.small-top {
|
||||||
|
border-top: 1px solid var(--dv-drag-over-border-color);
|
||||||
|
}
|
||||||
|
&.small-bottom {
|
||||||
|
border-bottom: 1px solid var(--dv-drag-over-border-color);
|
||||||
|
}
|
||||||
|
&.small-left {
|
||||||
|
border-left: 1px solid var(--dv-drag-over-border-color);
|
||||||
|
}
|
||||||
|
&.small-right {
|
||||||
|
border-right: 1px solid var(--dv-drag-over-border-color);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,37 @@
|
|||||||
import { toggleClass } from '../dom';
|
import { toggleClass } from '../dom';
|
||||||
import { Emitter, Event } from '../events';
|
import { Emitter, Event } from '../events';
|
||||||
import { LocalSelectionTransfer } from './dataTransfer';
|
import { CompositeDisposable } from '../lifecycle';
|
||||||
|
import { DragAndDropObserver } from './dnd';
|
||||||
|
|
||||||
|
export interface DroptargetEvent {
|
||||||
|
position: Position;
|
||||||
|
event: DragEvent;
|
||||||
|
}
|
||||||
|
|
||||||
|
class TransferObject {
|
||||||
|
constructor() {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class PanelTransfer extends TransferObject {
|
||||||
|
constructor(
|
||||||
|
public readonly viewId: string,
|
||||||
|
public readonly groupId: string,
|
||||||
|
public readonly panelId: string
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class PaneTransfer extends TransferObject {
|
||||||
|
constructor(
|
||||||
|
public readonly viewId: string,
|
||||||
|
public readonly paneId: string
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export enum Position {
|
export enum Position {
|
||||||
Top = 'Top',
|
Top = 'Top',
|
||||||
@ -15,93 +46,71 @@ export interface DroptargetEvent {
|
|||||||
event: DragEvent;
|
event: DragEvent;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Droptarget {
|
export type DropTargetDirections = 'vertical' | 'horizontal' | 'all' | 'none';
|
||||||
|
|
||||||
|
function isBooleanValue(
|
||||||
|
canDisplayOverlay: CanDisplayOverlay
|
||||||
|
): canDisplayOverlay is boolean {
|
||||||
|
return typeof canDisplayOverlay === 'boolean';
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CanDisplayOverlay = boolean | ((dragEvent: DragEvent) => boolean);
|
||||||
|
|
||||||
|
export class Droptarget extends CompositeDisposable {
|
||||||
private target: HTMLElement | undefined;
|
private target: HTMLElement | undefined;
|
||||||
private overlay: HTMLElement | undefined;
|
private overlay: HTMLElement | undefined;
|
||||||
private _state: Position | undefined;
|
private _state: Position | undefined;
|
||||||
|
|
||||||
private readonly _onDidChange = new Emitter<DroptargetEvent>();
|
private readonly _onDrop = new Emitter<DroptargetEvent>();
|
||||||
readonly onDidChange: Event<DroptargetEvent> = this._onDidChange.event;
|
readonly onDrop: Event<DroptargetEvent> = this._onDrop.event;
|
||||||
|
|
||||||
get state() {
|
get state() {
|
||||||
return this._state;
|
return this._state;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
set validOverlays(value: DropTargetDirections) {
|
||||||
|
this.options.validOverlays = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
set canDisplayOverlay(value: CanDisplayOverlay) {
|
||||||
|
this.options.canDisplayOverlay = value;
|
||||||
|
}
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private element: HTMLElement,
|
private readonly element: HTMLElement,
|
||||||
private options: {
|
private readonly options: {
|
||||||
isDisabled: () => boolean;
|
canDisplayOverlay: CanDisplayOverlay;
|
||||||
isDirectional: boolean;
|
validOverlays: DropTargetDirections;
|
||||||
id: string;
|
|
||||||
enableExternalDragEvents?: boolean;
|
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
this.element.addEventListener('dragenter', this.onDragEnter);
|
super();
|
||||||
}
|
|
||||||
|
|
||||||
public dispose() {
|
this.addDisposables(
|
||||||
this._onDidChange.dispose();
|
new DragAndDropObserver(this.element, {
|
||||||
this.removeDropTarget();
|
onDragEnter: (e) => undefined,
|
||||||
this.element.removeEventListener('dragenter', this.onDragEnter);
|
onDragOver: (e) => {
|
||||||
|
if (isBooleanValue(this.options.canDisplayOverlay)) {
|
||||||
|
if (!this.options.canDisplayOverlay) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
} else if (!this.options.canDisplayOverlay(e)) {
|
||||||
private onDragEnter = (event: DragEvent) => {
|
|
||||||
if (
|
|
||||||
!this.options.enableExternalDragEvents &&
|
|
||||||
!LocalSelectionTransfer.getInstance().hasData(this.options.id)
|
|
||||||
) {
|
|
||||||
console.debug('[droptarget] invalid event');
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.options.isDisabled()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
event.preventDefault();
|
|
||||||
if (!this.target) {
|
if (!this.target) {
|
||||||
console.debug('[droptarget] created');
|
console.debug('[droptarget] created');
|
||||||
this.target = document.createElement('div');
|
this.target = document.createElement('div');
|
||||||
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 = Position.Center;
|
||||||
this.target.addEventListener('dragover', this.onDragOver);
|
|
||||||
this.target.addEventListener('dragleave', this.onDragLeave);
|
|
||||||
this.target.addEventListener('drop', this.onDrop);
|
|
||||||
this.target.appendChild(this.overlay);
|
this.target.appendChild(this.overlay);
|
||||||
|
|
||||||
this.element.classList.add('drop-target');
|
this.element.classList.add('drop-target');
|
||||||
this.element.append(this.target);
|
this.element.append(this.target);
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
private onDrop = (event: DragEvent) => {
|
if (this.options.validOverlays === 'none') {
|
||||||
if (
|
|
||||||
!this.options.enableExternalDragEvents &&
|
|
||||||
!LocalSelectionTransfer.getInstance().hasData(this.options.id)
|
|
||||||
) {
|
|
||||||
console.debug('[dragtarget] invalid');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const state = this._state;
|
|
||||||
|
|
||||||
console.debug('[dragtarget] drop');
|
|
||||||
this.removeDropTarget();
|
|
||||||
|
|
||||||
if (event.defaultPrevented) {
|
|
||||||
console.debug('[dragtarget] defaultPrevented');
|
|
||||||
} else if (state) {
|
|
||||||
this._onDidChange.fire({ position: state, event });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private onDragOver = (event: DragEvent) => {
|
|
||||||
event.preventDefault();
|
|
||||||
|
|
||||||
if (!this.options.isDirectional) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -116,20 +125,53 @@ export class Droptarget {
|
|||||||
return; // avoid div!0
|
return; // avoid div!0
|
||||||
}
|
}
|
||||||
|
|
||||||
const x = event.offsetX;
|
const x = e.offsetX;
|
||||||
const y = event.offsetY;
|
const y = e.offsetY;
|
||||||
const xp = (100 * x) / width;
|
const xp = (100 * x) / width;
|
||||||
const yp = (100 * y) / height;
|
const yp = (100 * y) / height;
|
||||||
|
|
||||||
const isRight = xp > 80;
|
let isRight = false;
|
||||||
const isLeft = xp < 20;
|
let isLeft = false;
|
||||||
const isTop = !isRight && !isLeft && yp < 20;
|
let isTop = false;
|
||||||
const isBottom = !isRight && !isLeft && yp > 80;
|
let isBottom = false;
|
||||||
|
|
||||||
toggleClass(this.overlay, 'right', isRight);
|
switch (this.options.validOverlays) {
|
||||||
toggleClass(this.overlay, 'left', isLeft);
|
case 'all':
|
||||||
toggleClass(this.overlay, 'top', isTop);
|
isRight = xp > 80;
|
||||||
toggleClass(this.overlay, 'bottom', isBottom);
|
isLeft = xp < 20;
|
||||||
|
isTop = !isRight && !isLeft && yp < 20;
|
||||||
|
isBottom = !isRight && !isLeft && yp > 80;
|
||||||
|
break;
|
||||||
|
case 'vertical':
|
||||||
|
isTop = yp < 50;
|
||||||
|
isBottom = yp >= 50;
|
||||||
|
break;
|
||||||
|
case 'horizontal':
|
||||||
|
isLeft = xp < 50;
|
||||||
|
isRight = xp >= 50;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isSmallX = width < 100;
|
||||||
|
const isSmallY = height < 100;
|
||||||
|
|
||||||
|
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);
|
||||||
|
toggleClass(
|
||||||
|
this.overlay,
|
||||||
|
'small-bottom',
|
||||||
|
isSmallY && isBottom
|
||||||
|
);
|
||||||
|
|
||||||
if (isRight) {
|
if (isRight) {
|
||||||
this._state = Position.Right;
|
this._state = Position.Right;
|
||||||
@ -142,19 +184,38 @@ export class Droptarget {
|
|||||||
} else {
|
} else {
|
||||||
this._state = Position.Center;
|
this._state = Position.Center;
|
||||||
}
|
}
|
||||||
};
|
},
|
||||||
|
onDragLeave: (e) => {
|
||||||
private onDragLeave = (event: DragEvent) => {
|
|
||||||
console.debug('[droptarget] leave');
|
|
||||||
this.removeDropTarget();
|
this.removeDropTarget();
|
||||||
};
|
},
|
||||||
|
onDragEnd: (e) => {
|
||||||
|
this.removeDropTarget();
|
||||||
|
},
|
||||||
|
onDrop: (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
const state = this._state;
|
||||||
|
|
||||||
|
console.debug('[dragtarget] drop');
|
||||||
|
this.removeDropTarget();
|
||||||
|
|
||||||
|
if (state) {
|
||||||
|
this._onDrop.fire({ position: state, event: e });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public dispose() {
|
||||||
|
this._onDrop.dispose();
|
||||||
|
this.removeDropTarget();
|
||||||
|
}
|
||||||
|
|
||||||
private removeDropTarget() {
|
private removeDropTarget() {
|
||||||
if (this.target) {
|
if (this.target) {
|
||||||
this._state = undefined;
|
this._state = undefined;
|
||||||
this.target.removeEventListener('dragover', this.onDragOver);
|
|
||||||
this.target.removeEventListener('dragleave', this.onDragLeave);
|
|
||||||
this.target.removeEventListener('drop', this.onDrop);
|
|
||||||
this.element.removeChild(this.target);
|
this.element.removeChild(this.target);
|
||||||
this.target = undefined;
|
this.target = undefined;
|
||||||
this.element.classList.remove('drop-target');
|
this.element.classList.remove('drop-target');
|
||||||
|
@ -1,7 +1,4 @@
|
|||||||
.tab {
|
.dragged {
|
||||||
flex-shrink: 0;
|
|
||||||
|
|
||||||
&.dragged {
|
|
||||||
transform: translate3d(
|
transform: translate3d(
|
||||||
0px,
|
0px,
|
||||||
0px,
|
0px,
|
||||||
@ -9,6 +6,17 @@
|
|||||||
); /* forces tab to be drawn on a separate layer (see https://github.com/microsoft/vscode/issues/18733) */
|
); /* forces tab to be drawn on a separate layer (see https://github.com/microsoft/vscode/issues/18733) */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tab {
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
// &.dragged {
|
||||||
|
// transform: translate3d(
|
||||||
|
// 0px,
|
||||||
|
// 0px,
|
||||||
|
// 0px
|
||||||
|
// ); /* forces tab to be drawn on a separate layer (see https://github.com/microsoft/vscode/issues/18733) */
|
||||||
|
// }
|
||||||
|
|
||||||
&.dragging {
|
&.dragging {
|
||||||
.tab-action {
|
.tab-action {
|
||||||
background-color: var(--dv-activegroup-visiblepanel-tab-color);
|
background-color: var(--dv-activegroup-visiblepanel-tab-color);
|
||||||
|
@ -7,13 +7,8 @@ import { Position } from '../dnd/droptarget';
|
|||||||
import { tail, sequenceEquals } from '../array';
|
import { tail, sequenceEquals } from '../array';
|
||||||
import { GroupviewPanelState, IGroupPanel } from '../groupview/groupPanel';
|
import { GroupviewPanelState, IGroupPanel } from '../groupview/groupPanel';
|
||||||
import { DockviewGroupPanel } from './dockviewGroupPanel';
|
import { DockviewGroupPanel } from './dockviewGroupPanel';
|
||||||
import {
|
import { CompositeDisposable, IValueDisposable } from '../lifecycle';
|
||||||
CompositeDisposable,
|
import { Event, Emitter } from '../events';
|
||||||
IDisposable,
|
|
||||||
IValueDisposable,
|
|
||||||
MutableDisposable,
|
|
||||||
} from '../lifecycle';
|
|
||||||
import { Event, Emitter, addDisposableListener } from '../events';
|
|
||||||
import { Watermark } from './components/watermark/watermark';
|
import { Watermark } from './components/watermark/watermark';
|
||||||
import { timeoutAsPromise } from '../async';
|
import { timeoutAsPromise } from '../async';
|
||||||
import {
|
import {
|
||||||
@ -28,16 +23,10 @@ import { createComponent } from '../panel/componentFactory';
|
|||||||
import {
|
import {
|
||||||
AddGroupOptions,
|
AddGroupOptions,
|
||||||
AddPanelOptions,
|
AddPanelOptions,
|
||||||
PanelOptions,
|
|
||||||
DockviewOptions as DockviewComponentOptions,
|
DockviewOptions as DockviewComponentOptions,
|
||||||
MovementOptions,
|
MovementOptions,
|
||||||
TabContextMenuEvent,
|
TabContextMenuEvent,
|
||||||
} from './options';
|
} from './options';
|
||||||
import {
|
|
||||||
DATA_KEY,
|
|
||||||
DragType,
|
|
||||||
LocalSelectionTransfer,
|
|
||||||
} from '../dnd/dataTransfer';
|
|
||||||
import {
|
import {
|
||||||
BaseGrid,
|
BaseGrid,
|
||||||
IBaseGrid,
|
IBaseGrid,
|
||||||
@ -50,7 +39,6 @@ import { Orientation } from '../splitview/core/splitview';
|
|||||||
import { DefaultTab } from './components/tab/defaultTab';
|
import { DefaultTab } from './components/tab/defaultTab';
|
||||||
import {
|
import {
|
||||||
GroupChangeKind,
|
GroupChangeKind,
|
||||||
GroupDropEvent,
|
|
||||||
GroupOptions,
|
GroupOptions,
|
||||||
GroupPanelViewState,
|
GroupPanelViewState,
|
||||||
} from '../groupview/groupview';
|
} from '../groupview/groupview';
|
||||||
@ -113,17 +101,17 @@ export interface IDockviewComponent extends IBaseGrid<GroupviewPanel> {
|
|||||||
onTabContextMenu: Event<TabContextMenuEvent>;
|
onTabContextMenu: Event<TabContextMenuEvent>;
|
||||||
moveToNext(options?: MovementOptions): void;
|
moveToNext(options?: MovementOptions): void;
|
||||||
moveToPrevious(options?: MovementOptions): void;
|
moveToPrevious(options?: MovementOptions): void;
|
||||||
createDragTarget(
|
// createDragTarget(
|
||||||
target: {
|
// target: {
|
||||||
element: HTMLElement;
|
// element: HTMLElement;
|
||||||
content: string;
|
// content: string;
|
||||||
},
|
// },
|
||||||
options: (() => PanelOptions) | PanelOptions
|
// options: (() => PanelOptions) | PanelOptions
|
||||||
): IDisposable;
|
// ): IDisposable;
|
||||||
addDndHandle(
|
// addDndHandle(
|
||||||
type: string,
|
// type: string,
|
||||||
cb: (event: LayoutDropEvent) => PanelOptions
|
// cb: (event: LayoutDropEvent) => PanelOptions
|
||||||
): void;
|
// ): void;
|
||||||
setActivePanel(panel: IGroupPanel): void;
|
setActivePanel(panel: IGroupPanel): void;
|
||||||
focus(): void;
|
focus(): void;
|
||||||
toJSON(): SerializedDockview;
|
toJSON(): SerializedDockview;
|
||||||
@ -131,10 +119,6 @@ export interface IDockviewComponent extends IBaseGrid<GroupviewPanel> {
|
|||||||
onDidLayoutChange: Event<void>;
|
onDidLayoutChange: Event<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LayoutDropEvent {
|
|
||||||
event: GroupDropEvent;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class DockviewComponent
|
export class DockviewComponent
|
||||||
extends BaseGrid<GroupviewPanel>
|
extends BaseGrid<GroupviewPanel>
|
||||||
implements IDockviewComponent
|
implements IDockviewComponent
|
||||||
@ -153,13 +137,13 @@ export class DockviewComponent
|
|||||||
readonly onTabContextMenu: Event<TabContextMenuEvent> =
|
readonly onTabContextMenu: Event<TabContextMenuEvent> =
|
||||||
this._onTabContextMenu.event;
|
this._onTabContextMenu.event;
|
||||||
// everything else
|
// everything else
|
||||||
private drag = new MutableDisposable();
|
// private drag = new MutableDisposable();
|
||||||
private _deserializer: IPanelDeserializer | undefined;
|
private _deserializer: IPanelDeserializer | undefined;
|
||||||
private panelState: State = {};
|
private panelState: State = {};
|
||||||
private registry = new Map<
|
// private registry = new Map<
|
||||||
string,
|
// string,
|
||||||
(event: LayoutDropEvent) => PanelOptions
|
// (event: LayoutDropEvent) => PanelOptions
|
||||||
>();
|
// >();
|
||||||
private _api: DockviewApi;
|
private _api: DockviewApi;
|
||||||
private _options: DockviewComponentOptions;
|
private _options: DockviewComponentOptions;
|
||||||
|
|
||||||
@ -274,12 +258,12 @@ export class DockviewComponent
|
|||||||
this.layout(this.gridview.width, this.gridview.height, true);
|
this.layout(this.gridview.width, this.gridview.height, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
addDndHandle(
|
// addDndHandle(
|
||||||
type: string,
|
// type: string,
|
||||||
cb: (event: LayoutDropEvent) => PanelOptions
|
// cb: (event: LayoutDropEvent) => PanelOptions
|
||||||
): void {
|
// ): void {
|
||||||
this.registry.set(type, cb);
|
// this.registry.set(type, cb);
|
||||||
}
|
// }
|
||||||
|
|
||||||
focus(): void {
|
focus(): void {
|
||||||
this.activeGroup?.focus();
|
this.activeGroup?.focus();
|
||||||
@ -289,57 +273,57 @@ export class DockviewComponent
|
|||||||
return this.panels.get(id)?.value;
|
return this.panels.get(id)?.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
createDragTarget(
|
// createDragTarget(
|
||||||
target: {
|
// target: {
|
||||||
element: HTMLElement;
|
// element: HTMLElement;
|
||||||
content: string;
|
// content: string;
|
||||||
},
|
// },
|
||||||
options: (() => PanelOptions) | PanelOptions
|
// options: (() => PanelOptions) | PanelOptions
|
||||||
): IDisposable {
|
// ): IDisposable {
|
||||||
return new CompositeDisposable(
|
// return new CompositeDisposable(
|
||||||
addDisposableListener(target.element, 'dragstart', (event) => {
|
// addDisposableListener(target.element, 'dragstart', (event) => {
|
||||||
if (!event.dataTransfer) {
|
// if (!event.dataTransfer) {
|
||||||
throw new Error('unsupported');
|
// throw new Error('unsupported');
|
||||||
}
|
// }
|
||||||
|
|
||||||
const panelOptions =
|
// const panelOptions =
|
||||||
typeof options === 'function' ? options() : options;
|
// typeof options === 'function' ? options() : options;
|
||||||
|
|
||||||
const panel = this.panels.get(panelOptions.id)?.value;
|
// const panel = this.panels.get(panelOptions.id)?.value;
|
||||||
if (panel) {
|
// if (panel) {
|
||||||
this.drag.value = panel.group!.model.startActiveDrag(panel);
|
// this.drag.value = panel.group!.model.startActiveDrag(panel);
|
||||||
}
|
// }
|
||||||
|
|
||||||
const data = JSON.stringify({
|
// const data = JSON.stringify({
|
||||||
type: DragType.EXTERNAL,
|
// type: DragType.EXTERNAL,
|
||||||
...panelOptions,
|
// ...panelOptions,
|
||||||
});
|
// });
|
||||||
|
|
||||||
LocalSelectionTransfer.getInstance().setData([data], this.id);
|
// LocalSelectionTransfer.getInstance().setData([data], this.id);
|
||||||
|
|
||||||
event.dataTransfer.effectAllowed = 'move';
|
// event.dataTransfer.effectAllowed = 'move';
|
||||||
|
|
||||||
const dragImage = document.createElement('div');
|
// const dragImage = document.createElement('div');
|
||||||
dragImage.textContent = target.content;
|
// dragImage.textContent = target.content;
|
||||||
dragImage.classList.add('custom-dragging');
|
// dragImage.classList.add('custom-dragging');
|
||||||
|
|
||||||
document.body.appendChild(dragImage);
|
// document.body.appendChild(dragImage);
|
||||||
event.dataTransfer.setDragImage(
|
// event.dataTransfer.setDragImage(
|
||||||
dragImage,
|
// dragImage,
|
||||||
event.offsetX,
|
// event.offsetX,
|
||||||
event.offsetY
|
// event.offsetY
|
||||||
);
|
// );
|
||||||
setTimeout(() => document.body.removeChild(dragImage), 0);
|
// setTimeout(() => document.body.removeChild(dragImage), 0);
|
||||||
|
|
||||||
event.dataTransfer.setData(DATA_KEY, data);
|
// event.dataTransfer.setData(DATA_KEY, data);
|
||||||
}),
|
// }),
|
||||||
addDisposableListener(this.element, 'dragend', (ev) => {
|
// addDisposableListener(this.element, 'dragend', (ev) => {
|
||||||
// drop events fire before dragend so we can remove this safely
|
// // drop events fire before dragend so we can remove this safely
|
||||||
LocalSelectionTransfer.getInstance().clearData(this.id);
|
// LocalSelectionTransfer.getInstance().clearData(this.id);
|
||||||
this.drag.dispose();
|
// this.drag.dispose();
|
||||||
})
|
// })
|
||||||
);
|
// );
|
||||||
}
|
// }
|
||||||
|
|
||||||
setActivePanel(panel: IGroupPanel): void {
|
setActivePanel(panel: IGroupPanel): void {
|
||||||
if (!panel.group) {
|
if (!panel.group) {
|
||||||
@ -759,47 +743,6 @@ export class DockviewComponent
|
|||||||
}),
|
}),
|
||||||
view.model.onDidGroupChange((event) => {
|
view.model.onDidGroupChange((event) => {
|
||||||
this._onGridEvent.fire(event);
|
this._onGridEvent.fire(event);
|
||||||
}),
|
|
||||||
view.model.onDrop((event) => {
|
|
||||||
const dragEvent = event.event;
|
|
||||||
const dataTransfer = dragEvent.dataTransfer;
|
|
||||||
|
|
||||||
if (!dataTransfer) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dataTransfer.types.length === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const cb = this.registry.get(dataTransfer.types[0]);
|
|
||||||
|
|
||||||
if (!cb) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const panelOptions = cb({ event });
|
|
||||||
|
|
||||||
let panel = this.getGroupPanel(panelOptions.id);
|
|
||||||
|
|
||||||
if (!panel) {
|
|
||||||
panel = this._addPanel(panelOptions);
|
|
||||||
}
|
|
||||||
|
|
||||||
const groupId = panel.group?.id;
|
|
||||||
|
|
||||||
if (!groupId) {
|
|
||||||
throw new Error(
|
|
||||||
`Panel ${panel.id} has no associated group`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.moveGroupOrPanel(
|
|
||||||
view,
|
|
||||||
groupId,
|
|
||||||
panel.id,
|
|
||||||
event.target,
|
|
||||||
event.index
|
|
||||||
);
|
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -555,9 +555,8 @@ export class Gridview implements IDisposable {
|
|||||||
|
|
||||||
let newSiblingSize: number | Sizing = 0;
|
let newSiblingSize: number | Sizing = 0;
|
||||||
|
|
||||||
const newSiblingCachedVisibleSize = grandParent.getChildCachedVisibleSize(
|
const newSiblingCachedVisibleSize =
|
||||||
parentIndex
|
grandParent.getChildCachedVisibleSize(parentIndex);
|
||||||
);
|
|
||||||
if (typeof newSiblingCachedVisibleSize === 'number') {
|
if (typeof newSiblingCachedVisibleSize === 'number') {
|
||||||
newSiblingSize = Sizing.Invisible(newSiblingCachedVisibleSize);
|
newSiblingSize = Sizing.Invisible(newSiblingCachedVisibleSize);
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,8 @@
|
|||||||
import { DockviewApi } from '../api/component.api';
|
import { DockviewApi } from '../api/component.api';
|
||||||
import { timeoutAsPromise } from '../async';
|
import { timeoutAsPromise } from '../async';
|
||||||
import {
|
import { getPanelData } from '../dnd/dataTransfer';
|
||||||
extractData,
|
import { Position } from '../dnd/droptarget';
|
||||||
isCustomDragEvent,
|
import { Droptarget } from '../dnd/droptarget';
|
||||||
isPanelTransferEvent,
|
|
||||||
isTabDragEvent,
|
|
||||||
} from '../dnd/dataTransfer';
|
|
||||||
import { Droptarget, DroptargetEvent, Position } from '../dnd/droptarget';
|
|
||||||
import {
|
import {
|
||||||
DockviewComponent,
|
DockviewComponent,
|
||||||
IDockviewComponent,
|
IDockviewComponent,
|
||||||
@ -15,7 +11,7 @@ import { isAncestor, toggleClass } from '../dom';
|
|||||||
import { addDisposableListener, Emitter, Event } from '../events';
|
import { addDisposableListener, Emitter, Event } from '../events';
|
||||||
import { IGridPanelView } from '../gridview/baseComponentGridview';
|
import { IGridPanelView } from '../gridview/baseComponentGridview';
|
||||||
import { IViewSize } from '../gridview/gridview';
|
import { IViewSize } from '../gridview/gridview';
|
||||||
import { CompositeDisposable, Disposable, IDisposable } from '../lifecycle';
|
import { CompositeDisposable, IDisposable } from '../lifecycle';
|
||||||
import { PanelInitParameters, PanelUpdateEvent } from '../panel/types';
|
import { PanelInitParameters, PanelUpdateEvent } from '../panel/types';
|
||||||
import { IGroupPanel } from './groupPanel';
|
import { IGroupPanel } from './groupPanel';
|
||||||
import { ContentContainer, IContentContainer } from './panel/content';
|
import { ContentContainer, IContentContainer } from './panel/content';
|
||||||
@ -46,6 +42,20 @@ export enum GroupChangeKind {
|
|||||||
LAYOUT_CONFIG_UPDATED = 'LAYOUT_CONFIG_UPDATED',
|
LAYOUT_CONFIG_UPDATED = 'LAYOUT_CONFIG_UPDATED',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface DndService {
|
||||||
|
canDisplayOverlay(
|
||||||
|
group: IGroupview,
|
||||||
|
event: DragEvent,
|
||||||
|
target: DockviewDropTargets
|
||||||
|
): boolean;
|
||||||
|
onDrop(
|
||||||
|
group: IGroupview,
|
||||||
|
event: DragEvent,
|
||||||
|
position: Position,
|
||||||
|
index?: number
|
||||||
|
): void;
|
||||||
|
}
|
||||||
|
|
||||||
export interface IGroupItem {
|
export interface IGroupItem {
|
||||||
id: string;
|
id: string;
|
||||||
header: { element: HTMLElement };
|
header: { element: HTMLElement };
|
||||||
@ -77,6 +87,12 @@ export interface GroupPanelViewState {
|
|||||||
id: string;
|
id: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum DockviewDropTargets {
|
||||||
|
Tab,
|
||||||
|
Panel,
|
||||||
|
TabContainer,
|
||||||
|
}
|
||||||
|
|
||||||
export interface IGroupview extends IDisposable, IGridPanelView {
|
export interface IGroupview extends IDisposable, IGridPanelView {
|
||||||
readonly isActive: boolean;
|
readonly isActive: boolean;
|
||||||
readonly size: number;
|
readonly size: number;
|
||||||
@ -99,7 +115,7 @@ export interface IGroupview extends IDisposable, IGridPanelView {
|
|||||||
onDidGroupChange: Event<{ kind: GroupChangeKind }>;
|
onDidGroupChange: Event<{ kind: GroupChangeKind }>;
|
||||||
onMove: Event<GroupMoveEvent>;
|
onMove: Event<GroupMoveEvent>;
|
||||||
//
|
//
|
||||||
startActiveDrag(panel: IGroupPanel): IDisposable;
|
// startActiveDrag(panel: IGroupPanel): IDisposable;
|
||||||
//
|
//
|
||||||
moveToNext(options?: { panel?: IGroupPanel; suppressRoll?: boolean }): void;
|
moveToNext(options?: { panel?: IGroupPanel; suppressRoll?: boolean }): void;
|
||||||
moveToPrevious(options?: {
|
moveToPrevious(options?: {
|
||||||
@ -108,12 +124,7 @@ export interface IGroupview extends IDisposable, IGridPanelView {
|
|||||||
}): void;
|
}): void;
|
||||||
isContentFocused(): boolean;
|
isContentFocused(): boolean;
|
||||||
updateActions(): void;
|
updateActions(): void;
|
||||||
}
|
canDisplayOverlay(event: DragEvent, target: DockviewDropTargets): boolean;
|
||||||
|
|
||||||
export interface GroupDropEvent {
|
|
||||||
event: DragEvent;
|
|
||||||
target: Position;
|
|
||||||
index?: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Groupview extends CompositeDisposable implements IGroupview {
|
export class Groupview extends CompositeDisposable implements IGroupview {
|
||||||
@ -138,9 +149,6 @@ export class Groupview extends CompositeDisposable implements IGroupview {
|
|||||||
private readonly _onMove = new Emitter<GroupMoveEvent>();
|
private readonly _onMove = new Emitter<GroupMoveEvent>();
|
||||||
readonly onMove: Event<GroupMoveEvent> = this._onMove.event;
|
readonly onMove: Event<GroupMoveEvent> = this._onMove.event;
|
||||||
|
|
||||||
private readonly _onDrop = new Emitter<GroupDropEvent>();
|
|
||||||
readonly onDrop: Event<GroupDropEvent> = this._onDrop.event;
|
|
||||||
|
|
||||||
private readonly _onDidGroupChange = new Emitter<GroupChangeEvent>();
|
private readonly _onDidGroupChange = new Emitter<GroupChangeEvent>();
|
||||||
readonly onDidGroupChange: Event<{ kind: GroupChangeKind }> =
|
readonly onDidGroupChange: Event<{ kind: GroupChangeKind }> =
|
||||||
this._onDidGroupChange.event;
|
this._onDidGroupChange.event;
|
||||||
@ -205,24 +213,26 @@ export class Groupview extends CompositeDisposable implements IGroupview {
|
|||||||
|
|
||||||
this.container.classList.add('groupview');
|
this.container.classList.add('groupview');
|
||||||
|
|
||||||
this.addDisposables(this._onMove, this._onDidGroupChange, this._onDrop);
|
this.addDisposables(this._onMove, this._onDidGroupChange);
|
||||||
|
|
||||||
this.tabsContainer = new TabsContainer(this.accessor, this.parent, {
|
this.tabsContainer = new TabsContainer(this.accessor, this.parent, {
|
||||||
tabHeight: options.tabHeight,
|
tabHeight: options.tabHeight,
|
||||||
});
|
});
|
||||||
this.contentContainer = new ContentContainer();
|
this.contentContainer = new ContentContainer();
|
||||||
this.dropTarget = new Droptarget(this.contentContainer.element, {
|
this.dropTarget = new Droptarget(this.contentContainer.element, {
|
||||||
isDirectional: true,
|
validOverlays: 'all',
|
||||||
id: this.accessor.id,
|
canDisplayOverlay: (event) => {
|
||||||
isDisabled: () => {
|
const data = getPanelData();
|
||||||
// disable the drop target if we only have one tab, and that is also the tab we are moving
|
|
||||||
return (
|
if (data) {
|
||||||
this._panels.length === 1 &&
|
const groupHasOnePanelAndIsActiveDragElement =
|
||||||
this.tabsContainer.hasActiveDragEvent
|
this._panels.length === 1 && data.groupId === this.id;
|
||||||
);
|
|
||||||
|
return !groupHasOnePanelAndIsActiveDragElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.canDisplayOverlay(event, DockviewDropTargets.Panel);
|
||||||
},
|
},
|
||||||
enableExternalDragEvents:
|
|
||||||
this.accessor.options.enableExternalDragEvents,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
container.append(
|
container.append(
|
||||||
@ -233,28 +243,22 @@ export class Groupview extends CompositeDisposable implements IGroupview {
|
|||||||
this.addDisposables(
|
this.addDisposables(
|
||||||
this._onMove,
|
this._onMove,
|
||||||
this._onDidGroupChange,
|
this._onDidGroupChange,
|
||||||
this.tabsContainer.onDropEvent((event) =>
|
this.tabsContainer.onDrop((event) => {
|
||||||
this.handleDropEvent(event.event, event.index)
|
this.handleDropEvent(event.event, Position.Center, event.index);
|
||||||
),
|
}),
|
||||||
this.contentContainer.onDidFocus(() => {
|
this.contentContainer.onDidFocus(() => {
|
||||||
this.accessor.doSetGroupActive(this.parent, true);
|
this.accessor.doSetGroupActive(this.parent, true);
|
||||||
}),
|
}),
|
||||||
this.contentContainer.onDidBlur(() => {
|
this.contentContainer.onDidBlur(() => {
|
||||||
// this._activePanel?.api._ondid
|
// this._activePanel?.api._ondid
|
||||||
}),
|
}),
|
||||||
this.dropTarget.onDidChange((event) => {
|
this.dropTarget.onDrop((event) => {
|
||||||
// if we've center dropped on ourself then ignore
|
this.handleDropEvent(event.event, event.position);
|
||||||
if (
|
|
||||||
event.position === Position.Center &&
|
|
||||||
this.tabsContainer.hasActiveDragEvent
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.handleDropEvent(event);
|
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
initialize() {
|
||||||
if (this.options?.panels) {
|
if (this.options?.panels) {
|
||||||
this.options.panels.forEach((panel) => {
|
this.options.panels.forEach((panel) => {
|
||||||
this.doAddPanel(panel);
|
this.doAddPanel(panel);
|
||||||
@ -264,9 +268,7 @@ export class Groupview extends CompositeDisposable implements IGroupview {
|
|||||||
if (this.options?.activePanel) {
|
if (this.options?.activePanel) {
|
||||||
this.openPanel(this.options.activePanel);
|
this.openPanel(this.options.activePanel);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
initialize() {
|
|
||||||
// must be run after the constructor otherwise this.parent may not be
|
// must be run after the constructor otherwise this.parent may not be
|
||||||
// correctly initialized
|
// correctly initialized
|
||||||
this.setActive(this.isActive, true, true);
|
this.setActive(this.isActive, true, true);
|
||||||
@ -295,19 +297,19 @@ export class Groupview extends CompositeDisposable implements IGroupview {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public startActiveDrag(panel: IGroupPanel): IDisposable {
|
// public startActiveDrag(panel: IGroupPanel): IDisposable {
|
||||||
const index = this.tabsContainer.indexOf(panel.id);
|
// const index = this.tabsContainer.indexOf(panel.id);
|
||||||
if (index > -1) {
|
// if (index > -1) {
|
||||||
const tab = this.tabsContainer.at(index);
|
// const tab = this.tabsContainer.at(index);
|
||||||
tab.startDragEvent();
|
// tab.startDragEvent();
|
||||||
return {
|
// return {
|
||||||
dispose: () => {
|
// dispose: () => {
|
||||||
tab.stopDragEvent();
|
// tab.stopDragEvent();
|
||||||
},
|
// },
|
||||||
};
|
// };
|
||||||
}
|
// }
|
||||||
return Disposable.NONE;
|
// return Disposable.NONE;
|
||||||
}
|
// }
|
||||||
|
|
||||||
public moveToNext(options?: {
|
public moveToNext(options?: {
|
||||||
panel?: IGroupPanel;
|
panel?: IGroupPanel;
|
||||||
@ -385,7 +387,10 @@ export class Groupview extends CompositeDisposable implements IGroupview {
|
|||||||
panel: IGroupPanel,
|
panel: IGroupPanel,
|
||||||
options: { index?: number; skipFocus?: boolean } = {}
|
options: { index?: number; skipFocus?: boolean } = {}
|
||||||
) {
|
) {
|
||||||
if (typeof options.index !== 'number') {
|
if (
|
||||||
|
typeof options.index !== 'number' ||
|
||||||
|
options.index > this.panels.length
|
||||||
|
) {
|
||||||
options.index = this.panels.length;
|
options.index = this.panels.length;
|
||||||
}
|
}
|
||||||
if (this._activePanel === panel) {
|
if (this._activePanel === panel) {
|
||||||
@ -668,33 +673,34 @@ export class Groupview extends CompositeDisposable implements IGroupview {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleDropEvent(event: DroptargetEvent, index?: number) {
|
canDisplayOverlay(
|
||||||
if (isPanelTransferEvent(event.event)) {
|
dragOverEvent: DragEvent,
|
||||||
this.handlePanelDropEvent(event.event, event.position, index);
|
target: DockviewDropTargets
|
||||||
|
): boolean {
|
||||||
|
// custom overlay handler
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleDropEvent(
|
||||||
|
event: DragEvent,
|
||||||
|
position: Position,
|
||||||
|
index?: number
|
||||||
|
) {
|
||||||
|
const data = getPanelData();
|
||||||
|
|
||||||
|
if (data) {
|
||||||
|
const fromSameGroup =
|
||||||
|
this.tabsContainer.indexOf(data.panelId) !== -1;
|
||||||
|
|
||||||
|
if (fromSameGroup && this.tabsContainer.size === 1) {
|
||||||
|
console.debug('[tabs] ignore event');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this._onDrop.fire({
|
const { groupId, panelId } = data;
|
||||||
event: event.event,
|
|
||||||
target: event.position,
|
|
||||||
index,
|
|
||||||
});
|
|
||||||
|
|
||||||
console.debug('[customDropEvent]');
|
|
||||||
}
|
|
||||||
|
|
||||||
private handlePanelDropEvent(
|
|
||||||
event: DragEvent,
|
|
||||||
target: Position,
|
|
||||||
index?: number
|
|
||||||
) {
|
|
||||||
const dataObject = extractData(event);
|
|
||||||
|
|
||||||
if (isTabDragEvent(dataObject)) {
|
|
||||||
const { groupId, itemId } = dataObject;
|
|
||||||
const isSameGroup = this.id === groupId;
|
const isSameGroup = this.id === groupId;
|
||||||
if (isSameGroup && !target) {
|
if (isSameGroup && !position) {
|
||||||
const oldIndex = this.tabsContainer.indexOf(itemId);
|
const oldIndex = this.tabsContainer.indexOf(panelId);
|
||||||
if (oldIndex === index) {
|
if (oldIndex === index) {
|
||||||
console.debug(
|
console.debug(
|
||||||
'[tabs] drop indicates no change in position'
|
'[tabs] drop indicates no change in position'
|
||||||
@ -704,30 +710,13 @@ export class Groupview extends CompositeDisposable implements IGroupview {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this._onMove.fire({
|
this._onMove.fire({
|
||||||
target,
|
target: position,
|
||||||
groupId: dataObject.groupId,
|
groupId: data.groupId,
|
||||||
itemId: dataObject.itemId,
|
itemId: data.panelId,
|
||||||
index,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isCustomDragEvent(dataObject)) {
|
|
||||||
let panel = this.accessor.getGroupPanel(dataObject.id);
|
|
||||||
|
|
||||||
if (!panel) {
|
|
||||||
panel = this.accessor.addPanel(dataObject);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!panel.group) {
|
|
||||||
throw new Error(`panel ${panel.id} has no associated group`);
|
|
||||||
}
|
|
||||||
|
|
||||||
this._onMove.fire({
|
|
||||||
target,
|
|
||||||
groupId: panel.group.id,
|
|
||||||
itemId: panel.id,
|
|
||||||
index,
|
index,
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
// custom drop handler
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,17 +1,14 @@
|
|||||||
import { addDisposableListener, Emitter, Event } from '../events';
|
import { addDisposableListener, Emitter, Event } from '../events';
|
||||||
import { Droptarget, DroptargetEvent } from '../dnd/droptarget';
|
|
||||||
import { CompositeDisposable } from '../lifecycle';
|
import { CompositeDisposable } from '../lifecycle';
|
||||||
import {
|
import { getPanelData, LocalSelectionTransfer } from '../dnd/dataTransfer';
|
||||||
DATA_KEY,
|
|
||||||
DragType,
|
|
||||||
LocalSelectionTransfer,
|
|
||||||
} from '../dnd/dataTransfer';
|
|
||||||
import { getElementsByTagName, toggleClass } from '../dom';
|
import { getElementsByTagName, toggleClass } from '../dom';
|
||||||
import { IDockviewComponent } from '../dockview/dockviewComponent';
|
import { IDockviewComponent } from '../dockview/dockviewComponent';
|
||||||
import { ITabRenderer } from './types';
|
import { ITabRenderer } from './types';
|
||||||
import { focusedElement } from '../focusedElement';
|
import { focusedElement } from '../focusedElement';
|
||||||
import { IGroupPanel } from './groupPanel';
|
import { IGroupPanel } from './groupPanel';
|
||||||
import { GroupviewPanel } from './groupviewPanel';
|
import { GroupviewPanel } from './groupviewPanel';
|
||||||
|
import { DroptargetEvent, Droptarget, PanelTransfer } from '../dnd/droptarget';
|
||||||
|
import { DockviewDropTargets } from './groupview';
|
||||||
|
|
||||||
export enum MouseEventKind {
|
export enum MouseEventKind {
|
||||||
CLICK = 'CLICK',
|
CLICK = 'CLICK',
|
||||||
@ -26,22 +23,16 @@ export interface LayoutMouseEvent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface ITab {
|
export interface ITab {
|
||||||
id: string;
|
panelId: string;
|
||||||
element: HTMLElement;
|
element: HTMLElement;
|
||||||
hasActiveDragEvent: boolean;
|
|
||||||
setContent: (element: ITabRenderer) => void;
|
setContent: (element: ITabRenderer) => void;
|
||||||
onChanged: Event<LayoutMouseEvent>;
|
onChanged: Event<LayoutMouseEvent>;
|
||||||
onDropped: Event<DroptargetEvent>;
|
onDrop: Event<DroptargetEvent>;
|
||||||
setActive(isActive: boolean): void;
|
setActive(isActive: boolean): void;
|
||||||
startDragEvent(): void;
|
|
||||||
stopDragEvent(): void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Tab extends CompositeDisposable implements ITab {
|
export class Tab extends CompositeDisposable implements ITab {
|
||||||
private _element: HTMLElement;
|
private _element: HTMLElement;
|
||||||
private dragInPlayDetails: { id?: string; isDragging: boolean } = {
|
|
||||||
isDragging: false,
|
|
||||||
};
|
|
||||||
private droptarget: Droptarget;
|
private droptarget: Droptarget;
|
||||||
private content?: ITabRenderer;
|
private content?: ITabRenderer;
|
||||||
|
|
||||||
@ -49,28 +40,19 @@ export class Tab extends CompositeDisposable implements ITab {
|
|||||||
readonly onChanged: Event<LayoutMouseEvent> = this._onChanged.event;
|
readonly onChanged: Event<LayoutMouseEvent> = this._onChanged.event;
|
||||||
|
|
||||||
private readonly _onDropped = new Emitter<DroptargetEvent>();
|
private readonly _onDropped = new Emitter<DroptargetEvent>();
|
||||||
readonly onDropped: Event<DroptargetEvent> = this._onDropped.event;
|
readonly onDrop: Event<DroptargetEvent> = this._onDropped.event;
|
||||||
|
|
||||||
|
private readonly panelTransfer =
|
||||||
|
LocalSelectionTransfer.getInstance<PanelTransfer>();
|
||||||
|
|
||||||
public get element() {
|
public get element() {
|
||||||
return this._element;
|
return this._element;
|
||||||
}
|
}
|
||||||
|
|
||||||
public get hasActiveDragEvent() {
|
|
||||||
return this.dragInPlayDetails?.isDragging;
|
|
||||||
}
|
|
||||||
|
|
||||||
public startDragEvent() {
|
|
||||||
this.dragInPlayDetails = { isDragging: true, id: this.accessor.id };
|
|
||||||
}
|
|
||||||
|
|
||||||
public stopDragEvent() {
|
|
||||||
this.dragInPlayDetails = { isDragging: false, id: undefined };
|
|
||||||
}
|
|
||||||
|
|
||||||
private iframes: HTMLElement[] = [];
|
private iframes: HTMLElement[] = [];
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public id: string,
|
public panelId: string,
|
||||||
private readonly accessor: IDockviewComponent,
|
private readonly accessor: IDockviewComponent,
|
||||||
private group: GroupviewPanel
|
private group: GroupviewPanel
|
||||||
) {
|
) {
|
||||||
@ -85,11 +67,6 @@ export class Tab extends CompositeDisposable implements ITab {
|
|||||||
|
|
||||||
this.addDisposables(
|
this.addDisposables(
|
||||||
addDisposableListener(this._element, 'dragstart', (event) => {
|
addDisposableListener(this._element, 'dragstart', (event) => {
|
||||||
this.dragInPlayDetails = {
|
|
||||||
isDragging: true,
|
|
||||||
id: this.accessor.id,
|
|
||||||
};
|
|
||||||
|
|
||||||
this.iframes = [
|
this.iframes = [
|
||||||
...getElementsByTagName('iframe'),
|
...getElementsByTagName('iframe'),
|
||||||
...getElementsByTagName('webview'),
|
...getElementsByTagName('webview'),
|
||||||
@ -102,18 +79,18 @@ export class Tab extends CompositeDisposable implements ITab {
|
|||||||
this.element.classList.add('dragged');
|
this.element.classList.add('dragged');
|
||||||
setTimeout(() => this.element.classList.remove('dragged'), 0);
|
setTimeout(() => this.element.classList.remove('dragged'), 0);
|
||||||
|
|
||||||
const data = JSON.stringify({
|
this.panelTransfer.setData(
|
||||||
type: DragType.ITEM,
|
[
|
||||||
itemId: this.id,
|
new PanelTransfer(
|
||||||
groupId: this.group.id,
|
this.accessor.id,
|
||||||
});
|
this.group.id,
|
||||||
LocalSelectionTransfer.getInstance().setData(
|
this.panelId
|
||||||
[data],
|
),
|
||||||
this.dragInPlayDetails.id
|
],
|
||||||
|
PanelTransfer.prototype
|
||||||
);
|
);
|
||||||
|
|
||||||
if (event.dataTransfer) {
|
if (event.dataTransfer) {
|
||||||
event.dataTransfer.setData(DATA_KEY, data);
|
|
||||||
event.dataTransfer.effectAllowed = 'move';
|
event.dataTransfer.effectAllowed = 'move';
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
@ -123,14 +100,7 @@ export class Tab extends CompositeDisposable implements ITab {
|
|||||||
}
|
}
|
||||||
this.iframes = [];
|
this.iframes = [];
|
||||||
|
|
||||||
// drop events fire before dragend so we can remove this safely
|
this.panelTransfer.clearData(PanelTransfer.prototype);
|
||||||
LocalSelectionTransfer.getInstance().clearData(
|
|
||||||
this.dragInPlayDetails.id
|
|
||||||
);
|
|
||||||
this.dragInPlayDetails = {
|
|
||||||
isDragging: false,
|
|
||||||
id: undefined,
|
|
||||||
};
|
|
||||||
}),
|
}),
|
||||||
addDisposableListener(this._element, 'mousedown', (event) => {
|
addDisposableListener(this._element, 'mousedown', (event) => {
|
||||||
if (event.defaultPrevented) {
|
if (event.defaultPrevented) {
|
||||||
@ -168,16 +138,22 @@ export class Tab extends CompositeDisposable implements ITab {
|
|||||||
);
|
);
|
||||||
|
|
||||||
this.droptarget = new Droptarget(this._element, {
|
this.droptarget = new Droptarget(this._element, {
|
||||||
isDirectional: false,
|
validOverlays: 'none',
|
||||||
isDisabled: () => this.dragInPlayDetails.isDragging,
|
canDisplayOverlay: (event) => {
|
||||||
id: this.accessor.id,
|
const data = getPanelData();
|
||||||
enableExternalDragEvents: this.accessor.options
|
if (data) {
|
||||||
.enableExternalDragEvents,
|
return this.panelId !== data.panelId;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.group.model.canDisplayOverlay(
|
||||||
|
event,
|
||||||
|
DockviewDropTargets.Tab
|
||||||
|
);
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
this.addDisposables(
|
this.addDisposables(
|
||||||
this.droptarget.onDidChange((event) => {
|
this.droptarget.onDrop((event) => {
|
||||||
event.event.preventDefault();
|
|
||||||
this._onDropped.fire(event);
|
this._onDropped.fire(event);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
@ -10,8 +10,12 @@
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tabs-container {
|
.void-container {
|
||||||
|
display: flex;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabs-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
overflow-x: overlay;
|
overflow-x: overlay;
|
||||||
overflow-y: hidden;
|
overflow-y: hidden;
|
||||||
@ -32,10 +36,6 @@
|
|||||||
background: var(--dv-tabs-container-scrollbar-color);
|
background: var(--dv-tabs-container-scrollbar-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.drag-over-target {
|
|
||||||
background-color: var(--dv-drag-over-background-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab {
|
.tab {
|
||||||
-webkit-user-drag: element;
|
-webkit-user-drag: element;
|
||||||
outline: none;
|
outline: none;
|
||||||
|
@ -5,28 +5,28 @@ import {
|
|||||||
} from '../../lifecycle';
|
} from '../../lifecycle';
|
||||||
import { addDisposableListener, Emitter, Event } from '../../events';
|
import { addDisposableListener, Emitter, Event } from '../../events';
|
||||||
import { ITab, MouseEventKind, Tab } from '../tab';
|
import { ITab, MouseEventKind, Tab } from '../tab';
|
||||||
import { removeClasses, addClasses } from '../../dom';
|
|
||||||
import { DroptargetEvent, Position } from '../../dnd/droptarget';
|
|
||||||
import { last } from '../../array';
|
import { last } from '../../array';
|
||||||
import { IGroupPanel } from '../groupPanel';
|
import { IGroupPanel } from '../groupPanel';
|
||||||
import { IDockviewComponent } from '../../dockview/dockviewComponent';
|
import { IDockviewComponent } from '../../dockview/dockviewComponent';
|
||||||
import { LocalSelectionTransfer } from '../../dnd/dataTransfer';
|
import { getPanelData } from '../../dnd/dataTransfer';
|
||||||
import { GroupviewPanel } from '../groupviewPanel';
|
import { GroupviewPanel } from '../groupviewPanel';
|
||||||
|
import { Droptarget } from '../../dnd/droptarget';
|
||||||
|
import { DockviewDropTargets } from '../groupview';
|
||||||
|
|
||||||
export interface TabDropEvent {
|
export interface TabDropIndexEvent {
|
||||||
readonly event: DroptargetEvent;
|
event: DragEvent;
|
||||||
readonly index?: number;
|
readonly index: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ITabsContainer extends IDisposable {
|
export interface ITabsContainer extends IDisposable {
|
||||||
readonly element: HTMLElement;
|
readonly element: HTMLElement;
|
||||||
readonly panels: string[];
|
readonly panels: string[];
|
||||||
readonly hasActiveDragEvent: boolean;
|
readonly size: number;
|
||||||
height: number | undefined;
|
height: number | undefined;
|
||||||
delete: (id: string) => void;
|
delete: (id: string) => void;
|
||||||
indexOf: (id: string) => number;
|
indexOf: (id: string) => number;
|
||||||
at: (index: number) => ITab;
|
at: (index: number) => ITab;
|
||||||
onDropEvent: Event<TabDropEvent>;
|
onDrop: Event<TabDropIndexEvent>;
|
||||||
setActive: (isGroupActive: boolean) => void;
|
setActive: (isGroupActive: boolean) => void;
|
||||||
setActivePanel: (panel: IGroupPanel) => void;
|
setActivePanel: (panel: IGroupPanel) => void;
|
||||||
isActive: (tab: ITab) => boolean;
|
isActive: (tab: ITab) => boolean;
|
||||||
@ -43,8 +43,11 @@ export class TabsContainer
|
|||||||
{
|
{
|
||||||
private readonly _element: HTMLElement;
|
private readonly _element: HTMLElement;
|
||||||
private readonly tabContainer: HTMLElement;
|
private readonly tabContainer: HTMLElement;
|
||||||
|
private readonly voidContainer: HTMLElement;
|
||||||
private readonly actionContainer: HTMLElement;
|
private readonly actionContainer: HTMLElement;
|
||||||
|
|
||||||
|
private readonly voidDropTarget: Droptarget;
|
||||||
|
|
||||||
private tabs: IValueDisposable<ITab>[] = [];
|
private tabs: IValueDisposable<ITab>[] = [];
|
||||||
private selectedIndex = -1;
|
private selectedIndex = -1;
|
||||||
private active = false;
|
private active = false;
|
||||||
@ -53,11 +56,15 @@ export class TabsContainer
|
|||||||
|
|
||||||
private _height: number | undefined;
|
private _height: number | undefined;
|
||||||
|
|
||||||
private readonly _onDropped = new Emitter<TabDropEvent>();
|
private readonly _onDrop = new Emitter<TabDropIndexEvent>();
|
||||||
readonly onDropEvent: Event<TabDropEvent> = this._onDropped.event;
|
readonly onDrop: Event<TabDropIndexEvent> = this._onDrop.event;
|
||||||
|
|
||||||
get panels() {
|
get panels() {
|
||||||
return this.tabs.map((_) => _.value.id);
|
return this.tabs.map((_) => _.value.panelId);
|
||||||
|
}
|
||||||
|
|
||||||
|
get size() {
|
||||||
|
return this.tabs.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
get height(): number | undefined {
|
get height(): number | undefined {
|
||||||
@ -67,20 +74,15 @@ export class TabsContainer
|
|||||||
set height(value: number | undefined) {
|
set height(value: number | undefined) {
|
||||||
this._height = value;
|
this._height = value;
|
||||||
if (typeof value !== 'number') {
|
if (typeof value !== 'number') {
|
||||||
// removeClasses(this.element, 'separator-border');
|
|
||||||
this.element.style.removeProperty(
|
this.element.style.removeProperty(
|
||||||
'--dv-tabs-and-actions-container-height'
|
'--dv-tabs-and-actions-container-height'
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
// addClasses(this.element, 'separator-border');
|
|
||||||
// if (styles?.separatorBorder) {
|
|
||||||
this.element.style.setProperty(
|
this.element.style.setProperty(
|
||||||
'--dv-tabs-and-actions-container-height',
|
'--dv-tabs-and-actions-container-height',
|
||||||
`${value}px`
|
`${value}px`
|
||||||
);
|
);
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
// this._element.style.height = `${this.height}px`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
show() {
|
show() {
|
||||||
@ -116,16 +118,12 @@ export class TabsContainer
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public get hasActiveDragEvent() {
|
|
||||||
return !!this.tabs.find((tab) => tab.value.hasActiveDragEvent);
|
|
||||||
}
|
|
||||||
|
|
||||||
public at(index: number) {
|
public at(index: number) {
|
||||||
return this.tabs[index]?.value;
|
return this.tabs[index]?.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public indexOf(id: string): number {
|
public indexOf(id: string): number {
|
||||||
return this.tabs.findIndex((tab) => tab.value.id === id);
|
return this.tabs.findIndex((tab) => tab.value.panelId === id);
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@ -135,7 +133,7 @@ export class TabsContainer
|
|||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.addDisposables(this._onDropped);
|
this.addDisposables(this._onDrop);
|
||||||
|
|
||||||
this._element = document.createElement('div');
|
this._element = document.createElement('div');
|
||||||
this._element.className = 'tabs-and-actions-container';
|
this._element.className = 'tabs-and-actions-container';
|
||||||
@ -148,10 +146,38 @@ export class TabsContainer
|
|||||||
this.tabContainer = document.createElement('div');
|
this.tabContainer = document.createElement('div');
|
||||||
this.tabContainer.className = 'tabs-container';
|
this.tabContainer.className = 'tabs-container';
|
||||||
|
|
||||||
|
this.voidContainer = document.createElement('div');
|
||||||
|
this.voidContainer.className = 'void-container';
|
||||||
|
|
||||||
this._element.appendChild(this.tabContainer);
|
this._element.appendChild(this.tabContainer);
|
||||||
|
this._element.appendChild(this.voidContainer);
|
||||||
this._element.appendChild(this.actionContainer);
|
this._element.appendChild(this.actionContainer);
|
||||||
|
|
||||||
|
this.voidDropTarget = new Droptarget(this.voidContainer, {
|
||||||
|
validOverlays: 'none',
|
||||||
|
canDisplayOverlay: (event) => {
|
||||||
|
const data = getPanelData();
|
||||||
|
|
||||||
|
if (data) {
|
||||||
|
// don't show the overlay if the tab being dragged is the last panel of this group
|
||||||
|
return last(this.tabs)?.value.panelId !== data.panelId;
|
||||||
|
}
|
||||||
|
|
||||||
|
return group.model.canDisplayOverlay(
|
||||||
|
event,
|
||||||
|
DockviewDropTargets.Panel
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
this.addDisposables(
|
this.addDisposables(
|
||||||
|
this.voidDropTarget.onDrop((event) => {
|
||||||
|
this._onDrop.fire({
|
||||||
|
event: event.event,
|
||||||
|
index: this.tabs.length,
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
this.voidDropTarget,
|
||||||
addDisposableListener(this.tabContainer, 'mousedown', (event) => {
|
addDisposableListener(this.tabContainer, 'mousedown', (event) => {
|
||||||
if (event.defaultPrevented) {
|
if (event.defaultPrevented) {
|
||||||
return;
|
return;
|
||||||
@ -162,62 +188,6 @@ export class TabsContainer
|
|||||||
if (isLeftClick) {
|
if (isLeftClick) {
|
||||||
this.accessor.doSetGroupActive(this.group);
|
this.accessor.doSetGroupActive(this.group);
|
||||||
}
|
}
|
||||||
}),
|
|
||||||
addDisposableListener(this.tabContainer, 'dragenter', (event) => {
|
|
||||||
if (
|
|
||||||
!LocalSelectionTransfer.getInstance().hasData(
|
|
||||||
this.accessor.id
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
console.debug('[tabs] invalid drop event');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!last(this.tabs)?.value.hasActiveDragEvent) {
|
|
||||||
addClasses(this.tabContainer, 'drag-over-target');
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
addDisposableListener(this.tabContainer, 'dragover', (event) => {
|
|
||||||
event.preventDefault();
|
|
||||||
}),
|
|
||||||
addDisposableListener(this.tabContainer, 'dragleave', (event) => {
|
|
||||||
removeClasses(this.tabContainer, 'drag-over-target');
|
|
||||||
}),
|
|
||||||
addDisposableListener(this.tabContainer, 'drop', (event) => {
|
|
||||||
if (
|
|
||||||
!LocalSelectionTransfer.getInstance().hasData(
|
|
||||||
this.accessor.id
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
console.debug('[tabs] invalid drop event');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (event.defaultPrevented) {
|
|
||||||
console.debug('[tab] drop event defaultprevented');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
removeClasses(this.tabContainer, 'drag-over-target');
|
|
||||||
|
|
||||||
const activetab = this.tabs.find(
|
|
||||||
(tab) => tab.value.hasActiveDragEvent
|
|
||||||
);
|
|
||||||
|
|
||||||
const ignore = !!(
|
|
||||||
activetab &&
|
|
||||||
event
|
|
||||||
.composedPath()
|
|
||||||
.find((x) => activetab.value.element === x)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (ignore) {
|
|
||||||
console.debug('[tabs] ignore event');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this._onDropped.fire({
|
|
||||||
event: { event, position: Position.Center },
|
|
||||||
index: this.tabs.length - (activetab ? 1 : 0),
|
|
||||||
});
|
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -251,7 +221,7 @@ export class TabsContainer
|
|||||||
}
|
}
|
||||||
|
|
||||||
public delete(id: string) {
|
public delete(id: string) {
|
||||||
const index = this.tabs.findIndex((tab) => tab.value.id === id);
|
const index = this.tabs.findIndex((tab) => tab.value.panelId === id);
|
||||||
|
|
||||||
const tabToRemove = this.tabs.splice(index, 1)[0];
|
const tabToRemove = this.tabs.splice(index, 1)[0];
|
||||||
|
|
||||||
@ -263,13 +233,13 @@ export class TabsContainer
|
|||||||
|
|
||||||
public setActivePanel(panel: IGroupPanel) {
|
public setActivePanel(panel: IGroupPanel) {
|
||||||
this.tabs.forEach((tab) => {
|
this.tabs.forEach((tab) => {
|
||||||
const isActivePanel = panel.id === tab.value.id;
|
const isActivePanel = panel.id === tab.value.panelId;
|
||||||
tab.value.setActive(isActivePanel);
|
tab.value.setActive(isActivePanel);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public openPanel(panel: IGroupPanel, index: number = this.tabs.length) {
|
public openPanel(panel: IGroupPanel, index: number = this.tabs.length) {
|
||||||
if (this.tabs.find((tab) => tab.value.id === panel.id)) {
|
if (this.tabs.find((tab) => tab.value.panelId === panel.id)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const tabToAdd = new Tab(panel.id, this.accessor, this.group);
|
const tabToAdd = new Tab(panel.id, this.accessor, this.group);
|
||||||
@ -299,9 +269,9 @@ export class TabsContainer
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
tabToAdd.onDropped((event) => {
|
tabToAdd.onDrop((event) => {
|
||||||
this._onDropped.fire({
|
this._onDrop.fire({
|
||||||
event,
|
event: event.event,
|
||||||
index: this.tabs.findIndex((x) => x.value === tabToAdd),
|
index: this.tabs.findIndex((x) => x.value === tabToAdd),
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
|
126
packages/dockview/src/paneview/draggablePaneviewPanel.ts
Normal file
126
packages/dockview/src/paneview/draggablePaneviewPanel.ts
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
import { DragHandler } from '../dnd/abstractDragHandler';
|
||||||
|
import { getPaneData, LocalSelectionTransfer } from '../dnd/dataTransfer';
|
||||||
|
import {
|
||||||
|
Droptarget,
|
||||||
|
DroptargetEvent,
|
||||||
|
PaneTransfer,
|
||||||
|
Position,
|
||||||
|
} from '../dnd/droptarget';
|
||||||
|
import { Emitter, Event } from '../events';
|
||||||
|
import { IDisposable } from '../lifecycle';
|
||||||
|
import { Orientation } from '../splitview/core/splitview';
|
||||||
|
import { PanePanelInitParameter, PaneviewPanel } from './paneviewPanel';
|
||||||
|
|
||||||
|
interface ViewContainer {
|
||||||
|
readonly title: string;
|
||||||
|
readonly icon: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ViewContainerModel {
|
||||||
|
readonly title: string;
|
||||||
|
readonly icon: string;
|
||||||
|
readonly onDidAdd: Event<void>;
|
||||||
|
readonly onDidRemove: Event<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IViewContainerService {
|
||||||
|
getViewContainerById(id: string): ViewContainer;
|
||||||
|
getViewContainerModel(container: ViewContainer): ViewContainerModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
export abstract class DraggablePaneviewPanel extends PaneviewPanel {
|
||||||
|
private handler: DragHandler | undefined;
|
||||||
|
private target: Droptarget | undefined;
|
||||||
|
|
||||||
|
private readonly _onDidDrop = new Emitter<DroptargetEvent>();
|
||||||
|
readonly onDidDrop = this._onDidDrop.event;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
id: string,
|
||||||
|
component: string,
|
||||||
|
headerComponent: string | undefined,
|
||||||
|
orientation: Orientation,
|
||||||
|
isExpanded: boolean,
|
||||||
|
disableDnd: boolean
|
||||||
|
) {
|
||||||
|
super(id, component, headerComponent, orientation, isExpanded);
|
||||||
|
|
||||||
|
if (!disableDnd) {
|
||||||
|
this.initDragFeatures();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private initDragFeatures() {
|
||||||
|
const id = this.id;
|
||||||
|
this.header!.draggable = true;
|
||||||
|
this.header!.tabIndex = 0;
|
||||||
|
|
||||||
|
this.handler = new (class PaneDragHandler extends DragHandler {
|
||||||
|
getData(): IDisposable {
|
||||||
|
LocalSelectionTransfer.getInstance().setData(
|
||||||
|
[new PaneTransfer('paneview', id)],
|
||||||
|
PaneTransfer.prototype
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
dispose: () => {
|
||||||
|
LocalSelectionTransfer.getInstance().clearData(
|
||||||
|
PaneTransfer.prototype
|
||||||
|
);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
})(this.header!);
|
||||||
|
|
||||||
|
this.target = new Droptarget(this.element, {
|
||||||
|
validOverlays: 'vertical',
|
||||||
|
canDisplayOverlay: (event: DragEvent) => {
|
||||||
|
const data = getPaneData();
|
||||||
|
|
||||||
|
if (!data) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return data.paneId !== this.id;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
this.addDisposables(
|
||||||
|
this._onDidDrop,
|
||||||
|
this.handler,
|
||||||
|
this.target,
|
||||||
|
this.target.onDrop((event) => {
|
||||||
|
const data = getPaneData();
|
||||||
|
|
||||||
|
if (!data) {
|
||||||
|
this._onDidDrop.fire(event);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const containerApi = (this.params! as PanePanelInitParameter)
|
||||||
|
.containerApi;
|
||||||
|
const id = data.paneId;
|
||||||
|
|
||||||
|
const existingPanel = containerApi.getPanel(id);
|
||||||
|
if (!existingPanel) {
|
||||||
|
this._onDidDrop.fire(event);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fromIndex = containerApi
|
||||||
|
.getPanels()
|
||||||
|
.indexOf(existingPanel);
|
||||||
|
let toIndex = containerApi.getPanels().indexOf(this);
|
||||||
|
|
||||||
|
if (
|
||||||
|
event.position === Position.Right ||
|
||||||
|
event.position === Position.Bottom
|
||||||
|
) {
|
||||||
|
toIndex = Math.max(0, toIndex + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
containerApi.movePanel(fromIndex, toIndex);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -22,4 +22,5 @@ export interface PaneviewComponentOptions {
|
|||||||
header: FrameworkFactory<IPaneHeaderPart>;
|
header: FrameworkFactory<IPaneHeaderPart>;
|
||||||
body: FrameworkFactory<IPaneBodyPart>;
|
body: FrameworkFactory<IPaneBodyPart>;
|
||||||
};
|
};
|
||||||
|
disableDnd?: boolean;
|
||||||
}
|
}
|
||||||
|
@ -58,11 +58,6 @@
|
|||||||
outline-offset: -1px;
|
outline-offset: -1px;
|
||||||
outline-color: var(--dv-paneview-active-outline-color);
|
outline-color: var(--dv-paneview-active-outline-color);
|
||||||
}
|
}
|
||||||
// outline-width: 1px;
|
|
||||||
// outline-style: solid;
|
|
||||||
// outline-offset: -1px;
|
|
||||||
// opacity: 1 !important;
|
|
||||||
// outline-color: dodgerblue;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.pane-body {
|
.pane-body {
|
||||||
|
@ -24,6 +24,13 @@ export class Paneview extends CompositeDisposable implements IDisposable {
|
|||||||
private readonly _onDidChange = new Emitter<void>();
|
private readonly _onDidChange = new Emitter<void>();
|
||||||
readonly onDidChange: Event<void> = this._onDidChange.event;
|
readonly onDidChange: Event<void> = this._onDidChange.event;
|
||||||
|
|
||||||
|
get onDidAddView() {
|
||||||
|
return <Event<PaneviewPanel>>this.splitview.onDidAddView;
|
||||||
|
}
|
||||||
|
get onDidRemoveView() {
|
||||||
|
return <Event<PaneviewPanel>>this.splitview.onDidRemoveView;
|
||||||
|
}
|
||||||
|
|
||||||
get minimumSize() {
|
get minimumSize() {
|
||||||
return this.splitview.minimumSize;
|
return this.splitview.minimumSize;
|
||||||
}
|
}
|
||||||
@ -66,10 +73,12 @@ export class Paneview extends CompositeDisposable implements IDisposable {
|
|||||||
// if we've added views from the descriptor we need to
|
// if we've added views from the descriptor we need to
|
||||||
// add the panes to our Pane array and setup animation
|
// add the panes to our Pane array and setup animation
|
||||||
this.getPanes().forEach((pane, index) => {
|
this.getPanes().forEach((pane, index) => {
|
||||||
const disposable = pane.onDidChangeExpansionState(() => {
|
const disposable = new CompositeDisposable(
|
||||||
|
pane.onDidChangeExpansionState(() => {
|
||||||
this.setupAnimation();
|
this.setupAnimation();
|
||||||
this._onDidChange.fire(undefined);
|
this._onDidChange.fire(undefined);
|
||||||
});
|
})
|
||||||
|
);
|
||||||
|
|
||||||
const paneItem: PaneItem = {
|
const paneItem: PaneItem = {
|
||||||
pane,
|
pane,
|
||||||
@ -112,6 +121,7 @@ export class Paneview extends CompositeDisposable implements IDisposable {
|
|||||||
};
|
};
|
||||||
|
|
||||||
this.paneItems.splice(index, 0, paneItem);
|
this.paneItems.splice(index, 0, paneItem);
|
||||||
|
|
||||||
pane.orthogonalSize = this.splitview.orthogonalSize;
|
pane.orthogonalSize = this.splitview.orthogonalSize;
|
||||||
this.splitview.addView(pane, size, index, skipLayout);
|
this.splitview.addView(pane, size, index, skipLayout);
|
||||||
}
|
}
|
||||||
@ -131,9 +141,21 @@ export class Paneview extends CompositeDisposable implements IDisposable {
|
|||||||
return paneItem;
|
return paneItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private skipAnimation = false;
|
||||||
|
|
||||||
public moveView(from: number, to: number) {
|
public moveView(from: number, to: number) {
|
||||||
|
if (from === to) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const view = this.removePane(from);
|
const view = this.removePane(from);
|
||||||
this.addPane(view.pane, to);
|
|
||||||
|
this.skipAnimation = true;
|
||||||
|
try {
|
||||||
|
this.addPane(view.pane, view.pane.size, to, false);
|
||||||
|
} finally {
|
||||||
|
this.skipAnimation = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public layout(size: number, orthogonalSize: number): void {
|
public layout(size: number, orthogonalSize: number): void {
|
||||||
@ -145,6 +167,10 @@ export class Paneview extends CompositeDisposable implements IDisposable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private setupAnimation() {
|
private setupAnimation() {
|
||||||
|
if (this.skipAnimation) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (this.animationTimer) {
|
if (this.animationTimer) {
|
||||||
clearTimeout(this.animationTimer);
|
clearTimeout(this.animationTimer);
|
||||||
this.animationTimer = undefined;
|
this.animationTimer = undefined;
|
||||||
|
@ -22,6 +22,8 @@ import {
|
|||||||
PanePanelInitParameter,
|
PanePanelInitParameter,
|
||||||
IPaneviewPanel,
|
IPaneviewPanel,
|
||||||
} from './paneviewPanel';
|
} from './paneviewPanel';
|
||||||
|
import { DraggablePaneviewPanel } from './draggablePaneviewPanel';
|
||||||
|
import { DroptargetEvent } from '../dnd/droptarget';
|
||||||
|
|
||||||
export interface SerializedPaneviewPanel {
|
export interface SerializedPaneviewPanel {
|
||||||
snap?: boolean;
|
snap?: boolean;
|
||||||
@ -74,7 +76,7 @@ class DefaultHeader extends CompositeDisposable implements IPaneHeaderPart {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class PaneFramework extends PaneviewPanel {
|
export class PaneFramework extends DraggablePaneviewPanel {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly options: {
|
private readonly options: {
|
||||||
id: string;
|
id: string;
|
||||||
@ -84,6 +86,7 @@ export class PaneFramework extends PaneviewPanel {
|
|||||||
header: IPaneHeaderPart;
|
header: IPaneHeaderPart;
|
||||||
orientation: Orientation;
|
orientation: Orientation;
|
||||||
isExpanded: boolean;
|
isExpanded: boolean;
|
||||||
|
disableDnd: boolean;
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
super(
|
super(
|
||||||
@ -91,7 +94,8 @@ export class PaneFramework extends PaneviewPanel {
|
|||||||
options.component,
|
options.component,
|
||||||
options.headerComponent,
|
options.headerComponent,
|
||||||
options.orientation,
|
options.orientation,
|
||||||
options.isExpanded
|
options.isExpanded,
|
||||||
|
options.disableDnd
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -142,13 +146,25 @@ export interface IPaneviewComponent extends IDisposable {
|
|||||||
|
|
||||||
export class PaneviewComponent
|
export class PaneviewComponent
|
||||||
extends CompositeDisposable
|
extends CompositeDisposable
|
||||||
implements IPaneviewComponent {
|
implements IPaneviewComponent
|
||||||
|
{
|
||||||
private _disposable = new MutableDisposable();
|
private _disposable = new MutableDisposable();
|
||||||
private _paneview!: Paneview;
|
private _paneview!: Paneview;
|
||||||
|
|
||||||
private readonly _onDidLayoutChange = new Emitter<void>();
|
private readonly _onDidLayoutChange = new Emitter<void>();
|
||||||
readonly onDidLayoutChange: Event<void> = this._onDidLayoutChange.event;
|
readonly onDidLayoutChange: Event<void> = this._onDidLayoutChange.event;
|
||||||
|
|
||||||
|
private readonly _onDidDrop = new Emitter<DroptargetEvent>();
|
||||||
|
readonly onDidDrop: Event<DroptargetEvent> = this._onDidDrop.event;
|
||||||
|
|
||||||
|
get onDidAddView() {
|
||||||
|
return this._paneview.onDidAddView;
|
||||||
|
}
|
||||||
|
|
||||||
|
get onDidRemoveView() {
|
||||||
|
return this._paneview.onDidRemoveView;
|
||||||
|
}
|
||||||
|
|
||||||
set paneview(value: Paneview) {
|
set paneview(value: Paneview) {
|
||||||
this._paneview = value;
|
this._paneview = value;
|
||||||
|
|
||||||
@ -216,8 +232,8 @@ export class PaneviewComponent
|
|||||||
this.options.frameworkComponents || {},
|
this.options.frameworkComponents || {},
|
||||||
this.options.frameworkWrapper
|
this.options.frameworkWrapper
|
||||||
? {
|
? {
|
||||||
createComponent: this.options.frameworkWrapper.body
|
createComponent:
|
||||||
.createComponent,
|
this.options.frameworkWrapper.body.createComponent,
|
||||||
}
|
}
|
||||||
: undefined
|
: undefined
|
||||||
);
|
);
|
||||||
@ -232,7 +248,8 @@ export class PaneviewComponent
|
|||||||
this.options.headerframeworkComponents,
|
this.options.headerframeworkComponents,
|
||||||
this.options.frameworkWrapper
|
this.options.frameworkWrapper
|
||||||
? {
|
? {
|
||||||
createComponent: this.options.frameworkWrapper.header
|
createComponent:
|
||||||
|
this.options.frameworkWrapper.header
|
||||||
.createComponent,
|
.createComponent,
|
||||||
}
|
}
|
||||||
: undefined
|
: undefined
|
||||||
@ -249,6 +266,11 @@ export class PaneviewComponent
|
|||||||
body,
|
body,
|
||||||
orientation: Orientation.VERTICAL,
|
orientation: Orientation.VERTICAL,
|
||||||
isExpanded: !!options.isExpanded,
|
isExpanded: !!options.isExpanded,
|
||||||
|
disableDnd: !!this.options.disableDnd,
|
||||||
|
});
|
||||||
|
|
||||||
|
view.onDidDrop((event) => {
|
||||||
|
this._onDidDrop.fire(event);
|
||||||
});
|
});
|
||||||
|
|
||||||
const size: Sizing | number =
|
const size: Sizing | number =
|
||||||
@ -309,10 +331,8 @@ export class PaneviewComponent
|
|||||||
if (!this.element.parentElement) {
|
if (!this.element.parentElement) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const {
|
const { width, height } =
|
||||||
width,
|
this.element.parentElement.getBoundingClientRect();
|
||||||
height,
|
|
||||||
} = this.element.parentElement.getBoundingClientRect();
|
|
||||||
this.layout(width, height);
|
this.layout(width, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -367,8 +387,9 @@ export class PaneviewComponent
|
|||||||
this.options.frameworkComponents || {},
|
this.options.frameworkComponents || {},
|
||||||
this.options.frameworkWrapper
|
this.options.frameworkWrapper
|
||||||
? {
|
? {
|
||||||
createComponent: this.options.frameworkWrapper
|
createComponent:
|
||||||
.body.createComponent,
|
this.options.frameworkWrapper.body
|
||||||
|
.createComponent,
|
||||||
}
|
}
|
||||||
: undefined
|
: undefined
|
||||||
);
|
);
|
||||||
@ -383,8 +404,8 @@ export class PaneviewComponent
|
|||||||
this.options.headerframeworkComponents || {},
|
this.options.headerframeworkComponents || {},
|
||||||
this.options.frameworkWrapper
|
this.options.frameworkWrapper
|
||||||
? {
|
? {
|
||||||
createComponent: this.options
|
createComponent:
|
||||||
.frameworkWrapper.header
|
this.options.frameworkWrapper.header
|
||||||
.createComponent,
|
.createComponent,
|
||||||
}
|
}
|
||||||
: undefined
|
: undefined
|
||||||
@ -401,6 +422,11 @@ export class PaneviewComponent
|
|||||||
body,
|
body,
|
||||||
orientation: Orientation.VERTICAL,
|
orientation: Orientation.VERTICAL,
|
||||||
isExpanded: !!view.expanded,
|
isExpanded: !!view.expanded,
|
||||||
|
disableDnd: !!this.options.disableDnd,
|
||||||
|
});
|
||||||
|
|
||||||
|
panel.onDidDrop((event) => {
|
||||||
|
this._onDidDrop.fire(event);
|
||||||
});
|
});
|
||||||
|
|
||||||
queue.push(() => {
|
queue.push(() => {
|
||||||
|
@ -61,7 +61,8 @@ export interface IPaneviewPanel
|
|||||||
|
|
||||||
export abstract class PaneviewPanel
|
export abstract class PaneviewPanel
|
||||||
extends BasePanelView<PaneviewPanelApiImpl>
|
extends BasePanelView<PaneviewPanelApiImpl>
|
||||||
implements IPaneview, IPaneviewPanel {
|
implements IPaneview, IPaneviewPanel
|
||||||
|
{
|
||||||
private _onDidChangeExpansionState: Emitter<boolean> = new Emitter<boolean>(
|
private _onDidChangeExpansionState: Emitter<boolean> = new Emitter<boolean>(
|
||||||
{ replay: true }
|
{ replay: true }
|
||||||
);
|
);
|
||||||
@ -71,6 +72,7 @@ export abstract class PaneviewPanel
|
|||||||
|
|
||||||
private headerSize = 22;
|
private headerSize = 22;
|
||||||
private _orthogonalSize = 0;
|
private _orthogonalSize = 0;
|
||||||
|
private _size = 0;
|
||||||
private _minimumBodySize = 0;
|
private _minimumBodySize = 0;
|
||||||
private _maximumBodySize: number = Number.POSITIVE_INFINITY;
|
private _maximumBodySize: number = Number.POSITIVE_INFINITY;
|
||||||
private _isExpanded = false;
|
private _isExpanded = false;
|
||||||
@ -80,7 +82,6 @@ export abstract class PaneviewPanel
|
|||||||
private headerPart?: IPaneBodyPart;
|
private headerPart?: IPaneBodyPart;
|
||||||
private expandedSize = 0;
|
private expandedSize = 0;
|
||||||
private animationTimer: any | undefined;
|
private animationTimer: any | undefined;
|
||||||
|
|
||||||
private _orientation: Orientation;
|
private _orientation: Orientation;
|
||||||
|
|
||||||
set orientation(value: Orientation) {
|
set orientation(value: Orientation) {
|
||||||
@ -107,6 +108,10 @@ export abstract class PaneviewPanel
|
|||||||
return headerSize + maximumBodySize;
|
return headerSize + maximumBodySize;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get size() {
|
||||||
|
return this._size;
|
||||||
|
}
|
||||||
|
|
||||||
get orthogonalSize() {
|
get orthogonalSize() {
|
||||||
return this._orthogonalSize;
|
return this._orthogonalSize;
|
||||||
}
|
}
|
||||||
@ -176,7 +181,7 @@ export abstract class PaneviewPanel
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
this.render();
|
this.renderOnce();
|
||||||
}
|
}
|
||||||
|
|
||||||
setVisible(isVisible: boolean) {
|
setVisible(isVisible: boolean) {
|
||||||
@ -216,6 +221,8 @@ export abstract class PaneviewPanel
|
|||||||
}
|
}
|
||||||
|
|
||||||
layout(size: number, orthogonalSize: number) {
|
layout(size: number, orthogonalSize: number) {
|
||||||
|
this._size = size;
|
||||||
|
this._orthogonalSize = orthogonalSize;
|
||||||
const [width, height] =
|
const [width, height] =
|
||||||
this.orientation === Orientation.HORIZONTAL
|
this.orientation === Orientation.HORIZONTAL
|
||||||
? [size, orthogonalSize]
|
? [size, orthogonalSize]
|
||||||
@ -259,7 +266,7 @@ export abstract class PaneviewPanel
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private render() {
|
private renderOnce() {
|
||||||
this.header = document.createElement('div');
|
this.header = document.createElement('div');
|
||||||
this.header.tabIndex = -1;
|
this.header.tabIndex = -1;
|
||||||
|
|
||||||
|
44
packages/dockview/src/react/dropTarget.tsx
Normal file
44
packages/dockview/src/react/dropTarget.tsx
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import {
|
||||||
|
CanDisplayOverlay,
|
||||||
|
Droptarget,
|
||||||
|
DropTargetDirections,
|
||||||
|
} from '../dnd/droptarget';
|
||||||
|
|
||||||
|
export interface IDragTragetProps {
|
||||||
|
canDisplayOverlay: CanDisplayOverlay;
|
||||||
|
validOverlays: DropTargetDirections;
|
||||||
|
children: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DockviewDropTarget = React.forwardRef(
|
||||||
|
(props: IDragTragetProps, ref: React.ForwardedRef<HTMLDivElement>) => {
|
||||||
|
const domRef = React.useRef<HTMLDivElement>(null);
|
||||||
|
const dropTargetRef = React.useRef<Droptarget>();
|
||||||
|
|
||||||
|
React.useImperativeHandle(ref, () => domRef.current!, []);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
dropTargetRef.current = new Droptarget(domRef.current!, {
|
||||||
|
canDisplayOverlay: props.canDisplayOverlay,
|
||||||
|
validOverlays: props.validOverlays,
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
dropTargetRef.current?.dispose();
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
dropTargetRef.current!.validOverlays = props.validOverlays;
|
||||||
|
}, [props.validOverlays]);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
dropTargetRef.current!.canDisplayOverlay = props.canDisplayOverlay;
|
||||||
|
}, [props.canDisplayOverlay]);
|
||||||
|
|
||||||
|
return <div ref={domRef}>{props.children}</div>;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
DockviewDropTarget.displayName = 'DockviewDropTarget';
|
@ -8,3 +8,4 @@ export * from '../gridview/gridviewPanel';
|
|||||||
export * from './paneview/paneview';
|
export * from './paneview/paneview';
|
||||||
export * from './types';
|
export * from './types';
|
||||||
export * from './react';
|
export * from './react';
|
||||||
|
export * from './dropTarget';
|
||||||
|
@ -9,6 +9,7 @@ import { PaneviewApi } from '../../api/component.api';
|
|||||||
import { PanePanelSection } from './view';
|
import { PanePanelSection } from './view';
|
||||||
import { PanelCollection, PanelParameters } from '../types';
|
import { PanelCollection, PanelParameters } from '../types';
|
||||||
import { watchElementResize } from '../../dom';
|
import { watchElementResize } from '../../dom';
|
||||||
|
import { DroptargetEvent } from '../../dnd/droptarget';
|
||||||
|
|
||||||
export interface PaneviewReadyEvent {
|
export interface PaneviewReadyEvent {
|
||||||
api: PaneviewApi;
|
api: PaneviewApi;
|
||||||
@ -21,12 +22,19 @@ export interface IPaneviewPanelProps<T extends {} = Record<string, any>>
|
|||||||
title: string;
|
title: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface PaneviewDropEvent {
|
||||||
|
api: PaneviewApi;
|
||||||
|
event: DroptargetEvent;
|
||||||
|
}
|
||||||
|
|
||||||
export interface IPaneviewReactProps {
|
export interface IPaneviewReactProps {
|
||||||
onReady?: (event: PaneviewReadyEvent) => void;
|
onReady?: (event: PaneviewReadyEvent) => void;
|
||||||
components?: PanelCollection<IPaneviewPanelProps>;
|
components?: PanelCollection<IPaneviewPanelProps>;
|
||||||
headerComponents?: PanelCollection<IPaneviewPanelProps>;
|
headerComponents?: PanelCollection<IPaneviewPanelProps>;
|
||||||
className?: string;
|
className?: string;
|
||||||
disableAutoResizing?: boolean;
|
disableAutoResizing?: boolean;
|
||||||
|
disableDnd?: boolean;
|
||||||
|
onDidDrop?(event: PaneviewDropEvent): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const PaneviewReact = React.forwardRef(
|
export const PaneviewReact = React.forwardRef(
|
||||||
@ -68,6 +76,7 @@ export const PaneviewReact = React.forwardRef(
|
|||||||
frameworkComponents: props.components,
|
frameworkComponents: props.components,
|
||||||
components: {},
|
components: {},
|
||||||
headerComponents: {},
|
headerComponents: {},
|
||||||
|
disableDnd: props.disableDnd,
|
||||||
headerframeworkComponents: props.headerComponents,
|
headerframeworkComponents: props.headerComponents,
|
||||||
frameworkWrapper: {
|
frameworkWrapper: {
|
||||||
header: {
|
header: {
|
||||||
@ -79,16 +88,25 @@ export const PaneviewReact = React.forwardRef(
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const api = new PaneviewApi(paneview);
|
||||||
|
|
||||||
|
const disposable = paneview.onDidDrop((event) => {
|
||||||
|
if (props.onDidDrop) {
|
||||||
|
props.onDidDrop({ event, api });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const { clientWidth, clientHeight } = domRef.current!;
|
const { clientWidth, clientHeight } = domRef.current!;
|
||||||
paneview.layout(clientWidth, clientHeight);
|
paneview.layout(clientWidth, clientHeight);
|
||||||
|
|
||||||
if (props.onReady) {
|
if (props.onReady) {
|
||||||
props.onReady({ api: new PaneviewApi(paneview) });
|
props.onReady({ api });
|
||||||
}
|
}
|
||||||
|
|
||||||
paneviewRef.current = paneview;
|
paneviewRef.current = paneview;
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
|
disposable.dispose();
|
||||||
paneview.dispose();
|
paneview.dispose();
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
@ -106,8 +106,12 @@ export class Splitview {
|
|||||||
private _proportions: number[] | undefined = undefined;
|
private _proportions: number[] | undefined = undefined;
|
||||||
private proportionalLayout: boolean;
|
private proportionalLayout: boolean;
|
||||||
|
|
||||||
private _onDidSashEnd = new Emitter<void>();
|
private readonly _onDidSashEnd = new Emitter<void>();
|
||||||
public onDidSashEnd = this._onDidSashEnd.event;
|
readonly onDidSashEnd = this._onDidSashEnd.event;
|
||||||
|
private readonly _onDidAddView = new Emitter<IView>();
|
||||||
|
readonly onDidAddView = this._onDidAddView.event;
|
||||||
|
private readonly _onDidRemoveView = new Emitter<IView>();
|
||||||
|
readonly onDidRemoveView = this._onDidAddView.event;
|
||||||
|
|
||||||
get size() {
|
get size() {
|
||||||
return this._size;
|
return this._size;
|
||||||
@ -548,6 +552,8 @@ export class Splitview {
|
|||||||
) {
|
) {
|
||||||
this.distributeViewSizes();
|
this.distributeViewSizes();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this._onDidAddView.fire(view);
|
||||||
}
|
}
|
||||||
|
|
||||||
distributeViewSizes(): void {
|
distributeViewSizes(): void {
|
||||||
@ -602,6 +608,8 @@ export class Splitview {
|
|||||||
this.distributeViewSizes();
|
this.distributeViewSizes();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this._onDidRemoveView.fire(viewItem.view);
|
||||||
|
|
||||||
return viewItem.view;
|
return viewItem.view;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1033,6 +1041,10 @@ export class Splitview {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public dispose() {
|
public dispose() {
|
||||||
|
this._onDidSashEnd.dispose();
|
||||||
|
this._onDidAddView.dispose();
|
||||||
|
this._onDidRemoveView.dispose();
|
||||||
|
|
||||||
this.element.remove();
|
this.element.remove();
|
||||||
for (let i = 0; i < this.element.children.length; i++) {
|
for (let i = 0; i < this.element.children.length; i++) {
|
||||||
if (this.element.children.item(i) === this.element) {
|
if (this.element.children.item(i) === this.element) {
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
--dv-tab-close-icon: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path d="M0 0h24v24H0z" fill="none"/><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/></svg>');
|
--dv-tab-close-icon: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path d="M0 0h24v24H0z" fill="none"/><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/></svg>');
|
||||||
--dv-tab-dirty-icon: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path d="M0 0h24v24H0z" fill="none"/><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2z"/></svg>');
|
--dv-tab-dirty-icon: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path d="M0 0h24v24H0z" fill="none"/><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2z"/></svg>');
|
||||||
--dv-drag-over-background-color: rgba(83, 89, 93, 0.5);
|
--dv-drag-over-background-color: rgba(83, 89, 93, 0.5);
|
||||||
|
--dv-drag-over-border-color: white;
|
||||||
--dv-tabs-container-scrollbar-color: #888;
|
--dv-tabs-container-scrollbar-color: #888;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user