<commit_before>(function ($) {
  var frameSheet,

      // Constants:
      TRANSPARENT_COLOR = 'tc',
      //TRANSPARENT_COLOR = 'pink',
      DEFAULT_PEN_COLOR = '#000000',

      // Configuration:
      // Canvas size in pixel size (not dpi related)
      framePixelWidth = 32,
      framePixelHeight = 32,

      // Scaling factors for a given frameSheet rendering:
      // Main drawing area:
      drawingCanvasDpi = 20,
      // Canvas previous in the slideshow:
      previewTileCanvasDpi = 4,
      // Ainmated canvas preview:
      previewAnimationCanvasDpi = 8,

      // DOM references:
      drawingAreaContainer,
      drawingAreaCanvas,
      previewCanvas,
      paletteEl,

      // States:
      isClicked = false,
      isRightClicked = false,
      activeFrameIndex = -1,
      animIndex = 0,
      penColor = DEFAULT_PEN_COLOR,
      paletteColors = [],

      //utility
      _normalizeColor = function (color) {
        if(color == undefined || color == TRANSPARENT_COLOR || color.indexOf("#") == 0) {
          return color;
        } else {
          return "#" + color;
        }
      },

      // setTimeout/setInterval references:
      localStorageThrottler = null
      ;

  var piskel = {
    init : function () {
      frameSheet = FrameSheetModel.getInstance(framePixelWidth, framePixelHeight);
      frameSheet.addEmptyFrame();
      this.setActiveFrame(0);

      this.initPalette();
      this.initDrawingArea();
      this.initPreviewSlideshow();
      this.initAnimationPreview();
      this.initColorPicker();

      if(window.localStorage && window.localStorage['snapShot']) {
        var message = document.createElement('div');
        message.id = "user-message";
        message.className = "user-message";
        var reloadLink = "reload";
        var discardLink = "discard";
        message.innerHTML = "Non saved version found. " + reloadLink + " or " + discardLink;
        message.onclick = function() {
          message.parentNode.removeChild(message);
        };
        document.body.appendChild(message);
      }
    },

    persistToLocalStorage: function() {
      console.log('persited')
      window.localStorage['snapShot'] = frameSheet.serialize();
    },

    restoreFromLocalStorage: function() {
      frameSheet.deserialize(window.localStorage['snapShot']);
      this.setActiveFrameAndRedraw(0);
    },

    cleanLocalStorage: function() {
      delete window.localStorage['snapShot'];
    },

    setActiveFrame: function(index) {
      activeFrameIndex = index;
    },

    setActiveFrameAndRedraw: function(index) {
      this.setActiveFrame(index);

      // Update drawing canvas:
      this.drawFrameToCanvas(frameSheet.getFrameByIndex(this.getActiveFrameIndex()), drawingAreaCanvas, drawingCanvasDpi);

      // Update slideshow:
      this.createPreviews();

      // Update animation preview:
      animIndex = 0;
    },

    getActiveFrameIndex: function() {
      if(-1 == activeFrameIndex) {
        throw "Bad active frane initialization."
      }
      return activeFrameIndex;
    },

    initColorPicker: function() {
      this.colorPicker = $('color-picker');
      this.colorPicker.value = DEFAULT_PEN_COLOR;
      this.colorPicker.addEventListener('change', this.onPickerChange.bind(this));
    },

    onPickerChange : function(evt) {
        penColor = _normalizeColor(this.colorPicker.value);
    },

    initPalette : function (color) {
      paletteEl = $('palette');
    },

    addColorToPalette : function (color) {
      if (color && color != TRANSPARENT_COLOR && paletteColors.indexOf(color) == -1) {
        var colorEl = document.createElement("li");
        colorEl.setAttribute("data-color", color);
        colorEl.setAttribute("title", color);
        colorEl.style.background = color;
        paletteEl.appendChild(colorEl);
        paletteColors.push(color);
      }
    },

    initDrawingArea : function() {
        drawingAreaContainer = $('drawing-canvas-container');

        drawingAreaCanvas = document.createElement("canvas");
        drawingAreaCanvas.className = 'canvas';
        drawingAreaCanvas.setAttribute('width', '' + framePixelWidth * drawingCanvasDpi);
        drawingAreaCanvas.setAttribute('height', '' + framePixelHeight * drawingCanvasDpi);

        drawingAreaContainer.setAttribute('style',
          'width:' + framePixelWidth * drawingCanvasDpi + 'px; height:' + framePixelHeight * drawingCanvasDpi + 'px;');

        drawingAreaCanvas.setAttribute('oncontextmenu', 'piskel.onCanvasContextMenu(event)');
        drawingAreaContainer.appendChild(drawingAreaCanvas);

        var body = document.getElementsByTagName('body')[0];
        body.setAttribute('onmouseup', 'piskel.onCanvasMouseup(event)');
        drawingAreaContainer.setAttribute('onmousedown', 'piskel.onCanvasMousedown(event)');
        drawingAreaContainer.setAttribute('onmousemove', 'piskel.onCanvasMousemove(event)');

        this.drawFrameToCanvas(frameSheet.getFrameByIndex(this.getActiveFrameIndex()), drawingAreaCanvas, drawingCanvasDpi);
    },

    initPreviewSlideshow: function() {
      var addFrameButton = $('add-frame-button');
      addFrameButton.addEventListener('mousedown', function() {
        frameSheet.addEmptyFrame();
        piskel.setActiveFrameAndRedraw(frameSheet.getFrameCount() - 1);
      });
      this.createPreviews();
    },

    initAnimationPreview : function() {
      var scope = this;

      var previewAnimationContainer = $('preview-canvas-container');
      previewCanvas = document.createElement('canvas');
      previewCanvas.className = 'canvas';
      previewAnimationContainer.setAttribute('style',
        'width:' + framePixelWidth * previewAnimationCanvasDpi + 'px; height:' + framePixelHeight * previewAnimationCanvasDpi + 'px;');
      previewAnimationContainer.appendChild(previewCanvas);
      previewCanvas.setAttribute('width', framePixelWidth * previewAnimationCanvasDpi);
      previewCanvas.setAttribute('height', framePixelHeight * previewAnimationCanvasDpi);

      var animFPSTuner = document.getElementById("preview-fps");
      var animPreviewFPS = parseInt(animFPSTuner.value, 10);
      var startPreviewRefresh = function() {
        return setInterval(scope.refreshAnimatedPreview, 1000/animPreviewFPS);
      };
      var refreshUpdater = startPreviewRefresh();

      animFPSTuner.addEventListener('change', function(evt) {
        window.clearInterval(refreshUpdater);
        animPreviewFPS = parseInt(animFPSTuner.value, 10);
        if(isNaN(animPreviewFPS)) {
          animPreviewFPS = 1;
        }
        if(evt.keyCode == 38) {
          animPreviewFPS++;
        }
        else if (evt.keyCode == 40) {
          animPreviewFPS--;
        }
        if(animPreviewFPS < 1) {
          animPreviewFPS = 1;
        }
        if(animPreviewFPS > 100) {
          animPreviewFPS = 100;
        }
        $("display-fps").innerHTML = animPreviewFPS + " fps";
        animFPSTuner.value = animPreviewFPS;
        refreshUpdater = startPreviewRefresh();
      });
    },

    createPreviews : function () {
      var container = $('preview-list'), previewTile;
      container.innerHTML = "";
      for (var i = 0, l = frameSheet.getFrameCount(); i < l ; i++) {
        previewTile = this.createPreviewTile(i);
        container.appendChild(previewTile);
      }
    },

    createPreviewTile: function(tileNumber) {
      var previewTileRoot = document.createElement("li");
      var classname = "preview-tile";

      if (this.getActiveFrameIndex() == tileNumber) {
        classname += " selected";
      }
      previewTileRoot.className = classname;

      var canvasContainer = document.createElement("div");
      canvasContainer.className = "canvas-container";
      canvasContainer.setAttribute('style',
        'width:' + framePixelWidth * previewTileCanvasDpi + 'px; height:' + framePixelHeight * previewTileCanvasDpi + 'px;');

      var canvasBackground = document.createElement("div");
      canvasBackground.className = "canvas-background";
      canvasContainer.appendChild(canvasBackground);

      var canvasPreview = document.createElement("canvas");
      canvasPreview.className = "canvas tile-view"

      canvasPreview.setAttribute('width', framePixelWidth * previewTileCanvasDpi);
      canvasPreview.setAttribute('height', framePixelHeight * previewTileCanvasDpi);

      previewTileRoot.addEventListener('click', function(evt) {
        // has not class tile-action:
        // TODO: let me know when you want to start using a framework :)
        if(!evt.target.className.match(new RegExp('(\\s|^)'+ 'tile-action' +'(\\s|$)'))) {
          piskel.setActiveFrameAndRedraw(tileNumber);
        }
      });

      var canvasPreviewDuplicateAction = document.createElement("button");
      canvasPreviewDuplicateAction.className = "tile-action"
      canvasPreviewDuplicateAction.innerHTML = "dup"

      canvasPreviewDuplicateAction.addEventListener('click', function(evt) {
        piskel.duplicateFrame(tileNumber);
      });

      this.drawFrameToCanvas(frameSheet.getFrameByIndex(tileNumber), canvasPreview, previewTileCanvasDpi);
      canvasContainer.appendChild(canvasPreview);
      previewTileRoot.appendChild(canvasContainer);
      previewTileRoot.appendChild(canvasPreviewDuplicateAction);

      if(tileNumber > 0 || frameSheet.getFrameCount() > 1) {
        var canvasPreviewDeleteAction = document.createElement("button");
        canvasPreviewDeleteAction.className = "tile-action"
        canvasPreviewDeleteAction.innerHTML = "del"
        canvasPreviewDeleteAction.addEventListener('click', function(evt) {
          piskel.removeFrame(tileNumber);
        });
        previewTileRoot.appendChild(canvasPreviewDeleteAction);
      }

      return previewTileRoot;
    },

    refreshAnimatedPreview : function () {
      piskel.drawFrameToCanvas(frameSheet.getFrameByIndex(animIndex), previewCanvas, previewAnimationCanvasDpi);
      animIndex++;
      if (animIndex == frameSheet.getFrameCount()) {
        animIndex = 0;
      }
    },

    removeFrame: function(frameIndex) {
      frameSheet.removeFrameByIndex(frameIndex);

      this.setActiveFrameAndRedraw(frameIndex - 1);
    },

    duplicateFrame: function(frameIndex) {
      frameSheet.duplicateFrameByIndex(frameIndex);

      this.setActiveFrameAndRedraw(frameIndex + 1);
    },

    updateCursorInfo : function (event) {
      var cursor = $('cursorInfo');
      cursor.style.top = event.clientY + 10 + "px";
      cursor.style.left = event.clientX + 10 + "px";

      var coordinates = this.getRelativeCoordinates(event.clientX, event.clientY)
      cursor.innerHTML = [
        "X : " + coordinates.x,
        "Y : " + coordinates.y
      ].join(", ");
    },

    onCanvasMousedown : function (event) {
      isClicked = true;
      var coords = this.getRelativeCoordinates(event.clientX, event.clientY);
      if(event.button == 0) {
        this.drawAt(coords.x, coords.y, penColor);
      } else {
        // Right click used to delete.
        isRightClicked = true;
        this.drawAt(coords.x, coords.y, TRANSPARENT_COLOR);
      }
    },

    onCanvasMousemove : function (event) {
      //this.updateCursorInfo(event);
      if (isClicked) {
        var coords = this.getRelativeCoordinates(event.clientX, event.clientY);
        if(isRightClicked) {
          this.drawAt(coords.x, coords.y, TRANSPARENT_COLOR);
        } else {
          this.drawAt(coords.x, coords.y, penColor);
        }
      }
    },

    onCanvasMouseup : function (event) {
      if(isClicked || isRightClicked) {
        // A mouse button was clicked on the drawing canvas before this mouseup event,
        // the user was probably drawing on the canvas.
        // Note: The mousemove movement (and the mouseup) may end up outside
        // of the drawing canvas.
        this.createPreviews();
      }
      isClicked = false;
      isRightClicked = false;
    },

    drawAt : function (x, y, color) {
      var col = (x - x%drawingCanvasDpi) / drawingCanvasDpi;
      var row = (y - y%drawingCanvasDpi) / drawingCanvasDpi;

      // Update model:
      var currentFrame = frameSheet.getFrameByIndex(this.getActiveFrameIndex());

      // TODO: make a better accessor for pixel state update:
      // TODO: Make pen color dynamic:
      var color = _normalizeColor(color);
      if (color != currentFrame[col][row]) {
        currentFrame[col][row] = color;
        this.drawPixelInCanvas(row, col, color, drawingAreaCanvas, drawingCanvasDpi);
      }

      // Persist to localStorage when drawing. We throttle localStorage accesses
      // for high frequency drawing (eg mousemove).
      if(localStorageThrottler == null) {
          localStorageThrottler = window.setTimeout(function() {
            piskel.persistToLocalStorage();
            localStorageThrottler = null;
          }, 1000);
      }

    },

    drawPixelInCanvas : function (row, col, color, canvas, dpi) {
      var context = canvas.getContext('2d');
      if(color == undefined || color == TRANSPARENT_COLOR) {
        context.clearRect(col * dpi, row * dpi, dpi, dpi);
      } else {
        this.addColorToPalette(color);
        context.fillStyle = color;
        context.fillRect(col * dpi, row * dpi, dpi, dpi);
      }
    },

    // TODO: move that to a FrameRenderer (/w cache) ?
    drawFrameToCanvas: function(frame, canvasElement, dpi) {
      var color;
      for(var col = 0, num_col = frame.length; col < num_col; col++) {
        for(var row = 0, num_row = frame[col].length; row < num_row; row++) {
          color = _normalizeColor(frame[col][row]);
          this.drawPixelInCanvas(row, col, color, canvasElement, dpi);
        }
      }
    },

    onCanvasContextMenu : function (event) {
      event.preventDefault();
      event.stopPropagation();
      event.cancelBubble = true;
      return false;
    },

    onPaletteClick : function (event) {
      var color = event.target.getAttribute("data-color");
      if (null !== color) {
        var colorPicker = $('color-picker');
        colorPicker.color.fromString(color);
        this.onPickerChange();
      }
    },

    getRelativeCoordinates : function (x, y) {
      var canvasRect = drawingAreaCanvas.getBoundingClientRect();
      return {
        x : x - canvasRect.left,
        y : y - canvasRect.top
      }
    }
  };

  window.piskel = piskel;
  piskel.init();

})(function(id){return document.getElementById(id)});<commit_msg>Creating init method for localstorage<commit_after>(function ($) {
  var frameSheet,

      // Constants:
      TRANSPARENT_COLOR = 'tc',
      //TRANSPARENT_COLOR = 'pink',
      DEFAULT_PEN_COLOR = '#000000',

      // Configuration:
      // Canvas size in pixel size (not dpi related)
      framePixelWidth = 32,
      framePixelHeight = 32,

      // Scaling factors for a given frameSheet rendering:
      // Main drawing area:
      drawingCanvasDpi = 20,
      // Canvas previous in the slideshow:
      previewTileCanvasDpi = 4,
      // Ainmated canvas preview:
      previewAnimationCanvasDpi = 8,

      // DOM references:
      drawingAreaContainer,
      drawingAreaCanvas,
      previewCanvas,
      paletteEl,

      // States:
      isClicked = false,
      isRightClicked = false,
      activeFrameIndex = -1,
      animIndex = 0,
      penColor = DEFAULT_PEN_COLOR,
      paletteColors = [],

      //utility
      _normalizeColor = function (color) {
        if(color == undefined || color == TRANSPARENT_COLOR || color.indexOf("#") == 0) {
          return color;
        } else {
          return "#" + color;
        }
      },

      // setTimeout/setInterval references:
      localStorageThrottler = null
      ;

  var piskel = {
    init : function () {
      frameSheet = FrameSheetModel.getInstance(framePixelWidth, framePixelHeight);
      frameSheet.addEmptyFrame();
      this.setActiveFrame(0);

      this.initPalette();
      this.initDrawingArea();
      this.initPreviewSlideshow();
      this.initAnimationPreview();
      this.initColorPicker();
      this.initLocalStorageBackup();
    },

    initLocalStorageBackup: function() {
      if(window.localStorage && window.localStorage['snapShot']) {
        var message = document.createElement('div');
        message.id = "user-message";
        message.className = "user-message";
        var reloadLink = "reload";
        var discardLink = "discard";
        message.innerHTML = "Non saved version found. " + reloadLink + " or " + discardLink;
        message.onclick = function() {
          message.parentNode.removeChild(message);
        };
        document.body.appendChild(message);
      }
    },

    persistToLocalStorage: function() {
      console.log('persited')
      window.localStorage['snapShot'] = frameSheet.serialize();
    },

    restoreFromLocalStorage: function() {
      frameSheet.deserialize(window.localStorage['snapShot']);
      this.setActiveFrameAndRedraw(0);
    },

    cleanLocalStorage: function() {
      delete window.localStorage['snapShot'];
    },

    setActiveFrame: function(index) {
      activeFrameIndex = index;
    },

    setActiveFrameAndRedraw: function(index) {
      this.setActiveFrame(index);

      // Update drawing canvas:
      this.drawFrameToCanvas(frameSheet.getFrameByIndex(this.getActiveFrameIndex()), drawingAreaCanvas, drawingCanvasDpi);

      // Update slideshow:
      this.createPreviews();

      // Update animation preview:
      animIndex = 0;
    },

    getActiveFrameIndex: function() {
      if(-1 == activeFrameIndex) {
        throw "Bad active frane initialization."
      }
      return activeFrameIndex;
    },

    initColorPicker: function() {
      this.colorPicker = $('color-picker');
      this.colorPicker.value = DEFAULT_PEN_COLOR;
      this.colorPicker.addEventListener('change', this.onPickerChange.bind(this));
    },

    onPickerChange : function(evt) {
        penColor = _normalizeColor(this.colorPicker.value);
    },

    initPalette : function (color) {
      paletteEl = $('palette');
    },

    addColorToPalette : function (color) {
      if (color && color != TRANSPARENT_COLOR && paletteColors.indexOf(color) == -1) {
        var colorEl = document.createElement("li");
        colorEl.setAttribute("data-color", color);
        colorEl.setAttribute("title", color);
        colorEl.style.background = color;
        paletteEl.appendChild(colorEl);
        paletteColors.push(color);
      }
    },

    initDrawingArea : function() {
        drawingAreaContainer = $('drawing-canvas-container');

        drawingAreaCanvas = document.createElement("canvas");
        drawingAreaCanvas.className = 'canvas';
        drawingAreaCanvas.setAttribute('width', '' + framePixelWidth * drawingCanvasDpi);
        drawingAreaCanvas.setAttribute('height', '' + framePixelHeight * drawingCanvasDpi);

        drawingAreaContainer.setAttribute('style',
          'width:' + framePixelWidth * drawingCanvasDpi + 'px; height:' + framePixelHeight * drawingCanvasDpi + 'px;');

        drawingAreaCanvas.setAttribute('oncontextmenu', 'piskel.onCanvasContextMenu(event)');
        drawingAreaContainer.appendChild(drawingAreaCanvas);

        var body = document.getElementsByTagName('body')[0];
        body.setAttribute('onmouseup', 'piskel.onCanvasMouseup(event)');
        drawingAreaContainer.setAttribute('onmousedown', 'piskel.onCanvasMousedown(event)');
        drawingAreaContainer.setAttribute('onmousemove', 'piskel.onCanvasMousemove(event)');

        this.drawFrameToCanvas(frameSheet.getFrameByIndex(this.getActiveFrameIndex()), drawingAreaCanvas, drawingCanvasDpi);
    },

    initPreviewSlideshow: function() {
      var addFrameButton = $('add-frame-button');
      addFrameButton.addEventListener('mousedown', function() {
        frameSheet.addEmptyFrame();
        piskel.setActiveFrameAndRedraw(frameSheet.getFrameCount() - 1);
      });
      this.createPreviews();
    },

    initPreviewSlideshow: function() {
      var addFrameButton = $('add-frame-button');
      addFrameButton.addEventListener('mousedown', function() {
        frameSheet.addEmptyFrame();
        piskel.setActiveFrameAndRedraw(frameSheet.getFrameCount() - 1);
      });
      this.createPreviews();
    },

    initAnimationPreview : function() {
      var scope = this;

      var previewAnimationContainer = $('preview-canvas-container');
      previewCanvas = document.createElement('canvas');
      previewCanvas.className = 'canvas';
      previewAnimationContainer.setAttribute('style',
        'width:' + framePixelWidth * previewAnimationCanvasDpi + 'px; height:' + framePixelHeight * previewAnimationCanvasDpi + 'px;');
      previewAnimationContainer.appendChild(previewCanvas);
      previewCanvas.setAttribute('width', framePixelWidth * previewAnimationCanvasDpi);
      previewCanvas.setAttribute('height', framePixelHeight * previewAnimationCanvasDpi);

      var animFPSTuner = document.getElementById("preview-fps");
      var animPreviewFPS = parseInt(animFPSTuner.value, 10);
      var startPreviewRefresh = function() {
        return setInterval(scope.refreshAnimatedPreview, 1000/animPreviewFPS);
      };
      var refreshUpdater = startPreviewRefresh();

      animFPSTuner.addEventListener('change', function(evt) {
        window.clearInterval(refreshUpdater);
        animPreviewFPS = parseInt(animFPSTuner.value, 10);
        if(isNaN(animPreviewFPS)) {
          animPreviewFPS = 1;
        }
        if(evt.keyCode == 38) {
          animPreviewFPS++;
        }
        else if (evt.keyCode == 40) {
          animPreviewFPS--;
        }
        if(animPreviewFPS < 1) {
          animPreviewFPS = 1;
        }
        if(animPreviewFPS > 100) {
          animPreviewFPS = 100;
        }
        $("display-fps").innerHTML = animPreviewFPS + " fps";
        animFPSTuner.value = animPreviewFPS;
        refreshUpdater = startPreviewRefresh();
      });
    },

    createPreviews : function () {
      var container = $('preview-list'), previewTile;
      container.innerHTML = "";
      for (var i = 0, l = frameSheet.getFrameCount(); i < l ; i++) {
        previewTile = this.createPreviewTile(i);
        container.appendChild(previewTile);
      }
    },

    createPreviewTile: function(tileNumber) {
      var previewTileRoot = document.createElement("li");
      var classname = "preview-tile";

      if (this.getActiveFrameIndex() == tileNumber) {
        classname += " selected";
      }
      previewTileRoot.className = classname;

      var canvasContainer = document.createElement("div");
      canvasContainer.className = "canvas-container";
      canvasContainer.setAttribute('style',
        'width:' + framePixelWidth * previewTileCanvasDpi + 'px; height:' + framePixelHeight * previewTileCanvasDpi + 'px;');

      var canvasBackground = document.createElement("div");
      canvasBackground.className = "canvas-background";
      canvasContainer.appendChild(canvasBackground);

      var canvasPreview = document.createElement("canvas");
      canvasPreview.className = "canvas tile-view"

      canvasPreview.setAttribute('width', framePixelWidth * previewTileCanvasDpi);
      canvasPreview.setAttribute('height', framePixelHeight * previewTileCanvasDpi);

      previewTileRoot.addEventListener('click', function(evt) {
        // has not class tile-action:
        // TODO: let me know when you want to start using a framework :)
        if(!evt.target.className.match(new RegExp('(\\s|^)'+ 'tile-action' +'(\\s|$)'))) {
          piskel.setActiveFrameAndRedraw(tileNumber);
        }
      });

      var canvasPreviewDuplicateAction = document.createElement("button");
      canvasPreviewDuplicateAction.className = "tile-action"
      canvasPreviewDuplicateAction.innerHTML = "dup"

      canvasPreviewDuplicateAction.addEventListener('click', function(evt) {
        piskel.duplicateFrame(tileNumber);
      });

      this.drawFrameToCanvas(frameSheet.getFrameByIndex(tileNumber), canvasPreview, previewTileCanvasDpi);
      canvasContainer.appendChild(canvasPreview);
      previewTileRoot.appendChild(canvasContainer);
      previewTileRoot.appendChild(canvasPreviewDuplicateAction);

      if(tileNumber > 0 || frameSheet.getFrameCount() > 1) {
        var canvasPreviewDeleteAction = document.createElement("button");
        canvasPreviewDeleteAction.className = "tile-action"
        canvasPreviewDeleteAction.innerHTML = "del"
        canvasPreviewDeleteAction.addEventListener('click', function(evt) {
          piskel.removeFrame(tileNumber);
        });
        previewTileRoot.appendChild(canvasPreviewDeleteAction);
      }

      return previewTileRoot;
    },

    refreshAnimatedPreview : function () {
      piskel.drawFrameToCanvas(frameSheet.getFrameByIndex(animIndex), previewCanvas, previewAnimationCanvasDpi);
      animIndex++;
      if (animIndex == frameSheet.getFrameCount()) {
        animIndex = 0;
      }
    },

    removeFrame: function(frameIndex) {
      frameSheet.removeFrameByIndex(frameIndex);

      this.setActiveFrameAndRedraw(frameIndex - 1);
    },

    duplicateFrame: function(frameIndex) {
      frameSheet.duplicateFrameByIndex(frameIndex);

      this.setActiveFrameAndRedraw(frameIndex + 1);
    },

    updateCursorInfo : function (event) {
      var cursor = $('cursorInfo');
      cursor.style.top = event.clientY + 10 + "px";
      cursor.style.left = event.clientX + 10 + "px";

      var coordinates = this.getRelativeCoordinates(event.clientX, event.clientY)
      cursor.innerHTML = [
        "X : " + coordinates.x,
        "Y : " + coordinates.y
      ].join(", ");
    },

    onCanvasMousedown : function (event) {
      isClicked = true;
      var coords = this.getRelativeCoordinates(event.clientX, event.clientY);
      if(event.button == 0) {
        this.drawAt(coords.x, coords.y, penColor);
      } else {
        // Right click used to delete.
        isRightClicked = true;
        this.drawAt(coords.x, coords.y, TRANSPARENT_COLOR);
      }
    },

    onCanvasMousemove : function (event) {
      //this.updateCursorInfo(event);
      if (isClicked) {
        var coords = this.getRelativeCoordinates(event.clientX, event.clientY);
        if(isRightClicked) {
          this.drawAt(coords.x, coords.y, TRANSPARENT_COLOR);
        } else {
          this.drawAt(coords.x, coords.y, penColor);
        }
      }
    },

    onCanvasMouseup : function (event) {
      if(isClicked || isRightClicked) {
        // A mouse button was clicked on the drawing canvas before this mouseup event,
        // the user was probably drawing on the canvas.
        // Note: The mousemove movement (and the mouseup) may end up outside
        // of the drawing canvas.
        this.createPreviews();
      }
      isClicked = false;
      isRightClicked = false;
    },

    drawAt : function (x, y, color) {
      var col = (x - x%drawingCanvasDpi) / drawingCanvasDpi;
      var row = (y - y%drawingCanvasDpi) / drawingCanvasDpi;

      // Update model:
      var currentFrame = frameSheet.getFrameByIndex(this.getActiveFrameIndex());

      // TODO: make a better accessor for pixel state update:
      // TODO: Make pen color dynamic:
      var color = _normalizeColor(color);
      if (color != currentFrame[col][row]) {
        currentFrame[col][row] = color;
        this.drawPixelInCanvas(row, col, color, drawingAreaCanvas, drawingCanvasDpi);
      }

      // Persist to localStorage when drawing. We throttle localStorage accesses
      // for high frequency drawing (eg mousemove).
      if(localStorageThrottler == null) {
          localStorageThrottler = window.setTimeout(function() {
            piskel.persistToLocalStorage();
            localStorageThrottler = null;
          }, 1000);
      }

    },

    drawPixelInCanvas : function (row, col, color, canvas, dpi) {
      var context = canvas.getContext('2d');
      if(color == undefined || color == TRANSPARENT_COLOR) {
        context.clearRect(col * dpi, row * dpi, dpi, dpi);
      } else {
        this.addColorToPalette(color);
        context.fillStyle = color;
        context.fillRect(col * dpi, row * dpi, dpi, dpi);
      }
    },

    // TODO: move that to a FrameRenderer (/w cache) ?
    drawFrameToCanvas: function(frame, canvasElement, dpi) {
      var color;
      for(var col = 0, num_col = frame.length; col < num_col; col++) {
        for(var row = 0, num_row = frame[col].length; row < num_row; row++) {
          color = _normalizeColor(frame[col][row]);
          this.drawPixelInCanvas(row, col, color, canvasElement, dpi);
        }
      }
    },

    onCanvasContextMenu : function (event) {
      event.preventDefault();
      event.stopPropagation();
      event.cancelBubble = true;
      return false;
    },

    onPaletteClick : function (event) {
      var color = event.target.getAttribute("data-color");
      if (null !== color) {
        var colorPicker = $('color-picker');
        colorPicker.color.fromString(color);
        this.onPickerChange();
      }
    },

    getRelativeCoordinates : function (x, y) {
      var canvasRect = drawingAreaCanvas.getBoundingClientRect();
      return {
        x : x - canvasRect.left,
        y : y - canvasRect.top
      }
    }
  };

  window.piskel = piskel;
  piskel.init();

})(function(id){return document.getElementById(id)});<|endoftext|>


<commit_before>function createPPBar(x, y, min, max, currentVal){
  var targetVal, animator = new LinearPerRatioModel(0.1, 4);
  var inlayer = sceneManager.getScene().spriteFactory.create('images/bar/inlayer.png', {
    attrs: {x: x, y: y, scale: 1, patternSpeed: 0, loopPattern: true, fixedPosition: true },
    callbacks: {
      getOx: function(s){ return 0; },
      getOy: function(s){ return 0; },
      onUpdate: function(){}
    }
  });
  var blood = sceneManager.getScene().spriteFactory.create('images/bar/blood.png', {
    attrs: {x: x, y: y, scale: 1, patternSpeed: 0, loopPattern: true, fixedPosition: true },
    callbacks: {
      getOx: function(s){ return 0; },
      getOy: function(s){ return 0; },
      onUpdate: function(){
        if (currentVal == targetVal) return;
        setCurrentVal(animator.getNextValue(currentVal));
      }
    }
  });
  function setValue(val){
    val = Math.max(Math.min(val, max), min);
    targetVal = val;
    if (currentVal == undefined) setCurrentVal(targetVal);
    if (currentVal != targetVal) animator.setStartAttrs(currentVal, targetVal);
  }
  function setCurrentVal(val){
    currentVal = val;
    blood.attrs.ratio = (currentVal - min) / (max - min);
  }
  setValue(currentVal == undefined ? min : currentVal);
  return {
    setValue: setValue,
    getCurrentValue: function(){ return currentVal; },
    destroy: function(){
      blood.destroy();
      inlayer.destroy();
      blood = undefined;
      inlayer = undefined;
    }
  }
}
function createPlayer(VIEWPORT_X, BASE_Y, callbacks){
  var player, vx = 0, vy = 0;
  var currentStatus, STATUSES = {};
  function changeStatus(status){
    if (currentStatus) currentStatus.onLeft();
    currentStatus = status;
    status.initialize();
  }
  IDLE_PATH = 'images/characters/wolf.png';
  JUMP_PATH = 'images/characters/wolf_jump.png';
  STATUSES.IDLE = new function(){
    var singCD = 0;
    return {
      initialize: function(){
        player.setPath(IDLE_PATH);
      },
      updateInput: function(character){
        if (singCD > 0) singCD -= 1;
        if (Input.pressed(Input.KEYS.SPACE) && singCD == 0){
          singCD = 15;
          return changeStatus(STATUSES.SING);
        }
        if (Input.pressed(Input.KEYS.RIGHT)) vx = 6;
        else if (Input.pressed(Input.KEYS.LEFT)) vx = 3;
        else vx = 4;
        if (Input.pressed(Input.KEYS.UP) && character.attrs.y == BASE_Y){
          player.setPath(JUMP_PATH);
          vy = 15;
        }
      },
      update: function(character){
        if (character.attrs.y > BASE_Y) vy -= 1; //gravity
      },
      onLeft: function(){

      }
    };
  };
  STATUSES.SING = new function(){
    var singCounter, ppbar;
    return {
      initialize: function(){
        ppbar = createPPBar(100, 300, 0, 80);
        player.setPath(JUMP_PATH);
        singCounter = 30;
        var singEffect = sceneManager.getScene().spriteFactory.create('images/characters/sing_effect.png', {
          attrs: {
            x: 0,
            y: 0,
            scale: 4,
            loopPattern: false,
            patternSpeed: 50
          },
          callbacks: {
            getOx: function(s){ return s / 2; },
            getOy: function(s){ return s / 2; },
            onUpdate: function(){
              singEffect.attrs.x = player.attrs.x;
              singEffect.attrs.y = player.attrs.y + 20;
            }
          }
        });
        vy = 6;
      },
      updateInput: function(character){
        if (Input.pressed(Input.KEYS.A)){ ppbar.setValue(35); }
        if (Input.pressed(Input.KEYS.S)){ ppbar.setValue(45); }
        if (Input.pressed(Input.KEYS.D)){ ppbar.setValue(55); }
      },
      update: function(character){
        if ((singCounter -= 1) < 0){
          var val = ppbar.getCurrentValue();
          if (val >= 30 && val < 40) character.shoot('images/characters/magic_ball-01.png', 'water' );
          if (val >= 40 && val < 50) character.shoot('images/characters/magic_ball-02.png', 'fire'  );
          if (val >= 50 && val < 60) character.shoot('images/characters/magic_ball-03.png', 'ground');
          return changeStatus(STATUSES.IDLE);
        }
        vy *= 0.9;
      },
      onLeft: function(){
        ppbar.destroy();
        ppbar = undefined;
      }
    };
  };
  player = sceneManager.getScene().characterFactory.create('images/characters/wolf.png', {
    attrs: {
      character: {
        race: 1,
        hp: 100,
        atk: 100,
        hitRange: 3 //not using
      },
      onKilled: callbacks.onKilled,
      onDestroy: callbacks.onDestroy,
      x: VIEWPORT_X,
      y: BASE_Y,
      scale: 0.5,
      loopPattern: true,
      patternSpeed: 12
    },
    callbacks: {
      getOx: function(s){ return s / 2; },
      getOy: function(s){ return s; },
      onUpdate: function(){
        if (player.image){ //allow control after image loaded
          currentStatus.updateInput(player);
          currentStatus.update(player);
        }
        player.attrs.x += vx;
        player.attrs.y += vy;
        if (player.attrs.y < BASE_Y){
          player.attrs.y = BASE_Y;
          vy = 0;
          player.setPath(IDLE_PATH);
        }
      }
    }
  });
  changeStatus(STATUSES.IDLE);
  return player;
}
<commit_msg>adjust bar<commit_after>function createPPBar(x, y, min, max, currentVal){
  var targetVal, animator = new LinearPerRatioModel(0.1, 4);
  var inlayer = sceneManager.getScene().spriteFactory.create('images/bar/inlayer.png', {
    attrs: {x: x, y: y, scale: 1, patternSpeed: 0, loopPattern: true, fixedPosition: true },
    callbacks: {
      getOx: function(s){ return 0; },
      getOy: function(s){ return 0; },
      onUpdate: function(){}
    }
  });
  var blood = sceneManager.getScene().spriteFactory.create('images/bar/blood.png', {
    attrs: {x: x, y: y, scale: 1, patternSpeed: 0, loopPattern: true, fixedPosition: true },
    callbacks: {
      getOx: function(s){ return 0; },
      getOy: function(s){ return 0; },
      onUpdate: function(){
        if (currentVal == targetVal) return;
        setCurrentVal(animator.getNextValue(currentVal));
      }
    }
  });
  function setValue(val){
    val = Math.max(Math.min(val, max), min);
    targetVal = val;
    if (currentVal == undefined) setCurrentVal(targetVal);
    if (currentVal != targetVal) animator.setStartAttrs(currentVal, targetVal);
  }
  function setCurrentVal(val){
    currentVal = val;
    blood.attrs.ratio = (currentVal - min) / (max - min);
  }
  setValue(currentVal == undefined ? min : currentVal);
  return {
    setValue: setValue,
    getCurrentValue: function(){ return currentVal; },
    destroy: function(){
      blood.destroy();
      inlayer.destroy();
      blood = undefined;
      inlayer = undefined;
    }
  }
}
function createPlayer(VIEWPORT_X, BASE_Y, callbacks){
  var player, vx = 0, vy = 0;
  var currentStatus, STATUSES = {};
  function changeStatus(status){
    if (currentStatus) currentStatus.onLeft();
    currentStatus = status;
    status.initialize();
  }
  IDLE_PATH = 'images/characters/wolf.png';
  JUMP_PATH = 'images/characters/wolf_jump.png';
  STATUSES.IDLE = new function(){
    var singCD = 0;
    return {
      initialize: function(){
        player.setPath(IDLE_PATH);
      },
      updateInput: function(character){
        if (singCD > 0) singCD -= 1;
        if (Input.pressed(Input.KEYS.SPACE) && singCD == 0){
          singCD = 15;
          return changeStatus(STATUSES.SING);
        }
        if (Input.pressed(Input.KEYS.RIGHT)) vx = 6;
        else if (Input.pressed(Input.KEYS.LEFT)) vx = 3;
        else vx = 4;
        if (Input.pressed(Input.KEYS.UP) && character.attrs.y == BASE_Y){
          player.setPath(JUMP_PATH);
          vy = 15;
        }
      },
      update: function(character){
        if (character.attrs.y > BASE_Y) vy -= 1; //gravity
      },
      onLeft: function(){

      }
    };
  };
  STATUSES.SING = new function(){
    var singCounter, ppbar;
    return {
      initialize: function(){
        ppbar = createPPBar(100, 300, 30, 80);
        player.setPath(JUMP_PATH);
        singCounter = 30;
        var singEffect = sceneManager.getScene().spriteFactory.create('images/characters/sing_effect.png', {
          attrs: {
            x: 0,
            y: 0,
            scale: 4,
            loopPattern: false,
            patternSpeed: 50
          },
          callbacks: {
            getOx: function(s){ return s / 2; },
            getOy: function(s){ return s / 2; },
            onUpdate: function(){
              singEffect.attrs.x = player.attrs.x;
              singEffect.attrs.y = player.attrs.y + 20;
            }
          }
        });
        vy = 6;
      },
      updateInput: function(character){
        if (Input.pressed(Input.KEYS.A)){ ppbar.setValue(45); }
        if (Input.pressed(Input.KEYS.S)){ ppbar.setValue(55); }
        if (Input.pressed(Input.KEYS.D)){ ppbar.setValue(65); }
      },
      update: function(character){
        if ((singCounter -= 1) < 0){
          var val = ppbar.getCurrentValue();
          if (val >= 40 && val < 50) character.shoot('images/characters/magic_ball-03.png', 'ground');
          if (val >= 50 && val < 60) character.shoot('images/characters/magic_ball-01.png', 'water' );
          if (val >= 60 && val < 70) character.shoot('images/characters/magic_ball-02.png', 'fire'  );
          return changeStatus(STATUSES.IDLE);
        }
        vy *= 0.9;
      },
      onLeft: function(){
        ppbar.destroy();
        ppbar = undefined;
      }
    };
  };
  player = sceneManager.getScene().characterFactory.create('images/characters/wolf.png', {
    attrs: {
      character: {
        race: 1,
        hp: 100,
        atk: 100,
        hitRange: 3 //not using
      },
      onKilled: callbacks.onKilled,
      onDestroy: callbacks.onDestroy,
      x: VIEWPORT_X,
      y: BASE_Y,
      scale: 0.5,
      loopPattern: true,
      patternSpeed: 12
    },
    callbacks: {
      getOx: function(s){ return s / 2; },
      getOy: function(s){ return s; },
      onUpdate: function(){
        if (player.image){ //allow control after image loaded
          currentStatus.updateInput(player);
          currentStatus.update(player);
        }
        player.attrs.x += vx;
        player.attrs.y += vy;
        if (player.attrs.y < BASE_Y){
          player.attrs.y = BASE_Y;
          vy = 0;
          player.setPath(IDLE_PATH);
        }
      }
    }
  });
  changeStatus(STATUSES.IDLE);
  return player;
}
<|endoftext|>


<commit_before>Template.project.helpers({
	photos: function () {
		return Photos.find({ _id: { $in: this.photos }}, { sort: { publish_date: -1, _id: -1}});
	},
	projects: function() {
		return Projects.find({}, { sort: { create_date: -1}});
	},
	showProjectLinks: function() {
		return (Projects.find().count() > 1);
	}
});

Template.project.rendered = function() {
	$("img.lazy").unveil();
};
<commit_msg>remove unused method<commit_after>Template.project.helpers({
	photos: function () {
		return Photos.find({ _id: { $in: this.photos }}, { sort: { publish_date: -1, _id: -1}});
	},
	projects: function() {
		return Projects.find({}, { sort: { create_date: -1}});
	}
});

Template.project.rendered = function() {
	$("img.lazy").unveil();
};
<|endoftext|>


<commit_before>import Ember from "ember";
import Component from "@ember/component";
import InboundActions from "ember-component-inbound-actions/inbound-actions";
import EmberJstreeActions from "ember-cli-jstree/mixins/ember-jstree-actions";
import { registerWaiter, unregisterWaiter } from "@ember/test";
import { run } from "@ember/runloop";
import { observer } from "@ember/object";
import { isPresent, typeOf } from "@ember/utils";
import { A } from "@ember/array";
import $ from "jquery";

const { testing } = Ember;

export default Component.extend(InboundActions, EmberJstreeActions, {
  // Properties for Ember communication
  actionReceiver: null,
  currentNode: null,
  selectedNodes: null,

  // Basic configuration objects
  data: A(),
  plugins: A(),
  themes: A(),
  checkCallback: true,
  multiple: true,
  worker: true,

  // Refresh configuration variables
  skipLoading: false,
  forgetState: false,

  // Plugin option objects
  checkboxOptions: null,
  contextmenuOptions: null,
  typesOptions: null,
  searchOptions: null,
  dndOptions: null,

  selectionDidChange: null,
  treeObject: null,

  // Internals
  _isDestroying: false,

  isReady: false,
  _searchTerm: null,

  _isReadyTestWaiter() {
    return this.get("isReady") === true;
  },

  didInsertElement() {
    run.schedule("afterRender", this, this.createTree);
  },

  createTree() {
    if (testing) {
      // Add test waiter.
      registerWaiter(this, this._isReadyTestWaiter);
    }

    let treeObject = this._setupJsTree();

    this._setupEventHandlers(treeObject);

    this.set("treeObject", treeObject);
  },

  willDestroyElement() {
    if (testing) {
      unregisterWaiter(this, this._isReadyTestWaiter);
    }

    this.set("isReady", false);
    this.set("_isDestroying", true);
    this.send("destroy");
  },

  didUpdateAttrs() {
    this._super(...arguments);

    let pluginsArray = this.get("plugins");
    if (isPresent(pluginsArray)) {
      let searchOptions = this.get("searchOptions");
      if (isPresent(searchOptions) && pluginsArray.indexOf("search") >= 0) {
        let searchTerm = this.get("searchTerm");
        if (this.get("_searchTerm") !== searchTerm) {
          run.next("afterRender", () => {
            this.set("_searchTerm", searchTerm);
            this.getTree().search(searchTerm);
          });
        }
      }
    }
  },

  /**
   * Main setup function that registers all the plugins and sets up the core
   * configuration object for jsTree
   *
   * @method _setupJsTree
   */
  _setupJsTree() {
    return this.$().jstree(this._buildConfig());
  },

  /**
   * Builds config object for jsTree. Could be used to override config in descendant classes.
   *
   * @method _buildConfig
   */
  _buildConfig() {
    let configObject = {};

    configObject["core"] = {
      data: this.get("data"),
      check_callback: this.get("checkCallback"),
      multiple: this.get("multiple"),
      worker: this.get("worker")
    };

    let themes = this.get("themes");
    if (isPresent(themes) && typeOf(themes) === "object") {
      configObject["core"]["themes"] = themes;
    }

    let pluginsArray = this.get("plugins");
    if (isPresent(pluginsArray)) {
      pluginsArray = pluginsArray.replace(/ /g, "").split(",");
      configObject["plugins"] = pluginsArray;

      if (
        pluginsArray.includes("contextmenu") ||
        pluginsArray.includes("dnd") ||
        pluginsArray.includes("unique")
      ) {
        // These plugins need core.check_callback
        configObject["core"]["check_callback"] =
          configObject["core"]["check_callback"] || true;
      }

      let checkboxOptions = this.get("checkboxOptions");
      if (isPresent(checkboxOptions) && pluginsArray.includes("checkbox")) {
        configObject["checkbox"] = checkboxOptions;
      }

      let searchOptions = this.get("searchOptions");
      if (isPresent(searchOptions) && pluginsArray.includes("search")) {
        configObject["search"] = searchOptions;
      }

      let stateOptions = this.get("stateOptions");
      if (isPresent(stateOptions) && pluginsArray.includes("state")) {
        configObject["state"] = stateOptions;
      }

      let typesOptions = this.get("typesOptions");
      if (isPresent(typesOptions) && pluginsArray.includes("types")) {
        configObject["types"] = typesOptions;
      }

      let contextmenuOptions = this.get("contextmenuOptions");
      if (
        isPresent(contextmenuOptions) &&
        pluginsArray.includes("contextmenu")
      ) {
        configObject["contextmenu"] = this._setupContextMenus(
          contextmenuOptions
        );
      }

      let dndOptions = this.get("dndOptions");
      if (isPresent(dndOptions) && pluginsArray.includes("dnd")) {
        configObject["dnd"] = dndOptions;
      }
    }

    return configObject;
  },

  /**
   * Setup context menu action handlers to handle ember actions
   *
   * @method _setupContextMenus
   * @param  {Array} contextmenuOptions Context menu configuration options
   * @return {Array} An Array of Ember-friendly options to pass back into the config object
   */
  _setupContextMenus(contextmenuOptions) {
    if (typeOf(contextmenuOptions["items"]) === "object") {
      let newMenuItems = {};
      let menuItems = Object.keys(contextmenuOptions["items"]);
      for (let menuItem of menuItems) {
        let itemData = contextmenuOptions["items"][menuItem];
        newMenuItems[menuItem] = itemData;

        // Only change if not a function
        // This needs to be done to handle Ember actions
        if (typeOf(itemData["action"]) !== "function") {
          let emberAction = itemData["action"];

          newMenuItems[menuItem]["action"] = data => {
            this.send("contextmenuItemDidClick", emberAction, data);
          };
        }
      }

      contextmenuOptions["items"] = newMenuItems;
    }

    return contextmenuOptions;
  },

  /**
   * Register all sorts of events
   * TODO: This should eventually encompass all of the jsTree events declared in their API.
   *
   * @method _setupEventHandlers
   * @param  {Object}
   * @return
   */
  _setupEventHandlers(treeObject) {
    if (typeof treeObject !== "object") {
      throw new Error(
        "You must pass a valid jsTree object to set up its event handlers"
      );
    }

    /*
          Event: init.jstree
          Action: eventDidInit
          triggered after all events are bound
        */
    treeObject.on("init.jstree", () => {
      run(this, function() {
        if (this.get("isDestroyed") || this.get("isDestroying")) {
          return;
        }
        this.callAction("eventDidInit");
      });
    });

    /*
          Event: loading.jstree
          Action: eventIsLoading
          triggered after the loading text is shown and before loading starts
        */
    treeObject.on("loading.jstree", () => {
      run(this, function() {
        if (this.get("isDestroyed") || this.get("isDestroying")) {
          return;
        }
        this.callAction("eventIsLoading");
      });
    });

    /*
          Event: loaded.jstree
          Action: eventDidLoad
          triggered after the root node is loaded for the first time
        */
    treeObject.on("loaded.jstree", () => {
      run(this, function() {
        if (this.get("isDestroyed") || this.get("isDestroying")) {
          return;
        }
        this.callAction("eventDidLoad");
      });
    });

    /*
          Event: ready.jstree
          Action: eventDidBecomeReady
          triggered after all nodes are finished loading
        */
    treeObject.on("ready.jstree", () => {
      run(this, function() {
        if (this.get("isDestroyed") || this.get("isDestroying")) {
          return;
        }
        this.set("isReady", true);
        this.callAction("eventDidBecomeReady");
      });
    });

    /*
          Event: redraw.jstree
          Action: eventDidRedraw
          triggered after nodes are redrawn
        */
    treeObject.on("redraw.jstree", () => {
      run(this, function() {
        if (this.get("isDestroyed") || this.get("isDestroying")) {
          return;
        }
        this.callAction("eventDidRedraw");
      });
    });

    /*
          Event: after_open.jstree
          Action: eventDidOpen
          triggered when a node is opened and the animation is complete
        */
    treeObject.on("after_open.jstree", (event, data) => {
      run(this, function() {
        if (this.get("isDestroyed") || this.get("isDestroying")) {
          return;
        }
        this.callAction("eventDidOpen", data.node);
      });
    });

    /*
          Event: after_close.jstree
          Action: eventDidClose
          triggered when a node is closed and the animation is complete
        */
    treeObject.on("after_close.jstree", (event, data) => {
      run(this, function() {
        if (this.get("isDestroyed") || this.get("isDestroying")) {
          return;
        }
        this.callAction("eventDidClose", data.node);
      });
    });

    /*
          Event: select_node.jstree
          Action: eventDidSelectNode
          triggered when an node is selected
        */
    treeObject.on("select_node.jstree", (event, data) => {
      run(this, function() {
        if (this.get("isDestroyed") || this.get("isDestroying")) {
          return;
        }
        this.callAction(
          "eventDidSelectNode",
          data.node,
          data.selected,
          data.event
        );
      });
    });

    /*
          Event: deselect_node.jstree
          Action: eventDidDeselectNode
          triggered when an node is deselected
        */
    treeObject.on("deselect_node.jstree", (event, data) => {
      run(this, function() {
        if (this.get("isDestroyed") || this.get("isDestroying")) {
          return;
        }
        this.callAction(
          "eventDidDeselectNode",
          data.node,
          data.selected,
          data.event
        );
      });
    });

    /*
          Event: changed.jstree
          Action: jstreeDidChange
          triggered when selection changes
        */
    treeObject.on("changed.jstree", (event, data) => {
      run(this, function() {
        if (this.get("isDestroyed") || this.get("isDestroying")) {
          return;
        }

        // Check if selection changed
        if (isPresent(this.get("treeObject"))) {
          let selectionChangedEventNames = [
            "model",
            "select_node",
            "deselect_node",
            "select_all",
            "deselect_all"
          ];
          if (
            isPresent(data.action) &&
            selectionChangedEventNames.includes(data.action)
          ) {
            let selNodes = A(
              this.get("treeObject")
                .jstree(true)
                .get_selected(true)
            );
            this.set("selectedNodes", selNodes);
          }
        }

        this.callAction("eventDidChange", data);
      });
    });

    /*
          Event: hover_node.jstree
          Action: eventDidHoverNode
          triggered when a node is hovered
        */
    treeObject.on("hover_node.jstree", (event, data) => {
      run(this, function() {
        if (this.get("isDestroyed") || this.get("isDestroying")) {
          return;
        }
        this.callAction("eventDidHoverNode", data.node);
      });
    });

    /*
          Event: dehover_node.jstree
          Action: eventDidDehoverNode
          triggered when a node is no longer hovered
        */
    treeObject.on("dehover_node.jstree", (event, data) => {
      run(this, function() {
        if (this.get("isDestroyed") || this.get("isDestroying")) {
          return;
        }
        this.callAction("eventDidDehoverNode", data.node);
      });
    });

    /*
          Event: show_node.jstree
          Action: eventDidShowNode
          triggered when a node is no longer hovered
        */
    treeObject.on("show_node.jstree", (event, data) => {
      run(this, function() {
        if (this.get("isDestroyed") || this.get("isDestroying")) {
          return;
        }
        this.callAction("eventDidShowNode", data.node);
      });
    });

    /*
          Event: move_node.jstree
          Action: eventDidMoveNode
          triggered when a node is moved
        */
    treeObject.on("move_node.jstree", (event, data) => {
      run(this, function() {
        if (this.get("isDestroyed") || this.get("isDestroying")) {
          return;
        }
        this.callAction("eventDidMoveNode", data.node);
      });
    });

    if (this.get("plugins").indexOf("checkbox") > -1) {
      /*
           Event: disable_checkbox.jstree
           Action: eventDidDisableCheckbox
           triggered when an node's checkbox is disabled
         */
      treeObject.on("disable_checkbox.jstree", (event, data) => {
        run(this, function() {
          if (this.get("isDestroyed") || this.get("isDestroying")) {
            return;
          }
          this.callAction("eventDidDisableCheckbox", data.node);
        });
      });

      /*
           Event: enable_checkbox.jstree
           Action: eventDidEnableCheckbox
           triggered when an node's checkbox is enabled
         */
      treeObject.on("enable_checkbox.jstree", (event, data) => {
        run(this, function() {
          if (this.get("isDestroyed") || this.get("isDestroying")) {
            return;
          }
          this.callAction("eventDidEnableCheckbox", data.node);
        });
      });

      if (
        isPresent("checkboxOptions.tie_selected") &&
        !this.get("checkboxOptions.tie_selected")
      ) {
        /*
             Event: check_node.jstree
             Action: eventDidCheckNode
             triggered when an node is checked (only if tie_selection in checkbox settings is false)
           */
        treeObject.on("check_node.jstree", (event, data) => {
          run(this, function() {
            if (this.get("isDestroyed") || this.get("isDestroying")) {
              return;
            }
            this.callAction(
              "eventDidCheckNode",
              data.node,
              data.selected,
              data.event
            );
          });
        });

        /*
             Event: uncheck_node.jstree
             Action: eventDidUncheckNode
             triggered when an node is unchecked (only if tie_selection in checkbox settings is false)
           */
        treeObject.on("uncheck_node.jstree", (event, data) => {
          run(this, function() {
            if (this.get("isDestroyed") || this.get("isDestroying")) {
              return;
            }
            this.callAction(
              "eventDidUncheckNode",
              data.node,
              data.selected,
              data.event
            );
          });
        });

        /*
             Event: check_all.jstree
             Action: eventDidCheckAll
             triggered when all nodes are checked (only if tie_selection in checkbox settings is false)
           */
        treeObject.on("check_all.jstree", (event, data) => {
          run(this, function() {
            if (this.get("isDestroyed") || this.get("isDestroying")) {
              return;
            }
            this.callAction("eventDidCheckAll", data.selected);
          });
        });

        /*
             Event: uncheck_all.jstree
             Action: eventDidUncheckAll
             triggered when all nodes are unchecked (only if tie_selection in checkbox settings is false)
           */
        treeObject.on("uncheck_all.jstree", (event, data) => {
          run(this, function() {
            if (this.get("isDestroyed") || this.get("isDestroying")) {
              return;
            }
            this.callAction("eventDidUncheckAll", data.node, data.selected);
          });
        });
      }
    }
  },

  /**
   * Refreshes the data in the tree
   * TODO: Investigate why redraw(true) doesn't work...
   *
   * @method _redrawTree
   */
  _refreshTree: observer("data", function() {
    let tree = this.getTree();
    if (null !== tree && false !== tree) {
      tree.settings.core["data"] = this.get("data");
      tree.refresh(this.get("skipLoading"), this.get("forgetState"));
    } else {
      // setup again if destroyed
      let treeObject = this._setupJsTree();
      this._setupEventHandlers(treeObject);
      this.set("treeObject", treeObject);
    }
  }),

  getTree() {
    let tree = this.get("treeObject");
    return tree.jstree(true);
  },

  actions: {
    contextmenuItemDidClick(actionName, data) {
      let emberTreeObj = this.get("getTree");

      let instance = $.jstree.reference(data.reference);
      let node = instance.get_node(data.reference);

      this.callAction(actionName, node, emberTreeObj);
    }
  }
});
<commit_msg>Fix issue if no plugins are defined<commit_after>import Ember from "ember";
import Component from "@ember/component";
import InboundActions from "ember-component-inbound-actions/inbound-actions";
import EmberJstreeActions from "ember-cli-jstree/mixins/ember-jstree-actions";
import { registerWaiter, unregisterWaiter } from "@ember/test";
import { run } from "@ember/runloop";
import { observer } from "@ember/object";
import { isPresent, typeOf } from "@ember/utils";
import { A } from "@ember/array";
import $ from "jquery";

const { testing } = Ember;

export default Component.extend(InboundActions, EmberJstreeActions, {
  // Properties for Ember communication
  actionReceiver: null,
  currentNode: null,
  selectedNodes: null,

  // Basic configuration objects
  data: A(),
  plugins: A(),
  themes: A(),
  checkCallback: true,
  multiple: true,
  worker: true,

  // Refresh configuration variables
  skipLoading: false,
  forgetState: false,

  // Plugin option objects
  checkboxOptions: null,
  contextmenuOptions: null,
  typesOptions: null,
  searchOptions: null,
  dndOptions: null,

  selectionDidChange: null,
  treeObject: null,

  // Internals
  _isDestroying: false,

  isReady: false,
  _searchTerm: null,

  _isReadyTestWaiter() {
    return this.get("isReady") === true;
  },

  didInsertElement() {
    run.schedule("afterRender", this, this.createTree);
  },

  createTree() {
    if (testing) {
      // Add test waiter.
      registerWaiter(this, this._isReadyTestWaiter);
    }

    let treeObject = this._setupJsTree();

    this._setupEventHandlers(treeObject);

    this.set("treeObject", treeObject);
  },

  willDestroyElement() {
    if (testing) {
      unregisterWaiter(this, this._isReadyTestWaiter);
    }

    this.set("isReady", false);
    this.set("_isDestroying", true);
    this.send("destroy");
  },

  didUpdateAttrs() {
    this._super(...arguments);

    let pluginsArray = this.get("plugins");
    if (isPresent(pluginsArray)) {
      let searchOptions = this.get("searchOptions");
      if (isPresent(searchOptions) && pluginsArray.indexOf("search") >= 0) {
        let searchTerm = this.get("searchTerm");
        if (this.get("_searchTerm") !== searchTerm) {
          run.next("afterRender", () => {
            this.set("_searchTerm", searchTerm);
            this.getTree().search(searchTerm);
          });
        }
      }
    }
  },

  /**
   * Main setup function that registers all the plugins and sets up the core
   * configuration object for jsTree
   *
   * @method _setupJsTree
   */
  _setupJsTree() {
    return this.$().jstree(this._buildConfig());
  },

  /**
   * Builds config object for jsTree. Could be used to override config in descendant classes.
   *
   * @method _buildConfig
   */
  _buildConfig() {
    let configObject = {};

    configObject["core"] = {
      data: this.get("data"),
      check_callback: this.get("checkCallback"),
      multiple: this.get("multiple"),
      worker: this.get("worker")
    };

    let themes = this.get("themes");
    if (isPresent(themes) && typeOf(themes) === "object") {
      configObject["core"]["themes"] = themes;
    }

    let pluginsArray = this.get("plugins");
    if (isPresent(pluginsArray)) {
      pluginsArray = pluginsArray.replace(/ /g, "").split(",");
      configObject["plugins"] = pluginsArray;

      if (
        pluginsArray.includes("contextmenu") ||
        pluginsArray.includes("dnd") ||
        pluginsArray.includes("unique")
      ) {
        // These plugins need core.check_callback
        configObject["core"]["check_callback"] =
          configObject["core"]["check_callback"] || true;
      }

      let checkboxOptions = this.get("checkboxOptions");
      if (isPresent(checkboxOptions) && pluginsArray.includes("checkbox")) {
        configObject["checkbox"] = checkboxOptions;
      }

      let searchOptions = this.get("searchOptions");
      if (isPresent(searchOptions) && pluginsArray.includes("search")) {
        configObject["search"] = searchOptions;
      }

      let stateOptions = this.get("stateOptions");
      if (isPresent(stateOptions) && pluginsArray.includes("state")) {
        configObject["state"] = stateOptions;
      }

      let typesOptions = this.get("typesOptions");
      if (isPresent(typesOptions) && pluginsArray.includes("types")) {
        configObject["types"] = typesOptions;
      }

      let contextmenuOptions = this.get("contextmenuOptions");
      if (
        isPresent(contextmenuOptions) &&
        pluginsArray.includes("contextmenu")
      ) {
        configObject["contextmenu"] = this._setupContextMenus(
          contextmenuOptions
        );
      }

      let dndOptions = this.get("dndOptions");
      if (isPresent(dndOptions) && pluginsArray.includes("dnd")) {
        configObject["dnd"] = dndOptions;
      }
    }

    return configObject;
  },

  /**
   * Setup context menu action handlers to handle ember actions
   *
   * @method _setupContextMenus
   * @param  {Array} contextmenuOptions Context menu configuration options
   * @return {Array} An Array of Ember-friendly options to pass back into the config object
   */
  _setupContextMenus(contextmenuOptions) {
    if (typeOf(contextmenuOptions["items"]) === "object") {
      let newMenuItems = {};
      let menuItems = Object.keys(contextmenuOptions["items"]);
      for (let menuItem of menuItems) {
        let itemData = contextmenuOptions["items"][menuItem];
        newMenuItems[menuItem] = itemData;

        // Only change if not a function
        // This needs to be done to handle Ember actions
        if (typeOf(itemData["action"]) !== "function") {
          let emberAction = itemData["action"];

          newMenuItems[menuItem]["action"] = data => {
            this.send("contextmenuItemDidClick", emberAction, data);
          };
        }
      }

      contextmenuOptions["items"] = newMenuItems;
    }

    return contextmenuOptions;
  },

  /**
   * Register all sorts of events
   * TODO: This should eventually encompass all of the jsTree events declared in their API.
   *
   * @method _setupEventHandlers
   * @param  {Object}
   * @return
   */
  _setupEventHandlers(treeObject) {
    if (typeof treeObject !== "object") {
      throw new Error(
        "You must pass a valid jsTree object to set up its event handlers"
      );
    }

    /*
          Event: init.jstree
          Action: eventDidInit
          triggered after all events are bound
        */
    treeObject.on("init.jstree", () => {
      run(this, function() {
        if (this.get("isDestroyed") || this.get("isDestroying")) {
          return;
        }
        this.callAction("eventDidInit");
      });
    });

    /*
          Event: loading.jstree
          Action: eventIsLoading
          triggered after the loading text is shown and before loading starts
        */
    treeObject.on("loading.jstree", () => {
      run(this, function() {
        if (this.get("isDestroyed") || this.get("isDestroying")) {
          return;
        }
        this.callAction("eventIsLoading");
      });
    });

    /*
          Event: loaded.jstree
          Action: eventDidLoad
          triggered after the root node is loaded for the first time
        */
    treeObject.on("loaded.jstree", () => {
      run(this, function() {
        if (this.get("isDestroyed") || this.get("isDestroying")) {
          return;
        }
        this.callAction("eventDidLoad");
      });
    });

    /*
          Event: ready.jstree
          Action: eventDidBecomeReady
          triggered after all nodes are finished loading
        */
    treeObject.on("ready.jstree", () => {
      run(this, function() {
        if (this.get("isDestroyed") || this.get("isDestroying")) {
          return;
        }
        this.set("isReady", true);
        this.callAction("eventDidBecomeReady");
      });
    });

    /*
          Event: redraw.jstree
          Action: eventDidRedraw
          triggered after nodes are redrawn
        */
    treeObject.on("redraw.jstree", () => {
      run(this, function() {
        if (this.get("isDestroyed") || this.get("isDestroying")) {
          return;
        }
        this.callAction("eventDidRedraw");
      });
    });

    /*
          Event: after_open.jstree
          Action: eventDidOpen
          triggered when a node is opened and the animation is complete
        */
    treeObject.on("after_open.jstree", (event, data) => {
      run(this, function() {
        if (this.get("isDestroyed") || this.get("isDestroying")) {
          return;
        }
        this.callAction("eventDidOpen", data.node);
      });
    });

    /*
          Event: after_close.jstree
          Action: eventDidClose
          triggered when a node is closed and the animation is complete
        */
    treeObject.on("after_close.jstree", (event, data) => {
      run(this, function() {
        if (this.get("isDestroyed") || this.get("isDestroying")) {
          return;
        }
        this.callAction("eventDidClose", data.node);
      });
    });

    /*
          Event: select_node.jstree
          Action: eventDidSelectNode
          triggered when an node is selected
        */
    treeObject.on("select_node.jstree", (event, data) => {
      run(this, function() {
        if (this.get("isDestroyed") || this.get("isDestroying")) {
          return;
        }
        this.callAction(
          "eventDidSelectNode",
          data.node,
          data.selected,
          data.event
        );
      });
    });

    /*
          Event: deselect_node.jstree
          Action: eventDidDeselectNode
          triggered when an node is deselected
        */
    treeObject.on("deselect_node.jstree", (event, data) => {
      run(this, function() {
        if (this.get("isDestroyed") || this.get("isDestroying")) {
          return;
        }
        this.callAction(
          "eventDidDeselectNode",
          data.node,
          data.selected,
          data.event
        );
      });
    });

    /*
          Event: changed.jstree
          Action: jstreeDidChange
          triggered when selection changes
        */
    treeObject.on("changed.jstree", (event, data) => {
      run(this, function() {
        if (this.get("isDestroyed") || this.get("isDestroying")) {
          return;
        }

        // Check if selection changed
        if (isPresent(this.get("treeObject"))) {
          let selectionChangedEventNames = [
            "model",
            "select_node",
            "deselect_node",
            "select_all",
            "deselect_all"
          ];
          if (
            isPresent(data.action) &&
            selectionChangedEventNames.includes(data.action)
          ) {
            let selNodes = A(
              this.get("treeObject")
                .jstree(true)
                .get_selected(true)
            );
            this.set("selectedNodes", selNodes);
          }
        }

        this.callAction("eventDidChange", data);
      });
    });

    /*
          Event: hover_node.jstree
          Action: eventDidHoverNode
          triggered when a node is hovered
        */
    treeObject.on("hover_node.jstree", (event, data) => {
      run(this, function() {
        if (this.get("isDestroyed") || this.get("isDestroying")) {
          return;
        }
        this.callAction("eventDidHoverNode", data.node);
      });
    });

    /*
          Event: dehover_node.jstree
          Action: eventDidDehoverNode
          triggered when a node is no longer hovered
        */
    treeObject.on("dehover_node.jstree", (event, data) => {
      run(this, function() {
        if (this.get("isDestroyed") || this.get("isDestroying")) {
          return;
        }
        this.callAction("eventDidDehoverNode", data.node);
      });
    });

    /*
          Event: show_node.jstree
          Action: eventDidShowNode
          triggered when a node is no longer hovered
        */
    treeObject.on("show_node.jstree", (event, data) => {
      run(this, function() {
        if (this.get("isDestroyed") || this.get("isDestroying")) {
          return;
        }
        this.callAction("eventDidShowNode", data.node);
      });
    });

    /*
          Event: move_node.jstree
          Action: eventDidMoveNode
          triggered when a node is moved
        */
    treeObject.on("move_node.jstree", (event, data) => {
      run(this, function() {
        if (this.get("isDestroyed") || this.get("isDestroying")) {
          return;
        }
        this.callAction("eventDidMoveNode", data.node);
      });
    });

    let pluginsArray = this.get("plugins");
    if (isPresent(pluginsArray) && pluginsArray.indexOf("checkbox") > -1) {
      /*
           Event: disable_checkbox.jstree
           Action: eventDidDisableCheckbox
           triggered when an node's checkbox is disabled
         */
      treeObject.on("disable_checkbox.jstree", (event, data) => {
        run(this, function() {
          if (this.get("isDestroyed") || this.get("isDestroying")) {
            return;
          }
          this.callAction("eventDidDisableCheckbox", data.node);
        });
      });

      /*
           Event: enable_checkbox.jstree
           Action: eventDidEnableCheckbox
           triggered when an node's checkbox is enabled
         */
      treeObject.on("enable_checkbox.jstree", (event, data) => {
        run(this, function() {
          if (this.get("isDestroyed") || this.get("isDestroying")) {
            return;
          }
          this.callAction("eventDidEnableCheckbox", data.node);
        });
      });

      if (
        isPresent("checkboxOptions.tie_selected") &&
        !this.get("checkboxOptions.tie_selected")
      ) {
        /*
             Event: check_node.jstree
             Action: eventDidCheckNode
             triggered when an node is checked (only if tie_selection in checkbox settings is false)
           */
        treeObject.on("check_node.jstree", (event, data) => {
          run(this, function() {
            if (this.get("isDestroyed") || this.get("isDestroying")) {
              return;
            }
            this.callAction(
              "eventDidCheckNode",
              data.node,
              data.selected,
              data.event
            );
          });
        });

        /*
             Event: uncheck_node.jstree
             Action: eventDidUncheckNode
             triggered when an node is unchecked (only if tie_selection in checkbox settings is false)
           */
        treeObject.on("uncheck_node.jstree", (event, data) => {
          run(this, function() {
            if (this.get("isDestroyed") || this.get("isDestroying")) {
              return;
            }
            this.callAction(
              "eventDidUncheckNode",
              data.node,
              data.selected,
              data.event
            );
          });
        });

        /*
             Event: check_all.jstree
             Action: eventDidCheckAll
             triggered when all nodes are checked (only if tie_selection in checkbox settings is false)
           */
        treeObject.on("check_all.jstree", (event, data) => {
          run(this, function() {
            if (this.get("isDestroyed") || this.get("isDestroying")) {
              return;
            }
            this.callAction("eventDidCheckAll", data.selected);
          });
        });

        /*
             Event: uncheck_all.jstree
             Action: eventDidUncheckAll
             triggered when all nodes are unchecked (only if tie_selection in checkbox settings is false)
           */
        treeObject.on("uncheck_all.jstree", (event, data) => {
          run(this, function() {
            if (this.get("isDestroyed") || this.get("isDestroying")) {
              return;
            }
            this.callAction("eventDidUncheckAll", data.node, data.selected);
          });
        });
      }
    }
  },

  /**
   * Refreshes the data in the tree
   * TODO: Investigate why redraw(true) doesn't work...
   *
   * @method _redrawTree
   */
  _refreshTree: observer("data", function() {
    let tree = this.getTree();
    if (null !== tree && false !== tree) {
      tree.settings.core["data"] = this.get("data");
      tree.refresh(this.get("skipLoading"), this.get("forgetState"));
    } else {
      // setup again if destroyed
      let treeObject = this._setupJsTree();
      this._setupEventHandlers(treeObject);
      this.set("treeObject", treeObject);
    }
  }),

  getTree() {
    let tree = this.get("treeObject");
    return tree.jstree(true);
  },

  actions: {
    contextmenuItemDidClick(actionName, data) {
      let emberTreeObj = this.get("getTree");

      let instance = $.jstree.reference(data.reference);
      let node = instance.get_node(data.reference);

      this.callAction(actionName, node, emberTreeObj);
    }
  }
});
<|endoftext|>


<commit_before>import 'react-native';
import React from 'react';
// Note: test renderer must be required after react-native.
import renderer from 'react-test-renderer';
import BusListHeader from '../BusListHeader';

jest.mock('react-native-localization');

describe('BusListHeader component', () => {
  it('renders correctly', () => {
    const tree = renderer.create(<BusListHeader />).toJSON();

    expect(tree).toMatchSnapshot();
  });
});
<commit_msg>added tests for RouteListHeader<commit_after>import 'react-native';
import React from 'react';
// Note: test renderer must be required after react-native.
import renderer from 'react-test-renderer';
import BusListHeader, { RouteListHeader } from '../BusListHeader';

jest.mock('react-native-localization');

describe('BusListHeader component', () => {
  it('renders correctly', () => {
    const tree = renderer.create(<BusListHeader />).toJSON();

    expect(tree).toMatchSnapshot();
  });
});

describe('RouteListHeader component', () => {
    it('renders correctly', () => {
        const tree = renderer.create(<RouteListHeader />).toJSON();

        expect(tree).toMatchSnapshot();
    });
});<|endoftext|>


<commit_before>/*
**  ComponentJS -- Component System for JavaScript <http://componentjs.com>
**  Copyright (c) 2009-2013 Ralf S. Engelschall <http://engelschall.com>
**
**  This Source Code Form is subject to the terms of the Mozilla Public
**  License, v. 2.0. If a copy of the MPL was not distributed with this
**  file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/

/*  API function: select an arbitrary value via a path specification
    and either get the current value or set the new value  */
$cs.select = function (obj, spec, value) {
    /*  compile path specification (or use pre-compiled path)  */
    var path = (typeof spec === "string" ? _cs.select_parse(spec) : spec);

    /*  subset the object graph  */
    return (
          arguments.length === 2
        ? _cs.select_path(obj, path)
        : _cs.select_path(obj, path, value)
    );
};

/*  the internal compile cache  */
_cs.select_cache = {};

/*  compile a path specification into array of dereferencing steps  */
_cs.select_parse = function (spec) {
    var path = _cs.select_cache[spec];
    if (typeof path === "undefined") {
        path = [];
        var pos = 0;
        var txt = spec;
        var m;
        while (txt !== "") {
            if ((m = txt.match(/^\s*(?:\.)?\s*([a-zA-Z$_][a-zA-Z$0-9_:-]*)/)) !== null)
                path.push(m[1]);
            else if ((m = txt.match(/^\s*\[\s*(\d+|\*{1,2})\s*\]/)) !== null)
                path.push(m[1]);
            else if ((m = txt.match(/^\s*\[\s*"((?:\\"|.)*?)"\s*\]/)) !== null)
                path.push(m[1].replace(/\\"/g, "\""));
            else if ((m = txt.match(/^\s*\[\s*'((?:\\'|.)*?)'\s*\]/)) !== null)
                path.push(m[1].replace(/\\'/g, "'"));
            else if ((m = txt.match(/^\s+$/)) !== null)
                break;
            else
                throw _cs.exception("select", "parse error: invalid character at: " +
                    spec.substr(0, pos) + "<" + txt.substr(0, 1) + ">" + txt.substr(1));
            pos += m[0].length;
            txt = txt.substr(m[0].length);
        }
        _cs.select_cache[spec] = path;
    }
    return path;
};

/*  subset an object graph  */
_cs.select_path = function (obj, path) {
    /*  handle special case of empty path */
    if (path.length === 0) {
        if (arguments.length === 3)
            throw _cs.exception("select", "cannot set value on empty path");
        else
            return obj;
    }

    /*  step into object graph according to path prefix  */
    var i = 0;
    while (i < path.length - 1) {
        if (typeof obj !== "object")
            throw _cs.exception("select", "cannot further dereference: no more intermediate objects in path");
        obj = obj[path[i++]];
    }

    /*  get the old value  */
    if (typeof obj !== "object")
        throw _cs.exception("select", "cannot further dereference: no object at end of path");
    var value_old = obj[path[i]];

    /*  optionally set new value  */
    if (arguments.length === 3) {
        var value_new = arguments[2];
        if (value_new === undefined) {
            /*  delete value from collection  */
            if (obj instanceof Array)
                obj.splice(parseInt(path[i], 10), 1);
            else
                delete obj[path[i]];
        }
        else
            /*  set value into collection  */
            obj[path[i]] = value_new;
    }

    return value_old;
};
<commit_msg>allow also foo.N notation and not just foo[N], where N is a digit<commit_after>/*
**  ComponentJS -- Component System for JavaScript <http://componentjs.com>
**  Copyright (c) 2009-2013 Ralf S. Engelschall <http://engelschall.com>
**
**  This Source Code Form is subject to the terms of the Mozilla Public
**  License, v. 2.0. If a copy of the MPL was not distributed with this
**  file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/

/*  API function: select an arbitrary value via a path specification
    and either get the current value or set the new value  */
$cs.select = function (obj, spec, value) {
    /*  compile path specification (or use pre-compiled path)  */
    var path = (typeof spec === "string" ? _cs.select_parse(spec) : spec);

    /*  subset the object graph  */
    return (
          arguments.length === 2
        ? _cs.select_path(obj, path)
        : _cs.select_path(obj, path, value)
    );
};

/*  the internal compile cache  */
_cs.select_cache = {};

/*  compile a path specification into array of dereferencing steps  */
_cs.select_parse = function (spec) {
    var path = _cs.select_cache[spec];
    if (typeof path === "undefined") {
        path = [];
        var pos = 0;
        var txt = spec;
        var m;
        while (txt !== "") {
            if ((m = txt.match(/^\s*(?:\.)?\s*([a-zA-Z$0-9_][a-zA-Z$0-9_:-]*)/)) !== null)
                path.push(m[1]);
            else if ((m = txt.match(/^\s*\[\s*(\d+|\*{1,2})\s*\]/)) !== null)
                path.push(m[1]);
            else if ((m = txt.match(/^\s*\[\s*"((?:\\"|.)*?)"\s*\]/)) !== null)
                path.push(m[1].replace(/\\"/g, "\""));
            else if ((m = txt.match(/^\s*\[\s*'((?:\\'|.)*?)'\s*\]/)) !== null)
                path.push(m[1].replace(/\\'/g, "'"));
            else if ((m = txt.match(/^\s+$/)) !== null)
                break;
            else
                throw _cs.exception("select", "parse error: invalid character at: " +
                    spec.substr(0, pos) + "<" + txt.substr(0, 1) + ">" + txt.substr(1));
            pos += m[0].length;
            txt = txt.substr(m[0].length);
        }
        _cs.select_cache[spec] = path;
    }
    return path;
};

/*  subset an object graph  */
_cs.select_path = function (obj, path) {
    /*  handle special case of empty path */
    if (path.length === 0) {
        if (arguments.length === 3)
            throw _cs.exception("select", "cannot set value on empty path");
        else
            return obj;
    }

    /*  step into object graph according to path prefix  */
    var i = 0;
    while (i < path.length - 1) {
        if (typeof obj !== "object")
            throw _cs.exception("select", "cannot further dereference: no more intermediate objects in path");
        obj = obj[path[i++]];
    }

    /*  get the old value  */
    if (typeof obj !== "object")
        throw _cs.exception("select", "cannot further dereference: no object at end of path");
    var value_old = obj[path[i]];

    /*  optionally set new value  */
    if (arguments.length === 3) {
        var value_new = arguments[2];
        if (value_new === undefined) {
            /*  delete value from collection  */
            if (obj instanceof Array)
                obj.splice(parseInt(path[i], 10), 1);
            else
                delete obj[path[i]];
        }
        else
            /*  set value into collection  */
            obj[path[i]] = value_new;
    }

    return value_old;
};
<|endoftext|>


<commit_before>var CONVERSION_VALUE = 50.0;
var MIN_NUM_CONVERSIONS = 25;

function main() {
  ////setAdGroupBids("ALL_TIME");

  setAdGroupBids("LAST_30_DAYS");
  setAdGroupBids_highCost("LAST_30_DAYS");
  setAdGroupBids("LAST_14_DAYS");
  setAdGroupBids_highCost("LAST_14_DAYS");
  setAdGroupBids("LAST_7_DAYS");
  setAdGroupBids_highCost("LAST_7_DAYS");

  setKeywordBids("LAST_30_DAYS");
  setKeywordBids_highCost("LAST_30_DAYS");
  setKeywordBids("LAST_14_DAYS");
  setKeywordBids_highCost("LAST_14_DAYS");
  setKeywordBids("LAST_7_DAYS");
  setKeywordBids_highCost("LAST_7_DAYS");
}

// ******************************************************************
// SET ADGROUP BIDS
// ******************************************************************
function setAdGroupBids(dateRange) {
   var adGroupIterator = AdWordsApp.adGroups()
      .forDateRange(dateRange)
      .withCondition("Status = ENABLED")
      .withCondition("CampaignStatus = ENABLED")
      .withCondition("LabelNames CONTAINS_NONE ['Script Ignore']")
      .withCondition("ConvertedClicks > " + MIN_NUM_CONVERSIONS)
      .get();

  Logger.log('Total adGroups found : ' + adGroupIterator.totalNumEntities());

  while (adGroupIterator.hasNext()) {
    var adGroup = adGroupIterator.next();
    var stats = adGroup.getStatsFor(dateRange);
    var conv_rate = stats.getClickConversionRate();
    var max_cpc = roundDown(conv_rate * CONVERSION_VALUE);

    //Logger.log('AdGroup Name: ' + adGroup.getName() + ' ConvRate:' + conv_rate + ' MaxCPC:' + max_cpc);
    adGroup.bidding().setCpc(max_cpc);
  }
}

// ******************************************************************
// SET ADGROUP BIDS FOR HIGH COST ADWORDS
// ******************************************************************
function setAdGroupBids_highCost(dateRange) {
   Logger.log('\nHigh Cost AdGroups : ' + dateRange);
    var highCostThreshold = (CONVERSION_VALUE * .80);

   var adGroupIterator = AdWordsApp.adGroups()
      .forDateRange(dateRange)
      .withCondition("Status = ENABLED")
      .withCondition("CampaignStatus = ENABLED")
      .withCondition("LabelNames CONTAINS_NONE ['Script Ignore']")
      .withCondition("ConvertedClicks <= " + MIN_NUM_CONVERSIONS)
      .get();

  Logger.log('Total adGroups found : ' + adGroupIterator.totalNumEntities());

  while (adGroupIterator.hasNext()) {
    var adGroup = adGroupIterator.next();
    var stats = adGroup.getStatsFor(dateRange);
    var conversions = stats.getConvertedClicks();
    var clicks = stats.getClicks();
    var cost = stats.getCost();
    var conv_rate = stats.getClickConversionRate();

    if( conversions == 0 && clicks > 0) {
      conversions = 1;
      conv_rate = conversions / clicks;
    }

    var cpa = cost / conversions;

    if (cpa > highCostThreshold) {
      var max_cpc = roundDown(conv_rate * CONVERSION_VALUE);

      if( max_cpc < adGroup.bidding().getCpc()) {
        adGroup.bidding().setCpc(max_cpc);
      }
    }
  }
}

// ******************************************************************
// SET KEYWORD BIDS
// ******************************************************************
function setKeywordBids(dateRange) {
  Logger.log('\nSet Keyword Bids : ' + dateRange);

   var KeywordIterator = AdWordsApp.keywords()
      .forDateRange(dateRange)
      .withCondition("Status = ENABLED")
      .withCondition("CampaignStatus = ENABLED")
      .withCondition("AdGroupStatus = ENABLED")
      .withCondition("LabelNames CONTAINS_NONE ['Script Ignore']")
      .withCondition("ConvertedClicks > " + MIN_NUM_CONVERSIONS)
      .get();

  Logger.log('Total Keywords found : ' + KeywordIterator.totalNumEntities());

  while (KeywordIterator.hasNext()) {
    var keyword = KeywordIterator.next();
    var stats = keyword.getStatsFor(dateRange);
    var conv_rate = stats.getClickConversionRate();
    var max_cpc = roundDown(conv_rate * CONVERSION_VALUE);

    // Temp variables
    var keywordBidding = keyword.bidding();
    var keywordCpc = keywordBidding.getCpc();

    // Calculate Range for wich we want to keep adgroup bids
    var AdGroupCpc = keyword.getAdGroup().bidding().getCpc();
    var AdGroupCpcMin = AdGroupCpc * 0.9;
    var AdGroupCpcMax = AdGroupCpc * 1.1;

    if( max_cpc > keywordCpc && stats.getAveragePosition() < 1.2 ) {
      Logger.log('Keyword: ' + keyword.getText() + ' Position too high. Bid not updated.');
    } else if( max_cpc > AdGroupCpcMin && max_cpc < AdGroupCpcMax ) {
      keywordBidding.clearCpc();
      Logger.log('Keyword: ' + keyword.getText() + ' Keyword text reset to AdGroup bid');
    } else {
      Logger.log('Keyword: ' + keyword.getText() + ' ConvRate:' + conv_rate + ' MaxCPC:' + max_cpc);
      keywordBidding.setCpc(max_cpc);
    }
  }
}

// ******************************************************************
// SET KEYWORD BIDS, HIGH COST
// ******************************************************************
function setKeywordBids_highCost(dateRange) {
  Logger.log('\nSet Keyword Bids, High Cost : ' + dateRange);
 var highCostThreshold = (CONVERSION_VALUE * .80);

 var KeywordIterator = AdWordsApp.keywords()
      .forDateRange(dateRange)
      .withCondition("Status = ENABLED")
      .withCondition("CampaignStatus = ENABLED")
      .withCondition("AdGroupStatus = ENABLED")
      .withCondition("LabelNames CONTAINS_NONE ['Script Ignore']")
      .withCondition("ConvertedClicks <= " + MIN_NUM_CONVERSIONS)
      .get();

  Logger.log('Total Keywords found : ' + KeywordIterator.totalNumEntities());

  while (KeywordIterator.hasNext()) {
    var keyword = KeywordIterator.next();
    var stats = keyword.getStatsFor(dateRange);
    var conversions = stats.getConvertedClicks();
    var clicks = stats.getClicks();
    var cost = stats.getCost();
    var conv_rate = stats.getClickConversionRate();

    if( conversions == 0 && clicks > 0) {
      conversions = 1;
      conv_rate = conversions / clicks;
    }

    var cpa = cost / conversions;

    if (cpa > highCostThreshold) {
      var max_cpc = roundDown(conv_rate * CONVERSION_VALUE);

      if( max_cpc < keyword.bidding().getCpc()) {
        keyword.bidding().setCpc(max_cpc);
      }
    }
  }
}

// Round down bids to the closest quarter dollar.
function roundDown(value) {
  var suffix = value % 1;
  var prefix = value - suffix;

  var newSuffix = suffix;

  if( suffix < 0.25 ) {
    if( prefix > 0 ) newSuffix = 0.0;
  } else if( suffix < 0.50 ) {
    newSuffix = 0.25 ;
  } else if( suffix < 0.75) {
    newSuffix = 0.50;
  } else {
    newSuffix = 0.75;
  }

  var newBid = prefix + newSuffix;

  //Logger.log('bid: ' + value + '; new bid: ' + newBid);

  return newBid;
}
<commit_msg>Refactoring + Better Filtering / AdGroup Selection<commit_after>var CONVERSION_VALUE = 50.0;
var MIN_NUM_CONVERSIONS = 25;
var TAG_IGNORE = 'Script Ignore';
var CAMPAIGN_INCLUDE = '[INCLUDE]'; // Only include Adgroups and keywords in Campaigns with this text string in the name

function main() {
  ////setAdGroupBids("ALL_TIME");

  Logger.log('\n***** 30 DAYS *****');
  setAdGroupBids("LAST_30_DAYS");
  setAdGroupBids_highCost("LAST_30_DAYS");
  setKeywordBids("LAST_30_DAYS");
  setKeywordBids_highCost("LAST_30_DAYS");

  Logger.log('\n***** 14 DAYS *****');
  setAdGroupBids("LAST_14_DAYS");
  setAdGroupBids_highCost("LAST_14_DAYS");
  setKeywordBids("LAST_14_DAYS");
  setKeywordBids_highCost("LAST_14_DAYS");

  Logger.log('\n***** 7 DAYS *****');
  setAdGroupBids("LAST_7_DAYS");
  setAdGroupBids_highCost("LAST_7_DAYS");
  setKeywordBids("LAST_7_DAYS");
  setKeywordBids_highCost("LAST_7_DAYS");
}

// ******************************************************************
// SET ADGROUP BIDS
// ******************************************************************
function setAdGroupBids(dateRange) {
   Logger.log('\nSet Ad Group Bids, > ' + MIN_NUM_CONVERSIONS + ' Conv : ' + dateRange);
   var adGroupIterator = GetAdGroupSelector(dateRange)
      .withCondition("ConvertedClicks > " + MIN_NUM_CONVERSIONS)
      .get();

  Logger.log('Total adGroups found : ' + adGroupIterator.totalNumEntities());

  while (adGroupIterator.hasNext()) {
    var adGroup = adGroupIterator.next();
    var stats = adGroup.getStatsFor(dateRange);
    var conv_rate = stats.getClickConversionRate();
    var max_cpc = roundDown(conv_rate * CONVERSION_VALUE);

    //Logger.log('AdGroup Name: ' + adGroup.getName() + ' ConvRate:' + conv_rate + ' MaxCPC:' + max_cpc);
    adGroup.bidding().setCpc(max_cpc);
  }
}

// ******************************************************************
// SET ADGROUP BIDS FOR HIGH COST ADWORDS
// ******************************************************************
function setAdGroupBids_highCost(dateRange) {
   Logger.log('\nHigh Cost AdGroups : ' + dateRange);
    var highCostThreshold = (CONVERSION_VALUE * .80);

   var adGroupIterator = GetAdGroupSelector(dateRange)
     .withCondition("ConvertedClicks <= " + MIN_NUM_CONVERSIONS)
     .get();

  Logger.log('Total adGroups found : ' + adGroupIterator.totalNumEntities());

  while (adGroupIterator.hasNext()) {
    var adGroup = adGroupIterator.next();
    var stats = adGroup.getStatsFor(dateRange);
    var conversions = stats.getConvertedClicks();
    var clicks = stats.getClicks();
    var cost = stats.getCost();
    var conv_rate = stats.getClickConversionRate();

    if( conversions == 0 && clicks > 0) {
      conversions = 1;
      conv_rate = conversions / clicks;
    }

    var cpa = cost / conversions;

    if (cpa > highCostThreshold) {
      var max_cpc = roundDown(conv_rate * CONVERSION_VALUE);

      if( max_cpc < adGroup.bidding().getCpc()) {
        adGroup.bidding().setCpc(max_cpc);
      }
    }
  }
}

// ******************************************************************
// SET KEYWORD BIDS
// ******************************************************************
function setKeywordBids(dateRange) {
  Logger.log('\nSet Keyword Bids : ' + dateRange);

  var KeywordIterator = GetKeywordSelector(dateRange)
      .withCondition("ConvertedClicks > " + MIN_NUM_CONVERSIONS)
      .get();

  Logger.log('Total Keywords found : ' + KeywordIterator.totalNumEntities());

  while (KeywordIterator.hasNext()) {
    var keyword = KeywordIterator.next();
    var stats = keyword.getStatsFor(dateRange);
    var conv_rate = stats.getClickConversionRate();
    var max_cpc = roundDown(conv_rate * CONVERSION_VALUE);

    // Temp variables
    var keywordBidding = keyword.bidding();
    var keywordCpc = keywordBidding.getCpc();

    // Calculate Range for wich we want to keep adgroup bids
    var AdGroupCpc = keyword.getAdGroup().bidding().getCpc();
    var AdGroupCpcMin = AdGroupCpc * 0.9;
    var AdGroupCpcMax = AdGroupCpc * 1.1;

    if( max_cpc > keywordCpc && stats.getAveragePosition() < 1.2 ) {
      Logger.log('Keyword: ' + keyword.getText() + ' Position too high. Bid not updated.');
    } else if( max_cpc > AdGroupCpcMin && max_cpc < AdGroupCpcMax ) {
      keywordBidding.clearCpc();
      Logger.log('Keyword: ' + keyword.getText() + ' Keyword text reset to AdGroup bid');
    } else {
      Logger.log('Keyword: ' + keyword.getText() + ' ConvRate:' + conv_rate + ' MaxCPC:' + max_cpc);
      keywordBidding.setCpc(max_cpc);
    }
  }
}

// ******************************************************************
// SET KEYWORD BIDS, HIGH COST
// ******************************************************************
function setKeywordBids_highCost(dateRange) {
  Logger.log('\nSet Keyword Bids, High Cost : ' + dateRange);
 var highCostThreshold = (CONVERSION_VALUE * .80);

  var KeywordIterator = GetKeywordSelector(dateRange)
     .withCondition("ConvertedClicks <= " + MIN_NUM_CONVERSIONS)
     .get();

  Logger.log('Total Keywords found : ' + KeywordIterator.totalNumEntities());

  while (KeywordIterator.hasNext()) {
    var keyword = KeywordIterator.next();
    var stats = keyword.getStatsFor(dateRange);
    var conversions = stats.getConvertedClicks();
    var clicks = stats.getClicks();
    var cost = stats.getCost();
    var conv_rate = stats.getClickConversionRate();

    if( conversions == 0 && clicks > 0) {
      conversions = 1;
      conv_rate = conversions / clicks;
    }

    var cpa = cost / conversions;

    if (cpa > highCostThreshold) {
      var max_cpc = roundDown(conv_rate * CONVERSION_VALUE);

      if( max_cpc < keyword.bidding().getCpc()) {
        keyword.bidding().setCpc(max_cpc);
      }
    }
  }
}

//**************************************************
// Return Keyword Selector
//**************************************************
function GetKeywordSelector(dateRange) {
 var keywordSelector = AdWordsApp.keywords()
      .forDateRange(dateRange)
      .withCondition("Status = ENABLED")
      .withCondition("CampaignStatus = ENABLED")
      .withCondition("AdGroupStatus = ENABLED");

  if( TAG_IGNORE.length > 0 ) {
    keywordSelector = keywordSelectore.withCondition("LabelNames CONTAINS_NONE ['" + TAG_IGNORE + "']");
  }

  if( CAMPAIGN_INCLUDE.length > 0 ) {
    keywordSelector = keywordSelector.withCondition("CampaignName CONTAINS_IGNORE_CASE '" + CAMPAIGN_INCLUDE + "'");
  }

  return keywordSelector;
}

//***************************************************
// Return AdGroup Selector
//***************************************************
function GetAdGroupSelector(dateRange) {
  var adGroupSelector = AdWordsApp.adGroups()
      .forDateRange(dateRange)
      .withCondition("Status = ENABLED")
      .withCondition("CampaignStatus = ENABLED")
      .withCondition("AdGroupStatus = ENABLED");

  if( TAG_IGNORE.length > 0 ) {
    adGroupSelector = adGroupSelector.withCondition("LabelNames CONTAINS_NONE ['" + TAG_IGNORE + "']");
  }

  if( CAMPAIGN_INCLUDE.length > 0 ) {
    adGroupSelector = adGroupSelector.withCondition("CampaignName CONTAINS_IGNORE_CASE '" + CAMPAIGN_INCLUDE + "'");
  }

  return adGroupSelector;
}

// Round down bids to the closest quarter dollar.
function roundDown(value) {
  var suffix = value % 1;
  var prefix = value - suffix;

  var newSuffix = suffix;

  if( suffix < 0.25 ) {
    if( prefix > 0 ) newSuffix = 0.0;
  } else if( suffix < 0.50 ) {
    newSuffix = 0.25 ;
  } else if( suffix < 0.75) {
    newSuffix = 0.50;
  } else {
    newSuffix = 0.75;
  }

  var newBid = prefix + newSuffix;

  //Logger.log('bid: ' + value + '; new bid: ' + newBid);

  return newBid;
}
<|endoftext|>