mirror of
https://github.com/mathuo/dockview
synced 2025-02-08 17:35:44 +00:00
feat: serialization of maximized views
This commit is contained in:
parent
24cc974a68
commit
2f4150013b
@ -679,7 +679,8 @@ describe('dockviewComponent', () => {
|
||||
expect(viewQuery.length).toBe(1);
|
||||
});
|
||||
|
||||
test('serialization', () => {
|
||||
describe('serialization', () => {
|
||||
test('basic', () => {
|
||||
dockview.layout(1000, 1000);
|
||||
|
||||
dockview.fromJSON({
|
||||
@ -710,7 +711,10 @@ describe('dockviewComponent', () => {
|
||||
},
|
||||
{
|
||||
type: 'leaf',
|
||||
data: { views: ['panel4'], id: 'group-3' },
|
||||
data: {
|
||||
views: ['panel4'],
|
||||
id: 'group-3',
|
||||
},
|
||||
size: 500,
|
||||
},
|
||||
],
|
||||
@ -856,6 +860,53 @@ describe('dockviewComponent', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('serialized layout with maximized node', () => {
|
||||
const api = new DockviewApi(dockview);
|
||||
|
||||
api.layout(500, 1000);
|
||||
|
||||
api.addPanel({
|
||||
id: 'panel1',
|
||||
component: 'default',
|
||||
});
|
||||
|
||||
api.addPanel({
|
||||
id: 'panel2',
|
||||
component: 'default',
|
||||
position: { direction: 'right' },
|
||||
});
|
||||
|
||||
api.addPanel({
|
||||
id: 'panel3',
|
||||
component: 'default',
|
||||
position: { direction: 'below' },
|
||||
});
|
||||
|
||||
const panel4 = api.addPanel({
|
||||
id: 'panel4',
|
||||
component: 'default',
|
||||
});
|
||||
|
||||
panel4.api.maximize();
|
||||
expect(panel4.api.isMaximized()).toBeTruthy();
|
||||
|
||||
const state = api.toJSON();
|
||||
expect(api.hasMaximizedGroup()).toBeTruthy();
|
||||
expect(panel4.api.isMaximized()).toBeTruthy();
|
||||
|
||||
api.clear();
|
||||
expect(api.groups.length).toBe(0);
|
||||
expect(api.panels.length).toBe(0);
|
||||
|
||||
api.fromJSON(state);
|
||||
const newPanel4 = api.getPanel('panel4')!;
|
||||
expect(api.hasMaximizedGroup()).toBeTruthy();
|
||||
expect(newPanel4.api.isMaximized()).toBeTruthy();
|
||||
|
||||
expect(state).toEqual(api.toJSON());
|
||||
});
|
||||
});
|
||||
|
||||
test('add panel', () => {
|
||||
dockview.layout(500, 1000);
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
import {
|
||||
DockviewMaximizedGroupChanged,
|
||||
FloatingGroupOptions,
|
||||
IDockviewComponent,
|
||||
MovePanelEvent,
|
||||
@ -898,7 +899,7 @@ export class DockviewApi implements CommonApi<SerializedDockview> {
|
||||
this.component.exitMaximizedGroup();
|
||||
}
|
||||
|
||||
get onDidMaximizedGroupChange(): Event<void> {
|
||||
get onDidMaximizedGroupChange(): Event<DockviewMaximizedGroupChanged> {
|
||||
return this.component.onDidMaximizedGroupChange;
|
||||
}
|
||||
|
||||
|
@ -163,6 +163,11 @@ export interface FloatingGroupOptionsInternal extends FloatingGroupOptions {
|
||||
skipActiveGroup?: boolean;
|
||||
}
|
||||
|
||||
export interface DockviewMaximizedGroupChanged {
|
||||
group: DockviewGroupPanel;
|
||||
isMaximized: boolean;
|
||||
}
|
||||
|
||||
export interface IDockviewComponent extends IBaseGrid<DockviewGroupPanel> {
|
||||
readonly activePanel: IDockviewPanel | undefined;
|
||||
readonly totalPanels: number;
|
||||
@ -183,6 +188,7 @@ export interface IDockviewComponent extends IBaseGrid<DockviewGroupPanel> {
|
||||
readonly onDidActiveGroupChange: Event<DockviewGroupPanel | undefined>;
|
||||
readonly onUnhandledDragOverEvent: Event<DockviewDndOverlayEvent>;
|
||||
readonly onDidMovePanel: Event<MovePanelEvent>;
|
||||
readonly onDidMaximizedGroupChange: Event<DockviewMaximizedGroupChanged>;
|
||||
readonly options: DockviewComponentOptions;
|
||||
updateOptions(options: DockviewOptions): void;
|
||||
moveGroupOrPanel(options: MoveGroupOrPanelOptions): void;
|
||||
@ -275,6 +281,10 @@ export class DockviewComponent
|
||||
private readonly _onDidMovePanel = new Emitter<MovePanelEvent>();
|
||||
readonly onDidMovePanel = this._onDidMovePanel.event;
|
||||
|
||||
private readonly _onDidMaximizedGroupChange =
|
||||
new Emitter<DockviewMaximizedGroupChanged>();
|
||||
readonly onDidMaximizedGroupChange = this._onDidMaximizedGroupChange.event;
|
||||
|
||||
private readonly _floatingGroups: DockviewFloatingGroupPanel[] = [];
|
||||
private readonly _popoutGroups: {
|
||||
window: PopoutWindow;
|
||||
@ -395,6 +405,12 @@ export class DockviewComponent
|
||||
this._onDidActiveGroupChange.fire(event);
|
||||
}
|
||||
}),
|
||||
this.onDidMaximizedChange((event) => {
|
||||
this._onDidMaximizedGroupChange.fire({
|
||||
group: event.panel,
|
||||
isMaximized: event.isMaximized,
|
||||
});
|
||||
}),
|
||||
Event.any(
|
||||
this.onDidAdd,
|
||||
this.onDidRemove
|
||||
|
@ -1,5 +1,10 @@
|
||||
import { Emitter, Event, AsapEvent } from '../events';
|
||||
import { getGridLocation, Gridview, IGridView } from './gridview';
|
||||
import {
|
||||
getGridLocation,
|
||||
Gridview,
|
||||
IGridView,
|
||||
MaximizedViewChanged,
|
||||
} from './gridview';
|
||||
import { Position } from '../dnd/droptarget';
|
||||
import { Disposable, IDisposable, IValueDisposable } from '../lifecycle';
|
||||
import { sequentialNumberGenerator } from '../math';
|
||||
@ -8,6 +13,7 @@ import { IPanel } from '../panel/types';
|
||||
import { MovementOptions2 } from '../dockview/options';
|
||||
import { Resizable } from '../resizable';
|
||||
import { Classnames } from '../dom';
|
||||
import { IGridviewComponent } from './gridviewComponent';
|
||||
|
||||
const nextLayoutId = sequentialNumberGenerator();
|
||||
|
||||
@ -29,6 +35,11 @@ export function toTarget(direction: Direction): Position {
|
||||
}
|
||||
}
|
||||
|
||||
export interface MaximizedChanged<T extends IGridPanelView> {
|
||||
panel: T;
|
||||
isMaximized: boolean;
|
||||
}
|
||||
|
||||
export interface BaseGridOptions {
|
||||
readonly proportionalLayout: boolean;
|
||||
readonly orientation: Orientation;
|
||||
@ -56,6 +67,8 @@ export interface IBaseGrid<T extends IGridPanelView> extends IDisposable {
|
||||
readonly activeGroup: T | undefined;
|
||||
readonly size: number;
|
||||
readonly groups: T[];
|
||||
readonly onDidMaximizedChange: Event<MaximizedChanged<T>>;
|
||||
readonly onDidLayoutChange: Event<void>;
|
||||
getPanel(id: string): T | undefined;
|
||||
toJSON(): object;
|
||||
fromJSON(data: any): void;
|
||||
@ -67,8 +80,6 @@ export interface IBaseGrid<T extends IGridPanelView> extends IDisposable {
|
||||
isMaximizedGroup(panel: T): boolean;
|
||||
exitMaximizedGroup(): void;
|
||||
hasMaximizedGroup(): boolean;
|
||||
readonly onDidMaximizedGroupChange: Event<void>;
|
||||
readonly onDidLayoutChange: Event<void>;
|
||||
}
|
||||
|
||||
export abstract class BaseGrid<T extends IGridPanelView>
|
||||
@ -87,6 +98,10 @@ export abstract class BaseGrid<T extends IGridPanelView>
|
||||
private readonly _onDidAdd = new Emitter<T>();
|
||||
readonly onDidAdd: Event<T> = this._onDidAdd.event;
|
||||
|
||||
private readonly _onDidMaximizedChange = new Emitter<MaximizedChanged<T>>();
|
||||
readonly onDidMaximizedChange: Event<MaximizedChanged<T>> =
|
||||
this._onDidMaximizedChange.event;
|
||||
|
||||
private readonly _onDidActiveChange = new Emitter<T | undefined>();
|
||||
readonly onDidActiveChange: Event<T | undefined> =
|
||||
this._onDidActiveChange.event;
|
||||
@ -171,6 +186,12 @@ export abstract class BaseGrid<T extends IGridPanelView>
|
||||
this.layout(0, 0, true); // set some elements height/widths
|
||||
|
||||
this.addDisposables(
|
||||
this.gridview.onDidMaximizedNodeChange((event) => {
|
||||
this._onDidMaximizedChange.fire({
|
||||
panel: event.view as T,
|
||||
isMaximized: event.isMaximized,
|
||||
});
|
||||
}),
|
||||
this.gridview.onDidViewVisibilityChange(() =>
|
||||
this._onDidViewVisibilityChangeMicroTaskQueue.fire()
|
||||
),
|
||||
@ -250,10 +271,6 @@ export abstract class BaseGrid<T extends IGridPanelView>
|
||||
return this.gridview.hasMaximizedView();
|
||||
}
|
||||
|
||||
get onDidMaximizedGroupChange(): Event<void> {
|
||||
return this.gridview.onDidMaximizedNodeChange;
|
||||
}
|
||||
|
||||
protected doAddGroup(
|
||||
group: T,
|
||||
location: number[] = [0],
|
||||
|
@ -265,11 +265,21 @@ export interface IViewDeserializer {
|
||||
fromJSON: (data: ISerializedLeafNode) => IGridView;
|
||||
}
|
||||
|
||||
export interface SerializedNodeDescriptor {
|
||||
location: number[];
|
||||
}
|
||||
|
||||
export interface SerializedGridview<T> {
|
||||
root: SerializedGridObject<T>;
|
||||
width: number;
|
||||
height: number;
|
||||
orientation: Orientation;
|
||||
maximizedNode?: SerializedNodeDescriptor;
|
||||
}
|
||||
|
||||
export interface MaximizedViewChanged {
|
||||
view: IGridView;
|
||||
isMaximized: boolean;
|
||||
}
|
||||
|
||||
export class Gridview implements IDisposable {
|
||||
@ -293,7 +303,8 @@ export class Gridview implements IDisposable {
|
||||
private readonly _onDidViewVisibilityChange = new Emitter<void>();
|
||||
readonly onDidViewVisibilityChange = this._onDidViewVisibilityChange.event;
|
||||
|
||||
private readonly _onDidMaximizedNodeChange = new Emitter<void>();
|
||||
private readonly _onDidMaximizedNodeChange =
|
||||
new Emitter<MaximizedViewChanged>();
|
||||
readonly onDidMaximizedNodeChange = this._onDidMaximizedNodeChange.event;
|
||||
|
||||
public get length(): number {
|
||||
@ -395,6 +406,8 @@ export class Gridview implements IDisposable {
|
||||
this.exitMaximizedView();
|
||||
}
|
||||
|
||||
serializeBranchNode(this.getView(), this.orientation);
|
||||
|
||||
const hiddenOnMaximize: LeafNode[] = [];
|
||||
|
||||
function hideAllViewsBut(parent: BranchNode, exclude: LeafNode): void {
|
||||
@ -416,7 +429,10 @@ export class Gridview implements IDisposable {
|
||||
|
||||
hideAllViewsBut(this.root, node);
|
||||
this._maximizedNode = { leaf: node, hiddenOnMaximize };
|
||||
this._onDidMaximizedNodeChange.fire();
|
||||
this._onDidMaximizedNodeChange.fire({
|
||||
view: node.view,
|
||||
isMaximized: true,
|
||||
});
|
||||
}
|
||||
|
||||
exitMaximizedView(): void {
|
||||
@ -441,27 +457,60 @@ export class Gridview implements IDisposable {
|
||||
|
||||
showViewsInReverseOrder(this.root);
|
||||
|
||||
const tmp = this._maximizedNode.leaf;
|
||||
this._maximizedNode = undefined;
|
||||
this._onDidMaximizedNodeChange.fire();
|
||||
this._onDidMaximizedNodeChange.fire({
|
||||
view: tmp.view,
|
||||
isMaximized: false,
|
||||
});
|
||||
}
|
||||
|
||||
public serialize(): SerializedGridview<any> {
|
||||
const maximizedView = this.maximizedView();
|
||||
|
||||
let maxmizedViewLocation: number[] | undefined;
|
||||
|
||||
if (maximizedView) {
|
||||
/**
|
||||
* The minimum information we can get away with in order to serialize a maxmized view is it's location within the grid
|
||||
* which is represented as a branch of indices
|
||||
*/
|
||||
maxmizedViewLocation = getGridLocation(maximizedView.element);
|
||||
}
|
||||
|
||||
if (this.hasMaximizedView()) {
|
||||
/**
|
||||
* do not persist maximized view state
|
||||
* firstly exit any maximized views to ensure the correct dimensions are persisted
|
||||
* the saved layout cannot be in its maxmized state otherwise all of the underlying
|
||||
* view dimensions will be wrong
|
||||
*
|
||||
* To counteract this we temporaily remove the maximized view to compute the serialized output
|
||||
* of the grid before adding back the maxmized view as to not alter the layout from the users
|
||||
* perspective when `.toJSON()` is called
|
||||
*/
|
||||
this.exitMaximizedView();
|
||||
}
|
||||
|
||||
const root = serializeBranchNode(this.getView(), this.orientation);
|
||||
|
||||
return {
|
||||
const resullt: SerializedGridview<any> = {
|
||||
root,
|
||||
width: this.width,
|
||||
height: this.height,
|
||||
orientation: this.orientation,
|
||||
};
|
||||
|
||||
if (maxmizedViewLocation) {
|
||||
resullt.maximizedNode = {
|
||||
location: maxmizedViewLocation,
|
||||
};
|
||||
}
|
||||
|
||||
if (maximizedView) {
|
||||
// replace any maximzied view that was removed for serialization purposes
|
||||
this.maximizeView(maximizedView);
|
||||
}
|
||||
|
||||
return resullt;
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
@ -502,6 +551,24 @@ export class Gridview implements IDisposable {
|
||||
deserializer,
|
||||
height
|
||||
);
|
||||
|
||||
/**
|
||||
* The deserialied layout must be positioned through this.layout(...)
|
||||
* before any maximizedNode can be positioned
|
||||
*/
|
||||
this.layout(json.width, json.height);
|
||||
|
||||
if (json.maximizedNode) {
|
||||
const location = json.maximizedNode.location;
|
||||
|
||||
const [_, node] = this.getNode(location);
|
||||
|
||||
if (!(node instanceof LeafNode)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.maximizeView(node.view);
|
||||
}
|
||||
}
|
||||
|
||||
private _deserialize(
|
||||
|
@ -206,6 +206,12 @@ const DockviewDemo = (props: { theme?: string }) => {
|
||||
addLogLine(`Panel Moved ${event.panel.id}`);
|
||||
});
|
||||
|
||||
event.api.onDidMaximizedGroupChange((event) => {
|
||||
addLogLine(
|
||||
`Group Maximized Changed ${event.view.id} [${event.isMaximized}]`
|
||||
);
|
||||
});
|
||||
|
||||
event.api.onDidRemoveGroup((event) => {
|
||||
setGroups((_) => {
|
||||
const next = [..._];
|
||||
@ -318,6 +324,15 @@ const DockviewDemo = (props: { theme?: string }) => {
|
||||
engineering
|
||||
</span>
|
||||
</button>
|
||||
{showLogs && (
|
||||
<button
|
||||
onClick={() => {
|
||||
setLogLines([]);
|
||||
}}
|
||||
>
|
||||
<span className="material-symbols-outlined">undo</span>
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
onClick={() => {
|
||||
setShowLogs(!showLogs);
|
||||
|
@ -112,11 +112,10 @@ export const GridActions = (props: {
|
||||
|
||||
const onSave = () => {
|
||||
if (props.api) {
|
||||
console.log(props.api.toJSON());
|
||||
localStorage.setItem(
|
||||
'dv-demo-state',
|
||||
JSON.stringify(props.api.toJSON())
|
||||
);
|
||||
const state = props.api.toJSON();
|
||||
console.log(state);
|
||||
|
||||
localStorage.setItem('dv-demo-state', JSON.stringify(state));
|
||||
}
|
||||
};
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user