File

src/app/campaigns/timeline/timeline.component.ts

Implements

OnInit AfterViewChecked OnChanges

Metadata

selector app-timeline
styleUrls timeline.component.css
templateUrl ./timeline.component.html

Inputs

resources
state

Outputs

alignedLeft $event type: EventEmitter
alignedRight $event type: EventEmitter
channelAdded $event type: EventEmitter
channelClicked $event type: EventEmitter
closedGaps $event type: EventEmitter
itemAdded $event type: EventEmitter
itemsClicked $event type: EventEmitter
itemsMoved $event type: EventEmitter
itemsResized $event type: EventEmitter
outputAdded $event type: EventEmitter
outputClicked $event type: EventEmitter
resizedToLargest $event type: EventEmitter

Constructor

constructor()

Methods

ngOnInit
ngOnInit()
Returns : void
ngOnChanges
ngOnChanges()
Returns : void
initializeStateChanges
initializeStateChanges()
Returns : void
ngAfterViewChecked
ngAfterViewChecked()
Returns : void
timelineDurationChange
timelineDurationChange(dur: any)
Returns : void
resetSelection
resetSelection()
Returns : void
drawChannels
drawChannels()
Returns : void
applyItemBounds
applyItemBounds()
Returns : void
onScroll
onScroll(e: any)
Returns : void
drag
drag(e: any, type: any, resourceIndex: any)
Returns : void
resizeToLargest
resizeToLargest()
Returns : void
closeGaps
closeGaps()
Returns : void
alignLeft
alignLeft()
Returns : void
alignRight
alignRight()
Returns : void
changeZoom
changeZoom(e: any)
Returns : void
toggleFrozen
toggleFrozen(e: any)
Returns : void
updateItemChannel
updateItemChannel(item: any)
Returns : void
moveItem
moveItem(item: any, x: any, y: any, dur: number)
Returns : void
addChannel
addChannel(channel: any)
Returns : void
addCommonChannel
addCommonChannel(channel: any)
Returns : void
addOutput
addOutput(output: any)
Returns : void
addItem
addItem(item: any)
Returns : void
getChannelById
getChannelById(id: any)
Returns : void
updateContainerSize
updateContainerSize()
Returns : void
selectChannel
selectChannel(i: any)
Returns : void
selectItem
selectItem(item: any)
Returns : void
selectOutput
selectOutput(i: any)
Returns : void
resetObjectSelection
resetObjectSelection()
Returns : void
deleteChannel
deleteChannel(i: any)
Returns : void
deleteOutput
deleteOutput(i: any)
Returns : void
deleteItem
deleteItem(i: any)
Returns : void

Properties

$container
$container: any
defaultState
defaultState: { duration: number; gridWidth: number; gridHeight: number; items: any[]; channels: any[]; outputs...
dev
dev: boolean
Default value : false
draggingItem
draggingItem: any
ruler
ruler: any
Default value : undefined
scrollPosition
scrollPosition: number
Default value : 0
import { Component, OnInit, Input, AfterViewChecked, OnChanges, EventEmitter, Output } from '@angular/core';

declare let $: any;
declare let Draggable: any;
declare let TweenLite: any;

@Component({
  selector: 'app-timeline',
  templateUrl: './timeline.component.html',
  styleUrls: ['./timeline.component.css']
})
export class TimelineComponent implements OnInit, AfterViewChecked, OnChanges {
  $container;

  ruler = undefined;
  dev = false;
  draggingItem;
  scrollPosition = 0;

  defaultState = {
    duration: 3600,
    gridWidth: 36000,
    gridHeight: 50,
    items: [],
    channels: [],
    outputs: [],
    selectedChannel: undefined,
    selectedItem: undefined,
    selectedOutput: undefined,
    frozen: false,
    magnetic: false,
    switch: false,
    zoom: 1
  };

  @Input() resources;
  @Input() state;

  @Output() itemsMoved = new EventEmitter<Object>();
  @Output() itemAdded = new EventEmitter<Object>();
  @Output() itemsClicked = new EventEmitter<Object>();
  @Output() itemsResized = new EventEmitter<Object>();

  @Output() channelAdded = new EventEmitter<Object>();
  @Output() channelClicked = new EventEmitter<Object>();

  @Output() outputAdded = new EventEmitter<Object>();
  @Output() outputClicked = new EventEmitter<Object>();

  @Output() closedGaps = new EventEmitter<Object>();
  @Output() resizedToLargest = new EventEmitter<Object>();
  @Output() alignedLeft = new EventEmitter<Object>();
  @Output() alignedRight = new EventEmitter<Object>();


  constructor() { }

  ngOnInit() {
    // reset item selection when the container is clicked
    this.$container.click((e) => {
      if (!$(e.target).hasClass('box') && !$(e.target).hasClass('box-image') && !$(e.target).hasClass('item-title')) {
        this.resetSelection();
      }
    });
  }

  ngOnChanges() {
    // ngOnChanges gets called first, so initialize here
    this.$container = $('#container');

    this.state = Object.assign({}, this.defaultState, this.state.toJS());

    this.initializeStateChanges();
  }

  initializeStateChanges() {
    // draw channels
    this.drawChannels();

    // initialize item positions
    this.state.items.map((item) => {
      item.left = item.start * (10 / this.state.zoom);
      item.width = item.duration * (10 / this.state.zoom);
      item.top = this.getChannelById(item.channel).top;
      item.overlapsLeft = [];
      item.overlapsRight = [];
    });
  }

  ngAfterViewChecked() {
    var self = this;
    this.state.items.map((item, i) => {
      if (item.$el === undefined) {


        var startX, startY;
        var companions;
        var lastOverlap = 0;

        item.$el = $("li[data-bid='" + i + "']");

        if (item.selected) {
          item.$el.addClass('ui-selected');
        }

        var container = this.$container;

        item.draggable = Draggable.create(item.$el, {
          bounds: self.$container,
          edgeResistance: 1.0,
          type: "x,y",
          throwProps: false,
          lockAxis: false,
          x: 0,
          y: 0,
          liveSnap: {
            y: function(endValue) { return Math.round(endValue / self.state.gridHeight) * self.state.gridHeight; },
            x: function(endValue) {
              // magnetic snapping
              if (self.state.magnetic) {
                self.state.items.filter(filterItem => filterItem != item && filterItem.channel == item.channel).map((otherItem) => {
                  if (endValue >= otherItem.left + otherItem.width - 5 &&
                    endValue <= otherItem.left + otherItem.width + 5) {
                    endValue = otherItem.left + otherItem.width;
                  }

                  if (endValue + item.width >= otherItem.left - 5 &&
                    endValue + item.width <= otherItem.left + 5) {
                    endValue = otherItem.left - item.width;
                  }
                });
              }

              // only allow objects to stop at positions that represent whole numbers of seconds
              var timeValue = Math.round(endValue * self.state.zoom / 10);
              return timeValue * (10 / self.state.zoom);
            }
          },
          onRelease: function () {
            // emit click event for selected items on release
            var selectedItems = self.state.items.filter(i => i.selected);
            self.itemsClicked.emit(selectedItems);
            // console.log("Item clicked: ", selectedItems);
          },
          onPress: function(e) {
            // mutli-select functionality
            if (!e.ctrlKey && $(".box.ui-selected").length == 1) {
              self.resetSelection();
            }
            self.selectItem(item);
            e.stopPropagation();

            //when the user presses, we'll create an array ("companions") and populate it with all the OTHER elements that have the ".ui-selected" class applied (excluding the one that's being dragged). We also record their x and y position so that we can apply the delta to it in the onDrag.
            var boxes = $(".box.ui-selected"),
              i = boxes.length;

            companions = [];
            startX = this.x;
            startY = this.y;

            while (--i > -1) {
              var boxId = $(boxes[i]).data('bid');
              if (boxes[i] !== this.target) {
                companions.push({
                  e: e,
                  selfId: self.state.items.indexOf(item),
                  item: self.state.items[boxId],
                  itemId: boxId,
                  element: boxes[i],
                  x: boxes[i]._gsTransform.x,
                  y: boxes[i]._gsTransform.y,
                  lastX: boxes[i]._gsTransform.x,
                  lastY: boxes[i]._gsTransform.y
                });
              } else {
                self.state.items[boxId].selected = true;
              }
            }
            TweenLite.killTweensOf(".box");
          },
          onDrag: function() {

            // mutliselect movement
            var i = companions.length,
              deltaX = this.x - startX,
              deltaY = this.y - startY,
              companion;

            while (--i > -1) {
              companion = companions[i];

              self.moveItem(companion.item, companion.x + deltaX, companion.y + deltaY);

              if (!companion.item.draggable.hitTest('#container', "100%")) { // if the item is moved outside of the bounds, move it back
                self.moveItem(companion.item, companion.lastX, companion.lastY);
              } else {
                companion.lastX = companion.x + deltaX;
                companion.lastY = companion.y + deltaY;
              }

              // start the companion dragging with the original event
              self.state.items[companion.selfId].draggable.startDrag(companion.e);
            }

            // switch
            if (self.state.switch) {
              // self.state.items.filter(item => item.selected).map((selectedItem) => {
              //   self.state.items.filter(item => !item.selected).map((otherItem) => {
              //     if (selectedItem.channel == otherItem.channel) {
              //       var selectedItemId = self.state.items.indexOf(selectedItem);
              //
              //       var min = 0,
              //         max = (selectedItem.channel.type == "common" ? 13000 : self.state.gridWidth) - otherItem.width;
              //
              //       if (selectedItem.left >= otherItem.left + otherItem.width / 2 - 10 &&
              //         selectedItem.left <= otherItem.left + otherItem.width / 2 + 10) {
              //         self.moveItem(otherItem, Math.min(max, Math.max(min, selectedItem.left + selectedItem.width)), otherItem.top, 100);
              //       }
              //
              //       if (selectedItem.left + selectedItem.width >= otherItem.left + otherItem.width / 2 - 10 &&
              //         selectedItem.left + selectedItem.width <= otherItem.left + otherItem.width / 2 + 10) {
              //         self.moveItem(otherItem, Math.min(max, Math.max(min, selectedItem.left - otherItem.width)), otherItem.top, 100);
              //       }
              //     }
              //   });
              // });

              self.state.items.filter(item => item.selected).map((selectedItem) => {
                self.state.items.filter(item => !item.selected).map((otherItem) => {
                  if (selectedItem.channel == otherItem.channel) {
                    if (selectedItem.overlapsLeft == undefined) {
                      selectedItem.overlapsLeft = [];
                    }

                    if (selectedItem.overlapsRight == undefined) {
                      selectedItem.overlapsRight = [];
                    }

                    if (selectedItem.draggable.hitTest(otherItem.$el)) {
                      if (selectedItem.left <= otherItem.left &&
                        selectedItem.overlapsLeft.indexOf(otherItem) === -1 &&
                        selectedItem.overlapsRight.indexOf(otherItem) === -1
                      ) {
                        selectedItem.overlapsLeft.push(otherItem);
                      }
                      if (selectedItem.left > otherItem.left &&
                        selectedItem.overlapsRight.indexOf(otherItem) === -1 &&
                        selectedItem.overlapsLeft.indexOf(otherItem) === -1) {
                        selectedItem.overlapsRight.push(otherItem);
                      }
                    }
                  }
                });
                selectedItem.overlapsLeft.map((otherItem, idx) => {
                  if (!selectedItem.draggable.hitTest(otherItem.$el, "5%")) {
                    selectedItem.overlapsLeft.splice(idx, 1);
                  }
                });
                selectedItem.overlapsRight.map((otherItem, idx) => {
                  if (!selectedItem.draggable.hitTest(otherItem.$el, "5%")) {
                    selectedItem.overlapsRight.splice(idx, 1);
                  }
                });
              });

              self.state.items.filter(item => item.selected).map((selectedItem) => {
                selectedItem.overlapsLeft.map(otherItem => {
                        var min = 0,
                          max = (self.getChannelById(selectedItem.channel).type == "common" ? 13000 : self.state.gridWidth) - otherItem.width;

                  if (selectedItem.left + selectedItem.width >= otherItem.left + otherItem.width - 10 &&
                    selectedItem.left + selectedItem.width <= otherItem.left + otherItem.width + 10) {
                    self.moveItem(otherItem, Math.min(max, Math.max(min, otherItem.left - selectedItem.width)), otherItem.top, 100);
                    selectedItem.overlapsLeft = [];
                  }
                });

                selectedItem.overlapsRight.map(otherItem => {
                  var min = 0,
                    max = (self.getChannelById(selectedItem.channel).type == "common" ? 13000 : self.state.gridWidth) - otherItem.width;
                  if (selectedItem.left >= otherItem.left - 10 &&
                    selectedItem.left <= otherItem.left + 10) {

                    var newPos = otherItem.left + selectedItem.width;

                    self.moveItem(otherItem, Math.min(max, Math.max(min, newPos)), otherItem.top, 100);
                    selectedItem.overlapsRight = [];
                  }
                });
              });
            }
          }
        })[0];

        //connect object to drag event listener to update position
        item.draggable.addEventListener("drag", function() {
          item.left = this._gsTransform.x;
          item.start = item.left * self.state.zoom / 10;
          item.top = this._gsTransform.y;

          self.updateItemChannel(item);
        });

        item.draggable.addEventListener("dragend", function() {
          var selectedItems = self.state.items.filter(i => i.selected);
          self.itemsMoved.emit(selectedItems);
          // console.log("Item Moved: ", selectedItems);
        });

        // set item initial position
        this.moveItem(item, item.left, item.top);

        // resize functionality
        var startWidth; // define a start width to calculate deltas for multi-resize
        $('.resizable').resizable({
          handles: 'e, w',
          start: function (event, ui) {
            var id = ui.originalElement.data('bid');
            var resizingItem = self.state.items[id];

            startWidth = resizingItem.width;
            // console.log('start: ' + startWidth);
          },
          resize: function (event, ui) {
            // var newWidth = Math.round(ui.size.width / 10) * 10;
            // $(this).width(newWidth);
            var id = ui.originalElement.data('bid');
            var resizingItem = self.state.items[id];

            resizingItem.width = ui.size.width;
            resizingItem.duration = ui.size.width * self.state.zoom / 10;
            var widthDelta = resizingItem.width - startWidth;

            // resizingItem.width = newWidth;
            // resizingItem.duration = newWidth * (self.state.zoom / 10);
            // var widthDelta = newWidth - startWidth;

            // move any companions
            self.state.items.filter((item) => item.selected).map((selectedItem) => {
              if (selectedItem != resizingItem) {
                selectedItem.width += widthDelta;
                selectedItem.duration = selectedItem.width * self.state.zoom / 10;
              }
            });
            // console.log('newWidth: ' + resizingItem.width);

            startWidth = resizingItem.width;
          },
          stop: function (event, ui) {
            // resizable modifies the left which messes with things, so we undo it and calculate the offsets
            var left = parseInt($(this).css('left'));
            var id = ui.originalElement.data('bid');
            var resizingItem = self.state.items[id];

            $(this).css({ left: 0 });

            self.moveItem(resizingItem, resizingItem.left + left, resizingItem.top);

            var selectedItems = self.state.items.filter(i => i.selected);

            self.itemsResized.emit(selectedItems);
            // console.log("Item resized: " + selectedItems);
          }
        });

        // makes GSAP Draggable avoid clicks on the resize handles
        $('.ui-resizable-handle').attr('data-clickable', true);

        this.applyItemBounds();
      }
    });
  }

  timelineDurationChange(dur) {
    this.state.duration = dur;
    // console.log('new timeline duration: ', dur);
    this.updateContainerSize();
  }

  resetSelection() {
    $('.resizable').removeClass('ui-selected');
    this.state.items.map((item) => {
      item.selected = false;
    })
  }

  drawChannels() {
    this.$container.find(".timeline-row").remove();

    this.state.outputs.concat(this.state.channels).map((channel, i) => {
      var type = this.state.channels.indexOf(channel) !== -1 ? "channel" : "output";
      var resources;

      if (type == "channel") {
        resources = this.resources.items;
      } else {
        resources = this.resources.outputs;
      }
      // create element for channel and append it to the container

      channel.top = i * this.state.gridHeight;
      channel.$el = $("<div/>").css({
        height: this.state.gridHeight - 1,
        top: channel.top,
        left: 0
      }).addClass('timeline-row').appendTo(this.$container);

      //add ondrop event listener for accepting item drops
      channel.$el[0].ondrop = (e) => {
        e.preventDefault();

        var data = resources[e.dataTransfer.getData("text")];
        var offset = this.$container.offset();
        var left = e.x - offset.left;
        var top = i * this.state.gridHeight;

        this.addItem({
          resource: {
            src: data.src,
            type: data.type
          },
          title: data.name,
          type: type,
          left: left,
          width: 60 * (10 / this.state.zoom),
          channel: channel.id,
          top: top,
          draggable: undefined,
          $el: undefined,
          selected: false
        });
      };

      channel.$el[0].ondragover = (e) => {
        if (type == this.draggingItem) {
          e.preventDefault();
        }
      };
    });

    //set the container's size to match the grid, and ensure that the box widths/heights reflect the variables above
    this.updateContainerSize();

    this.applyItemBounds();
  }

  applyItemBounds() {
    this.state.items.map((item) => {
      if (item.draggable !== undefined) {
        if (item.type == 'output') {
          var bounds = {
            left: 0,
            top: 0,
            width: this.state.gridWidth,
            height: this.state.outputs.length * this.state.gridHeight
          };
          item.draggable.applyBounds(bounds);
        } else if (item.type == 'channel') {
          if (this.getChannelById(item.channel).type == "common") {
            item.draggable.applyBounds({
              top: 0,
              left: 0,
              width: 13000,
              height: this.$container.height()
            });
          } else {
            var bounds = {
              left: 0,
              top: this.state.outputs.length * this.state.gridHeight,
              width: this.state.gridWidth,
              height: this.state.channels.length * this.state.gridHeight
            };
            item.draggable.applyBounds(bounds);
          }
        }
      }
    });
  }

  onScroll(e) {
    this.scrollPosition = e.target.scrollLeft;
  }

  drag(e, type, resourceIndex) {
    e.dataTransfer.setData("text", resourceIndex);
    this.draggingItem = type;
  }

  resizeToLargest() {
    var largest = this.state.items.reduce((accum, item) => {
      return Math.max(accum, item.width);
    }, 0);

    var resizedItems = this.state.items.filter(item => item.selected)
      .map((item) => {
        item.width = largest;
        item.duration = largest * this.state.zoom / 10;
        item.$el.css({ width: largest });
        return item;
      });

    this.resizedToLargest.emit(resizedItems);
    // console.log("Resized to largest", resizedItems);
  }

  closeGaps() {
    var channel;
    this.state.channels.concat(this.state.outputs).map((c) => {
      if (c.selected) {
        channel = c.id;
      }
    });
    for (var i = 0; i < this.state.items.length; ++i) {
      var item = this.state.items[i];
      if (item.selected) {
        channel = item.channel;
      }
    }
    if (channel !== undefined) {
      // get the items in this channel and sort them left to right
      var groupedItems = this.state.items.filter((item) => {
        return item.channel == channel;
      }).sort((a, b) => {
        return a.left - b.left;
      });

      // place the channels
      var nextStartPos = 0;
      groupedItems.map((item, i) => {
        this.moveItem(item, nextStartPos, item.top);
        nextStartPos += item.width;
      });
      this.closedGaps.emit(groupedItems);
      // console.log("Closed gaps", groupedItems);
    }
  }

  alignLeft() {
    var leftAlign = this.state.items.filter((item) => item.selected)
      .reduce((accum, item) => Math.min(accum, item.left), Infinity);

    var alignedItems = this.state.items.filter(item => item.selected)
      .map((item, i) => {
        this.moveItem(item, leftAlign, item.top);
        return item;
      });

    this.alignedLeft.emit(alignedItems);
    // console.log("Aligned Left", alignedItems);
  }

  alignRight() {
    var rightAlign = this.state.items.filter((item) => item.selected)
      .reduce((accum, item) => Math.max(accum, item.left + item.width), 0);

    var alignedItems = this.state.items.filter(item => item.selected)
      .map((item, i) => {
        this.moveItem(item, rightAlign - item.width, item.top);
        return item;
      });

    this.alignedRight.emit(alignedItems);
    // console.log("Aligned Right", alignedItems);
  }

  changeZoom(e) {
    if (!this.state) return;
    var zoomFactor = 10 / this.state.zoom;

    this.state.items.map((item) => {
      item.width = item.duration * zoomFactor;
      this.moveItem(item, item.start * zoomFactor, item.top);
    });

    this.updateContainerSize();
    this.applyItemBounds();
  }

  toggleFrozen(e) {
    this.state.frozen = !this.state.frozen;

    if (this.state.frozen) {
      this.state.items.map((item) => {
        item.draggable.disable();
      });
    } else {
      this.state.items.map((item) => {
        item.draggable.enable();
      });
    }
  }

  updateItemChannel(item) {
    var newChannel = this.state.channels.filter((c) => c.top == item.top)[0];
    if (newChannel) {
      item.channel = newChannel.id;
      this.applyItemBounds();
    }
  }

  moveItem(item, x, y, dur = 0) {
    if (item) {
      x = (x === undefined) ? item.left : x;
      y = (y === undefined) ? item.top : y;

      TweenLite.to(item.$el[0], dur / 1000, { x: x, y: y });
      item.draggable.update(); // update the draggable position
      item.left = x;
      item.start = item.left * this.state.zoom / 10;
      item.top = y;

      this.updateItemChannel(item);
    }
  }

  addChannel(channel) {
    var newChannel = Object.assign(
      {},
      {
        id: -1,
        $el: undefined,
        name: "CH" + this.state.channels.length,
        type: "normal",
        color: '#00FFFF'
      },
      channel
    );
    this.state.channels.push(newChannel);
    this.drawChannels();

    this.channelAdded.emit(newChannel);
    // console.log("Channel Added: " + newChannel);
  }

  addCommonChannel(channel) {
    var newChannel = Object.assign(
      {},
      {
        id: -1,
        $el: undefined,
        name: "CH" + this.state.channels.length,
        type: "common",
        color: '#0000FF'
      },
      channel
    );
    this.state.channels.push(newChannel);
    this.drawChannels();

    this.channelAdded.emit(newChannel);
    // console.log("Channel added: " + newChannel);
  }

  addOutput(output) {
    var newOutput = Object.assign(
      {},
      {
        id: -1,
        name: "Output",
        color: "#000"
      },
      output
    );

    this.state.outputs.push(newOutput);

    // move all existing items on channels down one row
    this.state.items.filter((item) => item.type == 'channel').map((item) => {
      this.moveItem(item, item.left, item.top + this.state.gridHeight);
    });
    this.drawChannels();

    this.outputAdded.emit(newOutput);
    // console.log("Output added: " + newOutput);
  }

  addItem(item) {
    var newItem = Object.assign(
      {},
      {
        id: -1,
        duration: item.width,
        start: item.left,
        overlapsLeft: [],
        overlapsRight: []
      },
      item
    );
    this.state.items.push(newItem);

    this.itemAdded.emit(newItem);
    // console.log("Item added: " + newItem);
  }

  getChannelById(id) {
    for (var i = 0; i < this.state.channels.length; ++i) {
      if (this.state.channels[i].id == id)
        return this.state.channels[i];
    }
    return false;
  }

  updateContainerSize() {
    this.state.gridWidth = this.state.duration * (10 / this.state.zoom);
    TweenLite.set(
      this.$container, {
        height: (this.state.channels.length + this.state.outputs.length) * this.state.gridHeight + 1,
        width: this.state.gridWidth
      }
    );
  }

  selectChannel(i) {
    var channel = this.state.channels[i];
    this.resetObjectSelection();
    channel.selected = true;
    this.channelClicked.emit(channel);
    // console.log("Channel clicked: " + channel);
  }

  selectItem(item) {
    //this.resetObjectSelection();
    this.state.channels
      .concat(this.state.outputs).map((o) => {
        o.selected = false;
      });
    item.selected = true;
    item.$el.addClass('ui-selected');
  }

  selectOutput(i) {
    var output = this.state.outputs[i];
    this.resetObjectSelection();
    output.selected = true;

    this.outputClicked.emit(output);
    // console.log("Output clicked: " + output);
  }

  resetObjectSelection() {
    this.state.channels
      .concat(this.state.outputs)
      .map((o) => {
        o.selected = false;
      });

    this.resetSelection();
  }

  deleteChannel(i) {
    var deleted = this.state.channels.splice(i, 1)[0];
    deleted.$el.remove();

    this.drawChannels();
  }

  deleteOutput(i) {
    var deleted = this.state.outputs.splice(i, 1)[0];
    deleted.$el.remove();

    // move all existing items on channels up one row
    this.state.items.filter((item) => item.type == 'channel').map((item) => {
      this.moveItem(item, item.left, item.top - this.state.gridHeight);
    });

    this.drawChannels();
  }

  deleteItem(i) {
    var deleted = this.state.items.splice(i, 1)[0];
  }

}
<div style="width: 100%">
  <div class="row" style="padding-top:50px;padding-bottom:50px;" *ngIf="dev">
    <button type="button" class="btn btn-default" (click)="addChannel()">Add Channel</button>
    <button type="button" class="btn btn-default" (click)="addCommonChannel()">Add Common Channel</button>
    <button type="button" class="btn btn-default" (click)="addOutput()">Add Output</button>
    <button type="button" class="btn btn-default" (click)="closeGaps()">Close Gaps</button>
    <input type="checkbox" [(ngModel)]="state.magnetic"> Magnetic
    <button type="button" class="btn btn-default" (click)="resizeToLargest()">Resize to largest</button>
    <button type="button" class="btn btn-default" (click)="alignLeft()">Align Left</button>
    <button type="button" class="btn btn-default" (click)="alignRight()">Align Right</button>
    <input type="checkbox" [(ngModel)]="state.switch"> Switch
    <input type="checkbox" (change)="toggleFrozen($event)"> Frozen
    <select (change)="changeZoom($event)" [(ngModel)]="state.zoom">
      <option value="1">1.0 : 10</option>
      <option value="2">2.0 : 10</option>
      <option value="5">5.0 : 10</option>
      <option value="10">10 : 10</option>
      <option value="20">20 : 10</option>
      <option value="40">40 : 10</option>
    </select>

  </div>
  <div class="row" style="padding-bottom:50px;">
    <!--<app-duration-input-->
      <!--[setDuration]="state.duration"-->
      <!--(durationChange)="timelineDurationChange($event)"-->
    <!--&gt;</app-duration-input>-->
  </div>
  <div class="row timeline-ruler">
    <div class="col-xs-11 col-xs-offset-1" style="padding: 0;">
      <app-timeline-ruler
        width="{{ state.gridWidth }}"
        height="50"
        position="{{ scrollPosition }}"
        scale="{{ state.zoom / 10}}"
      >
      </app-timeline-ruler>
    </div>
  </div>
  <div class="row timeline-container">
    <div class="col-xs-1 channel-titles">
      <div class="channel-title" *ngFor="let output of state.outputs; let i = index" (click)="selectOutput(i)" [class.channel-selected]="output.selected">
        <span class="channel-color" [style.backgroundColor]="output.color"></span> {{ output.name }}
      </div>
      <div class="channel-title" *ngFor="let channel of state.channels; let i = index" [class.common-channel]="channel.type == 'common'" [class.channel-selected]="channel.selected" (click)="selectChannel(i)">
        <span class="channel-color" [style.backgroundColor]="channel.color"></span> {{ channel.name }}
      </div>
    </div>
    <div class="col-xs-11 timeline" (scroll)="onScroll($event)">

      <div id="container">
        <ng-container *ngFor="let item of state.items;let i = index">
          <li *ngIf="item.type == 'output'"
              class="resizable selectable box" [attr.data-bid]="i" [style.width.px]="item.width">

            <img
              *ngIf="item.resource.type == 'png' || item.resource.type == 'svg'"
              class="box-image" src="{{ item.resource.src }}"
              style="height: 100%;width: 100%;max-width: 50px;float:left"
            />

            <i *ngIf="item.resource.type == 'fa'" class="box-image fa {{ item.resource.src }} fa-item"></i>

            <div class="item-title">{{ item.title }}</div>
          </li>
        </ng-container>
        <ng-container *ngFor="let item of state.items;let i = index">
          <li *ngIf="item.type == 'channel'"
              class="resizable selectable box" [attr.data-bid]="i" [style.width.px]="item.width">

              <img
                *ngIf="item.resource.type == 'png' || item.resource.type == 'svg'"
                class="box-image" src="{{ item.resource.src }}"
                style="height: 100%;width: 100%;max-width: 50px;float:left"
              />

              <i *ngIf="item.resource.type == 'fa'" class="box-image fa {{ item.resource.src }} fa-item"></i>

            <div class="item-title">{{ item.title }}</div>
          </li>
        </ng-container>
      </div>
    </div>
  </div>
  <div class="row"  style="padding-top:50px;padding-bottom:50px;" *ngIf="dev">
    <div class="col-xs-4">
      <h4>Resources</h4>
      <table class="table">
        <tr>
          <th>Name</th>
          <th>Type</th>
          <th>Total time</th>
          <th>size</th>
        </tr>
        <tr *ngFor="let resource of resources.items; let i = index" draggable="true" (dragstart)="drag($event, 'channel', i)">
          <td>{{ resource.name }}</td>
          <td>{{ resource.type }}</td>
          <td>{{ resource.time }}</td>
          <td>{{ resource.size }}</td>
        </tr>
      </table>

      <h4>Outputs</h4>
      <table class="table">
        <tr>
          <th>Name</th>
          <th>Type</th>
          <th>Total time</th>
          <th>size</th>
        </tr>
        <tr *ngFor="let resource of resources.outputs; let i = index" draggable="true" (dragstart)="drag($event, 'output', i)">
          <td>{{ resource.name }}</td>
          <td>{{ resource.type }}</td>
          <td>{{ resource.time }}</td>
          <td>{{ resource.size }}</td>
        </tr>
      </table>
    </div>
    <div class="col-xs-4 col-xs-offset-4">
      <ng-container *ngFor="let channel of state.channels; let i = index">
        <div *ngIf="channel.selected">
          <form (keydown.enter)="$event.preventDefault()">
            <div class="form-group">
              <label for="exampleInputEmail1">Name</label>
              <input type="text" class="form-control" name="channel-name" [(ngModel)]="channel.name" placeholder="Email">
            </div>
            <div class="form-group">
              <label for="exampleInputEmail1">Color</label>
              <input type="text" class="form-control" name="channel-color" [(ngModel)]="channel.color" placeholder="Color">
            </div>
            <button type="submit" class="btn btn-default" (click)="deleteChannel(i)">Delete</button>
          </form>
        </div>
      </ng-container>
      <ng-container *ngFor="let output of state.outputs; let i = index">
        <div *ngIf="output.selected">
          <form (keydown.enter)="$event.preventDefault()">
            <div class="form-group">
              <label for="exampleInputEmail1">Name</label>
              <input type="text" class="form-control" name="output-name" [(ngModel)]="output.name" placeholder="Email">
            </div>
            <div class="form-group">
              <label for="exampleInputEmail1">Color</label>
              <input type="text" class="form-control" name="output-color" [(ngModel)]="output.color" placeholder="Color">
            </div>
            <button type="submit" class="btn btn-default" (click)="deleteOutput(i)">Delete</button>
          </form>
        </div>
      </ng-container>
      <ng-container *ngFor="let item of state.items; let i = index">
        <div *ngIf="item.selected">
          <form (keydown.enter)="$event.preventDefault()">
            <div class="form-group">
              <label for="exampleInputEmail1">Name</label>
              <input type="text" class="form-control" name="item-title" [(ngModel)]="item.title" placeholder="Email">
            </div>
            <button type="submit" class="btn btn-default" (click)="deleteItem(i)">Delete</button>
          </form>
        </div>
      </ng-container>
    </div>
  </div>
</div>
Legend
Html element
Component
Html element with directive

results matching ""

    No results matching ""