<commit_before>// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

cr.define('dnd', function() {
  'use strict';

  /** @const */ var BookmarkList = bmm.BookmarkList;
  /** @const */ var ListItem = cr.ui.ListItem;
  /** @const */ var TreeItem = cr.ui.TreeItem;

  /**
   * Enumeration of valid drop locations relative to an element. These are
   * bit masks to allow combining multiple locations in a single value.
   * @enum {number}
   * @const
   */
  var DropPosition = {
    NONE: 0,
    ABOVE: 1,
    ON: 2,
    BELOW: 4
  };

  /**
   * @type {Object} Drop information calculated in |handleDragOver|.
   */
  var dropDestination = null;

  /**
    * @type {number} Timer id used to help minimize flicker.
    */
  var removeDropIndicatorTimer;

  /**
    * The element that had a style applied it to indicate the drop location.
    * This is used to easily remove the style when necessary.
    * @type {Element}
    */
  var lastIndicatorElement;

  /**
    * The style that was applied to indicate the drop location.
    * @type {string}
    */
  var lastIndicatorClassName;

  var dropIndicator = {
    /**
     * Applies the drop indicator style on the target element and stores that
     * information to easily remove the style in the future.
     */
    addDropIndicatorStyle: function(indicatorElement, position) {
      var indicatorStyleName = position == DropPosition.ABOVE ? 'drag-above' :
                               position == DropPosition.BELOW ? 'drag-below' :
                               'drag-on';

      lastIndicatorElement = indicatorElement;
      lastIndicatorClassName = indicatorStyleName;

      indicatorElement.classList.add(indicatorStyleName);
    },

    /**
     * Clears the drop indicator style from the last element was the drop target
     * so the drop indicator is no longer for that element.
     */
    removeDropIndicatorStyle: function() {
      if (!lastIndicatorElement || !lastIndicatorClassName)
        return;
      lastIndicatorElement.classList.remove(lastIndicatorClassName);
      lastIndicatorElement = null;
      lastIndicatorClassName = null;
    },

    /**
      * Displays the drop indicator on the current drop target to give the
      * user feedback on where the drop will occur.
      */
    update: function(dropDest) {
      window.clearTimeout(removeDropIndicatorTimer);

      var indicatorElement = dropDest.element;
      var position = dropDest.position;
      if (dropDest.element instanceof BookmarkList) {
        // For an empty bookmark list use 'drop-above' style.
        position = DropPosition.ABOVE;
      } else if (dropDest.element instanceof TreeItem) {
        indicatorElement = indicatorElement.querySelector('.tree-row');
      }
      dropIndicator.removeDropIndicatorStyle();
      dropIndicator.addDropIndicatorStyle(indicatorElement, position);
    },

    /**
     * Stop displaying the drop indicator.
     */
    finish: function() {
      // The use of a timeout is in order to reduce flickering as we move
      // between valid drop targets.
      window.clearTimeout(removeDropIndicatorTimer);
      removeDropIndicatorTimer = window.setTimeout(function() {
        dropIndicator.removeDropIndicatorStyle();
      }, 100);
    }
  };

  /**
    * Delay for expanding folder when pointer hovers on folder in tree view in
    * milliseconds.
    * @type {number}
    * @const
    */
  // TODO(yosin): EXPAND_FOLDER_DELAY should follow system settings. 400ms is
  // taken from Windows default settings.
  var EXPAND_FOLDER_DELAY = 400;

  /**
    * The timestamp when the mouse was over a folder during a drag operation.
    * Used to open the hovered folder after a certain time.
    * @type {number}
    */
  var lastHoverOnFolderTimeStamp = 0;

  /**
    * Expand a folder if the user has hovered for longer than the specified
    * time during a drag action.
    */
  function updateAutoExpander(eventTimeStamp, overElement) {
    // Expands a folder in tree view when pointer hovers on it longer than
    // EXPAND_FOLDER_DELAY.
    var hoverOnFolderTimeStamp = lastHoverOnFolderTimeStamp;
    lastHoverOnFolderTimeStamp = 0;
    if (hoverOnFolderTimeStamp) {
      if (eventTimeStamp - hoverOnFolderTimeStamp >= EXPAND_FOLDER_DELAY)
        overElement.expanded = true;
      else
        lastHoverOnFolderTimeStamp = hoverOnFolderTimeStamp;
    } else if (overElement instanceof TreeItem &&
                bmm.isFolder(overElement.bookmarkNode) &&
                overElement.hasChildren &&
                !overElement.expanded) {
      lastHoverOnFolderTimeStamp = eventTimeStamp;
    }
  }

  /**
    * Stores the information abou the bookmark and folders being dragged.
    * @type {Object}
    */
  var dragData = null;
  var dragInfo = {
    handleChromeDragEnter: function(newDragData) {
      dragData = newDragData;
    },
    clearDragData: function() {
      dragData = null;
    },
    isDragValid: function() {
      return !!dragData;
    },
    isSameProfile: function() {
      return dragData && dragData.sameProfile;
    },
    isDraggingFolders: function() {
      return dragData && dragData.elements.some(function(node) {
        return !node.url;
      });
    },
    isDraggingBookmark: function(bookmarkId) {
      return dragData && dragData.elements.some(function(node) {
        return node.id == bookmarkId;
      });
    },
    isDraggingChildBookmark: function(folderId) {
      return dragData && dragData.elements.some(function(node) {
        return node.parentId == folderId;
      });
    },
    isDraggingFolderToDescendant: function(bookmarkNode) {
      return dragData && dragData.elements.some(function(node) {
        var dragFolder = bmm.treeLookup[node.id];
        var dragFolderNode = dragFolder && dragFolder.bookmarkNode;
        return dragFolderNode && bmm.contains(dragFolderNode, bookmarkNode);
      });
    }
  };

  /**
   * External function to select folders or bookmarks after a drop action.
   * @type {function}
   */
  var selectItemsAfterUserAction = null;

  function getBookmarkElement(el) {
    while (el && !el.bookmarkNode) {
      el = el.parentNode;
    }
    return el;
  }

  // If we are over the list and the list is showing search result, we cannot
  // drop.
  function isOverSearch(overElement) {
    return list.isSearch() && list.contains(overElement);
  }

  /**
   * Determines the valid drop positions for the given target element.
   * @param {!HTMLElement} overElement The element that we are currently
   *     dragging over.
   * @return {DropPosition} An bit field enumeration of valid drop locations.
   */
  function calculateValidDropTargets(overElement) {
    if (!dragInfo.isDragValid() || isOverSearch(overElement))
      return DropPosition.NONE;

    if (dragInfo.isSameProfile() &&
        (dragInfo.isDraggingBookmark(overElement.bookmarkNode.id) ||
         dragInfo.isDraggingFolderToDescendant(overElement.bookmarkNode))) {
      return DropPosition.NONE;
    }

    var canDropInfo = calculateDropAboveBelow(overElement);
    if (canDropOn(overElement))
      canDropInfo |= DropPosition.ON;

    return canDropInfo;
  }

  function calculateDropAboveBelow(overElement) {
    if (overElement instanceof BookmarkList)
      return DropPosition.NONE;

    // We cannot drop between Bookmarks bar and Other bookmarks.
    if (overElement.bookmarkNode.parentId == bmm.ROOT_ID)
      return DropPosition.NONE;

    var isOverTreeItem = overElement instanceof TreeItem;
    var isOverExpandedTree = isOverTreeItem && overElement.expanded;
    var isDraggingFolders = dragInfo.isDraggingFolders();

    // We can only drop between items in the tree if we have any folders.
    if (isOverTreeItem && !isDraggingFolders)
      return DropPosition.NONE;

    // When dragging from a different profile we do not need to consider
    // conflicts between the dragged items and the drop target.
    if (!dragInfo.isSameProfile()) {
      // Don't allow dropping below an expanded tree item since it is confusing
      // to the user anyway.
      return isOverExpandedTree ? DropPosition.ABOVE :
                                  (DropPosition.ABOVE | DropPosition.BELOW);
    }

    var resultPositions = DropPosition.NONE;

    // Cannot drop above if the item above is already in the drag source.
    var previousElem = overElement.previousElementSibling;
    if (!previousElem || !dragInfo.isDraggingBookmark(previousElem.bookmarkId))
      resultPositions |= DropPosition.ABOVE;

    // Don't allow dropping below an expanded tree item since it is confusing
    // to the user anyway.
    if (isOverExpandedTree)
      return resultPositions;

    // Cannot drop below if the item below is already in the drag source.
    var nextElement = overElement.nextElementSibling;
    if (!nextElement || !dragInfo.isDraggingBookmark(nextElement.bookmarkId))
      resultPositions |= DropPosition.BELOW;

    return resultPositions;
  }

  /**
   * Determine whether we can drop the dragged items on the drop target.
   * @param {!HTMLElement} overElement The element that we are currently
   *     dragging over.
   * @return {boolean} Whether we can drop the dragged items on the drop
   *     target.
   */
  function canDropOn(overElement) {
    // We can only drop on a folder.
    if (!bmm.isFolder(overElement.bookmarkNode))
      return false;

    if (!dragInfo.isSameProfile())
      return true;

    if (overElement instanceof BookmarkList) {
      // We are trying to drop an item past the last item. This is
      // only allowed if dragged item is different from the last item
      // in the list.
      var listItems = list.items;
      var len = listItems.length;
      if (!len || !dragInfo.isDraggingBookmark(listItems[len - 1].bookmarkId))
        return true;
    }

    return !dragInfo.isDraggingChildBookmark(overElement.bookmarkNode.id);
  }

  /**
   * Callback for the dragstart event.
   * @param {Event} e The dragstart event.
   */
  function handleDragStart(e) {
    // Determine the selected bookmarks.
    var target = e.target;
    var draggedNodes = [];
    if (target instanceof ListItem) {
      // Use selected items.
      draggedNodes = target.parentNode.selectedItems;
    } else if (target instanceof TreeItem) {
      draggedNodes.push(target.bookmarkNode);
    }

    // We manage starting the drag by using the extension API.
    e.preventDefault();

    if (draggedNodes.length) {
      // If we are dragging a single link, we can do the *Link* effect.
      // Otherwise, we only allow copy and move.
      e.dataTransfer.effectAllowed = draggedNodes.length == 1 &&
          !bmm.isFolder(draggedNodes[0]) ? 'copyMoveLink' : 'copyMove';

      chrome.bookmarkManagerPrivate.startDrag(draggedNodes.map(function(node) {
        return node.id;
      }));
    }
  }

  function handleDragEnter(e) {
    e.preventDefault();
  }

  /**
   * Calback for the dragover event.
   * @param {Event} e The dragover event.
   */
  function handleDragOver(e) {
    // Allow DND on text inputs.
    if (e.target.tagName != 'INPUT') {
      // The default operation is to allow dropping links etc to do navigation.
      // We never want to do that for the bookmark manager.
      e.preventDefault();

      // Set to none. This will get set to something if we can do the drop.
      e.dataTransfer.dropEffect = 'none';
    }

    if (!dragInfo.isDragValid())
      return;

    var overElement = getBookmarkElement(e.target) ||
                      (e.target == list ? list : null);
    if (!overElement)
      return;

    updateAutoExpander(e.timeStamp, overElement);

    var canDropInfo = calculateValidDropTargets(overElement);
    if (canDropInfo == DropPosition.NONE)
      return;

    // Now we know that we can drop. Determine if we will drop above, on or
    // below based on mouse position etc.

    dropDestination = calcDropPosition(e.clientY, overElement, canDropInfo);
    if (!dropDestination) {
      e.dataTransfer.dropEffect = 'none';
      return;
    }

    e.dataTransfer.dropEffect = dragInfo.isSameProfile() ? 'move' : 'copy';
    dropIndicator.update(dropDestination);
  }

  /**
   * This function determines where the drop will occur relative to the element.
   * @return {?Object} If no valid drop position is found, null, otherwise
   *     an object containing the following parameters:
   *       element - The target element that will receive the drop.
   *       position - A |DropPosition| relative to the |element|.
   */
  function calcDropPosition(elementClientY, overElement, canDropInfo) {
    if (overElement instanceof BookmarkList) {
      // Dropping on the BookmarkList either means dropping below the last
      // bookmark element or on the list itself if it is empty.
      var length = overElement.items.length;
      if (length)
        return {
          element: overElement.getListItemByIndex(length - 1),
          position: DropPosition.BELOW
        };
      return {element: overElement, position: DropPosition.ON};
    }

    var above = canDropInfo & DropPosition.ABOVE;
    var below = canDropInfo & DropPosition.BELOW;
    var on = canDropInfo & DropPosition.ON;
    var rect = overElement.getBoundingClientRect();
    var yRatio = (elementClientY - rect.top) / rect.height;

    if (above && (yRatio <= .25 || yRatio <= .5 && (!below || !on)))
      return {element: overElement, position: DropPosition.ABOVE};
    if (below && (yRatio > .75 || yRatio > .5 && (!above || !on)))
      return {element: overElement, position: DropPosition.BELOW};
    if (on)
      return {element: overElement, position: DropPosition.ON};
    return null;
  }

  function calculateDropInfo(eventTarget, dropDestination) {
    if (!dropDestination || !dragInfo.isDragValid())
      return null;

    var dropPos = dropDestination.position;
    var relatedNode = dropDestination.element.bookmarkNode;
    var dropInfoResult = {
        selectTarget: null,
        selectedTreeId: -1,
        parentId: dropPos == DropPosition.ON ? relatedNode.id :
                                               relatedNode.parentId,
        index: -1,
        relatedIndex: -1
      };

    // Try to find the index in the dataModel so we don't have to always keep
    // the index for the list items up to date.
    var overElement = getBookmarkElement(eventTarget);
    if (overElement instanceof ListItem) {
      dropInfoResult.relatedIndex =
          overElement.parentNode.dataModel.indexOf(relatedNode);
      dropInfoResult.selectTarget = list;
    } else if (overElement instanceof BookmarkList) {
      dropInfoResult.relatedIndex = overElement.dataModel.length - 1;
      dropInfoResult.selectTarget = list;
    } else {
      // Tree
      dropInfoResult.relatedIndex = relatedNode.index;
      dropInfoResult.selectTarget = tree;
      dropInfoResult.selectedTreeId =
          tree.selectedItem ? tree.selectedItem.bookmarkId : null;
    }

    if (dropPos == DropPosition.ABOVE)
      dropInfoResult.index = dropInfoResult.relatedIndex;
    else if (dropPos == DropPosition.BELOW)
      dropInfoResult.index = dropInfoResult.relatedIndex + 1;

    return dropInfoResult;
  }

  function handleDragLeave(e) {
    dropIndicator.finish();
  }

  function handleDrop(e) {
    var dropInfo = calculateDropInfo(e.target, dropDestination);
    if (dropInfo) {
      selectItemsAfterUserAction(dropInfo.selectTarget,
                                 dropInfo.selectedTreeId);
      if (dropInfo.index != -1)
        chrome.bookmarkManagerPrivate.drop(dropInfo.parentId, dropInfo.index);
      else
        chrome.bookmarkManagerPrivate.drop(dropInfo.parentId);

      e.preventDefault();
    }
    dropDestination = null;
    dropIndicator.finish();
  }

  function clearDragData() {
    dragInfo.clearDragData();
    dropDestination = null;
  }

  function init(selectItemsAfterUserActionFunction) {
    function deferredClearData() {
      setTimeout(clearDragData);
    }

    selectItemsAfterUserAction = selectItemsAfterUserActionFunction;

    document.addEventListener('dragstart', handleDragStart);
    document.addEventListener('dragenter', handleDragEnter);
    document.addEventListener('dragover', handleDragOver);
    document.addEventListener('dragleave', handleDragLeave);
    document.addEventListener('drop', handleDrop);
    document.addEventListener('dragend', deferredClearData);
    document.addEventListener('mouseup', deferredClearData);

    chrome.bookmarkManagerPrivate.onDragEnter.addListener(
        dragInfo.handleChromeDragEnter);
    chrome.bookmarkManagerPrivate.onDragLeave.addListener(deferredClearData);
    chrome.bookmarkManagerPrivate.onDrop.addListener(deferredClearData);
  }
  return {init: init};
});
<commit_msg>Bookmark Manager: Prevent dragging when the user is editing an item<commit_after>// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

cr.define('dnd', function() {
  'use strict';

  /** @const */ var BookmarkList = bmm.BookmarkList;
  /** @const */ var ListItem = cr.ui.ListItem;
  /** @const */ var TreeItem = cr.ui.TreeItem;

  /**
   * Enumeration of valid drop locations relative to an element. These are
   * bit masks to allow combining multiple locations in a single value.
   * @enum {number}
   * @const
   */
  var DropPosition = {
    NONE: 0,
    ABOVE: 1,
    ON: 2,
    BELOW: 4
  };

  /**
   * @type {Object} Drop information calculated in |handleDragOver|.
   */
  var dropDestination = null;

  /**
    * @type {number} Timer id used to help minimize flicker.
    */
  var removeDropIndicatorTimer;

  /**
    * The element that had a style applied it to indicate the drop location.
    * This is used to easily remove the style when necessary.
    * @type {Element}
    */
  var lastIndicatorElement;

  /**
    * The style that was applied to indicate the drop location.
    * @type {string}
    */
  var lastIndicatorClassName;

  var dropIndicator = {
    /**
     * Applies the drop indicator style on the target element and stores that
     * information to easily remove the style in the future.
     */
    addDropIndicatorStyle: function(indicatorElement, position) {
      var indicatorStyleName = position == DropPosition.ABOVE ? 'drag-above' :
                               position == DropPosition.BELOW ? 'drag-below' :
                               'drag-on';

      lastIndicatorElement = indicatorElement;
      lastIndicatorClassName = indicatorStyleName;

      indicatorElement.classList.add(indicatorStyleName);
    },

    /**
     * Clears the drop indicator style from the last element was the drop target
     * so the drop indicator is no longer for that element.
     */
    removeDropIndicatorStyle: function() {
      if (!lastIndicatorElement || !lastIndicatorClassName)
        return;
      lastIndicatorElement.classList.remove(lastIndicatorClassName);
      lastIndicatorElement = null;
      lastIndicatorClassName = null;
    },

    /**
      * Displays the drop indicator on the current drop target to give the
      * user feedback on where the drop will occur.
      */
    update: function(dropDest) {
      window.clearTimeout(removeDropIndicatorTimer);

      var indicatorElement = dropDest.element;
      var position = dropDest.position;
      if (dropDest.element instanceof BookmarkList) {
        // For an empty bookmark list use 'drop-above' style.
        position = DropPosition.ABOVE;
      } else if (dropDest.element instanceof TreeItem) {
        indicatorElement = indicatorElement.querySelector('.tree-row');
      }
      dropIndicator.removeDropIndicatorStyle();
      dropIndicator.addDropIndicatorStyle(indicatorElement, position);
    },

    /**
     * Stop displaying the drop indicator.
     */
    finish: function() {
      // The use of a timeout is in order to reduce flickering as we move
      // between valid drop targets.
      window.clearTimeout(removeDropIndicatorTimer);
      removeDropIndicatorTimer = window.setTimeout(function() {
        dropIndicator.removeDropIndicatorStyle();
      }, 100);
    }
  };

  /**
    * Delay for expanding folder when pointer hovers on folder in tree view in
    * milliseconds.
    * @type {number}
    * @const
    */
  // TODO(yosin): EXPAND_FOLDER_DELAY should follow system settings. 400ms is
  // taken from Windows default settings.
  var EXPAND_FOLDER_DELAY = 400;

  /**
    * The timestamp when the mouse was over a folder during a drag operation.
    * Used to open the hovered folder after a certain time.
    * @type {number}
    */
  var lastHoverOnFolderTimeStamp = 0;

  /**
    * Expand a folder if the user has hovered for longer than the specified
    * time during a drag action.
    */
  function updateAutoExpander(eventTimeStamp, overElement) {
    // Expands a folder in tree view when pointer hovers on it longer than
    // EXPAND_FOLDER_DELAY.
    var hoverOnFolderTimeStamp = lastHoverOnFolderTimeStamp;
    lastHoverOnFolderTimeStamp = 0;
    if (hoverOnFolderTimeStamp) {
      if (eventTimeStamp - hoverOnFolderTimeStamp >= EXPAND_FOLDER_DELAY)
        overElement.expanded = true;
      else
        lastHoverOnFolderTimeStamp = hoverOnFolderTimeStamp;
    } else if (overElement instanceof TreeItem &&
                bmm.isFolder(overElement.bookmarkNode) &&
                overElement.hasChildren &&
                !overElement.expanded) {
      lastHoverOnFolderTimeStamp = eventTimeStamp;
    }
  }

  /**
    * Stores the information about the bookmark and folders being dragged.
    * @type {Object}
    */
  var dragData = null;
  var dragInfo = {
    handleChromeDragEnter: function(newDragData) {
      dragData = newDragData;
    },
    clearDragData: function() {
      dragData = null;
    },
    isDragValid: function() {
      return !!dragData;
    },
    isSameProfile: function() {
      return dragData && dragData.sameProfile;
    },
    isDraggingFolders: function() {
      return dragData && dragData.elements.some(function(node) {
        return !node.url;
      });
    },
    isDraggingBookmark: function(bookmarkId) {
      return dragData && dragData.elements.some(function(node) {
        return node.id == bookmarkId;
      });
    },
    isDraggingChildBookmark: function(folderId) {
      return dragData && dragData.elements.some(function(node) {
        return node.parentId == folderId;
      });
    },
    isDraggingFolderToDescendant: function(bookmarkNode) {
      return dragData && dragData.elements.some(function(node) {
        var dragFolder = bmm.treeLookup[node.id];
        var dragFolderNode = dragFolder && dragFolder.bookmarkNode;
        return dragFolderNode && bmm.contains(dragFolderNode, bookmarkNode);
      });
    }
  };

  /**
   * External function to select folders or bookmarks after a drop action.
   * @type {function}
   */
  var selectItemsAfterUserAction = null;

  function getBookmarkElement(el) {
    while (el && !el.bookmarkNode) {
      el = el.parentNode;
    }
    return el;
  }

  // If we are over the list and the list is showing search result, we cannot
  // drop.
  function isOverSearch(overElement) {
    return list.isSearch() && list.contains(overElement);
  }

  /**
   * Determines the valid drop positions for the given target element.
   * @param {!HTMLElement} overElement The element that we are currently
   *     dragging over.
   * @return {DropPosition} An bit field enumeration of valid drop locations.
   */
  function calculateValidDropTargets(overElement) {
    if (!dragInfo.isDragValid() || isOverSearch(overElement))
      return DropPosition.NONE;

    if (dragInfo.isSameProfile() &&
        (dragInfo.isDraggingBookmark(overElement.bookmarkNode.id) ||
         dragInfo.isDraggingFolderToDescendant(overElement.bookmarkNode))) {
      return DropPosition.NONE;
    }

    var canDropInfo = calculateDropAboveBelow(overElement);
    if (canDropOn(overElement))
      canDropInfo |= DropPosition.ON;

    return canDropInfo;
  }

  function calculateDropAboveBelow(overElement) {
    if (overElement instanceof BookmarkList)
      return DropPosition.NONE;

    // We cannot drop between Bookmarks bar and Other bookmarks.
    if (overElement.bookmarkNode.parentId == bmm.ROOT_ID)
      return DropPosition.NONE;

    var isOverTreeItem = overElement instanceof TreeItem;
    var isOverExpandedTree = isOverTreeItem && overElement.expanded;
    var isDraggingFolders = dragInfo.isDraggingFolders();

    // We can only drop between items in the tree if we have any folders.
    if (isOverTreeItem && !isDraggingFolders)
      return DropPosition.NONE;

    // When dragging from a different profile we do not need to consider
    // conflicts between the dragged items and the drop target.
    if (!dragInfo.isSameProfile()) {
      // Don't allow dropping below an expanded tree item since it is confusing
      // to the user anyway.
      return isOverExpandedTree ? DropPosition.ABOVE :
                                  (DropPosition.ABOVE | DropPosition.BELOW);
    }

    var resultPositions = DropPosition.NONE;

    // Cannot drop above if the item above is already in the drag source.
    var previousElem = overElement.previousElementSibling;
    if (!previousElem || !dragInfo.isDraggingBookmark(previousElem.bookmarkId))
      resultPositions |= DropPosition.ABOVE;

    // Don't allow dropping below an expanded tree item since it is confusing
    // to the user anyway.
    if (isOverExpandedTree)
      return resultPositions;

    // Cannot drop below if the item below is already in the drag source.
    var nextElement = overElement.nextElementSibling;
    if (!nextElement || !dragInfo.isDraggingBookmark(nextElement.bookmarkId))
      resultPositions |= DropPosition.BELOW;

    return resultPositions;
  }

  /**
   * Determine whether we can drop the dragged items on the drop target.
   * @param {!HTMLElement} overElement The element that we are currently
   *     dragging over.
   * @return {boolean} Whether we can drop the dragged items on the drop
   *     target.
   */
  function canDropOn(overElement) {
    // We can only drop on a folder.
    if (!bmm.isFolder(overElement.bookmarkNode))
      return false;

    if (!dragInfo.isSameProfile())
      return true;

    if (overElement instanceof BookmarkList) {
      // We are trying to drop an item past the last item. This is
      // only allowed if dragged item is different from the last item
      // in the list.
      var listItems = list.items;
      var len = listItems.length;
      if (!len || !dragInfo.isDraggingBookmark(listItems[len - 1].bookmarkId))
        return true;
    }

    return !dragInfo.isDraggingChildBookmark(overElement.bookmarkNode.id);
  }

  /**
   * Callback for the dragstart event.
   * @param {Event} e The dragstart event.
   */
  function handleDragStart(e) {
    // Determine the selected bookmarks.
    var target = e.target;
    var draggedNodes = [];
    if (target instanceof ListItem) {
      // Use selected items.
      draggedNodes = target.parentNode.selectedItems;
    } else if (target instanceof TreeItem) {
      draggedNodes.push(target.bookmarkNode);
    }

    // We manage starting the drag by using the extension API.
    e.preventDefault();

    // Do not allow dragging if there is an ephemeral item being edited at the
    // moment.
    for (var i = 0; i < draggedNodes.length; i++) {
      if (draggedNodes[i].id === 'new')
        return;
    }

    if (draggedNodes.length) {
      // If we are dragging a single link, we can do the *Link* effect.
      // Otherwise, we only allow copy and move.
      e.dataTransfer.effectAllowed = draggedNodes.length == 1 &&
          !bmm.isFolder(draggedNodes[0]) ? 'copyMoveLink' : 'copyMove';

      chrome.bookmarkManagerPrivate.startDrag(draggedNodes.map(function(node) {
        return node.id;
      }));
    }
  }

  function handleDragEnter(e) {
    e.preventDefault();
  }

  /**
   * Calback for the dragover event.
   * @param {Event} e The dragover event.
   */
  function handleDragOver(e) {
    // Allow DND on text inputs.
    if (e.target.tagName != 'INPUT') {
      // The default operation is to allow dropping links etc to do navigation.
      // We never want to do that for the bookmark manager.
      e.preventDefault();

      // Set to none. This will get set to something if we can do the drop.
      e.dataTransfer.dropEffect = 'none';
    }

    if (!dragInfo.isDragValid())
      return;

    var overElement = getBookmarkElement(e.target) ||
                      (e.target == list ? list : null);
    if (!overElement)
      return;

    updateAutoExpander(e.timeStamp, overElement);

    var canDropInfo = calculateValidDropTargets(overElement);
    if (canDropInfo == DropPosition.NONE)
      return;

    // Now we know that we can drop. Determine if we will drop above, on or
    // below based on mouse position etc.

    dropDestination = calcDropPosition(e.clientY, overElement, canDropInfo);
    if (!dropDestination) {
      e.dataTransfer.dropEffect = 'none';
      return;
    }

    e.dataTransfer.dropEffect = dragInfo.isSameProfile() ? 'move' : 'copy';
    dropIndicator.update(dropDestination);
  }

  /**
   * This function determines where the drop will occur relative to the element.
   * @return {?Object} If no valid drop position is found, null, otherwise
   *     an object containing the following parameters:
   *       element - The target element that will receive the drop.
   *       position - A |DropPosition| relative to the |element|.
   */
  function calcDropPosition(elementClientY, overElement, canDropInfo) {
    if (overElement instanceof BookmarkList) {
      // Dropping on the BookmarkList either means dropping below the last
      // bookmark element or on the list itself if it is empty.
      var length = overElement.items.length;
      if (length)
        return {
          element: overElement.getListItemByIndex(length - 1),
          position: DropPosition.BELOW
        };
      return {element: overElement, position: DropPosition.ON};
    }

    var above = canDropInfo & DropPosition.ABOVE;
    var below = canDropInfo & DropPosition.BELOW;
    var on = canDropInfo & DropPosition.ON;
    var rect = overElement.getBoundingClientRect();
    var yRatio = (elementClientY - rect.top) / rect.height;

    if (above && (yRatio <= .25 || yRatio <= .5 && (!below || !on)))
      return {element: overElement, position: DropPosition.ABOVE};
    if (below && (yRatio > .75 || yRatio > .5 && (!above || !on)))
      return {element: overElement, position: DropPosition.BELOW};
    if (on)
      return {element: overElement, position: DropPosition.ON};
    return null;
  }

  function calculateDropInfo(eventTarget, dropDestination) {
    if (!dropDestination || !dragInfo.isDragValid())
      return null;

    var dropPos = dropDestination.position;
    var relatedNode = dropDestination.element.bookmarkNode;
    var dropInfoResult = {
        selectTarget: null,
        selectedTreeId: -1,
        parentId: dropPos == DropPosition.ON ? relatedNode.id :
                                               relatedNode.parentId,
        index: -1,
        relatedIndex: -1
      };

    // Try to find the index in the dataModel so we don't have to always keep
    // the index for the list items up to date.
    var overElement = getBookmarkElement(eventTarget);
    if (overElement instanceof ListItem) {
      dropInfoResult.relatedIndex =
          overElement.parentNode.dataModel.indexOf(relatedNode);
      dropInfoResult.selectTarget = list;
    } else if (overElement instanceof BookmarkList) {
      dropInfoResult.relatedIndex = overElement.dataModel.length - 1;
      dropInfoResult.selectTarget = list;
    } else {
      // Tree
      dropInfoResult.relatedIndex = relatedNode.index;
      dropInfoResult.selectTarget = tree;
      dropInfoResult.selectedTreeId =
          tree.selectedItem ? tree.selectedItem.bookmarkId : null;
    }

    if (dropPos == DropPosition.ABOVE)
      dropInfoResult.index = dropInfoResult.relatedIndex;
    else if (dropPos == DropPosition.BELOW)
      dropInfoResult.index = dropInfoResult.relatedIndex + 1;

    return dropInfoResult;
  }

  function handleDragLeave(e) {
    dropIndicator.finish();
  }

  function handleDrop(e) {
    var dropInfo = calculateDropInfo(e.target, dropDestination);
    if (dropInfo) {
      selectItemsAfterUserAction(dropInfo.selectTarget,
                                 dropInfo.selectedTreeId);
      if (dropInfo.index != -1)
        chrome.bookmarkManagerPrivate.drop(dropInfo.parentId, dropInfo.index);
      else
        chrome.bookmarkManagerPrivate.drop(dropInfo.parentId);

      e.preventDefault();
    }
    dropDestination = null;
    dropIndicator.finish();
  }

  function clearDragData() {
    dragInfo.clearDragData();
    dropDestination = null;
  }

  function init(selectItemsAfterUserActionFunction) {
    function deferredClearData() {
      setTimeout(clearDragData);
    }

    selectItemsAfterUserAction = selectItemsAfterUserActionFunction;

    document.addEventListener('dragstart', handleDragStart);
    document.addEventListener('dragenter', handleDragEnter);
    document.addEventListener('dragover', handleDragOver);
    document.addEventListener('dragleave', handleDragLeave);
    document.addEventListener('drop', handleDrop);
    document.addEventListener('dragend', deferredClearData);
    document.addEventListener('mouseup', deferredClearData);

    chrome.bookmarkManagerPrivate.onDragEnter.addListener(
        dragInfo.handleChromeDragEnter);
    chrome.bookmarkManagerPrivate.onDragLeave.addListener(deferredClearData);
    chrome.bookmarkManagerPrivate.onDrop.addListener(deferredClearData);
  }
  return {init: init};
});
<|endoftext|>


<commit_before>var ANALYSIS_WINDOW_IN_DAYS = 2,
    todaysDate = moment().startOf('day').format('YYYY-MM-DD'),
    begginingOfAnalysisWindow = moment().startOf('day').subtract((ANALYSIS_WINDOW_IN_DAYS*2)-1, 'days').format('YYYY-MM-DD'),

    oldWindow = { 'start': begginingOfAnalysisWindow,
        'end': moment(begginingOfAnalysisWindow).add(ANALYSIS_WINDOW_IN_DAYS, 'd').format('YYYY-MM-DD'),
        'data': []
    },
    recentWindow = { 'start': moment(oldWindow.end).add('1', 'd').format('YYYY-MM-DD'),
        'end': todaysDate,
        'data': []
    };

// Entry point to the entire app.
var start = function(){
	loadData().then(function(data){
		console.log("drs2 ", data);
        // NOTE: split data into two arrays, an event can be opened in oldWindow
		// and closed in recentWindow
        $.map(data, function( entry, i ) {
          if (moment(entry.opened).isBefore(recentWindow.start) ||
            moment(entry.closed).isBefore(recentWindow.start)) {
              oldWindow.data.push(entry);
          } else {
              recentWindow.data.push(entry);
          }
        })
		// oldWindow = today-(2*ANALYSIS_WINDOW_IN_DAYS) to today-ANALYSIS_WINDOW_IN_DAYS
		// recentWindow is today-ANALYSIS_WINDOW_IN_DAYS+1 to today
		// NOTE: DONT DISTRUCT PARAMS PASSED INTO AGGREGATE FUNCTIONS.
		// TODO: DAVID
		aggregatedRecentWindow = aggregateData(recentWindow);
		aggregatedOldWindow = aggregateData(oldWindow);
		console.log('aggregatedOldWindow', aggregatedOldWindow);
		comparisonList = compareWindows(aggregatedRecentWindow, aggregatedOldWindow);
		console.log(comparisonList);
		// compare return must already be sorted.
		// comparisonList = [{agency: "blah ", change: 0.45, newValue: 12},...]
		//TODO: drawChart(comparisonList);
	});
};

/*
	Data: https://data.sfgov.org/City-Infrastructure/Case-Data-from-San-Francisco-311-SF311-/vw6y-z8j6
	Sample JSON API call: https://data.sfgov.org/resource/vw6y-z8j6.json?$limit=2000
	Data Sample in Sheet: https://docs.google.com/spreadsheets/d/1yGd2B7F8mlDg64L-uL3Byuxfxn1irl4ZLd0vETaq2GU/edit#gid=1562030977
	Socrata API docs: https://dev.socrata.com/consumers/getting-started.html
*/
var loadData = function(){
	var filters = "$where=(closed > \'"+begginingOfAnalysisWindow+"\' AND closed < \'"+todaysDate+"\') OR (opened > \'"+begginingOfAnalysisWindow+"\' AND opened < \'"+todaysDate+"\')";

	return $.getJSON('https://data.sfgov.org/resource/vw6y-z8j6.json?$limit=50000&'+filters, function(data){
		console.log('data', data);
		// do anything we want....but try to leave agregating out of the load function.
	});
};

// returns [{responsible_agency: "blah ", change: 0.45, newValue: 12},...]
var compareWindows = function(recentW, oldW){
	var comparisonList = [];
	_.each(recentW, function(data, agencyName){
		var oldRate = oldW[agencyName].closed;
		var recentRate = recentW[agencyName].closed;
		comparisonList.push({responsible_agency: agencyName,
			changeInCloseRate: recentRate - oldRate,
			recentCloseRate: recentRate,
			oldCloseRate: oldRate})
	});

	_.sortBy(comparisonList, function(agency){ return agency.changeInCloseRate; });

	return comparisonList;
}

// Aggregate data to the responsible agency.
// TODO3: instead of always doing responsible agency, make aggregateable key a param.
var aggregateData = function(dataWindow) {
	var STATUS_CLOSED = "Closed";
	// format: {"agency Name blah": {responsible_agency: "blah", newlyOpened:123, closed:456}}
	var aggregatedData = {};

	dataWindow.data.forEach(function(d){
		var agency = d.responsible_agency;
		// first time we see this agency
		if(!aggregatedData[agency]){
			aggregatedData[agency] = {responsible_agency: agency, newlyOpened: 0, closed: 0};
		}
		if (moment(d.closed).isBetween(dataWindow.start, dataWindow.end)) {
			aggregatedData[agency].closed++;
		}
		if (moment(d.opened).isBetween(dataWindow.end, dataWindow.end)){ // TODO: use moment to check if it isInNowWindow
			aggregatedData[agency].newlyOpened++;
		}
	});

	return aggregatedData;
}

var drawChart = function(comparisonList){
	comparisonList.forEach(function(agency){
		$('#leaderboard').add("
" + agency.responsible_agency + "  " + agency.changeInCloseRate + "
")
	});

};

// ===== nick write below here

start();
<commit_msg>add filter<commit_after>var ANALYSIS_WINDOW_IN_DAYS = 2,
    todaysDate = moment().startOf('day').format('YYYY-MM-DD'),
    begginingOfAnalysisWindow = moment().startOf('day').subtract((ANALYSIS_WINDOW_IN_DAYS*2)-1, 'days').format('YYYY-MM-DD'),

    oldWindow = { 'start': begginingOfAnalysisWindow,
        'end': moment(begginingOfAnalysisWindow).add(ANALYSIS_WINDOW_IN_DAYS, 'd').format('YYYY-MM-DD'),
        'data': []
    },
    recentWindow = { 'start': moment(oldWindow.end).add('1', 'd').format('YYYY-MM-DD'),
        'end': todaysDate,
        'data': []
    };

// Entry point to the entire app.
var start = function(){
	loadData().then(function(data){
		console.log("drs2 ", data);
        // NOTE: split data into two arrays, an event can be opened in oldWindow
		// and closed in recentWindow
        $.map(data, function( entry, i ) {
          if (moment(entry.opened).isBefore(recentWindow.start) ||
            moment(entry.closed).isBefore(recentWindow.start)) {
              oldWindow.data.push(entry);
          } else {
              recentWindow.data.push(entry);
          }
        })
		// oldWindow = today-(2*ANALYSIS_WINDOW_IN_DAYS) to today-ANALYSIS_WINDOW_IN_DAYS
		// recentWindow is today-ANALYSIS_WINDOW_IN_DAYS+1 to today
		// NOTE: DONT DISTRUCT PARAMS PASSED INTO AGGREGATE FUNCTIONS.
		// TODO: DAVID
		aggregatedRecentWindow = aggregateData(recentWindow);
		aggregatedOldWindow = aggregateData(oldWindow);
		console.log('aggregatedOldWindow', aggregatedOldWindow);
		comparisonList = compareWindows(aggregatedRecentWindow, aggregatedOldWindow);
		console.log(comparisonList);
		// compare return must already be sorted.
		// comparisonList = [{agency: "blah ", change: 0.45, newValue: 12},...]
		comparisonList = filterComparisonData(filterComparisonData);
		drawChart(comparisonList);
	});
};

/*
	Data: https://data.sfgov.org/City-Infrastructure/Case-Data-from-San-Francisco-311-SF311-/vw6y-z8j6
	Sample JSON API call: https://data.sfgov.org/resource/vw6y-z8j6.json?$limit=2000
	Data Sample in Sheet: https://docs.google.com/spreadsheets/d/1yGd2B7F8mlDg64L-uL3Byuxfxn1irl4ZLd0vETaq2GU/edit#gid=1562030977
	Socrata API docs: https://dev.socrata.com/consumers/getting-started.html
*/
var loadData = function(){
	var filters = "$where=(closed > \'"+begginingOfAnalysisWindow+"\' AND closed < \'"+todaysDate+"\') OR (opened > \'"+begginingOfAnalysisWindow+"\' AND opened < \'"+todaysDate+"\')";

	return $.getJSON('https://data.sfgov.org/resource/vw6y-z8j6.json?$limit=50000&'+filters, function(data){
		console.log('data', data);
		// do anything we want....but try to leave agregating out of the load function.
	});
};

// returns [{responsible_agency: "blah ", change: 0.45, newValue: 12},...]
var compareWindows = function(recentW, oldW){
	var comparisonList = [];
	_.each(recentW, function(data, agencyName){
		var oldRate = oldW[agencyName].closed;
		var recentRate = recentW[agencyName].closed;
		comparisonList.push({responsible_agency: agencyName,
			changeInCloseRate: recentRate - oldRate,
			recentCloseRate: recentRate,
			oldCloseRate: oldRate})
	});

	_.sortBy(comparisonList, function(agency){ return agency.changeInCloseRate; });

	return comparisonList;
}

// Aggregate data to the responsible agency.
// TODO3: instead of always doing responsible agency, make aggregateable key a param.
var aggregateData = function(dataWindow) {
	var STATUS_CLOSED = "Closed";
	// format: {"agency Name blah": {responsible_agency: "blah", newlyOpened:123, closed:456}}
	var aggregatedData = {};

	dataWindow.data.forEach(function(d){
		var agency = d.responsible_agency;
		// first time we see this agency
		if(!aggregatedData[agency]){
			aggregatedData[agency] = {responsible_agency: agency, newlyOpened: 0, closed: 0};
		}
		if (moment(d.closed).isBetween(dataWindow.start, dataWindow.end)) {
			aggregatedData[agency].closed++;
		}
		if (moment(d.opened).isBetween(dataWindow.end, dataWindow.end)){ // TODO: use moment to check if it isInNowWindow
			aggregatedData[agency].newlyOpened++;
		}
	});

	return aggregatedData;
}

var filterComparisonData = function(comparisonList){
	var MIN_CLOSES = 10;
	var newList = _.reject(comparisonList, function(item){
		return item.recentCloseRate < MIN_CLOSES || item.oldCloseRate < MIN_CLOSES;
	});
	return newList;
};

var drawChart = function(comparisonList){
	comparisonList.forEach(function(agency){
		$('#leaderboard').add("
" + agency.responsible_agency + "  " + agency.changeInCloseRate + "
")
	});

};

// ===== nick write below here

start();
<|endoftext|>


<commit_before>/**
 * Resume/CV template created with Bootstrap 5 by @vmoratog and @jdnichollsc
 */
const resume = {
  firstName: 'Thusitha',
  lastName: 'Jayalath',
  jobTitle: 'Full Stack Engineer',
  city: 'Kandy',
  postalCode: '20800',
  country: 'Sri Lanka',
  phone: '+94776682277',
  email: 'thusitha.4t@gmail.com',
  education: [
    {
      school: 'University of Colombo School of Computing',
      degree: 'Bachelor',
      graduationDate: '2017',
      description: 'Computer Science'
    }
  ],
  links: [
    {
      label: 'GitHub',
      link: 'https://github.com/thuyiya'
    },
    {
      label: 'LinkedIn',
      link: 'https://www.linkedin.com/in/thuyiya/'
    },
    {
      label: 'Website',
      link: 'https://thuyiya.github.io/profile'
    }
  ],
  skills: [
    'IOS',
    'Swift',
    'ReactJS',
    'React Native',
    'Ionic',
    'Angular',
    'C#',
    'JSX',
    'PHP',
    'HTML5',
    'CSS',
    'SASS',
    'JSS',
    'JavaScript',
    'TypeScript',
    'NodeJS/NestJS',
    'GCP',
    'Vue'
  ],
  databses: [
    'PostgreSQL',
    'MongoDB',
    'SQL',
    'Core Data',
    'SQLight',
    'Redis'
  ],
  tools: [
    'Loopback',
    'PM2',
    'NGINX',
    'Docker',
    'App-Center',
    'Heroku',
    'Webpack',
    'OneSignal',
    'ElectronJS',
    'Jest',
    'Mocha',
    'GraphQL',
    'Redux',
    'Agile',
    'Flux',
    'NPM',
    'Enzyme',
    'D3',
    'Firebase',
    'VSCode',
    'Unity3D',
    'Xcode',
    'Android Studio',
    'Bootstrap',
    'MUI',
    'Vue'
  ],
  referees: [
    {
      name: 'SHOHEI FUJIMORI',
      description: 'CEO | Asia Capital',
    },
    {
      name: 'SUJEEV RAJAKULENDRAN',
      description: 'CEO | EshvaTechq',
    }
  ],
  projects: [
    {
      title: 'Zonein',
      time: 'DEC.2019 - Present',
      role: 'Full-Stack Engineer',
      images: ['https://raw.githubusercontent.com/thuyiya/profile/main/assets/zonein/cover-zonein.png'],
      domain: '',
      products: [{
        title: 'Landing Page',
        link: 'https://getzonein.com',
        type: 'LANDING'
      },{
        title: 'Web Dashboard',
        link: 'https://dash.zoneinpro.com',
        type: 'CHEF, ADMIN, DIETITIAN, ANALYTIC'
      }],
      system: 'ZoneIn is the performance nutrition management platform for sports dietitians and their athletes. Leading comanies Like EXOS, NBA NFL Teams is client of zonein.',
      tech_tools: ['ReactJS', 'React Native', 'NodeJs', 'Loopback', 'Aws', 'MongoDB', 'IOS', 'Android', 'Swift', 'Stripe', 'Heroku', 'Firebase', 'Appcenter', 'NutritionX', 'GCP', 'OneSignal', 'Bixilon', 'Java', 'SocketIO', 'PM2', 'Nginx', 'Redis']
    },
    {
      title: 'Autohelp',
      time: 'APRIL.2018',
      role: 'Full-Stack Engineer',
      images: ['https://raw.githubusercontent.com/thuyiya/profile/main/assets/autohelp/cover-autohelp.png'],
      domain: '',
      products: [{
        title: 'Landing Page',
        link: 'https://autohelp.se',
        type: 'LANDING'
      },{
        title: 'Play Store',
        link: 'https://play.google.com/store/apps/details?id=com.autohelp_mobile',
        type: 'Android App'
      }],
      system: 'AutoHelp is a service platform for vehicle owners in the Nordic region. They can create ad and Others can help for it using simple request.',
      tech_tools: ['ReactJS', 'React Native', 'NodeJs', 'ExpressJs', 'Azure', 'MongoDB', 'IOS', 'Android', 'Swift', 'Stripe', 'Heroku', 'Firebase', 'Java', 'SocketIO', 'PM2', 'Nginx', 'Redis', 'Cloudinary']
    },
    {
      title: 'RumbleOn',
      time: 'AUG.2018 - SEP.2019',
      role: 'Senior Engineer',
      images: ['https://raw.githubusercontent.com/thuyiya/profile/main/assets/autohelp/cover-rumbelon.jpeg'],
      domain: '',
      products: [{
        title: 'Landing Page',
        link: 'https://www.rumbleon.com',
        type: 'LANDING'
      },{
        title: 'Play Store',
        link: 'https://play.google.com/store/apps/details?id=com.rumbleon.app&hl=en&gl=US',
        type: 'Android App'
      }],
      system: 'Disruptive e-commerce platform that facilitates the ability for both consumers and dealers to Buy-Sell-Trade-Finance all pre-owned recreation vehicles ',
      tech_tools: ['ReactJS', 'React Native', 'NodeJs', 'NextJS', 'Azure', 'Typescript', 'Javascript', 'Android', 'Swift', 'Java', 'PM2', 'Nginx', 'ElasticSearch', 'AWS']
    }
  ],

  languages: ['English', 'Sinhala'],
  professionalSummary: `Developer with a background in web, mobile and game development, having 5+ years of practice and leadership building interactive experiences.
    Great enthusiastic teamwork, considered helpful, creative, assertive and cautious.`,
  employmentHistory: [
    {
      jobTitle: 'Team Lead / Full-Stack Engineer',
      startDate: 'MAY 2020',
      endDate: 'Present',
      employer: 'Zonein',
      city: 'New York',
      achievements: [
        'Created the baseline for the development of reactive apps using new technologies such as ReactJS and GraphQL for rapid prototyping.',
        'Working with major system Updates (Native IOS to React Native, Loopback, ReactJS)',
        'Distribute data layer via mutiple databases and Load balancing for backend',
        'Handle Different features via servise layer',
        'Performance inreese for databases (Mongodb)',
        'Introduce caching system and analytics system for better perfomance',
        'Configurations of cloud infrastructure in AWS, Configuring automated CI/CD pipelines, Multi regional hosting, Enabling monitoring, Rollback feature',
        'UI optimization for fast loading',
        'Make application HIPAA Compliance',
        'Security enhancement for client requirements',
        'Build component package library and introduce new methods for fast deliveries',
        'New Developers Traning and Requirements'
      ]
    },
    {
      jobTitle: 'Consultant and Team Leader',
      startDate: 'JAN 2021',
      endDate: 'APR 2021',
      employer: 'NEMAS, AB',
      city: 'Sweden',
      achievements: [
        'Manage the team to learn more technologies the development and industrial experience for enhance and achieve their targets',
        'Contributed to configure continuous integration pipelines for new projects through firebase. Help to setup testflight and app center for push code'
      ]
    },
    {
      jobTitle: 'Frontend Lead',
      startDate: 'Nov 2019',
      endDate: 'May 2020',
      employer: 'Orvero/ Austal',
      city: 'Australia (Remote)',
      achievements:[
        'Build component library as base project and visualize them using storybook',
        'Train developers for Unit Testing with Enzyme and Jest.',
        'Build various type of chart for help airforce to make data visualization easy with D3.',
        'Experience with common front-end development tools such as Babel, Webpack, Grunt, Gulp, NPM etc.',
        'Enhance and improve the usability and accessibility, and also improve the rendering capability with Server side rendering for charts',
        'Convert Old Javascript code to TypeScript',
        'Increase performance using Profiler in the React DevTools',
        'Prevent and stop unnecessary re-rendering',
        'Development was done on agile methodology',
        'Review code developed by other developers and provide feedback to ensure best practices'
      ]
    },
    {
      jobTitle: 'Senior Software Engineer',
      startDate: 'Sep 2018',
      endDate: 'Nov 2019',
      employer: 'Esshva Techq - RumbleOn',
      city: 'USA',
      achievements: [
        'Markup data api for market value of the vehicles.',
        'Dealership connector - Build a platform that connect, all of the Rumbleon system connect with dealership.',
        'Maintain Mobile application in IOS and update the search feature with Elasticsearch search.',
        'Server side rendering for charts for Rumbelon analytics',
        'Create package for form steps'
      ]
    },
    {
      jobTitle: 'Guest Lecturer',
      startDate: 'Apr 2017',
      endDate: 'Present',
      employer: 'University Of colombo, NIBM',
      city: 'Sri Lanka',
      achievements: [
        'NIBM - IOS Development, Create content and assessment for 4th year students.',
        'Create content for student learn new technologies help student to understand the industrial way.',
        'UCSC - Adwance Web Development (Teach them about React, Angular)',
        'UCSC - Cross Plaform development, Build software product from one source to different platforms'
      ]
    },
    {
      jobTitle: 'Senior Software Engineer',
      startDate: 'Feb 2017',
      endDate: 'Aug 2018',
      employer: 'Crowderia',
      city: 'Sweden',
      achievements: [
        'Leading Frontend and backend team of Grade5 project.',
        'Implementation RESTful architecture and Push notification setup with OneSignal.',
        'Build Native IOS application for Grade5, Hoster, and Cokeep Projects',
        'Convert Native IOS application to IONIC 2 (Grade5)',
        'Worked closely with UX and Product Management to develop friendly UI experiences',
        'Experienced working in an Agile environment (e.g. Scrum, Kanban or ScrumBan)',
        'Optimize application for maximum speed and scalability',
        'Convert wordpress project to mobile application using react native and ionic',
        'Traning developers cross-browser compatibility issues and ways to work around them',
        'Follow web accessibility guidelines',
        'Experience with common front-end development tools such as Babel, Webpack, Grunt, Gulp, NPM etc.',
        'Organize event and trip and sport days in the company',
        'Start Crowd Lab, and help new recruits to clean language concept other than their day to day work'
      ]
    },
    {
      jobTitle: 'Intern, Software Engineer',
      startDate: 'AUG 2016',
      endDate: 'Jun 2017',
      employer: 'Crowderia',
      city: 'Sweden',
      achievements: [
        `Work with seniors in Game development, mobile development and web technologies.`,
        'Participate with client and technical meetings to learn high level idea of the project and concepts',
        'Directly work with CTO and QA Lead for learn company products',
        'Join with company open source team'
      ]
    },
    {
      jobTitle: 'Game Developer',
      startDate: 'Jul 2014',
      endDate: 'Dec 2014',
      employer: 'GCBlock',
      city: 'Colombo',
      achievements: [
        'Build game asset using blender and photoshop',
        'Deploy mobile game to mobile platform',
        'Program next level user experience other than joystick mechanism for drive a car'
      ]
    }
  ],
  photo: 'https://raw.githubusercontent.com/thuyiya/profile/main/profile.png',
}

const formatResume = (r) => ({
  ...r,
  address: [
    r.country,
    r.city,
    r.postalCode
  ].filter(Boolean).join(', ')
})

new Vue({
  el: "#app",
  data: formatResume(resume)
});

/**
 * Wait for animatable-component to be loaded (Only for VanillaJS)
 **/
function animatableLoaded() {
  document.querySelector('body').classList.remove('d-none');
}
if (customElements) {
  customElements.whenDefined('animatable-component').then(animatableLoaded);
} else animatableLoaded()<commit_msg>rumbelon<commit_after>/**
 * Resume/CV template created with Bootstrap 5 by @vmoratog and @jdnichollsc
 */
const resume = {
  firstName: 'Thusitha',
  lastName: 'Jayalath',
  jobTitle: 'Full Stack Engineer',
  city: 'Kandy',
  postalCode: '20800',
  country: 'Sri Lanka',
  phone: '+94776682277',
  email: 'thusitha.4t@gmail.com',
  education: [
    {
      school: 'University of Colombo School of Computing',
      degree: 'Bachelor',
      graduationDate: '2017',
      description: 'Computer Science'
    }
  ],
  links: [
    {
      label: 'GitHub',
      link: 'https://github.com/thuyiya'
    },
    {
      label: 'LinkedIn',
      link: 'https://www.linkedin.com/in/thuyiya/'
    },
    {
      label: 'Website',
      link: 'https://thuyiya.github.io/profile'
    }
  ],
  skills: [
    'IOS',
    'Swift',
    'ReactJS',
    'React Native',
    'Ionic',
    'Angular',
    'C#',
    'JSX',
    'PHP',
    'HTML5',
    'CSS',
    'SASS',
    'JSS',
    'JavaScript',
    'TypeScript',
    'NodeJS/NestJS',
    'GCP',
    'Vue'
  ],
  databses: [
    'PostgreSQL',
    'MongoDB',
    'SQL',
    'Core Data',
    'SQLight',
    'Redis'
  ],
  tools: [
    'Loopback',
    'PM2',
    'NGINX',
    'Docker',
    'App-Center',
    'Heroku',
    'Webpack',
    'OneSignal',
    'ElectronJS',
    'Jest',
    'Mocha',
    'GraphQL',
    'Redux',
    'Agile',
    'Flux',
    'NPM',
    'Enzyme',
    'D3',
    'Firebase',
    'VSCode',
    'Unity3D',
    'Xcode',
    'Android Studio',
    'Bootstrap',
    'MUI',
    'Vue'
  ],
  referees: [
    {
      name: 'SHOHEI FUJIMORI',
      description: 'CEO | Asia Capital',
    },
    {
      name: 'SUJEEV RAJAKULENDRAN',
      description: 'CEO | EshvaTechq',
    }
  ],
  projects: [
    {
      title: 'Zonein',
      time: 'DEC.2019 - Present',
      role: 'Full-Stack Engineer',
      images: ['https://raw.githubusercontent.com/thuyiya/profile/main/assets/zonein/cover-zonein.png'],
      domain: '',
      products: [{
        title: 'Landing Page',
        link: 'https://getzonein.com',
        type: 'LANDING'
      },{
        title: 'Web Dashboard',
        link: 'https://dash.zoneinpro.com',
        type: 'CHEF, ADMIN, DIETITIAN, ANALYTIC'
      }],
      system: 'ZoneIn is the performance nutrition management platform for sports dietitians and their athletes. Leading comanies Like EXOS, NBA NFL Teams is client of zonein.',
      tech_tools: ['ReactJS', 'React Native', 'NodeJs', 'Loopback', 'Aws', 'MongoDB', 'IOS', 'Android', 'Swift', 'Stripe', 'Heroku', 'Firebase', 'Appcenter', 'NutritionX', 'GCP', 'OneSignal', 'Bixilon', 'Java', 'SocketIO', 'PM2', 'Nginx', 'Redis']
    },
    {
      title: 'Autohelp',
      time: 'APRIL.2018',
      role: 'Full-Stack Engineer',
      images: ['https://raw.githubusercontent.com/thuyiya/profile/main/assets/autohelp/cover-autohelp.png'],
      domain: '',
      products: [{
        title: 'Landing Page',
        link: 'https://autohelp.se',
        type: 'LANDING'
      },{
        title: 'Play Store',
        link: 'https://play.google.com/store/apps/details?id=com.autohelp_mobile',
        type: 'Android App'
      }],
      system: 'AutoHelp is a service platform for vehicle owners in the Nordic region. They can create ad and Others can help for it using simple request.',
      tech_tools: ['ReactJS', 'React Native', 'NodeJs', 'ExpressJs', 'Azure', 'MongoDB', 'IOS', 'Android', 'Swift', 'Stripe', 'Heroku', 'Firebase', 'Java', 'SocketIO', 'PM2', 'Nginx', 'Redis', 'Cloudinary']
    },
    {
      title: 'RumbleOn',
      time: 'AUG.2018 - SEP.2019',
      role: 'Senior Engineer',
      images: ['https://raw.githubusercontent.com/thuyiya/profile/main/assets/rumbelon/cover-rumbelon.jpeg'],
      domain: '',
      products: [{
        title: 'Landing Page',
        link: 'https://www.rumbleon.com',
        type: 'LANDING'
      },{
        title: 'Play Store',
        link: 'https://play.google.com/store/apps/details?id=com.rumbleon.app&hl=en&gl=US',
        type: 'Android App'
      }],
      system: 'Disruptive e-commerce platform that facilitates the ability for both consumers and dealers to Buy-Sell-Trade-Finance all pre-owned recreation vehicles ',
      tech_tools: ['ReactJS', 'React Native', 'NodeJs', 'NextJS', 'Azure', 'Typescript', 'Javascript', 'Android', 'Swift', 'Java', 'PM2', 'Nginx', 'ElasticSearch', 'AWS']
    }
  ],

  languages: ['English', 'Sinhala'],
  professionalSummary: `Developer with a background in web, mobile and game development, having 5+ years of practice and leadership building interactive experiences.
    Great enthusiastic teamwork, considered helpful, creative, assertive and cautious.`,
  employmentHistory: [
    {
      jobTitle: 'Team Lead / Full-Stack Engineer',
      startDate: 'MAY 2020',
      endDate: 'Present',
      employer: 'Zonein',
      city: 'New York',
      achievements: [
        'Created the baseline for the development of reactive apps using new technologies such as ReactJS and GraphQL for rapid prototyping.',
        'Working with major system Updates (Native IOS to React Native, Loopback, ReactJS)',
        'Distribute data layer via mutiple databases and Load balancing for backend',
        'Handle Different features via servise layer',
        'Performance inreese for databases (Mongodb)',
        'Introduce caching system and analytics system for better perfomance',
        'Configurations of cloud infrastructure in AWS, Configuring automated CI/CD pipelines, Multi regional hosting, Enabling monitoring, Rollback feature',
        'UI optimization for fast loading',
        'Make application HIPAA Compliance',
        'Security enhancement for client requirements',
        'Build component package library and introduce new methods for fast deliveries',
        'New Developers Traning and Requirements'
      ]
    },
    {
      jobTitle: 'Consultant and Team Leader',
      startDate: 'JAN 2021',
      endDate: 'APR 2021',
      employer: 'NEMAS, AB',
      city: 'Sweden',
      achievements: [
        'Manage the team to learn more technologies the development and industrial experience for enhance and achieve their targets',
        'Contributed to configure continuous integration pipelines for new projects through firebase. Help to setup testflight and app center for push code'
      ]
    },
    {
      jobTitle: 'Frontend Lead',
      startDate: 'Nov 2019',
      endDate: 'May 2020',
      employer: 'Orvero/ Austal',
      city: 'Australia (Remote)',
      achievements:[
        'Build component library as base project and visualize them using storybook',
        'Train developers for Unit Testing with Enzyme and Jest.',
        'Build various type of chart for help airforce to make data visualization easy with D3.',
        'Experience with common front-end development tools such as Babel, Webpack, Grunt, Gulp, NPM etc.',
        'Enhance and improve the usability and accessibility, and also improve the rendering capability with Server side rendering for charts',
        'Convert Old Javascript code to TypeScript',
        'Increase performance using Profiler in the React DevTools',
        'Prevent and stop unnecessary re-rendering',
        'Development was done on agile methodology',
        'Review code developed by other developers and provide feedback to ensure best practices'
      ]
    },
    {
      jobTitle: 'Senior Software Engineer',
      startDate: 'Sep 2018',
      endDate: 'Nov 2019',
      employer: 'Esshva Techq - RumbleOn',
      city: 'USA',
      achievements: [
        'Markup data api for market value of the vehicles.',
        'Dealership connector - Build a platform that connect, all of the Rumbleon system connect with dealership.',
        'Maintain Mobile application in IOS and update the search feature with Elasticsearch search.',
        'Server side rendering for charts for Rumbelon analytics',
        'Create package for form steps'
      ]
    },
    {
      jobTitle: 'Guest Lecturer',
      startDate: 'Apr 2017',
      endDate: 'Present',
      employer: 'University Of colombo, NIBM',
      city: 'Sri Lanka',
      achievements: [
        'NIBM - IOS Development, Create content and assessment for 4th year students.',
        'Create content for student learn new technologies help student to understand the industrial way.',
        'UCSC - Adwance Web Development (Teach them about React, Angular)',
        'UCSC - Cross Plaform development, Build software product from one source to different platforms'
      ]
    },
    {
      jobTitle: 'Senior Software Engineer',
      startDate: 'Feb 2017',
      endDate: 'Aug 2018',
      employer: 'Crowderia',
      city: 'Sweden',
      achievements: [
        'Leading Frontend and backend team of Grade5 project.',
        'Implementation RESTful architecture and Push notification setup with OneSignal.',
        'Build Native IOS application for Grade5, Hoster, and Cokeep Projects',
        'Convert Native IOS application to IONIC 2 (Grade5)',
        'Worked closely with UX and Product Management to develop friendly UI experiences',
        'Experienced working in an Agile environment (e.g. Scrum, Kanban or ScrumBan)',
        'Optimize application for maximum speed and scalability',
        'Convert wordpress project to mobile application using react native and ionic',
        'Traning developers cross-browser compatibility issues and ways to work around them',
        'Follow web accessibility guidelines',
        'Experience with common front-end development tools such as Babel, Webpack, Grunt, Gulp, NPM etc.',
        'Organize event and trip and sport days in the company',
        'Start Crowd Lab, and help new recruits to clean language concept other than their day to day work'
      ]
    },
    {
      jobTitle: 'Intern, Software Engineer',
      startDate: 'AUG 2016',
      endDate: 'Jun 2017',
      employer: 'Crowderia',
      city: 'Sweden',
      achievements: [
        `Work with seniors in Game development, mobile development and web technologies.`,
        'Participate with client and technical meetings to learn high level idea of the project and concepts',
        'Directly work with CTO and QA Lead for learn company products',
        'Join with company open source team'
      ]
    },
    {
      jobTitle: 'Game Developer',
      startDate: 'Jul 2014',
      endDate: 'Dec 2014',
      employer: 'GCBlock',
      city: 'Colombo',
      achievements: [
        'Build game asset using blender and photoshop',
        'Deploy mobile game to mobile platform',
        'Program next level user experience other than joystick mechanism for drive a car'
      ]
    }
  ],
  photo: 'https://raw.githubusercontent.com/thuyiya/profile/main/profile.png',
}

const formatResume = (r) => ({
  ...r,
  address: [
    r.country,
    r.city,
    r.postalCode
  ].filter(Boolean).join(', ')
})

new Vue({
  el: "#app",
  data: formatResume(resume)
});

/**
 * Wait for animatable-component to be loaded (Only for VanillaJS)
 **/
function animatableLoaded() {
  document.querySelector('body').classList.remove('d-none');
}
if (customElements) {
  customElements.whenDefined('animatable-component').then(animatableLoaded);
} else animatableLoaded()<|endoftext|>


<commit_before>describe('current date', function() {

	var TITLE_FORMAT = 'MMMM D YYYY';
	var options;

	beforeEach(function() {
		affix('#cal');
		options = {
			titleFormat: TITLE_FORMAT,
			titleRangeSeparator: ' - ',
			defaultDate: '2014-06-01'
		};
	});

	describe('defaultDate & getDate', function() { // keep getDate
		describeWhenInMonth(function() {
			it('should initialize at the date', function() {
				options.defaultDate = '2011-03-10';
				$('#cal').fullCalendar(options);
				expectViewDates('2011-02-27', '2011-04-10', '2011-03-01', '2011-04-01');
				var currentDate = $('#cal').fullCalendar('getDate');
				expect(moment.isMoment(currentDate)).toEqual(true); // test the type, but only here
				expect(currentDate).toEqualMoment('2011-03-10');
			});
		});
		describeWhenInWeek(function() {
			it('should initialize at the date, given a date string', function() {
				options.defaultDate = '2011-03-10';
				$('#cal').fullCalendar(options);
				expectViewDates('2011-03-06', '2011-03-13');
				expect($('#cal').fullCalendar('getDate')).toEqualMoment('2011-03-10');
			});
			it('should initialize at the date, given a Moment object', function() {
				options.defaultDate = $.fullCalendar.moment('2011-03-10');
				$('#cal').fullCalendar(options);
				expectViewDates('2011-03-06', '2011-03-13');
				expect($('#cal').fullCalendar('getDate')).toEqualMoment('2011-03-10');
			});
		});
		describeWhenInDay(function() {
			it('should initialize at the date', function() {
				options.defaultDate = '2011-03-10';
				$('#cal').fullCalendar(options);
				expectViewDates('2011-03-10');
				expect($('#cal').fullCalendar('getDate')).toEqualMoment('2011-03-10');
			});
		});
	});

	describe('gotoDate', function() {
		describeWhenInMonth(function() {
			it('should go to a date when given a date string', function() {
				$('#cal').fullCalendar(options);
				$('#cal').fullCalendar('gotoDate', '2015-04-01');
				expectViewDates('2015-03-29', '2015-05-10', '2015-04-01', '2015-05-01');
			});
		});
		describeWhenInWeek(function() {
			it('should go to a date when given a date string', function() {
				$('#cal').fullCalendar(options);
				$('#cal').fullCalendar('gotoDate', '2015-04-01');
				expectViewDates('2015-03-29', '2015-04-05');
			});
			it('should go to a date when given a date string with a time', function() {
				$('#cal').fullCalendar(options);
				$('#cal').fullCalendar('gotoDate', '2015-04-01T12:00:00');
				expectViewDates('2015-03-29', '2015-04-05');
			});
			it('should go to a date when given a moment object', function() {
				$('#cal').fullCalendar(options);
				$('#cal').fullCalendar('gotoDate', $.fullCalendar.moment('2015-04-01'));
				expectViewDates('2015-03-29', '2015-04-05');
			});
		});
		describeWhenInDay(function() {
			it('should go to a date when given a date string', function() {
				$('#cal').fullCalendar(options);
				$('#cal').fullCalendar('gotoDate', '2015-04-01');
				expectViewDates('2015-04-01');
			});
		});
	});

	describe('incrementDate', function() {
		describeWhenInMonth(function() {
			it('should increment the date when given a Duration object', function() {
				$('#cal').fullCalendar(options);
				$('#cal').fullCalendar('incrementDate', { months: -1 });
				expectViewDates('2014-04-27', '2014-06-08', '2014-05-01', '2014-06-01');
			});
		});
		describeWhenInWeek(function() {
			it('should increment the date when given a Duration object', function() {
				$('#cal').fullCalendar(options);
				$('#cal').fullCalendar('incrementDate', { weeks: -2 });
				expectViewDates('2014-05-18', '2014-05-25');
			});
		});
		describeWhenInDay(function() {
			it('should increment the date when given a Duration object', function() {
				$('#cal').fullCalendar(options);
				$('#cal').fullCalendar('incrementDate', { days: 2 });
				expectViewDates('2014-06-03');
			});
			it('should increment the date when given a Duration string', function() {
				$('#cal').fullCalendar(options);
				$('#cal').fullCalendar('incrementDate', '2.00:00:00');
				expectViewDates('2014-06-03');
			});
			it('should increment the date when given a Duration string with a time', function() {
				$('#cal').fullCalendar(options);
				$('#cal').fullCalendar('incrementDate', '2.05:30:00');
				expectViewDates('2014-06-03');
			});
		});
	});

	describe('prev', function() {
		describeWhenInMonth(function() {
			it('should move the calendar back a month', function() {
				$('#cal').fullCalendar(options);
				$('#cal').fullCalendar('prev');
				expectViewDates('2014-04-27', '2014-06-08', '2014-05-01', '2014-06-01');
			});
		});
		describeWhenInWeek(function() {
			it('should move the calendar back a week', function() {
				$('#cal').fullCalendar(options);
				$('#cal').fullCalendar('prev');
				expectViewDates('2014-05-25', '2014-06-01');
			});
		});
		describeWhenInDay(function() {
			it('should move the calendar back a week', function() {
				$('#cal').fullCalendar(options);
				$('#cal').fullCalendar('prev');
				expectViewDates('2014-05-31');
			});
		});
	});

	describe('next', function() {
		describeWhenInMonth(function() {
			it('should move the calendar forward a month', function() {
				$('#cal').fullCalendar(options);
				$('#cal').fullCalendar('next');
				expectViewDates('2014-06-29', '2014-08-10', '2014-07-01', '2014-08-01');
			});
		});
		describeWhenInWeek(function() {
			it('should move the calendar forward a week', function() {
				$('#cal').fullCalendar(options);
				$('#cal').fullCalendar('next');
				expectViewDates('2014-06-08', '2014-06-15');
			});
		});
		describeWhenInDay(function() {
			it('should move the calendar forward a week', function() {
				$('#cal').fullCalendar(options);
				$('#cal').fullCalendar('next');
				expectViewDates('2014-06-02');
			});
		});
	});

	describe('prevYear', function() {
		describeWhenInMonth(function() {
			it('should move the calendar back a year', function() {
				$('#cal').fullCalendar(options);
				$('#cal').fullCalendar('prevYear');
				expectViewDates('2013-05-26', '2013-07-07', '2013-06-01', '2013-07-01');
			});
		});
		describeWhenInWeek(function() {
			it('should move the calendar back a year', function() {
				$('#cal').fullCalendar(options);
				$('#cal').fullCalendar('prevYear');
				expectViewDates('2013-05-26', '2013-06-02');
			});
		});
		describeWhenInDay(function() {
			it('should move the calendar back a year', function() {
				$('#cal').fullCalendar(options);
				$('#cal').fullCalendar('prevYear');
				expectViewDates('2013-06-01');
			});
		});
	});

	describe('nextYear', function() {
		describeWhenInMonth(function() {
			it('should move the calendar forward a year', function() {
				$('#cal').fullCalendar(options);
				$('#cal').fullCalendar('nextYear');
				expectViewDates('2015-05-31', '2015-07-12', '2015-06-01', '2015-07-01');
			});
		});
		describeWhenInWeek(function() {
			it('should move the calendar forward a year', function() {
				$('#cal').fullCalendar(options);
				$('#cal').fullCalendar('nextYear');
				expectViewDates('2015-05-31', '2015-06-07');
			});
		});
		describeWhenInDay(function() {
			it('should move the calendar forward a year', function() {
				$('#cal').fullCalendar(options);
				$('#cal').fullCalendar('nextYear');
				expectViewDates('2015-06-01');
			});
		});
	});

	describe('today', function() {
		beforeEach(function() {
			options.now = '2016-03-15'; // the "today" date
		});
		describeWhenInMonth(function() {
			it('should move the calendar to now', function() {
				$('#cal').fullCalendar(options);
				$('#cal').fullCalendar('today');
				expectViewDates('2016-02-28', '2016-04-10', '2016-03-01', '2016-04-01');
			});
		});
		describeWhenInWeek(function() {
			it('should move the calendar to now', function() {
				$('#cal').fullCalendar(options);
				$('#cal').fullCalendar('today');
				expectViewDates('2016-03-13', '2016-03-20');
			});
		});
		describeWhenInDay(function() {
			it('should move the calendar to now', function() {
				$('#cal').fullCalendar(options);
				$('#cal').fullCalendar('today');
				expectViewDates('2016-03-15');
			});
		});
	});

	describe('when current date is a hidden day', function() {
		describeWhenInMonth(function() {
			it('should display the current month even if first day of month', function() {
				options.now = options.defaultDate = '2014-06-01'; // a Sunday
				options.weekends = false;
				$('#cal').fullCalendar(options);
				var view = $('#cal').fullCalendar('getView');
				expect(view.start).toEqualMoment('2014-06-02');
				expect(view.end).toEqualMoment('2014-07-12');
				expect(view.intervalStart).toEqualMoment('2014-06-01');
				expect(view.intervalEnd).toEqualMoment('2014-07-01');
			});
			it('should display the current month', function() {
				options.now = options.defaultDate = '2014-05-04'; // a Sunday
				options.weekends = false;
				$('#cal').fullCalendar(options);
				var view = $('#cal').fullCalendar('getView');
				expect(view.start).toEqualMoment('2014-04-28');
				expect(view.end).toEqualMoment('2014-06-07');
				expect(view.intervalStart).toEqualMoment('2014-05-01');
				expect(view.intervalEnd).toEqualMoment('2014-06-01');
			});
			describe('when navigating back a month', function() {
				it('should not skip months', function() {
					options.defaultDate = '2014-07-07';
					options.weekends = false;
					$('#cal').fullCalendar(options);
					var view = $('#cal').fullCalendar('getView');
					expect(view.intervalStart).toEqualMoment('2014-07-01');
					expect(view.intervalEnd).toEqualMoment('2014-08-01');
					$('#cal').fullCalendar('prev'); // will move to Jun 1, which is a Sunday
					view = $('#cal').fullCalendar('getView');
					expect(view.intervalStart).toEqualMoment('2014-06-01');
					expect(view.intervalEnd).toEqualMoment('2014-07-01');
				});
			});
		});
		describeWhenInDay(function() {
			it('should display the next visible day', function() {
				options.now = options.defaultDate = '2014-06-01'; // a Sunday
				options.weekends = false;
				$('#cal').fullCalendar(options);
				var view = $('#cal').fullCalendar('getView');
				expect(view.start).toEqualMoment('2014-06-02');
				expect(view.end).toEqualMoment('2014-06-03');
				expect(view.intervalStart).toEqualMoment('2014-06-02');
				expect(view.intervalEnd).toEqualMoment('2014-06-03');
			});
		});
	});

	// UTILS
	// -----

	function describeWhenInMonth(func) {
		describeWhenIn('month', func);
	}

	function describeWhenInWeek(func) {
		describeWhenIn('basicWeek', func);
		describeWhenIn('agendaWeek', func);
	}

	function describeWhenInDay(func) {
		describeWhenIn('basicDay', func);
		describeWhenIn('agendaDay', func);
	}

	function describeWhenIn(viewName, func) {
		describe('when in ' + viewName, function() {
			beforeEach(function() {
				options.defaultView = viewName;
			});
			func();
		});
	}

	function expectViewDates(start, end, titleStart, titleEnd) {
		var view = $('#cal').fullCalendar('getView');
		var calculatedEnd;
		var title;

		start = $.fullCalendar.moment(start);
		calculatedEnd = end ? $.fullCalendar.moment(end) : start.clone().add(1, 'days');
		expect(start).toEqualMoment(view.start);
		expect(calculatedEnd).toEqualMoment(view.end);

		titleStart = titleStart ? $.fullCalendar.moment(titleStart) : start;
		titleEnd = titleEnd ? $.fullCalendar.moment(titleEnd) : calculatedEnd;

		if (titleEnd) {
			title = $.fullCalendar.formatRange(
				titleStart,
				titleEnd.clone().add(-1, 'ms'),
				TITLE_FORMAT,
				' - '
			);
		}
		else {
			title = titleStart.format(TITLE_FORMAT);
		}

		expect($('.fc-toolbar h2')).toContainText(title);
	}

});
<commit_msg>remove legacy redundant tests<commit_after>describe('current date', function() {

	var TITLE_FORMAT = 'MMMM D YYYY';
	var options;

	beforeEach(function() {
		affix('#cal');
		options = {
			titleFormat: TITLE_FORMAT,
			titleRangeSeparator: ' - ',
			defaultDate: '2014-06-01'
		};
	});

	describe('defaultDate & getDate', function() { // keep getDate
		describeWhenInMonth(function() {
			it('should initialize at the date', function() {
				options.defaultDate = '2011-03-10';
				$('#cal').fullCalendar(options);
				expectViewDates('2011-02-27', '2011-04-10', '2011-03-01', '2011-04-01');
				var currentDate = $('#cal').fullCalendar('getDate');
				expect(moment.isMoment(currentDate)).toEqual(true); // test the type, but only here
				expect(currentDate).toEqualMoment('2011-03-10');
			});
		});
		describeWhenInWeek(function() {
			it('should initialize at the date, given a date string', function() {
				options.defaultDate = '2011-03-10';
				$('#cal').fullCalendar(options);
				expectViewDates('2011-03-06', '2011-03-13');
				expect($('#cal').fullCalendar('getDate')).toEqualMoment('2011-03-10');
			});
			it('should initialize at the date, given a Moment object', function() {
				options.defaultDate = $.fullCalendar.moment('2011-03-10');
				$('#cal').fullCalendar(options);
				expectViewDates('2011-03-06', '2011-03-13');
				expect($('#cal').fullCalendar('getDate')).toEqualMoment('2011-03-10');
			});
		});
		describeWhenInDay(function() {
			it('should initialize at the date', function() {
				options.defaultDate = '2011-03-10';
				$('#cal').fullCalendar(options);
				expectViewDates('2011-03-10');
				expect($('#cal').fullCalendar('getDate')).toEqualMoment('2011-03-10');
			});
		});
	});

	describe('gotoDate', function() {
		describeWhenInMonth(function() {
			it('should go to a date when given a date string', function() {
				$('#cal').fullCalendar(options);
				$('#cal').fullCalendar('gotoDate', '2015-04-01');
				expectViewDates('2015-03-29', '2015-05-10', '2015-04-01', '2015-05-01');
			});
		});
		describeWhenInWeek(function() {
			it('should go to a date when given a date string', function() {
				$('#cal').fullCalendar(options);
				$('#cal').fullCalendar('gotoDate', '2015-04-01');
				expectViewDates('2015-03-29', '2015-04-05');
			});
			it('should go to a date when given a date string with a time', function() {
				$('#cal').fullCalendar(options);
				$('#cal').fullCalendar('gotoDate', '2015-04-01T12:00:00');
				expectViewDates('2015-03-29', '2015-04-05');
			});
			it('should go to a date when given a moment object', function() {
				$('#cal').fullCalendar(options);
				$('#cal').fullCalendar('gotoDate', $.fullCalendar.moment('2015-04-01'));
				expectViewDates('2015-03-29', '2015-04-05');
			});
		});
		describeWhenInDay(function() {
			it('should go to a date when given a date string', function() {
				$('#cal').fullCalendar(options);
				$('#cal').fullCalendar('gotoDate', '2015-04-01');
				expectViewDates('2015-04-01');
			});
		});
	});

	describe('incrementDate', function() {
		describeWhenInMonth(function() {
			it('should increment the date when given a Duration object', function() {
				$('#cal').fullCalendar(options);
				$('#cal').fullCalendar('incrementDate', { months: -1 });
				expectViewDates('2014-04-27', '2014-06-08', '2014-05-01', '2014-06-01');
			});
		});
		describeWhenInWeek(function() {
			it('should increment the date when given a Duration object', function() {
				$('#cal').fullCalendar(options);
				$('#cal').fullCalendar('incrementDate', { weeks: -2 });
				expectViewDates('2014-05-18', '2014-05-25');
			});
		});
		describeWhenInDay(function() {
			it('should increment the date when given a Duration object', function() {
				$('#cal').fullCalendar(options);
				$('#cal').fullCalendar('incrementDate', { days: 2 });
				expectViewDates('2014-06-03');
			});
			it('should increment the date when given a Duration string', function() {
				$('#cal').fullCalendar(options);
				$('#cal').fullCalendar('incrementDate', '2.00:00:00');
				expectViewDates('2014-06-03');
			});
			it('should increment the date when given a Duration string with a time', function() {
				$('#cal').fullCalendar(options);
				$('#cal').fullCalendar('incrementDate', '2.05:30:00');
				expectViewDates('2014-06-03');
			});
		});
	});

	describe('prevYear', function() {
		describeWhenInMonth(function() {
			it('should move the calendar back a year', function() {
				$('#cal').fullCalendar(options);
				$('#cal').fullCalendar('prevYear');
				expectViewDates('2013-05-26', '2013-07-07', '2013-06-01', '2013-07-01');
			});
		});
		describeWhenInWeek(function() {
			it('should move the calendar back a year', function() {
				$('#cal').fullCalendar(options);
				$('#cal').fullCalendar('prevYear');
				expectViewDates('2013-05-26', '2013-06-02');
			});
		});
		describeWhenInDay(function() {
			it('should move the calendar back a year', function() {
				$('#cal').fullCalendar(options);
				$('#cal').fullCalendar('prevYear');
				expectViewDates('2013-06-01');
			});
		});
	});

	describe('nextYear', function() {
		describeWhenInMonth(function() {
			it('should move the calendar forward a year', function() {
				$('#cal').fullCalendar(options);
				$('#cal').fullCalendar('nextYear');
				expectViewDates('2015-05-31', '2015-07-12', '2015-06-01', '2015-07-01');
			});
		});
		describeWhenInWeek(function() {
			it('should move the calendar forward a year', function() {
				$('#cal').fullCalendar(options);
				$('#cal').fullCalendar('nextYear');
				expectViewDates('2015-05-31', '2015-06-07');
			});
		});
		describeWhenInDay(function() {
			it('should move the calendar forward a year', function() {
				$('#cal').fullCalendar(options);
				$('#cal').fullCalendar('nextYear');
				expectViewDates('2015-06-01');
			});
		});
	});

	describe('when current date is a hidden day', function() {
		describeWhenInMonth(function() {
			it('should display the current month even if first day of month', function() {
				options.now = options.defaultDate = '2014-06-01'; // a Sunday
				options.weekends = false;
				$('#cal').fullCalendar(options);
				var view = $('#cal').fullCalendar('getView');
				expect(view.start).toEqualMoment('2014-06-02');
				expect(view.end).toEqualMoment('2014-07-12');
				expect(view.intervalStart).toEqualMoment('2014-06-01');
				expect(view.intervalEnd).toEqualMoment('2014-07-01');
			});
			it('should display the current month', function() {
				options.now = options.defaultDate = '2014-05-04'; // a Sunday
				options.weekends = false;
				$('#cal').fullCalendar(options);
				var view = $('#cal').fullCalendar('getView');
				expect(view.start).toEqualMoment('2014-04-28');
				expect(view.end).toEqualMoment('2014-06-07');
				expect(view.intervalStart).toEqualMoment('2014-05-01');
				expect(view.intervalEnd).toEqualMoment('2014-06-01');
			});
			describe('when navigating back a month', function() {
				it('should not skip months', function() {
					options.defaultDate = '2014-07-07';
					options.weekends = false;
					$('#cal').fullCalendar(options);
					var view = $('#cal').fullCalendar('getView');
					expect(view.intervalStart).toEqualMoment('2014-07-01');
					expect(view.intervalEnd).toEqualMoment('2014-08-01');
					$('#cal').fullCalendar('prev'); // will move to Jun 1, which is a Sunday
					view = $('#cal').fullCalendar('getView');
					expect(view.intervalStart).toEqualMoment('2014-06-01');
					expect(view.intervalEnd).toEqualMoment('2014-07-01');
				});
			});
		});
		describeWhenInDay(function() {
			it('should display the next visible day', function() {
				options.now = options.defaultDate = '2014-06-01'; // a Sunday
				options.weekends = false;
				$('#cal').fullCalendar(options);
				var view = $('#cal').fullCalendar('getView');
				expect(view.start).toEqualMoment('2014-06-02');
				expect(view.end).toEqualMoment('2014-06-03');
				expect(view.intervalStart).toEqualMoment('2014-06-02');
				expect(view.intervalEnd).toEqualMoment('2014-06-03');
			});
		});
	});

	// UTILS
	// -----

	function describeWhenInMonth(func) {
		describeWhenIn('month', func);
	}

	function describeWhenInWeek(func) {
		describeWhenIn('basicWeek', func);
		describeWhenIn('agendaWeek', func);
	}

	function describeWhenInDay(func) {
		describeWhenIn('basicDay', func);
		describeWhenIn('agendaDay', func);
	}

	function describeWhenIn(viewName, func) {
		describe('when in ' + viewName, function() {
			beforeEach(function() {
				options.defaultView = viewName;
			});
			func();
		});
	}

	function expectViewDates(start, end, titleStart, titleEnd) {
		var view = $('#cal').fullCalendar('getView');
		var calculatedEnd;
		var title;

		start = $.fullCalendar.moment(start);
		calculatedEnd = end ? $.fullCalendar.moment(end) : start.clone().add(1, 'days');
		expect(start).toEqualMoment(view.start);
		expect(calculatedEnd).toEqualMoment(view.end);

		titleStart = titleStart ? $.fullCalendar.moment(titleStart) : start;
		titleEnd = titleEnd ? $.fullCalendar.moment(titleEnd) : calculatedEnd;

		if (titleEnd) {
			title = $.fullCalendar.formatRange(
				titleStart,
				titleEnd.clone().add(-1, 'ms'),
				TITLE_FORMAT,
				' - '
			);
		}
		else {
			title = titleStart.format(TITLE_FORMAT);
		}

		expect($('.fc-toolbar h2')).toContainText(title);
	}

});
<|endoftext|>


<commit_before>parser = new DOMParser();

var template = {
        "provenance": {
            "image": {
                "slide": "ID"
            },
            "analysis": {
                "source": "human",
                "execution_id": "TEMPLATE",
                "name": "TEMPLATE"
            }
        },
        "properties": {
            "annotations": {
                "name": "TEMPLATE",
                "note": "Converted from XML"
            }
        },
        "geometries": {
            "type": "FeatureCollection"
        }
    }

function xml2geo(){
  let features = []
  let input = document.getElementById("xml_in").value;
  xmlDoc = parser.parseFromString(input, "text/xml");
  let regions = xmlDoc.getElementsByTagName("Region")
  for (let i of regions){
    console.log("Looking at Region ID", i.getAttribute("Id"))
    let vertices = i.getElementsByTagName("Vertex")
    let coordinates = []
    let min_x = 99e99
    let max_x = 0
    let min_y = 99e99
    let max_y = 0
    for (let j of vertices){
      let x = parseFloat(j.getAttribute("X"))
      let y = parseFloat(j.getAttribute("Y"))
      min_x = Math.min(min_x, x)
      min_y = Math.min(min_y, y)
      max_x = Math.max(max_x, x)
      max_y = Math.max(max_y, y)
      coordinates.push([x,y])
    }
    coordinates.push(coordinates[0])
    let bound_rect = [[min_x, min_y], [min_x, max_y], [max_x, max_y], [max_x, min_y], [min_x, min_y]]
    let feature = {}
    feature['type'] = "Feature"
    feature['geometry'] = {}
    feature['geometry']['type'] = "Polygon"
    feature['geometry']['coordinates'] = [coordinates]
    feature['bound'] = {}
    feature['bound']['type'] = "Polygon"
    feature['bound']['coordinates'] = [bound_rect]
    features.push(feature)
  }
  let output = Object.assign({}, template)
  output['geometries']['features'] = features
  output['provenance']['image']['slide'] = document.getElementById("slide_id").value
  output['provenance']['analysis']['execution'] = document.getElementById("annot_name").value
  output['properties']['annotations']['name'] = document.getElementById("annot_name").value
  document.getElementById("output").innerHTML = JSON.stringify(output)
}
<commit_msg>Style fix xml2geo<commit_after>parser = new DOMParser();

var template = {
  'provenance': {
    'image': {
      'slide': 'ID',
    },
    'analysis': {
      'source': 'human',
      'execution_id': 'TEMPLATE',
      'name': 'TEMPLATE',
    },
  },
  'properties': {
    'annotations': {
      'name': 'TEMPLATE',
      'note': 'Converted from XML',
    },
  },
  'geometries': {
    'type': 'FeatureCollection',
  },
};

function xml2geo() {
  let features = [];
  let input = document.getElementById('xml_in').value;
  xmlDoc = parser.parseFromString(input, 'text/xml');
  let regions = xmlDoc.getElementsByTagName('Region');
  for (let i of regions) {
    console.log('Looking at Region ID', i.getAttribute('Id'));
    let vertices = i.getElementsByTagName('Vertex');
    let coordinates = [];
    let minX = 99e99;
    let maxX = 0;
    let minY = 99e99;
    let maxY = 0;
    for (let j of vertices) {
      let x = parseFloat(j.getAttribute('X'));
      let y = parseFloat(j.getAttribute('Y'));
      minX = Math.min(minX, x);
      minY = Math.min(minY, y);
      maxX = Math.max(maxX, x);
      maxY = Math.max(maxY, y);
      coordinates.push([x, y]);
    }
    coordinates.push(coordinates[0]);
    let boundRect = [[minX, minY], [minX, maxY], [maxX, maxY], [maxX, minY], [minX, minY]];
    let feature = {};
    feature['type'] = 'Feature';
    feature['geometry'] = {};
    feature['geometry']['type'] = 'Polygon';
    feature['geometry']['coordinates'] = [coordinates];
    feature['bound'] = {};
    feature['bound']['type'] = 'Polygon';
    feature['bound']['coordinates'] = [boundRect];
    features.push(feature);
  }
  let output = Object.assign({}, template);
  output['geometries']['features'] = features;
  output['provenance']['image']['slide'] = document.getElementById('slide_id').value;
  output['provenance']['analysis']['execution'] = document.getElementById('annot_name').value;
  output['properties']['annotations']['name'] = document.getElementById('annot_name').value;
  document.getElementById('output').innerHTML = JSON.stringify(output);
}
<|endoftext|>