UNPKG

dockview

Version:

Zero dependency layout manager supporting tabs, grids and splitviews with ReactJS support

449 lines (448 loc) 16.7 kB
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(); } }