<commit_before>import $ from 'jquery';
import dom from '../core/dom';

export default class Handle {
  constructor(context) {
    this.context = context;
    this.$document = $(document);
    this.$editingArea = context.layoutInfo.editingArea;
    this.options = context.options;

    this.events = {
      'summernote.mousedown': (we, e) => {
        if (this.update(e.target)) {
          e.preventDefault();
        }
      },
      'summernote.keyup summernote.scroll summernote.change summernote.dialog.shown': () => {
        this.update();
      },
      'summernote.disable': () => {
        this.hide();
      },
      'summernote.codeview.toggled': () => {
        this.update();
      }
    };
  }

  initialize() {
    this.$handle = $([
      '
',
      '
',
      '

',
      '

',
      '

',
      '

',
      '

',
      (this.options.disableResizeImage ? '' : '

'),
      '
',
      '
'
    ].join('')).prependTo(this.$editingArea);

    this.$handle.on('mousedown', (event) => {
      if (dom.isControlSizing(event.target)) {
        event.preventDefault();
        event.stopPropagation();

        const $target = this.$handle.find('.note-control-selection').data('target');
        const posStart = $target.offset();
        const scrollTop = this.$document.scrollTop();

        const onMouseMove = (event) => {
          this.context.invoke('editor.resizeTo', {
            x: event.clientX - posStart.left,
            y: event.clientY - (posStart.top - scrollTop)
          }, $target, !event.shiftKey);

          this.update($target[0]);
        };

        this.$document
          .on('mousemove', onMouseMove)
          .one('mouseup', (e) => {
            e.preventDefault();
            this.$document.off('mousemove', onMouseMove);
            this.context.invoke('editor.afterCommand');
          });

        if (!$target.data('ratio')) { // original ratio.
          $target.data('ratio', $target.height() / $target.width());
        }
      }
    });

    // Listen for scrolling on the handle overlay.
    this.$handle.on('wheel', (e) => {
      e.preventDefault();
      this.update();
    });
  }

  destroy() {
    this.$handle.remove();
  }

  update(target) {
    if (this.context.isDisabled()) {
      return false;
    }

    const isImage = dom.isImg(target);
    const $selection = this.$handle.find('.note-control-selection');

    this.context.invoke('imagePopover.update', target);

    if (isImage) {
      const $image = $(target);
      const position = $image.position();
      const pos = {
        left: position.left + parseInt($image.css('marginLeft'), 10),
        top: position.top + parseInt($image.css('marginTop'), 10)
      };

      // exclude margin
      const imageSize = {
        w: $image.outerWidth(false),
        h: $image.outerHeight(false)
      };

      $selection.css({
        display: 'block',
        left: pos.left,
        top: pos.top,
        width: imageSize.w,
        height: imageSize.h
      }).data('target', $image); // save current image element.

      const origImageObj = new Image();
      origImageObj.src = $image.attr("src");

      const sizingText = imageSize.w + 'x' + imageSize.h + ' (Original: '+ origImageObj.width + 'x' + origImageObj.height +')';
      this.context.invoke('editor.saveTarget', target);
    } else {
      this.hide();
    }

    return isImage;
  }

  /**
   * hide
   *
   * @param {jQuery} $handle
   */
  hide() {
    this.context.invoke('editor.clearTarget');
    this.$handle.children().hide();
  }
}
<commit_msg>Add Image popover enhancements.<commit_after>import $ from 'jquery';
import dom from '../core/dom';

export default class Handle {
  constructor(context) {
    this.context = context;
    this.$document = $(document);
    this.$editingArea = context.layoutInfo.editingArea;
    this.options = context.options;

    this.events = {
      'summernote.mousedown': (we, e) => {
        if (this.update(e.target)) {
          e.preventDefault();
        }
      },
      'summernote.keyup summernote.scroll summernote.change summernote.dialog.shown': () => {
        this.update();
      },
      'summernote.disable': () => {
        this.hide();
      },
      'summernote.codeview.toggled': () => {
        this.update();
      }
    };
  }

  initialize() {
    this.$handle = $([
      '
',
      '
',
      '

',
      '

',
      '

',
      '

',
      '

',
      (this.options.disableResizeImage ? '' : '

'),
      '
',
      '
'
    ].join('')).prependTo(this.$editingArea);

    this.$handle.on('mousedown', (event) => {
      if (dom.isControlSizing(event.target)) {
        event.preventDefault();
        event.stopPropagation();

        const $target = this.$handle.find('.note-control-selection').data('target');
        const posStart = $target.offset();
        const scrollTop = this.$document.scrollTop();

        const onMouseMove = (event) => {
          this.context.invoke('editor.resizeTo', {
            x: event.clientX - posStart.left,
            y: event.clientY - (posStart.top - scrollTop)
          }, $target, !event.shiftKey);

          this.update($target[0]);
        };

        this.$document
          .on('mousemove', onMouseMove)
          .one('mouseup', (e) => {
            e.preventDefault();
            this.$document.off('mousemove', onMouseMove);
            this.context.invoke('editor.afterCommand');
          });

        if (!$target.data('ratio')) { // original ratio.
          $target.data('ratio', $target.height() / $target.width());
        }
      }
    });

    // Listen for scrolling on the handle overlay.
    this.$handle.on('wheel', (e) => {
      e.preventDefault();
      this.update();
    });
  }

  destroy() {
    this.$handle.remove();
  }

  update(target) {
    if (this.context.isDisabled()) {
      return false;
    }

    const isImage = dom.isImg(target);
    const $selection = this.$handle.find('.note-control-selection');

    this.context.invoke('imagePopover.update', target);

    if (isImage) {
      const $image = $(target);
      const position = $image.position();
      const pos = {
        left: position.left + parseInt($image.css('marginLeft'), 10),
        top: position.top + parseInt($image.css('marginTop'), 10)
      };

      // exclude margin
      const imageSize = {
        w: $image.outerWidth(false),
        h: $image.outerHeight(false)
      };

      $selection.css({
        display: 'block',
        left: pos.left,
        top: pos.top,
        width: imageSize.w,
        height: imageSize.h
      }).data('target', $image); // save current image element.

      const origImageObj = new Image();
      origImageObj.src = $image.attr("src");

      const sizingText = imageSize.w + 'x' + imageSize.h + ' (Original: '+ origImageObj.width + 'x' + origImageObj.height + ')';
      this.context.invoke('editor.saveTarget', target);
    } else {
      this.hide();
    }

    return isImage;
  }

  /**
   * hide
   *
   * @param {jQuery} $handle
   */
  hide() {
    this.context.invoke('editor.clearTarget');
    this.$handle.children().hide();
  }
}
<|endoftext|>


<commit_before>/* global NAME_CONTROLLER, DIRECTION_NEXT, DIRECTION_PREVIOUS, KEY_DOWN */
/* global KEY_UP, KEY_RIGHT, KEY_LEFT, PAGE_SIZE_DEFAULT, SCROLL_BUFFER, CLOSE_EVENT */
/* global OPEN_EVENT, NAME_FONTSSERVICE, FONTLIST_ENTRY_TYPE_FONT, WeakMap */
/* global FONTLIST_ENTRY_TYPE_HEADLINE, FONTLIST_ENTRY_TYPE_TEXT */
var NAME_JDFONTLIST = 'jdFontlist';
var NAME_JDFONTLIST_CONTROLLER = NAME_JDFONTLIST + NAME_CONTROLLER;

fontselectModule.directive(NAME_JDFONTLIST, function() {
  return {
    scope: {
      id: '=fsid',
      fonts: '=',
      meta: '=',
      current: '=',
      text: '='
    },
    restrict: 'E',
    templateUrl: 'fontlist.html',
    replace: true,
    controller: NAME_JDFONTLIST_CONTROLLER
  };
});

fontselectModule.controller(NAME_JDFONTLIST_CONTROLLER, [
  '$scope',
  '$rootScope',
  '$filter',
  NAME_FONTSSERVICE,
  '$element',
  '$document',
  'jdfsCuratedFonts',
  /* jshint maxparams: 7 */
  function($scope, $rootScope, $filter, fontsService, $element, $document, jdfsCuratedFonts) {
  /* jshint maxparams: 3 */
    var _fontlistEntries = [];
    var _lastPageCount = 0;
    var _scrollBuffer = 0;

    var ALL_FONTS_FILTER_STATE = {
      forceNext: false,
      fontsInProviders: [],
      fontsInSubsets: [],
      sortedFonts: [],
      categorizedFonts: [],
      searchedFonts: [],
      sortCache: { search: $scope.current.search }
    };

    var CURATED_FONTS_FILTER_STATE = angular.copy(ALL_FONTS_FILTER_STATE);

    var defaultPage = {
      size: PAGE_SIZE_DEFAULT,
      count: 0,
      current:  0,
      currentAbs: 0
    };

    var page = $scope.page = $scope.meta.page = angular.extend({}, defaultPage, $scope.meta.page);

    var fontmeta = $scope.meta.fonts = {
      total: 0,
      current: 0
    };

    function isOnCurrentPage(index) {
      var currentMinIndex = page.current * page.size;

      return index >= currentMinIndex && index < currentMinIndex + page.size;
    }

    $scope.keyfocus = function(direction, amount) {
      var index = -1;
      var i, l = _fontlistEntries.length;
      for (i = 0; i < l; i++) {
        if (_fontlistEntries[i].content === $scope.current.font) {
          index = i;
          break;
        }
      }

      var pageoffset = page.size * page.current;
      var onPage = isOnCurrentPage(index);

      if (angular.isUndefined(amount)) {
        amount = 1;
      }

      index += (direction === DIRECTION_PREVIOUS ? -amount : amount);

      if (!onPage && _fontlistEntries[index + pageoffset]) {
        index += pageoffset;
      }

      while (_fontlistEntries[index] && _fontlistEntries[index].type !== FONTLIST_ENTRY_TYPE_FONT) {
        index += (direction === DIRECTION_PREVIOUS ? -1 : 1);
      }

      if (_fontlistEntries[index]) {
        $scope.current.font = _fontlistEntries[index].content;

        page.currentAbs = page.current = Math.floor(index / page.size);

        $rootScope.$digest();
      }
    };

    function keyDownHandler(event) {
      function prevent() {
        event.preventDefault();
        return false;
      }

      var key = event.keyCode;

      if (key === KEY_DOWN) {
        $scope.keyfocus(DIRECTION_NEXT);
        return prevent();
      } else if (key === KEY_UP) {
        $scope.keyfocus(DIRECTION_PREVIOUS);
        return prevent();
      }

      if (document.activeElement.tagName === 'INPUT' && document.activeElement.value) {
        return;
      }

      var amount = page.size;
      if (key === KEY_RIGHT) {
        if (!$scope.current.font) {
          amount++;
        }
        $scope.keyfocus(DIRECTION_NEXT, amount);
        return prevent();
      } else if (key === KEY_LEFT) {
        $scope.keyfocus(DIRECTION_PREVIOUS, page.size);
        return prevent();
      }
    }

    function getDeltaFromEvent(event) {
      var delta = event.wheelDeltaY || event.wheelDelta ||
        event.deltaY * -1 || event.detail * -1;

      if (!isFinite(delta) &&
        !angular.isUndefined(event.originalEvent)) {
        delta = getDeltaFromEvent(event.originalEvent);
      }

      return delta;
    }

    var wheelHandler = function(event) {
      if (!event.target) {
        return;
      }

      if (_isDescendant($element[0], event.target)) {
        event.preventDefault();
        event.stopPropagation();

        var subpage = 1 / page.size;
        var delta = getDeltaFromEvent(event);
        var absDelta = Math.abs(delta);

        /* For touch-pads etc., we buffer small movements */
        if (absDelta > 1 && absDelta < SCROLL_BUFFER) {
          _scrollBuffer += delta;
          if (Math.abs(_scrollBuffer) < SCROLL_BUFFER) {
            return;
          }
          _scrollBuffer = 0;
        }

        if (delta !== 0 && $scope.paginate(delta > 0 ? -subpage : subpage) !== false) {
          $scope.$digest();
        }
      }
    };

    $scope.$on(OPEN_EVENT, function() {
      $document.on('keydown', keyDownHandler);
      $document.on('wheel', wheelHandler);
      $document.on('mousewheel', wheelHandler);
      $document.on('DOMMouseScroll', wheelHandler);
    });

    $scope.$on(CLOSE_EVENT, function() {
      $document.off('keydown', keyDownHandler);
      $document.off('wheel', wheelHandler);
      $document.off('mousewheel', wheelHandler);
      $document.off('DOMMouseScroll', wheelHandler);
    });

    /**
     * Set the current page
     *
     * @param {Number} currentPage
     * @return {void}
     */
    $scope.setCurrentPage = function(currentPage) {
      page.currentAbs = page.current = currentPage;
    };

    /**
     * Go to the next or previous page.
     *
     * @param  {Number} amount subpage steps
     * @param  {Object} $event jQuery scroll event
     * @return {void}
     */
    $scope.paginate = function(amount, $event) {
      if ($event && $event.preventDefault) {
        $event.preventDefault();
      }

      var direction = amount;
      if (angular.isNumber(amount)) {
        if (amount === 0) {
          return false;
        }
        direction = amount < 0 ? DIRECTION_PREVIOUS : DIRECTION_NEXT;
      } else {
        amount = _getAmountFromDirection(direction);
      }

      if (!$scope.paginationButtonActive(direction)) {
        return false;
      }

      if (page.current + amount < 0) {
        page.currentAbs = page.current = 0;
      } else {
        page.current += amount;
        page.currentAbs = Math.floor(page.current);
      }

      return page.current;
    };

    /**
     * Check if the pagination button is active
     *
     * @param  {String} direction 'next' or 'prev'
     * @return {Boolean}
     */
    $scope.paginationButtonActive = function(direction) {
      _updatePageCount();
      _updateCurrentPage();

      return (
        (direction === DIRECTION_NEXT && (page.current + 1) * page.size < _fontlistEntries.length) ||
        (direction === DIRECTION_PREVIOUS && page.current > 0)
      );
    };

    /**
     * Get an array with the length similar to the
     * amount of pages we have. (So we can use it in a repeater)
     *
     * Also update the current page and the current amount of pages.
     *
     * @return {Array}
     */
    $scope.getPages = function() {
      _updatePageCount();
      var pages = new Array(page.count);

      _updateCurrentPage();

      /* Display the page buttons only if we have at least two pages. */
      if (pages.length <= 1) {
        return [];
      }
      return pages;
    };

    /**
     * Apply the current provider filter to the given font list
     * @param {Array} fonts
     * @return {Array}
     */
    function _filterProviders(fonts, filterState) {
      var providersString = JSON.stringify($scope.current.providers);
      if (filterState.forceNext || filterState.sortCache.providers !== providersString) {
        filterState.sortCache.providers = providersString;
        filterState.forceNext = true;

        filterState.fontsInProviders = fonts.filter(function(font) {
          return $scope.current.providers[font.provider];
        });
      }

      return filterState.fontsInProviders;
    }

    /**
     * Apply current subset filters to given font list
     * @param  {Array} fonts
     * @return {Array}
     */
    function _filterSubsets(fonts, filterState) {
      var subSetString = JSON.stringify($scope.current.subsets);
      if (filterState.forceNext || filterState.sortCache.subsets !== subSetString) {
        filterState.sortCache.subsets = subSetString;
        filterState.forceNext = true;

        filterState.fontsInSubsets = $filter('hasAllSubsets')(
          fonts,
          $scope.current.subsets
        );
      }

      return filterState.fontsInSubsets;
    }

    /**
     * Apply current sort to given font list
     * @param  {Array} fonts
     * @return {Array}
     */
    function _filterSort(fonts, filterState) {
      var attrDirection = $scope.current.sort.attr.dir;
      var direction = $scope.current.sort.direction;

      if (filterState.forceNext ||
        filterState.sortCache.sortattr !== $scope.current.sort.attr.key ||
        filterState.sortCache.sortdir !== direction)
      {
        filterState.sortCache.sortattr = $scope.current.sort.attr.key;
        filterState.sortCache.sortdir = direction;
        filterState.forceNext = true;

        filterState.sortedFonts = $filter('orderBy')(
          fonts,
          $scope.current.sort.attr.key,
          $scope.current.sort.direction ? attrDirection : !attrDirection
        );
      }

      return filterState.sortedFonts;
    }

    /**
     * Apply current category filters to given font list.
     * @param  {Array} fonts
     * @return {Array}
     */
    function _filterCategory(fonts, filterState) {
      var category = $scope.current.category;
      if (filterState.forceNext || filterState.sortCache.category !== category) {
        filterState.sortCache.category = category;
        filterState.forceNext = true;

        if (angular.isUndefined(category)) {
          filterState.categorizedFonts = fonts;
        } else {
          filterState.categorizedFonts = $filter('filter')(fonts, {category: category}, true);
        }
      }

      return filterState.categorizedFonts;
    }

    /**
     * Apply current search to given font list.
     * @param  {Array} fonts
     * @return {Array}
     */
    function _filterSearch(fonts, filterState) {
      var search = $scope.current.search || '';
      var searchTermChanged = filterState.sortCache.search !== search;
      if (filterState.forceNext || searchTermChanged) {
        filterState.sortCache.search = search;
        filterState.forceNext = true;

        /* Unset category filter so every font is visible. */
        if (searchTermChanged) {
          $scope.current.category = undefined;
        }

        if (search.length) {
          filterState.searchedFonts = _priorize(
            $filter('fuzzySearch')(fonts, {name: search}),
            search.toLowerCase()
          );
        } else {
          filterState.searchedFonts = fonts;
        }
      }

      return filterState.searchedFonts;
    }

    var EMPTY_FILTERED_FONTS = [];

    function filterFontList(fonts, filterState) {
      filterState.forceNext = filterState.sortCache.fontAmount !== fonts.length;
      filterState.sortCache.fontAmount = fonts.length;

      var queue = [
        _filterProviders,
        _filterSubsets,
        _filterSort,
        _filterSearch,
        _filterCategory
      ];

      var filteredList = fonts;
      for (var i = 0, l = queue.length; i < l; i++) {
        filteredList = queue[i](filteredList, filterState);
      }

      return filteredList;
    }

    function convertFontToFontlistEntry(font) {
      return {
        type: FONTLIST_ENTRY_TYPE_FONT,
        content: font
      };
    }

    function createHeadlineEntry(content) {
      return {
        type: FONTLIST_ENTRY_TYPE_HEADLINE,
        content: content
      };
    }

    function createTextEntry(content) {
      return {
        type: FONTLIST_ENTRY_TYPE_TEXT,
        content: content
      };
    }

    var entryMap = new WeakMap();

    $scope.getFontlistEntries = function() {
      var filteredFonts = filterFontList($scope.fonts || EMPTY_FILTERED_FONTS, ALL_FONTS_FILTER_STATE);
      if (!entryMap.has(filteredFonts)) {

        var fontlistEntries = [];
        if (filteredFonts.length === 0) {
          fontlistEntries.push(createTextEntry($scope.text.noResultsLabel));
        } else {
          fontlistEntries = filteredFonts.map(convertFontToFontlistEntry);

          if (jdfsCuratedFonts.length !== 0) {
            var filteredCuratedFonts = filterFontList(jdfsCuratedFonts, CURATED_FONTS_FILTER_STATE);
            fontlistEntries = [createHeadlineEntry($scope.text.curatedFontsListHeadline)]
              .concat(filteredCuratedFonts.map(convertFontToFontlistEntry))
              .concat([createHeadlineEntry($scope.text.allFontsListHeadline)])
              .concat(fontlistEntries);
          }
        }

        fontmeta.total = $scope.fonts.length;
        fontmeta.current = filteredFonts.length;

        _fontlistEntries = fontlistEntries;
        entryMap.set(filteredFonts, fontlistEntries);
      }

      return entryMap.get(filteredFonts);
    };

    /**
     * Convert 'prev' and 'last' to -1 and 1
     * @param  {Number|String} direction
     * @return {Number}
     */
    function _getAmountFromDirection(direction) {
      if (angular.isNumber(direction)) {
        return direction;
      }
      return (direction === DIRECTION_PREVIOUS ? -1 : 1);
    }

    /**
     * Sort a list of fonts by matching them against a given search
     * @param  {Array} fonts
     * @param  {String} search
     * @return {Array}
     */
    function _priorize(fonts, search) {
      if (fonts.length > 1) {
        var rgx = new RegExp('[' + search + ']+');

        fonts.sort(function(a, b) {
          var nameA = a.name.toLowerCase();
          var nameB = b.name.toLowerCase();
          var firstCharA = nameA[0];
          var firstCharB = nameB[0];

          /* Prioritize by first character... */
          if (firstCharA !== firstCharB) {
            if (firstCharA === search[0]) {
              return -1;
            } else if (firstCharB === search[0]) {
              return 1;
            }
          }

          /* Prioritize by amount of matches. */
          return (nameA.replace(rgx, '').length < nameB.replace(rgx, '').length) ? -1 : 1;
        });
      }

      return fonts;
    }

    /**
     * Calculate the amount of pages we have.
     *
     * @return {void}
     */
    function _updatePageCount() {
      _lastPageCount = page.count;

      if (!angular.isArray($scope.fonts)) {
        return 0;
      }

      if (_fontlistEntries.length) {
        page.count = Math.ceil(_fontlistEntries.length / page.size);
      }
    }

    /**
     * Whenever the amount of pages is changing:
     * Make sure we're not staying on a page that does not exist.
     * And if we have a font selected, try to stay on the page of
     * that font.
     *
     * @return {void}
     */
    function _updateCurrentPage() {
      /* do nothing if the amount of pages hasn't change */
      if (_lastPageCount === page.count) {
        return;
      }

      $scope.setCurrentPage(0);
    }
  }
]);
<commit_msg>fix(fontlist): scrolling issues with jquery > 1.12<commit_after>/* global NAME_CONTROLLER, DIRECTION_NEXT, DIRECTION_PREVIOUS, KEY_DOWN */
/* global KEY_UP, KEY_RIGHT, KEY_LEFT, PAGE_SIZE_DEFAULT, SCROLL_BUFFER, CLOSE_EVENT */
/* global OPEN_EVENT, NAME_FONTSSERVICE, FONTLIST_ENTRY_TYPE_FONT, WeakMap */
/* global FONTLIST_ENTRY_TYPE_HEADLINE, FONTLIST_ENTRY_TYPE_TEXT */
var NAME_JDFONTLIST = 'jdFontlist';
var NAME_JDFONTLIST_CONTROLLER = NAME_JDFONTLIST + NAME_CONTROLLER;

fontselectModule.directive(NAME_JDFONTLIST, function() {
  return {
    scope: {
      id: '=fsid',
      fonts: '=',
      meta: '=',
      current: '=',
      text: '='
    },
    restrict: 'E',
    templateUrl: 'fontlist.html',
    replace: true,
    controller: NAME_JDFONTLIST_CONTROLLER
  };
});

fontselectModule.controller(NAME_JDFONTLIST_CONTROLLER, [
  '$scope',
  '$rootScope',
  '$filter',
  NAME_FONTSSERVICE,
  '$element',
  '$document',
  'jdfsCuratedFonts',
  /* jshint maxparams: 7 */
  function($scope, $rootScope, $filter, fontsService, $element, $document, jdfsCuratedFonts) {
  /* jshint maxparams: 3 */
    var _fontlistEntries = [];
    var _lastPageCount = 0;
    var _scrollBuffer = 0;

    var ALL_FONTS_FILTER_STATE = {
      forceNext: false,
      fontsInProviders: [],
      fontsInSubsets: [],
      sortedFonts: [],
      categorizedFonts: [],
      searchedFonts: [],
      sortCache: { search: $scope.current.search }
    };

    var CURATED_FONTS_FILTER_STATE = angular.copy(ALL_FONTS_FILTER_STATE);

    var defaultPage = {
      size: PAGE_SIZE_DEFAULT,
      count: 0,
      current:  0,
      currentAbs: 0
    };

    var page = $scope.page = $scope.meta.page = angular.extend({}, defaultPage, $scope.meta.page);

    var fontmeta = $scope.meta.fonts = {
      total: 0,
      current: 0
    };

    function isOnCurrentPage(index) {
      var currentMinIndex = page.current * page.size;

      return index >= currentMinIndex && index < currentMinIndex + page.size;
    }

    $scope.keyfocus = function(direction, amount) {
      var index = -1;
      var i, l = _fontlistEntries.length;
      for (i = 0; i < l; i++) {
        if (_fontlistEntries[i].content === $scope.current.font) {
          index = i;
          break;
        }
      }

      var pageoffset = page.size * page.current;
      var onPage = isOnCurrentPage(index);

      if (angular.isUndefined(amount)) {
        amount = 1;
      }

      index += (direction === DIRECTION_PREVIOUS ? -amount : amount);

      if (!onPage && _fontlistEntries[index + pageoffset]) {
        index += pageoffset;
      }

      while (_fontlistEntries[index] && _fontlistEntries[index].type !== FONTLIST_ENTRY_TYPE_FONT) {
        index += (direction === DIRECTION_PREVIOUS ? -1 : 1);
      }

      if (_fontlistEntries[index]) {
        $scope.current.font = _fontlistEntries[index].content;

        page.currentAbs = page.current = Math.floor(index / page.size);

        $rootScope.$digest();
      }
    };

    function keyDownHandler(event) {
      function prevent() {
        event.preventDefault();
        return false;
      }

      var key = event.keyCode;

      if (key === KEY_DOWN) {
        $scope.keyfocus(DIRECTION_NEXT);
        return prevent();
      } else if (key === KEY_UP) {
        $scope.keyfocus(DIRECTION_PREVIOUS);
        return prevent();
      }

      if (document.activeElement.tagName === 'INPUT' && document.activeElement.value) {
        return;
      }

      var amount = page.size;
      if (key === KEY_RIGHT) {
        if (!$scope.current.font) {
          amount++;
        }
        $scope.keyfocus(DIRECTION_NEXT, amount);
        return prevent();
      } else if (key === KEY_LEFT) {
        $scope.keyfocus(DIRECTION_PREVIOUS, page.size);
        return prevent();
      }
    }

    function getDeltaFromEvent(event) {
      var delta = event.wheelDeltaY || event.wheelDelta ||
        event.deltaY * -1;

      if (!isFinite(delta) &&
        !angular.isUndefined(event.originalEvent)) {
        delta = getDeltaFromEvent(event.originalEvent);
      }

      return delta;
    }

    var wheelHandler = function(event) {
      if (!event.target) {
        return;
      }

      if (_isDescendant($element[0], event.target)) {
        event.preventDefault();
        event.stopPropagation();

        var subpage = 1 / page.size;
        var delta = getDeltaFromEvent(event);
        var absDelta = Math.abs(delta);

        /* For touch-pads etc., we buffer small movements */
        if (absDelta > 1 && absDelta < SCROLL_BUFFER) {
          _scrollBuffer += delta;
          if (Math.abs(_scrollBuffer) < SCROLL_BUFFER) {
            return;
          }
          _scrollBuffer = 0;
        }

        if (delta !== 0 && $scope.paginate(delta > 0 ? -subpage : subpage) !== false) {
          $scope.$digest();
        }
      }
    };

    $scope.$on(OPEN_EVENT, function() {
      $document.on('keydown', keyDownHandler);
      $document.on('wheel', wheelHandler);
      $document.on('mousewheel', wheelHandler);
      $document.on('DOMMouseScroll', wheelHandler);
    });

    $scope.$on(CLOSE_EVENT, function() {
      $document.off('keydown', keyDownHandler);
      $document.off('wheel', wheelHandler);
      $document.off('mousewheel', wheelHandler);
      $document.off('DOMMouseScroll', wheelHandler);
    });

    /**
     * Set the current page
     *
     * @param {Number} currentPage
     * @return {void}
     */
    $scope.setCurrentPage = function(currentPage) {
      page.currentAbs = page.current = currentPage;
    };

    /**
     * Go to the next or previous page.
     *
     * @param  {Number} amount subpage steps
     * @param  {Object} $event jQuery scroll event
     * @return {void}
     */
    $scope.paginate = function(amount, $event) {
      if ($event && $event.preventDefault) {
        $event.preventDefault();
      }

      var direction = amount;
      if (angular.isNumber(amount)) {
        if (amount === 0) {
          return false;
        }
        direction = amount < 0 ? DIRECTION_PREVIOUS : DIRECTION_NEXT;
      } else {
        amount = _getAmountFromDirection(direction);
      }

      if (!$scope.paginationButtonActive(direction)) {
        return false;
      }

      if (page.current + amount < 0) {
        page.currentAbs = page.current = 0;
      } else {
        page.current += amount;
        page.currentAbs = Math.floor(page.current);
      }

      return page.current;
    };

    /**
     * Check if the pagination button is active
     *
     * @param  {String} direction 'next' or 'prev'
     * @return {Boolean}
     */
    $scope.paginationButtonActive = function(direction) {
      _updatePageCount();
      _updateCurrentPage();

      return (
        (direction === DIRECTION_NEXT && (page.current + 1) * page.size < _fontlistEntries.length) ||
        (direction === DIRECTION_PREVIOUS && page.current > 0)
      );
    };

    /**
     * Get an array with the length similar to the
     * amount of pages we have. (So we can use it in a repeater)
     *
     * Also update the current page and the current amount of pages.
     *
     * @return {Array}
     */
    $scope.getPages = function() {
      _updatePageCount();
      var pages = new Array(page.count);

      _updateCurrentPage();

      /* Display the page buttons only if we have at least two pages. */
      if (pages.length <= 1) {
        return [];
      }
      return pages;
    };

    /**
     * Apply the current provider filter to the given font list
     * @param {Array} fonts
     * @return {Array}
     */
    function _filterProviders(fonts, filterState) {
      var providersString = JSON.stringify($scope.current.providers);
      if (filterState.forceNext || filterState.sortCache.providers !== providersString) {
        filterState.sortCache.providers = providersString;
        filterState.forceNext = true;

        filterState.fontsInProviders = fonts.filter(function(font) {
          return $scope.current.providers[font.provider];
        });
      }

      return filterState.fontsInProviders;
    }

    /**
     * Apply current subset filters to given font list
     * @param  {Array} fonts
     * @return {Array}
     */
    function _filterSubsets(fonts, filterState) {
      var subSetString = JSON.stringify($scope.current.subsets);
      if (filterState.forceNext || filterState.sortCache.subsets !== subSetString) {
        filterState.sortCache.subsets = subSetString;
        filterState.forceNext = true;

        filterState.fontsInSubsets = $filter('hasAllSubsets')(
          fonts,
          $scope.current.subsets
        );
      }

      return filterState.fontsInSubsets;
    }

    /**
     * Apply current sort to given font list
     * @param  {Array} fonts
     * @return {Array}
     */
    function _filterSort(fonts, filterState) {
      var attrDirection = $scope.current.sort.attr.dir;
      var direction = $scope.current.sort.direction;

      if (filterState.forceNext ||
        filterState.sortCache.sortattr !== $scope.current.sort.attr.key ||
        filterState.sortCache.sortdir !== direction)
      {
        filterState.sortCache.sortattr = $scope.current.sort.attr.key;
        filterState.sortCache.sortdir = direction;
        filterState.forceNext = true;

        filterState.sortedFonts = $filter('orderBy')(
          fonts,
          $scope.current.sort.attr.key,
          $scope.current.sort.direction ? attrDirection : !attrDirection
        );
      }

      return filterState.sortedFonts;
    }

    /**
     * Apply current category filters to given font list.
     * @param  {Array} fonts
     * @return {Array}
     */
    function _filterCategory(fonts, filterState) {
      var category = $scope.current.category;
      if (filterState.forceNext || filterState.sortCache.category !== category) {
        filterState.sortCache.category = category;
        filterState.forceNext = true;

        if (angular.isUndefined(category)) {
          filterState.categorizedFonts = fonts;
        } else {
          filterState.categorizedFonts = $filter('filter')(fonts, {category: category}, true);
        }
      }

      return filterState.categorizedFonts;
    }

    /**
     * Apply current search to given font list.
     * @param  {Array} fonts
     * @return {Array}
     */
    function _filterSearch(fonts, filterState) {
      var search = $scope.current.search || '';
      var searchTermChanged = filterState.sortCache.search !== search;
      if (filterState.forceNext || searchTermChanged) {
        filterState.sortCache.search = search;
        filterState.forceNext = true;

        /* Unset category filter so every font is visible. */
        if (searchTermChanged) {
          $scope.current.category = undefined;
        }

        if (search.length) {
          filterState.searchedFonts = _priorize(
            $filter('fuzzySearch')(fonts, {name: search}),
            search.toLowerCase()
          );
        } else {
          filterState.searchedFonts = fonts;
        }
      }

      return filterState.searchedFonts;
    }

    var EMPTY_FILTERED_FONTS = [];

    function filterFontList(fonts, filterState) {
      filterState.forceNext = filterState.sortCache.fontAmount !== fonts.length;
      filterState.sortCache.fontAmount = fonts.length;

      var queue = [
        _filterProviders,
        _filterSubsets,
        _filterSort,
        _filterSearch,
        _filterCategory
      ];

      var filteredList = fonts;
      for (var i = 0, l = queue.length; i < l; i++) {
        filteredList = queue[i](filteredList, filterState);
      }

      return filteredList;
    }

    function convertFontToFontlistEntry(font) {
      return {
        type: FONTLIST_ENTRY_TYPE_FONT,
        content: font
      };
    }

    function createHeadlineEntry(content) {
      return {
        type: FONTLIST_ENTRY_TYPE_HEADLINE,
        content: content
      };
    }

    function createTextEntry(content) {
      return {
        type: FONTLIST_ENTRY_TYPE_TEXT,
        content: content
      };
    }

    var entryMap = new WeakMap();

    $scope.getFontlistEntries = function() {
      var filteredFonts = filterFontList($scope.fonts || EMPTY_FILTERED_FONTS, ALL_FONTS_FILTER_STATE);
      if (!entryMap.has(filteredFonts)) {

        var fontlistEntries = [];
        if (filteredFonts.length === 0) {
          fontlistEntries.push(createTextEntry($scope.text.noResultsLabel));
        } else {
          fontlistEntries = filteredFonts.map(convertFontToFontlistEntry);

          if (jdfsCuratedFonts.length !== 0) {
            var filteredCuratedFonts = filterFontList(jdfsCuratedFonts, CURATED_FONTS_FILTER_STATE);
            fontlistEntries = [createHeadlineEntry($scope.text.curatedFontsListHeadline)]
              .concat(filteredCuratedFonts.map(convertFontToFontlistEntry))
              .concat([createHeadlineEntry($scope.text.allFontsListHeadline)])
              .concat(fontlistEntries);
          }
        }

        fontmeta.total = $scope.fonts.length;
        fontmeta.current = filteredFonts.length;

        _fontlistEntries = fontlistEntries;
        entryMap.set(filteredFonts, fontlistEntries);
      }

      return entryMap.get(filteredFonts);
    };

    /**
     * Convert 'prev' and 'last' to -1 and 1
     * @param  {Number|String} direction
     * @return {Number}
     */
    function _getAmountFromDirection(direction) {
      if (angular.isNumber(direction)) {
        return direction;
      }
      return (direction === DIRECTION_PREVIOUS ? -1 : 1);
    }

    /**
     * Sort a list of fonts by matching them against a given search
     * @param  {Array} fonts
     * @param  {String} search
     * @return {Array}
     */
    function _priorize(fonts, search) {
      if (fonts.length > 1) {
        var rgx = new RegExp('[' + search + ']+');

        fonts.sort(function(a, b) {
          var nameA = a.name.toLowerCase();
          var nameB = b.name.toLowerCase();
          var firstCharA = nameA[0];
          var firstCharB = nameB[0];

          /* Prioritize by first character... */
          if (firstCharA !== firstCharB) {
            if (firstCharA === search[0]) {
              return -1;
            } else if (firstCharB === search[0]) {
              return 1;
            }
          }

          /* Prioritize by amount of matches. */
          return (nameA.replace(rgx, '').length < nameB.replace(rgx, '').length) ? -1 : 1;
        });
      }

      return fonts;
    }

    /**
     * Calculate the amount of pages we have.
     *
     * @return {void}
     */
    function _updatePageCount() {
      _lastPageCount = page.count;

      if (!angular.isArray($scope.fonts)) {
        return 0;
      }

      if (_fontlistEntries.length) {
        page.count = Math.ceil(_fontlistEntries.length / page.size);
      }
    }

    /**
     * Whenever the amount of pages is changing:
     * Make sure we're not staying on a page that does not exist.
     * And if we have a font selected, try to stay on the page of
     * that font.
     *
     * @return {void}
     */
    function _updateCurrentPage() {
      /* do nothing if the amount of pages hasn't change */
      if (_lastPageCount === page.count) {
        return;
      }

      $scope.setCurrentPage(0);
    }
  }
]);
<|endoftext|>


<commit_before>import {parseDOM} from "prosemirror/dist/model/from_dom"
import {Step} from "prosemirror/dist/transform"
import {Paste} from "../paste/paste"
import {COMMENT_ONLY_ROLES} from "../editor"
import {elt} from "prosemirror/dist/util/dom"
import {collabEditing} from "prosemirror/dist/collab"

/* Functions related to the footnote editor instance */
export class ModFootnoteEditor {
    constructor(mod) {
        mod.fnEditor = this
        this.mod = mod
        this.rendering = false
        this.bindEvents()
    }

    bindEvents() {
        let that = this
        this.mod.fnPm.mod.collab.mustSend.add(function() {
            that.footnoteEdit()
        })
        this.mod.fnPm.on.filterTransform.add((transform) => {
            return that.onFilterTransform(transform)
        })
        this.mod.fnPm.on.transformPastedHTML.add((inHTML) => {
            let ph = new Paste(inHTML, "footnote")
            return ph.getOutput()
        })
    }

    // filter transformations, disallowing all transformations going across document parts/footnotes.
    onFilterTransform(transform) {
        let prohibited = false

        if (COMMENT_ONLY_ROLES.indexOf(this.mod.editor.docInfo.right) > -1) {
            prohibited = true
        }

        if (transform.docs[0].childCount !== transform.doc.childCount) {
            prohibited = true
        }
        return prohibited
    }

    footnoteEdit() {
        // Handle an edit in the footnote editor.
        if (this.rendering) {
            // We are currently adding or removing footnotes in the footnote editor
            // due to changes at the footnote marker level, so abort.
            return false
        }
        console.log('footnote update')
        let length = this.mod.fnPm.mod.collab.unconfirmedSteps.length
        let lastStep = this.mod.fnPm.mod.collab.unconfirmedSteps[length - 1]
        if (lastStep.from) {
            // We find the number of the last footnote that was updated by
            // looking at the last step and seeing footnote number that change referred to.
            let updatedFootnote = this.mod.fnPm.doc.resolve(lastStep.from).index(0)
            this.mod.markers.updateFootnoteMarker(updatedFootnote)
        } else {
            // TODO: Figure out if there are cases where this is really needed.
        }
    }

    applyDiffs(diffs) {
        console.log('applying footnote diff')
        let steps = diffs.map(j => Step.fromJSON(this.mod.schema, j))
        let client_ids = diffs.map(j => j.client_id)
        this.mod.fnPm.mod.collab.receive(steps, client_ids)
    }

    renderAllFootnotes() {
        console.log('renderAllFootnotes')
        if (this.mod.markers.checkFootnoteMarkers()) {
            return false
        }
        let that = this
        let footnotes = this.mod.markers.findFootnoteMarkers()

        this.mod.footnotes = footnotes
        this.mod.fnPm.setDoc(this.mod.fnPm.schema.nodeFromJSON({"type":"doc","content":[{"type": "footnote_end"}]}))
        this.mod.footnotes.forEach((footnote, index) => {
            let node = that.mod.editor.pm.doc.nodeAt(footnote.from)
            that.renderFootnote(node.attrs.contents, index)
        })
        let collab = this.mod.fnPm.mod.collab
        collab.versionDoc = this.mod.fnPm.doc
        collab.unconfirmedSteps = []
        collab.unconfirmedMaps = []
        this.bindEvents()
    }

    // Convert the footnote HTML stored with the marker to a PM node representation of the footnote.
    htmlTofootnoteNode(contents) {
        let footnoteDOM = elt('div', {
            class: 'footnote-container'
        })
        footnoteDOM.innerHTML = contents
        let node = parseDOM(this.mod.schema, footnoteDOM, {
            preserveWhitespace: true,
            topNode: false
        })

        return node.firstChild
    }

    renderFootnote(contents, index = 0) {
        this.rendering = true

        let node = this.htmlTofootnoteNode(contents)
        let pos = 0
        for (let i=0; i<index;i++) {
            pos += this.mod.fnPm.doc.child(i).nodeSize
        }

        this.mod.fnPm.tr.insert(pos, node).apply({filter:false})
        // Most changes to the footnotes are followed by a change to the main editor,
        // so changes are sent to collaborators automatically. When footnotes are added/deleted,
        // the change is reverse, so we need to inform collabs manually.
        this.mod.editor.mod.collab.docChanges.sendToCollaborators()
        this.rendering = false

    }

    removeFootnote(footnote) {
        let index = 0
        while (footnote != this.mod.footnotes[index] && this.mod.footnotes.length > index) {
            index++
        }
        this.mod.footnotes.splice(index, 1)
        if (!this.mod.editor.mod.collab.docChanges.receiving) {
            this.rendering = true
            let startPos = 0
            for (let i=0;i<index;i++) {
                startPos += this.mod.fnPm.doc.child(i).nodeSize
            }
            let endPos = startPos + this.mod.fnPm.doc.child(index).nodeSize
            this.mod.fnPm.tr.delete(startPos, endPos).apply({filter:false})
            // Most changes to the footnotes are followed by a change to the main editor,
            // so changes are sent to collaborators automatically. When footnotes are added/deleted,
            // the change is reverse, so we need to inform collabs manually.
            this.mod.editor.mod.collab.docChanges.sendToCollaborators()
            this.rendering = false
        }
    }

}
<commit_msg>fix handling of paste into first footnote and collaboration<commit_after>import {parseDOM} from "prosemirror/dist/model/from_dom"
import {Step} from "prosemirror/dist/transform"
import {Paste} from "../paste/paste"
import {COMMENT_ONLY_ROLES} from "../editor"
import {elt} from "prosemirror/dist/util/dom"
import {collabEditing} from "prosemirror/dist/collab"

/* Functions related to the footnote editor instance */
export class ModFootnoteEditor {
    constructor(mod) {
        mod.fnEditor = this
        this.mod = mod
        this.rendering = false
        this.bindEvents()
    }

    bindEvents() {
        let that = this
        this.mod.fnPm.mod.collab.mustSend.add(function() {
            that.footnoteEdit()
        })
        this.mod.fnPm.on.filterTransform.add((transform) => {
            return that.onFilterTransform(transform)
        })
        this.mod.fnPm.on.transformPastedHTML.add((inHTML) => {
            let ph = new Paste(inHTML, "footnote")
            return ph.getOutput()
        })
    }

    // filter transformations, disallowing all transformations going across document parts/footnotes.
    onFilterTransform(transform) {
        let prohibited = false

        if (COMMENT_ONLY_ROLES.indexOf(this.mod.editor.docInfo.right) > -1) {
            prohibited = true
        }

        if (transform.docs[0].childCount !== transform.doc.childCount) {
            prohibited = true
        }
        return prohibited
    }

    footnoteEdit() {
        // Handle an edit in the footnote editor.
        if (this.rendering) {
            // We are currently adding or removing footnotes in the footnote editor
            // due to changes at the footnote marker level, so abort.
            return false
        }
        console.log('footnote update')
        let length = this.mod.fnPm.mod.collab.unconfirmedSteps.length
        let lastStep = this.mod.fnPm.mod.collab.unconfirmedSteps[length - 1]
        if (lastStep.hasOwnProperty('from')) {
            // We find the number of the last footnote that was updated by
            // looking at the last step and seeing footnote number that change referred to.
            let updatedFootnote = this.mod.fnPm.doc.resolve(lastStep.from).index(0)
            this.mod.markers.updateFootnoteMarker(updatedFootnote)
        } else {
            // TODO: Figure out if there are cases where this is really needed.
        }
    }

    applyDiffs(diffs) {
        console.log('applying footnote diff')
        let steps = diffs.map(j => Step.fromJSON(this.mod.schema, j))
        let client_ids = diffs.map(j => j.client_id)
        this.mod.fnPm.mod.collab.receive(steps, client_ids)
    }

    renderAllFootnotes() {
        console.log('renderAllFootnotes')
        if (this.mod.markers.checkFootnoteMarkers()) {
            return false
        }
        let that = this
        let footnotes = this.mod.markers.findFootnoteMarkers()

        this.mod.footnotes = footnotes
        this.mod.fnPm.setDoc(this.mod.fnPm.schema.nodeFromJSON({"type":"doc","content":[{"type": "footnote_end"}]}))
        this.mod.footnotes.forEach((footnote, index) => {
            let node = that.mod.editor.pm.doc.nodeAt(footnote.from)
            that.renderFootnote(node.attrs.contents, index)
        })
        let collab = this.mod.fnPm.mod.collab
        collab.versionDoc = this.mod.fnPm.doc
        collab.unconfirmedSteps = []
        collab.unconfirmedMaps = []
        this.bindEvents()
    }

    // Convert the footnote HTML stored with the marker to a PM node representation of the footnote.
    htmlTofootnoteNode(contents) {
        let footnoteDOM = elt('div', {
            class: 'footnote-container'
        })
        footnoteDOM.innerHTML = contents
        let node = parseDOM(this.mod.schema, footnoteDOM, {
            preserveWhitespace: true,
            topNode: false
        })

        return node.firstChild
    }

    renderFootnote(contents, index = 0) {
        this.rendering = true

        let node = this.htmlTofootnoteNode(contents)
        let pos = 0
        for (let i=0; i<index;i++) {
            pos += this.mod.fnPm.doc.child(i).nodeSize
        }

        this.mod.fnPm.tr.insert(pos, node).apply({filter:false})
        // Most changes to the footnotes are followed by a change to the main editor,
        // so changes are sent to collaborators automatically. When footnotes are added/deleted,
        // the change is reverse, so we need to inform collabs manually.
        this.mod.editor.mod.collab.docChanges.sendToCollaborators()
        this.rendering = false

    }

    removeFootnote(footnote) {
        let index = 0
        while (footnote != this.mod.footnotes[index] && this.mod.footnotes.length > index) {
            index++
        }
        this.mod.footnotes.splice(index, 1)
        if (!this.mod.editor.mod.collab.docChanges.receiving) {
            this.rendering = true
            let startPos = 0
            for (let i=0;i<index;i++) {
                startPos += this.mod.fnPm.doc.child(i).nodeSize
            }
            let endPos = startPos + this.mod.fnPm.doc.child(index).nodeSize
            this.mod.fnPm.tr.delete(startPos, endPos).apply({filter:false})
            // Most changes to the footnotes are followed by a change to the main editor,
            // so changes are sent to collaborators automatically. When footnotes are added/deleted,
            // the change is reverse, so we need to inform collabs manually.
            this.mod.editor.mod.collab.docChanges.sendToCollaborators()
            this.rendering = false
        }
    }

}
<|endoftext|>


<commit_before>'use strict';

var Color = require( '../math/color' );

var FrontSide  = 0,
    BackSide   = 1,
    DoubleSide = 2;

function Material( options ) {
  options = options || {};

  this.side = options.side || FrontSide;

  this.color = options.color || new Color();
  this.strokeColor = options.strokeColor;

  this.opacity = ( options.opacity !== undefined ) ? options.opacity : 1;
  this.lineWidth = options.lineWidth || 0;

  this.fill = ( options.fill !== undefined ) ? options.fill : true;
  this.wireframe = ( options.wireframe !== undefined ) ? options.wireframe : false;

  this.shadowColor = options.shadowColor || new Color();
  this.shadowBlur = options.shadowBlur || 0;

  this.blending = options.blending || 'source-over';
  this.overdraw = options.overdraw || 0;
}

Material.prototype.set = function( ctx ) {
  var color = this.color.toString();
  var strokeColor = ( this.strokeColor && this.color !== this.strokeColor ) ?
    this.strokeColor.toString() : color;

  ctx.fillStyle = color;
  ctx.strokeStyle = strokeColor;

  ctx.globalAlpha = this.opacity;
  ctx.lineWidth = this.lineWidth;

  ctx.shadowColor = this.shadowColor.toString();
  ctx.shadowBlur = this.shadowBlur;

  ctx.globalCompositeOperation = this.blending;
};

Material.prototype.draw = function( ctx, alpha ) {
  if ( alpha !== undefined ) {
    ctx.globalAlpha = this.opacity * alpha;
  }

  if ( this.fill ) {
    ctx.fill();
  }

  if ( this.wireframe ) {
    ctx.stroke();
  }
};

Material.FrontSide = FrontSide;
Material.BackSide = BackSide;
Material.DoubleSide = DoubleSide;

module.exports = Material;
<commit_msg>Clamp Material globalAlpha to [0, 1].<commit_after>'use strict';

var _ = require( '../utils' );
var Color = require( '../math/color' );

var FrontSide  = 0,
    BackSide   = 1,
    DoubleSide = 2;

function Material( options ) {
  options = options || {};

  this.side = options.side || FrontSide;

  this.color = options.color || new Color();
  this.strokeColor = options.strokeColor;

  this.opacity = ( options.opacity !== undefined ) ? options.opacity : 1;
  this.lineWidth = options.lineWidth || 0;

  this.fill = ( options.fill !== undefined ) ? options.fill : true;
  this.wireframe = ( options.wireframe !== undefined ) ? options.wireframe : false;

  this.shadowColor = options.shadowColor || new Color();
  this.shadowBlur = options.shadowBlur || 0;

  this.blending = options.blending || 'source-over';
  this.overdraw = options.overdraw || 0;
}

Material.prototype.set = function( ctx ) {
  var color = this.color.toString();
  var strokeColor = ( this.strokeColor && this.color !== this.strokeColor ) ?
    this.strokeColor.toString() : color;

  ctx.fillStyle = color;
  ctx.strokeStyle = strokeColor;

  ctx.globalAlpha = this.opacity;
  ctx.lineWidth = this.lineWidth;

  ctx.shadowColor = this.shadowColor.toString();
  ctx.shadowBlur = this.shadowBlur;

  ctx.globalCompositeOperation = this.blending;
};

Material.prototype.draw = function( ctx, alpha ) {
  if ( alpha !== undefined ) {
    ctx.globalAlpha = _.clamp( this.opacity * alpha, 0, 1 );
  }

  if ( this.fill ) {
    ctx.fill();
  }

  if ( this.wireframe ) {
    ctx.stroke();
  }
};

Material.FrontSide = FrontSide;
Material.BackSide = BackSide;
Material.DoubleSide = DoubleSide;

module.exports = Material;
<|endoftext|>


<commit_before>// -------------------------------------------------------------------------------------------------
// OpenSeaMap Water Depth - Web frontend for depth data handling.
//
// Written in 2012 by Dominik Fässler dfa@bezono.org
//
// To the extent possible under law, the author(s) have dedicated all copyright
// and related and neighboring rights to this software to the public domain
// worldwide. This software is distributed without any warranty.
//
// You should have received a copy of the CC0 Public Domain Dedication along
// with this software. If not, see <http://creativecommons.org/publicdomain/zero/1.0/>.
// -------------------------------------------------------------------------------------------------

// this view lists the tracks
OSeaM.views.Tracks = OSeaM.View
		.extend({
			events : {
				'change .oseam-upload-wrapper input' : 'onFileSelected',
				'change .licenseId' : 'onChangeLicenseConfigId',
				'change .vesselId' : 'onChangeVesselConfigId'
			},
			initialize : function() {
				this.listenTo(this.collection, 'add', this.onAddItem);
				 this.listenTo(this.collection, 'reset', this.render);
				OSeaM.frontend.on("change:language", this.render, this);
   		        // stores the item views for this view
				this._views = [];
				this.candidateTrack = new OSeaM.models.Track();

		       var that = this;
		       this.collection.fetch();
				this.listenTo(this.collection, 'remove', this.onRemoveItem);

				this.vessels = new OSeaM.models.Vessels();
		       this.listenTo(this.vessels, 'reset', this.render);
		       this.vessels.fetch();

			   this.licenses = new OSeaM.models.Licenses();
			   this.listenTo(this.licenses, 'reset', this.render);
			   this.licenses.fetch();
			},
			render : function() {
				var wait = '';
				var selection = '';
				var entrees = '';
				var singleConf = '';
				var language = OSeaM.frontend.getLanguage();
				var template = OSeaM.loadTemplate('tracks-' + language);
				var content = $(template());
				OSeaM.frontend.translate(content);
				this.$el.html(content);
				this.listEl = this.$el.find('tbody');
				this._views = [];

		        this.collection.forEach(this.onAddItem, this);
				if(this.vessels.length > 0) {
					this.vesselviews = new OSeaM.views.Selection({el : $("#vesselselection"), collection : this.vessels});
				}
				if(this.licenses) {
					this.licenseviews = new OSeaM.views.Selection({el : $("#licenseselection"), collection : this.licenses});
				}
			},

			onFileSelected : function(evt) {
				// alert('onFileSelected');
				for ( var i = 0; i < evt.target.files.length; i++) {
					var newTrack = new OSeaM.models.Track()
					// get vesselconfig from somewhere
					newTrack.set({
			            fileName : evt.target.files[i].name,
			            status : this.STATUS_STARTING_UPLOAD,
			            license : this.candidateTrack.get('license'),
			            vesselconfigid : this.candidateTrack.get('vesselconfigid')
			        });
					this.collection.add(newTrack);

					// issue a post request
					var jqXHR = newTrack.save({}, {
						// TODO: do something with the error
						error: function(newTrack, xhr, options) {
							this.collection.remove(newTrack);
					        console.log(xhr);
					    },
					    // on success start the progress of upload
					    success: function(newTrack, response, options) {
					    	newTrack.onReaderLoad(null, evt.target.files[i], newTrack.id);
					    }
					});
				}
			},
			onAddItem : function(model) {
				// alert('additem');
				var view = new OSeaM.views.Track({
					model : model
				});
		        // Adding project item view to the list
		        this._views.push(view);

				this.listEl.append(view.render().el);
				return this;
			},
//		    // remove the view from being rendered
		    onRemoveItem: function(model) {
		    	// a vessel item is removed and the appropriate view is added and rendered
		        var view = _(this._views).select(function(cv) {
		        	return cv.model === model; })[0];
		        $(view.el).remove();
		        return this;
		    },
			onValidateMeta : function() {

				this.removeAlerts();
				var errors = [];

				if (OSeaM.utils.Validation.gauge($("#gauge").val()) !== true) {

					this.markInvalid($('#gauge'),
							'1103:Please enter a decimal (e.g. 5.5)');
					// what is this for?
					// errors.push('1004:Email');
				}

				return this.isValid;

			},
			markInvalid : function(field, text) {
				field.parents('.control-group').addClass('error');
				// field.next('.help-inline').attr('data-trt', text);
				this.$el.find('.help-inline').attr('data-trt', text);
				OSeaM.frontend.translate(this.$el);
				this.isValid = false;
			},
			removeAlerts : function() {
				this.$el.find('.alert').remove();
				this.$el.find('.control-group').removeClass('error');
				this.$el.find('.help-inline').removeAttr('data-trt');
				this.$el.find('.help-inline').html('');
				this.isValid = true;
			},

			onChangeVesselConfigId : function() {
				this.candidateTrack.set('vesselconfigid', $("#vesselselection").val());
			},
			onChangeLicenseConfigId : function() {
				this.candidateTrack.set('license', $("#licenseselection").val());
			}
		});
<commit_msg>F: https://github.com/OpenSeaMap/depth/issues/61 ids are being stored to the local storage<commit_after>// -------------------------------------------------------------------------------------------------
// OpenSeaMap Water Depth - Web frontend for depth data handling.
//
// Written in 2012 by Dominik Fässler dfa@bezono.org
//
// To the extent possible under law, the author(s) have dedicated all copyright
// and related and neighboring rights to this software to the public domain
// worldwide. This software is distributed without any warranty.
//
// You should have received a copy of the CC0 Public Domain Dedication along
// with this software. If not, see <http://creativecommons.org/publicdomain/zero/1.0/>.
// -------------------------------------------------------------------------------------------------

// this view lists the tracks
OSeaM.views.Tracks = OSeaM.View
		.extend({
			events : {
				'change .oseam-upload-wrapper input' : 'onFileSelected',
				'change .licenseId' : 'onChangeLicenseConfigId',
				'change .vesselId' : 'onChangeVesselConfigId'
			},
			initialize : function() {
				this.listenTo(this.collection, 'add', this.onAddItem);
				 this.listenTo(this.collection, 'reset', this.render);
				OSeaM.frontend.on("change:language", this.render, this);
   		        // stores the item views for this view
				this._views = [];
				this.candidateTrack = new OSeaM.models.Track();

		       var that = this;
		       this.collection.fetch();
				this.listenTo(this.collection, 'remove', this.onRemoveItem);

				this.vessels = new OSeaM.models.Vessels();
		       this.listenTo(this.vessels, 'reset', this.render);
		       this.vessels.fetch();

			   this.licenses = new OSeaM.models.Licenses();
			   this.listenTo(this.licenses, 'reset', this.render);
			   this.licenses.fetch();
			},
			render : function() {
				var wait = '';
				var selection = '';
				var entrees = '';
				var singleConf = '';
				var language = OSeaM.frontend.getLanguage();
				var template = OSeaM.loadTemplate('tracks-' + language);
				var content = $(template());
				OSeaM.frontend.translate(content);
				this.$el.html(content);
				this.listEl = this.$el.find('tbody');
				this._views = [];

		        this.collection.forEach(this.onAddItem, this);
				if(this.vessels.length > 0) {
					this.vesselviews = new OSeaM.views.Selection({el : $("#vesselselection"), collection : this.vessels});
					$("#vesselselection option[value=" + localStorage.lastvessel + "]").attr("selected", "selected");
				}
				if(this.licenses) {
					this.licenseviews = new OSeaM.views.Selection({el : $("#licenseselection"), collection : this.licenses});
					$("#licenseselection option[value=" + localStorage.lastlicense + "]").attr("selected", "selected");
				}
			},

			onFileSelected : function(evt) {
				// alert('onFileSelected');
				for ( var i = 0; i < evt.target.files.length; i++) {
					var newTrack = new OSeaM.models.Track()
					// get vesselconfig from somewhere
					newTrack.set({
			            fileName : evt.target.files[i].name,
			            status : this.STATUS_STARTING_UPLOAD,
			            license : this.candidateTrack.get('license'),
			            vesselconfigid : this.candidateTrack.get('vesselconfigid')
			        });
					this.collection.add(newTrack);

					// issue a post request
					var jqXHR = newTrack.save({}, {
						// TODO: do something with the error
						error: function(newTrack, xhr, options) {
							this.collection.remove(newTrack);
					        console.log(xhr);
					    },
					    // on success start the progress of upload
					    success: function(newTrack, response, options) {
					    	newTrack.onReaderLoad(null, evt.target.files[i], newTrack.id);
					    }
					});
				}
			},
			onAddItem : function(model) {
				// alert('additem');
				var view = new OSeaM.views.Track({
					model : model
				});
		        // Adding project item view to the list
		        this._views.push(view);

				this.listEl.append(view.render().el);
				return this;
			},
//		    // remove the view from being rendered
		    onRemoveItem: function(model) {
		    	// a vessel item is removed and the appropriate view is added and rendered
		        var view = _(this._views).select(function(cv) {
		        	return cv.model === model; })[0];
		        $(view.el).remove();
		        return this;
		    },
			onValidateMeta : function() {

				this.removeAlerts();
				var errors = [];

				if (OSeaM.utils.Validation.gauge($("#gauge").val()) !== true) {

					this.markInvalid($('#gauge'),
							'1103:Please enter a decimal (e.g. 5.5)');
					// what is this for?
					// errors.push('1004:Email');
				}

				return this.isValid;

			},
			markInvalid : function(field, text) {
				field.parents('.control-group').addClass('error');
				// field.next('.help-inline').attr('data-trt', text);
				this.$el.find('.help-inline').attr('data-trt', text);
				OSeaM.frontend.translate(this.$el);
				this.isValid = false;
			},
			removeAlerts : function() {
				this.$el.find('.alert').remove();
				this.$el.find('.control-group').removeClass('error');
				this.$el.find('.help-inline').removeAttr('data-trt');
				this.$el.find('.help-inline').html('');
				this.isValid = true;
			},

			onChangeVesselConfigId : function() {
				this.candidateTrack.set('vesselconfigid', $("#vesselselection").val());
				localStorage.lastvessel = $("#vesselselection").val();
			},
			onChangeLicenseConfigId : function() {
				this.candidateTrack.set('license', $("#licenseselection").val());
				localStorage.lastlicense = $("#licenseselection").val();
			}
		});
<|endoftext|>


<commit_before>// declare global variables
var MONEY = new Money;
var VALIDATOR = new Validator;
var SalePage = new Page;
// declare Data Model
SalePage.sale = new Sale();
SalePage.sale.saleDetailsMap = new Map();
SalePage.sale.saleDetailsMap.put(0, new SaleDetail(null, '', '', 1, 0, 0, false));
// declare Page state variables
SalePage.lastSaleDetailKey = -1;

// declare Page event handlers
SalePage.updateTotalPrice = function() {
   var totalPrice = SalePage.sale.getTotalPrice();
   var exchange = parseFloat($('#payment').val()) - totalPrice;
   $('#totalPrice').text(MONEY.display(totalPrice));
   $('#exchange').text(MONEY.display(exchange));
};
SalePage.updateRowAndTotalPrice = function(rowKey) {
   var saleDetail = SalePage.sale.saleDetailsMap.get(rowKey);
   $('#pricePerUnit-'+rowKey).text(MONEY.display(saleDetail.pricePerUnit));
   $('#pricePerItemTotal-'+rowKey).text(MONEY.display(saleDetail.getTotalPrice()));
   SalePage.updateTotalPrice();
};
SalePage.updateQuantity = function(event) {
   var splitted = $(event.target).attr('id').split('-');
   var rowKey = parseInt(splitted[1]);
   var saleDetail = SalePage.sale.saleDetailsMap.get(rowKey);
   saleDetail.quantity = $(event.target).val();

   SalePage.updateRowAndTotalPrice(rowKey);
};
SalePage.validateQuantity = function(event) {
   var splitted = $(event.target).attr('id').split('-');
   var rowKey = parseInt(splitted[1]);
   var saleDetail = SalePage.sale.saleDetailsMap.get(rowKey);

   if (VALIDATOR.validatePositiveInteger(saleDetail.quantity)) {
	  $('#quantityDanger-'+rowKey).remove();
   } else {
	  if ($('#quantityDanger-'+rowKey).length == 0) {
	     var messageHtml = '
Input jumlah barang pada baris {rowNumber} tidak valid!
';
	     var substitute = {'rowKey': rowKey};
	     substitute.rowNumber = SalePage.sale.saleDetailsMap.getKeyIndex(rowKey)+1;
	     $('#errorMessage').append(SalePage.substitute(messageHtml, substitute));
      }
	  $(event.target).addClass('alert alert-danger');
   }
};
SalePage.removeQuantityDanger = function(event) {
   $(event.target).removeClass('alert alert-danger');
}
SalePage.showCustomPriceModal = function(event) {
   $('#customPriceModal').modal('show');
   var rowKey = parseInt($(event.target).val());
   $('#setCustomPriceButton').val(rowKey);
   $('#cancelCustomPriceButton').val(rowKey);
   $('#inputCustomPrice').val(SalePage.sale.saleDetailsMap.get(rowKey).pricePerUnit);
};
SalePage.setCustomPrice = function() {
   var inputCustomPrice = $('#inputCustomPrice').val();
   if (VALIDATOR.validatePositiveNumber(inputCustomPrice)) {
      var rowKey = parseInt($('#setCustomPriceButton').val());
      var saleDetail = SalePage.sale.saleDetailsMap.get(rowKey);
      saleDetail.pricePerUnit = parseFloat(inputCustomPrice);
      saleDetail.isCustomPrice = true;

      SalePage.updateRowAndTotalPrice(rowKey);
      $('#customPriceModal').modal('hide');
   } else {
      alert('Input harga per satuan tidak valid!');
	  $('#inputCustomPrice').focus();
   }
};
SalePage.addSaleDetailRow =  function() {
   var substitutes = {};
   substitutes['rowKey'] = ++SalePage.lastSaleDetailKey;
   SalePage.sale.saleDetailsMap.put(substitutes['rowKey'], new SaleDetail(null, '', '', 1, 0, 0, false));
   substitutes['rowNumber'] = SalePage.sale.saleDetailsMap.size();
   var rowHtml = SalePage.substitute($('#saleDetailTemplate').html(), substitutes);
   $('#saleTableBody').append(rowHtml);

   // unless initial load set focus to the first row input
   if (substitutes['rowKey'] > 0) {
      $('#itemBarcode-'+substitutes['rowKey']).focus();
   }
};
SalePage.confirmDeleteSaleDetailRow =  function(event) {
   if (SalePage.sale.saleDetailsMap.size() > 1) {
      $('#deleteRowWarning').modal('show');
	  var rowKey = $(event.target).val();
	  $('#confirmDeleteRowButton').val(rowKey);
	  $('#cancelDeleteRowButton').val(rowKey);
   }
};
SalePage.deleteSaleDetailRow =  function(event) {
   var rowKey = parseInt($(event.target).val());
   SalePage.sale.saleDetailsMap.remove(rowKey);

   $('#saleDetail-'+rowKey).slideUp('500', function() {$('#saleDetail-'+rowKey).remove()});
   $('#deleteRowWarning').modal('hide');
   SalePage.updateTotalPrice();
   $('#-'+rowKey).remove();

   if (rowKey != SalePage.lastSaleDetailKey) {
      SalePage.restartRowNumbering();
   }
};
SalePage.restartRowNumbering = function() {
   var rowKeys = SalePage.sale.saleDetailsMap.getKeys();
   var rowKeysCount = rowKeys.length;

   for (var index=0; index<rowKeysCount; index++) {
      $('#itemNumber-'+rowKeys[index]).text(index+1);
   }
};
SalePage.updateExchange = function(event) {
   var payment = parseFloat($(event.target).val());
   SalePage.sale.payment = payment;
   var exchange = payment - SalePage.sale.getTotalPrice();
   $('#exchange').text(MONEY.display(exchange));
};
SalePage.validatePayment = function(event) {
   var payment = parseFloat($(event.target).val());

   if (isNaN(payment) || !VALIDATOR.validateNonNegativeNumber(payment)) {
	  if ($('#paymentDanger').length == 0) {
	     $('#errorMessage').append('
Input pembayaran tidak valid
');
      }
	  $(event.target).addClass('alert alert-danger');
   } else {
	  $('#paymentDanger').remove();
   }
};
SalePage.removePaymentDanger = function(event) {
   $(event.target).removeClass('alert alert-danger');
}

// EVENT BINDINGS
// quantity updated
$(document).on('focus', '.quantityInput', null, SalePage.removeQuantityDanger);
$(document).on('change', '.quantityInput', null, SalePage.updateQuantity);
$(document).on('blur', '.quantityInput', null, SalePage.validateQuantity);
// show custom price modal
$(document).on('click', '.customPriceButton', null, SalePage.showCustomPriceModal);
// set custom price
$('#customPriceForm').submit(SalePage.setCustomPrice);
$('#setCustomPriceButton').click(SalePage.setCustomPrice);
// add row
$('#addRowButton').click(SalePage.addSaleDetailRow);
// confirm delete row
$(document).on('click', '.deleteRowButton', null, SalePage.confirmDeleteSaleDetailRow);
// delete row
$('#confirmDeleteRowButton').click(SalePage.deleteSaleDetailRow);
// payment updated
$('#payment').focus(SalePage.removePaymentDanger);
$('#payment').change(SalePage.updateExchange);
$('#payment').blur(SalePage.validatePayment);

// Boostrap customization
$('#customPriceModal').on('shown.bs.modal',
   function(event) {
	  $('#inputCustomPrice').focus();
   }
);

$('#customPriceModal').on('hidden.bs.modal',
   function() {
      var rowKey = parseInt($('#setCustomPriceButton').val());
	  $('#customPriceButton-'+rowKey).focus();
   }
);

$('#deleteRowWarning').on('shown.bs.modal',
   function() {
	  $('#confirmDeleteRowButton').focus();
   }
);

$('#deleteRowWarning').on('hidden.bs.modal',
   function() {
      var rowKey = parseInt($('#confirmDeleteRowButton').val());

	  if (SalePage.sale.saleDetailsMap.containsKey(rowKey)) {
	     $('#deleteRowButton-'+rowKey).focus();
	  } else {
	     $('#addRowButton').focus();
	  }
   }
);

// insert initial dynamic element
SalePage.addSaleDetailRow();
$('#customer-name').focus();<commit_msg>Remove useless saleDetailsMap element filling.<commit_after>// declare global variables
var MONEY = new Money;
var VALIDATOR = new Validator;
var SalePage = new Page;
// declare Data Model
SalePage.sale = new Sale;
SalePage.sale.saleDetailsMap = new Map;
// declare Page state variables
SalePage.lastSaleDetailKey = -1;

// declare Page event handlers
SalePage.updateTotalPrice = function() {
   var totalPrice = SalePage.sale.getTotalPrice();
   var exchange = parseFloat($('#payment').val()) - totalPrice;
   $('#totalPrice').text(MONEY.display(totalPrice));
   $('#exchange').text(MONEY.display(exchange));
};
SalePage.updateRowAndTotalPrice = function(rowKey) {
   var saleDetail = SalePage.sale.saleDetailsMap.get(rowKey);
   $('#pricePerUnit-'+rowKey).text(MONEY.display(saleDetail.pricePerUnit));
   $('#pricePerItemTotal-'+rowKey).text(MONEY.display(saleDetail.getTotalPrice()));
   SalePage.updateTotalPrice();
};
SalePage.updateQuantity = function(event) {
   var splitted = $(event.target).attr('id').split('-');
   var rowKey = parseInt(splitted[1]);
   var saleDetail = SalePage.sale.saleDetailsMap.get(rowKey);
   saleDetail.quantity = $(event.target).val();

   SalePage.updateRowAndTotalPrice(rowKey);
};
SalePage.validateQuantity = function(event) {
   var splitted = $(event.target).attr('id').split('-');
   var rowKey = parseInt(splitted[1]);
   var saleDetail = SalePage.sale.saleDetailsMap.get(rowKey);

   if (VALIDATOR.validatePositiveInteger(saleDetail.quantity)) {
	  $('#quantityDanger-'+rowKey).remove();
   } else {
	  if ($('#quantityDanger-'+rowKey).length == 0) {
	     var messageHtml = '
Input jumlah barang pada baris {rowNumber} tidak valid!
';
	     var substitute = {'rowKey': rowKey};
	     substitute.rowNumber = SalePage.sale.saleDetailsMap.getKeyIndex(rowKey)+1;
	     $('#errorMessage').append(SalePage.substitute(messageHtml, substitute));
      }
	  $(event.target).addClass('alert alert-danger');
   }
};
SalePage.removeQuantityDanger = function(event) {
   $(event.target).removeClass('alert alert-danger');
}
SalePage.showCustomPriceModal = function(event) {
   $('#customPriceModal').modal('show');
   var rowKey = parseInt($(event.target).val());
   $('#setCustomPriceButton').val(rowKey);
   $('#cancelCustomPriceButton').val(rowKey);
   $('#inputCustomPrice').val(SalePage.sale.saleDetailsMap.get(rowKey).pricePerUnit);
};
SalePage.setCustomPrice = function() {
   var inputCustomPrice = $('#inputCustomPrice').val();
   if (VALIDATOR.validatePositiveNumber(inputCustomPrice)) {
      var rowKey = parseInt($('#setCustomPriceButton').val());
      var saleDetail = SalePage.sale.saleDetailsMap.get(rowKey);
      saleDetail.pricePerUnit = parseFloat(inputCustomPrice);
      saleDetail.isCustomPrice = true;

      SalePage.updateRowAndTotalPrice(rowKey);
      $('#customPriceModal').modal('hide');
   } else {
      alert('Input harga per satuan tidak valid!');
	  $('#inputCustomPrice').focus();
   }
};
SalePage.addSaleDetailRow =  function() {
   var substitutes = {};
   substitutes['rowKey'] = ++SalePage.lastSaleDetailKey;
   SalePage.sale.saleDetailsMap.put(substitutes['rowKey'], new SaleDetail(null, '', '', 1, 0, 0, false));
   substitutes['rowNumber'] = SalePage.sale.saleDetailsMap.size();
   var rowHtml = SalePage.substitute($('#saleDetailTemplate').html(), substitutes);
   $('#saleTableBody').append(rowHtml);

   // unless initial load set focus to the first row input
   if (substitutes['rowKey'] > 0) {
      $('#itemBarcode-'+substitutes['rowKey']).focus();
   }
};
SalePage.confirmDeleteSaleDetailRow =  function(event) {
   if (SalePage.sale.saleDetailsMap.size() > 1) {
      $('#deleteRowWarning').modal('show');
	  var rowKey = $(event.target).val();
	  $('#confirmDeleteRowButton').val(rowKey);
	  $('#cancelDeleteRowButton').val(rowKey);
   }
};
SalePage.deleteSaleDetailRow =  function(event) {
   var rowKey = parseInt($(event.target).val());
   SalePage.sale.saleDetailsMap.remove(rowKey);

   $('#saleDetail-'+rowKey).slideUp('500', function() {$('#saleDetail-'+rowKey).remove()});
   $('#deleteRowWarning').modal('hide');
   SalePage.updateTotalPrice();
   $('#-'+rowKey).remove();

   if (rowKey != SalePage.lastSaleDetailKey) {
      SalePage.restartRowNumbering();
   }
};
SalePage.restartRowNumbering = function() {
   var rowKeys = SalePage.sale.saleDetailsMap.getKeys();
   var rowKeysCount = rowKeys.length;

   for (var index=0; index<rowKeysCount; index++) {
      $('#itemNumber-'+rowKeys[index]).text(index+1);
   }
};
SalePage.updateExchange = function(event) {
   var payment = parseFloat($(event.target).val());
   SalePage.sale.payment = payment;
   var exchange = payment - SalePage.sale.getTotalPrice();
   $('#exchange').text(MONEY.display(exchange));
};
SalePage.validatePayment = function(event) {
   var payment = parseFloat($(event.target).val());

   if (isNaN(payment) || !VALIDATOR.validateNonNegativeNumber(payment)) {
	  if ($('#paymentDanger').length == 0) {
	     $('#errorMessage').append('
Input pembayaran tidak valid
');
      }
	  $(event.target).addClass('alert alert-danger');
   } else {
	  $('#paymentDanger').remove();
   }
};
SalePage.removePaymentDanger = function(event) {
   $(event.target).removeClass('alert alert-danger');
}

// EVENT BINDINGS
// quantity updated
$(document).on('focus', '.quantityInput', null, SalePage.removeQuantityDanger);
$(document).on('change', '.quantityInput', null, SalePage.updateQuantity);
$(document).on('blur', '.quantityInput', null, SalePage.validateQuantity);
// show custom price modal
$(document).on('click', '.customPriceButton', null, SalePage.showCustomPriceModal);
// set custom price
$('#customPriceForm').submit(SalePage.setCustomPrice);
$('#setCustomPriceButton').click(SalePage.setCustomPrice);
// add row
$('#addRowButton').click(SalePage.addSaleDetailRow);
// confirm delete row
$(document).on('click', '.deleteRowButton', null, SalePage.confirmDeleteSaleDetailRow);
// delete row
$('#confirmDeleteRowButton').click(SalePage.deleteSaleDetailRow);
// payment updated
$('#payment').focus(SalePage.removePaymentDanger);
$('#payment').change(SalePage.updateExchange);
$('#payment').blur(SalePage.validatePayment);

// Boostrap customization
$('#customPriceModal').on('shown.bs.modal',
   function(event) {
	  $('#inputCustomPrice').focus();
   }
);

$('#customPriceModal').on('hidden.bs.modal',
   function() {
      var rowKey = parseInt($('#setCustomPriceButton').val());
	  $('#customPriceButton-'+rowKey).focus();
   }
);

$('#deleteRowWarning').on('shown.bs.modal',
   function() {
	  $('#confirmDeleteRowButton').focus();
   }
);

$('#deleteRowWarning').on('hidden.bs.modal',
   function() {
      var rowKey = parseInt($('#confirmDeleteRowButton').val());

	  if (SalePage.sale.saleDetailsMap.containsKey(rowKey)) {
	     $('#deleteRowButton-'+rowKey).focus();
	  } else {
	     $('#addRowButton').focus();
	  }
   }
);

// insert initial dynamic element
SalePage.addSaleDetailRow();
$('#customer-name').focus();<|endoftext|>


<commit_before>var a = {
	newCalendar: function(events) {
		var cal = {
			domain: 'localhost',
			prodid: '//sebbo.net//ical-generator//EN',

			generated: null,
			events: [],

			methods: {
				setDomain: function(domain) {
					cal.domain = domain.toString();
					return cal.methods;
				},
				setProdID: function(id) {
					if(!id.company) {
						throw 'prodid.company is a mandatory item.';
					}
					if(!id.product) {
						throw 'prodid.product is a mandatory item.';
					}
					id.language = (id.language || 'EN').toUpperCase();

					cal.prodid = '//' + id.company + '//' + id.product + '//' + id.language;
					return cal.methods;
				},
				addEvent: function(e) {
					var _event = {};

					// UID
					_event.uid = e.uid || ("0000" + (Math.random()*Math.pow(36,4) << 0).toString(36)).substr(-4);

					// Date Start
					if(!e.start) {
						throw 'event.start is a mandatory item.';
					}
					if(Object.prototype.toString.call(e.start) != '[object Date]') {
						throw 'event.start must be a Date Object.';
					}
					_event.start = e.start;

					// Date Stop
					if(!e.end) {
						throw 'event.end is a mandatory item.';
					}
					if(Object.prototype.toString.call(e.end) != '[object Date]') {
						throw 'event.end must be a Date Object.';
					}
					_event.end = e.end;

					// Date Stamp
					if(e.stamp && Object.prototype.toString.call(e.stamp) != '[object Date]') {
						throw 'event.stamp must be a Date Object.';
					}
					_event.stamp = e.stamp || new Date();

					// Summary
					if(!e.summary) {
						throw 'event.summary is a mandatory item.';
					}
					_event.summary = e.summary;

					// Description
					_event.description = e.description || null;

					// Organizer
					_event.organizer = null;
					if(e.organizer) {
						if(!e.organizer.name) {
							throw 'event.organizer.name is empty.';
						}
						if(!e.organizer.email) {
							throw 'event.organizer.email is a empty.';
						}

						_event.organizer = {
							name: e.organizer.name,
							email: e.organizer.email
						};
					}

					// URL
					_event.url = e.url || null;

					cal.events.push(_event);
					return cal.methods;
				},
				generate: function() {
					var g;

					var _formatDate = function(d, dateonly) {
						var pad = function(i) {
							return (i < 10 ? '0': '') + i;
						};

						var s = d.getFullYear();
						s += pad(d.getMonth() + 1);
						s += pad(d.getDate());

						if(!dateonly) {
							s += 'T';
							s += pad(d.getHours());
							s += pad(d.getMinutes());
							s += pad(d.getSeconds());
						}

						return s;
					};

					var _getUID = function(e) {
						return _formatDate(e.start) + 'Z-' + e.uid + '@' + cal.domain;
					};

					// VCALENDAR and VERSION
					g = 'BEGIN:VCALENDAR\nVERSION:2.0\n';

					// PRODID
					g += 'PRODID:-' + cal.prodid + '\n';

					// Domain
					if(!cal.domain) {
						cal.domain = require("os").hostname() || 'localhost';
					}

					// Events
					cal.events.forEach(function(e) {
						g += 'BEGIN:VEVENT\n';
						g += 'UID:' + _getUID(e) + '\n';
						g += 'DTSTAMP:' + _formatDate(e.stamp) + '\n';
						g += 'DTSTART:' + _formatDate(e.start) + '\n';
						g += 'DTEND:' + _formatDate(e.end) + '\n';

						if(e.summary) g += 'SUMMARY:' + e.summary + '\n';
						if(e.description) g += 'DESCRIPTION:' + e.description + '\n';
						if(e.organizer) g += 'ORGANIZER;CN="' + e.organizer.name.replace(/\"/g, '\\"') + '":mailto:' + e.organizer.email + '\n';
						if(e.url) g += 'URL;VALUE=URI:' + e.url + '\n';

						g += 'END:VEVENT\n';
					});

					g += 'END:VCALENDAR';
					cal.generated = g;

					return cal.methods;
				},
				save: function(path, cb) {
					if(!cal.generated) cal.methods.generate();
					require('fs').writeFile(path, cal.generated, cb || function(){});
					return cal.methods;
				},
				saveSync: function(path) {
					if(!cal.generated) cal.methods.generate();
					return require('fs').writeFileSync(path, cal.generated);
				},
				serve: function(res) {
					if(!cal.generated) cal.methods.generate();

					res.writeHead(200, {
						'Content-Type': 'text/calendar',
						'Content-Disposition': 'attachment; filename="calendar.ics"'
					});
					res.end(cal.generated);
					return cal.methods;
				},
				toString: function() {
					if(!cal.generated) cal.methods.generate();
					return cal.generated;
				}
			}
		};

		return cal.methods;
	}
};

module.exports = a.newCalendar;<commit_msg>Bugfix<commit_after>var a = {
	newCalendar: function(events) {
		var cal = {
			domain: 'localhost',
			prodid: '//sebbo.net//ical-generator//EN',

			generated: null,
			events: [],

			methods: {
				setDomain: function(domain) {
					cal.domain = domain.toString();
					return cal.methods;
				},
				setProdID: function(id) {
					if(!id.company) {
						throw 'prodid.company is a mandatory item.';
					}
					if(!id.product) {
						throw 'prodid.product is a mandatory item.';
					}
					id.language = (id.language || 'EN').toUpperCase();

					cal.prodid = '//' + id.company + '//' + id.product + '//' + id.language;
					return cal.methods;
				},
				addEvent: function(e) {
					var _event = {};

					// UID
					_event.uid = e.uid || ("0000" + (Math.random()*Math.pow(36,4) << 0).toString(36)).substr(-4);

					// Date Start
					if(!e.start) {
						throw 'event.start is a mandatory item.';
					}
					if(Object.prototype.toString.call(e.start) != '[object Date]') {
						throw 'event.start must be a Date Object.';
					}
					_event.start = e.start;

					// Date Stop
					if(!e.end) {
						throw 'event.end is a mandatory item.';
					}
					if(Object.prototype.toString.call(e.end) != '[object Date]') {
						throw 'event.end must be a Date Object.';
					}
					_event.end = e.end;

					// Date Stamp
					if(e.stamp && Object.prototype.toString.call(e.stamp) != '[object Date]') {
						throw 'event.stamp must be a Date Object.';
					}
					_event.stamp = e.stamp || new Date();

					// Summary
					if(!e.summary) {
						throw 'event.summary is a mandatory item.';
					}
					_event.summary = e.summary;

					// Description
					_event.description = e.description || null;

					// Organizer
					_event.organizer = null;
					if(e.organizer) {
						if(!e.organizer.name) {
							throw 'event.organizer.name is empty.';
						}
						if(!e.organizer.email) {
							throw 'event.organizer.email is a empty.';
						}

						_event.organizer = {
							name: e.organizer.name,
							email: e.organizer.email
						};
					}

					// URL
					_event.url = e.url || null;

					cal.generated = null;
					cal.events.push(_event);
					return cal.methods;
				},
				generate: function() {
					var g;

					var _formatDate = function(d, dateonly) {
						var pad = function(i) {
							return (i < 10 ? '0': '') + i;
						};

						var s = d.getFullYear();
						s += pad(d.getMonth() + 1);
						s += pad(d.getDate());

						if(!dateonly) {
							s += 'T';
							s += pad(d.getHours());
							s += pad(d.getMinutes());
							s += pad(d.getSeconds());
						}

						return s;
					};

					var _getUID = function(e) {
						return _formatDate(e.start) + 'Z-' + e.uid + '@' + cal.domain;
					};

					// VCALENDAR and VERSION
					g = 'BEGIN:VCALENDAR\nVERSION:2.0\n';

					// PRODID
					g += 'PRODID:-' + cal.prodid + '\n';

					// Domain
					if(!cal.domain) {
						cal.domain = require("os").hostname() || 'localhost';
					}

					// Events
					cal.events.forEach(function(e) {
						g += 'BEGIN:VEVENT\n';
						g += 'UID:' + _getUID(e) + '\n';
						g += 'DTSTAMP:' + _formatDate(e.stamp) + '\n';
						g += 'DTSTART:' + _formatDate(e.start) + '\n';
						g += 'DTEND:' + _formatDate(e.end) + '\n';

						if(e.summary) g += 'SUMMARY:' + e.summary + '\n';
						if(e.description) g += 'DESCRIPTION:' + e.description + '\n';
						if(e.organizer) g += 'ORGANIZER;CN="' + e.organizer.name.replace(/\"/g, '\\"') + '":mailto:' + e.organizer.email + '\n';
						if(e.url) g += 'URL;VALUE=URI:' + e.url + '\n';

						g += 'END:VEVENT\n';
					});

					g += 'END:VCALENDAR';
					cal.generated = g;

					return cal.methods;
				},
				save: function(path, cb) {
					if(!cal.generated) cal.methods.generate();
					require('fs').writeFile(path, cal.generated, cb || function(){});
					return cal.methods;
				},
				saveSync: function(path) {
					if(!cal.generated) cal.methods.generate();
					return require('fs').writeFileSync(path, cal.generated);
				},
				serve: function(res) {
					if(!cal.generated) cal.methods.generate();

					res.writeHead(200, {
						'Content-Type': 'text/calendar',
						'Content-Disposition': 'attachment; filename="calendar.ics"'
					});
					res.end(cal.generated);
					return cal.methods;
				},
				toString: function() {
					if(!cal.generated) cal.methods.generate();
					return cal.generated;
				}
			}
		};

		return cal.methods;
	}
};

module.exports = a.newCalendar;<|endoftext|>


<commit_before>'use strict';

var AppDispatcher = require('../dispatchers/app-dispatcher');
var assign = require('object-assign');
var _ = require('lodash');
var EventEmitter = require('events').EventEmitter;
var ActionTypes = require('../constants/scene-constants').ActionTypes;
var HubClient = require('../utils/HubClient');
var CHANGE_EVENT = "change";
var _scenes = {};

function _updateScene (scene) {
    _scenes[scene._id] = scene;
}

var SceneStore = assign({}, EventEmitter.prototype, {
    getScene: function(id) {
        if (_scenes.hasOwnProperty(id)) {
            return _.cloneDeep(_scenes[id]);
        }
    },
    emitChange: function(){
        this.emit(CHANGE_EVENT);
    },

    addChangeListener: function(callback){
        this.on(CHANGE_EVENT, callback);
    },

    removeChangeListener: function(callback){
        this.removeListener(CHANGE_EVENT, callback);
    },

    dispatcherIndex: AppDispatcher.register(function(payload){
        var action = payload.action; // this is our action from handleViewAction
        switch(action.type){
            case ActionTypes.SCENE_CHANGE:
                _updateScene(action.scene);
                SceneStore.emitChange();
                break;

            case ActionTypes.DELETE_SCENE:
                var sceneId = action.sceneId;
                delete _scenes[sceneId];
                HubClient.deleteScene(sceneId);
                break;

            case ActionTypes.ADD_MEDIA_OBJECT:
                var scene = _scenes[action.sceneId];
                scene.scene.push(action.mediaObject);
                HubClient.save(scene);
                break;

            // should only be triggered when server sends data back, so no need to save
            case ActionTypes.RECIEVE_SCENE:
                _updateScene(action.scene);
                SceneStore.emitChange();
                break;
        }

        SceneStore.emitChange();

        return true;
    })
});

module.exports = SceneStore;
<commit_msg>Delete scene bug - unusual null so overridden using the usage of require to reimport the HubClient<commit_after>'use strict';

var AppDispatcher = require('../dispatchers/app-dispatcher');
var assign = require('object-assign');
var _ = require('lodash');
var EventEmitter = require('events').EventEmitter;
var ActionTypes = require('../constants/scene-constants').ActionTypes;
var HubClient = require('../utils/HubClient');
var CHANGE_EVENT = "change";
var _scenes = {};

function _updateScene (scene) {
    _scenes[scene._id] = scene;
}

var SceneStore = assign({}, EventEmitter.prototype, {
    getScene: function(id) {
        if (_scenes.hasOwnProperty(id)) {
            return _.cloneDeep(_scenes[id]);
        }
    },
    emitChange: function(){
        this.emit(CHANGE_EVENT);
    },

    addChangeListener: function(callback){
        this.on(CHANGE_EVENT, callback);
    },

    removeChangeListener: function(callback){
        this.removeListener(CHANGE_EVENT, callback);
    },

    dispatcherIndex: AppDispatcher.register(function(payload){
        var action = payload.action; // this is our action from handleViewAction

        var hC = HubClient;

        switch(action.type){
            case ActionTypes.SCENE_CHANGE:
                _updateScene(action.scene);
                SceneStore.emitChange();
                break;

            case ActionTypes.DELETE_SCENE:
                console.log("SceneStore - DELETE SCENE");
                try {
                    var sceneId = action.sceneId;
                    //APEP Unsure why but HubClient is null here.. so have had to hack and use require
                    require('../utils/HubClient').deleteScene(sceneId);
                    delete _scenes[sceneId];
                    SceneStore.emitChange();
                } catch (e) {
                    alert(e);
                    throw e;
                }

                break;

            case ActionTypes.ADD_MEDIA_OBJECT:
                var scene = _scenes[action.sceneId];
                scene.scene.push(action.mediaObject);
                HubClient.save(scene);
                break;

            // should only be triggered when server sends data back, so no need to save
            case ActionTypes.RECIEVE_SCENE:
                _updateScene(action.scene);
                SceneStore.emitChange();
                break;
        }

        SceneStore.emitChange();

        return true;
    })
});

module.exports = SceneStore;
<|endoftext|>


<commit_before>(function(exports){

    var savedSnapshot, savedEditorValue, savedTerminal, dag;

    function init(terminal) {
        savedTerminal = terminal;
    }

    function getSnapshotSize() {
        if (savedSnapshot === undefined) {
            return 0;
        } else {
            return savedSnapshot.length;
        }
    }

    function getNextValidEdit(from, to) {
    	if (savedSnapshot === undefined || dag === undefined) {
			return undefined;
    	}

		for (; from < savedSnapshot.length && from <= to; ++from) {
			// if dependency is out of range
			if (dag[from] > to || from >= dag[from]) {
				var text = normalizeObject(savedSnapshot[from].replace);
		    	var fromLine = savedSnapshot[from].range.from.line;
		    	var fromCh = savedSnapshot[from].range.from.ch;
		    	var toLine = savedSnapshot[from].range.to.line;
		    	var toCh = savedSnapshot[from].range.to.ch;
		    	return {text: text, fromLine: fromLine, toLine: toLine, fromCh: fromCh, toCh: toCh, i: from};
	    	}

		}

    	return undefined;
    }

    function showUpTo(x) {
    	if (savedSnapshot === undefined ||
    		savedEditorValue === undefined ||
    		savedTerminal === undefined ||
    		dag === undefined)
		{
			return;
		}

		// out of range, do nothing
    	if (x >= savedSnapshot.length && x < 0) {
    		return;
    	}

        var line = 0, ch = 0, j = 0, i = 0, fragments = [];
        // i = index for snapshot, j = index for editorValue
        var nextEdit = getNextValidEdit(i, x);
    	var editorValue = savedEditorValue;
    	// always have a valid edit if there is at least one edit
    	i = nextEdit.i;
        while (j < editorValue.length && nextEdit !== undefined) {
            if (editorValue[j] == '\n') {
                // reset count
                line += 1;
                ch = 0;
            }
            else {
                if (line == nextEdit.fromLine && ch == nextEdit.fromCh) {
                    fragments.push(editorValue.substring(0,j));
                    editorValue = editorValue.substring(j);
                    j = 0; // reset j counter, because editorvalue change
                }
                if (line == nextEdit.toLine && ch == nextEdit.toCh) {
                    fragments.push(nextEdit.text);
                    editorValue = editorValue.substring(j);
                    j = 0; // reset j counter, because editorValue change

                    // Get next edit
                    i += 1;
                    nextEdit = getNextValidEdit(i, x);
                    if (nextEdit === undefined)
                    	break;
                    i = nextEdit.i;
                }
                // increase ch count
                ++ch;
            }
            ++j;
        }

        editorValue = fragments.join('') + editorValue;
        savedTerminal.setValue(editorValue);

    }

    function align(editorValue,  snapshot) {
        if (snapshot.length === 0) {
            return;
        }

        // save variable
        savedEditorValue = editorValue;
        savedSnapshot = snapshot;
		dag = Tracer.buildDAG(snapshot);
		showUpTo(snapshot.length-1);
    }

    function normalizeObject(replace) {
        if (typeof(replace) === "function") {
            return "function()";
        } else if (typeof(replace) === "object") {
            return JSON.stringify(replace);
        } else {
            // if the replacement text is a string
            return replace.toString();
        }
    }

    function rewind(x) {
    	showUpTo(x);
    }

    function forward(x) {
		showUpTo(x);
    }

    exports.align = align;
    exports.init = init;
    exports.getSnapshotSize = getSnapshotSize;
    exports.rewind = rewind;
    exports.forward = forward;

}(typeof exports === 'undefined'? Format = {}: exports));
<commit_msg>fix string format<commit_after>(function(exports){

    var savedSnapshot, savedEditorValue, savedTerminal, dag;

    function init(terminal) {
        savedTerminal = terminal;
    }

    function getSnapshotSize() {
        if (savedSnapshot === undefined) {
            return 0;
        } else {
            return savedSnapshot.length;
        }
    }

    function getNextValidEdit(from, to) {
    	if (savedSnapshot === undefined || dag === undefined) {
			return undefined;
    	}

		for (; from < savedSnapshot.length && from <= to; ++from) {
			// if dependency is out of range
			if (dag[from] > to || from >= dag[from]) {
				var text = normalizeObject(savedSnapshot[from].replace);
		    	var fromLine = savedSnapshot[from].range.from.line;
		    	var fromCh = savedSnapshot[from].range.from.ch;
		    	var toLine = savedSnapshot[from].range.to.line;
		    	var toCh = savedSnapshot[from].range.to.ch;
		    	return {text: text, fromLine: fromLine, toLine: toLine, fromCh: fromCh, toCh: toCh, i: from};
	    	}

		}

    	return undefined;
    }

    function showUpTo(x) {
    	if (savedSnapshot === undefined ||
    		savedEditorValue === undefined ||
    		savedTerminal === undefined ||
    		dag === undefined)
		{
			return;
		}

		// out of range, do nothing
    	if (x >= savedSnapshot.length && x < 0) {
    		return;
    	}

        var line = 0, ch = 0, j = 0, i = 0, fragments = [];
        // i = index for snapshot, j = index for editorValue
        var nextEdit = getNextValidEdit(i, x);
    	var editorValue = savedEditorValue;
    	// always have a valid edit if there is at least one edit
    	i = nextEdit.i;
        while (j < editorValue.length && nextEdit !== undefined) {
            if (editorValue[j] == '\n') {
                // reset count
                line += 1;
                ch = 0;
            }
            else {
                if (line == nextEdit.fromLine && ch == nextEdit.fromCh) {
                    fragments.push(editorValue.substring(0,j));
                    editorValue = editorValue.substring(j);
                    j = 0; // reset j counter, because editorvalue change
                }
                if (line == nextEdit.toLine && ch == nextEdit.toCh) {
                    fragments.push(nextEdit.text);
                    editorValue = editorValue.substring(j);
                    j = 0; // reset j counter, because editorValue change

                    // Get next edit
                    i += 1;
                    nextEdit = getNextValidEdit(i, x);
                    if (nextEdit === undefined)
                    	break;
                    i = nextEdit.i;
                }
                // increase ch count
                ++ch;
            }
            ++j;
        }

        editorValue = fragments.join('') + editorValue;
        savedTerminal.setValue(editorValue);

    }

    function align(editorValue,  snapshot) {
        if (snapshot.length === 0) {
            return;
        }

        // save variable
        savedEditorValue = editorValue;
        savedSnapshot = snapshot;
		dag = Tracer.buildDAG(snapshot);
		showUpTo(snapshot.length-1);
    }

    function normalizeObject(replace) {
        if (typeof(replace) === "function") {
            return "function()";
        } else if (typeof(replace) === "object") {
            return JSON.stringify(replace);
        } else if (typeof(replace) === "string") {
        	return '"' + replace + '"';
        } else {
            // if the replacement text is a string
            return replace.toString();
        }
    }

    function rewind(x) {
    	showUpTo(x);
    }

    function forward(x) {
		showUpTo(x);
    }

    exports.align = align;
    exports.init = init;
    exports.getSnapshotSize = getSnapshotSize;
    exports.rewind = rewind;
    exports.forward = forward;

}(typeof exports === 'undefined'? Format = {}: exports));
<|endoftext|>