Merge pull request #212 from mathuo/210-setconstraints-on-gridview-enable-size-locking-1

fix: dockview panel group should derive constraints
This commit is contained in:
mathuo 2023-03-22 21:30:36 +01:00 committed by GitHub
commit 464c4fd938
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 418 additions and 214 deletions

View File

@ -50,6 +50,39 @@ describe('dockviewPanel', () => {
disposable.dispose(); disposable.dispose();
}); });
test('that .setTitle updates the title', () => {
const dockviewApiMock = jest.fn<DockviewApi, []>(() => {
return {
onDidActiveChange: jest.fn(),
} as any;
});
const accessorMock = jest.fn<DockviewComponent, []>(() => {
return {} as any;
});
const groupMock = jest.fn<DockviewGroupPanel, []>(() => {
return {} as any;
});
const panelModelMock = jest.fn<Partial<IDockviewPanelModel>, []>(() => {
return {
update: jest.fn(),
init: jest.fn(),
};
});
const api = new dockviewApiMock();
const accessor = new accessorMock();
const group = new groupMock();
const model = <IDockviewPanelModel>new panelModelMock();
const cut = new DockviewPanel('fake-id', accessor, api, group, model);
cut.init({ title: 'myTitle', params: {} });
expect(cut.title).toBe('myTitle');
cut.setTitle('newTitle');
expect(cut.title).toBe('newTitle');
});
test('dispose cleanup', () => { test('dispose cleanup', () => {
const dockviewApiMock = jest.fn<DockviewApi, []>(() => { const dockviewApiMock = jest.fn<DockviewApi, []>(() => {
return {} as any; return {} as any;

View File

@ -12,7 +12,11 @@ export interface TitleEvent {
* omit visibility modifiers since the visibility of a single group doesn't make sense * omit visibility modifiers since the visibility of a single group doesn't make sense
* because it belongs to a groupview * because it belongs to a groupview
*/ */
export interface DockviewPanelApi extends Omit<GridviewPanelApi, 'setVisible'> { export interface DockviewPanelApi
extends Omit<
GridviewPanelApi,
'setVisible' | 'onDidConstraintsChange' | 'setConstraints'
> {
readonly group: DockviewGroupPanel; readonly group: DockviewGroupPanel;
readonly isGroupActive: boolean; readonly isGroupActive: boolean;
readonly title: string; readonly title: string;

View File

@ -4,13 +4,14 @@ import { GridviewPanelApi } from '../api/gridviewPanelApi';
import { import {
DockviewGroupPanelModel, DockviewGroupPanelModel,
GroupOptions, GroupOptions,
IDockviewGroupPanelModel,
IHeader, IHeader,
} from './dockviewGroupPanelModel'; } from './dockviewGroupPanelModel';
import { GridviewPanel, IGridviewPanel } from '../gridview/gridviewPanel'; import { GridviewPanel, IGridviewPanel } from '../gridview/gridviewPanel';
import { IDockviewPanel } from '../dockview/dockviewPanel'; import { IDockviewPanel } from '../dockview/dockviewPanel';
export interface IDockviewGroupPanel extends IGridviewPanel { export interface IDockviewGroupPanel extends IGridviewPanel {
model: DockviewGroupPanelModel; model: IDockviewGroupPanelModel;
locked: boolean; locked: boolean;
readonly size: number; readonly size: number;
readonly panels: IDockviewPanel[]; readonly panels: IDockviewPanel[];
@ -25,7 +26,7 @@ export class DockviewGroupPanel
extends GridviewPanel extends GridviewPanel
implements IDockviewGroupPanel implements IDockviewGroupPanel
{ {
private readonly _model: DockviewGroupPanelModel; private readonly _model: IDockviewGroupPanelModel;
get panels(): IDockviewPanel[] { get panels(): IDockviewPanel[] {
return this._model.panels; return this._model.panels;
@ -39,26 +40,10 @@ export class DockviewGroupPanel
return this._model.size; return this._model.size;
} }
get model(): DockviewGroupPanelModel { get model(): IDockviewGroupPanelModel {
return this._model; return this._model;
} }
get minimumHeight(): number {
return this._model.minimumHeight;
}
get maximumHeight(): number {
return this._model.maximumHeight;
}
get minimumWidth(): number {
return this._model.minimumWidth;
}
get maximumWidth(): number {
return this._model.maximumWidth;
}
get locked(): boolean { get locked(): boolean {
return this._model.locked; return this._model.locked;
} }
@ -76,7 +61,10 @@ export class DockviewGroupPanel
id: string, id: string,
options: GroupOptions options: GroupOptions
) { ) {
super(id, 'groupview_default'); super(id, 'groupview_default', {
minimumHeight: 100,
minimumWidth: 100,
});
this._model = new DockviewGroupPanelModel( this._model = new DockviewGroupPanelModel(
this.element, this.element,

View File

@ -4,10 +4,9 @@ import { Droptarget, Position } from '../dnd/droptarget';
import { DockviewComponent } from './dockviewComponent'; import { DockviewComponent } from './dockviewComponent';
import { isAncestor, toggleClass } from '../dom'; import { isAncestor, toggleClass } from '../dom';
import { addDisposableListener, Emitter, Event } from '../events'; import { addDisposableListener, Emitter, Event } from '../events';
import { IGridPanelView } from '../gridview/baseComponentGridview';
import { IViewSize } from '../gridview/gridview'; import { IViewSize } from '../gridview/gridview';
import { CompositeDisposable } from '../lifecycle'; import { CompositeDisposable } from '../lifecycle';
import { PanelInitParameters, PanelUpdateEvent } from '../panel/types'; import { IPanel, PanelInitParameters, PanelUpdateEvent } from '../panel/types';
import { import {
ContentContainer, ContentContainer,
IContentContainer, IContentContainer,
@ -82,7 +81,7 @@ export interface IHeader {
height: number | undefined; height: number | undefined;
} }
export interface IDockviewGroupPanelModel extends IGridPanelView { export interface IDockviewGroupPanelModel extends IPanel {
readonly isActive: boolean; readonly isActive: boolean;
readonly size: number; readonly size: number;
readonly panels: IDockviewPanel[]; readonly panels: IDockviewPanel[];
@ -95,13 +94,20 @@ export interface IDockviewGroupPanelModel extends IGridPanelView {
readonly onDidActivePanelChange: Event<GroupviewChangeEvent>; readonly onDidActivePanelChange: Event<GroupviewChangeEvent>;
readonly onMove: Event<GroupMoveEvent>; readonly onMove: Event<GroupMoveEvent>;
locked: boolean; locked: boolean;
setActive(isActive: boolean): void;
initialize(): void;
// state // state
isPanelActive: (panel: IDockviewPanel) => boolean; isPanelActive: (panel: IDockviewPanel) => boolean;
indexOf(panel: IDockviewPanel): number; indexOf(panel: IDockviewPanel): number;
// panel lifecycle // panel lifecycle
openPanel( openPanel(
panel: IDockviewPanel, panel: IDockviewPanel,
options?: { index?: number; skipFocus?: boolean } options?: {
index?: number;
skipFocus?: boolean;
skipSetPanelActive?: boolean;
skipSetGroupActive?: boolean;
}
): void; ): void;
closePanel(panel: IDockviewPanel): void; closePanel(panel: IDockviewPanel): void;
closeAllPanels(): void; closeAllPanels(): void;
@ -199,22 +205,6 @@ export class DockviewGroupPanelModel
return this._panels.length === 0; return this._panels.length === 0;
} }
get minimumHeight(): number {
return 100;
}
get maximumHeight(): number {
return Number.MAX_SAFE_INTEGER;
}
get minimumWidth(): number {
return 100;
}
get maximumWidth(): number {
return Number.MAX_SAFE_INTEGER;
}
get hasWatermark(): boolean { get hasWatermark(): boolean {
return !!( return !!(
this.watermark && this.container.contains(this.watermark.element) this.watermark && this.container.contains(this.watermark.element)

View File

@ -126,9 +126,31 @@ export abstract class GridviewPanel
return this.api.isActive; return this.api.isActive;
} }
constructor(id: string, component: string) { constructor(
id: string,
component: string,
options?: {
minimumWidth?: number;
maximumWidth?: number;
minimumHeight?: number;
maximumHeight?: number;
}
) {
super(id, component, new GridviewPanelApiImpl(id)); super(id, component, new GridviewPanelApiImpl(id));
if (typeof options?.minimumWidth === 'number') {
this._minimumWidth = options.minimumWidth;
}
if (typeof options?.maximumWidth === 'number') {
this._maximumWidth = options.maximumWidth;
}
if (typeof options?.minimumHeight === 'number') {
this._minimumHeight = options.minimumHeight;
}
if (typeof options?.maximumHeight === 'number') {
this._maximumHeight = options.maximumHeight;
}
this.api.initialize(this); // TODO: required to by-pass 'super before this' requirement this.api.initialize(this); // TODO: required to by-pass 'super before this' requirement
this.addDisposables( this.addDisposables(

View File

@ -1,92 +0,0 @@
import { trackFocus } from './dom';
import { Emitter, Event } from './events';
import { IDisposable } from './lifecycle';
export interface HostedContainerOptions {
id: string;
parent?: HTMLElement;
}
export class HostedContainer implements IDisposable {
private readonly _element: HTMLElement;
private readonly _onDidFocus = new Emitter<void>();
readonly onDidFocus: Event<void> = this._onDidFocus.event;
private readonly _onDidBlur = new Emitter<void>();
readonly onDidBlur: Event<void> = this._onDidBlur.event;
get element() {
return this._element;
}
constructor(private readonly options: HostedContainerOptions) {
if (!options.parent) {
options.parent = document.getElementById('app') as HTMLElement;
options.parent.style.position = 'relative';
}
this._element = document.createElement('div');
this._element.style.visibility = 'hidden';
this._element.style.overflow = 'hidden';
// this._element.style.pointerEvents = 'none';
this._element.id = `webview-${options.id}`;
this._element.tabIndex = -1;
const { onDidFocus, onDidBlur } = trackFocus(this._element);
onDidFocus(() => this._onDidFocus.fire());
onDidBlur(() => this._onDidBlur.fire());
/**
* When dragging somebody
*/
window.addEventListener('dragstart', () => {
this.element.style.pointerEvents = 'none';
});
window.addEventListener('dragend', () => {
this.element.style.pointerEvents = '';
});
window.addEventListener('mousemove', (ev) => {
if (ev.buttons === 0) {
this.element.style.pointerEvents = '';
}
});
options.parent.appendChild(this._element);
}
hide() {
this._element.style.visibility = 'hidden';
}
show() {
this._element.style.visibility = 'visible';
}
layout(
element: HTMLElement,
dimension?: { width: number; height: number }
) {
if (!this.element || !this.element.parentElement) {
return;
}
const frameRect = element.getBoundingClientRect();
const containerRect =
this.element.parentElement.getBoundingClientRect();
this.element.style.position = 'absolute';
this.element.style.top = `${frameRect.top - containerRect.top}px`;
this.element.style.left = `${frameRect.left - containerRect.left}px`;
this.element.style.width = `${
dimension ? dimension.width : frameRect.width
}px`;
this.element.style.height = `${
dimension ? dimension.height : frameRect.height
}px`;
}
dispose() {
this._element.remove();
}
}

View File

@ -1,5 +1,3 @@
export * from './hostedContainer';
export * from './dnd/dataTransfer'; export * from './dnd/dataTransfer';
export { watchElementResize } from './dom'; export { watchElementResize } from './dom';

View File

@ -9,10 +9,9 @@ import {
toggleClass, toggleClass,
getElementsByTagName, getElementsByTagName,
} from '../dom'; } from '../dom';
import { clamp } from '../math';
import { Event, Emitter } from '../events'; import { Event, Emitter } from '../events';
import { pushToStart, pushToEnd, firstIndex } from '../array'; import { pushToStart, pushToEnd, firstIndex } from '../array';
import { range } from '../math'; import { range, clamp } from '../math';
import { ViewItem } from './viewItem'; import { ViewItem } from './viewItem';
export enum Orientation { export enum Orientation {

View File

@ -1,6 +1,5 @@
import * as React from 'react'; import * as React from 'react';
import { ReactPart, ReactPortalStore } from '../react'; import { ReactPart, ReactPortalStore } from '../react';
import { IGroupPanelBaseProps } from './dockview';
import { import {
PanelUpdateEvent, PanelUpdateEvent,
DockviewGroupPanel, DockviewGroupPanel,

View File

@ -32,7 +32,7 @@ import DockviewSetTitle from '@site/sandboxes/updatetitle-dockview/src/app';
Dockview is an abstraction built on top of [Gridviews](./gridview) where each view is a container of many tabbed panels. Dockview is an abstraction built on top of [Gridviews](./gridview) where each view is a container of many tabbed panels.
<Container> <Container sandboxId="simple-dockview">
<SimpleDockview /> <SimpleDockview />
</Container> </Container>
@ -209,7 +209,7 @@ const onReady = (event: DockviewReadyEvent) => {
Here is an example using the above code loading from and saving to localStorage. Here is an example using the above code loading from and saving to localStorage.
If you refresh the page you should notice your layout is loaded as you left it. If you refresh the page you should notice your layout is loaded as you left it.
<Container> <Container sandboxId="layout-dockview">
<DockviewPersistance /> <DockviewPersistance />
</Container> </Container>
@ -238,7 +238,7 @@ props.api.group.api.setSize({
You can see an example invoking both approaches below. You can see an example invoking both approaches below.
<Container> <Container sandboxId="resize-dockview">
<ResizeDockview /> <ResizeDockview />
</Container> </Container>
@ -248,7 +248,7 @@ When the dockview is empty you may want to display some fallback content, this i
By default there the watermark has no content but you can provide as a prop to `DockviewReact` a `watermarkComponent` By default there the watermark has no content but you can provide as a prop to `DockviewReact` a `watermarkComponent`
which will be rendered when there are no panels or groups. which will be rendered when there are no panels or groups.
<Container> <Container sandboxId="watermark-dockview">
<DockviewWatermark /> <DockviewWatermark />
</Container> </Container>
@ -327,7 +327,7 @@ return (
); );
``` ```
<Container> <Container sandboxId="dnd-dockview">
<DndDockview /> <DndDockview />
</Container> </Container>
@ -525,7 +525,7 @@ As a simple example the below attaches a custom event handler for the context me
The below example uses a custom tab renderer to reigster a popover when the user right clicked on a tab. The below example uses a custom tab renderer to reigster a popover when the user right clicked on a tab.
This still makes use of the `DockviewDefaultTab` since it's only a minor change. This still makes use of the `DockviewDefaultTab` since it's only a minor change.
<Container> <Container sandboxId="customheader-dockview">
<CustomHeadersDockview /> <CustomHeadersDockview />
</Container> </Container>
@ -550,7 +550,7 @@ api.setTitle('my_new_custom_title');
> Note this only works when using the default tab implementation. > Note this only works when using the default tab implementation.
<Container> <Container sandboxId="updatetitle-dockview">
<DockviewSetTitle /> <DockviewSetTitle />
</Container> </Container>
@ -599,7 +599,7 @@ to the entire width of the group. For example:
<DockviewReactComponent singleTabMode="fullwidth" {...otherProps} /> <DockviewReactComponent singleTabMode="fullwidth" {...otherProps} />
``` ```
<Container> <Container sandboxId="fullwidthtab-dockview">
<DockviewNative /> <DockviewNative />
</Container> </Container>
@ -653,7 +653,7 @@ const GroupControlComponent = (props: IDockviewGroupControlProps) => {
}; };
``` ```
<Container> <Container sandboxId="groupcontrol-dockview">
<DockviewGroupControl /> <DockviewGroupControl />
</Container> </Container>
@ -669,13 +669,15 @@ api.group.api.setConstraints(...)
> If you specific a constraint on a group and move a panel within that group to another group it will no > If you specific a constraint on a group and move a panel within that group to another group it will no
> longer be subject to those constraints since those constraints were on the group and not on the individual panel. > longer be subject to those constraints since those constraints were on the group and not on the individual panel.
<Container height={500}> <Container height={500} sandboxId="constraints-dockview">
<DockviewConstraints /> <DockviewConstraints />
</Container> </Container>
## Events ## Events
<Container height={600}> A simple example showing events fired by `dockviewz that can be interacted with.
<Container height={600} sandboxId="events-dockview">
<EventsDockview /> <EventsDockview />
</Container> </Container>
@ -686,7 +688,7 @@ api.group.api.setConstraints(...)
You can safely create multiple dockview instances within one page and nest dockviews within other dockviews. You can safely create multiple dockview instances within one page and nest dockviews within other dockviews.
If you wish to interact with the drop event from one dockview instance in another dockview instance you can implement the `showDndOverlay` and `onDidDrop` props on `DockviewReact`. If you wish to interact with the drop event from one dockview instance in another dockview instance you can implement the `showDndOverlay` and `onDidDrop` props on `DockviewReact`.
<Container> <Container sandboxId="nested-dockview">
<NestedDockview /> <NestedDockview />
</Container> </Container>

View File

@ -9,41 +9,16 @@ import {
import * as React from 'react'; import * as React from 'react';
const components = { const components = {
default: (props: IDockviewPanelProps<{ title: string }>) => { default: (props: IDockviewPanelProps) => {
const [contraints, setContraints] = const [contraints, setContraints] =
React.useState<GridConstraintChangeEvent | null>(null); React.useState<GridConstraintChangeEvent | null>(null);
React.useEffect(() => { React.useEffect(() => {
props.api.group.api.setConstraints({ props.api.group.api.onDidConstraintsChange((event) => {
maximumHeight: 200, setContraints(event);
maximumWidth: 200,
}); });
}, []); }, []);
React.useEffect(() => {
const disposable1 = new DockviewMutableDisposable();
const disposable = props.api.onDidGroupChange(() => {
disposable1.value = props.api.group.api.onDidConstraintsChange(
(event) => {
setContraints(event);
}
);
});
setContraints({
maximumHeight: props.api.group.maximumHeight,
minimumHeight: props.api.group.minimumHeight,
maximumWidth: props.api.group.maximumWidth,
minimumWidth: props.api.group.minimumWidth,
});
return () => {
disposable1.dispose();
disposable.dispose();
};
}, []);
return ( return (
<div <div
style={{ style={{
@ -53,13 +28,64 @@ const components = {
color: 'white', color: 'white',
}} }}
> >
<span> {props.params.title}</span>
{contraints && ( {contraints && (
<div> <div style={{ fontSize: '13px' }}>
<div>{`minHeight=${contraints.minimumHeight}`}</div> {typeof contraints.maximumHeight === 'number' && (
<div>{`maxHeight=${contraints.maximumHeight}`}</div> <div
<div>{`minWidth=${contraints.minimumWidth}`}</div> style={{
<div>{`maxWidth=${contraints.maximumWidth}`}</div> border: '1px solid grey',
margin: '2px',
padding: '1px',
}}
>
<span
style={{ color: 'grey' }}
>{`Maximum Height: `}</span>
<span>{`${contraints.maximumHeight}px`}</span>
</div>
)}
{typeof contraints.minimumHeight === 'number' && (
<div
style={{
border: '1px solid grey',
margin: '2px',
padding: '1px',
}}
>
<span
style={{ color: 'grey' }}
>{`Minimum Height: `}</span>
<span>{`${contraints.minimumHeight}px`}</span>
</div>
)}
{typeof contraints.maximumWidth === 'number' && (
<div
style={{
border: '1px solid grey',
margin: '2px',
padding: '1px',
}}
>
<span
style={{ color: 'grey' }}
>{`Maximum Width: `}</span>
<span>{`${contraints.maximumWidth}px`}</span>
</div>
)}
{typeof contraints.minimumWidth === 'number' && (
<div
style={{
border: '1px solid grey',
margin: '2px',
padding: '1px',
}}
>
<span
style={{ color: 'grey' }}
>{`Minimum Width: `}</span>
<span>{`${contraints.minimumWidth}px`}</span>
</div>
)}
</div> </div>
)} )}
</div> </div>
@ -71,19 +97,40 @@ const App = () => {
const [api, setApi] = React.useState<DockviewApi>(); const [api, setApi] = React.useState<DockviewApi>();
const onReady = (event: DockviewReadyEvent) => { const onReady = (event: DockviewReadyEvent) => {
event.api.addPanel({ const panel1 = event.api.addPanel({
id: 'panel_1', id: 'panel_1',
component: 'default', component: 'default',
}); });
event.api.addPanel({ const panel2 = event.api.addPanel({
id: 'panel_2', id: 'panel_2',
component: 'default', component: 'default',
position: {
referencePanel: panel1,
direction: 'right',
},
}); });
event.api.addPanel({ const panel3 = event.api.addPanel({
id: 'panel_3', id: 'panel_3',
component: 'default', component: 'default',
position: {
referencePanel: panel2,
direction: 'right',
},
});
const panel4 = event.api.addPanel({
id: 'panel_4',
component: 'default',
position: {
direction: 'below',
},
});
panel2.api.group.api.setConstraints({
maximumWidth: 300,
maximumHeight: 300,
}); });
}; };

View File

@ -182,49 +182,49 @@ const EventsDockview = () => {
panels: { panels: {
panel_1: { panel_1: {
id: 'panel_1', id: 'panel_1',
view: { content: { id: 'default' } }, contentComponent: 'default',
params: { title: 'Panel 1' }, params: { title: 'Panel 1' },
title: 'panel_1', title: 'panel_1',
}, },
panel_2: { panel_2: {
id: 'panel_2', id: 'panel_2',
view: { content: { id: 'default' } }, contentComponent: 'default',
params: { title: 'Panel 2' }, params: { title: 'Panel 2' },
title: 'panel_2', title: 'panel_2',
}, },
panel_3: { panel_3: {
id: 'panel_3', id: 'panel_3',
view: { content: { id: 'default' } }, contentComponent: 'default',
params: { title: 'Panel 3' }, params: { title: 'Panel 3' },
title: 'panel_3', title: 'panel_3',
}, },
panel_4: { panel_4: {
id: 'panel_4', id: 'panel_4',
view: { content: { id: 'default' } }, contentComponent: 'default',
params: { title: 'Panel 4' }, params: { title: 'Panel 4' },
title: 'panel_4', title: 'panel_4',
}, },
panel_5: { panel_5: {
id: 'panel_5', id: 'panel_5',
view: { content: { id: 'default' } }, contentComponent: 'default',
params: { title: 'Panel 5' }, params: { title: 'Panel 5' },
title: 'panel_5', title: 'panel_5',
}, },
panel_6: { panel_6: {
id: 'panel_6', id: 'panel_6',
view: { content: { id: 'default' } }, contentComponent: 'default',
params: { title: 'Panel 6' }, params: { title: 'Panel 6' },
title: 'panel_6', title: 'panel_6',
}, },
panel_8: { panel_8: {
id: 'panel_8', id: 'panel_8',
view: { content: { id: 'default' } }, contentComponent: 'default',
params: { title: 'Panel 8' }, params: { title: 'Panel 8' },
title: 'panel_8', title: 'panel_8',
}, },
panel_7: { panel_7: {
id: 'panel_7', id: 'panel_7',
view: { content: { id: 'default' } }, contentComponent: 'default',
params: { title: 'Panel 7' }, params: { title: 'Panel 7' },
title: 'panel_7', title: 'panel_7',
}, },
@ -334,7 +334,7 @@ const EventsDockview = () => {
className="dockview-theme-abyss" className="dockview-theme-abyss"
/> />
</div> </div>
<div style={{ flexGrow: 1 }}> <div style={{ flexGrow: 1, paddingTop: '5px' }}>
<Console lines={lines} /> <Console lines={lines} />
</div> </div>
</div> </div>

View File

@ -0,0 +1,22 @@
{
"name": "vanilla-dockview",
"description": "",
"keywords": [
"dockview"
],
"version": "1.0.0",
"main": "src/index.ts",
"dependencies": {
"dockview-core": "*"
},
"devDependencies": {
"typescript": "^4.9.5"
},
"scripts": {},
"browserslist": [
">0.2%",
"not dead",
"not ie <= 11",
"not op_mini all"
]
}

View File

@ -0,0 +1,43 @@
<!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">
<!--
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>

View File

@ -0,0 +1,88 @@
import {
DockviewComponent,
IContentRenderer,
IGroupPanelInitParameters,
} from 'dockview';
class DefaultPanel implements IContentRenderer {
private _element: HTMLElement;
get element(): HTMLElement {
return this._element;
}
constructor() {
this._element = document.createElement('div');
}
init(params: IGroupPanelInitParameters): void {
//
}
}
export function attach(parent: HTMLElement): {
dispose: () => void;
} {
const element = document.createElement('div');
element.className = 'dockview-theme-abyss';
parent.appendChild(element);
const dockview = new DockviewComponent(element, {
components: {
default: DefaultPanel,
},
});
const observer = new ResizeObserver((entires) => {
const firstEntry = entires[0];
const { width, height } = firstEntry.contentRect;
dockview.layout(width, height);
});
observer.observe(parent);
const panel1 = dockview.addPanel({
id: 'panel_1',
title: 'Panel 1',
component: 'default',
});
const panel2 = dockview.addPanel({
id: 'panel_2',
title: 'Panel 2',
component: 'default',
position: {
referencePanel: panel1,
direction: 'right',
},
});
const panel3 = dockview.addPanel({
id: 'panel_3',
title: 'Panel 3',
component: 'default',
position: {
referenceGroup: panel2.group,
},
});
const pane4 = dockview.addPanel({
id: 'panel_4',
title: 'Panel 4',
component: 'default',
position: {
direction: 'below',
},
});
return {
dispose: () => {
observer.unobserve(element);
observer.disconnect();
dockview.dispose();
element.remove();
},
};
}

View File

@ -0,0 +1,10 @@
import './styles.css';
import 'dockview/dist/styles/dockview.css';
import { attach } from './app';
const rootElement = document.getElementById('root');
if (rootElement) {
attach(rootElement);
}

View File

@ -0,0 +1,16 @@
body {
margin: 0px;
color: white;
font-family: sans-serif;
text-align: center;
}
#root {
height: 100vh;
width: 100vw;
}
.app {
height: 100%;
}

View File

@ -0,0 +1,20 @@
{
"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,
"suppressImplicitAnyIndexErrors": true,
"noUnusedLocals": true
}
}

View File

@ -1,7 +1,9 @@
import useBaseUrl from '@docusaurus/useBaseUrl';
import * as React from 'react'; import * as React from 'react';
import './container.scss'; import './container.scss';
const BASE_SANDBOX_URL =
'https://codesandbox.io/s/github/mathuo/dockview/tree/master/packages/docs/sandboxes';
const createSvgElementFromPath = (params: { const createSvgElementFromPath = (params: {
height: string; height: string;
width: string; width: string;
@ -33,9 +35,17 @@ export const Container = (props: {
children?: React.ReactNode; children?: React.ReactNode;
height?: number; height?: number;
injectVanillaJS?: (parent: HTMLElement) => void; injectVanillaJS?: (parent: HTMLElement) => void;
sandboxId?: string;
}) => { }) => {
const ref = React.useRef<HTMLDivElement>(null); const ref = React.useRef<HTMLDivElement>(null);
const url = React.useMemo(() => {
if (!props.sandboxId) {
return '';
}
return `${BASE_SANDBOX_URL}/${props.sandboxId}`;
}, [props.sandboxId]);
React.useEffect(() => { React.useEffect(() => {
if (!props.injectVanillaJS) { if (!props.injectVanillaJS) {
return; return;
@ -64,24 +74,29 @@ export const Container = (props: {
> >
<span style={{ flexGrow: 1 }} /> <span style={{ flexGrow: 1 }} />
<span {url && (
className="codesandbox-button" <span
style={{ display: 'flex', alignItems: 'center' }} className="codesandbox-button"
> style={{ display: 'flex', alignItems: 'center' }}
<span className="codesandbox-button-pretext">{`Open in `}</span>
<a
href="https://www.google.com"
target={'_blank'}
className="codesandbox-button-content"
> >
<span <span className="codesandbox-button-pretext">{`Open in `}</span>
style={{ fontWeight: 'bold', paddingRight: '4px' }} <a
href={url}
target={'_blank'}
className="codesandbox-button-content"
> >
CodeSandbox <span
</span> style={{
<CreateCloseButton /> fontWeight: 'bold',
</a> paddingRight: '4px',
</span> }}
>
CodeSandbox
</span>
<CreateCloseButton />
</a>
</span>
)}
{/* <span {/* <span
style={{ fontSize: '16px' }} style={{ fontSize: '16px' }}
className="material-symbols-outlined" className="material-symbols-outlined"