mirror of
https://github.com/mathuo/dockview
synced 2025-02-02 14:35:46 +00:00
feat: floating group persistance
This commit is contained in:
parent
c53d2690c3
commit
86be252e99
@ -8,7 +8,7 @@ import { PanelUpdateEvent } from '../../panel/types';
|
|||||||
import { Orientation } from '../../splitview/splitview';
|
import { Orientation } from '../../splitview/splitview';
|
||||||
import { CompositeDisposable } from '../../lifecycle';
|
import { CompositeDisposable } from '../../lifecycle';
|
||||||
import { Emitter } from '../../events';
|
import { Emitter } from '../../events';
|
||||||
import { IDockviewPanel } from '../../dockview/dockviewPanel';
|
import { DockviewPanel, IDockviewPanel } from '../../dockview/dockviewPanel';
|
||||||
import { DockviewGroupPanel } from '../../dockview/dockviewGroupPanel';
|
import { DockviewGroupPanel } from '../../dockview/dockviewGroupPanel';
|
||||||
|
|
||||||
class PanelContentPartTest implements IContentRenderer {
|
class PanelContentPartTest implements IContentRenderer {
|
||||||
@ -2619,4 +2619,32 @@ describe('dockviewComponent', () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('floating: group is removed', async () => {
|
||||||
|
const container = document.createElement('div');
|
||||||
|
|
||||||
|
const dockview = new DockviewComponent({
|
||||||
|
parentElement: container,
|
||||||
|
components: {
|
||||||
|
default: PanelContentPartTest,
|
||||||
|
},
|
||||||
|
tabComponents: {
|
||||||
|
test_tab_id: PanelTabPartTest,
|
||||||
|
},
|
||||||
|
orientation: Orientation.HORIZONTAL,
|
||||||
|
});
|
||||||
|
|
||||||
|
dockview.layout(1000, 500);
|
||||||
|
|
||||||
|
expect(dockview.groups.length).toBe(0);
|
||||||
|
const panel = dockview.addPanel({
|
||||||
|
id: 'panel_1',
|
||||||
|
component: 'default',
|
||||||
|
floating: true,
|
||||||
|
});
|
||||||
|
expect(dockview.groups.length).toBe(1);
|
||||||
|
|
||||||
|
dockview.removePanel(panel);
|
||||||
|
expect(dockview.groups.length).toBe(0);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -35,10 +35,10 @@ export abstract class DragHandler extends CompositeDisposable {
|
|||||||
this.addDisposables(
|
this.addDisposables(
|
||||||
this._onDragStart,
|
this._onDragStart,
|
||||||
addDisposableListener(this.el, 'dragstart', (event) => {
|
addDisposableListener(this.el, 'dragstart', (event) => {
|
||||||
if (this.isCancelled(event)) {
|
if (this.isCancelled(event)) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const iframes = [
|
const iframes = [
|
||||||
...getElementsByTagName('iframe'),
|
...getElementsByTagName('iframe'),
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
z-index: 10000;
|
z-index: 10000;
|
||||||
|
pointer-events: none;
|
||||||
|
|
||||||
> .drop-target-selection {
|
> .drop-target-selection {
|
||||||
position: relative;
|
position: relative;
|
||||||
@ -15,7 +16,9 @@
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
background-color: var(--dv-drag-over-background-color);
|
background-color: var(--dv-drag-over-background-color);
|
||||||
transition: top 70ms ease-out,left 70ms ease-out,width 70ms ease-out,height 70ms ease-out,opacity .15s ease-out;
|
transition: top 70ms ease-out, left 70ms ease-out,
|
||||||
|
width 70ms ease-out, height 70ms ease-out,
|
||||||
|
opacity 0.15s ease-out;
|
||||||
will-change: transform;
|
will-change: transform;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
|
|
||||||
|
@ -54,6 +54,17 @@ export type CanDisplayOverlay =
|
|||||||
| boolean
|
| boolean
|
||||||
| ((dragEvent: DragEvent, state: Position) => boolean);
|
| ((dragEvent: DragEvent, state: Position) => boolean);
|
||||||
|
|
||||||
|
const eventMarkTag = 'dv_droptarget_marked';
|
||||||
|
|
||||||
|
function markEvent(event: DragEvent): void {
|
||||||
|
(event as any)[eventMarkTag] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isEventMarked(event: DragEvent) {
|
||||||
|
const value = (event as any)[eventMarkTag];
|
||||||
|
return typeof value === 'boolean' && value;
|
||||||
|
}
|
||||||
|
|
||||||
export class Droptarget extends CompositeDisposable {
|
export class Droptarget extends CompositeDisposable {
|
||||||
private targetElement: HTMLElement | undefined;
|
private targetElement: HTMLElement | undefined;
|
||||||
private overlayElement: HTMLElement | undefined;
|
private overlayElement: HTMLElement | undefined;
|
||||||
@ -114,7 +125,7 @@ export class Droptarget extends CompositeDisposable {
|
|||||||
height
|
height
|
||||||
);
|
);
|
||||||
|
|
||||||
if (quadrant === null) {
|
if (isEventMarked(e) || quadrant === null) {
|
||||||
// no drop target should be displayed
|
// no drop target should be displayed
|
||||||
this.removeDropTarget();
|
this.removeDropTarget();
|
||||||
return;
|
return;
|
||||||
@ -128,6 +139,8 @@ export class Droptarget extends CompositeDisposable {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
markEvent(e);
|
||||||
|
|
||||||
if (!this.targetElement) {
|
if (!this.targetElement) {
|
||||||
this.targetElement = document.createElement('div');
|
this.targetElement = document.createElement('div');
|
||||||
this.targetElement.className = 'drop-target-dropzone';
|
this.targetElement.className = 'drop-target-dropzone';
|
||||||
|
@ -17,7 +17,7 @@ export class GroupDragHandler extends DragHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override isCancelled(_event: DragEvent): boolean {
|
override isCancelled(_event: DragEvent): boolean {
|
||||||
if (this.group.model.isFloating) {
|
if (this.group.model.isFloating && !_event.shiftKey) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
@ -1,5 +1,10 @@
|
|||||||
import { toggleClass } from '../dom';
|
import { toggleClass } from '../dom';
|
||||||
import { addDisposableListener, addDisposableWindowListener } from '../events';
|
import {
|
||||||
|
Emitter,
|
||||||
|
Event,
|
||||||
|
addDisposableListener,
|
||||||
|
addDisposableWindowListener,
|
||||||
|
} from '../events';
|
||||||
import { CompositeDisposable, MutableDisposable } from '../lifecycle';
|
import { CompositeDisposable, MutableDisposable } from '../lifecycle';
|
||||||
import { clamp } from '../math';
|
import { clamp } from '../math';
|
||||||
|
|
||||||
@ -21,6 +26,9 @@ const bringElementToFront = (() => {
|
|||||||
export class Overlay extends CompositeDisposable {
|
export class Overlay extends CompositeDisposable {
|
||||||
private _element: HTMLElement = document.createElement('div');
|
private _element: HTMLElement = document.createElement('div');
|
||||||
|
|
||||||
|
private readonly _onDidChange = new Emitter<void>();
|
||||||
|
readonly onDidChange: Event<void> = this._onDidChange.event;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly options: {
|
private readonly options: {
|
||||||
height: number;
|
height: number;
|
||||||
@ -35,6 +43,8 @@ export class Overlay extends CompositeDisposable {
|
|||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
|
this.addDisposables(this._onDidChange);
|
||||||
|
|
||||||
this.setupOverlay();
|
this.setupOverlay();
|
||||||
// this.setupDrag(true,this._element);
|
// this.setupDrag(true,this._element);
|
||||||
this.setupResize('top');
|
this.setupResize('top');
|
||||||
@ -52,6 +62,18 @@ export class Overlay extends CompositeDisposable {
|
|||||||
// this.renderWithinBoundaryConditions();
|
// this.renderWithinBoundaryConditions();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toJSON(): { top: number; left: number; height: number; width: number } {
|
||||||
|
const container = this.options.container.getBoundingClientRect();
|
||||||
|
const element = this._element.getBoundingClientRect();
|
||||||
|
|
||||||
|
return {
|
||||||
|
top: element.top - container.top,
|
||||||
|
left: element.left - container.left,
|
||||||
|
width: element.width,
|
||||||
|
height: element.height,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private setupResize(
|
private setupResize(
|
||||||
direction:
|
direction:
|
||||||
| 'top'
|
| 'top'
|
||||||
@ -216,6 +238,7 @@ export class Overlay extends CompositeDisposable {
|
|||||||
}),
|
}),
|
||||||
addDisposableWindowListener(window, 'mouseup', () => {
|
addDisposableWindowListener(window, 'mouseup', () => {
|
||||||
move.dispose();
|
move.dispose();
|
||||||
|
this._onDidChange.fire();
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
@ -296,24 +319,37 @@ export class Overlay extends CompositeDisposable {
|
|||||||
);
|
);
|
||||||
|
|
||||||
move.dispose();
|
move.dispose();
|
||||||
|
this._onDidChange.fire();
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
this.addDisposables(
|
this.addDisposables(
|
||||||
move,
|
move,
|
||||||
addDisposableListener(dragTarget, 'mousedown', (_) => {
|
addDisposableListener(dragTarget, 'mousedown', (event) => {
|
||||||
if (_.defaultPrevented) {
|
if (
|
||||||
|
// event.shiftKey ||
|
||||||
|
event.defaultPrevented
|
||||||
|
) {
|
||||||
|
event.preventDefault();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
track();
|
track();
|
||||||
}),
|
}),
|
||||||
addDisposableListener(this.options.content, 'mousedown', (_) => {
|
addDisposableListener(
|
||||||
if (_.shiftKey) {
|
this.options.content,
|
||||||
track();
|
'mousedown',
|
||||||
|
(event) => {
|
||||||
|
if (event.defaultPrevented) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.shiftKey) {
|
||||||
|
track();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}),
|
),
|
||||||
addDisposableListener(
|
addDisposableListener(
|
||||||
this.options.content,
|
this.options.content,
|
||||||
'mousedown',
|
'mousedown',
|
||||||
@ -324,29 +360,32 @@ export class Overlay extends CompositeDisposable {
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
bringElementToFront(this._element);
|
||||||
|
|
||||||
if (connect) {
|
if (connect) {
|
||||||
track();
|
track();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
renderWithinBoundaryConditions(): void {
|
renderWithinBoundaryConditions(): void {
|
||||||
const rect = this.options.container.getBoundingClientRect();
|
const containerRect = this.options.container.getBoundingClientRect();
|
||||||
const rect2 = this._element.getBoundingClientRect();
|
const overlayRect = this._element.getBoundingClientRect();
|
||||||
|
|
||||||
|
const xOffset = Math.max(0, overlayRect.width - this.options.minX);
|
||||||
|
const yOffset = Math.max(0, overlayRect.height - this.options.minY);
|
||||||
|
|
||||||
const left = clamp(
|
const left = clamp(
|
||||||
Math.max(this.options.left, 0),
|
this.options.left,
|
||||||
0,
|
-xOffset,
|
||||||
Math.max(0, rect.width - rect2.width)
|
Math.max(0, containerRect.width - overlayRect.width + xOffset)
|
||||||
);
|
);
|
||||||
|
|
||||||
const top = clamp(
|
const top = clamp(
|
||||||
Math.max(this.options.top, 0),
|
this.options.top,
|
||||||
0,
|
-yOffset,
|
||||||
Math.max(0, rect.height - rect2.height)
|
Math.max(0, containerRect.height - overlayRect.height + yOffset)
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log(new Error().stack);
|
|
||||||
|
|
||||||
this._element.style.left = `${left}px`;
|
this._element.style.left = `${left}px`;
|
||||||
this._element.style.top = `${top}px`;
|
this._element.style.top = `${top}px`;
|
||||||
}
|
}
|
||||||
|
@ -6,12 +6,11 @@ import {
|
|||||||
PanelTransfer,
|
PanelTransfer,
|
||||||
} from '../../../dnd/dataTransfer';
|
} from '../../../dnd/dataTransfer';
|
||||||
import { toggleClass } from '../../../dom';
|
import { toggleClass } from '../../../dom';
|
||||||
import { IDockviewComponent } from '../../dockviewComponent';
|
import { DockviewComponent } from '../../dockviewComponent';
|
||||||
import { DockviewDropTargets, ITabRenderer } from '../../types';
|
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 extends IDisposable {
|
export interface ITab extends IDisposable {
|
||||||
readonly panelId: string;
|
readonly panelId: string;
|
||||||
@ -39,7 +38,7 @@ export class Tab extends CompositeDisposable implements ITab {
|
|||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public readonly panelId: string,
|
public readonly panelId: string,
|
||||||
private readonly accessor: IDockviewComponent,
|
private readonly accessor: DockviewComponent,
|
||||||
private readonly group: DockviewGroupPanel
|
private readonly group: DockviewGroupPanel
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
@ -77,22 +76,10 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODO: alternative to stopPropagation
|
* TODO: alternative to stopPropagation
|
||||||
*
|
*
|
||||||
|
@ -9,7 +9,7 @@ import { DockviewComponent } from '../../dockviewComponent';
|
|||||||
import { DockviewGroupPanel } from '../../dockviewGroupPanel';
|
import { DockviewGroupPanel } from '../../dockviewGroupPanel';
|
||||||
import { VoidContainer } from './voidContainer';
|
import { VoidContainer } from './voidContainer';
|
||||||
import { toggleClass } from '../../../dom';
|
import { toggleClass } from '../../../dom';
|
||||||
import { IDockviewPanel } from '../../dockviewPanel';
|
import { DockviewPanel, IDockviewPanel } from '../../dockviewPanel';
|
||||||
|
|
||||||
export interface TabDropIndexEvent {
|
export interface TabDropIndexEvent {
|
||||||
readonly event: DragEvent;
|
readonly event: DragEvent;
|
||||||
@ -187,6 +187,26 @@ export class TabsContainer
|
|||||||
index: this.tabs.length,
|
index: this.tabs.length,
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
|
addDisposableListener(
|
||||||
|
this.voidContainer.element,
|
||||||
|
'mousedown',
|
||||||
|
(event) => {
|
||||||
|
if (event.shiftKey && !this.group.model.isFloating) {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
const { top, left } =
|
||||||
|
this.element.getBoundingClientRect();
|
||||||
|
const { top: rootTop, left: rootLeft } =
|
||||||
|
this.accessor.element.getBoundingClientRect();
|
||||||
|
|
||||||
|
this.accessor.addFloatingGroup(this.group, {
|
||||||
|
x: left - rootLeft + 20,
|
||||||
|
y: top - rootTop + 20,
|
||||||
|
});
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
),
|
||||||
addDisposableListener(this.tabContainer, 'mousedown', (event) => {
|
addDisposableListener(this.tabContainer, 'mousedown', (event) => {
|
||||||
if (event.defaultPrevented) {
|
if (event.defaultPrevented) {
|
||||||
return;
|
return;
|
||||||
@ -263,6 +283,23 @@ export class TabsContainer
|
|||||||
|
|
||||||
const disposable = CompositeDisposable.from(
|
const disposable = CompositeDisposable.from(
|
||||||
tabToAdd.onChanged((event) => {
|
tabToAdd.onChanged((event) => {
|
||||||
|
if (event.shiftKey) {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
const panel = this.accessor.getGroupPanel(tabToAdd.panelId);
|
||||||
|
|
||||||
|
const { top, left } =
|
||||||
|
tabToAdd.element.getBoundingClientRect();
|
||||||
|
const { top: rootTop, left: rootLeft } =
|
||||||
|
this.accessor.element.getBoundingClientRect();
|
||||||
|
|
||||||
|
this.accessor.addFloatingGroup(panel as DockviewPanel, {
|
||||||
|
x: left - rootLeft,
|
||||||
|
y: top - rootTop,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const alreadyFocused =
|
const alreadyFocused =
|
||||||
panel.id === this.group.model.activePanel?.id &&
|
panel.id === this.group.model.activePanel?.id &&
|
||||||
this.group.model.isContentFocused;
|
this.group.model.isContentFocused;
|
||||||
|
@ -69,16 +69,6 @@ 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);
|
||||||
}),
|
}),
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
left: 0px;
|
left: 0px;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
z-index: 9999;
|
z-index: 9997;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,6 +52,11 @@ export interface PanelReference {
|
|||||||
remove: () => void;
|
remove: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface SerializedFloatingGroup {
|
||||||
|
data: GroupPanelViewState;
|
||||||
|
position: { height: number; width: number; left: number; top: number };
|
||||||
|
}
|
||||||
|
|
||||||
export interface SerializedDockview {
|
export interface SerializedDockview {
|
||||||
grid: {
|
grid: {
|
||||||
root: SerializedGridObject<GroupPanelViewState>;
|
root: SerializedGridObject<GroupPanelViewState>;
|
||||||
@ -59,8 +64,9 @@ export interface SerializedDockview {
|
|||||||
width: number;
|
width: number;
|
||||||
orientation: Orientation;
|
orientation: Orientation;
|
||||||
};
|
};
|
||||||
panels: { [key: string]: GroupviewPanelState };
|
panels: Record<string, GroupviewPanelState>;
|
||||||
activeGroup?: string;
|
activeGroup?: string;
|
||||||
|
floatingGroups?: SerializedFloatingGroup[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export type DockviewComponentUpdateOptions = Pick<
|
export type DockviewComponentUpdateOptions = Pick<
|
||||||
@ -118,7 +124,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(
|
addFloatingGroup(
|
||||||
item: DockviewPanel | DockviewGroupPanel,
|
item: DockviewPanel | DockviewGroupPanel,
|
||||||
coord?: { x: number; y: number }
|
coord?: { x: number; y: number }
|
||||||
): void;
|
): void;
|
||||||
@ -156,7 +162,7 @@ export class DockviewComponent
|
|||||||
private readonly floatingGroups: {
|
private readonly floatingGroups: {
|
||||||
instance: DockviewGroupPanel;
|
instance: DockviewGroupPanel;
|
||||||
disposable: IDisposable;
|
disposable: IDisposable;
|
||||||
render: () => void;
|
overlay: Overlay;
|
||||||
}[] = [];
|
}[] = [];
|
||||||
|
|
||||||
get orientation(): Orientation {
|
get orientation(): Orientation {
|
||||||
@ -290,9 +296,10 @@ export class DockviewComponent
|
|||||||
this.updateWatermark();
|
this.updateWatermark();
|
||||||
}
|
}
|
||||||
|
|
||||||
addFloating(
|
addFloatingGroup(
|
||||||
item: DockviewPanel | DockviewGroupPanel,
|
item: DockviewPanel | DockviewGroupPanel,
|
||||||
coord?: { x: number; y: number }
|
coord?: { x?: number; y?: number; height?: number; width?: number },
|
||||||
|
options?: { skipRemoveGroup: boolean; connect: boolean }
|
||||||
): void {
|
): void {
|
||||||
let group: DockviewGroupPanel;
|
let group: DockviewGroupPanel;
|
||||||
|
|
||||||
@ -307,7 +314,14 @@ export class DockviewComponent
|
|||||||
group.model.openPanel(item);
|
group.model.openPanel(item);
|
||||||
} else {
|
} else {
|
||||||
group = item;
|
group = item;
|
||||||
this.doRemoveGroup(item, { skipDispose: true });
|
|
||||||
|
const skip =
|
||||||
|
typeof options?.skipRemoveGroup === 'boolean' &&
|
||||||
|
options.skipRemoveGroup;
|
||||||
|
|
||||||
|
if (!skip) {
|
||||||
|
this.doRemoveGroup(item, { skipDispose: true });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
group.model.isFloating = true;
|
group.model.isFloating = true;
|
||||||
@ -315,15 +329,15 @@ export class DockviewComponent
|
|||||||
const { left, top } = this.element.getBoundingClientRect();
|
const { left, top } = this.element.getBoundingClientRect();
|
||||||
|
|
||||||
const overlayLeft =
|
const overlayLeft =
|
||||||
typeof coord?.x === 'number' ? Math.max(coord.x - left, 0) : 100;
|
typeof coord?.x === 'number' ? Math.max(coord.x, 0) : 100;
|
||||||
const overlayTop =
|
const overlayTop =
|
||||||
typeof coord?.y === 'number' ? Math.max(0, coord.y - top) : 100;
|
typeof coord?.y === 'number' ? Math.max(coord.y, 0) : 100;
|
||||||
|
|
||||||
const overlay = new Overlay({
|
const overlay = new Overlay({
|
||||||
container: this.gridview.element,
|
container: this.gridview.element,
|
||||||
content: group.element,
|
content: group.element,
|
||||||
height: 300,
|
height: coord?.height ?? 300,
|
||||||
width: 300,
|
width: coord?.width ?? 300,
|
||||||
left: overlayLeft,
|
left: overlayLeft,
|
||||||
top: overlayTop,
|
top: overlayTop,
|
||||||
minX: 100,
|
minX: 100,
|
||||||
@ -333,20 +347,28 @@ export class DockviewComponent
|
|||||||
const el = group.element.querySelector('#dv-group-float-drag-handle');
|
const el = group.element.querySelector('#dv-group-float-drag-handle');
|
||||||
|
|
||||||
if (el) {
|
if (el) {
|
||||||
overlay.setupDrag(true, el as HTMLElement);
|
overlay.setupDrag(
|
||||||
|
typeof options?.connect === 'boolean' ? options.connect : true,
|
||||||
|
el as HTMLElement
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const instance = {
|
const instance = {
|
||||||
instance: group,
|
instance: group,
|
||||||
render: () => {
|
|
||||||
overlay.renderWithinBoundaryConditions();
|
overlay,
|
||||||
},
|
disposable: new CompositeDisposable(
|
||||||
disposable: new CompositeDisposable(overlay, {
|
overlay,
|
||||||
dispose: () => {
|
overlay.onDidChange(() => {
|
||||||
group.model.isFloating = false;
|
this._bufferOnDidLayoutChange.fire();
|
||||||
remove(this.floatingGroups, instance);
|
}),
|
||||||
},
|
{
|
||||||
}),
|
dispose: () => {
|
||||||
|
group.model.isFloating = false;
|
||||||
|
remove(this.floatingGroups, instance);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
this.floatingGroups.push(instance);
|
this.floatingGroups.push(instance);
|
||||||
@ -409,7 +431,7 @@ export class DockviewComponent
|
|||||||
|
|
||||||
if (this.floatingGroups) {
|
if (this.floatingGroups) {
|
||||||
for (const floating of this.floatingGroups) {
|
for (const floating of this.floatingGroups) {
|
||||||
floating.render();
|
floating.overlay.renderWithinBoundaryConditions();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -485,11 +507,26 @@ export class DockviewComponent
|
|||||||
return collection;
|
return collection;
|
||||||
}, {} as { [key: string]: GroupviewPanelState });
|
}, {} as { [key: string]: GroupviewPanelState });
|
||||||
|
|
||||||
return {
|
const floats: SerializedFloatingGroup[] = this.floatingGroups.map(
|
||||||
|
(floatingGroup) => {
|
||||||
|
return {
|
||||||
|
data: floatingGroup.instance.toJSON() as GroupPanelViewState,
|
||||||
|
position: floatingGroup.overlay.toJSON(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const result: SerializedDockview = {
|
||||||
grid: data,
|
grid: data,
|
||||||
panels,
|
panels,
|
||||||
activeGroup: this.activeGroup?.id,
|
activeGroup: this.activeGroup?.id,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (floats.length > 0) {
|
||||||
|
result.floatingGroups = floats;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
fromJSON(data: SerializedDockview): void {
|
fromJSON(data: SerializedDockview): void {
|
||||||
@ -505,49 +542,70 @@ export class DockviewComponent
|
|||||||
const width = this.width;
|
const width = this.width;
|
||||||
const height = this.height;
|
const height = this.height;
|
||||||
|
|
||||||
|
const createGroupFromSerializedState = (data: GroupPanelViewState) => {
|
||||||
|
const { id, locked, hideHeader, views, activeView } = data;
|
||||||
|
|
||||||
|
const group = this.createGroup({
|
||||||
|
id,
|
||||||
|
locked: !!locked,
|
||||||
|
hideHeader: !!hideHeader,
|
||||||
|
});
|
||||||
|
|
||||||
|
this._onDidAddGroup.fire(group);
|
||||||
|
|
||||||
|
for (const child of views) {
|
||||||
|
const panel = this._deserializer.fromJSON(panels[child], group);
|
||||||
|
|
||||||
|
const isActive =
|
||||||
|
typeof activeView === 'string' && activeView === panel.id;
|
||||||
|
|
||||||
|
group.model.openPanel(panel, {
|
||||||
|
skipSetPanelActive: !isActive,
|
||||||
|
skipSetGroupActive: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!group.activePanel && group.panels.length > 0) {
|
||||||
|
group.model.openPanel(group.panels[group.panels.length - 1], {
|
||||||
|
skipSetGroupActive: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return group;
|
||||||
|
};
|
||||||
|
|
||||||
this.gridview.deserialize(grid, {
|
this.gridview.deserialize(grid, {
|
||||||
fromJSON: (node: ISerializedLeafNode<GroupPanelViewState>) => {
|
fromJSON: (node: ISerializedLeafNode<GroupPanelViewState>) => {
|
||||||
const { id, locked, hideHeader, views, activeView } = node.data;
|
return createGroupFromSerializedState(node.data);
|
||||||
|
|
||||||
const group = this.createGroup({
|
|
||||||
id,
|
|
||||||
locked: !!locked,
|
|
||||||
hideHeader: !!hideHeader,
|
|
||||||
});
|
|
||||||
|
|
||||||
this._onDidAddGroup.fire(group);
|
|
||||||
|
|
||||||
for (const child of views) {
|
|
||||||
const panel = this._deserializer.fromJSON(
|
|
||||||
panels[child],
|
|
||||||
group
|
|
||||||
);
|
|
||||||
|
|
||||||
const isActive =
|
|
||||||
typeof activeView === 'string' &&
|
|
||||||
activeView === panel.id;
|
|
||||||
|
|
||||||
group.model.openPanel(panel, {
|
|
||||||
skipSetPanelActive: !isActive,
|
|
||||||
skipSetGroupActive: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!group.activePanel && group.panels.length > 0) {
|
|
||||||
group.model.openPanel(
|
|
||||||
group.panels[group.panels.length - 1],
|
|
||||||
{
|
|
||||||
skipSetGroupActive: true,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return group;
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
this.layout(width, height);
|
this.layout(width, height);
|
||||||
|
|
||||||
|
const serializedFloatingGroups = data.floatingGroups || [];
|
||||||
|
|
||||||
|
for (const serializedFloatingGroup of serializedFloatingGroups) {
|
||||||
|
const { data, position } = serializedFloatingGroup;
|
||||||
|
const group = createGroupFromSerializedState(data);
|
||||||
|
|
||||||
|
const { left, top } = this.element.getBoundingClientRect();
|
||||||
|
|
||||||
|
this.addFloatingGroup(
|
||||||
|
group,
|
||||||
|
{
|
||||||
|
x: position.left,
|
||||||
|
y: position.top,
|
||||||
|
height: position.height,
|
||||||
|
width: position.width,
|
||||||
|
},
|
||||||
|
{ skipRemoveGroup: true, connect: false }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const floatingGroup of this.floatingGroups) {
|
||||||
|
floatingGroup.overlay.renderWithinBoundaryConditions();
|
||||||
|
}
|
||||||
|
|
||||||
if (typeof activeGroup === 'string') {
|
if (typeof activeGroup === 'string') {
|
||||||
const panel = this.getPanel(activeGroup);
|
const panel = this.getPanel(activeGroup);
|
||||||
if (panel) {
|
if (panel) {
|
||||||
@ -595,6 +653,12 @@ export class DockviewComponent
|
|||||||
|
|
||||||
let referenceGroup: DockviewGroupPanel | undefined;
|
let referenceGroup: DockviewGroupPanel | undefined;
|
||||||
|
|
||||||
|
if (options.position && options.floating) {
|
||||||
|
throw new Error(
|
||||||
|
'you can only provide one of: position, floating as arguments to .addPanel(...)'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (options.position) {
|
if (options.position) {
|
||||||
if (isPanelOptionsWithPanel(options.position)) {
|
if (isPanelOptionsWithPanel(options.position)) {
|
||||||
const referencePanel =
|
const referencePanel =
|
||||||
@ -639,7 +703,23 @@ export class DockviewComponent
|
|||||||
const target = toTarget(
|
const target = toTarget(
|
||||||
<Direction>options.position?.direction || 'within'
|
<Direction>options.position?.direction || 'within'
|
||||||
);
|
);
|
||||||
if (target === 'center') {
|
|
||||||
|
if (options.floating) {
|
||||||
|
const group = this.createGroup();
|
||||||
|
panel = this.createPanel(options, group);
|
||||||
|
group.model.openPanel(panel);
|
||||||
|
|
||||||
|
const o =
|
||||||
|
typeof options.floating === 'object' &&
|
||||||
|
options.floating !== null
|
||||||
|
? options.floating
|
||||||
|
: {};
|
||||||
|
|
||||||
|
this.addFloatingGroup(group, o, {
|
||||||
|
connect: false,
|
||||||
|
skipRemoveGroup: true,
|
||||||
|
});
|
||||||
|
} else if (referenceGroup.model.isFloating || target === 'center') {
|
||||||
panel = this.createPanel(options, referenceGroup);
|
panel = this.createPanel(options, referenceGroup);
|
||||||
referenceGroup.model.openPanel(panel);
|
referenceGroup.model.openPanel(panel);
|
||||||
} else {
|
} else {
|
||||||
@ -653,10 +733,26 @@ export class DockviewComponent
|
|||||||
panel = this.createPanel(options, group);
|
panel = this.createPanel(options, group);
|
||||||
group.model.openPanel(panel);
|
group.model.openPanel(panel);
|
||||||
}
|
}
|
||||||
|
} else if (options.floating) {
|
||||||
|
const group = this.createGroup();
|
||||||
|
panel = this.createPanel(options, group);
|
||||||
|
group.model.openPanel(panel);
|
||||||
|
|
||||||
|
const o =
|
||||||
|
typeof options.floating === 'object' &&
|
||||||
|
options.floating !== null
|
||||||
|
? options.floating
|
||||||
|
: {};
|
||||||
|
|
||||||
|
this.addFloatingGroup(group, o, {
|
||||||
|
connect: false,
|
||||||
|
skipRemoveGroup: true,
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
const group = this.createGroupAtLocation();
|
const group = this.createGroupAtLocation();
|
||||||
|
|
||||||
panel = this.createPanel(options, group);
|
panel = this.createPanel(options, group);
|
||||||
|
|
||||||
group.model.openPanel(panel);
|
group.model.openPanel(panel);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -704,7 +800,7 @@ export class DockviewComponent
|
|||||||
}
|
}
|
||||||
|
|
||||||
private updateWatermark(): void {
|
private updateWatermark(): void {
|
||||||
if (this.groups.length === 0) {
|
if (this.groups.filter((x) => !x.model.isFloating).length === 0) {
|
||||||
if (!this.watermark) {
|
if (!this.watermark) {
|
||||||
this.watermark = this.createWatermarkComponent();
|
this.watermark = this.createWatermarkComponent();
|
||||||
|
|
||||||
@ -823,8 +919,10 @@ export class DockviewComponent
|
|||||||
if (floatingGroup) {
|
if (floatingGroup) {
|
||||||
if (!options?.skipDispose) {
|
if (!options?.skipDispose) {
|
||||||
floatingGroup.instance.dispose();
|
floatingGroup.instance.dispose();
|
||||||
|
this._groups.delete(group.id);
|
||||||
}
|
}
|
||||||
floatingGroup.disposable.dispose();
|
floatingGroup.disposable.dispose();
|
||||||
|
|
||||||
return floatingGroup.instance;
|
return floatingGroup.instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@ import { GridviewPanelApi } from '../api/gridviewPanelApi';
|
|||||||
import {
|
import {
|
||||||
DockviewGroupPanelModel,
|
DockviewGroupPanelModel,
|
||||||
GroupOptions,
|
GroupOptions,
|
||||||
|
GroupPanelViewState,
|
||||||
IDockviewGroupPanelModel,
|
IDockviewGroupPanelModel,
|
||||||
IHeader,
|
IHeader,
|
||||||
} from './dockviewGroupPanelModel';
|
} from './dockviewGroupPanelModel';
|
||||||
@ -94,7 +95,6 @@ export class DockviewGroupPanel
|
|||||||
}
|
}
|
||||||
|
|
||||||
toJSON(): any {
|
toJSON(): any {
|
||||||
// TODO fix typing
|
|
||||||
return this.model.toJSON();
|
return this.model.toJSON();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -261,6 +261,10 @@ export class DockviewGroupPanelModel
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (event.shiftKey && !this.isFloating) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
const data = getPanelData();
|
const data = getPanelData();
|
||||||
|
|
||||||
if (data && data.viewId === this.accessor.id) {
|
if (data && data.viewId === this.accessor.id) {
|
||||||
|
@ -18,6 +18,7 @@ import { IDisposable } from '../lifecycle';
|
|||||||
import { Position } from '../dnd/droptarget';
|
import { Position } from '../dnd/droptarget';
|
||||||
import { IDockviewPanel } from './dockviewPanel';
|
import { IDockviewPanel } from './dockviewPanel';
|
||||||
import { FrameworkFactory } from '../panel/componentFactory';
|
import { FrameworkFactory } from '../panel/componentFactory';
|
||||||
|
import { Optional } from '../types';
|
||||||
|
|
||||||
export interface IHeaderActionsRenderer extends IDisposable {
|
export interface IHeaderActionsRenderer extends IDisposable {
|
||||||
readonly element: HTMLElement;
|
readonly element: HTMLElement;
|
||||||
@ -134,12 +135,32 @@ export function isPanelOptionsWithGroup(
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AddPanelOptions
|
type AddPanelFloatingGroupUnion = {
|
||||||
extends Omit<PanelOptions, 'component' | 'tabComponent'> {
|
floating:
|
||||||
|
| {
|
||||||
|
height?: number;
|
||||||
|
width?: number;
|
||||||
|
x?: number;
|
||||||
|
y?: number;
|
||||||
|
}
|
||||||
|
| true;
|
||||||
|
position: never;
|
||||||
|
};
|
||||||
|
|
||||||
|
type AddPanelPositionUnion = {
|
||||||
|
floating: false | never;
|
||||||
|
position: AddPanelPositionOptions;
|
||||||
|
};
|
||||||
|
|
||||||
|
type AddPanelOptionsUnion = AddPanelFloatingGroupUnion | AddPanelPositionUnion;
|
||||||
|
|
||||||
|
export type AddPanelOptions = Omit<
|
||||||
|
PanelOptions,
|
||||||
|
'component' | 'tabComponent'
|
||||||
|
> & {
|
||||||
component: string;
|
component: string;
|
||||||
tabComponent?: string;
|
tabComponent?: string;
|
||||||
position?: AddPanelPositionOptions;
|
} & Partial<AddPanelOptionsUnion>;
|
||||||
}
|
|
||||||
|
|
||||||
type AddGroupOptionsWithPanel = {
|
type AddGroupOptionsWithPanel = {
|
||||||
referencePanel: string | IDockviewPanel;
|
referencePanel: string | IDockviewPanel;
|
||||||
|
@ -28,6 +28,7 @@ import DockviewExternalDnd from '@site/sandboxes/externaldnd-dockview/src/app';
|
|||||||
import DockviewResizeContainer from '@site/sandboxes/resizecontainer-dockview/src/app';
|
import DockviewResizeContainer from '@site/sandboxes/resizecontainer-dockview/src/app';
|
||||||
import DockviewTabheight from '@site/sandboxes/tabheight-dockview/src/app';
|
import DockviewTabheight from '@site/sandboxes/tabheight-dockview/src/app';
|
||||||
import DockviewWithIFrames from '@site/sandboxes/iframe-dockview/src/app';
|
import DockviewWithIFrames from '@site/sandboxes/iframe-dockview/src/app';
|
||||||
|
import DockviewFloating from '@site/sandboxes/floatinggroup-dockview/src/app';
|
||||||
|
|
||||||
import { attach as attachDockviewVanilla } from '@site/sandboxes/javascript/vanilla-dockview/src/app';
|
import { attach as attachDockviewVanilla } from '@site/sandboxes/javascript/vanilla-dockview/src/app';
|
||||||
import { attach as attachSimpleDockview } from '@site/sandboxes/javascript/simple-dockview/src/app';
|
import { attach as attachSimpleDockview } from '@site/sandboxes/javascript/simple-dockview/src/app';
|
||||||
@ -361,6 +362,15 @@ any drag and drop logic for other controls.
|
|||||||
<DockviewExternalDnd />
|
<DockviewExternalDnd />
|
||||||
</Container>
|
</Container>
|
||||||
|
|
||||||
|
## Floating Groups
|
||||||
|
|
||||||
|
Dockview has built-in support for floating groups. Each floating container can contain a single group with many panels
|
||||||
|
and you can have as many floating containers as needed. You cannot dock multiple groups together in the same floating container.
|
||||||
|
|
||||||
|
<Container height={600} sandboxId="floatinggroup-dockview">
|
||||||
|
<DockviewFloating />
|
||||||
|
</Container>
|
||||||
|
|
||||||
## Panels
|
## Panels
|
||||||
|
|
||||||
### Add Panel
|
### Add Panel
|
||||||
|
@ -39,13 +39,15 @@ const config = {
|
|||||||
'docusaurus-plugin-sass',
|
'docusaurus-plugin-sass',
|
||||||
(context, options) => {
|
(context, options) => {
|
||||||
return {
|
return {
|
||||||
name: 'webpack',
|
name: 'custom-webpack',
|
||||||
configureWebpack: (config, isServer, utils) => {
|
configureWebpack: (config, isServer, utils) => {
|
||||||
return {
|
return {
|
||||||
// externals: ['react', 'react-dom'],
|
// externals: ['react', 'react-dom'],
|
||||||
devtool: 'source-map',
|
devtool: 'source-map',
|
||||||
resolve: {
|
resolve: {
|
||||||
|
...config.resolve,
|
||||||
alias: {
|
alias: {
|
||||||
|
...config.resolve.alias,
|
||||||
react: path.join(
|
react: path.join(
|
||||||
__dirname,
|
__dirname,
|
||||||
'../../node_modules',
|
'../../node_modules',
|
||||||
@ -57,9 +59,6 @@ const config = {
|
|||||||
'react-dom'
|
'react-dom'
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
fallback: {
|
|
||||||
timers: false,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
32
packages/docs/sandboxes/floatinggroup-dockview/package.json
Normal file
32
packages/docs/sandboxes/floatinggroup-dockview/package.json
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"name": "floatinggroup-dockview",
|
||||||
|
"description": "",
|
||||||
|
"keywords": [
|
||||||
|
"dockview"
|
||||||
|
],
|
||||||
|
"version": "1.0.0",
|
||||||
|
"main": "src/index.tsx",
|
||||||
|
"dependencies": {
|
||||||
|
"dockview": "*",
|
||||||
|
"react": "^18.2.0",
|
||||||
|
"react-dom": "^18.2.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/react": "^18.0.28",
|
||||||
|
"@types/react-dom": "^18.0.11",
|
||||||
|
"typescript": "^4.9.5",
|
||||||
|
"react-scripts": "*"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"start": "react-scripts start",
|
||||||
|
"build": "react-scripts build",
|
||||||
|
"test": "react-scripts test --env=jsdom",
|
||||||
|
"eject": "react-scripts eject"
|
||||||
|
},
|
||||||
|
"browserslist": [
|
||||||
|
">0.2%",
|
||||||
|
"not dead",
|
||||||
|
"not ie <= 11",
|
||||||
|
"not op_mini all"
|
||||||
|
]
|
||||||
|
}
|
@ -0,0 +1,44 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||||
|
<meta name="theme-color" content="#000000">
|
||||||
|
<!--
|
||||||
|
manifest.json provides metadata used when your web app is added to the
|
||||||
|
homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/
|
||||||
|
-->
|
||||||
|
<link rel="manifest" href="%PUBLIC_URL%/manifest.json">
|
||||||
|
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
|
||||||
|
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200" />
|
||||||
|
<!--
|
||||||
|
Notice the use of %PUBLIC_URL% in the tags above.
|
||||||
|
It will be replaced with the URL of the `public` folder during the build.
|
||||||
|
Only files inside the `public` folder can be referenced from the HTML.
|
||||||
|
|
||||||
|
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
||||||
|
work correctly both with client-side routing and a non-root public URL.
|
||||||
|
Learn how to configure a non-root public URL by running `npm run build`.
|
||||||
|
-->
|
||||||
|
<title>React App</title>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<noscript>
|
||||||
|
You need to enable JavaScript to run this app.
|
||||||
|
</noscript>
|
||||||
|
<div id="root"></div>
|
||||||
|
<!--
|
||||||
|
This HTML file is a template.
|
||||||
|
If you open it directly in the browser, you will see an empty page.
|
||||||
|
|
||||||
|
You can add webfonts, meta tags, or analytics to this file.
|
||||||
|
The build step will place the bundled scripts into the <body> tag.
|
||||||
|
|
||||||
|
To begin the development, run `npm start` or `yarn start`.
|
||||||
|
To create a production bundle, use `npm run build` or `yarn build`.
|
||||||
|
-->
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
217
packages/docs/sandboxes/floatinggroup-dockview/src/app.tsx
Normal file
217
packages/docs/sandboxes/floatinggroup-dockview/src/app.tsx
Normal file
@ -0,0 +1,217 @@
|
|||||||
|
import {
|
||||||
|
DockviewApi,
|
||||||
|
DockviewReact,
|
||||||
|
DockviewReadyEvent,
|
||||||
|
IDockviewPanelProps,
|
||||||
|
SerializedDockview,
|
||||||
|
} from 'dockview';
|
||||||
|
import * as React from 'react';
|
||||||
|
|
||||||
|
const components = {
|
||||||
|
default: (props: IDockviewPanelProps<{ title: string }>) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
height: '100%',
|
||||||
|
padding: '20px',
|
||||||
|
background: 'var(--dv-group-view-background-color)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{props.params.title}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const counter = (() => {
|
||||||
|
let i = 0;
|
||||||
|
|
||||||
|
return {
|
||||||
|
next: () => ++i,
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
|
function loadDefaultLayout(api: DockviewApi) {
|
||||||
|
api.addPanel({
|
||||||
|
id: 'panel_1',
|
||||||
|
component: 'default',
|
||||||
|
});
|
||||||
|
|
||||||
|
api.addPanel({
|
||||||
|
id: 'panel_2',
|
||||||
|
component: 'default',
|
||||||
|
});
|
||||||
|
|
||||||
|
api.addPanel({
|
||||||
|
id: 'panel_3',
|
||||||
|
component: 'default',
|
||||||
|
});
|
||||||
|
|
||||||
|
const panel4 = api.addPanel({
|
||||||
|
id: 'panel_4',
|
||||||
|
component: 'default',
|
||||||
|
floating: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
api.addPanel({
|
||||||
|
id: 'panel_5',
|
||||||
|
component: 'default',
|
||||||
|
floating: false,
|
||||||
|
position: { referencePanel: panel4 },
|
||||||
|
});
|
||||||
|
|
||||||
|
api.addPanel({
|
||||||
|
id: 'panel_6',
|
||||||
|
component: 'default',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let panelCount = 0;
|
||||||
|
|
||||||
|
function addFloatingPanel(api: DockviewApi) {
|
||||||
|
api.addPanel({
|
||||||
|
id: (++panelCount).toString(),
|
||||||
|
title: `Tab ${panelCount}`,
|
||||||
|
component: 'default',
|
||||||
|
floating: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function addFloatingPanel2(api: DockviewApi) {
|
||||||
|
api.addPanel({
|
||||||
|
id: (++panelCount).toString(),
|
||||||
|
title: `Tab ${panelCount}`,
|
||||||
|
component: 'default',
|
||||||
|
floating: { width: 250, height: 150, x: 50, y: 50 },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function safeParse<T>(value: any): T | null {
|
||||||
|
try {
|
||||||
|
return JSON.parse(value) as T;
|
||||||
|
} catch (err) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const useLocalStorage = <T,>(
|
||||||
|
key: string
|
||||||
|
): [T | null, (setter: T | null) => void] => {
|
||||||
|
const [state, setState] = React.useState<T | null>(
|
||||||
|
safeParse(localStorage.getItem(key))
|
||||||
|
);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
const _state = localStorage.getItem('key');
|
||||||
|
try {
|
||||||
|
if (_state !== null) {
|
||||||
|
setState(JSON.parse(_state));
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
}, [key]);
|
||||||
|
|
||||||
|
return [
|
||||||
|
state,
|
||||||
|
(_state: T | null) => {
|
||||||
|
if (_state === null) {
|
||||||
|
localStorage.removeItem(key);
|
||||||
|
} else {
|
||||||
|
localStorage.setItem(key, JSON.stringify(_state));
|
||||||
|
setState(_state);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const DockviewPersistance = () => {
|
||||||
|
const [api, setApi] = React.useState<DockviewApi>();
|
||||||
|
const [layout, setLayout] =
|
||||||
|
useLocalStorage<SerializedDockview>('floating.layout');
|
||||||
|
|
||||||
|
const load = (api: DockviewApi) => {
|
||||||
|
api.clear();
|
||||||
|
if (layout) {
|
||||||
|
try {
|
||||||
|
api.fromJSON(layout);
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
api.clear();
|
||||||
|
loadDefaultLayout(api);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
loadDefaultLayout(api);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onReady = (event: DockviewReadyEvent) => {
|
||||||
|
load(event.api);
|
||||||
|
setApi(event.api);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
height: '100%',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{ height: '25px' }}>
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
if (api) {
|
||||||
|
setLayout(api.toJSON());
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Save
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
if (api) {
|
||||||
|
load(api);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Load
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
api!.clear();
|
||||||
|
setLayout(null);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Clear
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
addFloatingPanel2(api!);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Add Layout
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
flexGrow: 1,
|
||||||
|
overflow: 'hidden',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<DockviewReact
|
||||||
|
onReady={onReady}
|
||||||
|
components={components}
|
||||||
|
watermarkComponent={Watermark}
|
||||||
|
className="dockview-theme-abyss"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DockviewPersistance;
|
||||||
|
|
||||||
|
const Watermark = () => {
|
||||||
|
return <div style={{ color: 'white', padding: '8px' }}>watermark</div>;
|
||||||
|
};
|
20
packages/docs/sandboxes/floatinggroup-dockview/src/index.tsx
Normal file
20
packages/docs/sandboxes/floatinggroup-dockview/src/index.tsx
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import { StrictMode } from 'react';
|
||||||
|
import * as ReactDOMClient from 'react-dom/client';
|
||||||
|
import './styles.css';
|
||||||
|
import 'dockview/dist/styles/dockview.css';
|
||||||
|
|
||||||
|
import App from './app';
|
||||||
|
|
||||||
|
const rootElement = document.getElementById('root');
|
||||||
|
|
||||||
|
if (rootElement) {
|
||||||
|
const root = ReactDOMClient.createRoot(rootElement);
|
||||||
|
|
||||||
|
root.render(
|
||||||
|
<StrictMode>
|
||||||
|
<div className="app">
|
||||||
|
<App />
|
||||||
|
</div>
|
||||||
|
</StrictMode>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
body {
|
||||||
|
margin: 0px;
|
||||||
|
color: white;
|
||||||
|
font-family: sans-serif;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#root {
|
||||||
|
height: 100vh;
|
||||||
|
width: 100vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app {
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
}
|
18
packages/docs/sandboxes/floatinggroup-dockview/tsconfig.json
Normal file
18
packages/docs/sandboxes/floatinggroup-dockview/tsconfig.json
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "build/dist",
|
||||||
|
"module": "esnext",
|
||||||
|
"target": "es5",
|
||||||
|
"lib": ["es6", "dom"],
|
||||||
|
"sourceMap": true,
|
||||||
|
"allowJs": true,
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"rootDir": "src",
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"noImplicitReturns": true,
|
||||||
|
"noImplicitThis": true,
|
||||||
|
"noImplicitAny": true,
|
||||||
|
"strictNullChecks": true
|
||||||
|
}
|
||||||
|
}
|
@ -71,7 +71,7 @@ export const DockviewPersistance = () => {
|
|||||||
event.api.fromJSON(layout);
|
event.api.fromJSON(layout);
|
||||||
success = true;
|
success = true;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
//
|
console.error(err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -251,8 +251,6 @@ export const EventsGridview = () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log('sdf');
|
|
||||||
|
|
||||||
api.addPanel({
|
api.addPanel({
|
||||||
id: 'panel_4',
|
id: 'panel_4',
|
||||||
component: 'default',
|
component: 'default',
|
||||||
|
Loading…
Reference in New Issue
Block a user