Home Manual Reference Source Repository

docs/layout/Layout.js

import h from 'snabbdom/h';
import { VNode } from 'snabbdom/vnode';

import { Injector, Inject, Optional, Injectable } from '../di';
import { 
  Renderable, 
  RenderableInjector,
  ConfiguredRenderable,
  RenderableArea,
  RenderableConfig
} from '../dom';
import { 
  ContainerRef, 
  ConfigurationRef,
  RenderableArg,
  DropArea
} from '../common';
import { XYContainer } from '../XYContainer';
import { DragHost, DragHostContainer } from '../DragHost';

export interface LayoutConfig extends RenderableConfig {
  child: RenderableArg<Renderable>;
}

/**
 * A layout is a set of renderables scoped to a drag host.
 * @export
 * @class Layout
 * @extends {Renderable}
 */
@Injectable({
  providers: [ DragHost ]
})
export class Layout extends Renderable {
  constructor(
    @Inject(ConfigurationRef) private _config: LayoutConfig|null,
    @Inject(ContainerRef) protected _container: Renderable,
    @Inject(DragHost) protected _dragHost: DragHost
  ) {
    super();
  }  

  /**
   * The height of the layout in pixels.
   * @readonly
   * @type {number}
   */
  get height(): number {
    return this._container.height;
  }

  /**
   * The width of the layout in pixels.
   * @readonly
   * @type {number}
   */
  get width(): number {
    return this._container.width;
  }

  initialize(): void {
    super.initialize();
    
    if (!this._config || !this._config.child) {
      throw new Error('A layout requires a child renderable.');
    }

    this._contentItems.push(this.createChild(this._config.child));
    this._dragHost.start.subscribe(this._onDragHostStart.bind(this));
  }
  
  /**
   * Creates this renderables virtual node.
   * @returns {VNode} 
   */
  render(): VNode {
    return h('div.ug-layout__layout', {
      style: {
        height: `${this.height}px`,
        width: `${this.width}px`
      }
    }, 
      this._contentItems.map(i => i.render())
    );
  }

  /**
   * Destroys this renderable.
   */
  destroy(): void {
    this._dragHost.destroy();
    super.destroy();
  }

  /**
   * Gets the visible areas of all descendant Renderables.
   * @returns {Array<{ item: Renderable, area: RenderableArea }>} 
   */
  getItemVisibleAreas(): Array<{ item: Renderable, area: RenderableArea }> {
    return this.getDescendants()
      .filter(item => item.isVisible())
      .map(item => {
        return {
          item,
          area: item.getArea()
        };
      });
  }

  private _onDragHostStart(container: DragHostContainer): void {
    const { offsetX, offsetY, height, width } = this;
    const { dragArea, item } = container;
    
    this._dragHost.bounds = new RenderableArea(offsetX, width + offsetX - dragArea.width, offsetY, height + offsetY - dragArea.height);
    this._dragHost.setDropAreas(this._getDropTargets(item));
  }

  private _getDropTargets(target: Renderable): DropArea[] {
    return this.getItemVisibleAreas()
      .filter(({ item }) => {
        return DragHost.isDropTarget(item) 
          && item !== target
          && item.isDroppable(target)
          && !target.contains(item)
          && !target.isContainedWithin(item);
      }) as any; 
  }

  /**
   * Configures a layout renderable.
   * @static
   * @param {LayoutConfig} config 
   * @returns {ConfiguredRenderable<Layout>} 
   */
  static configure(config: LayoutConfig): ConfiguredRenderable<Layout> {
    return new ConfiguredRenderable(Layout, config);    
  }
}