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> {
|
||||
addFloating() {
|
||||
return this.component.addFloating();
|
||||
}
|
||||
|
||||
get id(): string {
|
||||
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 { DockviewPanelModel } from './dockviewPanelModel';
|
||||
import { getPanelData } from '../dnd/dataTransfer';
|
||||
import { Overlay } from '../dnd/overlay';
|
||||
|
||||
export interface PanelReference {
|
||||
update: (event: { params: { [key: string]: any } }) => void;
|
||||
@ -117,6 +118,7 @@ export interface IDockviewComponent extends IBaseGrid<DockviewGroupPanel> {
|
||||
readonly onDidAddPanel: Event<IDockviewPanel>;
|
||||
readonly onDidLayoutFromJSON: Event<void>;
|
||||
readonly onDidActivePanelChange: Event<IDockviewPanel | undefined>;
|
||||
addFloating(): void;
|
||||
}
|
||||
|
||||
export class DockviewComponent
|
||||
@ -288,6 +290,8 @@ export class DockviewComponent
|
||||
this._api = new DockviewApi(this);
|
||||
|
||||
this.updateWatermark();
|
||||
|
||||
this.element.style.position = 'relative';
|
||||
}
|
||||
|
||||
private orthogonalize(position: Position): DockviewGroupPanel {
|
||||
@ -984,4 +988,60 @@ export class DockviewComponent
|
||||
this._onDidRemovePanel.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