mirror of
https://github.com/mathuo/dockview
synced 2025-02-13 03:45:47 +00:00
feat: experimental floating groups
This commit is contained in:
parent
7fdede6952
commit
5b493b95e0
@ -329,10 +329,6 @@ 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;
|
||||||
}
|
}
|
||||||
|
@ -61,3 +61,13 @@ export function firstIndex<T>(
|
|||||||
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function remove<T>(array: T[], value: T): boolean {
|
||||||
|
const index = array.findIndex((t) => t === value);
|
||||||
|
|
||||||
|
if (index > -1) {
|
||||||
|
array.splice(index, 1);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
@ -21,10 +21,21 @@ export abstract class DragHandler extends CompositeDisposable {
|
|||||||
|
|
||||||
abstract getData(dataTransfer?: DataTransfer | null): IDisposable;
|
abstract getData(dataTransfer?: DataTransfer | null): IDisposable;
|
||||||
|
|
||||||
|
protected isCancelled(_event: DragEvent): boolean {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
private configure(): void {
|
private configure(): void {
|
||||||
this.addDisposables(
|
this.addDisposables(
|
||||||
this._onDragStart,
|
this._onDragStart,
|
||||||
addDisposableListener(this.el, 'dragstart', (event) => {
|
addDisposableListener(this.el, 'dragstart', (event) => {
|
||||||
|
if (this.isCancelled(event)) {
|
||||||
|
event.preventDefault();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.disposable.value = this.getData(event.dataTransfer);
|
||||||
|
|
||||||
this.iframes = [
|
this.iframes = [
|
||||||
...getElementsByTagName('iframe'),
|
...getElementsByTagName('iframe'),
|
||||||
...getElementsByTagName('webview'),
|
...getElementsByTagName('webview'),
|
||||||
@ -37,8 +48,6 @@ export abstract class DragHandler extends CompositeDisposable {
|
|||||||
this.el.classList.add('dv-dragged');
|
this.el.classList.add('dv-dragged');
|
||||||
setTimeout(() => this.el.classList.remove('dv-dragged'), 0);
|
setTimeout(() => this.el.classList.remove('dv-dragged'), 0);
|
||||||
|
|
||||||
this.disposable.value = this.getData(event.dataTransfer);
|
|
||||||
|
|
||||||
if (event.dataTransfer) {
|
if (event.dataTransfer) {
|
||||||
event.dataTransfer.effectAllowed = 'move';
|
event.dataTransfer.effectAllowed = 'move';
|
||||||
|
|
||||||
|
@ -58,6 +58,7 @@ export class Droptarget extends CompositeDisposable {
|
|||||||
private targetElement: HTMLElement | undefined;
|
private targetElement: HTMLElement | undefined;
|
||||||
private overlayElement: HTMLElement | undefined;
|
private overlayElement: HTMLElement | undefined;
|
||||||
private _state: Position | undefined;
|
private _state: Position | undefined;
|
||||||
|
private _acceptedTargetZonesSet: Set<Position>;
|
||||||
|
|
||||||
private readonly _onDrop = new Emitter<DroptargetEvent>();
|
private readonly _onDrop = new Emitter<DroptargetEvent>();
|
||||||
readonly onDrop: Event<DroptargetEvent> = this._onDrop.event;
|
readonly onDrop: Event<DroptargetEvent> = this._onDrop.event;
|
||||||
@ -83,7 +84,7 @@ export class Droptarget extends CompositeDisposable {
|
|||||||
super();
|
super();
|
||||||
|
|
||||||
// use a set to take advantage of #<set>.has
|
// use a set to take advantage of #<set>.has
|
||||||
const acceptedTargetZonesSet = new Set(
|
this._acceptedTargetZonesSet = new Set(
|
||||||
this.options.acceptedTargetZones
|
this.options.acceptedTargetZones
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -106,7 +107,7 @@ export class Droptarget extends CompositeDisposable {
|
|||||||
const y = e.clientY - rect.top;
|
const y = e.clientY - rect.top;
|
||||||
|
|
||||||
const quadrant = this.calculateQuadrant(
|
const quadrant = this.calculateQuadrant(
|
||||||
acceptedTargetZonesSet,
|
this._acceptedTargetZonesSet,
|
||||||
x,
|
x,
|
||||||
y,
|
y,
|
||||||
width,
|
width,
|
||||||
@ -175,6 +176,10 @@ export class Droptarget extends CompositeDisposable {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setTargetZones(acceptedTargetZones: Position[]): void {
|
||||||
|
this._acceptedTargetZonesSet = new Set(acceptedTargetZones);
|
||||||
|
}
|
||||||
|
|
||||||
public dispose(): void {
|
public dispose(): void {
|
||||||
this.removeDropTarget();
|
this.removeDropTarget();
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,13 @@ export class GroupDragHandler extends DragHandler {
|
|||||||
super(element);
|
super(element);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override isCancelled(_event: DragEvent): boolean {
|
||||||
|
if (this.group.model.isFloating) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
getData(dataTransfer: DataTransfer | null): IDisposable {
|
getData(dataTransfer: DataTransfer | null): IDisposable {
|
||||||
this.panelTransfer.setData(
|
this.panelTransfer.setData(
|
||||||
[new PanelTransfer(this.accessorId, this.group.id, null)],
|
[new PanelTransfer(this.accessorId, this.group.id, null)],
|
||||||
|
@ -1,11 +1,43 @@
|
|||||||
|
.dv-debug {
|
||||||
|
.dv-resize-container {
|
||||||
|
.dv-resize-handle-top {
|
||||||
|
background-color: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dv-resize-handle-bottom {
|
||||||
|
background-color: green;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dv-resize-handle-left {
|
||||||
|
background-color: yellow;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dv-resize-handle-right {
|
||||||
|
background-color: blue;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dv-resize-handle-topleft,
|
||||||
|
.dv-resize-handle-topright,
|
||||||
|
.dv-resize-handle-bottomleft,
|
||||||
|
.dv-resize-handle-bottomright {
|
||||||
|
background-color: cyan;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.dv-resize-container {
|
.dv-resize-container {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: 9998;
|
z-index: 9997;
|
||||||
|
|
||||||
background-color: white;
|
&.dv-resize-container-priority {
|
||||||
|
z-index: 9998;
|
||||||
|
}
|
||||||
|
|
||||||
|
border: 1px solid var(--dv-tab-divider-color);
|
||||||
|
box-shadow: var(--dv-floating-box-shadow);
|
||||||
|
|
||||||
&.dv-resize-container-dragging {
|
&.dv-resize-container-dragging {
|
||||||
opacity: 0.2;
|
opacity: 0.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dv-resize-handle-top {
|
.dv-resize-handle-top {
|
||||||
@ -16,8 +48,6 @@
|
|||||||
z-index: 9999;
|
z-index: 9999;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
cursor: ns-resize;
|
cursor: ns-resize;
|
||||||
|
|
||||||
background-color: red;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.dv-resize-handle-bottom {
|
.dv-resize-handle-bottom {
|
||||||
@ -28,8 +58,6 @@
|
|||||||
z-index: 9999;
|
z-index: 9999;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
cursor: ns-resize;
|
cursor: ns-resize;
|
||||||
|
|
||||||
background-color: green;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.dv-resize-handle-left {
|
.dv-resize-handle-left {
|
||||||
@ -40,8 +68,6 @@
|
|||||||
z-index: 9999;
|
z-index: 9999;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
cursor: ew-resize;
|
cursor: ew-resize;
|
||||||
|
|
||||||
background-color: yellow;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.dv-resize-handle-right {
|
.dv-resize-handle-right {
|
||||||
@ -52,8 +78,6 @@
|
|||||||
z-index: 9999;
|
z-index: 9999;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
cursor: ew-resize;
|
cursor: ew-resize;
|
||||||
|
|
||||||
background-color: blue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.dv-resize-handle-topleft {
|
.dv-resize-handle-topleft {
|
||||||
@ -64,8 +88,6 @@
|
|||||||
z-index: 9999;
|
z-index: 9999;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
cursor: nw-resize;
|
cursor: nw-resize;
|
||||||
|
|
||||||
background-color: cyan;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.dv-resize-handle-topright {
|
.dv-resize-handle-topright {
|
||||||
@ -76,8 +98,6 @@
|
|||||||
z-index: 9999;
|
z-index: 9999;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
cursor: ne-resize;
|
cursor: ne-resize;
|
||||||
|
|
||||||
background-color: cyan;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.dv-resize-handle-bottomleft {
|
.dv-resize-handle-bottomleft {
|
||||||
@ -88,8 +108,6 @@
|
|||||||
z-index: 9999;
|
z-index: 9999;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
cursor: sw-resize;
|
cursor: sw-resize;
|
||||||
|
|
||||||
background-color: cyan;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.dv-resize-handle-bottomright {
|
.dv-resize-handle-bottomright {
|
||||||
@ -100,7 +118,5 @@
|
|||||||
z-index: 9999;
|
z-index: 9999;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
cursor: se-resize;
|
cursor: se-resize;
|
||||||
|
|
||||||
background-color: cyan;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,23 +3,40 @@ import { addDisposableListener, addDisposableWindowListener } from '../events';
|
|||||||
import { CompositeDisposable, MutableDisposable } from '../lifecycle';
|
import { CompositeDisposable, MutableDisposable } from '../lifecycle';
|
||||||
import { clamp } from '../math';
|
import { clamp } from '../math';
|
||||||
|
|
||||||
|
const bringElementToFront = (() => {
|
||||||
|
let previous: HTMLElement | null = null;
|
||||||
|
|
||||||
|
function pushToTop(element: HTMLElement) {
|
||||||
|
if (previous !== element && previous !== null) {
|
||||||
|
toggleClass(previous, 'dv-resize-container-priority', false);
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleClass(element, 'dv-resize-container-priority', true);
|
||||||
|
previous = element;
|
||||||
|
}
|
||||||
|
|
||||||
|
return pushToTop;
|
||||||
|
})();
|
||||||
|
|
||||||
export class Overlay extends CompositeDisposable {
|
export class Overlay extends CompositeDisposable {
|
||||||
private _element: HTMLElement = document.createElement('div');
|
private _element: HTMLElement = document.createElement('div');
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly container: HTMLElement,
|
|
||||||
private readonly content: HTMLElement,
|
|
||||||
private readonly options: {
|
private readonly options: {
|
||||||
height: number;
|
height: number;
|
||||||
width: number;
|
width: number;
|
||||||
left: number;
|
left: number;
|
||||||
top: number;
|
top: number;
|
||||||
|
container: HTMLElement;
|
||||||
|
content: HTMLElement;
|
||||||
|
minX: number;
|
||||||
|
minY: number;
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.setupOverlay();
|
this.setupOverlay();
|
||||||
this.setupDrag();
|
// this.setupDrag(true,this._element);
|
||||||
this.setupResize('top');
|
this.setupResize('top');
|
||||||
this.setupResize('bottom');
|
this.setupResize('bottom');
|
||||||
this.setupResize('left');
|
this.setupResize('left');
|
||||||
@ -29,8 +46,10 @@ export class Overlay extends CompositeDisposable {
|
|||||||
this.setupResize('bottomleft');
|
this.setupResize('bottomleft');
|
||||||
this.setupResize('bottomright');
|
this.setupResize('bottomright');
|
||||||
|
|
||||||
this._element.appendChild(content);
|
this._element.appendChild(this.options.content);
|
||||||
this.container.appendChild(this._element);
|
this.options.container.appendChild(this._element);
|
||||||
|
|
||||||
|
// this.renderWithinBoundaryConditions();
|
||||||
}
|
}
|
||||||
|
|
||||||
private setupResize(
|
private setupResize(
|
||||||
@ -52,8 +71,8 @@ export class Overlay extends CompositeDisposable {
|
|||||||
|
|
||||||
this.addDisposables(
|
this.addDisposables(
|
||||||
move,
|
move,
|
||||||
addDisposableListener(resizeHandleElement, 'mousedown', (_) => {
|
addDisposableListener(resizeHandleElement, 'mousedown', (e) => {
|
||||||
_.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
let offset: {
|
let offset: {
|
||||||
originalY: number;
|
originalY: number;
|
||||||
@ -64,7 +83,8 @@ export class Overlay extends CompositeDisposable {
|
|||||||
|
|
||||||
move.value = new CompositeDisposable(
|
move.value = new CompositeDisposable(
|
||||||
addDisposableWindowListener(window, 'mousemove', (e) => {
|
addDisposableWindowListener(window, 'mousemove', (e) => {
|
||||||
const rect = this.container.getBoundingClientRect();
|
const rect =
|
||||||
|
this.options.container.getBoundingClientRect();
|
||||||
const y = e.clientY - rect.top;
|
const y = e.clientY - rect.top;
|
||||||
const x = e.clientX - rect.left;
|
const x = e.clientX - rect.left;
|
||||||
|
|
||||||
@ -91,9 +111,12 @@ export class Overlay extends CompositeDisposable {
|
|||||||
top = clamp(
|
top = clamp(
|
||||||
y,
|
y,
|
||||||
0,
|
0,
|
||||||
offset!.originalY +
|
Math.max(
|
||||||
offset!.originalHeight -
|
0,
|
||||||
MIN_HEIGHT
|
offset!.originalY +
|
||||||
|
offset!.originalHeight -
|
||||||
|
MIN_HEIGHT
|
||||||
|
)
|
||||||
);
|
);
|
||||||
height =
|
height =
|
||||||
offset!.originalY +
|
offset!.originalY +
|
||||||
@ -107,9 +130,12 @@ export class Overlay extends CompositeDisposable {
|
|||||||
height = clamp(
|
height = clamp(
|
||||||
y - top,
|
y - top,
|
||||||
MIN_HEIGHT,
|
MIN_HEIGHT,
|
||||||
rect.height -
|
Math.max(
|
||||||
offset!.originalY +
|
0,
|
||||||
offset!.originalHeight
|
rect.height -
|
||||||
|
offset!.originalY +
|
||||||
|
offset!.originalHeight
|
||||||
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -117,9 +143,12 @@ export class Overlay extends CompositeDisposable {
|
|||||||
left = clamp(
|
left = clamp(
|
||||||
x,
|
x,
|
||||||
0,
|
0,
|
||||||
offset!.originalX +
|
Math.max(
|
||||||
offset!.originalWidth -
|
0,
|
||||||
MIN_WIDTH
|
offset!.originalX +
|
||||||
|
offset!.originalWidth -
|
||||||
|
MIN_WIDTH
|
||||||
|
)
|
||||||
);
|
);
|
||||||
width =
|
width =
|
||||||
offset!.originalX +
|
offset!.originalX +
|
||||||
@ -132,9 +161,12 @@ export class Overlay extends CompositeDisposable {
|
|||||||
width = clamp(
|
width = clamp(
|
||||||
x - left,
|
x - left,
|
||||||
MIN_WIDTH,
|
MIN_WIDTH,
|
||||||
rect.width -
|
Math.max(
|
||||||
offset!.originalX +
|
0,
|
||||||
offset!.originalWidth
|
rect.width -
|
||||||
|
offset!.originalX +
|
||||||
|
offset!.originalWidth
|
||||||
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -199,68 +231,128 @@ export class Overlay extends CompositeDisposable {
|
|||||||
this._element.className = 'dv-resize-container';
|
this._element.className = 'dv-resize-container';
|
||||||
}
|
}
|
||||||
|
|
||||||
private setupDrag(): void {
|
setupDrag(connect: boolean, dragTarget: HTMLElement): void {
|
||||||
const move = new MutableDisposable();
|
const move = new MutableDisposable();
|
||||||
|
|
||||||
|
const track = () => {
|
||||||
|
let offset: { x: number; y: number } | null = null;
|
||||||
|
|
||||||
|
move.value = new CompositeDisposable(
|
||||||
|
addDisposableWindowListener(window, 'mousemove', (e) => {
|
||||||
|
const containerRect =
|
||||||
|
this.options.container.getBoundingClientRect();
|
||||||
|
const x = e.clientX - containerRect.left;
|
||||||
|
const y = e.clientY - containerRect.top;
|
||||||
|
|
||||||
|
toggleClass(
|
||||||
|
this._element,
|
||||||
|
'dv-resize-container-dragging',
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
const overlayRect = this._element.getBoundingClientRect();
|
||||||
|
if (offset === null) {
|
||||||
|
offset = {
|
||||||
|
x: e.clientX - overlayRect.left,
|
||||||
|
y: e.clientY - overlayRect.top,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const xOffset = Math.max(
|
||||||
|
0,
|
||||||
|
overlayRect.width - this.options.minX
|
||||||
|
);
|
||||||
|
const yOffset = Math.max(
|
||||||
|
0,
|
||||||
|
overlayRect.height - this.options.minY
|
||||||
|
);
|
||||||
|
|
||||||
|
const left = clamp(
|
||||||
|
x - offset.x,
|
||||||
|
-xOffset,
|
||||||
|
Math.max(
|
||||||
|
0,
|
||||||
|
containerRect.width - overlayRect.width + xOffset
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
const top = clamp(
|
||||||
|
y - offset.y,
|
||||||
|
-yOffset,
|
||||||
|
Math.max(
|
||||||
|
0,
|
||||||
|
containerRect.height - overlayRect.height + yOffset
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
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();
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
this.addDisposables(
|
this.addDisposables(
|
||||||
move,
|
move,
|
||||||
addDisposableListener(this._element, 'mousedown', (_) => {
|
addDisposableListener(dragTarget, 'mousedown', (_) => {
|
||||||
if (_.defaultPrevented) {
|
if (_.defaultPrevented) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let offset: { x: number; y: number } | null = null;
|
track();
|
||||||
|
}),
|
||||||
move.value = new CompositeDisposable(
|
addDisposableListener(this.options.content, 'mousedown', (_) => {
|
||||||
addDisposableWindowListener(window, 'mousemove', (e) => {
|
if (_.shiftKey) {
|
||||||
const rect = this.container.getBoundingClientRect();
|
track();
|
||||||
const x = e.clientX - rect.left;
|
}
|
||||||
const y = e.clientY - rect.top;
|
}),
|
||||||
|
addDisposableListener(
|
||||||
toggleClass(
|
this.options.content,
|
||||||
this._element,
|
'mousedown',
|
||||||
'dv-resize-container-dragging',
|
() => {
|
||||||
true
|
bringElementToFront(this._element);
|
||||||
);
|
},
|
||||||
|
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();
|
|
||||||
})
|
|
||||||
);
|
|
||||||
})
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (connect) {
|
||||||
|
track();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dispose(): void {
|
renderWithinBoundaryConditions(): void {
|
||||||
|
const rect = this.options.container.getBoundingClientRect();
|
||||||
|
const rect2 = this._element.getBoundingClientRect();
|
||||||
|
|
||||||
|
const left = clamp(
|
||||||
|
Math.max(this.options.left, 0),
|
||||||
|
0,
|
||||||
|
Math.max(0, rect.width - rect2.width)
|
||||||
|
);
|
||||||
|
|
||||||
|
const top = clamp(
|
||||||
|
Math.max(this.options.top, 0),
|
||||||
|
0,
|
||||||
|
Math.max(0, rect.height - rect2.height)
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log(new Error().stack);
|
||||||
|
|
||||||
|
this._element.style.left = `${left}px`;
|
||||||
|
this._element.style.top = `${top}px`;
|
||||||
|
}
|
||||||
|
|
||||||
|
override dispose(): void {
|
||||||
this._element.remove();
|
this._element.remove();
|
||||||
|
super.dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,7 @@ import { DockviewDropTargets, ITabRenderer } from '../../types';
|
|||||||
import { DockviewGroupPanel } from '../../dockviewGroupPanel';
|
import { DockviewGroupPanel } from '../../dockviewGroupPanel';
|
||||||
import { DroptargetEvent, Droptarget } from '../../../dnd/droptarget';
|
import { DroptargetEvent, Droptarget } from '../../../dnd/droptarget';
|
||||||
import { DragHandler } from '../../../dnd/abstractDragHandler';
|
import { DragHandler } from '../../../dnd/abstractDragHandler';
|
||||||
|
import { DockviewPanel } from '../../dockviewPanel';
|
||||||
|
|
||||||
export interface ITab {
|
export interface ITab {
|
||||||
readonly panelId: string;
|
readonly panelId: string;
|
||||||
@ -80,6 +81,19 @@ export class Tab extends CompositeDisposable implements ITab {
|
|||||||
|
|
||||||
this.addDisposables(
|
this.addDisposables(
|
||||||
addDisposableListener(this._element, 'mousedown', (event) => {
|
addDisposableListener(this._element, 'mousedown', (event) => {
|
||||||
|
if (event.shiftKey) {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
const panel = this.accessor.getGroupPanel(this.panelId);
|
||||||
|
|
||||||
|
const { top, left } = this.element.getBoundingClientRect();
|
||||||
|
|
||||||
|
this.accessor.addFloating(panel as DockviewPanel, {
|
||||||
|
x: left,
|
||||||
|
y: top,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (event.defaultPrevented) {
|
if (event.defaultPrevented) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -28,6 +28,7 @@ export class VoidContainer extends CompositeDisposable {
|
|||||||
this._element = document.createElement('div');
|
this._element = document.createElement('div');
|
||||||
|
|
||||||
this._element.className = 'void-container';
|
this._element.className = 'void-container';
|
||||||
|
this._element.id = 'dv-group-float-drag-handle';
|
||||||
this._element.tabIndex = 0;
|
this._element.tabIndex = 0;
|
||||||
this._element.draggable = true;
|
this._element.draggable = true;
|
||||||
|
|
||||||
@ -68,6 +69,16 @@ export class VoidContainer extends CompositeDisposable {
|
|||||||
|
|
||||||
this.addDisposables(
|
this.addDisposables(
|
||||||
handler,
|
handler,
|
||||||
|
addDisposableListener(this._element, 'mousedown', (event) => {
|
||||||
|
if (event.shiftKey && !this.group.model.isFloating) {
|
||||||
|
event.preventDefault();
|
||||||
|
this.accessor.addFloating(this.group, {
|
||||||
|
x: event.clientX + 20,
|
||||||
|
y: event.clientY + 20,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
this.voidDropTarget.onDrop((event) => {
|
this.voidDropTarget.onDrop((event) => {
|
||||||
this._onDrop.fire(event);
|
this._onDrop.fire(event);
|
||||||
}),
|
}),
|
||||||
|
@ -1,14 +1,15 @@
|
|||||||
.dv-dockview {
|
.dv-dockview {
|
||||||
position: relative;
|
position: relative;
|
||||||
background-color: var(--dv-group-view-background-color);
|
background-color: var(--dv-group-view-background-color);
|
||||||
|
|
||||||
.dv-watermark-container {
|
.dv-watermark-container {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0px;
|
top: 0px;
|
||||||
left: 0px;
|
left: 0px;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
z-index: 9999;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.groupview {
|
.groupview {
|
||||||
|
@ -5,9 +5,9 @@ import {
|
|||||||
ISerializedLeafNode,
|
ISerializedLeafNode,
|
||||||
} from '../gridview/gridview';
|
} from '../gridview/gridview';
|
||||||
import { directionToPosition, Droptarget, Position } from '../dnd/droptarget';
|
import { directionToPosition, Droptarget, Position } from '../dnd/droptarget';
|
||||||
import { tail, sequenceEquals } from '../array';
|
import { tail, sequenceEquals, remove } from '../array';
|
||||||
import { DockviewPanel, IDockviewPanel } from './dockviewPanel';
|
import { DockviewPanel, IDockviewPanel } from './dockviewPanel';
|
||||||
import { CompositeDisposable } from '../lifecycle';
|
import { CompositeDisposable, IDisposable } from '../lifecycle';
|
||||||
import { Event, Emitter } from '../events';
|
import { Event, Emitter } from '../events';
|
||||||
import { Watermark } from './components/watermark/watermark';
|
import { Watermark } from './components/watermark/watermark';
|
||||||
import {
|
import {
|
||||||
@ -45,6 +45,7 @@ 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';
|
import { Overlay } from '../dnd/overlay';
|
||||||
|
import { toggleClass } from '../dom';
|
||||||
|
|
||||||
export interface PanelReference {
|
export interface PanelReference {
|
||||||
update: (event: { params: { [key: string]: any } }) => void;
|
update: (event: { params: { [key: string]: any } }) => void;
|
||||||
@ -116,7 +117,10 @@ 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;
|
addFloating(
|
||||||
|
item: DockviewPanel | DockviewGroupPanel,
|
||||||
|
coord?: { x: number; y: number }
|
||||||
|
): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class DockviewComponent
|
export class DockviewComponent
|
||||||
@ -148,6 +152,12 @@ export class DockviewComponent
|
|||||||
readonly onDidActivePanelChange: Event<IDockviewPanel | undefined> =
|
readonly onDidActivePanelChange: Event<IDockviewPanel | undefined> =
|
||||||
this._onDidActivePanelChange.event;
|
this._onDidActivePanelChange.event;
|
||||||
|
|
||||||
|
private readonly floatingGroups: {
|
||||||
|
instance: DockviewGroupPanel;
|
||||||
|
disposable: IDisposable;
|
||||||
|
render: () => void;
|
||||||
|
}[] = [];
|
||||||
|
|
||||||
get orientation(): Orientation {
|
get orientation(): Orientation {
|
||||||
return this.gridview.orientation;
|
return this.gridview.orientation;
|
||||||
}
|
}
|
||||||
@ -182,7 +192,7 @@ export class DockviewComponent
|
|||||||
parentElement: options.parentElement,
|
parentElement: options.parentElement,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.element.classList.add('dv-dockview');
|
toggleClass(this.gridview.element, 'dv-dockview', true);
|
||||||
|
|
||||||
this.addDisposables(
|
this.addDisposables(
|
||||||
this._onDidDrop,
|
this._onDidDrop,
|
||||||
@ -277,8 +287,68 @@ export class DockviewComponent
|
|||||||
this._api = new DockviewApi(this);
|
this._api = new DockviewApi(this);
|
||||||
|
|
||||||
this.updateWatermark();
|
this.updateWatermark();
|
||||||
|
}
|
||||||
|
|
||||||
this.element.style.position = 'relative';
|
addFloating(
|
||||||
|
item: DockviewPanel | DockviewGroupPanel,
|
||||||
|
coord?: { x: number; y: number }
|
||||||
|
): void {
|
||||||
|
let group: DockviewGroupPanel;
|
||||||
|
|
||||||
|
if (item instanceof DockviewPanel) {
|
||||||
|
group = this.createGroup();
|
||||||
|
|
||||||
|
this.removePanel(item, {
|
||||||
|
removeEmptyGroup: true,
|
||||||
|
skipDispose: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
group.model.openPanel(item);
|
||||||
|
} else {
|
||||||
|
group = item;
|
||||||
|
this.doRemoveGroup(item, { skipDispose: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
group.model.isFloating = true;
|
||||||
|
|
||||||
|
const { left, top } = this.element.getBoundingClientRect();
|
||||||
|
|
||||||
|
const overlayLeft =
|
||||||
|
typeof coord?.x === 'number' ? Math.max(coord.x - left, 0) : 100;
|
||||||
|
const overlayTop =
|
||||||
|
typeof coord?.y === 'number' ? Math.max(0, coord.y - top) : 100;
|
||||||
|
|
||||||
|
const overlay = new Overlay({
|
||||||
|
container: this.gridview.element,
|
||||||
|
content: group.element,
|
||||||
|
height: 300,
|
||||||
|
width: 300,
|
||||||
|
left: overlayLeft,
|
||||||
|
top: overlayTop,
|
||||||
|
minX: 100,
|
||||||
|
minY: 100,
|
||||||
|
});
|
||||||
|
|
||||||
|
const el = group.element.querySelector('#dv-group-float-drag-handle');
|
||||||
|
|
||||||
|
if (el) {
|
||||||
|
overlay.setupDrag(true, el as HTMLElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
const instance = {
|
||||||
|
instance: group,
|
||||||
|
render: () => {
|
||||||
|
overlay.renderWithinBoundaryConditions();
|
||||||
|
},
|
||||||
|
disposable: new CompositeDisposable(overlay, {
|
||||||
|
dispose: () => {
|
||||||
|
group.model.isFloating = false;
|
||||||
|
remove(this.floatingGroups, instance);
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
this.floatingGroups.push(instance);
|
||||||
}
|
}
|
||||||
|
|
||||||
private orthogonalize(position: Position): DockviewGroupPanel {
|
private orthogonalize(position: Position): DockviewGroupPanel {
|
||||||
@ -329,6 +399,20 @@ export class DockviewComponent
|
|||||||
this.layout(this.gridview.width, this.gridview.height, true);
|
this.layout(this.gridview.width, this.gridview.height, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override layout(
|
||||||
|
width: number,
|
||||||
|
height: number,
|
||||||
|
forceResize?: boolean | undefined
|
||||||
|
): void {
|
||||||
|
super.layout(width, height, forceResize);
|
||||||
|
|
||||||
|
if (this.floatingGroups) {
|
||||||
|
for (const floating of this.floatingGroups) {
|
||||||
|
floating.render();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
focus(): void {
|
focus(): void {
|
||||||
this.activeGroup?.focus();
|
this.activeGroup?.focus();
|
||||||
}
|
}
|
||||||
@ -477,7 +561,7 @@ export class DockviewComponent
|
|||||||
|
|
||||||
for (const group of groups) {
|
for (const group of groups) {
|
||||||
// remove the group will automatically remove the panels
|
// remove the group will automatically remove the panels
|
||||||
this.removeGroup(group, true);
|
this.removeGroup(group, { skipActive: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasActiveGroup) {
|
if (hasActiveGroup) {
|
||||||
@ -591,7 +675,9 @@ export class DockviewComponent
|
|||||||
|
|
||||||
group.model.removePanel(panel);
|
group.model.removePanel(panel);
|
||||||
|
|
||||||
panel.dispose();
|
if (!options.skipDispose) {
|
||||||
|
panel.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
if (group.size === 0 && options.removeEmptyGroup) {
|
if (group.size === 0 && options.removeEmptyGroup) {
|
||||||
this.removeGroup(group);
|
this.removeGroup(group);
|
||||||
@ -625,7 +711,7 @@ export class DockviewComponent
|
|||||||
watermarkContainer.className = 'dv-watermark-container';
|
watermarkContainer.className = 'dv-watermark-container';
|
||||||
watermarkContainer.appendChild(this.watermark.element);
|
watermarkContainer.appendChild(this.watermark.element);
|
||||||
|
|
||||||
this.element.appendChild(watermarkContainer);
|
this.gridview.element.appendChild(watermarkContainer);
|
||||||
}
|
}
|
||||||
} else if (this.watermark) {
|
} else if (this.watermark) {
|
||||||
this.watermark.element.parentElement!.remove();
|
this.watermark.element.parentElement!.remove();
|
||||||
@ -695,17 +781,49 @@ export class DockviewComponent
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
removeGroup(group: DockviewGroupPanel, skipActive = false): void {
|
removeGroup(
|
||||||
|
group: DockviewGroupPanel,
|
||||||
|
options?:
|
||||||
|
| {
|
||||||
|
skipActive?: boolean | undefined;
|
||||||
|
skipDispose?: boolean | undefined;
|
||||||
|
}
|
||||||
|
| undefined
|
||||||
|
): void {
|
||||||
const panels = [...group.panels]; // reassign since group panels will mutate
|
const panels = [...group.panels]; // reassign since group panels will mutate
|
||||||
|
|
||||||
for (const panel of panels) {
|
for (const panel of panels) {
|
||||||
this.removePanel(panel, {
|
this.removePanel(panel, {
|
||||||
removeEmptyGroup: false,
|
removeEmptyGroup: false,
|
||||||
skipDispose: false,
|
skipDispose: options?.skipDispose ?? false,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
super.doRemoveGroup(group, { skipActive });
|
this.doRemoveGroup(group, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override doRemoveGroup(
|
||||||
|
group: DockviewGroupPanel,
|
||||||
|
options?:
|
||||||
|
| {
|
||||||
|
skipActive?: boolean | undefined;
|
||||||
|
skipDispose?: boolean | undefined;
|
||||||
|
}
|
||||||
|
| undefined
|
||||||
|
): DockviewGroupPanel {
|
||||||
|
const floatingGroup = this.floatingGroups.find(
|
||||||
|
(_) => _.instance === group
|
||||||
|
);
|
||||||
|
|
||||||
|
if (floatingGroup) {
|
||||||
|
if (!options?.skipDispose) {
|
||||||
|
floatingGroup.instance.dispose();
|
||||||
|
}
|
||||||
|
floatingGroup.disposable.dispose();
|
||||||
|
return floatingGroup.instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.doRemoveGroup(group, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
moveGroupOrPanel(
|
moveGroupOrPanel(
|
||||||
@ -721,7 +839,7 @@ export class DockviewComponent
|
|||||||
|
|
||||||
if (itemId === undefined) {
|
if (itemId === undefined) {
|
||||||
if (sourceGroup) {
|
if (sourceGroup) {
|
||||||
this.moveGroup(sourceGroup, referenceGroup, target);
|
this.moveGroup(sourceGroup, referenceGroup, target);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -750,34 +868,44 @@ export class DockviewComponent
|
|||||||
|
|
||||||
if (sourceGroup && sourceGroup.size < 2) {
|
if (sourceGroup && sourceGroup.size < 2) {
|
||||||
const [targetParentLocation, to] = tail(targetLocation);
|
const [targetParentLocation, to] = tail(targetLocation);
|
||||||
const sourceLocation = getGridLocation(sourceGroup.element);
|
|
||||||
const [sourceParentLocation, from] = tail(sourceLocation);
|
|
||||||
|
|
||||||
if (
|
const isFloating = this.floatingGroups.find(
|
||||||
sequenceEquals(sourceParentLocation, targetParentLocation)
|
(x) => x.instance === sourceGroup
|
||||||
) {
|
);
|
||||||
// special case when 'swapping' two views within same grid location
|
|
||||||
// if a group has one tab - we are essentially moving the 'group'
|
|
||||||
// which is equivalent to swapping two views in this case
|
|
||||||
this.gridview.moveView(sourceParentLocation, from, to);
|
|
||||||
} else {
|
|
||||||
// source group will become empty so delete the group
|
|
||||||
const targetGroup = this.doRemoveGroup(sourceGroup, {
|
|
||||||
skipActive: true,
|
|
||||||
skipDispose: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
// after deleting the group we need to re-evaulate the ref location
|
if (!isFloating) {
|
||||||
const updatedReferenceLocation = getGridLocation(
|
const sourceLocation = getGridLocation(sourceGroup.element);
|
||||||
referenceGroup.element
|
const [sourceParentLocation, from] = tail(sourceLocation);
|
||||||
);
|
|
||||||
const location = getRelativeLocation(
|
if (
|
||||||
this.gridview.orientation,
|
sequenceEquals(
|
||||||
updatedReferenceLocation,
|
sourceParentLocation,
|
||||||
target
|
targetParentLocation
|
||||||
);
|
)
|
||||||
this.doAddGroup(targetGroup, location);
|
) {
|
||||||
|
// special case when 'swapping' two views within same grid location
|
||||||
|
// if a group has one tab - we are essentially moving the 'group'
|
||||||
|
// which is equivalent to swapping two views in this case
|
||||||
|
this.gridview.moveView(sourceParentLocation, from, to);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// source group will become empty so delete the group
|
||||||
|
const targetGroup = this.doRemoveGroup(sourceGroup, {
|
||||||
|
skipActive: true,
|
||||||
|
skipDispose: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// after deleting the group we need to re-evaulate the ref location
|
||||||
|
const updatedReferenceLocation = getGridLocation(
|
||||||
|
referenceGroup.element
|
||||||
|
);
|
||||||
|
const location = getRelativeLocation(
|
||||||
|
this.gridview.orientation,
|
||||||
|
updatedReferenceLocation,
|
||||||
|
target
|
||||||
|
);
|
||||||
|
this.doAddGroup(targetGroup, location);
|
||||||
} else {
|
} else {
|
||||||
const groupItem: IDockviewPanel | undefined =
|
const groupItem: IDockviewPanel | undefined =
|
||||||
sourceGroup?.model.removePanel(itemId) ||
|
sourceGroup?.model.removePanel(itemId) ||
|
||||||
@ -821,7 +949,17 @@ export class DockviewComponent
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.gridview.removeView(getGridLocation(sourceGroup.element));
|
const floatingGroup = this.floatingGroups.find(
|
||||||
|
(x) => x.instance === sourceGroup
|
||||||
|
);
|
||||||
|
|
||||||
|
if (floatingGroup) {
|
||||||
|
floatingGroup.disposable.dispose();
|
||||||
|
} else {
|
||||||
|
this.gridview.removeView(
|
||||||
|
getGridLocation(sourceGroup.element)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const referenceLocation = getGridLocation(
|
const referenceLocation = getGridLocation(
|
||||||
referenceGroup.element
|
referenceGroup.element
|
||||||
@ -963,60 +1101,4 @@ 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,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,7 @@ export class DockviewGroupPanel
|
|||||||
extends GridviewPanel
|
extends GridviewPanel
|
||||||
implements IDockviewGroupPanel
|
implements IDockviewGroupPanel
|
||||||
{
|
{
|
||||||
private readonly _model: IDockviewGroupPanelModel;
|
private readonly _model: DockviewGroupPanelModel;
|
||||||
|
|
||||||
get panels(): IDockviewPanel[] {
|
get panels(): IDockviewPanel[] {
|
||||||
return this._model.panels;
|
return this._model.panels;
|
||||||
@ -40,7 +40,7 @@ export class DockviewGroupPanel
|
|||||||
return this._model.size;
|
return this._model.size;
|
||||||
}
|
}
|
||||||
|
|
||||||
get model(): IDockviewGroupPanelModel {
|
get model(): DockviewGroupPanelModel {
|
||||||
return this._model;
|
return this._model;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -138,6 +138,7 @@ export class DockviewGroupPanelModel
|
|||||||
private _isGroupActive = false;
|
private _isGroupActive = false;
|
||||||
private _locked = false;
|
private _locked = false;
|
||||||
private _control: IGroupControlRenderer | undefined;
|
private _control: IGroupControlRenderer | undefined;
|
||||||
|
private _isFloating = false;
|
||||||
|
|
||||||
private mostRecentlyUsed: IDockviewPanel[] = [];
|
private mostRecentlyUsed: IDockviewPanel[] = [];
|
||||||
|
|
||||||
@ -223,6 +224,20 @@ export class DockviewGroupPanelModel
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get isFloating(): boolean {
|
||||||
|
return this._isFloating;
|
||||||
|
}
|
||||||
|
|
||||||
|
set isFloating(value: boolean) {
|
||||||
|
this._isFloating = value;
|
||||||
|
|
||||||
|
this.dropTarget.setTargetZones(
|
||||||
|
value ? ['center'] : ['top', 'bottom', 'left', 'right', 'center']
|
||||||
|
);
|
||||||
|
|
||||||
|
toggleClass(this.container, 'dv-groupview-floating', value);
|
||||||
|
}
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly container: HTMLElement,
|
private readonly container: HTMLElement,
|
||||||
private accessor: DockviewComponent,
|
private accessor: DockviewComponent,
|
||||||
@ -232,7 +247,7 @@ export class DockviewGroupPanelModel
|
|||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.container.classList.add('groupview');
|
toggleClass(this.container, 'groupview', true);
|
||||||
|
|
||||||
this.tabsContainer = new TabsContainer(this.accessor, this.groupPanel);
|
this.tabsContainer = new TabsContainer(this.accessor, this.groupPanel);
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
--dv-drag-over-border-color: white;
|
--dv-drag-over-border-color: white;
|
||||||
--dv-tabs-container-scrollbar-color: #888;
|
--dv-tabs-container-scrollbar-color: #888;
|
||||||
--dv-icon-hover-background-color: rgba(90, 93, 94, 0.31);
|
--dv-icon-hover-background-color: rgba(90, 93, 94, 0.31);
|
||||||
|
--dv-floating-box-shadow: 8px 8px 8px 0px rgba(83, 89, 93, 0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
@mixin dockview-theme-dark-mixin {
|
@mixin dockview-theme-dark-mixin {
|
||||||
@ -225,3 +226,124 @@
|
|||||||
.dockview-theme-dracula {
|
.dockview-theme-dracula {
|
||||||
@include dockview-theme-dracula-mixin();
|
@include dockview-theme-dracula-mixin();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@mixin dockview-design-replit-mixin {
|
||||||
|
&.dv-dockview {
|
||||||
|
padding: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.view:has(> .groupview) {
|
||||||
|
padding: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dv-resize-container:has(> .groupview) {
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.groupview {
|
||||||
|
overflow: hidden;
|
||||||
|
border-radius: 10px;
|
||||||
|
|
||||||
|
.tabs-and-actions-container {
|
||||||
|
.tab {
|
||||||
|
margin: 4px;
|
||||||
|
border-radius: 8px;
|
||||||
|
|
||||||
|
.dockview-svg {
|
||||||
|
height: 8px;
|
||||||
|
width: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: #e4e5e6 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
border-bottom: 1px solid rgba(128, 128, 128, 0.35);
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-container {
|
||||||
|
background-color: #fcfcfc;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active-group {
|
||||||
|
border: 1px solid rgba(128, 128, 128, 0.35);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.inactive-group {
|
||||||
|
border: 1px solid transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.vertical > .sash-container > .sash {
|
||||||
|
&::after {
|
||||||
|
content: '';
|
||||||
|
height: 4px;
|
||||||
|
width: 40px;
|
||||||
|
border-radius: 2px;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
background-color: var(--dv-separator-handle-background-color);
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
&::after {
|
||||||
|
background-color: var(
|
||||||
|
--dv-separator-handle-hover-background-color
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.horizontal > .sash-container > .sash {
|
||||||
|
&::after {
|
||||||
|
content: '';
|
||||||
|
height: 40px;
|
||||||
|
width: 4px;
|
||||||
|
border-radius: 2px;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
background-color: var(--dv-separator-handle-background-color);
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
&::after {
|
||||||
|
background-color: var(
|
||||||
|
--dv-separator-handle-hover-background-color
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dockview-theme-replit {
|
||||||
|
@include dockview-theme-core-mixin();
|
||||||
|
@include dockview-design-replit-mixin();
|
||||||
|
//
|
||||||
|
--dv-group-view-background-color: #ebeced;
|
||||||
|
//
|
||||||
|
--dv-tabs-and-actions-container-background-color: #fcfcfc;
|
||||||
|
//
|
||||||
|
--dv-activegroup-visiblepanel-tab-background-color: #f0f1f2;
|
||||||
|
--dv-activegroup-hiddenpanel-tab-background-color: ##fcfcfc;
|
||||||
|
--dv-inactivegroup-visiblepanel-tab-background-color: #f0f1f2;
|
||||||
|
--dv-inactivegroup-hiddenpanel-tab-background-color: #fcfcfc;
|
||||||
|
--dv-tab-divider-color: transparent;
|
||||||
|
//
|
||||||
|
--dv-activegroup-visiblepanel-tab-color: rgb(51, 51, 51);
|
||||||
|
--dv-activegroup-hiddenpanel-tab-color: rgb(51, 51, 51);
|
||||||
|
--dv-inactivegroup-visiblepanel-tab-color: rgb(51, 51, 51);
|
||||||
|
--dv-inactivegroup-hiddenpanel-tab-color: rgb(51, 51, 51);
|
||||||
|
//
|
||||||
|
--dv-separator-border: transparent;
|
||||||
|
--dv-paneview-header-border-color: rgb(51, 51, 51);
|
||||||
|
|
||||||
|
--dv-background-color: #ebeced;
|
||||||
|
|
||||||
|
/////
|
||||||
|
--dv-separator-handle-background-color: #cfd1d3;
|
||||||
|
--dv-separator-handle-hover-background-color: #babbbb;
|
||||||
|
}
|
||||||
|
@ -1,12 +1,6 @@
|
|||||||
export * from 'dockview-core';
|
export * from 'dockview-core';
|
||||||
|
|
||||||
export {
|
export * from './dockview/dockview';
|
||||||
IDockviewPanelHeaderProps,
|
|
||||||
IDockviewPanelProps,
|
|
||||||
DockviewReadyEvent,
|
|
||||||
IDockviewReactProps,
|
|
||||||
DockviewReact,
|
|
||||||
} from './dockview/dockview';
|
|
||||||
export * from './dockview/defaultTab';
|
export * from './dockview/defaultTab';
|
||||||
export * from './splitview/splitview';
|
export * from './splitview/splitview';
|
||||||
export * from './gridview/gridview';
|
export * from './gridview/gridview';
|
||||||
|
@ -11,6 +11,28 @@ import * as ReactDOM from 'react-dom';
|
|||||||
import { v4 } from 'uuid';
|
import { v4 } from 'uuid';
|
||||||
import './app.scss';
|
import './app.scss';
|
||||||
|
|
||||||
|
function useLocalStorageItem(key: string, defaultValue: string): string {
|
||||||
|
const [item, setItem] = React.useState<string | null>(
|
||||||
|
localStorage.getItem(key)
|
||||||
|
);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
const listener = (event: StorageEvent) => {
|
||||||
|
setItem(localStorage.getItem(key));
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener('storage', listener);
|
||||||
|
|
||||||
|
setItem(localStorage.getItem(key));
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('storage', listener);
|
||||||
|
};
|
||||||
|
}, [key]);
|
||||||
|
|
||||||
|
return item === null ? defaultValue : item;
|
||||||
|
}
|
||||||
|
|
||||||
const components = {
|
const components = {
|
||||||
default: (props: IDockviewPanelProps<{ title: string }>) => {
|
default: (props: IDockviewPanelProps<{ title: string }>) => {
|
||||||
return <div style={{ padding: '20px' }}>{props.params.title}</div>;
|
return <div style={{ padding: '20px' }}>{props.params.title}</div>;
|
||||||
@ -196,8 +218,8 @@ const DockviewDemo = () => {
|
|||||||
title: 'Panel 6',
|
title: 'Panel 6',
|
||||||
position: { referencePanel: 'panel_4', direction: 'below' },
|
position: { referencePanel: 'panel_4', direction: 'below' },
|
||||||
});
|
});
|
||||||
panel6.group.locked = true;
|
// panel6.group.locked = true;
|
||||||
panel6.group.header.hidden = true;
|
// panel6.group.header.hidden = true;
|
||||||
event.api.addPanel({
|
event.api.addPanel({
|
||||||
id: 'panel_7',
|
id: 'panel_7',
|
||||||
component: 'default',
|
component: 'default',
|
||||||
@ -211,18 +233,23 @@ const DockviewDemo = () => {
|
|||||||
position: { referencePanel: 'panel_7', direction: 'within' },
|
position: { referencePanel: 'panel_7', direction: 'within' },
|
||||||
});
|
});
|
||||||
|
|
||||||
event.api.addGroup();
|
// event.api.addGroup();
|
||||||
|
|
||||||
event.api.getPanel('panel_1')!.api.setActive();
|
event.api.getPanel('panel_1')!.api.setActive();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const theme = useLocalStorageItem(
|
||||||
|
'dv-theme-class-name',
|
||||||
|
'dockview-theme-abyss'
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DockviewReact
|
<DockviewReact
|
||||||
components={components}
|
components={components}
|
||||||
defaultTabComponent={headerComponents.default}
|
defaultTabComponent={headerComponents.default}
|
||||||
groupControlComponent={GroupControls}
|
groupControlComponent={GroupControls}
|
||||||
onReady={onReady}
|
onReady={onReady}
|
||||||
className="dockview-theme-abyss"
|
className={theme}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import './codeSandboxButton.scss';
|
import './codeSandboxButton.scss';
|
||||||
|
import { ThemePicker } from './container';
|
||||||
|
|
||||||
const BASE_SANDBOX_URL =
|
const BASE_SANDBOX_URL =
|
||||||
'https://codesandbox.io/s/github/mathuo/dockview/tree/master/packages/docs/sandboxes';
|
'https://codesandbox.io/s/github/mathuo/dockview/tree/master/packages/docs/sandboxes';
|
||||||
@ -40,26 +41,29 @@ export const CodeSandboxButton = (props: { id: string }) => {
|
|||||||
}, [props.id]);
|
}, [props.id]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span
|
<>
|
||||||
className="codesandbox-button"
|
<ThemePicker />
|
||||||
style={{ display: 'flex', alignItems: 'center' }}
|
<span
|
||||||
>
|
className="codesandbox-button"
|
||||||
<span className="codesandbox-button-pretext">{`Open in `}</span>
|
style={{ display: 'flex', alignItems: 'center' }}
|
||||||
<a
|
|
||||||
href={url}
|
|
||||||
target={'_blank'}
|
|
||||||
className="codesandbox-button-content"
|
|
||||||
>
|
>
|
||||||
<span
|
<span className="codesandbox-button-pretext">{`Open in `}</span>
|
||||||
style={{
|
<a
|
||||||
fontWeight: 'bold',
|
href={url}
|
||||||
paddingRight: '4px',
|
target={'_blank'}
|
||||||
}}
|
className="codesandbox-button-content"
|
||||||
>
|
>
|
||||||
CodeSandbox
|
<span
|
||||||
</span>
|
style={{
|
||||||
<CloseButton />
|
fontWeight: 'bold',
|
||||||
</a>
|
paddingRight: '4px',
|
||||||
</span>
|
}}
|
||||||
|
>
|
||||||
|
CodeSandbox
|
||||||
|
</span>
|
||||||
|
<CloseButton />
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -69,6 +69,49 @@ const JavascriptIcon = (props: { height: number; width: number }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const themes = [
|
||||||
|
'dockview-theme-dark',
|
||||||
|
'dockview-theme-light',
|
||||||
|
'dockview-theme-vs',
|
||||||
|
'dockview-theme-dracula',
|
||||||
|
'dockview-theme-replit',
|
||||||
|
];
|
||||||
|
|
||||||
|
export const ThemePicker = () => {
|
||||||
|
const [theme, setTheme] = React.useState<string>(
|
||||||
|
localStorage.getItem('dv-theme-class-name') || themes[0]
|
||||||
|
);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
localStorage.setItem('dv-theme-class-name', theme);
|
||||||
|
window.dispatchEvent(new StorageEvent('storage'));
|
||||||
|
}, [theme]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
height: '20px',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
padding: '0px 0px 0px 4px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span style={{ paddingRight: '4px' }}>{'Theme: '}</span>
|
||||||
|
<select
|
||||||
|
style={{ backgroundColor: 'inherit', color: 'inherit' }}
|
||||||
|
onChange={(e) => setTheme(e.target.value)}
|
||||||
|
value={theme}
|
||||||
|
>
|
||||||
|
{themes.map((theme) => (
|
||||||
|
<option key={theme} value={theme}>
|
||||||
|
{theme}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export const MultiFrameworkContainer = (props: {
|
export const MultiFrameworkContainer = (props: {
|
||||||
react: React.FC;
|
react: React.FC;
|
||||||
typescript: (parent: HTMLElement) => { dispose: () => void };
|
typescript: (parent: HTMLElement) => { dispose: () => void };
|
||||||
@ -183,6 +226,7 @@ export const MultiFrameworkContainer = (props: {
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<span style={{ flexGrow: 1 }} />
|
<span style={{ flexGrow: 1 }} />
|
||||||
|
<ThemePicker />
|
||||||
<CodeSandboxButton id={sandboxId} />
|
<CodeSandboxButton id={sandboxId} />
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
Loading…
Reference in New Issue
Block a user