UNPKG

@deephaven/golden-layout

Version:

A multi-screen javascript Layout manager

1,078 lines (1,019 loc) • 38.8 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 lm from "./base.js"; import { defaultConfig } from "./config/index.js"; import { BrowserPopout, DragSource, DragSourceFromEvent, DropTargetIndicator } from "./controls/index.js"; import { ConfigurationError } from "./errors/index.js"; import { AbstractContentItem, isStack, Component, Root, RowOrColumn, Stack } from "./items/index.js"; import { minifyConfig, unminifyConfig, EventEmitter, EventHub, ReactComponentHandler, getQueryStringParam, getUniqueId, stripTags } from "./utils/index.js"; /** * A simpler function type for components that don't use class constructors. * This is commonly used in tests and legacy code. */ /** * Item configuration types that are supported inside of `createContentItem` to * create content items. Note that `ReactComponentConfig` is a valid input type, * but it gets converted to `ComponentConfig` inside the method before this * constraint comes into play. */ /** * Item configuration `type` values that are supported inside of * `createContentItem` to create content items. Note that `react-component` * is a valid input value, but it gets converted to `component` inside the * method before this constraint comes into play. */ var LAYOUT_ITEM_CONFIG_TYPES = ['column', 'row', 'stack', 'component']; /** * The main class that will be exposed as GoldenLayout. * * @param config * @param container Can be a jQuery selector string or a Dom element. Defaults to body */ export class LayoutManager extends EventEmitter { /** * Returns true if the given item config can be used to create a layout item. * (Used internally by `createContentItem`). */ static isLayoutItemConfig(config) { return LAYOUT_ITEM_CONFIG_TYPES.includes(config.type); } /** * Takes a GoldenLayout configuration object and * replaces its keys and values recursively with * one letter codes * * @param config A GoldenLayout config object * @returns minified config */ static minifyConfig(config) { return minifyConfig(config); } /** * Takes a configuration Object that was previously minified * using minifyConfig and returns its original version * * @param minifiedConfig * @returns the original configuration */ static unminifyConfig(config) { return unminifyConfig(config); } constructor(config, container) { super(); _defineProperty(this, "isInitialised", false); _defineProperty(this, "_isFullPage", false); _defineProperty(this, "_resizeTimeoutId", void 0); _defineProperty(this, "_components", { 'lm-react-component': ReactComponentHandler }); _defineProperty(this, "_fallbackComponent", void 0); _defineProperty(this, "_itemAreas", []); _defineProperty(this, "_maximisedItem", null); _defineProperty(this, "_maximisePlaceholder", $('<div class="lm_maximise_place"></div>')); _defineProperty(this, "_creationTimeoutPassed", false); _defineProperty(this, "_subWindowsCreated", false); _defineProperty(this, "_dragSources", []); _defineProperty(this, "_updatingColumnsResponsive", false); _defineProperty(this, "_firstLoad", true); _defineProperty(this, "_reactChildMap", new Map()); _defineProperty(this, "_reactChildren", null); _defineProperty(this, "_initialHeaderHeight", void 0); _defineProperty(this, "width", null); _defineProperty(this, "height", null); _defineProperty(this, "root", void 0); // This will be created after init is called. _defineProperty(this, "openPopouts", []); _defineProperty(this, "selectedItem", null); _defineProperty(this, "isSubWindow", false); _defineProperty(this, "eventHub", new EventHub(this)); _defineProperty(this, "config", void 0); _defineProperty(this, "container", void 0); _defineProperty(this, "_originalContainer", void 0); _defineProperty(this, "dropTargetIndicator", null); _defineProperty(this, "tabDropPlaceholder", $('<div class="lm_drop_tab_placeholder"></div>')); this._onResize = this._onResize.bind(this); this._onUnload = this._onUnload.bind(this); this._windowBlur = this._windowBlur.bind(this); this._windowFocus = this._windowFocus.bind(this); this._windowKeydown = this._windowKeydown.bind(this); this.config = this._createConfig(config); this._originalContainer = container; this.container = this._getContainer(); this._initialHeaderHeight = this.config.dimensions.headerHeight; if (this.isSubWindow) { $('body').css('visibility', 'hidden'); } } /** * Register a component with the layout manager. If a configuration node * of type component is reached it will look up componentName and create the * associated component * * { * type: "component", * componentName: "EquityNewsFeed", * componentState: { "feedTopic": "us-bluechips" } * } * * @param name * @param constructor * @returns cleanup function to deregister component */ registerComponent(name, constructor) { if (typeof constructor !== 'function' && (constructor == null || constructor.render == null || typeof constructor.render !== 'function')) { throw new Error('Please register a constructor function'); } if (this._components[name] !== undefined) { throw new Error('Component ' + name + ' is already registered'); } this._components[name] = constructor; var cleanup = () => { if (this._components[name] === undefined) { throw new Error('Component ' + name + ' is not registered'); } delete this._components[name]; }; return cleanup; } /** * Set a fallback component to be rendered in place of unregistered components * @param constructor */ setFallbackComponent(constructor) { this._fallbackComponent = constructor; } /** * Creates a layout configuration object based on the the current state * @param root * @returns GoldenLayout configuration */ toConfig(root) { if (this.isInitialised === false) { throw new Error("Can't create config, layout not yet initialised"); } if (root && !(root instanceof AbstractContentItem)) { throw new Error('Root must be a ContentItem'); } /* * settings & labels */ var config = { settings: _objectSpread({}, this.config.settings), dimensions: _objectSpread({}, this.config.dimensions), labels: _objectSpread({}, this.config.labels), content: [] }; /* * Content */ var _next = function next(configNode, item) { for (var key in item.config) { if (key !== 'content') { configNode[key] = item.config[key]; } } if (configNode.componentName === 'lm-react-component') { // We change the type in `createContentItem`, so change it back here configNode.type = 'react-component'; } if (item.contentItems.length) { configNode.content = []; for (var i = 0; i < item.contentItems.length; i++) { configNode.content[i] = {}; _next(configNode.content[i], item.contentItems[i]); } } }; if (root) { _next(config, { contentItems: [root] }); } else { _next(config, this.root); } /* * Retrieve config for subwindows */ this._$reconcilePopoutWindows(); config.openPopouts = []; for (var i = 0; i < this.openPopouts.length; i++) { config.openPopouts.push(this.openPopouts[i].toConfig()); } /* * Add maximised item */ config.maximisedItemId = this._maximisedItem ? '__glMaximised' : undefined; return config; } /** * Returns a previously registered component * @param name The name used */ getComponent(name) { return this._components[name]; } /** * Returns a fallback component to render in place of unregistered components * * @public * * @returns {Function} */ getFallbackComponent() { return this._fallbackComponent; } /** * Creates the actual layout. Must be called after all initial components * are registered. Recurses through the configuration and sets up * the item tree. * * If called before the document is ready it adds itself as a listener * to the document.ready event */ init() { /** * Create the popout windows straight away. If popouts are blocked * an error is thrown on the same 'thread' rather than a timeout and can * be caught. This also prevents any further initilisation from taking place. */ if (this._subWindowsCreated === false) { this._createSubWindows(); this._subWindowsCreated = true; } /** * If the document isn't ready yet, wait for it. */ if (document.readyState === 'loading' || document.body === null) { $(document).ready(this.init.bind(this)); return; } /** * If this is a subwindow, wait a few milliseconds for the original * page's js calls to be executed, then replace the bodies content * with GoldenLayout */ if (this.isSubWindow === true && this._creationTimeoutPassed === false) { setTimeout(this.init.bind(this), 7); this._creationTimeoutPassed = true; return; } if (this.isSubWindow === true) { this._adjustToWindowMode(); } this._setContainer(); this.dropTargetIndicator = new DropTargetIndicator(); this.updateSize(); this._create(this.config); this._bindEvents(); this.isInitialised = true; this._adjustColumnsResponsive(); this.emit('initialised'); } /** * Adds a react child to the layout manager * @param id Unique panel id * @param element The React element */ addReactChild(id, element) { this._reactChildMap.set(id, element); this._reactChildren = [...this._reactChildMap.values()]; this.emit('reactChildrenChanged'); } /** * Removes a react child from the layout manager * Only removes if the elements for the panelId has not been replaced by a different element * @param id Unique panel id * @param element The React element */ removeReactChild(id, element) { var mapElem = this._reactChildMap.get(id); if (mapElem === element) { // If an element was replaced it may be destroyed after the other is created // In that case, the new element would be removed // Make sure the element being removed is the current element associated with its id this._reactChildMap.delete(id); this._reactChildren = [...this._reactChildMap.values()]; this.emit('reactChildrenChanged'); } } /** * Gets the react children in the layout * * Used in @deephaven/dashboard to mount the react elements * inside the app's React tree * * @returns The react children to mount for this layout manager */ getReactChildren() { return this._reactChildren; } enableHeaders() { this.config.settings.hasHeaders = true; this.config.dimensions.headerHeight = this._initialHeaderHeight; this._findAllStackContainers().forEach(stack => { stack._header.show = true; stack.header.element.toggle(true); }); this.updateSize(); } disableHeaders() { this.config.settings.hasHeaders = false; this.config.dimensions.headerHeight = 0; this._findAllStackContainers().forEach(stack => { stack._header.show = false; stack.header.element.toggle(false); }); this.updateSize(); } /** * Updates the layout managers size * @param width width in pixels * @param height height in pixels */ updateSize(width, height) { var _ref, _ref2; this.width = (_ref = width !== null && width !== void 0 ? width : this.container.width()) !== null && _ref !== void 0 ? _ref : 0; this.height = (_ref2 = height !== null && height !== void 0 ? height : this.container.height()) !== null && _ref2 !== void 0 ? _ref2 : 0; if (this.isInitialised === true) { this.root.callDownwards('setSize', [this.width, this.height]); if (this._maximisedItem) { var _this$container$width, _this$container$heigh; this._maximisedItem.element.width((_this$container$width = this.container.width()) !== null && _this$container$width !== void 0 ? _this$container$width : 0); this._maximisedItem.element.height((_this$container$heigh = this.container.height()) !== null && _this$container$heigh !== void 0 ? _this$container$heigh : 0); this._maximisedItem.callDownwards('setSize'); } this._adjustColumnsResponsive(); } } /** * Destroys the LayoutManager instance itself as well as every ContentItem * within it. After this is called nothing should be left of the LayoutManager. */ destroy() { var _this$dropTargetIndic; if (this.isInitialised === false || !this.root) { return; } this._onUnload(); $(window).off('resize', this._onResize); $(window).off('unload beforeunload', this._onUnload); $(window).off('blur.lm').off('focus.lm'); $(window).off('keydown', this._windowKeydown); this.root.callDownwards('_$destroy', [], true); this.root.contentItems = []; this.tabDropPlaceholder.remove(); (_this$dropTargetIndic = this.dropTargetIndicator) === null || _this$dropTargetIndic === void 0 || _this$dropTargetIndic.destroy(); this.eventHub.destroy(); this._dragSources.forEach(function (dragSource) { dragSource._dragListener.destroy(); }); this._dragSources = []; } /** * Recursively creates new item tree structures based on a provided * ItemConfiguration object * * @public * @param config ItemConfig * @param parent The item the newly created item should be a child of * * @returns Created item */ // Default and Root configs will throw an error hence the `never` return type // This signature is necessary for this function to handle the broader // `ItemConfig` type since it won't be able to narrow the result in such cases. createContentItem(config, parent) { var _config$id; var typeErrorMsg, contentItem; if (typeof config.type !== 'string') { throw new ConfigurationError("Missing parameter 'type'", config); } if (config.type === 'react-component') { config.type = 'component'; config.componentName = 'lm-react-component'; } if (!LayoutManager.isLayoutItemConfig(config)) { typeErrorMsg = "Unknown type '" + config.type + "'. " + 'Valid types are ' + LAYOUT_ITEM_CONFIG_TYPES.join(','); throw new ConfigurationError(typeErrorMsg); } /** * We add an additional stack around every component that's not within a stack anyways. */ if ( // If this is a component config.type === 'component' && // and it's not already within a stack !(parent instanceof Stack) && // and we have a parent !!parent && // and it's not the topmost item in a new window !(this.isSubWindow === true && parent instanceof Root)) { config = { type: 'stack', width: config.width, height: config.height, content: [config] }; } config.id = (_config$id = config.id) !== null && _config$id !== void 0 ? _config$id : getUniqueId(); if (config.type === 'stack') { return new Stack(this, config, parent); } if (config.type === 'row') { return new RowOrColumn(false, this, config, parent); } if (config.type === 'column') { return new RowOrColumn(true, this, config, parent); } return new Component(this, config, parent); } /** * Creates a popout window with the specified content and dimensions * * @param configOrContentItem * @param dimensions A map with width, height, left and top * @param parentId the id of the element this item will be appended to * when popIn is called * @param indexInParent The position of this item within its parent element * @returns Created popout */ createPopout(configOrContentItem, dimensions, parentId, indexInParent) { var config = configOrContentItem; var configArray = []; var isItem = configOrContentItem instanceof AbstractContentItem; var self = this; if (isItem) { var _parent3; configArray = this.toConfig(configOrContentItem).content; parentId = getUniqueId(); /** * If the item is the only component within a stack or for some * other reason the only child of its parent the parent will be destroyed * when the child is removed. * * In order to support this we move up the tree until we find something * that will remain after the item is being popped out */ var _parent = configOrContentItem.parent; var child = configOrContentItem; while (((_parent2 = _parent) === null || _parent2 === void 0 ? void 0 : _parent2.contentItems.length) === 1 && !_parent.isRoot) { var _parent2; child = _parent; _parent = _parent.parent; } (_parent3 = _parent) === null || _parent3 === void 0 || _parent3.addId(parentId); if (indexInParent == undefined || Number.isNaN(indexInParent)) { var _parent4; indexInParent = (_parent4 = _parent) === null || _parent4 === void 0 ? void 0 : _parent4.contentItems.indexOf(child); } } else { if (!(configOrContentItem instanceof Array)) { configArray = [configOrContentItem]; } else { configArray = configOrContentItem; } } if (!dimensions && isItem) { var _configOrContentItem$, _configOrContentItem$2, _configOrContentItem$3; var windowLeft = window.screenX || window.screenLeft; var windowTop = window.screenY || window.screenTop; var offset = (_configOrContentItem$ = configOrContentItem.element.offset()) !== null && _configOrContentItem$ !== void 0 ? _configOrContentItem$ : { left: 0, top: 0 }; dimensions = { left: windowLeft + offset.left, top: windowTop + offset.top, width: (_configOrContentItem$2 = configOrContentItem.element.width()) !== null && _configOrContentItem$2 !== void 0 ? _configOrContentItem$2 : 0, height: (_configOrContentItem$3 = configOrContentItem.element.height()) !== null && _configOrContentItem$3 !== void 0 ? _configOrContentItem$3 : 0 }; } if (!dimensions && !isItem) { dimensions = { left: window.screenX || window.screenLeft + 20, top: window.screenY || window.screenTop + 20, width: 500, height: 309 }; } if (isItem) { configOrContentItem.remove(); } if (!dimensions || !parentId || indexInParent === undefined) { return; } var browserPopout = new BrowserPopout(configArray, dimensions, parentId, indexInParent, this); browserPopout.on('initialised', function () { self.emit('windowOpened', browserPopout); }); browserPopout.on('closed', function () { self._$reconcilePopoutWindows(); }); this.openPopouts.push(browserPopout); return browserPopout; } /** * Attaches DragListener to any given DOM element * and turns it into a way of creating new ContentItems * by 'dragging' the DOM element into the layout * * @param element * @param itemConfig for the new item to be created, or a function which will provide it */ createDragSource(element, itemConfig) { this.config.settings.constrainDragToContainer = false; var dragSource = new DragSource(element, itemConfig, this); this._dragSources.push(dragSource); return dragSource; } /** * Create a new item in a dragging state, given a starting mouse event to act as the initial position * * @param itemConfig for the new item to be created, or a function which will provide it * @param event used as the starting position for the dragProxy */ createDragSourceFromEvent(itemConfig, event) { this.config.settings.constrainDragToContainer = false; return new DragSourceFromEvent(itemConfig, this, event); } /** * Programmatically selects an item. This deselects * the currently selected item, selects the specified item * and emits a selectionChanged event * * @param item * @param _$silent Wheather to notify the item of its selection */ selectItem(item, _$silent) { if (this.config.settings.selectionEnabled !== true) { throw new Error('Please set selectionEnabled to true to use this feature'); } if (item === this.selectedItem) { return; } if (this.selectedItem !== null) { this.selectedItem.deselect(); } if (item && _$silent !== true) { item.select(); } this.selectedItem = item; this.emit('selectionChanged', item); } /************************* * PACKAGE PRIVATE *************************/ _$maximiseItem(contentItem) { var _this$container$width2, _this$container$heigh2; if (this._maximisedItem !== null) { this._$minimiseItem(this._maximisedItem); } this._maximisedItem = contentItem; this._maximisedItem.addId('__glMaximised'); contentItem.element.addClass('lm_maximised'); contentItem.element.after(this._maximisePlaceholder); this.root.element.prepend(contentItem.element); contentItem.element.width((_this$container$width2 = this.container.width()) !== null && _this$container$width2 !== void 0 ? _this$container$width2 : 0); contentItem.element.height((_this$container$heigh2 = this.container.height()) !== null && _this$container$heigh2 !== void 0 ? _this$container$heigh2 : 0); contentItem.callDownwards('setSize'); this._maximisedItem.emit('maximised'); this.emit('stateChanged'); } _$minimiseItem(contentItem) { var _contentItem$parent; contentItem.element.removeClass('lm_maximised'); contentItem.removeId('__glMaximised'); this._maximisePlaceholder.after(contentItem.element); this._maximisePlaceholder.remove(); (_contentItem$parent = contentItem.parent) === null || _contentItem$parent === void 0 || _contentItem$parent.callDownwards('setSize'); this._maximisedItem = null; contentItem.emit('minimised'); this.emit('stateChanged'); } /** * This method is used to get around sandboxed iframe restrictions. * If 'allow-top-navigation' is not specified in the iframe's 'sandbox' attribute * (as is the case with codepens) the parent window is forbidden from calling certain * methods on the child, such as window.close() or setting document.location.href. * * This prevented GoldenLayout popouts from popping in in codepens. The fix is to call * _$closeWindow on the child window's gl instance which (after a timeout to disconnect * the invoking method from the close call) closes itself. */ _$closeWindow() { window.setTimeout(function () { window.close(); }, 1); } _$getArea(x, y) { var smallestSurface = Infinity; var mathingArea = null; for (var i = 0; i < this._itemAreas.length; i++) { var area = this._itemAreas[i]; if (x > area.x1 && x < area.x2 && y > area.y1 && y < area.y2 && smallestSurface > area.surface) { smallestSurface = area.surface; mathingArea = area; } } return mathingArea; } /** * Creates the drop zones at the edges of the screen */ _$createRootItemAreas() { var areaSize = 50; var rootArea = _objectSpread({}, this.root._$getArea()); var areas = [_objectSpread(_objectSpread({}, rootArea), {}, { side: 'left', x2: rootArea.x1 + areaSize }), _objectSpread(_objectSpread({}, rootArea), {}, { side: 'right', x1: rootArea.x2 - areaSize }), _objectSpread(_objectSpread({}, rootArea), {}, { side: 'top', y2: rootArea.y1 + areaSize }), _objectSpread(_objectSpread({}, rootArea), {}, { side: 'bottom', y1: rootArea.y2 - areaSize })]; areas.forEach(area => { area.surface = (area.x2 - area.x1) * (area.y2 - area.y1); }); this._itemAreas.push(...areas); } _$calculateItemAreas() { var allContentItems = this._getAllContentItems(); this._itemAreas = []; /** * If the last item is dragged out, highlight the entire container size to * allow to re-drop it. allContentItems[ 0 ] === this.root at this point * * Don't include root into the possible drop areas though otherwise since it * will used for every gap in the layout, e.g. splitters */ if (allContentItems.length === 1) { this._itemAreas.push(this.root._$getArea()); return; } this._$createRootItemAreas(); for (var i = 0; i < allContentItems.length; i++) { var item = allContentItems[i]; if (!isStack(item)) { continue; } var area = item._$getArea(); if (area === null) { continue; } else if (area instanceof Array) { this._itemAreas = this._itemAreas.concat(area); } else { var _area$contentItem$_co; this._itemAreas.push(area); var header = _objectSpread(_objectSpread({}, area), (_area$contentItem$_co = area.contentItem._contentAreaDimensions) === null || _area$contentItem$_co === void 0 ? void 0 : _area$contentItem$_co.header.highlightArea); header.surface = (header.x2 - header.x1) * (header.y2 - header.y1); this._itemAreas.push(header); } } } /** * Takes a contentItem or a configuration and optionally a parent * item and returns an initialised instance of the contentItem. * If the contentItem is a function, it is first called * * @param contentItemOrConfig * @param parent Only necessary when passing in config */ _$normalizeContentItem(contentItemOrConfig, parent) { if (!contentItemOrConfig) { throw new Error('No content item defined'); } if (typeof contentItemOrConfig === 'function') { contentItemOrConfig = contentItemOrConfig(); } if (contentItemOrConfig instanceof AbstractContentItem) { return contentItemOrConfig; } if ($.isPlainObject(contentItemOrConfig) && contentItemOrConfig.type) { var newContentItem = this.createContentItem(contentItemOrConfig, parent !== null && parent !== void 0 ? parent : null); newContentItem.callDownwards('_$init'); return newContentItem; } else { throw new Error('Invalid contentItem'); } } /** * Iterates through the array of open popout windows and removes the ones * that are effectively closed. This is necessary due to the lack of reliably * listening for window.close / unload events in a cross browser compatible fashion. */ _$reconcilePopoutWindows() { var openPopouts = []; for (var i = 0; i < this.openPopouts.length; i++) { var _this$openPopouts$i$g; if (((_this$openPopouts$i$g = this.openPopouts[i].getWindow()) === null || _this$openPopouts$i$g === void 0 ? void 0 : _this$openPopouts$i$g.closed) === false) { openPopouts.push(this.openPopouts[i]); } else { this.emit('windowClosed', this.openPopouts[i]); } } if (this.openPopouts.length !== openPopouts.length) { this.emit('stateChanged'); this.openPopouts = openPopouts; } } /*************************** * PRIVATE ***************************/ /** * Returns a flattened array of all content items, * regardles of level or type * @return Flattened array of content items */ _getAllContentItems() { var allContentItems = []; var addChildren = contentItem => { allContentItems.push(contentItem); if (contentItem.contentItems instanceof Array) { for (var i = 0; i < contentItem.contentItems.length; i++) { addChildren(contentItem.contentItems[i]); } } }; addChildren(this.root); return allContentItems; } /** * Binds to DOM/BOM events on init */ _bindEvents() { if (this._isFullPage) { $(window).resize(this._onResize); } $(window).on('unload beforeunload', this._onUnload).on('blur.lm', this._windowBlur).on('focus.lm', this._windowFocus).on('keydown', this._windowKeydown); } /** * Handles setting a class based on window focus, useful for focus indicators */ _windowBlur() { this.root.element.addClass('lm_window_blur'); } _windowFocus() { this.root.element.removeClass('lm_window_blur'); } /** * Handles the escape key to close maximized items, as long as the focus isn't within an input. * The escape key has local behaviors in inputs like the Monaco editor or search that we don't * want to react to as they are not captured. * * @private * @param e The keydown event */ _windowKeydown(e) { if (e.key === 'Escape' && this._maximisedItem !== null) { var active = document.activeElement; if (active && (active.tagName === 'INPUT' || active.tagName === 'TEXTAREA' || active.tagName === 'SELECT' || active.isContentEditable)) { return; } this._maximisedItem.toggleMaximise(); } } /** * Debounces resize events */ _onResize() { clearTimeout(this._resizeTimeoutId); this._resizeTimeoutId = window.setTimeout(this.updateSize.bind(this), 100); } /** * Extends the default config with the user specific settings and applies * derivations. Please note that there's a seperate method (AbstractContentItem._extendItemNode) * that deals with the extension of item configs * * @param config * @returns config */ _createConfig(inputConfig) { var _config$settings; var windowConfigKey = getQueryStringParam('gl-window'); if (windowConfigKey) { this.isSubWindow = true; inputConfig = JSON.parse(localStorage.getItem(windowConfigKey) || '{}'); inputConfig = unminifyConfig(inputConfig); localStorage.removeItem(windowConfigKey); } // Merge with defaults - after this, config has all required fields var config = $.extend(true, {}, defaultConfig, inputConfig); var _nextNode = function nextNode(node) { for (var key in node) { var value = node[key]; if (key !== 'props' && typeof value === 'object' && value != null) { _nextNode(value); } else if (key === 'type' && value === 'react-component') { node.type = 'component'; node.componentName = 'lm-react-component'; } } }; _nextNode(config); if (((_config$settings = config.settings) === null || _config$settings === void 0 ? void 0 : _config$settings.hasHeaders) === false) { config.dimensions.headerHeight = 0; } return config; } /** * This is executed when GoldenLayout detects that it is run * within a previously opened popout window. */ _adjustToWindowMode() { var _this$config$content$; var popInButton = $('<div class="lm_popin" title="' + this.config.labels.popin + '">' + '<div class="lm_icon"></div>' + '<div class="lm_bg"></div>' + '</div>'); popInButton.click(() => { this.emit('popIn'); }); document.title = stripTags((_this$config$content$ = this.config.content[0].title) !== null && _this$config$content$ !== void 0 ? _this$config$content$ : ''); $('head').append($('body link, body style, template, .gl_keep')); this.container = $('body').html('').css('visibility', 'visible').append(popInButton); /* * This seems a bit pointless, but actually causes a reflow/re-evaluation getting around * slickgrid's "Cannot find stylesheet." bug in chrome */ var x = document.body.offsetHeight; // jshint ignore:line /* * Expose this instance on the window object * to allow the opening window to interact with * it */ window.__glInstance = this; } /** * Creates Subwindows (if there are any). Throws an error * if popouts are blocked. */ _createSubWindows() { if (!this.config.openPopouts) { return; } for (var i = 0; i < this.config.openPopouts.length; i++) { var popout = this.config.openPopouts[i]; this.createPopout(popout.content, popout.dimensions, popout.parentId, popout.indexInParent); } } _getContainer() { var container = this._originalContainer ? $(this._originalContainer) : $('body'); if (container.length === 0) { throw new Error('GoldenLayout container not found'); } if (container.length > 1) { throw new Error('GoldenLayout more than one container element specified'); } return container; } /** * Determines what element the layout will be created in */ _setContainer() { var container = this._getContainer(); if (container[0] === document.body) { this._isFullPage = true; $('html, body').css({ height: '100%', margin: 0, padding: 0, overflow: 'hidden' }); } this.container = container; } /** * Kicks of the initial, recursive creation chain * * @param config GoldenLayout Config */ _create(config) { var errorMsg; if (!(config.content instanceof Array)) { if (config.content === undefined) { errorMsg = "Missing setting 'content' on top level of configuration"; } else { errorMsg = "Configuration parameter 'content' must be an array"; } throw new ConfigurationError(errorMsg, config); } if (config.content.length > 1) { errorMsg = "Top level content can't contain more then one element."; throw new ConfigurationError(errorMsg, config); } this.root = new Root(this, { content: config.content }, this.container); this.root.callDownwards('_$init'); if (config.maximisedItemId === '__glMaximised') { this.root.getItemsById(config.maximisedItemId)[0].toggleMaximise(); } } /** * Called when the window is closed or the user navigates away * from the page */ _onUnload() { if (this.config.settings.closePopoutsOnUnload === true) { for (var i = 0; i < this.openPopouts.length; i++) { this.openPopouts[i].close(); } } } /** * Adjusts the number of columns to be lower to fit the screen and still maintain minItemWidth. */ _adjustColumnsResponsive() { // If there is no min width set, or not content items, do nothing. if (!this._useResponsiveLayout() || this._updatingColumnsResponsive || !this.config.dimensions || !this.config.dimensions.minItemWidth || this.root.contentItems.length === 0 || !this.root.contentItems[0].isRow) { this._firstLoad = false; return; } this._firstLoad = false; // If there is only one column, do nothing. var columnCount = this.root.contentItems[0].contentItems.length; if (columnCount <= 1) { return; } // If they all still fit, do nothing. var minItemWidth = this.config.dimensions.minItemWidth; var totalMinWidth = columnCount * minItemWidth; if (this.width == null || totalMinWidth <= this.width) { return; } // Prevent updates while it is already happening. this._updatingColumnsResponsive = true; // Figure out how many columns to stack, and put them all in the first stack container. var finalColumnCount = Math.max(Math.floor(this.width / minItemWidth), 1); var stackColumnCount = columnCount - finalColumnCount; var rootContentItem = this.root.contentItems[0]; var firstStackContainer = this._findAllStackContainers()[0]; for (var i = 0; i < stackColumnCount; i++) { // Stack from right. var column = rootContentItem.contentItems[rootContentItem.contentItems.length - 1]; this._addChildContentItemsToContainer(firstStackContainer, column); } this._updatingColumnsResponsive = false; } /** * Determines if responsive layout should be used. * * @returns True if responsive layout should be used; otherwise false. */ _useResponsiveLayout() { return this.config.settings && (this.config.settings.responsiveMode == 'always' || this.config.settings.responsiveMode == 'onload' && this._firstLoad); } /** * Adds all children of a node to another container recursively. * @param container - Container to add child content items to. * @param node - Node to search for content items. */ _addChildContentItemsToContainer(container, node) { if (node.type === 'stack') { node.contentItems.forEach(function (item) { container.addChild(item); node.removeChild(item, true); }); } else { node.contentItems.forEach(item => { this._addChildContentItemsToContainer(container, item); }); } } /** * Finds all the stack containers. * @returns The found stack containers. */ _findAllStackContainers() { var stackContainers = []; this._findAllStackContainersRecursive(stackContainers, this.root); return stackContainers; } /** * Finds all the stack containers. * * @param stackContainers Set of containers to populate. * @param node Current node to process. */ _findAllStackContainersRecursive(stackContainers, node) { node.contentItems.forEach(item => { if (isStack(item)) { stackContainers.push(item); } else if (!item.isComponent) { this._findAllStackContainersRecursive(stackContainers, item); } }); } } /** * Hook that allows to access private classes */ _defineProperty(LayoutManager, "__lm", lm); export default LayoutManager; //# sourceMappingURL=LayoutManager.js.map