dockview
Version:
Zero dependency layout manager supporting tabs, grids and splitviews with ReactJS support
449 lines (448 loc) • 16.7 kB
JavaScript
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
import { DockviewApi } from '../api/component.api';
import { getPanelData } from '../dnd/dataTransfer';
import { Droptarget, Position } from '../dnd/droptarget';
import { isAncestor, toggleClass } from '../dom';
import { addDisposableListener, Emitter } from '../events';
import { CompositeDisposable } from '../lifecycle';
import { ContentContainer } from './panel/content';
import { TabsContainer } from './titlebar/tabsContainer';
import { DockviewDropTargets } from './dnd';
export var GroupChangeKind2;
(function (GroupChangeKind2) {
GroupChangeKind2["ADD_PANEL"] = "ADD_PANEL";
GroupChangeKind2["REMOVE_PANEL"] = "REMOVE_PANEL";
GroupChangeKind2["PANEL_ACTIVE"] = "PANEL_ACTIVE";
GroupChangeKind2["GROUP_ACTIVE"] = "GROUP_ACTIVE";
})(GroupChangeKind2 || (GroupChangeKind2 = {}));
export class Groupview extends CompositeDisposable {
constructor(container, accessor, id, options, parent) {
super();
this.container = container;
this.accessor = accessor;
this.id = id;
this.options = options;
this.parent = parent;
this._isGroupActive = false;
this.mostRecentlyUsed = [];
this._onDidChange = new Emitter();
this.onDidChange = this._onDidChange.event;
this._width = 0;
this._height = 0;
this._panels = [];
this._onMove = new Emitter();
this.onMove = this._onMove.event;
this._onDidGroupChange = new Emitter();
this.onDidGroupChange = this._onDidGroupChange.event;
this.closePanel = (panel) => __awaiter(this, void 0, void 0, function* () {
if (panel.close && !(yield panel.close())) {
return false;
}
this.doClose(panel);
return true;
});
this.container.classList.add('groupview');
this.addDisposables(this._onMove, this._onDidGroupChange);
this.tabsContainer = new TabsContainer(this.accessor, this.parent, {
tabHeight: options.tabHeight,
});
this.contentContainer = new ContentContainer();
this.dropTarget = new Droptarget(this.contentContainer.element, {
validOverlays: 'all',
canDisplayOverlay: (event) => {
const data = getPanelData();
if (data) {
const groupHasOnePanelAndIsActiveDragElement = this._panels.length === 1 && data.groupId === this.id;
return !groupHasOnePanelAndIsActiveDragElement;
}
return this.canDisplayOverlay(event, DockviewDropTargets.Panel);
},
});
container.append(this.tabsContainer.element, this.contentContainer.element);
this.addDisposables(this._onMove, this._onDidGroupChange, this.tabsContainer.onDrop((event) => {
this.handleDropEvent(event.event, Position.Center, event.index);
}), this.contentContainer.onDidFocus(() => {
this.accessor.doSetGroupActive(this.parent, true);
}), this.contentContainer.onDidBlur(() => {
// this._activePanel?.api._ondid
}), this.dropTarget.onDrop((event) => {
this.handleDropEvent(event.event, event.position);
}));
}
get element() {
throw new Error('not supported');
}
get activePanel() {
return this._activePanel;
}
get tabHeight() {
return this.tabsContainer.height;
}
set tabHeight(height) {
this.tabsContainer.height = height;
this.layout(this._width, this._height);
}
get isActive() {
return this._isGroupActive;
}
get panels() {
return this._panels;
}
get size() {
return this._panels.length;
}
get isEmpty() {
return this._panels.length === 0;
}
get minimumHeight() {
return 100;
}
get maximumHeight() {
return Number.MAX_SAFE_INTEGER;
}
get minimumWidth() {
return 100;
}
get maximumWidth() {
return Number.MAX_SAFE_INTEGER;
}
initialize() {
var _a, _b;
if ((_a = this.options) === null || _a === void 0 ? void 0 : _a.panels) {
this.options.panels.forEach((panel) => {
this.doAddPanel(panel);
});
}
if ((_b = this.options) === null || _b === void 0 ? void 0 : _b.activePanel) {
this.openPanel(this.options.activePanel);
}
// must be run after the constructor otherwise this.parent may not be
// correctly initialized
this.setActive(this.isActive, true, true);
this.updateContainer();
}
isContentFocused() {
if (!document.activeElement) {
return false;
}
return isAncestor(document.activeElement, this.contentContainer.element);
}
indexOf(panel) {
return this.tabsContainer.indexOf(panel.id);
}
toJSON() {
var _a;
return {
views: this.tabsContainer.panels,
activeView: (_a = this._activePanel) === null || _a === void 0 ? void 0 : _a.id,
id: this.id,
};
}
moveToNext(options) {
if (!options) {
options = {};
}
if (!options.panel) {
options.panel = this.activePanel;
}
const index = options.panel ? this.panels.indexOf(options.panel) : -1;
let normalizedIndex;
if (index < this.panels.length - 1) {
normalizedIndex = index + 1;
}
else if (!options.suppressRoll) {
normalizedIndex = 0;
}
else {
return;
}
this.openPanel(this.panels[normalizedIndex]);
}
moveToPrevious(options) {
if (!options) {
options = {};
}
if (!options.panel) {
options.panel = this.activePanel;
}
if (!options.panel) {
return;
}
const index = this.panels.indexOf(options.panel);
let normalizedIndex;
if (index > 0) {
normalizedIndex = index - 1;
}
else if (!options.suppressRoll) {
normalizedIndex = this.panels.length - 1;
}
else {
return;
}
this.openPanel(this.panels[normalizedIndex]);
}
containsPanel(panel) {
return this.panels.includes(panel);
}
init(params) {
//noop
}
update(params) {
//noop
}
focus() {
var _a;
(_a = this._activePanel) === null || _a === void 0 ? void 0 : _a.focus();
}
openPanel(panel, options = {}) {
if (typeof options.index !== 'number' ||
options.index > this.panels.length) {
options.index = this.panels.length;
}
if (this._activePanel === panel) {
this.accessor.doSetGroupActive(this.parent);
return;
}
this.doAddPanel(panel, options.index);
this.doSetActivePanel(panel);
this.accessor.doSetGroupActive(this.parent, !!options.skipFocus);
this.updateContainer();
}
removePanel(groupItemOrId) {
const id = typeof groupItemOrId === 'string'
? groupItemOrId
: groupItemOrId.id;
const panelToRemove = this._panels.find((panel) => panel.id === id);
if (!panelToRemove) {
throw new Error('invalid operation');
}
return this._removePanel(panelToRemove);
}
closeAllPanels() {
var _a;
return __awaiter(this, void 0, void 0, function* () {
const index = this._activePanel
? this.panels.indexOf(this._activePanel)
: -1;
if (this._activePanel && index > -1) {
if (this.panels.indexOf(this._activePanel) < 0) {
console.warn('active panel not tracked');
}
const canClose = !((_a = this._activePanel) === null || _a === void 0 ? void 0 : _a.close) || (yield this._activePanel.close());
if (!canClose) {
return false;
}
}
for (let i = 0; i < this.panels.length; i++) {
if (i === index) {
continue;
}
const panel = this.panels[i];
this.openPanel(panel);
if (panel.close) {
const canClose = yield panel.close();
if (!canClose) {
return false;
}
}
}
if (this.panels.length > 0) {
// take a copy since we will be edting the array as we iterate through
const arrPanelCpy = [...this.panels];
yield Promise.all(arrPanelCpy.map((p) => this.doClose(p)));
}
else {
this.accessor.removeGroup(this.parent);
}
return true;
});
}
doClose(panel) {
this.accessor.removePanel(panel);
}
isPanelActive(panel) {
return this._activePanel === panel;
}
updateActions() {
var _a, _b;
if (this.isActive && ((_b = (_a = this._activePanel) === null || _a === void 0 ? void 0 : _a.view) === null || _b === void 0 ? void 0 : _b.actions)) {
this.tabsContainer.setActionElement(this._activePanel.view.actions.element);
}
else {
this.tabsContainer.setActionElement(undefined);
}
}
setActive(isGroupActive, skipFocus = false, force = false) {
var _a, _b;
if (!force && this.isActive === isGroupActive) {
if (!skipFocus) {
(_a = this._activePanel) === null || _a === void 0 ? void 0 : _a.focus();
}
return;
}
this._isGroupActive = isGroupActive;
toggleClass(this.container, 'active-group', isGroupActive);
toggleClass(this.container, 'inactive-group', !isGroupActive);
this.tabsContainer.setActive(this.isActive);
if (!this._activePanel && this.panels.length > 0) {
this.doSetActivePanel(this.panels[0]);
}
this.updateContainer();
if (isGroupActive) {
if (!skipFocus) {
(_b = this._activePanel) === null || _b === void 0 ? void 0 : _b.focus();
}
}
}
layout(width, height) {
var _a;
this._width = width;
this._height = height;
this.contentContainer.layout(this._width, this._height);
if ((_a = this._activePanel) === null || _a === void 0 ? void 0 : _a.layout) {
this._activePanel.layout(this._width, this._height);
}
}
_removePanel(panel) {
const isActivePanel = this._activePanel === panel;
this.doRemovePanel(panel);
if (isActivePanel && this.panels.length > 0) {
const nextPanel = this.mostRecentlyUsed[0];
this.openPanel(nextPanel);
}
if (this._activePanel && this.panels.length === 0) {
this.doSetActivePanel(undefined);
}
this.updateContainer();
return panel;
}
doRemovePanel(panel) {
const index = this.panels.indexOf(panel);
if (this._activePanel === panel) {
this.contentContainer.closePanel();
}
this.tabsContainer.delete(panel.id);
this._panels.splice(index, 1);
if (this.mostRecentlyUsed.includes(panel)) {
this.mostRecentlyUsed.splice(this.mostRecentlyUsed.indexOf(panel), 1);
}
this._onDidGroupChange.fire({
kind: GroupChangeKind2.REMOVE_PANEL,
panel,
});
}
doAddPanel(panel, index = this.panels.length) {
const existingPanel = this._panels.indexOf(panel);
const hasExistingPanel = existingPanel > -1;
this.tabsContainer.openPanel(panel, index);
this.contentContainer.openPanel(panel);
this.tabsContainer.show();
this.contentContainer.show();
if (hasExistingPanel) {
// TODO - need to ensure ordering hasn't changed and if it has need to re-order this.panels
return;
}
this.updateMru(panel);
this.panels.splice(index, 0, panel);
this._onDidGroupChange.fire({
kind: GroupChangeKind2.ADD_PANEL,
panel,
});
}
doSetActivePanel(panel) {
this._activePanel = panel;
if (panel) {
this.tabsContainer.setActivePanel(panel);
panel.layout(this._width, this._height);
this.updateMru(panel);
this._onDidGroupChange.fire({
kind: GroupChangeKind2.PANEL_ACTIVE,
panel,
});
}
}
updateMru(panel) {
if (this.mostRecentlyUsed.includes(panel)) {
this.mostRecentlyUsed.splice(this.mostRecentlyUsed.indexOf(panel), 1);
}
this.mostRecentlyUsed = [panel, ...this.mostRecentlyUsed];
}
updateContainer() {
this.updateActions();
toggleClass(this.container, 'empty', this.isEmpty);
this.panels.forEach((panel) => panel.updateParentGroup(this.parent, this.isActive));
if (this.isEmpty && !this.watermark) {
const watermark = this.accessor.createWatermarkComponent();
watermark.init({
containerApi: new DockviewApi(this.accessor),
params: {},
title: '',
api: null,
});
this.watermark = watermark;
addDisposableListener(this.watermark.element, 'click', () => {
if (!this.isActive) {
this.accessor.doSetGroupActive(this.parent);
}
});
this.contentContainer.hide();
this.tabsContainer.hide();
this.container.appendChild(this.watermark.element);
this.watermark.updateParentGroup(this.parent, true);
}
if (!this.isEmpty && this.watermark) {
this.watermark.element.remove();
this.watermark.dispose();
this.watermark = undefined;
this.contentContainer.show();
this.tabsContainer.show();
}
}
canDisplayOverlay(dragOverEvent, target) {
// custom overlay handler
return false;
}
handleDropEvent(event, position, index) {
const data = getPanelData();
if (data) {
const fromSameGroup = this.tabsContainer.indexOf(data.panelId) !== -1;
if (fromSameGroup && this.tabsContainer.size === 1) {
return;
}
const { groupId, panelId } = data;
const isSameGroup = this.id === groupId;
if (isSameGroup && !position) {
const oldIndex = this.tabsContainer.indexOf(panelId);
if (oldIndex === index) {
return;
}
}
this._onMove.fire({
target: position,
groupId: data.groupId,
itemId: data.panelId,
index,
});
}
else {
// custom drop handler
}
}
dispose() {
for (const panel of this.panels) {
panel.dispose();
}
super.dispose();
this.dropTarget.dispose();
this.tabsContainer.dispose();
this.contentContainer.dispose();
}
}