@deephaven/golden-layout
Version:
A multi-screen javascript Layout manager
486 lines (470 loc) • 24.6 kB
JavaScript
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 || _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$0;
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 || _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 || _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$0 = this.header.element.innerHeight()) !== null && _this$header$element$0 !== void 0 ? _this$header$element$0 : 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;
var highlightArea = (_this$_contentAreaDim = this._contentAreaDimensions) === null || _this$_contentAreaDim === void 0 || (_this$_contentAreaDim = _this$_contentAreaDim[segment]) === null || _this$_contentAreaDim === void 0 ? void 0 : _this$_contentAreaDim.highlightArea;
if (highlightArea) {
var _this$layoutManager$d3;
(_this$layoutManager$d3 = this.layoutManager.dropTargetIndicator) === null || _this$layoutManager$d3 === void 0 || _this$layoutManager$d3.highlightArea(highlightArea);
}
this._dropSegment = segment;
}
}
//# sourceMappingURL=Stack.js.map