dockview
Version:
Zero dependency layout manager supporting tabs, grids and splitviews with ReactJS support
242 lines (241 loc) • 8.91 kB
JavaScript
import { CompositeDisposable, MutableDisposable, } from '../lifecycle';
import { Orientation, Sizing, Splitview, } from './core/splitview';
import { Emitter } from '../events';
import { SplitviewApi } from '../api/component.api';
import { createComponent } from '../panel/componentFactory';
/**
* A high-level implementation of splitview that works using 'panels'
*/
export class SplitviewComponent extends CompositeDisposable {
constructor(element, options) {
super();
this.element = element;
this._disposable = new MutableDisposable();
this.panels = new Map();
this._onDidLayoutChange = new Emitter();
this.onDidLayoutChange = this._onDidLayoutChange.event;
this._options = options;
if (!options.components) {
options.components = {};
}
if (!options.frameworkComponents) {
options.frameworkComponents = {};
}
this.splitview = new Splitview(this.element, options);
this.addDisposables(this._disposable);
}
get options() {
return this._options;
}
get orientation() {
return this.splitview.orientation;
}
get splitview() {
return this._splitview;
}
set splitview(value) {
this._splitview = value;
this._disposable.value = new CompositeDisposable(this._splitview.onDidSashEnd(() => {
this._onDidLayoutChange.fire(undefined);
}));
}
get minimumSize() {
return this.splitview.minimumSize;
}
get maximumSize() {
return this.splitview.maximumSize;
}
get height() {
return this.splitview.orientation === Orientation.HORIZONTAL
? this.splitview.orthogonalSize
: this.splitview.size;
}
get width() {
return this.splitview.orientation === Orientation.HORIZONTAL
? this.splitview.size
: this.splitview.orthogonalSize;
}
get length() {
return this.panels.size;
}
updateOptions(options) {
const hasOrientationChanged = typeof options.orientation === 'string' &&
this.options.orientation !== options.orientation;
this._options = Object.assign(Object.assign({}, this.options), options);
if (hasOrientationChanged) {
this.splitview.orientation = options.orientation;
}
this.splitview.layout(this.splitview.size, this.splitview.orthogonalSize);
}
focus() {
var _a;
(_a = this._activePanel) === null || _a === void 0 ? void 0 : _a.focus();
}
movePanel(from, to) {
this.splitview.moveView(from, to);
}
setVisible(panel, visible) {
const index = this.getPanels().indexOf(panel);
this.splitview.setViewVisible(index, visible);
}
setActive(view, skipFocus) {
this._activePanel = view;
this.getPanels()
.filter((v) => v !== view)
.forEach((v) => {
v.api._onDidActiveChange.fire({ isActive: false });
if (!skipFocus) {
v.focus();
}
});
view.api._onDidActiveChange.fire({ isActive: true });
if (!skipFocus) {
view.focus();
}
}
getPanels() {
return this.splitview.getViews();
}
removePanel(panel, sizing) {
const disposable = this.panels.get(panel.id);
disposable === null || disposable === void 0 ? void 0 : disposable.dispose();
this.panels.delete(panel.id);
const index = this.getPanels().findIndex((_) => _ === panel);
this.splitview.removeView(index, sizing);
const panels = this.getPanels();
if (panels.length > 0) {
this.setActive(panels[panels.length - 1]);
}
}
getPanel(id) {
return this.getPanels().find((view) => view.id === id);
}
addPanel(options) {
if (this.panels.has(options.id)) {
throw new Error(`panel ${options.id} already exists`);
}
const view = createComponent(options.id, options.component, this.options.components || {}, this.options.frameworkComponents || {}, this.options.frameworkWrapper
? {
createComponent: this.options.frameworkWrapper.createComponent,
}
: undefined);
view.orientation = this.splitview.orientation;
view.init({
params: options.params || {},
minimumSize: options.minimumSize,
maximumSize: options.maximumSize,
snap: options.snap,
priority: options.priority,
containerApi: new SplitviewApi(this),
});
const size = typeof options.size === 'number' ? options.size : Sizing.Distribute;
const index = typeof options.index === 'number' ? options.index : undefined;
this.splitview.addView(view, size, index);
this.doAddView(view);
this.setActive(view);
}
/**
* Resize the layout to fit the parent container
*/
resizeToFit() {
if (!this.element.parentElement) {
return;
}
const { width, height } = this.element.parentElement.getBoundingClientRect();
this.layout(width, height);
}
layout(width, height) {
const [size, orthogonalSize] = this.splitview.orientation === Orientation.HORIZONTAL
? [width, height]
: [height, width];
this.splitview.layout(size, orthogonalSize);
}
doAddView(view) {
const disposable = view.api.onDidFocusChange((event) => {
if (!event.isFocused) {
return;
}
this.setActive(view, true);
});
this.panels.set(view.id, disposable);
}
toJSON() {
var _a;
const views = this.splitview
.getViews()
.map((view, i) => {
const size = this.splitview.getViewSize(i);
return {
size,
data: view.toJSON(),
snap: !!view.snap,
priority: view.priority,
};
});
return {
views,
activeView: (_a = this._activePanel) === null || _a === void 0 ? void 0 : _a.id,
size: this.splitview.size,
orientation: this.splitview.orientation,
};
}
fromJSON(serializedSplitview, deferComponentLayout = false) {
const { views, orientation, size, activeView } = serializedSplitview;
this.splitview.dispose();
const queue = [];
this.splitview = new Splitview(this.element, {
orientation,
proportionalLayout: this.options.proportionalLayout,
descriptor: {
size,
views: views.map((view) => {
const data = view.data;
if (this.panels.has(data.id)) {
throw new Error(`panel ${data.id} already exists`);
}
const panel = createComponent(data.id, data.component, this.options.components || {}, this.options.frameworkComponents || {}, this.options.frameworkWrapper
? {
createComponent: this.options.frameworkWrapper
.createComponent,
}
: undefined);
queue.push(() => {
panel.init({
params: data.params || {},
minimumSize: data.minimumSize,
maximumSize: data.maximumSize,
snap: view.snap,
priority: view.priority,
containerApi: new SplitviewApi(this),
});
});
panel.orientation = orientation;
this.doAddView(panel);
return { size: view.size, view: panel };
}),
},
});
this.layout(this.width, this.height);
if (deferComponentLayout) {
setTimeout(() => {
queue.forEach((f) => f());
}, 0);
}
else {
queue.forEach((f) => f());
}
if (typeof activeView === 'string') {
const panel = this.getPanel(activeView);
if (panel) {
this.setActive(panel);
}
}
}
dispose() {
Array.from(this.panels.values()).forEach((value) => {
value.dispose();
});
this.panels.clear();
super.dispose();
}
}