@deephaven/golden-layout 0.17.1-beta.1 → 0.17.1-beta.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/dist/LayoutManager.js +1140 -0
  2. package/dist/LayoutManager.js.map +1 -0
  3. package/dist/base.js +16 -0
  4. package/dist/base.js.map +1 -0
  5. package/dist/config/ItemDefaultConfig.js +8 -0
  6. package/dist/config/ItemDefaultConfig.js.map +1 -0
  7. package/dist/config/defaultConfig.js +42 -0
  8. package/dist/config/defaultConfig.js.map +1 -0
  9. package/dist/config/index.js +7 -0
  10. package/dist/config/index.js.map +1 -0
  11. package/dist/container/ItemContainer.js +192 -0
  12. package/dist/container/ItemContainer.js.map +1 -0
  13. package/dist/container/index.js +5 -0
  14. package/dist/container/index.js.map +1 -0
  15. package/dist/controls/BrowserPopout.js +260 -0
  16. package/dist/controls/BrowserPopout.js.map +1 -0
  17. package/dist/controls/DragProxy.js +236 -0
  18. package/dist/controls/DragProxy.js.map +1 -0
  19. package/dist/controls/DragSource.js +60 -0
  20. package/dist/controls/DragSource.js.map +1 -0
  21. package/dist/controls/DragSourceFromEvent.js +75 -0
  22. package/dist/controls/DragSourceFromEvent.js.map +1 -0
  23. package/dist/controls/DropTargetIndicator.js +28 -0
  24. package/dist/controls/DropTargetIndicator.js.map +1 -0
  25. package/dist/controls/Header.js +698 -0
  26. package/dist/controls/Header.js.map +1 -0
  27. package/dist/controls/HeaderButton.js +23 -0
  28. package/dist/controls/HeaderButton.js.map +1 -0
  29. package/dist/controls/Splitter.js +45 -0
  30. package/dist/controls/Splitter.js.map +1 -0
  31. package/dist/controls/Tab.js +259 -0
  32. package/dist/controls/Tab.js.map +1 -0
  33. package/dist/controls/TransitionIndicator.js +64 -0
  34. package/dist/controls/TransitionIndicator.js.map +1 -0
  35. package/dist/controls/index.js +23 -0
  36. package/dist/controls/index.js.map +1 -0
  37. package/dist/errors/ConfigurationError.js +10 -0
  38. package/dist/errors/ConfigurationError.js.map +1 -0
  39. package/dist/errors/index.js +5 -0
  40. package/dist/errors/index.js.map +1 -0
  41. package/dist/index.js +3 -0
  42. package/dist/index.js.map +1 -0
  43. package/dist/items/AbstractContentItem.js +617 -0
  44. package/dist/items/AbstractContentItem.js.map +1 -0
  45. package/dist/items/Component.js +84 -0
  46. package/dist/items/Component.js.map +1 -0
  47. package/dist/items/Root.js +93 -0
  48. package/dist/items/Root.js.map +1 -0
  49. package/dist/items/RowOrColumn.js +550 -0
  50. package/dist/items/RowOrColumn.js.map +1 -0
  51. package/dist/items/Stack.js +498 -0
  52. package/dist/items/Stack.js.map +1 -0
  53. package/dist/items/index.js +13 -0
  54. package/dist/items/index.js.map +1 -0
  55. package/dist/utils/BubblingEvent.js +12 -0
  56. package/dist/utils/BubblingEvent.js.map +1 -0
  57. package/dist/utils/ConfigMinifier.js +160 -0
  58. package/dist/utils/ConfigMinifier.js.map +1 -0
  59. package/dist/utils/DragListener.js +128 -0
  60. package/dist/utils/DragListener.js.map +1 -0
  61. package/dist/utils/EventEmitter.js +133 -0
  62. package/dist/utils/EventEmitter.js.map +1 -0
  63. package/dist/utils/EventHub.js +147 -0
  64. package/dist/utils/EventHub.js.map +1 -0
  65. package/dist/utils/ReactComponentHandler.js +135 -0
  66. package/dist/utils/ReactComponentHandler.js.map +1 -0
  67. package/dist/utils/index.js +22 -0
  68. package/dist/utils/index.js.map +1 -0
  69. package/dist/utils/utils.js +195 -0
  70. package/dist/utils/utils.js.map +1 -0
  71. package/package.json +20 -47
  72. package/dist/goldenlayout.js +0 -6314
  73. package/dist/goldenlayout.min.js +0 -1
@@ -0,0 +1,1140 @@
1
+ import $ from 'jquery';
2
+ import lm from './base.js';
3
+ /**
4
+ * The main class that will be exposed as GoldenLayout.
5
+ *
6
+ * @public
7
+ * @constructor
8
+ * @param {GoldenLayout config} config
9
+ * @param {[DOM element container]} container Can be a jQuery selector string or a Dom element. Defaults to body
10
+ *
11
+ * @returns {VOID}
12
+ */
13
+
14
+ var LayoutManager = function LayoutManager(config, container) {
15
+ if (!$ || typeof $.noConflict !== 'function') {
16
+ var errorMsg = 'jQuery is missing as dependency for GoldenLayout. ';
17
+ errorMsg += 'Please either expose $ on GoldenLayout\'s scope (e.g. window) or add "jquery" to ';
18
+ errorMsg += 'your paths when using RequireJS/AMD';
19
+ throw new Error(errorMsg);
20
+ }
21
+
22
+ lm.utils.EventEmitter.call(this);
23
+ this.isInitialised = false;
24
+ this._isFullPage = false;
25
+ this._resizeTimeoutId = null;
26
+ this._components = {
27
+ 'lm-react-component': lm.utils.ReactComponentHandler
28
+ };
29
+ this._fallbackComponent = undefined;
30
+ this._itemAreas = [];
31
+ this._resizeFunction = lm.utils.fnBind(this._onResize, this);
32
+ this._unloadFunction = lm.utils.fnBind(this._onUnload, this);
33
+ this._windowBlur = this._windowBlur.bind(this);
34
+ this._windowFocus = this._windowFocus.bind(this);
35
+ this._maximisedItem = null;
36
+ this._maximisePlaceholder = $('<div class="lm_maximise_place"></div>');
37
+ this._creationTimeoutPassed = false;
38
+ this._subWindowsCreated = false;
39
+ this._dragSources = [];
40
+ this._updatingColumnsResponsive = false;
41
+ this._firstLoad = true;
42
+ this.width = null;
43
+ this.height = null;
44
+ this.root = null;
45
+ this.openPopouts = [];
46
+ this.selectedItem = null;
47
+ this.isSubWindow = false;
48
+ this.eventHub = new lm.utils.EventHub(this);
49
+ this.config = this._createConfig(config);
50
+ this.container = container;
51
+ this.dropTargetIndicator = null;
52
+ this.transitionIndicator = null;
53
+ this.tabDropPlaceholder = $('<div class="lm_drop_tab_placeholder"></div>');
54
+
55
+ if (this.isSubWindow === true) {
56
+ $('body').css('visibility', 'hidden');
57
+ }
58
+
59
+ this._typeToItem = {
60
+ column: lm.utils.fnBind(lm.items.RowOrColumn, this, [true]),
61
+ row: lm.utils.fnBind(lm.items.RowOrColumn, this, [false]),
62
+ stack: lm.items.Stack,
63
+ component: lm.items.Component
64
+ };
65
+ };
66
+ /**
67
+ * Hook that allows to access private classes
68
+ */
69
+
70
+
71
+ LayoutManager.__lm = lm;
72
+ /**
73
+ * Takes a GoldenLayout configuration object and
74
+ * replaces its keys and values recursively with
75
+ * one letter codes
76
+ *
77
+ * @static
78
+ * @public
79
+ * @param {Object} config A GoldenLayout config object
80
+ *
81
+ * @returns {Object} minified config
82
+ */
83
+
84
+ LayoutManager.minifyConfig = function (config) {
85
+ return new lm.utils.ConfigMinifier().minifyConfig(config);
86
+ };
87
+ /**
88
+ * Takes a configuration Object that was previously minified
89
+ * using minifyConfig and returns its original version
90
+ *
91
+ * @static
92
+ * @public
93
+ * @param {Object} minifiedConfig
94
+ *
95
+ * @returns {Object} the original configuration
96
+ */
97
+
98
+
99
+ LayoutManager.unminifyConfig = function (config) {
100
+ return new lm.utils.ConfigMinifier().unminifyConfig(config);
101
+ };
102
+
103
+ lm.utils.copy(LayoutManager.prototype, {
104
+ /**
105
+ * Register a component with the layout manager. If a configuration node
106
+ * of type component is reached it will look up componentName and create the
107
+ * associated component
108
+ *
109
+ * {
110
+ * type: "component",
111
+ * componentName: "EquityNewsFeed",
112
+ * componentState: { "feedTopic": "us-bluechips" }
113
+ * }
114
+ *
115
+ * @public
116
+ * @param {String} name
117
+ * @param {Function} constructor
118
+ *
119
+ * @returns {Function} cleanup function to deregister component
120
+ */
121
+ registerComponent: function registerComponent(name, constructor) {
122
+ if (typeof constructor !== 'function' && (constructor == null || constructor.render == null || typeof constructor.render !== 'function')) {
123
+ throw new Error('Please register a constructor function');
124
+ }
125
+
126
+ if (this._components[name] !== undefined) {
127
+ throw new Error('Component ' + name + ' is already registered');
128
+ }
129
+
130
+ this._components[name] = constructor;
131
+
132
+ function cleanup() {
133
+ if (this._components[name] === undefined) {
134
+ throw new Error('Component ' + name + ' is not registered');
135
+ }
136
+
137
+ delete this._components[name];
138
+ }
139
+
140
+ return cleanup.bind(this);
141
+ },
142
+
143
+ /**
144
+ * Set a fallback component to be rendered in place of unregistered components
145
+ *
146
+ * @public
147
+ * @param {Function} constructor
148
+ *
149
+ * @returns {void}
150
+ */
151
+ setFallbackComponent: function setFallbackComponent(constructor) {
152
+ this._fallbackComponent = constructor;
153
+ },
154
+
155
+ /**
156
+ * Creates a layout configuration object based on the the current state
157
+ *
158
+ * @public
159
+ * @returns {Object} GoldenLayout configuration
160
+ */
161
+ toConfig: function toConfig(root) {
162
+ var config, _next, i;
163
+
164
+ if (this.isInitialised === false) {
165
+ throw new Error("Can't create config, layout not yet initialised");
166
+ }
167
+
168
+ if (root && !(root instanceof lm.items.AbstractContentItem)) {
169
+ throw new Error('Root must be a ContentItem');
170
+ }
171
+ /*
172
+ * settings & labels
173
+ */
174
+
175
+
176
+ config = {
177
+ settings: lm.utils.copy({}, this.config.settings),
178
+ dimensions: lm.utils.copy({}, this.config.dimensions),
179
+ labels: lm.utils.copy({}, this.config.labels)
180
+ };
181
+ /*
182
+ * Content
183
+ */
184
+
185
+ config.content = [];
186
+
187
+ _next = function next(configNode, item) {
188
+ var key, i;
189
+
190
+ for (key in item.config) {
191
+ if (key !== 'content') {
192
+ configNode[key] = item.config[key];
193
+ }
194
+ }
195
+
196
+ if (configNode.componentName === 'lm-react-component') {
197
+ // We change the type in `createContentItem`, so change it back here
198
+ configNode.type = 'react-component';
199
+ }
200
+
201
+ if (item.contentItems.length) {
202
+ configNode.content = [];
203
+
204
+ for (i = 0; i < item.contentItems.length; i++) {
205
+ configNode.content[i] = {};
206
+
207
+ _next(configNode.content[i], item.contentItems[i]);
208
+ }
209
+ }
210
+ };
211
+
212
+ if (root) {
213
+ _next(config, {
214
+ contentItems: [root]
215
+ });
216
+ } else {
217
+ _next(config, this.root);
218
+ }
219
+ /*
220
+ * Retrieve config for subwindows
221
+ */
222
+
223
+
224
+ this._$reconcilePopoutWindows();
225
+
226
+ config.openPopouts = [];
227
+
228
+ for (i = 0; i < this.openPopouts.length; i++) {
229
+ config.openPopouts.push(this.openPopouts[i].toConfig());
230
+ }
231
+ /*
232
+ * Add maximised item
233
+ */
234
+
235
+
236
+ config.maximisedItemId = this._maximisedItem ? '__glMaximised' : null;
237
+ return config;
238
+ },
239
+
240
+ /**
241
+ * Returns a previously registered component
242
+ *
243
+ * @public
244
+ * @param {String} name The name used
245
+ *
246
+ * @returns {Function}
247
+ */
248
+ getComponent: function getComponent(name) {
249
+ return this._components[name];
250
+ },
251
+
252
+ /**
253
+ * Returns a fallback component to render in place of unregistered components
254
+ *
255
+ * @public
256
+ *
257
+ * @returns {Function}
258
+ */
259
+ getFallbackComponent: function getFallbackComponent() {
260
+ return this._fallbackComponent;
261
+ },
262
+
263
+ /**
264
+ * Creates the actual layout. Must be called after all initial components
265
+ * are registered. Recurses through the configuration and sets up
266
+ * the item tree.
267
+ *
268
+ * If called before the document is ready it adds itself as a listener
269
+ * to the document.ready event
270
+ *
271
+ * @public
272
+ *
273
+ * @returns {void}
274
+ */
275
+ init: function init() {
276
+ /**
277
+ * Create the popout windows straight away. If popouts are blocked
278
+ * an error is thrown on the same 'thread' rather than a timeout and can
279
+ * be caught. This also prevents any further initilisation from taking place.
280
+ */
281
+ if (this._subWindowsCreated === false) {
282
+ this._createSubWindows();
283
+
284
+ this._subWindowsCreated = true;
285
+ }
286
+ /**
287
+ * If the document isn't ready yet, wait for it.
288
+ */
289
+
290
+
291
+ if (document.readyState === 'loading' || document.body === null) {
292
+ $(document).ready(lm.utils.fnBind(this.init, this));
293
+ return;
294
+ }
295
+ /**
296
+ * If this is a subwindow, wait a few milliseconds for the original
297
+ * page's js calls to be executed, then replace the bodies content
298
+ * with GoldenLayout
299
+ */
300
+
301
+
302
+ if (this.isSubWindow === true && this._creationTimeoutPassed === false) {
303
+ setTimeout(lm.utils.fnBind(this.init, this), 7);
304
+ this._creationTimeoutPassed = true;
305
+ return;
306
+ }
307
+
308
+ if (this.isSubWindow === true) {
309
+ this._adjustToWindowMode();
310
+ }
311
+
312
+ this._setContainer();
313
+
314
+ this.dropTargetIndicator = new lm.controls.DropTargetIndicator(this.container);
315
+ this.transitionIndicator = new lm.controls.TransitionIndicator();
316
+ this.updateSize();
317
+
318
+ this._create(this.config);
319
+
320
+ this._bindEvents();
321
+
322
+ this.isInitialised = true;
323
+
324
+ this._adjustColumnsResponsive();
325
+
326
+ this.emit('initialised');
327
+ },
328
+
329
+ /**
330
+ * Updates the layout managers size
331
+ *
332
+ * @public
333
+ * @param {[int]} width height in pixels
334
+ * @param {[int]} height width in pixels
335
+ *
336
+ * @returns {void}
337
+ */
338
+ updateSize: function updateSize(width, height) {
339
+ if (arguments.length === 2) {
340
+ this.width = width;
341
+ this.height = height;
342
+ } else {
343
+ this.width = this.container.width();
344
+ this.height = this.container.height();
345
+ }
346
+
347
+ if (this.isInitialised === true) {
348
+ this.root.callDownwards('setSize', [this.width, this.height]);
349
+
350
+ if (this._maximisedItem) {
351
+ this._maximisedItem.element.width(this.container.width());
352
+
353
+ this._maximisedItem.element.height(this.container.height());
354
+
355
+ this._maximisedItem.callDownwards('setSize');
356
+ }
357
+
358
+ this._adjustColumnsResponsive();
359
+ }
360
+ },
361
+
362
+ /**
363
+ * Destroys the LayoutManager instance itself as well as every ContentItem
364
+ * within it. After this is called nothing should be left of the LayoutManager.
365
+ *
366
+ * @public
367
+ * @returns {void}
368
+ */
369
+ destroy: function destroy() {
370
+ if (this.isInitialised === false) {
371
+ return;
372
+ }
373
+
374
+ this._onUnload();
375
+
376
+ $(window).off('resize', this._resizeFunction);
377
+ $(window).off('unload beforeunload', this._unloadFunction);
378
+ $(window).off('blur.lm').off('focus.lm');
379
+ this.root.callDownwards('_$destroy', [], true);
380
+ this.root.contentItems = [];
381
+ this.tabDropPlaceholder.remove();
382
+ this.dropTargetIndicator.destroy();
383
+ this.transitionIndicator.destroy();
384
+ this.eventHub.destroy();
385
+
386
+ this._dragSources.forEach(function (dragSource) {
387
+ dragSource._dragListener.destroy();
388
+
389
+ dragSource._element = null;
390
+ dragSource._itemConfig = null;
391
+ dragSource._dragListener = null;
392
+ });
393
+
394
+ this._dragSources = [];
395
+ },
396
+
397
+ /**
398
+ * Recursively creates new item tree structures based on a provided
399
+ * ItemConfiguration object
400
+ *
401
+ * @public
402
+ * @param {Object} config ItemConfig
403
+ * @param {[ContentItem]} parent The item the newly created item should be a child of
404
+ *
405
+ * @returns {lm.items.ContentItem}
406
+ */
407
+ createContentItem: function createContentItem(config, parent) {
408
+ var typeErrorMsg, contentItem;
409
+
410
+ if (typeof config.type !== 'string') {
411
+ throw new lm.errors.ConfigurationError("Missing parameter 'type'", config);
412
+ }
413
+
414
+ if (config.type === 'react-component') {
415
+ config.type = 'component';
416
+ config.componentName = 'lm-react-component';
417
+ }
418
+
419
+ if (!this._typeToItem[config.type]) {
420
+ typeErrorMsg = "Unknown type '" + config.type + "'. " + 'Valid types are ' + lm.utils.objectKeys(this._typeToItem).join(',');
421
+ throw new lm.errors.ConfigurationError(typeErrorMsg);
422
+ }
423
+ /**
424
+ * We add an additional stack around every component that's not within a stack anyways.
425
+ */
426
+
427
+
428
+ if ( // If this is a component
429
+ config.type === 'component' && // and it's not already within a stack
430
+ !(parent instanceof lm.items.Stack) && // and we have a parent
431
+ !!parent && // and it's not the topmost item in a new window
432
+ !(this.isSubWindow === true && parent instanceof lm.items.Root)) {
433
+ config = {
434
+ type: 'stack',
435
+ width: config.width,
436
+ height: config.height,
437
+ content: [config]
438
+ };
439
+ }
440
+
441
+ contentItem = new this._typeToItem[config.type](this, config, parent);
442
+ return contentItem;
443
+ },
444
+
445
+ /**
446
+ * Creates a popout window with the specified content and dimensions
447
+ *
448
+ * @param {Object|lm.itemsAbstractContentItem} configOrContentItem
449
+ * @param {[Object]} dimensions A map with width, height, left and top
450
+ * @param {[String]} parentId the id of the element this item will be appended to
451
+ * when popIn is called
452
+ * @param {[Number]} indexInParent The position of this item within its parent element
453
+ * @returns {lm.controls.BrowserPopout}
454
+ */
455
+ createPopout: function createPopout(configOrContentItem, dimensions, parentId, indexInParent) {
456
+ var config = configOrContentItem,
457
+ isItem = configOrContentItem instanceof lm.items.AbstractContentItem,
458
+ self = this,
459
+ windowLeft,
460
+ windowTop,
461
+ offset,
462
+ parent,
463
+ child,
464
+ browserPopout;
465
+ parentId = parentId || null;
466
+
467
+ if (isItem) {
468
+ config = this.toConfig(configOrContentItem).content;
469
+ parentId = lm.utils.getUniqueId();
470
+ /**
471
+ * If the item is the only component within a stack or for some
472
+ * other reason the only child of its parent the parent will be destroyed
473
+ * when the child is removed.
474
+ *
475
+ * In order to support this we move up the tree until we find something
476
+ * that will remain after the item is being popped out
477
+ */
478
+
479
+ parent = configOrContentItem.parent;
480
+ child = configOrContentItem;
481
+
482
+ while (parent.contentItems.length === 1 && !parent.isRoot) {
483
+ parent = parent.parent;
484
+ child = child.parent;
485
+ }
486
+
487
+ parent.addId(parentId);
488
+
489
+ if (isNaN(indexInParent)) {
490
+ indexInParent = lm.utils.indexOf(child, parent.contentItems);
491
+ }
492
+ } else {
493
+ if (!(config instanceof Array)) {
494
+ config = [config];
495
+ }
496
+ }
497
+
498
+ if (!dimensions && isItem) {
499
+ windowLeft = window.screenX || window.screenLeft;
500
+ windowTop = window.screenY || window.screenTop;
501
+ offset = configOrContentItem.element.offset();
502
+ dimensions = {
503
+ left: windowLeft + offset.left,
504
+ top: windowTop + offset.top,
505
+ width: configOrContentItem.element.width(),
506
+ height: configOrContentItem.element.height()
507
+ };
508
+ }
509
+
510
+ if (!dimensions && !isItem) {
511
+ dimensions = {
512
+ left: window.screenX || window.screenLeft + 20,
513
+ top: window.screenY || window.screenTop + 20,
514
+ width: 500,
515
+ height: 309
516
+ };
517
+ }
518
+
519
+ if (isItem) {
520
+ configOrContentItem.remove();
521
+ }
522
+
523
+ browserPopout = new lm.controls.BrowserPopout(config, dimensions, parentId, indexInParent, this);
524
+ browserPopout.on('initialised', function () {
525
+ self.emit('windowOpened', browserPopout);
526
+ });
527
+ browserPopout.on('closed', function () {
528
+ self._$reconcilePopoutWindows();
529
+ });
530
+ this.openPopouts.push(browserPopout);
531
+ return browserPopout;
532
+ },
533
+
534
+ /**
535
+ * Attaches DragListener to any given DOM element
536
+ * and turns it into a way of creating new ContentItems
537
+ * by 'dragging' the DOM element into the layout
538
+ *
539
+ * @param {jQuery DOM element} element
540
+ * @param {Object|Function} itemConfig for the new item to be created, or a function which will provide it
541
+ *
542
+ * @returns {void}
543
+ */
544
+ createDragSource: function createDragSource(element, itemConfig) {
545
+ this.config.settings.constrainDragToContainer = false;
546
+ var dragSource = new lm.controls.DragSource($(element), itemConfig, this);
547
+
548
+ this._dragSources.push(dragSource);
549
+
550
+ return dragSource;
551
+ },
552
+
553
+ /**
554
+ * Create a new item in a dragging state, given a starting mouse event to act as the initial position
555
+ *
556
+ * @param {Object|Function} itemConfig for the new item to be created, or a function which will provide it
557
+ * @param {MouseEvent} event used as the starting position for the dragProxy
558
+ *
559
+ * @returns {void}
560
+ */
561
+ createDragSourceFromEvent: function createDragSourceFromEvent(itemConfig, event) {
562
+ this.config.settings.constrainDragToContainer = false;
563
+ return new lm.controls.DragSourceFromEvent(itemConfig, this, event);
564
+ },
565
+
566
+ /**
567
+ * Programmatically selects an item. This deselects
568
+ * the currently selected item, selects the specified item
569
+ * and emits a selectionChanged event
570
+ *
571
+ * @param {lm.item.AbstractContentItem} item#
572
+ * @param {[Boolean]} _$silent Wheather to notify the item of its selection
573
+ * @event selectionChanged
574
+ *
575
+ * @returns {VOID}
576
+ */
577
+ selectItem: function selectItem(item, _$silent) {
578
+ if (this.config.settings.selectionEnabled !== true) {
579
+ throw new Error('Please set selectionEnabled to true to use this feature');
580
+ }
581
+
582
+ if (item === this.selectedItem) {
583
+ return;
584
+ }
585
+
586
+ if (this.selectedItem !== null) {
587
+ this.selectedItem.deselect();
588
+ }
589
+
590
+ if (item && _$silent !== true) {
591
+ item.select();
592
+ }
593
+
594
+ this.selectedItem = item;
595
+ this.emit('selectionChanged', item);
596
+ },
597
+
598
+ /*************************
599
+ * PACKAGE PRIVATE
600
+ *************************/
601
+ _$maximiseItem: function _$maximiseItem(contentItem) {
602
+ if (this._maximisedItem !== null) {
603
+ this._$minimiseItem(this._maximisedItem);
604
+ }
605
+
606
+ this._maximisedItem = contentItem;
607
+
608
+ this._maximisedItem.addId('__glMaximised');
609
+
610
+ contentItem.element.addClass('lm_maximised');
611
+ contentItem.element.after(this._maximisePlaceholder);
612
+ this.root.element.prepend(contentItem.element);
613
+ contentItem.element.width(this.container.width());
614
+ contentItem.element.height(this.container.height());
615
+ contentItem.callDownwards('setSize');
616
+
617
+ this._maximisedItem.emit('maximised');
618
+
619
+ this.emit('stateChanged');
620
+ },
621
+ _$minimiseItem: function _$minimiseItem(contentItem) {
622
+ contentItem.element.removeClass('lm_maximised');
623
+ contentItem.removeId('__glMaximised');
624
+
625
+ this._maximisePlaceholder.after(contentItem.element);
626
+
627
+ this._maximisePlaceholder.remove();
628
+
629
+ contentItem.parent.callDownwards('setSize');
630
+ this._maximisedItem = null;
631
+ contentItem.emit('minimised');
632
+ this.emit('stateChanged');
633
+ },
634
+
635
+ /**
636
+ * This method is used to get around sandboxed iframe restrictions.
637
+ * If 'allow-top-navigation' is not specified in the iframe's 'sandbox' attribute
638
+ * (as is the case with codepens) the parent window is forbidden from calling certain
639
+ * methods on the child, such as window.close() or setting document.location.href.
640
+ *
641
+ * This prevented GoldenLayout popouts from popping in in codepens. The fix is to call
642
+ * _$closeWindow on the child window's gl instance which (after a timeout to disconnect
643
+ * the invoking method from the close call) closes itself.
644
+ *
645
+ * @packagePrivate
646
+ *
647
+ * @returns {void}
648
+ */
649
+ _$closeWindow: function _$closeWindow() {
650
+ window.setTimeout(function () {
651
+ window.close();
652
+ }, 1);
653
+ },
654
+ _$getArea: function _$getArea(x, y) {
655
+ var i,
656
+ area,
657
+ smallestSurface = Infinity,
658
+ mathingArea = null;
659
+
660
+ for (i = 0; i < this._itemAreas.length; i++) {
661
+ area = this._itemAreas[i];
662
+
663
+ if (x > area.x1 && x < area.x2 && y > area.y1 && y < area.y2 && smallestSurface > area.surface) {
664
+ smallestSurface = area.surface;
665
+ mathingArea = area;
666
+ }
667
+ }
668
+
669
+ return mathingArea;
670
+ },
671
+ _$createRootItemAreas: function _$createRootItemAreas() {
672
+ var areaSize = 50;
673
+ var sides = {
674
+ y2: 'y1',
675
+ x2: 'x1',
676
+ y1: 'y2',
677
+ x1: 'x2'
678
+ };
679
+
680
+ for (var side in sides) {
681
+ var area = this.root._$getArea();
682
+
683
+ area.side = side;
684
+ if (sides[side][1] == '2') area[side] = area[sides[side]] - areaSize;else {
685
+ area[side] = area[sides[side]] + areaSize;
686
+ }
687
+ area.surface = (area.x2 - area.x1) * (area.y2 - area.y1);
688
+
689
+ this._itemAreas.push(area);
690
+ }
691
+ },
692
+ _$calculateItemAreas: function _$calculateItemAreas() {
693
+ var i,
694
+ area,
695
+ allContentItems = this._getAllContentItems();
696
+
697
+ this._itemAreas = [];
698
+ /**
699
+ * If the last item is dragged out, highlight the entire container size to
700
+ * allow to re-drop it. allContentItems[ 0 ] === this.root at this point
701
+ *
702
+ * Don't include root into the possible drop areas though otherwise since it
703
+ * will used for every gap in the layout, e.g. splitters
704
+ */
705
+
706
+ if (allContentItems.length === 1) {
707
+ this._itemAreas.push(this.root._$getArea());
708
+
709
+ return;
710
+ }
711
+
712
+ this._$createRootItemAreas();
713
+
714
+ for (i = 0; i < allContentItems.length; i++) {
715
+ if (!allContentItems[i].isStack) {
716
+ continue;
717
+ }
718
+
719
+ area = allContentItems[i]._$getArea();
720
+
721
+ if (area === null) {
722
+ continue;
723
+ } else if (area instanceof Array) {
724
+ this._itemAreas = this._itemAreas.concat(area);
725
+ } else {
726
+ this._itemAreas.push(area);
727
+
728
+ var header = {};
729
+ lm.utils.copy(header, area);
730
+ lm.utils.copy(header, area.contentItem._contentAreaDimensions.header.highlightArea);
731
+ header.surface = (header.x2 - header.x1) * (header.y2 - header.y1);
732
+
733
+ this._itemAreas.push(header);
734
+ }
735
+ }
736
+ },
737
+
738
+ /**
739
+ * Takes a contentItem or a configuration and optionally a parent
740
+ * item and returns an initialised instance of the contentItem.
741
+ * If the contentItem is a function, it is first called
742
+ *
743
+ * @packagePrivate
744
+ *
745
+ * @param {lm.items.AbtractContentItem|Object|Function} contentItemOrConfig
746
+ * @param {lm.items.AbtractContentItem} parent Only necessary when passing in config
747
+ *
748
+ * @returns {lm.items.AbtractContentItem}
749
+ */
750
+ _$normalizeContentItem: function _$normalizeContentItem(contentItemOrConfig, parent) {
751
+ if (!contentItemOrConfig) {
752
+ throw new Error('No content item defined');
753
+ }
754
+
755
+ if (lm.utils.isFunction(contentItemOrConfig)) {
756
+ contentItemOrConfig = contentItemOrConfig();
757
+ }
758
+
759
+ if (contentItemOrConfig instanceof lm.items.AbstractContentItem) {
760
+ return contentItemOrConfig;
761
+ }
762
+
763
+ if ($.isPlainObject(contentItemOrConfig) && contentItemOrConfig.type) {
764
+ var newContentItem = this.createContentItem(contentItemOrConfig, parent);
765
+ newContentItem.callDownwards('_$init');
766
+ return newContentItem;
767
+ } else {
768
+ throw new Error('Invalid contentItem');
769
+ }
770
+ },
771
+
772
+ /**
773
+ * Iterates through the array of open popout windows and removes the ones
774
+ * that are effectively closed. This is necessary due to the lack of reliably
775
+ * listening for window.close / unload events in a cross browser compatible fashion.
776
+ *
777
+ * @packagePrivate
778
+ *
779
+ * @returns {void}
780
+ */
781
+ _$reconcilePopoutWindows: function _$reconcilePopoutWindows() {
782
+ var openPopouts = [],
783
+ i;
784
+
785
+ for (i = 0; i < this.openPopouts.length; i++) {
786
+ if (this.openPopouts[i].getWindow().closed === false) {
787
+ openPopouts.push(this.openPopouts[i]);
788
+ } else {
789
+ this.emit('windowClosed', this.openPopouts[i]);
790
+ }
791
+ }
792
+
793
+ if (this.openPopouts.length !== openPopouts.length) {
794
+ this.emit('stateChanged');
795
+ this.openPopouts = openPopouts;
796
+ }
797
+ },
798
+
799
+ /***************************
800
+ * PRIVATE
801
+ ***************************/
802
+
803
+ /**
804
+ * Returns a flattened array of all content items,
805
+ * regardles of level or type
806
+ *
807
+ * @private
808
+ *
809
+ * @returns {void}
810
+ */
811
+ _getAllContentItems: function _getAllContentItems() {
812
+ var allContentItems = [];
813
+
814
+ var addChildren = function addChildren(contentItem) {
815
+ allContentItems.push(contentItem);
816
+
817
+ if (contentItem.contentItems instanceof Array) {
818
+ for (var i = 0; i < contentItem.contentItems.length; i++) {
819
+ addChildren(contentItem.contentItems[i]);
820
+ }
821
+ }
822
+ };
823
+
824
+ addChildren(this.root);
825
+ return allContentItems;
826
+ },
827
+
828
+ /**
829
+ * Binds to DOM/BOM events on init
830
+ *
831
+ * @private
832
+ *
833
+ * @returns {void}
834
+ */
835
+ _bindEvents: function _bindEvents() {
836
+ if (this._isFullPage) {
837
+ $(window).resize(this._resizeFunction);
838
+ }
839
+
840
+ $(window).on('unload beforeunload', this._unloadFunction).on('blur.lm', this._windowBlur).on('focus.lm', this._windowFocus);
841
+ },
842
+
843
+ /**
844
+ * Handles setting a class based on window focus, useful for focus indicators
845
+ */
846
+ _windowBlur: function _windowBlur() {
847
+ this.root.element.addClass('lm_window_blur');
848
+ },
849
+ _windowFocus: function _windowFocus() {
850
+ this.root.element.removeClass('lm_window_blur');
851
+ },
852
+
853
+ /**
854
+ * Debounces resize events
855
+ *
856
+ * @private
857
+ *
858
+ * @returns {void}
859
+ */
860
+ _onResize: function _onResize() {
861
+ clearTimeout(this._resizeTimeoutId);
862
+ this._resizeTimeoutId = setTimeout(lm.utils.fnBind(this.updateSize, this), 100);
863
+ },
864
+
865
+ /**
866
+ * Extends the default config with the user specific settings and applies
867
+ * derivations. Please note that there's a seperate method (AbstractContentItem._extendItemNode)
868
+ * that deals with the extension of item configs
869
+ *
870
+ * @param {Object} config
871
+ * @static
872
+ * @returns {Object} config
873
+ */
874
+ _createConfig: function _createConfig(config) {
875
+ var windowConfigKey = lm.utils.getQueryStringParam('gl-window');
876
+
877
+ if (windowConfigKey) {
878
+ this.isSubWindow = true;
879
+ config = localStorage.getItem(windowConfigKey);
880
+ config = JSON.parse(config);
881
+ config = new lm.utils.ConfigMinifier().unminifyConfig(config);
882
+ localStorage.removeItem(windowConfigKey);
883
+ }
884
+
885
+ config = $.extend(true, {}, lm.config.defaultConfig, config);
886
+
887
+ var nextNode = function nextNode(node) {
888
+ for (var key in node) {
889
+ if (key !== 'props' && typeof node[key] === 'object') {
890
+ nextNode(node[key]);
891
+ } else if (key === 'type' && node[key] === 'react-component') {
892
+ node.type = 'component';
893
+ node.componentName = 'lm-react-component';
894
+ }
895
+ }
896
+ };
897
+
898
+ nextNode(config);
899
+
900
+ if (config.settings.hasHeaders === false) {
901
+ config.dimensions.headerHeight = 0;
902
+ }
903
+
904
+ return config;
905
+ },
906
+
907
+ /**
908
+ * This is executed when GoldenLayout detects that it is run
909
+ * within a previously opened popout window.
910
+ *
911
+ * @private
912
+ *
913
+ * @returns {void}
914
+ */
915
+ _adjustToWindowMode: function _adjustToWindowMode() {
916
+ var popInButton = $('<div class="lm_popin" title="' + this.config.labels.popin + '">' + '<div class="lm_icon"></div>' + '<div class="lm_bg"></div>' + '</div>');
917
+ popInButton.click(lm.utils.fnBind(function () {
918
+ this.emit('popIn');
919
+ }, this));
920
+ document.title = lm.utils.stripTags(this.config.content[0].title);
921
+ $('head').append($('body link, body style, template, .gl_keep'));
922
+ this.container = $('body').html('').css('visibility', 'visible').append(popInButton);
923
+ /*
924
+ * This seems a bit pointless, but actually causes a reflow/re-evaluation getting around
925
+ * slickgrid's "Cannot find stylesheet." bug in chrome
926
+ */
927
+
928
+ var x = document.body.offsetHeight; // jshint ignore:line
929
+
930
+ /*
931
+ * Expose this instance on the window object
932
+ * to allow the opening window to interact with
933
+ * it
934
+ */
935
+
936
+ window.__glInstance = this;
937
+ },
938
+
939
+ /**
940
+ * Creates Subwindows (if there are any). Throws an error
941
+ * if popouts are blocked.
942
+ *
943
+ * @returns {void}
944
+ */
945
+ _createSubWindows: function _createSubWindows() {
946
+ var i, popout;
947
+
948
+ for (i = 0; i < this.config.openPopouts.length; i++) {
949
+ popout = this.config.openPopouts[i];
950
+ this.createPopout(popout.content, popout.dimensions, popout.parentId, popout.indexInParent);
951
+ }
952
+ },
953
+
954
+ /**
955
+ * Determines what element the layout will be created in
956
+ *
957
+ * @private
958
+ *
959
+ * @returns {void}
960
+ */
961
+ _setContainer: function _setContainer() {
962
+ var container = $(this.container || 'body');
963
+
964
+ if (container.length === 0) {
965
+ throw new Error('GoldenLayout container not found');
966
+ }
967
+
968
+ if (container.length > 1) {
969
+ throw new Error('GoldenLayout more than one container element specified');
970
+ }
971
+
972
+ if (container[0] === document.body) {
973
+ this._isFullPage = true;
974
+ $('html, body').css({
975
+ height: '100%',
976
+ margin: 0,
977
+ padding: 0,
978
+ overflow: 'hidden'
979
+ });
980
+ }
981
+
982
+ this.container = container;
983
+ },
984
+
985
+ /**
986
+ * Kicks of the initial, recursive creation chain
987
+ *
988
+ * @param {Object} config GoldenLayout Config
989
+ *
990
+ * @returns {void}
991
+ */
992
+ _create: function _create(config) {
993
+ var errorMsg;
994
+
995
+ if (!(config.content instanceof Array)) {
996
+ if (config.content === undefined) {
997
+ errorMsg = "Missing setting 'content' on top level of configuration";
998
+ } else {
999
+ errorMsg = "Configuration parameter 'content' must be an array";
1000
+ }
1001
+
1002
+ throw new lm.errors.ConfigurationError(errorMsg, config);
1003
+ }
1004
+
1005
+ if (config.content.length > 1) {
1006
+ errorMsg = "Top level content can't contain more then one element.";
1007
+ throw new lm.errors.ConfigurationError(errorMsg, config);
1008
+ }
1009
+
1010
+ this.root = new lm.items.Root(this, {
1011
+ content: config.content
1012
+ }, this.container);
1013
+ this.root.callDownwards('_$init');
1014
+
1015
+ if (config.maximisedItemId === '__glMaximised') {
1016
+ this.root.getItemsById(config.maximisedItemId)[0].toggleMaximise();
1017
+ }
1018
+ },
1019
+
1020
+ /**
1021
+ * Called when the window is closed or the user navigates away
1022
+ * from the page
1023
+ *
1024
+ * @returns {void}
1025
+ */
1026
+ _onUnload: function _onUnload() {
1027
+ if (this.config.settings.closePopoutsOnUnload === true) {
1028
+ for (var i = 0; i < this.openPopouts.length; i++) {
1029
+ this.openPopouts[i].close();
1030
+ }
1031
+ }
1032
+ },
1033
+
1034
+ /**
1035
+ * Adjusts the number of columns to be lower to fit the screen and still maintain minItemWidth.
1036
+ *
1037
+ * @returns {void}
1038
+ */
1039
+ _adjustColumnsResponsive: function _adjustColumnsResponsive() {
1040
+ // If there is no min width set, or not content items, do nothing.
1041
+ if (!this._useResponsiveLayout() || this._updatingColumnsResponsive || !this.config.dimensions || !this.config.dimensions.minItemWidth || this.root.contentItems.length === 0 || !this.root.contentItems[0].isRow) {
1042
+ this._firstLoad = false;
1043
+ return;
1044
+ }
1045
+
1046
+ this._firstLoad = false; // If there is only one column, do nothing.
1047
+
1048
+ var columnCount = this.root.contentItems[0].contentItems.length;
1049
+
1050
+ if (columnCount <= 1) {
1051
+ return;
1052
+ } // If they all still fit, do nothing.
1053
+
1054
+
1055
+ var minItemWidth = this.config.dimensions.minItemWidth;
1056
+ var totalMinWidth = columnCount * minItemWidth;
1057
+
1058
+ if (totalMinWidth <= this.width) {
1059
+ return;
1060
+ } // Prevent updates while it is already happening.
1061
+
1062
+
1063
+ this._updatingColumnsResponsive = true; // Figure out how many columns to stack, and put them all in the first stack container.
1064
+
1065
+ var finalColumnCount = Math.max(Math.floor(this.width / minItemWidth), 1);
1066
+ var stackColumnCount = columnCount - finalColumnCount;
1067
+ var rootContentItem = this.root.contentItems[0];
1068
+
1069
+ var firstStackContainer = this._findAllStackContainers()[0];
1070
+
1071
+ for (var i = 0; i < stackColumnCount; i++) {
1072
+ // Stack from right.
1073
+ var column = rootContentItem.contentItems[rootContentItem.contentItems.length - 1];
1074
+
1075
+ this._addChildContentItemsToContainer(firstStackContainer, column);
1076
+ }
1077
+
1078
+ this._updatingColumnsResponsive = false;
1079
+ },
1080
+
1081
+ /**
1082
+ * Determines if responsive layout should be used.
1083
+ *
1084
+ * @returns {bool} - True if responsive layout should be used; otherwise false.
1085
+ */
1086
+ _useResponsiveLayout: function _useResponsiveLayout() {
1087
+ return this.config.settings && (this.config.settings.responsiveMode == 'always' || this.config.settings.responsiveMode == 'onload' && this._firstLoad);
1088
+ },
1089
+
1090
+ /**
1091
+ * Adds all children of a node to another container recursively.
1092
+ * @param {object} container - Container to add child content items to.
1093
+ * @param {object} node - Node to search for content items.
1094
+ * @returns {void}
1095
+ */
1096
+ _addChildContentItemsToContainer: function _addChildContentItemsToContainer(container, node) {
1097
+ if (node.type === 'stack') {
1098
+ node.contentItems.forEach(function (item) {
1099
+ container.addChild(item);
1100
+ node.removeChild(item, true);
1101
+ });
1102
+ } else {
1103
+ node.contentItems.forEach(lm.utils.fnBind(function (item) {
1104
+ this._addChildContentItemsToContainer(container, item);
1105
+ }, this));
1106
+ }
1107
+ },
1108
+
1109
+ /**
1110
+ * Finds all the stack containers.
1111
+ * @returns {array} - The found stack containers.
1112
+ */
1113
+ _findAllStackContainers: function _findAllStackContainers() {
1114
+ var stackContainers = [];
1115
+
1116
+ this._findAllStackContainersRecursive(stackContainers, this.root);
1117
+
1118
+ return stackContainers;
1119
+ },
1120
+
1121
+ /**
1122
+ * Finds all the stack containers.
1123
+ *
1124
+ * @param {array} - Set of containers to populate.
1125
+ * @param {object} - Current node to process.
1126
+ *
1127
+ * @returns {void}
1128
+ */
1129
+ _findAllStackContainersRecursive: function _findAllStackContainersRecursive(stackContainers, node) {
1130
+ node.contentItems.forEach(lm.utils.fnBind(function (item) {
1131
+ if (item.type == 'stack') {
1132
+ stackContainers.push(item);
1133
+ } else if (!item.isComponent) {
1134
+ this._findAllStackContainersRecursive(stackContainers, item);
1135
+ }
1136
+ }, this));
1137
+ }
1138
+ });
1139
+ export default LayoutManager;
1140
+ //# sourceMappingURL=LayoutManager.js.map