mirror of
https://github.com/mathuo/dockview
synced 2025-02-13 11:55:45 +00:00
feature: floating groups
This commit is contained in:
parent
1f384c3c65
commit
ffd5db273e
@ -329,6 +329,10 @@ export class GridviewApi implements CommonApi<SerializedGridviewComponent> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class DockviewApi implements CommonApi<SerializedDockview> {
|
export class DockviewApi implements CommonApi<SerializedDockview> {
|
||||||
|
addFloating() {
|
||||||
|
return this.component.addFloating();
|
||||||
|
}
|
||||||
|
|
||||||
get id(): string {
|
get id(): string {
|
||||||
return this.component.id;
|
return this.component.id;
|
||||||
}
|
}
|
||||||
|
106
packages/dockview-core/src/dnd/overlay.scss
Normal file
106
packages/dockview-core/src/dnd/overlay.scss
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
.dv-resize-container {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 9998;
|
||||||
|
|
||||||
|
background-color: white;
|
||||||
|
|
||||||
|
&.dv-resize-container-dragging {
|
||||||
|
opacity: 0.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dv-resize-handle-top {
|
||||||
|
height: 4px;
|
||||||
|
width: calc(100% - 8px);
|
||||||
|
left: 4px;
|
||||||
|
top: -2px;
|
||||||
|
z-index: 9999;
|
||||||
|
position: absolute;
|
||||||
|
cursor: ns-resize;
|
||||||
|
|
||||||
|
background-color: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dv-resize-handle-bottom {
|
||||||
|
height: 4px;
|
||||||
|
width: calc(100% - 8px);
|
||||||
|
left: 4px;
|
||||||
|
bottom: -2px;
|
||||||
|
z-index: 9999;
|
||||||
|
position: absolute;
|
||||||
|
cursor: ns-resize;
|
||||||
|
|
||||||
|
background-color: green;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dv-resize-handle-left {
|
||||||
|
height: calc(100% - 8px);
|
||||||
|
width: 4px;
|
||||||
|
left: -2px;
|
||||||
|
top: 4px;
|
||||||
|
z-index: 9999;
|
||||||
|
position: absolute;
|
||||||
|
cursor: ew-resize;
|
||||||
|
|
||||||
|
background-color: yellow;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dv-resize-handle-right {
|
||||||
|
height: calc(100% - 8px);
|
||||||
|
width: 4px;
|
||||||
|
right: -2px;
|
||||||
|
top: 4px;
|
||||||
|
z-index: 9999;
|
||||||
|
position: absolute;
|
||||||
|
cursor: ew-resize;
|
||||||
|
|
||||||
|
background-color: blue;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dv-resize-handle-topleft {
|
||||||
|
height: 4px;
|
||||||
|
width: 4px;
|
||||||
|
top: -2px;
|
||||||
|
left: -2px;
|
||||||
|
z-index: 9999;
|
||||||
|
position: absolute;
|
||||||
|
cursor: nw-resize;
|
||||||
|
|
||||||
|
background-color: cyan;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dv-resize-handle-topright {
|
||||||
|
height: 4px;
|
||||||
|
width: 4px;
|
||||||
|
right: -2px;
|
||||||
|
top: -2px;
|
||||||
|
z-index: 9999;
|
||||||
|
position: absolute;
|
||||||
|
cursor: ne-resize;
|
||||||
|
|
||||||
|
background-color: cyan;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dv-resize-handle-bottomleft {
|
||||||
|
height: 4px;
|
||||||
|
width: 4px;
|
||||||
|
left: -2px;
|
||||||
|
bottom: -2px;
|
||||||
|
z-index: 9999;
|
||||||
|
position: absolute;
|
||||||
|
cursor: sw-resize;
|
||||||
|
|
||||||
|
background-color: cyan;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dv-resize-handle-bottomright {
|
||||||
|
height: 4px;
|
||||||
|
width: 4px;
|
||||||
|
right: -2px;
|
||||||
|
bottom: -2px;
|
||||||
|
z-index: 9999;
|
||||||
|
position: absolute;
|
||||||
|
cursor: se-resize;
|
||||||
|
|
||||||
|
background-color: cyan;
|
||||||
|
}
|
||||||
|
}
|
266
packages/dockview-core/src/dnd/overlay.ts
Normal file
266
packages/dockview-core/src/dnd/overlay.ts
Normal file
@ -0,0 +1,266 @@
|
|||||||
|
import { toggleClass } from '../dom';
|
||||||
|
import { addDisposableListener, addDisposableWindowListener } from '../events';
|
||||||
|
import { CompositeDisposable, MutableDisposable } from '../lifecycle';
|
||||||
|
import { clamp } from '../math';
|
||||||
|
|
||||||
|
export class Overlay extends CompositeDisposable {
|
||||||
|
private _element: HTMLElement = document.createElement('div');
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private readonly container: HTMLElement,
|
||||||
|
private readonly content: HTMLElement,
|
||||||
|
private readonly options: {
|
||||||
|
height: number;
|
||||||
|
width: number;
|
||||||
|
left: number;
|
||||||
|
top: number;
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.setupOverlay();
|
||||||
|
this.setupDrag();
|
||||||
|
this.setupResize('top');
|
||||||
|
this.setupResize('bottom');
|
||||||
|
this.setupResize('left');
|
||||||
|
this.setupResize('right');
|
||||||
|
this.setupResize('topleft');
|
||||||
|
this.setupResize('topright');
|
||||||
|
this.setupResize('bottomleft');
|
||||||
|
this.setupResize('bottomright');
|
||||||
|
|
||||||
|
this._element.appendChild(content);
|
||||||
|
this.container.appendChild(this._element);
|
||||||
|
}
|
||||||
|
|
||||||
|
private setupResize(
|
||||||
|
direction:
|
||||||
|
| 'top'
|
||||||
|
| 'bottom'
|
||||||
|
| 'left'
|
||||||
|
| 'right'
|
||||||
|
| 'topleft'
|
||||||
|
| 'topright'
|
||||||
|
| 'bottomleft'
|
||||||
|
| 'bottomright'
|
||||||
|
): void {
|
||||||
|
const resizeHandleElement = document.createElement('div');
|
||||||
|
resizeHandleElement.className = `dv-resize-handle-${direction}`;
|
||||||
|
this._element.appendChild(resizeHandleElement);
|
||||||
|
|
||||||
|
const move = new MutableDisposable();
|
||||||
|
|
||||||
|
this.addDisposables(
|
||||||
|
move,
|
||||||
|
addDisposableListener(resizeHandleElement, 'mousedown', (_) => {
|
||||||
|
_.preventDefault();
|
||||||
|
|
||||||
|
let offset: {
|
||||||
|
originalY: number;
|
||||||
|
originalHeight: number;
|
||||||
|
originalX: number;
|
||||||
|
originalWidth: number;
|
||||||
|
} | null = null;
|
||||||
|
|
||||||
|
move.value = new CompositeDisposable(
|
||||||
|
addDisposableWindowListener(window, 'mousemove', (e) => {
|
||||||
|
const rect = this.container.getBoundingClientRect();
|
||||||
|
const y = e.clientY - rect.top;
|
||||||
|
const x = e.clientX - rect.left;
|
||||||
|
|
||||||
|
const rect2 = this._element.getBoundingClientRect();
|
||||||
|
|
||||||
|
if (offset === null) {
|
||||||
|
offset = {
|
||||||
|
originalY: y,
|
||||||
|
originalHeight: rect2.height,
|
||||||
|
originalX: x,
|
||||||
|
originalWidth: rect2.width,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let top: number | null = null;
|
||||||
|
let height: number | null = null;
|
||||||
|
let left: number | null = null;
|
||||||
|
let width: number | null = null;
|
||||||
|
|
||||||
|
const MIN_HEIGHT = 20;
|
||||||
|
const MIN_WIDTH = 20;
|
||||||
|
|
||||||
|
function moveTop() {
|
||||||
|
top = clamp(
|
||||||
|
y,
|
||||||
|
0,
|
||||||
|
offset!.originalY +
|
||||||
|
offset!.originalHeight -
|
||||||
|
MIN_HEIGHT
|
||||||
|
);
|
||||||
|
height =
|
||||||
|
offset!.originalY +
|
||||||
|
offset!.originalHeight -
|
||||||
|
top;
|
||||||
|
}
|
||||||
|
|
||||||
|
function moveBottom() {
|
||||||
|
top = offset!.originalY - offset!.originalHeight;
|
||||||
|
|
||||||
|
height = clamp(
|
||||||
|
y - top,
|
||||||
|
MIN_HEIGHT,
|
||||||
|
rect.height -
|
||||||
|
offset!.originalY +
|
||||||
|
offset!.originalHeight
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function moveLeft() {
|
||||||
|
left = clamp(
|
||||||
|
x,
|
||||||
|
0,
|
||||||
|
offset!.originalX +
|
||||||
|
offset!.originalWidth -
|
||||||
|
MIN_WIDTH
|
||||||
|
);
|
||||||
|
width =
|
||||||
|
offset!.originalX +
|
||||||
|
offset!.originalWidth -
|
||||||
|
left;
|
||||||
|
}
|
||||||
|
|
||||||
|
function moveRight() {
|
||||||
|
left = offset!.originalX - offset!.originalWidth;
|
||||||
|
width = clamp(
|
||||||
|
x - left,
|
||||||
|
MIN_WIDTH,
|
||||||
|
rect.width -
|
||||||
|
offset!.originalX +
|
||||||
|
offset!.originalWidth
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (direction) {
|
||||||
|
case 'top':
|
||||||
|
moveTop();
|
||||||
|
break;
|
||||||
|
case 'bottom':
|
||||||
|
moveBottom();
|
||||||
|
break;
|
||||||
|
case 'left':
|
||||||
|
moveLeft();
|
||||||
|
break;
|
||||||
|
case 'right':
|
||||||
|
moveRight();
|
||||||
|
break;
|
||||||
|
case 'topleft':
|
||||||
|
moveTop();
|
||||||
|
moveLeft();
|
||||||
|
break;
|
||||||
|
case 'topright':
|
||||||
|
moveTop();
|
||||||
|
moveRight();
|
||||||
|
break;
|
||||||
|
case 'bottomleft':
|
||||||
|
moveBottom();
|
||||||
|
moveLeft();
|
||||||
|
break;
|
||||||
|
case 'bottomright':
|
||||||
|
moveBottom();
|
||||||
|
moveRight();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (height !== null) {
|
||||||
|
this._element.style.height = `${height}px`;
|
||||||
|
}
|
||||||
|
if (top !== null) {
|
||||||
|
this._element.style.top = `${top}px`;
|
||||||
|
}
|
||||||
|
if (left !== null) {
|
||||||
|
this._element.style.left = `${left}px`;
|
||||||
|
}
|
||||||
|
if (width !== null) {
|
||||||
|
this._element.style.width = `${width}px`;
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
addDisposableWindowListener(window, 'mouseup', () => {
|
||||||
|
move.dispose();
|
||||||
|
})
|
||||||
|
);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private setupOverlay(): void {
|
||||||
|
this._element.style.height = `${this.options.height}px`;
|
||||||
|
this._element.style.width = `${this.options.width}px`;
|
||||||
|
this._element.style.left = `${this.options.left}px`;
|
||||||
|
this._element.style.top = `${this.options.top}px`;
|
||||||
|
//
|
||||||
|
this._element.className = 'dv-resize-container';
|
||||||
|
}
|
||||||
|
|
||||||
|
private setupDrag(): void {
|
||||||
|
const move = new MutableDisposable();
|
||||||
|
|
||||||
|
this.addDisposables(
|
||||||
|
move,
|
||||||
|
addDisposableListener(this._element, 'mousedown', (_) => {
|
||||||
|
if (_.defaultPrevented) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let offset: { x: number; y: number } | null = null;
|
||||||
|
|
||||||
|
move.value = new CompositeDisposable(
|
||||||
|
addDisposableWindowListener(window, 'mousemove', (e) => {
|
||||||
|
const rect = this.container.getBoundingClientRect();
|
||||||
|
const x = e.clientX - rect.left;
|
||||||
|
const y = e.clientY - rect.top;
|
||||||
|
|
||||||
|
toggleClass(
|
||||||
|
this._element,
|
||||||
|
'dv-resize-container-dragging',
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
const rect2 = this._element.getBoundingClientRect();
|
||||||
|
if (offset === null) {
|
||||||
|
offset = {
|
||||||
|
x: e.clientX - rect2.left,
|
||||||
|
y: e.clientY - rect2.top,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const left = clamp(
|
||||||
|
Math.max(0, x - offset.x),
|
||||||
|
0,
|
||||||
|
rect.width - rect2.width
|
||||||
|
);
|
||||||
|
|
||||||
|
const top = clamp(
|
||||||
|
Math.max(0, y - offset.y),
|
||||||
|
0,
|
||||||
|
rect.height - rect2.height
|
||||||
|
);
|
||||||
|
|
||||||
|
this._element.style.left = `${left}px`;
|
||||||
|
this._element.style.top = `${top}px`;
|
||||||
|
}),
|
||||||
|
addDisposableWindowListener(window, 'mouseup', () => {
|
||||||
|
toggleClass(
|
||||||
|
this._element,
|
||||||
|
'dv-resize-container-dragging',
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
|
move.dispose();
|
||||||
|
})
|
||||||
|
);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
dispose(): void {
|
||||||
|
this._element.remove();
|
||||||
|
}
|
||||||
|
}
|
@ -44,6 +44,7 @@ import {
|
|||||||
import { DockviewGroupPanel, IDockviewGroupPanel } from './dockviewGroupPanel';
|
import { DockviewGroupPanel, IDockviewGroupPanel } from './dockviewGroupPanel';
|
||||||
import { DockviewPanelModel } from './dockviewPanelModel';
|
import { DockviewPanelModel } from './dockviewPanelModel';
|
||||||
import { getPanelData } from '../dnd/dataTransfer';
|
import { getPanelData } from '../dnd/dataTransfer';
|
||||||
|
import { Overlay } from '../dnd/overlay';
|
||||||
|
|
||||||
export interface PanelReference {
|
export interface PanelReference {
|
||||||
update: (event: { params: { [key: string]: any } }) => void;
|
update: (event: { params: { [key: string]: any } }) => void;
|
||||||
@ -117,6 +118,7 @@ export interface IDockviewComponent extends IBaseGrid<DockviewGroupPanel> {
|
|||||||
readonly onDidAddPanel: Event<IDockviewPanel>;
|
readonly onDidAddPanel: Event<IDockviewPanel>;
|
||||||
readonly onDidLayoutFromJSON: Event<void>;
|
readonly onDidLayoutFromJSON: Event<void>;
|
||||||
readonly onDidActivePanelChange: Event<IDockviewPanel | undefined>;
|
readonly onDidActivePanelChange: Event<IDockviewPanel | undefined>;
|
||||||
|
addFloating(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class DockviewComponent
|
export class DockviewComponent
|
||||||
@ -288,6 +290,8 @@ export class DockviewComponent
|
|||||||
this._api = new DockviewApi(this);
|
this._api = new DockviewApi(this);
|
||||||
|
|
||||||
this.updateWatermark();
|
this.updateWatermark();
|
||||||
|
|
||||||
|
this.element.style.position = 'relative';
|
||||||
}
|
}
|
||||||
|
|
||||||
private orthogonalize(position: Position): DockviewGroupPanel {
|
private orthogonalize(position: Position): DockviewGroupPanel {
|
||||||
@ -984,4 +988,60 @@ export class DockviewComponent
|
|||||||
this._onDidRemovePanel.dispose();
|
this._onDidRemovePanel.dispose();
|
||||||
this._onDidLayoutFromJSON.dispose();
|
this._onDidLayoutFromJSON.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
|
||||||
|
addFloating() {
|
||||||
|
const parentDockview = this;
|
||||||
|
|
||||||
|
const floatingDockview = new DockviewComponent({
|
||||||
|
...this.options,
|
||||||
|
parentElement: undefined,
|
||||||
|
showDndOverlay: (event) => {
|
||||||
|
const data = event.getData();
|
||||||
|
|
||||||
|
if (data && data.viewId === parentDockview.id) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
floatingDockview.onDidDrop((event) => {
|
||||||
|
const data = event.getData();
|
||||||
|
|
||||||
|
if (!data || data.viewId !== parentDockview.id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.panelId === null) {
|
||||||
|
const group = parentDockview.removeGroup(
|
||||||
|
parentDockview.getPanel(data.groupId)!
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
const panel = parentDockview.removePanel(
|
||||||
|
parentDockview.getGroupPanel(data.panelId)!
|
||||||
|
);
|
||||||
|
|
||||||
|
parentDockview.moveGroupOrPanel()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
floatingDockview.addPanel({
|
||||||
|
id: '__test__',
|
||||||
|
component: 'default',
|
||||||
|
});
|
||||||
|
floatingDockview.addPanel({
|
||||||
|
id: '__test__2__',
|
||||||
|
component: 'default',
|
||||||
|
});
|
||||||
|
|
||||||
|
const overlay = new Overlay(this.element, floatingDockview.element, {
|
||||||
|
height: 300,
|
||||||
|
width: 300,
|
||||||
|
left: 100,
|
||||||
|
top: 100,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user