feat: refactor dockview update logic

This commit is contained in:
mathuo 2021-06-07 18:13:04 +01:00
parent 3f93b34f73
commit ccd8e7fdf5
14 changed files with 479 additions and 259 deletions

View File

@ -83,7 +83,7 @@ export const Params = (props: {
const interval = setInterval(() => {
const panel1 = gridApi.getPanel('panel1');
panel1.update({ params: { ticker: Date.now() } });
panel1.update({ params: { params: { ticker: Date.now() } } });
}, 1000);
return () => {
clearInterval(interval);

View File

@ -0,0 +1,111 @@
import {
GridviewApi,
GridviewReact,
GridviewReadyEvent,
IGridviewPanelProps,
Orientation,
PanelCollection,
} from 'dockview';
import * as React from 'react';
import { Meta } from '@storybook/react';
const components: PanelCollection<IGridviewPanelProps<any>> = {
default: (props) => {
return (
<div style={{ padding: '10px', height: '100%' }}>hello world</div>
);
},
ticker: (props: IGridviewPanelProps<{ ticker: number }>) => {
return (
<div style={{ padding: '10px', height: '100%' }}>
{`The current ticker value is ${props.params.ticker}`}
</div>
);
},
};
export const Params = (props: {
onEvent: (name: string) => void;
theme: string;
hideBorders: boolean;
disableAutoResizing: boolean;
}) => {
const api = React.useRef<GridviewApi>();
React.useEffect(() => {
if (!api.current) {
return () => {
// noop
};
}
const gridApi = api.current;
const interval = setInterval(() => {
const panel1 = gridApi.getPanel('panel1');
panel1.update({ params: { ticker: Date.now() } });
}, 1000);
return () => {
clearInterval(interval);
};
}, [api]);
const onReady = (event: GridviewReadyEvent) => {
api.current = event.api;
event.api.onGridEvent((e) => props.onEvent(e.kind));
event.api.addPanel({
id: 'panel1',
component: 'ticker',
params: {
ticker: 0,
},
});
event.api.addPanel({
id: 'panel2',
component: 'default',
});
event.api.addPanel({
id: 'panel3',
component: 'default',
});
};
return (
<GridviewReact
orientation={Orientation.HORIZONTAL}
className={props.theme}
onReady={onReady}
components={components}
hideBorders={props.hideBorders}
disableAutoResizing={props.disableAutoResizing}
/>
);
};
export default {
title: 'Library/Gridview/Params',
component: Params,
decorators: [
(Component) => {
document.body.style.padding = '0px';
return (
<div style={{ height: '100vh', fontFamily: 'Arial' }}>
<Component />
</div>
);
},
],
args: { theme: 'dockview-theme-light' },
argTypes: {
theme: {
control: {
type: 'select',
options: ['dockview-theme-dark', 'dockview-theme-light'],
},
},
onEvent: { action: 'onEvent' },
},
} as Meta;

View File

@ -9,7 +9,7 @@ import {
import * as React from 'react';
import { Meta } from '@storybook/react';
const components: PanelCollection = {
const components: PanelCollection<ISplitviewPanelProps> = {
default: (props: ISplitviewPanelProps<{ color: string }>) => {
const resize = () => {
props.api.setSize({ size: 300 });

View File

@ -0,0 +1,109 @@
import {
ISplitviewPanelProps,
Orientation,
PanelCollection,
SplitviewApi,
SplitviewReact,
SplitviewReadyEvent,
} from 'dockview';
import * as React from 'react';
import { Meta } from '@storybook/react';
const components: PanelCollection<ISplitviewPanelProps<any>> = {
default: (props) => {
return (
<div style={{ padding: '10px', height: '100%' }}>hello world</div>
);
},
ticker: (props: ISplitviewPanelProps<{ ticker: number }>) => {
return (
<div style={{ padding: '10px', height: '100%' }}>
{`The current ticker value is ${props.params.ticker}`}
</div>
);
},
};
export const Params = (props: {
onEvent: (name: string) => void;
theme: string;
hideBorders: boolean;
disableAutoResizing: boolean;
}) => {
const api = React.useRef<SplitviewApi>();
React.useEffect(() => {
if (!api.current) {
return () => {
// noop
};
}
const gridApi = api.current;
const interval = setInterval(() => {
const panel1 = gridApi.getPanel('panel1');
panel1.update({ params: { ticker: Date.now() } });
}, 1000);
return () => {
clearInterval(interval);
};
}, [api]);
const onReady = (event: SplitviewReadyEvent) => {
api.current = event.api;
event.api.addPanel({
id: 'panel1',
component: 'ticker',
params: {
ticker: 0,
},
});
event.api.addPanel({
id: 'panel2',
component: 'default',
});
event.api.addPanel({
id: 'panel3',
component: 'default',
});
};
return (
<SplitviewReact
orientation={Orientation.HORIZONTAL}
className={props.theme}
onReady={onReady}
components={components}
hideBorders={props.hideBorders}
disableAutoResizing={props.disableAutoResizing}
/>
);
};
export default {
title: 'Library/Splitview/Params',
component: Params,
decorators: [
(Component) => {
document.body.style.padding = '0px';
return (
<div style={{ height: '100vh', fontFamily: 'Arial' }}>
<Component />
</div>
);
},
],
args: { theme: 'dockview-theme-light' },
argTypes: {
theme: {
control: {
type: 'select',
options: ['dockview-theme-dark', 'dockview-theme-light'],
},
},
onEvent: { action: 'onEvent' },
},
} as Meta;

View File

@ -58,11 +58,11 @@ export class DockviewPanelApiImpl
}
get title() {
return this.panel.params?.title || '';
return this.panel.title;
}
get suppressClosable() {
return !!this.panel.params?.suppressClosable;
return !!this.panel.suppressClosable;
}
get isGroupActive() {

View File

@ -1,22 +1,19 @@
import {
DefaultTab,
WrappedTab,
} from '../../../dockview/components/tab/defaultTab';
import { DefaultTab, WrappedTab } from './components/tab/defaultTab';
import {
GroupPanelPartInitParameters,
IActionsRenderer,
IContentRenderer,
ITabRenderer,
} from '../../../groupview/types';
import { GroupviewPanel } from '../../../groupview/groupviewPanel';
import { IDisposable } from '../../../lifecycle';
import { PanelUpdateEvent } from '../../../panel/types';
} from '../groupview/types';
import { GroupviewPanel } from '../groupview/groupviewPanel';
import { IDisposable } from '../lifecycle';
import { GroupPanelUpdateEvent } from '../groupview/groupPanel';
export interface IGroupPanelView extends IDisposable {
readonly content: IContentRenderer;
readonly tab?: ITabRenderer;
readonly actions?: IActionsRenderer;
update(event: PanelUpdateEvent): void;
update(event: GroupPanelUpdateEvent): void;
layout(width: number, height: number): void;
init(params: GroupPanelPartInitParameters): void;
updateParentGroup(group: GroupviewPanel, isPanelVisible: boolean): void;
@ -65,19 +62,19 @@ export class DefaultGroupPanelView implements IGroupPanelView {
}
updateParentGroup(group: GroupviewPanel, isPanelVisible: boolean): void {
//
// TODO
}
layout(width: number, height: number): void {
this.content.layout(width, height);
}
update(event: PanelUpdateEvent): void {
update(event: GroupPanelUpdateEvent): void {
this.content.update(event);
this.tab.update(event);
}
toJSON() {
toJSON(): {} {
return {
content: this.content.toJSON(),
tab:

View File

@ -5,11 +5,8 @@ import {
} from '../gridview/gridview';
import { Position } from '../dnd/droptarget';
import { tail, sequenceEquals } from '../array';
import {
GroupPanel,
GroupviewPanelState,
IGroupPanel,
} from '../groupview/groupPanel';
import { GroupviewPanelState, IGroupPanel } from '../groupview/groupPanel';
import { DockviewGroupPanel } from './dockviewGroupPanel';
import {
CompositeDisposable,
IDisposable,
@ -58,7 +55,7 @@ import {
GroupPanelViewState,
} from '../groupview/groupview';
import { GroupviewPanel } from '../groupview/groupviewPanel';
import { DefaultGroupPanelView } from '../react/dockview/v2/defaultGroupPanelView';
import { DefaultGroupPanelView } from './defaultGroupPanelView';
const nextGroupId = sequentialNumberGenerator();
@ -595,7 +592,10 @@ export class DockviewComponent
tab: this.createTabComponent(options.id, options.tabComponent),
});
const panel: IGroupPanel = new GroupPanel(options.id, this._api);
const panel: IGroupPanel = new DockviewGroupPanel(
options.id,
this._api
);
panel.init({
view,
title: options.title || options.id,
@ -704,15 +704,8 @@ export class DockviewComponent
): void {
let group: GroupviewPanel;
// if (
// this.groups.size === 1 &&
// Array.from(this.groups.values())[0].value.size === 0
// ) {
// group = Array.from(this.groups.values())[0].value;
// } else {
group! = this.createGroup();
this.doAddGroup(group, location);
// }
group.group.openPanel(panel);
}
@ -815,7 +808,6 @@ export class DockviewComponent
`Duplicate group id ${options?.id}. reassigning group id to avoid errors`
);
id = undefined;
// throw new Error(`duplicate group ${options.id}`);
}
if (!id) {

View File

@ -0,0 +1,203 @@
import { DockviewApi } from '../api/component.api';
import { DockviewPanelApiImpl } from '../api/groupPanelApi';
import { Event } from '../events';
import {
GroupPanelUpdateEvent,
GroupviewPanelState,
IGroupPanel,
IGroupPanelInitParameters,
} from '../groupview/groupPanel';
import { GroupChangeKind } from '../groupview/groupview';
import { GroupviewPanel } from '../groupview/groupviewPanel';
import { CompositeDisposable, MutableDisposable } from '../lifecycle';
import { Parameters } from '../panel/types';
import { IGroupPanelView } from './defaultGroupPanelView';
export class DockviewGroupPanel
extends CompositeDisposable
implements IGroupPanel {
private readonly mutableDisposable = new MutableDisposable();
readonly api: DockviewPanelApiImpl;
private _group: GroupviewPanel | undefined;
private _params?: Parameters;
readonly onDidStateChange: Event<void>;
private _view?: IGroupPanelView;
private _title: string;
private _suppressClosable: boolean;
get title() {
return this._title;
}
get suppressClosable() {
return this._suppressClosable;
}
get group(): GroupviewPanel | undefined {
return this._group;
}
get view() {
return this._view;
}
constructor(
public readonly id: string,
private readonly containerApi: DockviewApi
) {
super();
this._suppressClosable = false;
this._title = '';
this.api = new DockviewPanelApiImpl(this, this._group);
this.onDidStateChange = this.api.onDidStateChange;
this.addDisposables(
this.api.onActiveChange(() => {
this.containerApi.setActivePanel(this);
}),
this.api.onDidTitleChange((event) => {
const title = event.title;
this.update({ params: { title } });
})
);
}
public init(params: IGroupPanelInitParameters): void {
this._params = params.params;
this._view = params.view;
this._title = params.title;
this._suppressClosable = params.suppressClosable || false;
if (params.state) {
this.api.setState(params.state);
}
this.view?.init({
...params,
api: this.api,
containerApi: this.containerApi,
});
}
focus() {
this.api._onFocusEvent.fire();
}
public setDirty(isDirty: boolean) {
this.api._onDidDirtyChange.fire(isDirty);
}
public close(): Promise<boolean> {
if (this.api.tryClose) {
return this.api.tryClose();
}
return Promise.resolve(true);
}
public toJSON(): GroupviewPanelState {
const state = this.api.getState();
return {
id: this.id,
view: this.view!.toJSON(),
params:
Object.keys(this._params || {}).length > 0
? this._params
: undefined,
title: this._params?.title as string,
suppressClosable: this._params?.suppressClosable,
state: state && Object.keys(state).length > 0 ? state : undefined,
};
}
public update(event: GroupPanelUpdateEvent): void {
const params = event.params as IGroupPanelInitParameters;
const didTitleChange =
typeof params.title === 'string' &&
params.title !== this._params?.title;
const didSuppressChangableClose =
typeof params.suppressClosable === 'boolean' &&
params.suppressClosable !== this._params?.suppressClosable;
this._params = {
...(this._params || {}),
...event.params.params,
};
if (didTitleChange) {
this.api._titleChanged.fire({ title: params.title });
}
if (didSuppressChangableClose) {
this.api._suppressClosableChanged.fire({
suppressClosable: !!params.suppressClosable,
});
}
this.view?.update({
params: {
params: this._params,
title: this.title,
suppressClosable: this.suppressClosable,
},
});
}
public updateParentGroup(group: GroupviewPanel, isGroupActive: boolean) {
this._group = group;
this.api.group = group;
this.mutableDisposable.value = this._group.group.onDidGroupChange(
(ev) => {
if (ev.kind === GroupChangeKind.GROUP_ACTIVE) {
const isVisible = !!this._group?.group.isPanelActive(this);
this.api._onDidActiveChange.fire({
isActive: isGroupActive && isVisible,
});
this.api._onDidVisibilityChange.fire({
isVisible,
});
}
}
);
const isPanelVisible = this._group.group.isPanelActive(this);
this.api._onDidActiveChange.fire({
isActive: isGroupActive && isPanelVisible,
});
this.api._onDidVisibilityChange.fire({
isVisible: isPanelVisible,
});
this.view?.updateParentGroup(
this._group,
this._group.group.isPanelActive(this)
);
}
public layout(width: number, height: number) {
// the obtain the correct dimensions of the content panel we must deduct the tab height
this.api._onDidPanelDimensionChange.fire({
width,
height: height - (this.group?.group.tabHeight || 0),
});
this.view?.layout(width, height);
}
public dispose() {
this.api.dispose();
this.mutableDisposable.dispose();
this.view?.dispose();
}
}

View File

@ -97,15 +97,15 @@ export abstract class BasePanelView<T extends PanelApiImpl>
this.part = this.getComponent();
}
update(params: PanelUpdateEvent) {
update(event: PanelUpdateEvent) {
this.params = {
...this.params,
params: {
...this.params?.params,
...params.params,
...event.params,
},
};
this.part?.update(this.params.params);
this.part?.update({ params: this.params.params });
}
toJSON(): BasePanelViewState {

View File

@ -1,16 +1,15 @@
import { DockviewPanelApiImpl, DockviewPanelApi } from '../api/groupPanelApi';
import { DockviewPanelApi } from '../api/groupPanelApi';
import { Event } from '../events';
import {
MutableDisposable,
CompositeDisposable,
IDisposable,
} from '../lifecycle';
import { IDisposable } from '../lifecycle';
import { HeaderPartInitParameters } from './types';
import { IPanel, PanelInitParameters, PanelUpdateEvent } from '../panel/types';
import { DockviewApi } from '../api/component.api';
import {
IPanel,
PanelInitParameters,
PanelUpdateEvent,
Parameters,
} from '../panel/types';
import { GroupviewPanel } from './groupviewPanel';
import { GroupChangeKind } from './groupview';
import { IGroupPanelView } from '../react/dockview/v2/defaultGroupPanelView';
import { IGroupPanelView } from '../dockview/defaultGroupPanelView';
export interface IGroupPanelInitParameters
extends PanelInitParameters,
@ -18,193 +17,32 @@ export interface IGroupPanelInitParameters
view: IGroupPanelView;
}
export type GroupPanelUpdateEvent = PanelUpdateEvent<{
params?: Parameters;
title?: string;
suppressClosable?: boolean;
}>;
export interface IGroupPanel extends IDisposable, IPanel {
readonly view?: IGroupPanelView;
readonly group?: GroupviewPanel;
readonly api: DockviewPanelApi;
readonly params?: IGroupPanelInitParameters;
readonly title: string;
readonly suppressClosable: boolean;
updateParentGroup(group: GroupviewPanel, isGroupActive: boolean): void;
setDirty(isDirty: boolean): void;
close?(): Promise<boolean>;
init(params: IGroupPanelInitParameters): void;
onDidStateChange: Event<void>;
toJSON(): GroupviewPanelState;
update(event: GroupPanelUpdateEvent): void;
}
export interface GroupviewPanelState {
id: string;
view?: any;
params?: { [key: string]: any };
title: string;
params?: { [key: string]: any };
suppressClosable?: boolean;
state?: { [key: string]: any };
}
export class GroupPanel extends CompositeDisposable implements IGroupPanel {
private readonly mutableDisposable = new MutableDisposable();
readonly api: DockviewPanelApiImpl;
private _group: GroupviewPanel | undefined;
private _params?: IGroupPanelInitParameters;
readonly onDidStateChange: Event<void>;
private _view?: IGroupPanelView;
get params() {
return this._params;
}
get group(): GroupviewPanel | undefined {
return this._group;
}
get view() {
return this._view;
}
constructor(
public readonly id: string,
private readonly containerApi: DockviewApi
) {
super();
this.api = new DockviewPanelApiImpl(this, this._group);
this.onDidStateChange = this.api.onDidStateChange;
this.addDisposables(
this.api.onActiveChange(() => {
this.containerApi.setActivePanel(this);
}),
this.api.onDidTitleChange((event) => {
const title = event.title;
this.update({ params: { title } });
})
);
}
focus() {
this.api._onFocusEvent.fire();
}
public setDirty(isDirty: boolean) {
this.api._onDidDirtyChange.fire(isDirty);
}
public close(): Promise<boolean> {
if (this.api.tryClose) {
return this.api.tryClose();
}
return Promise.resolve(true);
}
public toJSON(): GroupviewPanelState {
const params = this._params?.params;
const state = this.api.getState();
return {
id: this.id,
view: this.view!.toJSON(),
params:
params && Object.keys(params).length > 0 ? params : undefined,
title: this._params?.title as string,
suppressClosable: this._params?.suppressClosable,
state: state && Object.keys(state).length > 0 ? state : undefined,
};
}
public update(params: PanelUpdateEvent<IGroupPanelInitParameters>): void {
let didTitleChange = false;
let didSuppressChangableClose = false;
const innerParams = params.params as IGroupPanelInitParameters;
if (this._params) {
didTitleChange = this._params.title !== innerParams.title;
didSuppressChangableClose =
this._params.suppressClosable !== innerParams.suppressClosable;
this._params.params = {
...(this._params?.params || {}),
...params,
};
}
if (didTitleChange) {
this.api._titleChanged.fire({ title: innerParams.title });
}
if (didSuppressChangableClose) {
this.api._suppressClosableChanged.fire({
suppressClosable: !!innerParams.suppressClosable,
});
}
this.view?.update(params);
}
public init(params: IGroupPanelInitParameters): void {
this._params = params;
this._view = params.view;
if (params.state) {
this.api.setState(params.state);
}
this.view?.init({
...params,
api: this.api,
containerApi: this.containerApi,
});
}
public updateParentGroup(group: GroupviewPanel, isGroupActive: boolean) {
this._group = group;
this.api.group = group;
this.mutableDisposable.value = this._group.group.onDidGroupChange(
(ev) => {
if (ev.kind === GroupChangeKind.GROUP_ACTIVE) {
const isVisible = !!this._group?.group.isPanelActive(this);
this.api._onDidActiveChange.fire({
isActive: isGroupActive && isVisible,
});
this.api._onDidVisibilityChange.fire({
isVisible,
});
}
}
);
const isPanelVisible = this._group.group.isPanelActive(this);
this.api._onDidActiveChange.fire({
isActive: isGroupActive && isPanelVisible,
});
this.api._onDidVisibilityChange.fire({
isVisible: isPanelVisible,
});
this.view?.updateParentGroup(
this._group,
this._group.group.isPanelActive(this)
);
}
public layout(width: number, height: number) {
// the obtain the correct dimensions of the content panel we must deduct the tab height
this.api._onDidPanelDimensionChange.fire({
width,
height: height - (this.group?.group.tabHeight || 0),
});
this.view?.layout(width, height);
}
public dispose() {
this.api.dispose();
this.mutableDisposable.dispose();
this.view?.dispose();
}
}

View File

@ -1,14 +1,11 @@
import { DockviewComponent } from '../dockview/dockviewComponent';
import {
GroupPanel,
GroupviewPanelState,
IGroupPanel,
} from '../groupview/groupPanel';
import { GroupviewPanelState, IGroupPanel } from '../groupview/groupPanel';
import { DockviewGroupPanel } from '../dockview/dockviewGroupPanel';
import { IPanelDeserializer } from '../dockview/deserializer';
import { createComponent } from '../panel/componentFactory';
import { DockviewApi } from '../api/component.api';
import { DefaultTab } from '../dockview/components/tab/defaultTab';
import { DefaultGroupPanelView } from './dockview/v2/defaultGroupPanelView';
import { DefaultGroupPanelView } from '../dockview/defaultGroupPanelView';
export class ReactPanelDeserialzier implements IPanelDeserializer {
constructor(private readonly layout: DockviewComponent) {}
@ -40,7 +37,10 @@ export class ReactPanelDeserialzier implements IPanelDeserializer {
: new DefaultTab(),
});
const panel = new GroupPanel(panelId, new DockviewApi(this.layout));
const panel = new DockviewGroupPanel(
panelId,
new DockviewApi(this.layout)
);
panel.init({
view,

View File

@ -27,12 +27,10 @@ export interface ReactContentPartContext {
export class ReactPanelContentPart implements IContentRenderer {
private _element: HTMLElement;
private part?: ReactPart<IDockviewPanelProps>;
private _group: GroupviewPanel | undefined;
//
private _actionsElement: HTMLElement;
private actionsPart?: ReactPart<any>;
private parameters: GroupPanelContentPartInitParameters | undefined;
private _group: GroupviewPanel | undefined;
// private hostedContainer: HostedContainer;
@ -72,29 +70,15 @@ export class ReactPanelContentPart implements IContentRenderer {
}
focus() {
// this._element.focus();
// TODO
}
public init(parameters: GroupPanelContentPartInitParameters): void {
this.parameters = parameters;
// const api = parameters.api;
// api.onDidVisibilityChange((event) => {
// const { isVisible } = event;
// if (isVisible) {
// this.hostedContainer.show();
// } else {
// this.hostedContainer.hide();
// }
// });
const context: ReactContentPartContext = {
api: parameters.api,
containerApi: parameters.containerApi,
actionsPortalElement: this._actionsElement,
tabPortalElement: this.parameters.tab,
tabPortalElement: parameters.tab,
};
this.part = new ReactPart(
@ -116,12 +100,8 @@ export class ReactPanelContentPart implements IContentRenderer {
};
}
public update(params: PanelUpdateEvent) {
if (this.parameters) {
this.parameters.params = params.params;
}
this.part?.update({ params: this.parameters?.params || {} });
public update(event: PanelUpdateEvent) {
this.part?.update(event.params);
}
public updateParentGroup(

View File

@ -11,7 +11,6 @@ import { IGroupPanelBaseProps } from './dockview';
export class ReactPanelHeaderPart implements ITabRenderer {
private _element: HTMLElement;
private part?: ReactPart<IGroupPanelBaseProps>;
private parameters: GroupPanelPartInitParameters | undefined;
get element() {
return this._element;
@ -30,8 +29,6 @@ export class ReactPanelHeaderPart implements ITabRenderer {
}
public init(parameters: GroupPanelPartInitParameters): void {
this.parameters = parameters;
this.part = new ReactPart(
this.element,
this.reactPortalStore,
@ -45,14 +42,7 @@ export class ReactPanelHeaderPart implements ITabRenderer {
}
public update(event: PanelUpdateEvent) {
if (this.parameters) {
this.parameters.params = {
...this.parameters?.params,
...event.params,
};
}
this.part?.update({ params: this.parameters?.params || {} });
this.part?.update(event.params);
}
public toJSON() {

View File

@ -4,7 +4,7 @@ import {
ViewFactoryData,
} from '../../../dockview/options';
import { ReactPortalStore } from '../../react';
import { DefaultGroupPanelView } from './defaultGroupPanelView';
import { DefaultGroupPanelView } from '../../../dockview/defaultGroupPanelView';
import { ReactContentRenderer } from './reactContentRenderer';
export class ReactGroupPanelView extends DefaultGroupPanelView {