UNPKG

@deephaven/golden-layout

Version:

A multi-screen javascript Layout manager

486 lines (470 loc) • 24.7 kB
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; } function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; } function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; } function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; } function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } import $ from 'jquery'; import AbstractContentItem, { isComponent } from "./AbstractContentItem.js"; import { Header } from "../controls/index.js"; import RowOrColumn from "./RowOrColumn.js"; export default class Stack extends AbstractContentItem { constructor(layoutManager, config, parent) { var _cfg$settings, _cfg$settings2, _cfg$labels, _cfg$settings3, _cfg$labels2, _cfg$settings4, _cfg$labels3, _cfg$labels$minimise, _cfg$labels4; super(layoutManager, config, parent, $('<div class="lm_item lm_stack"></div>')); _defineProperty(this, "_activeContentItem", null); _defineProperty(this, "_header", void 0); _defineProperty(this, "childElementContainer", $('<div class="lm_items"></div>')); _defineProperty(this, "header", void 0); _defineProperty(this, "parent", void 0); _defineProperty(this, "isStack", true); _defineProperty(this, "_dropSegment", null); _defineProperty(this, "_contentAreaDimensions", null); _defineProperty(this, "_dropIndex", void 0); _defineProperty(this, "_side", void 0); _defineProperty(this, "_sided", false); _defineProperty(this, "config", void 0); this.parent = parent; this.config = config; var cfg = layoutManager.config; this._side = false; this._header = { // defaults' reconstruction from old configuration style show: ((_cfg$settings = cfg.settings) === null || _cfg$settings === void 0 ? void 0 : _cfg$settings.hasHeaders) && config.hasHeaders !== false, popout: (_cfg$settings2 = cfg.settings) !== null && _cfg$settings2 !== void 0 && _cfg$settings2.showPopoutIcon ? (_cfg$labels = cfg.labels) === null || _cfg$labels === void 0 ? void 0 : _cfg$labels.popout : undefined, maximise: (_cfg$settings3 = cfg.settings) !== null && _cfg$settings3 !== void 0 && _cfg$settings3.showMaximiseIcon ? (_cfg$labels2 = cfg.labels) === null || _cfg$labels2 === void 0 ? void 0 : _cfg$labels2.maximise : undefined, close: (_cfg$settings4 = cfg.settings) !== null && _cfg$settings4 !== void 0 && _cfg$settings4.showCloseIcon ? (_cfg$labels3 = cfg.labels) === null || _cfg$labels3 === void 0 ? void 0 : _cfg$labels3.close : undefined, minimise: (_cfg$labels$minimise = (_cfg$labels4 = cfg.labels) === null || _cfg$labels4 === void 0 ? void 0 : _cfg$labels4.minimise) !== null && _cfg$labels$minimise !== void 0 ? _cfg$labels$minimise : undefined }; // load simplified version of header configuration (https://github.com/deepstreamIO/golden-layout/pull/245) if (cfg.header) this._header = _objectSpread(_objectSpread({}, this._header), cfg.header); if (config.header) // load from stack this._header = _objectSpread(_objectSpread({}, this._header), config.header); if (config.content && config.content[0] && config.content[0].header) // load from component if stack omitted this._header = _objectSpread(_objectSpread({}, this._header), config.content[0].header); this.header = new Header(layoutManager, this); this.element.append(this.header.element); this.element.append(this.childElementContainer); this._setupHeaderPosition(); this._$validateClosability(); } setSize() { var _this$layoutManager$c, _this$layoutManager$c2, _this$element$width, _this$element$height; var i, headerSize = this._header.show ? (_this$layoutManager$c = (_this$layoutManager$c2 = this.layoutManager.config.dimensions) === null || _this$layoutManager$c2 === void 0 ? void 0 : _this$layoutManager$c2.headerHeight) !== null && _this$layoutManager$c !== void 0 ? _this$layoutManager$c : 0 : 0, contentWidth = ((_this$element$width = this.element.width()) !== null && _this$element$width !== void 0 ? _this$element$width : 0) - (this._sided ? headerSize : 0), contentHeight = ((_this$element$height = this.element.height()) !== null && _this$element$height !== void 0 ? _this$element$height : 0) - (!this._sided ? headerSize : 0); this.childElementContainer.width(contentWidth); this.childElementContainer.height(contentHeight); for (i = 0; i < this.contentItems.length; i++) { this.contentItems[i].element.width(contentWidth).height(contentHeight); } this.emit('resize'); this.emitBubblingEvent('stateChanged'); } _$init() { if (this.isInitialised === true) return; this.header._attachWheelListener(); super._$init(); for (var i = 0; i < this.contentItems.length; i++) { this.header.createTab(this.contentItems[i]); this.contentItems[i]._$hide(); } if (this.contentItems.length > 0) { var initialItem = this.contentItems[this.config.activeItemIndex || 0]; if (!initialItem) { throw new Error('Configured activeItemIndex out of bounds'); } this.setActiveContentItem(initialItem); } } setActiveContentItem(contentItem, forceFocus) { if (this.contentItems.indexOf(contentItem) === -1) { throw new Error('contentItem is not a child of this stack'); } if (this._activeContentItem !== null) { this._activeContentItem._$hide(); } this._activeContentItem = contentItem; this.header.setActiveContentItem(contentItem); if (isComponent(contentItem)) { contentItem._$show(forceFocus); } else { contentItem._$show(); } this.emit('activeContentItemChanged', contentItem); this.layoutManager.emit('activeContentItemChanged', contentItem); this.emitBubblingEvent('stateChanged'); } getActiveContentItem() { return this.header.activeContentItem; } addChild(contentItem, index) { contentItem = this.layoutManager._$normalizeContentItem(contentItem, this); super.addChild(contentItem, index); this.childElementContainer.append(contentItem.element); this.header.createTab(contentItem, index); this.setActiveContentItem(contentItem); this.callDownwards('setSize'); this._$validateClosability(); this.emitBubblingEvent('stateChanged'); } removeChild(contentItem) { var keepChild = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; var index = this.contentItems.indexOf(contentItem); super.removeChild(contentItem, keepChild); this.header.removeTab(contentItem); if (this.header.activeContentItem === contentItem) { if (this.contentItems.length > 0) { this.setActiveContentItem(this.contentItems[Math.max(index - 1, 0)]); } else { this._activeContentItem = null; } } this._$validateClosability(); this.emitBubblingEvent('stateChanged'); } /** * Validates that the stack is still closable or not. If a stack is able * to close, but has a non closable component added to it, the stack is no * longer closable until all components are closable. */ _$validateClosability() { var isClosable = this.header._isClosable(); for (var i = 0, len = this.contentItems.length; i < len; i++) { var _this$contentItems$i$; if (!isClosable) { break; } isClosable = (_this$contentItems$i$ = this.contentItems[i].config.isClosable) !== null && _this$contentItems$i$ !== void 0 ? _this$contentItems$i$ : false; } this.header._$setClosable(isClosable); } _$destroy() { AbstractContentItem.prototype._$destroy.call(this); this.header._$destroy(); } /** * Ok, this one is going to be the tricky one: The user has dropped {contentItem} onto this stack. * * It was dropped on either the stacks header or the top, right, bottom or left bit of the content area * (which one of those is stored in this._dropSegment). Now, if the user has dropped on the header the case * is relatively clear: We add the item to the existing stack... job done (might be good to have * tab reordering at some point, but lets not sweat it right now) * * If the item was dropped on the content part things are a bit more complicated. If it was dropped on either the * top or bottom region we need to create a new column and place the items accordingly. * Unless, of course if the stack is already within a column... in which case we want * to add the newly created item to the existing column... * either prepend or append it, depending on wether its top or bottom. * * Same thing for rows and left / right drop segments... so in total there are 9 things that can potentially happen * (left, top, right, bottom) * is child of the right parent (row, column) + header drop * * @param contentItem */ _$onDrop(contentItem) { /* * The item was dropped on the header area. Just add it as a child of this stack and * get the hell out of this logic */ if (this._dropSegment === 'header') { this._resetHeaderDropZone(); this.addChild(contentItem, this._dropIndex); return; } /* * The stack is empty. Let's just add the element. */ if (this._dropSegment === 'body') { this.addChild(contentItem); return; } /* * The item was dropped on the top-, left-, bottom- or right- part of the content. Let's * aggregate some conditions to make the if statements later on more readable */ var isVertical = this._dropSegment === 'top' || this._dropSegment === 'bottom'; var isHorizontal = this._dropSegment === 'left' || this._dropSegment === 'right'; var insertBefore = this._dropSegment === 'top' || this._dropSegment === 'left'; var hasCorrectParent = this.parent instanceof RowOrColumn && (isVertical && this.parent.isColumn || isHorizontal && this.parent.isRow); var type = isVertical ? 'column' : 'row'; var dimension = isVertical ? 'height' : 'width'; /* * The content item can be either a component or a stack. If it is a component, wrap it into a stack */ if (isComponent(contentItem)) { var stack = this.layoutManager.createContentItem({ type: 'stack', header: contentItem.config.header || {} }, this); stack._$init(); stack.addChild(contentItem); contentItem = stack; } /* * If the item is dropped on top or bottom of a column or left and right of a row, it's already * layd out in the correct way. Just add it as a child */ if (hasCorrectParent) { var _this$config$dimensio; var index = this.parent.contentItems.indexOf(this); // Should be a `RowOrColumn` if `hasCorrectParent` is true this.parent.addChild(contentItem, insertBefore ? index : index + 1, true); this.config[dimension] = ((_this$config$dimensio = this.config[dimension]) !== null && _this$config$dimensio !== void 0 ? _this$config$dimensio : 0) * 0.5; contentItem.config[dimension] = this.config[dimension]; this.parent.callDownwards('setSize'); /* * This handles items that are dropped on top or bottom of a row or left / right of a column. We need * to create the appropriate contentItem for them to live in */ } else { var _this$parent; var rowOrColumn = this.layoutManager.createContentItem({ type }, this); (_this$parent = this.parent) === null || _this$parent === void 0 ? void 0 : _this$parent.replaceChild(this, rowOrColumn); rowOrColumn.addChild(contentItem, insertBefore ? 0 : undefined, true); rowOrColumn.addChild(this, insertBefore ? undefined : 0, true); this.config[dimension] = 50; contentItem.config[dimension] = 50; rowOrColumn.callDownwards('setSize'); } } /** * If the user hovers above the header part of the stack, indicate drop positions for tabs. * otherwise indicate which segment of the body the dragged item would be dropped on * * @param x Absolute Screen X * @param y Absolute Screen Y */ _$highlightDropZone(x, y) { if (!this._contentAreaDimensions) { return; } for (var [segment, dimensions] of Object.entries(this._contentAreaDimensions)) { var area = dimensions.hoverArea; if (area.x1 < x && area.x2 > x && area.y1 < y && area.y2 > y) { if (segment === 'header') { this._dropSegment = 'header'; this._highlightHeaderDropZone(x); } else { this._resetHeaderDropZone(); this._highlightBodyDropZone(segment); } return; } } } _$getArea() { if (this.element.is(':visible') === false) { return null; } var headerArea = super._$getArea(this.header.element); var contentArea = super._$getArea(this.childElementContainer); if (headerArea == null || contentArea == null) { return null; } var contentWidth = contentArea.x2 - contentArea.x1; var contentHeight = contentArea.y2 - contentArea.y1; this._contentAreaDimensions = { header: { hoverArea: { x1: headerArea.x1, y1: headerArea.y1, x2: headerArea.x2, y2: headerArea.y2 }, highlightArea: { x1: headerArea.x1, y1: headerArea.y1, x2: headerArea.x2, y2: headerArea.y2 } } }; /** * If this Stack is a parent to rows, columns or other stacks only its * header is a valid dropzone. */ if (this._activeContentItem && this._activeContentItem.isComponent === false) { return headerArea; } /** * Highlight the entire body if the stack is empty */ if (this.contentItems.length === 0) { this._contentAreaDimensions.body = { hoverArea: { x1: contentArea.x1, y1: contentArea.y1, x2: contentArea.x2, y2: contentArea.y2 }, highlightArea: { x1: contentArea.x1, y1: contentArea.y1, x2: contentArea.x2, y2: contentArea.y2 } }; return super._$getArea(this.element); } this._contentAreaDimensions.left = { hoverArea: { x1: contentArea.x1, y1: contentArea.y1, x2: contentArea.x1 + contentWidth * 0.25, y2: contentArea.y2 }, highlightArea: { x1: contentArea.x1, y1: contentArea.y1, x2: contentArea.x1 + contentWidth * 0.5, y2: contentArea.y2 } }; this._contentAreaDimensions.top = { hoverArea: { x1: contentArea.x1 + contentWidth * 0.25, y1: contentArea.y1, x2: contentArea.x1 + contentWidth * 0.75, y2: contentArea.y1 + contentHeight * 0.5 }, highlightArea: { x1: contentArea.x1, y1: contentArea.y1, x2: contentArea.x2, y2: contentArea.y1 + contentHeight * 0.5 } }; this._contentAreaDimensions.right = { hoverArea: { x1: contentArea.x1 + contentWidth * 0.75, y1: contentArea.y1, x2: contentArea.x2, y2: contentArea.y2 }, highlightArea: { x1: contentArea.x1 + contentWidth * 0.5, y1: contentArea.y1, x2: contentArea.x2, y2: contentArea.y2 } }; this._contentAreaDimensions.bottom = { hoverArea: { x1: contentArea.x1 + contentWidth * 0.25, y1: contentArea.y1 + contentHeight * 0.5, x2: contentArea.x1 + contentWidth * 0.75, y2: contentArea.y2 }, highlightArea: { x1: contentArea.x1, y1: contentArea.y1 + contentHeight * 0.5, x2: contentArea.x2, y2: contentArea.y2 } }; return super._$getArea(this.element); } _highlightHeaderDropZone(x) { var _tabsContainer$get, _this$layoutManager$t, _this$layoutManager$t2, _this$layoutManager$t3, _this$header$tabsCont, _this$header$tabsCont2, _this$layoutManager$t4, _this$header$tabsCont3, _this$header$tabsCont4, _this$header$tabsCont5, _this$layoutManager$d2, _this$header$element$6, _this$header$element$7, _this$header$element$8, _this$header$element$9, _this$header$element$10; var tabsLength = this.header.tabs.length; // I've omitted code for side edge tabs here // illumon doesn't need it, will slowly pull that code out elsewhere too // Empty stack if (tabsLength === 0) { var _this$layoutManager$d, _headerOffset$left, _headerOffset$left2, _this$header$element$, _this$header$element$2, _this$header$element$3, _this$header$element$4, _this$header$element$5; var headerOffset = this.header.element.offset(); // we don't have a placeholder to measure in the dom, lets just cheat and make it 100px. (_this$layoutManager$d = this.layoutManager.dropTargetIndicator) === null || _this$layoutManager$d === void 0 ? void 0 : _this$layoutManager$d.highlightArea({ x1: (_headerOffset$left = headerOffset === null || headerOffset === void 0 ? void 0 : headerOffset.left) !== null && _headerOffset$left !== void 0 ? _headerOffset$left : 0, x2: ((_headerOffset$left2 = headerOffset === null || headerOffset === void 0 ? void 0 : headerOffset.left) !== null && _headerOffset$left2 !== void 0 ? _headerOffset$left2 : 0) + 100, y1: (_this$header$element$ = (_this$header$element$2 = this.header.element.offset()) === null || _this$header$element$2 === void 0 ? void 0 : _this$header$element$2.top) !== null && _this$header$element$ !== void 0 ? _this$header$element$ : 0, y2: ((_this$header$element$3 = (_this$header$element$4 = this.header.element.offset()) === null || _this$header$element$4 === void 0 ? void 0 : _this$header$element$4.top) !== null && _this$header$element$3 !== void 0 ? _this$header$element$3 : 0) + ((_this$header$element$5 = this.header.element.innerHeight()) !== null && _this$header$element$5 !== void 0 ? _this$header$element$5 : 0) }); return; } var tabsContainer = this.header.tabsContainer; var tabsContainerRect = (_tabsContainer$get = tabsContainer.get(0)) === null || _tabsContainer$get === void 0 ? void 0 : _tabsContainer$get.getBoundingClientRect(); var placeholderRect = (_this$layoutManager$t = this.layoutManager.tabDropPlaceholder.get(0)) === null || _this$layoutManager$t === void 0 ? void 0 : _this$layoutManager$t.getBoundingClientRect(); if (!tabsContainerRect || !placeholderRect) { return; } if (x < tabsContainerRect.left) { // is over left tab controls button // move x to a new point to inside left edge of container x = tabsContainerRect.left + 1; } else if (x > tabsContainerRect.right) { // is over right tab controls button // move x to a new point to inside right edge of container x = tabsContainerRect.right - 1; } var tabElement; var tabRect; // if its not inide a placeholder, if (!(placeholderRect.left < x && x < placeholderRect.right)) { // which tab is it over ... for (var i = 0; i < tabsLength; i++) { var _tabElement$get; tabElement = this.header.tabs[i].element; tabRect = (_tabElement$get = tabElement.get(0)) === null || _tabElement$get === void 0 ? void 0 : _tabElement$get.getBoundingClientRect(); if (tabRect && tabRect.left < x && x < tabRect.right) { this._dropIndex = i; break; } } // we have tabRect at this x,y from the loop above if (tabElement && tabRect && x < tabRect.left + tabRect.width * 0.5) { // mostly before an element, insert placeholder before tabElement.before(this.layoutManager.tabDropPlaceholder); } else if (tabElement) { var _this$_dropIndex; // x is likely after the lhe last item, position after and increase drop index this._dropIndex = Math.min(((_this$_dropIndex = this._dropIndex) !== null && _this$_dropIndex !== void 0 ? _this$_dropIndex : 0) + 1, tabsLength); tabElement.after(this.layoutManager.tabDropPlaceholder); } } var placeHolderLeft = (_this$layoutManager$t2 = (_this$layoutManager$t3 = this.layoutManager.tabDropPlaceholder.offset()) === null || _this$layoutManager$t3 === void 0 ? void 0 : _this$layoutManager$t3.left) !== null && _this$layoutManager$t2 !== void 0 ? _this$layoutManager$t2 : 0; placeHolderLeft = Math.max(placeHolderLeft, (_this$header$tabsCont = (_this$header$tabsCont2 = this.header.tabsContainer.offset()) === null || _this$header$tabsCont2 === void 0 ? void 0 : _this$header$tabsCont2.left) !== null && _this$header$tabsCont !== void 0 ? _this$header$tabsCont : 0); var placeHolderRight = placeHolderLeft + ((_this$layoutManager$t4 = this.layoutManager.tabDropPlaceholder.width()) !== null && _this$layoutManager$t4 !== void 0 ? _this$layoutManager$t4 : 0); placeHolderRight = Math.min(placeHolderRight, ((_this$header$tabsCont3 = (_this$header$tabsCont4 = this.header.tabsContainer.offset()) === null || _this$header$tabsCont4 === void 0 ? void 0 : _this$header$tabsCont4.left) !== null && _this$header$tabsCont3 !== void 0 ? _this$header$tabsCont3 : 0) + ((_this$header$tabsCont5 = this.header.tabsContainer.innerWidth()) !== null && _this$header$tabsCont5 !== void 0 ? _this$header$tabsCont5 : 0)); (_this$layoutManager$d2 = this.layoutManager.dropTargetIndicator) === null || _this$layoutManager$d2 === void 0 ? void 0 : _this$layoutManager$d2.highlightArea({ x1: placeHolderLeft, x2: placeHolderRight, y1: (_this$header$element$6 = (_this$header$element$7 = this.header.element.offset()) === null || _this$header$element$7 === void 0 ? void 0 : _this$header$element$7.top) !== null && _this$header$element$6 !== void 0 ? _this$header$element$6 : 0, y2: ((_this$header$element$8 = (_this$header$element$9 = this.header.element.offset()) === null || _this$header$element$9 === void 0 ? void 0 : _this$header$element$9.top) !== null && _this$header$element$8 !== void 0 ? _this$header$element$8 : 0) + ((_this$header$element$10 = this.header.element.innerHeight()) !== null && _this$header$element$10 !== void 0 ? _this$header$element$10 : 0) }); } _resetHeaderDropZone() { this.layoutManager.tabDropPlaceholder.remove(); } _setupHeaderPosition() { var side = ['right', 'left', 'bottom'].some(elem => elem === this._header.show) ? this._header.show : undefined; this.header.element.toggle(!!this._header.show); if (!side) { return; } this._side = side; this._sided = ['right', 'left'].indexOf(this._side.toString()) >= 0; this.element.removeClass('lm_left lm_right lm_bottom'); if (this._side) this.element.addClass('lm_' + this._side); if (this.element.find('.lm_header').length && this.childElementContainer) { var headerPosition = ['right', 'bottom'].indexOf(this._side.toString()) >= 0 ? 'before' : 'after'; this.header.element[headerPosition](this.childElementContainer); this.callDownwards('setSize'); } } _highlightBodyDropZone(segment) { var _this$_contentAreaDim, _this$_contentAreaDim2; var highlightArea = (_this$_contentAreaDim = this._contentAreaDimensions) === null || _this$_contentAreaDim === void 0 ? void 0 : (_this$_contentAreaDim2 = _this$_contentAreaDim[segment]) === null || _this$_contentAreaDim2 === void 0 ? void 0 : _this$_contentAreaDim2.highlightArea; if (highlightArea) { var _this$layoutManager$d3; (_this$layoutManager$d3 = this.layoutManager.dropTargetIndicator) === null || _this$layoutManager$d3 === void 0 ? void 0 : _this$layoutManager$d3.highlightArea(highlightArea); } this._dropSegment = segment; } } //# sourceMappingURL=Stack.js.map