<commit_before>var labelType,
useGradients,
nativeTextSupport,
animate;

 (function() {
    var ua = navigator.userAgent,
    iStuff = ua.match(/iPhone/i) || ua.match(/iPad/i),
    typeOfCanvas = typeof HTMLCanvasElement,
    nativeCanvasSupport = (typeOfCanvas == 'object' || typeOfCanvas == 'function'),
    textSupport = nativeCanvasSupport
    && (typeof document.createElement('canvas').getContext('2d').fillText == 'function');
    //I'm setting this based on the fact that ExCanvas provides text support for IE
    //and that as of today iPhone/iPad current text support is lame
    labelType = (!nativeCanvasSupport || (textSupport && !iStuff)) ? 'Native': 'HTML';
    nativeTextSupport = labelType == 'Native';
    useGradients = nativeCanvasSupport;
    animate = !(iStuff || !nativeCanvasSupport);
})();

function init(data) {
    //treemap
    initTreeMap(data);

    //jstree
    initJsTree(data);
}

function initTreeMap(data) {
    //init TreeMap
    var tm = new $jit.TM.Squarified({
        //where to inject the visualization
        injectInto: 'treeMap',
        //parent box title heights
        titleHeight: 15,
        //enable animations
        animate: animate,
        //box offsets
        offset: 0,
        //constrained: true,
        //levelsToShow: 1,
        //Attach left and right click events
        Events: {
            enable: true,
            onClick: function(node) {
                if (node) tm.enter(node);
            },
            onRightClick: function() {
                tm.out();
            }
        },
        duration: 1000,
        //Enable tips
        Tips: {
            enable: true,
            //add positioning offsets
            offsetX: 20,
            offsetY: 20,
            //implement the onShow method to
            //add content to the tooltip when a node
            //is hovered
            onShow: function(tip, node, isLeaf, domElement) {
				var title = "";
				if (node.data.count != 0)
	            	title = node.name + " (" + node.data.count + ")";
	            else
	            	title = node.name;
                var html = "
" + title
                + "

";
                var data = node.data;
                tip.innerHTML = html;
            }
        },

        //Add the name of the node in the correponding label
        //This method is called once, on label creation.
        onCreateLabel: function(domElement, node) {
            if (node.data.count != 0)
            	domElement.innerHTML = node.name + " (" + node.data.count + ")";
            else
            	domElement.innerHTML = node.name;
            var style = domElement.style;
            style.display = '';
            style.border = '2px solid transparent';
            domElement.onmouseover = function() {
                style.border = '2px solid #9FD4FF';
            };
            domElement.onmouseout = function() {
                style.border = '2px solid transparent';
            };
        }
    });
    tm.loadJSON(data);
    tm.refresh();
    //end
}

function initJsTree(data) {
	$("#jstree").bind("select_node.jstree", function(node, tree){
		var peptides = $(tree.rslt.obj).data();
		var margin = tree.rslt.obj.context.offsetTop - $("#jstree").offset().top;
		var innertext = tree.rslt.obj.context.innerText.split("(")[0];
		var list = $("#jstree_data").html("
"+innertext+"

").find("ul");
		$("#jstree_data").animate({marginTop: margin}, 1000);
		for(var peptide in peptides){
			list.append("
"+peptides[peptide]+"
")
		}
	});

    $("#jstree").jstree({
        core: {
            /* core options go here */
        },
        plugins: ["themes", "json_data", "ui"],
        json_data: {
            "data": data
        },
        themes: {
            "icons": false
        },
		ui: {
			"select_limit": 1
		}
    });

}<commit_msg>fixed innerText bug in firefox<commit_after>var labelType,
useGradients,
nativeTextSupport,
animate;

 (function() {
    var ua = navigator.userAgent,
    iStuff = ua.match(/iPhone/i) || ua.match(/iPad/i),
    typeOfCanvas = typeof HTMLCanvasElement,
    nativeCanvasSupport = (typeOfCanvas == 'object' || typeOfCanvas == 'function'),
    textSupport = nativeCanvasSupport
    && (typeof document.createElement('canvas').getContext('2d').fillText == 'function');
    //I'm setting this based on the fact that ExCanvas provides text support for IE
    //and that as of today iPhone/iPad current text support is lame
    labelType = (!nativeCanvasSupport || (textSupport && !iStuff)) ? 'Native': 'HTML';
    nativeTextSupport = labelType == 'Native';
    useGradients = nativeCanvasSupport;
    animate = !(iStuff || !nativeCanvasSupport);
})();

function init(data) {
    //treemap
    initTreeMap(data);

    //jstree
    initJsTree(data);
}

function initTreeMap(data) {
    //init TreeMap
    var tm = new $jit.TM.Squarified({
        //where to inject the visualization
        injectInto: 'treeMap',
        //parent box title heights
        titleHeight: 15,
        //enable animations
        animate: animate,
        //box offsets
        offset: 0,
        //constrained: true,
        //levelsToShow: 1,
        //Attach left and right click events
        Events: {
            enable: true,
            onClick: function(node) {
                if (node) tm.enter(node);
            },
            onRightClick: function() {
                tm.out();
            }
        },
        duration: 1000,
        //Enable tips
        Tips: {
            enable: true,
            //add positioning offsets
            offsetX: 20,
            offsetY: 20,
            //implement the onShow method to
            //add content to the tooltip when a node
            //is hovered
            onShow: function(tip, node, isLeaf, domElement) {
				var title = "";
				if (node.data.count != 0)
	            	title = node.name + " (" + node.data.count + ")";
	            else
	            	title = node.name;
                var html = "
" + title
                + "

";
                var data = node.data;
                tip.innerHTML = html;
            }
        },

        //Add the name of the node in the correponding label
        //This method is called once, on label creation.
        onCreateLabel: function(domElement, node) {
            if (node.data.count != 0)
            	domElement.innerHTML = node.name + " (" + node.data.count + ")";
            else
            	domElement.innerHTML = node.name;
            var style = domElement.style;
            style.display = '';
            style.border = '2px solid transparent';
            domElement.onmouseover = function() {
                style.border = '2px solid #9FD4FF';
            };
            domElement.onmouseout = function() {
                style.border = '2px solid transparent';
            };
        }
    });
    tm.loadJSON(data);
    tm.refresh();
    //end
}

function initJsTree(data) {
	$("#jstree").bind("select_node.jstree", function(node, tree){
		var peptides = $(tree.rslt.obj).data();
		var margin = tree.rslt.obj.context.offsetTop - $("#jstree").offset().top;
		var innertext = $(tree.rslt.obj).find("a").text().split("(")[0];
		var list = $("#jstree_data").html("
"+innertext+"

").find("ul");
		$("#jstree_data").animate({marginTop: margin}, 1000);
		for(var peptide in peptides){
			list.append("
"+peptides[peptide]+"
")
		}
	});

    $("#jstree").jstree({
        core: {
            /* core options go here */
        },
        plugins: ["themes", "json_data", "ui"],
        json_data: {
            "data": data
        },
        themes: {
            "icons": false
        },
		ui: {
			"select_limit": 1
		}
    });

}<|endoftext|>


<commit_before>// Copyright 2015-2020, University of Colorado Boulder

/**
 * Generate a rasterized screenshot for a simulation using scenery's built-in machinery.
 * Used in phet-io as well as PhetMenu (optionally)
 *
 * @author Sam Reid (PhET Interactive Simulations)
 */

import inherit from '../../phet-core/js/inherit.js';
import CanvasContextWrapper from '../../scenery/js/util/CanvasContextWrapper.js';
import joist from './joist.js';

/**
 *
 * @constructor
 */
function ScreenshotGenerator() {
}

joist.register( 'ScreenshotGenerator', ScreenshotGenerator );

inherit( Object, ScreenshotGenerator, {}, {

  /**
   * Given a sim, generate a screenshot as a data url
   * @param {Sim} sim
   * @param {string} [mimeType] - String for the image mimeType, defaults to 'image/png'
   * @returns {string} dataURL
   * @public
   */
  generateScreenshot: function( sim, mimeType ) {

    // Default to PNG
    mimeType = mimeType || 'image/png';

    // set up our Canvas with the correct background color
    const canvas = document.createElement( 'canvas' );
    canvas.width = sim.display.width;
    canvas.height = sim.display.height;
    const context = canvas.getContext( '2d' );
    context.fillStyle = sim.display.domElement.style.backgroundColor;
    context.fillRect( 0, 0, canvas.width, canvas.height );
    const wrapper = new CanvasContextWrapper( canvas, context );

    sim.rootNode.renderToCanvasSubtree( wrapper );

    // get the data URL in PNG format
    const dataURL = canvas.toDataURL( mimeType );

    return dataURL;
  }
} );

export default ScreenshotGenerator;<commit_msg>convert ScreenshotGenerator to ES6, https://github.com/phetsims/tasks/issues/1044<commit_after>// Copyright 2015-2020, University of Colorado Boulder

/**
 * Generate a rasterized screenshot for a simulation using scenery's built-in machinery.
 * Used in phet-io as well as PhetMenu (optionally)
 *
 * @author Sam Reid (PhET Interactive Simulations)
 */

import CanvasContextWrapper from '../../scenery/js/util/CanvasContextWrapper.js';
import joist from './joist.js';

class ScreenshotGenerator {

  /**
   * Given a sim, generate a screenshot as a data url
   * @param {Sim} sim
   * @param {string} [mimeType] - String for the image mimeType, defaults to 'image/png'
   * @returns {string} dataURL
   * @public
   */
  static generateScreenshot( sim, mimeType ) {

    // Default to PNG
    mimeType = mimeType || 'image/png';

    // set up our Canvas with the correct background color
    const canvas = document.createElement( 'canvas' );
    canvas.width = sim.display.width;
    canvas.height = sim.display.height;
    const context = canvas.getContext( '2d' );
    context.fillStyle = sim.display.domElement.style.backgroundColor;
    context.fillRect( 0, 0, canvas.width, canvas.height );
    const wrapper = new CanvasContextWrapper( canvas, context );

    sim.rootNode.renderToCanvasSubtree( wrapper );

    // get the data URL in PNG format
    return canvas.toDataURL( mimeType );
  }
}

joist.register( 'ScreenshotGenerator', ScreenshotGenerator );
export default ScreenshotGenerator;<|endoftext|>


<commit_before>import * as Utils from "./utils";
import MakiObject from "./runtime/MakiObject";
import GuiObject from "./runtime/GuiObject";
import JsWinampAbstractionLayer from "./runtime/JsWinampAbstractionLayer";
import Layout from "./runtime/Layout";
import Layer from "./runtime/Layer";
import Container from "./runtime/Container";
import JsScript from "./runtime/JsScript";
import JsElements from "./runtime/JsElements";
import JsGammaSet from "./runtime/JsGammaSet";
import JsGroupDef from "./runtime/JsGroupDef";
import Group from "./runtime/Group";
import Button from "./runtime/Button";
import ToggleButton from "./runtime/ToggleButton";
import Text from "./runtime/Text";
import Status from "./runtime/Status";
import Slider from "./runtime/Slider";
import Vis from "./runtime/Vis";
import EqVis from "./runtime/EqVis";
import AnimatedLayer from "./runtime/AnimatedLayer";
import WindowHolder from "./runtime/WindowHolder";

async function prepareMakiImage(node, zip, file) {
  let { h, w } = node.attributes;
  // TODO: Escape file for regex
  const img = Utils.getCaseInsensitveFile(zip, file);
  if (img === undefined) {
    return {};
  }
  const imgBlob = await img.async("blob");
  const imgUrl = await Utils.getUrlFromBlob(imgBlob);
  if (w === undefined || h === undefined) {
    const { width, height } = await Utils.getSizeFromUrl(imgUrl);
    w = width;
    h = height;
  }

  return {
    h,
    w,
    imgUrl,
  };
}

function imageAttributesFromNode(node) {
  if (!node.name) return [];
  switch (node.name.toLowerCase()) {
    case "layer":
    case "animatedlayer": {
      return ["image"];
    }
    case "layout": {
      return ["background"];
    }
    case "button":
    case "togglebutton": {
      return ["image", "downImage"];
    }
    default: {
      return [];
    }
  }
}

const noop = (node, parent) => new GuiObject(node, parent, undefined);

const parsers = {
  groupdef: (node, parent) => new JsGroupDef(node, parent, undefined),
  skininfo: noop,
  guiobject: noop,
  version: noop,
  name: noop,
  comment: noop,
  syscmds: noop,
  author: noop,
  email: noop,
  homepage: noop,
  screenshot: noop,
  container: (node, parent) => new Container(node, parent, undefined),
  scripts: noop,
  gammaset: (node, parent) => new JsGammaSet(node, parent, undefined),
  color: noop,
  layer: (node, parent) => new Layer(node, parent, undefined),
  layoutstatus: noop,
  hideobject: noop,
  button: (node, parent) => new Button(node, parent, undefined),
  group: (node, parent) => new Group(node, parent, undefined),
  layout: (node, parent) => new Layout(node, parent, undefined),
  sendparams: noop,
  elements: (node, parent) => new JsElements(node, parent, undefined),
  bitmap: noop,
  eqvis: (node, parent) => new EqVis(node, parent, undefined),
  slider: (node, parent) => new Slider(node, parent, undefined),
  gammagroup: noop,
  truetypefont: async (node, parent, zip) => {
    const { file } = node.attributes;
    const font = Utils.getCaseInsensitveFile(zip, file);
    const fontBlob = await font.async("blob");
    const fontUrl = await Utils.getUrlFromBlob(fontBlob);
    const fontFamily = `font-${Utils.getId()}-${file.replace(/\./, "_")}`;
    try {
      await Utils.loadFont(fontUrl, fontFamily);
    } catch {
      console.warn(`Failed to load font ${fontFamily}`);
      return new MakiObject(node, parent);
    }
    return new MakiObject(node, parent, { fontFamily });
  },
  component: (node, parent) => new WindowHolder(node, parent, undefined),
  text: (node, parent) => new Text(node, parent, undefined),
  togglebutton: (node, parent) => new ToggleButton(node, parent, undefined),
  status: (node, parent) => new Status(node, parent, undefined),
  bitmapfont: noop,
  vis: (node, parent) => new Vis(node, parent, undefined),
  "wasabi:titlebar": noop,
  "colorthemes:list": noop,
  "wasabi:standardframe:status": noop,
  "wasabi:standardframe:nostatus": noop,
  "wasabi:button": noop,
  accelerators: noop,
  accelerator: noop,
  cursor: noop,
  elementalias: noop,
  grid: noop,
  rect: noop,
  animatedlayer: (node, parent) => new AnimatedLayer(node, parent, undefined),
  nstatesbutton: noop,
  songticker: noop,
  menu: noop,
  albumart: noop,
  playlistplus: noop,
  script: (node, parent) => new JsScript(node, parent, undefined),
};

async function parseChildren(node, children, zip) {
  if (node.type === "comment") {
    return;
  }
  if (node.name == null) {
    console.error(node);
    throw new Error("Unknown node");
  }

  const resolvedChildren = await Promise.all(
    children.map(async child => {
      if (child.type === "comment") {
        return;
      }
      if (child.type === "text") {
        // TODO: Handle text
        return new MakiObject({ ...child }, node, undefined);
      }
      if (child.name == null) {
        console.error(child);
        throw new Error("Unknown node");
      }
      const childName = child.name.toLowerCase();
      if (childName == null) {
        console.error(node);
        throw new Error("Unknown node");
      }

      let childParser = parsers[childName];
      if (childParser == null) {
        console.warn(`Missing parser in initialize for ${childName}`);
        childParser = noop;
      }
      const parsedChild = await childParser(child, node, zip);
      child.maki = parsedChild;
      if (child.children != null && child.children.length > 0) {
        await parseChildren(parsedChild, child.children, zip);
      }
      return parsedChild;
    })
  );
  // remove comments other trimmed nodes
  const filteredChildren = resolvedChildren.filter(item => item !== undefined);

  node.js_addChildren(filteredChildren);
}

async function nodeImageLookup(node, root, zip) {
  const imageAttributes = imageAttributesFromNode(node);
  if (!imageAttributes || imageAttributes.length === 0) {
    return;
  }
  if (!node.attributes.js_assets) {
    node.attributes.js_assets = {};
  }
  await Promise.all(
    imageAttributes.map(async attribute => {
      const image = node.attributes[attribute];
      if (!image || !Utils.isString(image)) {
        return;
      }
      let img;
      if (image.endsWith(".png")) {
        img = await prepareMakiImage(node, zip, image);
      } else {
        const elementNode = Utils.findXmlElementById(node, image, root);
        if (elementNode) {
          img = await prepareMakiImage(
            elementNode,
            zip,
            elementNode.attributes.file
          );

          const { x, y } = elementNode.attributes;
          img.x = x !== undefined ? x : 0;
          img.y = y !== undefined ? y : 0;
        } else {
          console.warn("Unable to find image:", image);
        }
      }
      node.attributes.js_assets[attribute.toLowerCase()] = img;
    })
  );
}

async function applyImageLookups(root, zip) {
  await Utils.asyncTreeFlatMap(root, async node => {
    await nodeImageLookup(node, root, zip);
    return node;
  });
}

async function applyGroupDefs(root) {
  await Utils.asyncTreeFlatMap(root, async node => {
    switch (node.name) {
      case "group": {
        if (!node.children || node.children.length === 0) {
          const groupdef = node.js_groupdefLookup(node.attributes.id);
          if (!groupdef) {
            console.warn(
              "Unable to find groupdef. Rendering null",
              node.attributes.id
            );
            return {};
          }
          node.children = groupdef.children;
          // Do we need to copy the items instead of just changing the parent?
          node.children.forEach(item => {
            item.parent = node;
          });
          node.attributes = {
            ...node.attributes,
            ...groupdef.attributes,
          };
        }
        return {};
      }
      default: {
        return node;
      }
    }
  });
}

async function initialize(zip, skinXml) {
  const xmlRoot = skinXml.children[0];
  await applyImageLookups(xmlRoot, zip);
  const root = new JsWinampAbstractionLayer(xmlRoot, null, undefined);
  await parseChildren(root, xmlRoot.children, zip);
  await applyGroupDefs(root);
  return root;
}

export default initialize;
<commit_msg>Handle not finding font file (#942)<commit_after>import * as Utils from "./utils";
import MakiObject from "./runtime/MakiObject";
import GuiObject from "./runtime/GuiObject";
import JsWinampAbstractionLayer from "./runtime/JsWinampAbstractionLayer";
import Layout from "./runtime/Layout";
import Layer from "./runtime/Layer";
import Container from "./runtime/Container";
import JsScript from "./runtime/JsScript";
import JsElements from "./runtime/JsElements";
import JsGammaSet from "./runtime/JsGammaSet";
import JsGroupDef from "./runtime/JsGroupDef";
import Group from "./runtime/Group";
import Button from "./runtime/Button";
import ToggleButton from "./runtime/ToggleButton";
import Text from "./runtime/Text";
import Status from "./runtime/Status";
import Slider from "./runtime/Slider";
import Vis from "./runtime/Vis";
import EqVis from "./runtime/EqVis";
import AnimatedLayer from "./runtime/AnimatedLayer";
import WindowHolder from "./runtime/WindowHolder";

async function prepareMakiImage(node, zip, file) {
  let { h, w } = node.attributes;
  // TODO: Escape file for regex
  const img = Utils.getCaseInsensitveFile(zip, file);
  if (img === undefined) {
    return {};
  }
  const imgBlob = await img.async("blob");
  const imgUrl = await Utils.getUrlFromBlob(imgBlob);
  if (w === undefined || h === undefined) {
    const { width, height } = await Utils.getSizeFromUrl(imgUrl);
    w = width;
    h = height;
  }

  return {
    h,
    w,
    imgUrl,
  };
}

function imageAttributesFromNode(node) {
  if (!node.name) return [];
  switch (node.name.toLowerCase()) {
    case "layer":
    case "animatedlayer": {
      return ["image"];
    }
    case "layout": {
      return ["background"];
    }
    case "button":
    case "togglebutton": {
      return ["image", "downImage"];
    }
    default: {
      return [];
    }
  }
}

const noop = (node, parent) => new GuiObject(node, parent, undefined);

const parsers = {
  groupdef: (node, parent) => new JsGroupDef(node, parent, undefined),
  skininfo: noop,
  guiobject: noop,
  version: noop,
  name: noop,
  comment: noop,
  syscmds: noop,
  author: noop,
  email: noop,
  homepage: noop,
  screenshot: noop,
  container: (node, parent) => new Container(node, parent, undefined),
  scripts: noop,
  gammaset: (node, parent) => new JsGammaSet(node, parent, undefined),
  color: noop,
  layer: (node, parent) => new Layer(node, parent, undefined),
  layoutstatus: noop,
  hideobject: noop,
  button: (node, parent) => new Button(node, parent, undefined),
  group: (node, parent) => new Group(node, parent, undefined),
  layout: (node, parent) => new Layout(node, parent, undefined),
  sendparams: noop,
  elements: (node, parent) => new JsElements(node, parent, undefined),
  bitmap: noop,
  eqvis: (node, parent) => new EqVis(node, parent, undefined),
  slider: (node, parent) => new Slider(node, parent, undefined),
  gammagroup: noop,
  truetypefont: async (node, parent, zip) => {
    const { file } = node.attributes;
    const font = Utils.getCaseInsensitveFile(zip, file);
    if (!font) {
      console.warn(`Unable to find font file ${file}`);
      return new MakiObject(node, parent);
    }
    const fontBlob = await font.async("blob");
    const fontUrl = await Utils.getUrlFromBlob(fontBlob);
    const fontFamily = `font-${Utils.getId()}-${file.replace(/\./, "_")}`;
    try {
      await Utils.loadFont(fontUrl, fontFamily);
    } catch {
      console.warn(`Failed to load font ${fontFamily}`);
      return new MakiObject(node, parent);
    }
    return new MakiObject(node, parent, { fontFamily });
  },
  component: (node, parent) => new WindowHolder(node, parent, undefined),
  text: (node, parent) => new Text(node, parent, undefined),
  togglebutton: (node, parent) => new ToggleButton(node, parent, undefined),
  status: (node, parent) => new Status(node, parent, undefined),
  bitmapfont: noop,
  vis: (node, parent) => new Vis(node, parent, undefined),
  "wasabi:titlebar": noop,
  "colorthemes:list": noop,
  "wasabi:standardframe:status": noop,
  "wasabi:standardframe:nostatus": noop,
  "wasabi:button": noop,
  accelerators: noop,
  accelerator: noop,
  cursor: noop,
  elementalias: noop,
  grid: noop,
  rect: noop,
  animatedlayer: (node, parent) => new AnimatedLayer(node, parent, undefined),
  nstatesbutton: noop,
  songticker: noop,
  menu: noop,
  albumart: noop,
  playlistplus: noop,
  script: (node, parent) => new JsScript(node, parent, undefined),
};

async function parseChildren(node, children, zip) {
  if (node.type === "comment") {
    return;
  }
  if (node.name == null) {
    console.error(node);
    throw new Error("Unknown node");
  }

  const resolvedChildren = await Promise.all(
    children.map(async child => {
      if (child.type === "comment") {
        return;
      }
      if (child.type === "text") {
        // TODO: Handle text
        return new MakiObject({ ...child }, node, undefined);
      }
      if (child.name == null) {
        console.error(child);
        throw new Error("Unknown node");
      }
      const childName = child.name.toLowerCase();
      if (childName == null) {
        console.error(node);
        throw new Error("Unknown node");
      }

      let childParser = parsers[childName];
      if (childParser == null) {
        console.warn(`Missing parser in initialize for ${childName}`);
        childParser = noop;
      }
      const parsedChild = await childParser(child, node, zip);
      child.maki = parsedChild;
      if (child.children != null && child.children.length > 0) {
        await parseChildren(parsedChild, child.children, zip);
      }
      return parsedChild;
    })
  );
  // remove comments other trimmed nodes
  const filteredChildren = resolvedChildren.filter(item => item !== undefined);

  node.js_addChildren(filteredChildren);
}

async function nodeImageLookup(node, root, zip) {
  const imageAttributes = imageAttributesFromNode(node);
  if (!imageAttributes || imageAttributes.length === 0) {
    return;
  }
  if (!node.attributes.js_assets) {
    node.attributes.js_assets = {};
  }
  await Promise.all(
    imageAttributes.map(async attribute => {
      const image = node.attributes[attribute];
      if (!image || !Utils.isString(image)) {
        return;
      }
      let img;
      if (image.endsWith(".png")) {
        img = await prepareMakiImage(node, zip, image);
      } else {
        const elementNode = Utils.findXmlElementById(node, image, root);
        if (elementNode) {
          img = await prepareMakiImage(
            elementNode,
            zip,
            elementNode.attributes.file
          );

          const { x, y } = elementNode.attributes;
          img.x = x !== undefined ? x : 0;
          img.y = y !== undefined ? y : 0;
        } else {
          console.warn("Unable to find image:", image);
        }
      }
      node.attributes.js_assets[attribute.toLowerCase()] = img;
    })
  );
}

async function applyImageLookups(root, zip) {
  await Utils.asyncTreeFlatMap(root, async node => {
    await nodeImageLookup(node, root, zip);
    return node;
  });
}

async function applyGroupDefs(root) {
  await Utils.asyncTreeFlatMap(root, async node => {
    switch (node.name) {
      case "group": {
        if (!node.children || node.children.length === 0) {
          const groupdef = node.js_groupdefLookup(node.attributes.id);
          if (!groupdef) {
            console.warn(
              "Unable to find groupdef. Rendering null",
              node.attributes.id
            );
            return {};
          }
          node.children = groupdef.children;
          // Do we need to copy the items instead of just changing the parent?
          node.children.forEach(item => {
            item.parent = node;
          });
          node.attributes = {
            ...node.attributes,
            ...groupdef.attributes,
          };
        }
        return {};
      }
      default: {
        return node;
      }
    }
  });
}

async function initialize(zip, skinXml) {
  const xmlRoot = skinXml.children[0];
  await applyImageLookups(xmlRoot, zip);
  const root = new JsWinampAbstractionLayer(xmlRoot, null, undefined);
  await parseChildren(root, xmlRoot.children, zip);
  await applyGroupDefs(root);
  return root;
}

export default initialize;
<|endoftext|>


<commit_before>function encodeDOM(elem) {
  var breadcrumbs = [];

  while (elem != document) {
    if (elem.id) {
      breadcrumbs.unshift('#' + elem.id);
      break;
    }

    var idx = Array.prototype.indexOf.call(elem.parentNode.childNodes, elem);
    breadcrumbs.unshift(idx);

    elem = elem.parentNode;
  }

  return breadcrumbs.join(' ');
}

function decodeDOM(index_str) {
  var elem = document;
  var indexes = index_str.split(' ');

  for (var i in indexes) {
    if (indexes[i].startsWith('#')) {
      elem = elem.getElementById(indexes[i].substr(1));
    } else {
      elem = elem.childNodes[indexes[i]];
    }
  }

  return elem;
}

window.addEventListener("load", function (event) {
  // check with local storage
  chrome.storage.local.get("monitoring", function (items) {
    var curr_url = window.location.host + "/" + window.location.pathname;
    var monitoring_regions = items.monitoring[curr_url];

    // if there is a record, fetch the element by index and compute hash
    if (monitoring_regions) {
      for (var i in monitoring_regions) {

      }
    }
  });

  // TODO: if there is a change on hash,
  //       send the update to the backend (through event page?)
});

chrome.runtime.onMessage.addListener(
  function(request, sender, sendResponse) {
    // TODO:
    //   selecting new region
  });

// TODO: listen on selecting new region event
// TODO: send adding new region event to event page
<commit_msg>finished detection part of content script<commit_after>function buildURL(window) {
  return window.location.host + "/" + window.location.pathname;
}

function encodeDOM(elem) {
  var breadcrumbs = [];

  while (elem != document) {
    if (elem.id) {
      breadcrumbs.unshift('#' + elem.id);
      break;
    }

    var idx = Array.prototype.indexOf.call(elem.parentNode.childNodes, elem);
    breadcrumbs.unshift(idx);

    elem = elem.parentNode;
  }

  return breadcrumbs.join(' ');
}

function decodeDOM(breadcrumb_str) {
  var elem = document;
  var breadcrumbs = breadcrumb_str.split(' ');

  for (var i in breadcrumbs) {
    if (breadcrumbs[i].startsWith('#')) {
      elem = elem.getElementById(breadcrumbs[i].substr(1));
    } else {
      elem = elem.childNodes[breadcrumbs[i]];
    }
  }

  return elem;
}

// Source: http://werxltd.com/wp/2010/05/13/javascript-implementation-of-javas-string-hashcode-method/
function hashCode(text) {
  var hash = 0;

  if (text.length == 0) {
    return hash;
  }

  for (i = 0; i < text.length; i++) {
    c = text.charCodeAt(i);
    hash = ((hash << 5) - hash) + c;
    hash = hash & hash;
  }

  return hash;
}

window.addEventListener("load", function (event) {
  // check with local storage
  chrome.storage.local.get("monitoring", function (items) {
    var curr_url = buildURL(window);
    var regions = items.monitoring[curr_url];

    if (regions) {
      console.log("It's a monitored website. Checking changes...");
      var changed_regions = [];

      // if there is a record, fetch the element by index and compute hash
      for (var i in regions) {
        var dom = decodeDOM(regions[i].index);
        var hash = hashCode(dom.innerHTML);

        if (hash != regions[i].hashVal) {
          changed_regions.push({index: regions[i].index, hashVal: hash});
        }
      }

      // if there is a change on hash,
      // send the updated regions to the event page
      if (changed_regions) {
        chrome.runtime.sendMessage({
          type: "changed_regions",
          url: curr_url,
          regions: changed_regions
        });
      }
    }
  });
});

chrome.runtime.onMessage.addListener(
  function(request, sender, sendResponse) {
    // TODO:
    //   selecting new region
  });

// TODO: send adding new region event to event page
<|endoftext|>


<commit_before>app.
/**
 * Control para manipular los datos de los cajeros
 * @param $scope            - Scope del control
 * @param $modal            - Directiva para crear un modal
 * @param $filter           - para formato de fechas
 * @param $rootScope        - Root Scope para comunicar con otros controles
 * @param CajeroFactory     - Factory de para obtener los cajeros
 */
controller('MedicamentoCtrl', function($scope, $modal, $filter, $rootScope, BuscarFactory) {
    $scope.datosTabla = BuscarFactory.getMedicamentos();
    $scope.datosProductosTabla = [];
    $scope.Prueba = "0";

    // Arreglo con los datos de las columnas que espera datatables
    $scope.aoColsProductos =[
        {
            "mData"     : "posicion",
            "bVisible"  : false
        },
        {
            "mData"     : "nombreComercial"
        },
        {
            "mData": "cantidad"
        },
        {
            "mData": "precio"
        },
        {
            "mData": "existencias"
        },
        {
            "mData": function(data, typeCall, dataCall){
                return '' +
                    ' ';
            },
            "sClass"    : "table-icon-button",
            "bSortable" : false
        },
        {
            "mData"     : function(data, typeCall, dataCall){
                return '';
            },
            "sClass"    : "table-icon-button",
            "bSortable" : false
        }
    ];

    $scope.aoCols =[
        {
            "mData"     : "nombreComercial",
        },
        {
            "mData"     : "precio",
        },
        {
            "mData"     : "existencias",
        },
        {
            "mData"     : function(data, typeCall, dataCall){
                return '';
            },
            "sClass"    : "table-icon-button",
            "bSortable" : false
        }
    ];

    $scope.events = [
        {
            name    : 'agregar',
            event   : function(posicion){
                var rowToAdd = $scope.datosTabla[posicion];
                var size = $scope.datosProductosTabla.length;
                var exists = false;
                for(var i = 0;i<size;i++){
                    if(posicion == $scope.datosProductosTabla[i]['posicion']){
                        exists = true;
                    }
                }
                if(!exists){
                    var element = {
                        nombreComercial: rowToAdd['nombreComercial'],
                        cantidad: 1,
                        precio: rowToAdd['precio'],
                        existencias: rowToAdd['existencias'],
                        posicion: posicion
                    }
                    var currentTotal = 0;
                    currentTotal += element.cantidad * element.precio.substr(1  );
                    for(var i = 0;i<$scope.datosProductosTabla.length;i++){
                        var precio = $scope.datosProductosTabla[i]['precio'];
                        precio=precio.substring(1);
                        currentTotal += $scope.datosProductosTabla[i]['cantidad'] * parseFloat(precio);
                    }
                    $scope.Prueba = currentTotal.toString();
                    $scope.datosProductosTabla.push(
                        element
                    );
                }
            }
        }
    ];

    $scope.events2 = [
        {
            name    : 'eliminar',
            event   : function(posicion){
                var size = $scope.datosProductosTabla.length;
                var index = 0;
                for(var i = 0;i<size;i++){
                    if(posicion == $scope.datosProductosTabla[i]['posicion']){
                        break;
                    }
                    index++;
                }
                var currentTotal = parseFloat($scope.Prueba);
                var precio =$scope.datosProductosTabla[i]['precio'];
                precio=precio.substring(1);
                currentTotal  = currentTotal - precio * $scope.datosProductosTabla[i]['cantidad'];
                $scope.Prueba = currentTotal.toString();
                $scope.datosProductosTabla.splice(index,1);
            }
        },
        {
            name    : 'inc',
            event   : function(posicion){
                    var size = $scope.datosProductosTabla.length;
                    var index = 0;
                    for(var i = 0;i<size;i++){
                        if(posicion == $scope.datosProductosTabla[i]['posicion']){
                            break;
                        }
                        index++;
                    }
                var rowToReplace = $scope.datosProductosTabla[index];
                //checar la cantidad maxima del producto

                if(rowToReplace["cantidad"] < rowToReplace["existencias"]){
                    var newRow = {
                        "nombreComercial": rowToReplace['nombreComercial'],
                        "posicion": rowToReplace['posicion'],
                        "cantidad": rowToReplace['cantidad']+1,
                        "precio": rowToReplace['precio'],
                        "existencias": rowToReplace['existencias']
                    }
                    var currentTotal = parseFloat($scope.Prueba);
                    var precio =newRow.precio;
                    precio=precio.substring(1);
                    currentTotal  = currentTotal + parseFloat(precio);
                    $scope.Prueba = currentTotal.toString();
                    $scope.datosProductosTabla.splice(index,1,newRow);
                 }else{
                    alert("Ha llegado a la máxima cantidad de medicamentos disponibles en almacén...");
                }
            }
        },
        {
            name    : 'dec',
            event   : function(posicion) {
                var size = $scope.datosProductosTabla.length;
                var index = 0;
                for(var i = 0;i<size;i++){
                    if(posicion == $scope.datosProductosTabla[i]['posicion']){
                        break;
                    }
                    index++;
                }
                var rowToReplace = $scope.datosProductosTabla[index];
                //checar la cantidad maxima del producto

                if(rowToReplace["cantidad"] > 1){
                    var newRow = {
                        "nombreComercial": rowToReplace['nombreComercial'],
                        "posicion": rowToReplace['posicion'],
                        "cantidad": rowToReplace['cantidad']-1,
                        "precio": rowToReplace['precio'],
                        "existencias": rowToReplace['existencias']
                    }
                    var currentTotal = parseFloat($scope.Prueba);
                    var precio =newRow.precio;
                    precio=precio.substring(1);
                    currentTotal  = currentTotal - parseFloat(precio);
                    $scope.Prueba = currentTotal.toString();
                    $scope.datosProductosTabla.splice(index,1,newRow);

                }
            }
        }
    ];

    $scope.realizarCompra = function(){
        var monto = $scope.Prueba;
        alert(monto);
        BuscarFactory.saveMedicamento({ medicamentos: $scope.datosProductosTabla, monto: parseFloat(monto)}, function(response){
            alert(response.mensaje);
            location.reload();
        });
    };
});<commit_msg>Eliminado el alert en pago de medicamento<commit_after>app.
/**
 * Control para manipular los datos de los cajeros
 * @param $scope            - Scope del control
 * @param $modal            - Directiva para crear un modal
 * @param $filter           - para formato de fechas
 * @param $rootScope        - Root Scope para comunicar con otros controles
 * @param CajeroFactory     - Factory de para obtener los cajeros
 */
controller('MedicamentoCtrl', function($scope, $modal, $filter, $rootScope, BuscarFactory) {
    $scope.datosTabla = BuscarFactory.getMedicamentos();
    $scope.datosProductosTabla = [];
    $scope.Prueba = "0";

    // Arreglo con los datos de las columnas que espera datatables
    $scope.aoColsProductos =[
        {
            "mData"     : "posicion",
            "bVisible"  : false
        },
        {
            "mData"     : "nombreComercial"
        },
        {
            "mData": "cantidad"
        },
        {
            "mData": "precio"
        },
        {
            "mData": "existencias"
        },
        {
            "mData": function(data, typeCall, dataCall){
                return '' +
                    ' ';
            },
            "sClass"    : "table-icon-button",
            "bSortable" : false
        },
        {
            "mData"     : function(data, typeCall, dataCall){
                return '';
            },
            "sClass"    : "table-icon-button",
            "bSortable" : false
        }
    ];

    $scope.aoCols =[
        {
            "mData"     : "nombreComercial",
        },
        {
            "mData"     : "precio",
        },
        {
            "mData"     : "existencias",
        },
        {
            "mData"     : function(data, typeCall, dataCall){
                return '';
            },
            "sClass"    : "table-icon-button",
            "bSortable" : false
        }
    ];

    $scope.events = [
        {
            name    : 'agregar',
            event   : function(posicion){
                var rowToAdd = $scope.datosTabla[posicion];
                var size = $scope.datosProductosTabla.length;
                var exists = false;
                for(var i = 0;i<size;i++){
                    if(posicion == $scope.datosProductosTabla[i]['posicion']){
                        exists = true;
                    }
                }
                if(!exists){
                    var element = {
                        nombreComercial: rowToAdd['nombreComercial'],
                        cantidad: 1,
                        precio: rowToAdd['precio'],
                        existencias: rowToAdd['existencias'],
                        posicion: posicion
                    }
                    var currentTotal = 0;
                    currentTotal += element.cantidad * element.precio.substr(1  );
                    for(var i = 0;i<$scope.datosProductosTabla.length;i++){
                        var precio = $scope.datosProductosTabla[i]['precio'];
                        precio=precio.substring(1);
                        currentTotal += $scope.datosProductosTabla[i]['cantidad'] * parseFloat(precio);
                    }
                    $scope.Prueba = currentTotal.toString();
                    $scope.datosProductosTabla.push(
                        element
                    );
                }
            }
        }
    ];

    $scope.events2 = [
        {
            name    : 'eliminar',
            event   : function(posicion){
                var size = $scope.datosProductosTabla.length;
                var index = 0;
                for(var i = 0;i<size;i++){
                    if(posicion == $scope.datosProductosTabla[i]['posicion']){
                        break;
                    }
                    index++;
                }
                var currentTotal = parseFloat($scope.Prueba);
                var precio =$scope.datosProductosTabla[i]['precio'];
                precio=precio.substring(1);
                currentTotal  = currentTotal - precio * $scope.datosProductosTabla[i]['cantidad'];
                $scope.Prueba = currentTotal.toString();
                $scope.datosProductosTabla.splice(index,1);
            }
        },
        {
            name    : 'inc',
            event   : function(posicion){
                    var size = $scope.datosProductosTabla.length;
                    var index = 0;
                    for(var i = 0;i<size;i++){
                        if(posicion == $scope.datosProductosTabla[i]['posicion']){
                            break;
                        }
                        index++;
                    }
                var rowToReplace = $scope.datosProductosTabla[index];
                //checar la cantidad maxima del producto

                if(rowToReplace["cantidad"] < rowToReplace["existencias"]){
                    var newRow = {
                        "nombreComercial": rowToReplace['nombreComercial'],
                        "posicion": rowToReplace['posicion'],
                        "cantidad": rowToReplace['cantidad']+1,
                        "precio": rowToReplace['precio'],
                        "existencias": rowToReplace['existencias']
                    }
                    var currentTotal = parseFloat($scope.Prueba);
                    var precio =newRow.precio;
                    precio=precio.substring(1);
                    currentTotal  = currentTotal + parseFloat(precio);
                    $scope.Prueba = currentTotal.toString();
                    $scope.datosProductosTabla.splice(index,1,newRow);
                 }else{
                    alert("Ha llegado a la máxima cantidad de medicamentos disponibles en almacén...");
                }
            }
        },
        {
            name    : 'dec',
            event   : function(posicion) {
                var size = $scope.datosProductosTabla.length;
                var index = 0;
                for(var i = 0;i<size;i++){
                    if(posicion == $scope.datosProductosTabla[i]['posicion']){
                        break;
                    }
                    index++;
                }
                var rowToReplace = $scope.datosProductosTabla[index];
                //checar la cantidad maxima del producto

                if(rowToReplace["cantidad"] > 1){
                    var newRow = {
                        "nombreComercial": rowToReplace['nombreComercial'],
                        "posicion": rowToReplace['posicion'],
                        "cantidad": rowToReplace['cantidad']-1,
                        "precio": rowToReplace['precio'],
                        "existencias": rowToReplace['existencias']
                    }
                    var currentTotal = parseFloat($scope.Prueba);
                    var precio =newRow.precio;
                    precio=precio.substring(1);
                    currentTotal  = currentTotal - parseFloat(precio);
                    $scope.Prueba = currentTotal.toString();
                    $scope.datosProductosTabla.splice(index,1,newRow);

                }
            }
        }
    ];

    $scope.realizarCompra = function(){
        var monto = $scope.Prueba;
        BuscarFactory.saveMedicamento({ medicamentos: $scope.datosProductosTabla, monto: parseFloat(monto)}, function(response){
            alert(response.mensaje);
            location.reload();
        });
    };
});<|endoftext|>


<commit_before>/* eslint-disable no-var, vars-on-top, eqeqeq, no-mixed-operators, no-return-assign, newline-per-chained-call, prefer-arrow-callback, consistent-return, one-var, one-var-declaration-per-line, prefer-template, quotes, no-unused-vars, no-else-return, max-len, class-methods-use-this */

import d3 from 'd3';

const LOADING_HTML = `

`;

export default class ActivityCalendar {
  constructor(timestamps, calendarActivitiesPath) {
    this.calendarActivitiesPath = calendarActivitiesPath;
    this.clickDay = this.clickDay.bind(this);
    this.currentSelectedDate = '';
    this.daySpace = 1;
    this.daySize = 15;
    this.daySizeWithSpace = this.daySize + (this.daySpace * 2);
    this.monthNames = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
    this.months = [];

    // Loop through the timestamps to create a group of objects
    // The group of objects will be grouped based on the day of the week they are
    this.timestampsTmp = [];
    var group = 0;

    var today = new Date();
    today.setHours(0, 0, 0, 0, 0);

    var oneYearAgo = new Date(today);
    oneYearAgo.setFullYear(today.getFullYear() - 1);

    var days = gl.utils.getDayDifference(oneYearAgo, today);

    for (var i = 0; i <= days; i += 1) {
      var date = new Date(oneYearAgo);
      date.setDate(date.getDate() + i);

      var day = date.getDay();
      var count = timestamps[date.format('yyyy-mm-dd')] || 0;

      // Create a new group array if this is the first day of the week
      // or if is first object
      if ((day === 0 && i !== 0) || i === 0) {
        this.timestampsTmp.push([]);
        group += 1;
      }

      // Push to the inner array the values that will be used to render map
      var innerArray = this.timestampsTmp[group - 1];
      innerArray.push({ count, date, day });
    }

    // Init color functions
    this.colorKey = this.initColorKey();
    this.color = this.initColor();
    // Init the svg element
    this.renderSvg(group);
    this.renderDays();
    this.renderMonths();
    this.renderDayTitles();
    this.renderKey();
    this.initTooltips();
  }

  // Add extra padding for the last month label if it is also the last column
  getExtraWidthPadding(group) {
    var extraWidthPadding = 0;
    var lastColMonth = this.timestampsTmp[group - 1][0].date.getMonth();
    var secondLastColMonth = this.timestampsTmp[group - 2][0].date.getMonth();

    if (lastColMonth != secondLastColMonth) {
      extraWidthPadding = 3;
    }

    return extraWidthPadding;
  }

  renderSvg(group) {
    var width = (group + 1) * this.daySizeWithSpace + this.getExtraWidthPadding(group);
    return this.svg = d3.select('.js-contrib-calendar').append('svg').attr('width', width).attr('height', 167).attr('class', 'contrib-calendar');
  }

  renderDays() {
    this.svg.selectAll('g').data(this.timestampsTmp).enter().append('g')
      .attr('transform', (group, i) => {
        _.each(group, (stamp, a) => {
          var lastMonth, lastMonthX, month, x;
          if (a === 0 && stamp.day === 0) {
            month = stamp.date.getMonth();
            x = (this.daySizeWithSpace * i + 1) + this.daySizeWithSpace;
            lastMonth = _.last(this.months);
            if (lastMonth != null) {
              lastMonthX = lastMonth.x;
            }
            if (lastMonth == null) {
              return this.months.push({ month, x });
            } else if (month !== lastMonth.month && x - this.daySizeWithSpace !== lastMonthX) {
              return this.months.push({ month, x });
            }
          }
        });
        return "translate(" + ((this.daySizeWithSpace * i + 1) + this.daySizeWithSpace) + ", 18)";
      })
      .selectAll('rect')
      .data(stamp => stamp)
      .enter()
      .append('rect')
      .attr('x', '0')
      .attr('y', stamp => this.daySizeWithSpace * stamp.day)
      .attr('width', this.daySize)
      .attr('height', this.daySize)
      .attr('title', (stamp) => {
        var contribText, date, dateText;
        date = new Date(stamp.date);
        contribText = 'No contributions';
        if (stamp.count > 0) {
          contribText = stamp.count + " contribution" + (stamp.count > 1 ? 's' : '');
        }
        dateText = date.format('mmm d, yyyy');
        return contribText + "
" + (gl.utils.getDayName(date)) + " " + dateText;
      })
      .attr('class', 'user-contrib-cell js-tooltip').attr('fill', (stamp) => {
        if (stamp.count !== 0) {
          return this.color(Math.min(stamp.count, 40));
        } else {
          return '#ededed';
        }
      })
      .attr('data-container', 'body')
      .on('click', this.clickDay);
  }

  renderDayTitles() {
    const days = [
      {
        text: 'M',
        y: 29 + (this.daySizeWithSpace * 1),
      }, {
        text: 'W',
        y: 29 + (this.daySizeWithSpace * 3),
      }, {
        text: 'F',
        y: 29 + (this.daySizeWithSpace * 5),
      },
    ];
    this.svg.append('g')
      .selectAll('text')
        .data(days)
        .enter()
        .append('text')
          .attr('text-anchor', 'middle')
          .attr('x', 8)
          .attr('y', day => day.y)
          .text(day => day.text)
          .attr('class', 'user-contrib-text');
  }

  renderMonths() {
    this.svg.append('g')
      .attr('direction', 'ltr')
      .selectAll('text')
        .data(this.months)
        .enter()
        .append('text')
          .attr('x', date => date.x)
          .attr('y', 10)
          .attr('class', 'user-contrib-text')
          .text(date => this.monthNames[date.month]);
  }

  renderKey() {
    const keyValues = ['no contributions', '1-9 contributions', '10-19 contributions', '20-29 contributions', '30+ contributions'];
    const keyColors = ['#ededed', this.colorKey(0), this.colorKey(1), this.colorKey(2), this.colorKey(3)];

    this.svg.append('g')
      .attr('transform', `translate(18, ${this.daySizeWithSpace * 8 + 16})`)
      .selectAll('rect')
        .data(keyColors)
        .enter()
        .append('rect')
          .attr('width', this.daySize)
          .attr('height', this.daySize)
          .attr('x', (color, i) => this.daySizeWithSpace * i)
          .attr('y', 0)
          .attr('fill', color => color)
          .attr('class', 'js-tooltip')
          .attr('title', (color, i) => keyValues[i])
          .attr('data-container', 'body');
  }

  initColor() {
    var colorRange;
    colorRange = ['#ededed', this.colorKey(0), this.colorKey(1), this.colorKey(2), this.colorKey(3)];
    return d3.scale.threshold().domain([0, 10, 20, 30]).range(colorRange);
  }

  initColorKey() {
    return d3.scale.linear().range(['#acd5f2', '#254e77']).domain([0, 3]);
  }

  clickDay(stamp) {
    if (this.currentSelectedDate !== stamp.date) {
      this.currentSelectedDate = stamp.date;
      const date = this.currentSelectedDate.getFullYear() + "-" + (this.currentSelectedDate.getMonth() + 1) + "-" + this.currentSelectedDate.getDate();
      return $.ajax({
        url: this.calendarActivitiesPath,
        data: { date },
        cache: false,
        dataType: 'html',
        beforeSend: () => $('.user-calendar-activities').html(LOADING_HTML),
        success: data => $('.user-calendar-activities').html(data),
      });
    } else {
      this.currentSelectedDate = '';
      return $('.user-calendar-activities').html('');
    }
  }

  initTooltips() {
    $('.js-contrib-calendar .js-tooltip').tooltip({ html: true });
  }
}
<commit_msg>resolve no-mixed-operators and no-return-assign eslint violations<commit_after>/* eslint-disable no-var, vars-on-top, eqeqeq, newline-per-chained-call, prefer-arrow-callback, consistent-return, one-var, one-var-declaration-per-line, prefer-template, quotes, no-unused-vars, no-else-return, max-len, class-methods-use-this */

import d3 from 'd3';

const LOADING_HTML = `

`;

export default class ActivityCalendar {
  constructor(timestamps, calendarActivitiesPath) {
    this.calendarActivitiesPath = calendarActivitiesPath;
    this.clickDay = this.clickDay.bind(this);
    this.currentSelectedDate = '';
    this.daySpace = 1;
    this.daySize = 15;
    this.daySizeWithSpace = this.daySize + (this.daySpace * 2);
    this.monthNames = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
    this.months = [];

    // Loop through the timestamps to create a group of objects
    // The group of objects will be grouped based on the day of the week they are
    this.timestampsTmp = [];
    var group = 0;

    var today = new Date();
    today.setHours(0, 0, 0, 0, 0);

    var oneYearAgo = new Date(today);
    oneYearAgo.setFullYear(today.getFullYear() - 1);

    var days = gl.utils.getDayDifference(oneYearAgo, today);

    for (var i = 0; i <= days; i += 1) {
      var date = new Date(oneYearAgo);
      date.setDate(date.getDate() + i);

      var day = date.getDay();
      var count = timestamps[date.format('yyyy-mm-dd')] || 0;

      // Create a new group array if this is the first day of the week
      // or if is first object
      if ((day === 0 && i !== 0) || i === 0) {
        this.timestampsTmp.push([]);
        group += 1;
      }

      // Push to the inner array the values that will be used to render map
      var innerArray = this.timestampsTmp[group - 1];
      innerArray.push({ count, date, day });
    }

    // Init color functions
    this.colorKey = this.initColorKey();
    this.color = this.initColor();
    // Init the svg element
    this.renderSvg(group);
    this.renderDays();
    this.renderMonths();
    this.renderDayTitles();
    this.renderKey();
    this.initTooltips();
  }

  // Add extra padding for the last month label if it is also the last column
  getExtraWidthPadding(group) {
    var extraWidthPadding = 0;
    var lastColMonth = this.timestampsTmp[group - 1][0].date.getMonth();
    var secondLastColMonth = this.timestampsTmp[group - 2][0].date.getMonth();

    if (lastColMonth != secondLastColMonth) {
      extraWidthPadding = 3;
    }

    return extraWidthPadding;
  }

  renderSvg(group) {
    var width = ((group + 1) * this.daySizeWithSpace) + this.getExtraWidthPadding(group);
    this.svg = d3.select('.js-contrib-calendar').append('svg').attr('width', width).attr('height', 167).attr('class', 'contrib-calendar');
  }

  renderDays() {
    this.svg.selectAll('g').data(this.timestampsTmp).enter().append('g')
      .attr('transform', (group, i) => {
        _.each(group, (stamp, a) => {
          var lastMonth, lastMonthX, month, x;
          if (a === 0 && stamp.day === 0) {
            month = stamp.date.getMonth();
            x = (this.daySizeWithSpace * i) + 1 + this.daySizeWithSpace;
            lastMonth = _.last(this.months);
            if (lastMonth != null) {
              lastMonthX = lastMonth.x;
            }
            if (lastMonth == null) {
              return this.months.push({ month, x });
            } else if (month !== lastMonth.month && x - this.daySizeWithSpace !== lastMonthX) {
              return this.months.push({ month, x });
            }
          }
        });
        return "translate(" + ((this.daySizeWithSpace * i) + 1 + this.daySizeWithSpace) + ", 18)";
      })
      .selectAll('rect')
      .data(stamp => stamp)
      .enter()
      .append('rect')
      .attr('x', '0')
      .attr('y', stamp => this.daySizeWithSpace * stamp.day)
      .attr('width', this.daySize)
      .attr('height', this.daySize)
      .attr('title', (stamp) => {
        var contribText, date, dateText;
        date = new Date(stamp.date);
        contribText = 'No contributions';
        if (stamp.count > 0) {
          contribText = stamp.count + " contribution" + (stamp.count > 1 ? 's' : '');
        }
        dateText = date.format('mmm d, yyyy');
        return contribText + "
" + (gl.utils.getDayName(date)) + " " + dateText;
      })
      .attr('class', 'user-contrib-cell js-tooltip').attr('fill', (stamp) => {
        if (stamp.count !== 0) {
          return this.color(Math.min(stamp.count, 40));
        } else {
          return '#ededed';
        }
      })
      .attr('data-container', 'body')
      .on('click', this.clickDay);
  }

  renderDayTitles() {
    const days = [
      {
        text: 'M',
        y: 29 + (this.daySizeWithSpace * 1),
      }, {
        text: 'W',
        y: 29 + (this.daySizeWithSpace * 3),
      }, {
        text: 'F',
        y: 29 + (this.daySizeWithSpace * 5),
      },
    ];
    this.svg.append('g')
      .selectAll('text')
        .data(days)
        .enter()
        .append('text')
          .attr('text-anchor', 'middle')
          .attr('x', 8)
          .attr('y', day => day.y)
          .text(day => day.text)
          .attr('class', 'user-contrib-text');
  }

  renderMonths() {
    this.svg.append('g')
      .attr('direction', 'ltr')
      .selectAll('text')
        .data(this.months)
        .enter()
        .append('text')
          .attr('x', date => date.x)
          .attr('y', 10)
          .attr('class', 'user-contrib-text')
          .text(date => this.monthNames[date.month]);
  }

  renderKey() {
    const keyValues = ['no contributions', '1-9 contributions', '10-19 contributions', '20-29 contributions', '30+ contributions'];
    const keyColors = ['#ededed', this.colorKey(0), this.colorKey(1), this.colorKey(2), this.colorKey(3)];

    this.svg.append('g')
      .attr('transform', `translate(18, ${(this.daySizeWithSpace * 8) + 16})`)
      .selectAll('rect')
        .data(keyColors)
        .enter()
        .append('rect')
          .attr('width', this.daySize)
          .attr('height', this.daySize)
          .attr('x', (color, i) => this.daySizeWithSpace * i)
          .attr('y', 0)
          .attr('fill', color => color)
          .attr('class', 'js-tooltip')
          .attr('title', (color, i) => keyValues[i])
          .attr('data-container', 'body');
  }

  initColor() {
    var colorRange;
    colorRange = ['#ededed', this.colorKey(0), this.colorKey(1), this.colorKey(2), this.colorKey(3)];
    return d3.scale.threshold().domain([0, 10, 20, 30]).range(colorRange);
  }

  initColorKey() {
    return d3.scale.linear().range(['#acd5f2', '#254e77']).domain([0, 3]);
  }

  clickDay(stamp) {
    if (this.currentSelectedDate !== stamp.date) {
      this.currentSelectedDate = stamp.date;
      const date = this.currentSelectedDate.getFullYear() + "-" + (this.currentSelectedDate.getMonth() + 1) + "-" + this.currentSelectedDate.getDate();
      return $.ajax({
        url: this.calendarActivitiesPath,
        data: { date },
        cache: false,
        dataType: 'html',
        beforeSend: () => $('.user-calendar-activities').html(LOADING_HTML),
        success: data => $('.user-calendar-activities').html(data),
      });
    } else {
      this.currentSelectedDate = '';
      return $('.user-calendar-activities').html('');
    }
  }

  initTooltips() {
    $('.js-contrib-calendar .js-tooltip').tooltip({ html: true });
  }
}
<|endoftext|>


<commit_before>import PropTypes from 'prop-types';
import React from 'react';
import { push } from 'react-router-redux';
import Title from 'react-title-component';
import { connect } from 'react-redux';
import { FormattedMessage, injectIntl } from 'react-intl';
import { Grid, Row, Col } from 'react-flexbox-grid/lib';
import { fetchTopicSummary, updateTopic, setTopicNeedsNewSnapshot } from '../../../actions/topicActions';
import { updateFeedback } from '../../../actions/appActions';
import messages from '../../../resources/messages';
import BackLinkingControlBar from '../BackLinkingControlBar';
import Permissioned from '../../common/Permissioned';
import { PERMISSION_TOPIC_WRITE } from '../../../lib/auth';
import TopicForm, { TOPIC_FORM_MODE_EDIT } from './TopicForm';

const localMessages = {
  editTopicTitle: { id: 'topic.edit.title', defaultMessage: 'Edit Topic Settings' },
  editTopicText: { id: 'topic.edit.text', defaultMessage: 'You can update this Topic. If you make changes to the query, media sourcs, or dates, those will be reflected in the next snapshot you run.' },
  editTopic: { id: 'topic.edit', defaultMessage: 'Edit Topic' },
  editTopicCollectionsTitle: { id: 'topic.edit.editTopicCollectionsTitle', defaultMessage: 'Edit Sources and Collections' },
  editTopicCollectionsIntro: { id: 'topic.edit.editTopicCollectionsIntro', defaultMessage: 'The following are the Sources and Collections associated with this topic.' },
  feedback: { id: 'topic.edit.save.feedback', defaultMessage: 'We saved your changes' },
  failed: { id: 'topic.edit.save.failed', defaultMessage: 'Sorry, that didn\'t work!' },
};

const EditTopicContainer = (props) => {
  const { handleSave, topicInfo, topicId } = props;
  const { formatMessage } = props.intl;
  let initialValues = {};

  if (topicInfo) {
    // load sources and collections in a backwards compatible way
    const sources = topicInfo.media ? topicInfo.media.map(t => ({ ...t })) : [];
    const collections = topicInfo.media_tags ? topicInfo.media_tags.map(t => ({ ...t, name: t.label })) : [];
    const sourcesAndCollections = sources.concat(collections);
    initialValues = {
      buttonLabel: formatMessage(messages.save),
      ...topicInfo,
      sourcesAndCollections,
    };
  }
  return (

      <BackLinkingControlBar message={messages.backToTopic} linkTo={`/topics/${topicId}/summary`} />
      <Grid>
        <Title render={formatMessage(localMessages.editTopicTitle)} />
        <Row>
          <Col lg={12}>

<FormattedMessage {...localMessages.editTopicTitle} />

<FormattedMessage {...localMessages.editTopicText} />

          </Col>
        </Row>
        <Permissioned onlyTopic={PERMISSION_TOPIC_WRITE}>
          <TopicForm
            onSaveTopic={handleSave}
            initialValues={initialValues}
            title={formatMessage(localMessages.editTopicCollectionsTitle)}
            intro={formatMessage(localMessages.editTopicCollectionsIntro)}
            mode={TOPIC_FORM_MODE_EDIT}
          />
        </Permissioned>
      </Grid>

  );
};

EditTopicContainer.propTypes = {
  // from context
  location: PropTypes.object.isRequired,
  intl: PropTypes.object.isRequired,
  params: PropTypes.object,
  // from state
  timespan: PropTypes.object,
  filters: PropTypes.object.isRequired,
  topicId: PropTypes.number,
  topicInfo: PropTypes.object,
  handleSave: PropTypes.func.isRequired,
};

const mapStateToProps = (state, ownProps) => ({
  filters: state.topics.selected.filters,
  topicId: parseInt(ownProps.params.topicId, 10),
  topicInfo: state.topics.selected.info,
  timespan: state.topics.selected.timespans.selected,
  snapshots: state.topics.selected.snapshots.list,
  user: state.user,
});

const mapDispatchToProps = (dispatch, ownProps) => ({
  handleSave: (values) => {
    const infoToSave = {
      name: values.name,
      description: values.description,
      start_date: values.start_date,
      end_date: values.end_date,
      solr_seed_query: values.solr_seed_query,
      max_iterations: values.max_iterations,
      ch_monitor_id: values.ch_monitor_id,
      is_public: values.is_public,
      twitter_topics_id: values.twitter_topics_id,
    };
    infoToSave.is_public = infoToSave.is_public ? 1 : 0;
    if ('sourcesAndCollections' in values) {
      infoToSave['sources[]'] = values.sourcesAndCollections.filter(s => s.media_id).map(s => s.media_id);
      infoToSave['collections[]'] = values.sourcesAndCollections.filter(s => s.tags_id).map(s => s.tags_id);
    } else {
      infoToSave['sources[]'] = '';
      infoToSave['collections[]'] = '';
    }
    dispatch(updateTopic(ownProps.params.topicId, infoToSave))
      .then((results) => {
        if (results.topics_id) {
          // let them know it worked
          dispatch(updateFeedback({ open: true, message: ownProps.intl.formatMessage(localMessages.feedback) }));
          // if the dates changed tell them it needs a new snapshot
          if ((infoToSave.start_date !== ownProps.topicInfo.start_date) || (infoToSave.end_date !== ownProps.topicInfo.end_date)) {
            dispatch(setTopicNeedsNewSnapshot());
          }
          // update topic info and redirect back to topic summary (no filters because we lost them on edit)
          dispatch(fetchTopicSummary(results.topics_id))
            .then(() => dispatch(push(`/topics/${results.topics_id}/summary`)));
        } else {
          dispatch(updateFeedback({ open: true, message: ownProps.intl.formatMessage(localMessages.failed) }));
        }
      }
    );
  },
});

export default
  injectIntl(
    connect(mapStateToProps, mapDispatchToProps)(
      EditTopicContainer
    )
  );
<commit_msg>correctly use save handler in TopicEdit (fix #551)<commit_after>import PropTypes from 'prop-types';
import React from 'react';
import { push } from 'react-router-redux';
import Title from 'react-title-component';
import { connect } from 'react-redux';
import { FormattedMessage, injectIntl } from 'react-intl';
import { Grid, Row, Col } from 'react-flexbox-grid/lib';
import { fetchTopicSummary, updateTopic, setTopicNeedsNewSnapshot } from '../../../actions/topicActions';
import { updateFeedback } from '../../../actions/appActions';
import messages from '../../../resources/messages';
import BackLinkingControlBar from '../BackLinkingControlBar';
import Permissioned from '../../common/Permissioned';
import { PERMISSION_TOPIC_WRITE } from '../../../lib/auth';
import TopicForm, { TOPIC_FORM_MODE_EDIT } from './TopicForm';

const localMessages = {
  editTopicTitle: { id: 'topic.edit.title', defaultMessage: 'Edit Topic Settings' },
  editTopicText: { id: 'topic.edit.text', defaultMessage: 'You can update this Topic. If you make changes to the query, media sourcs, or dates, those will be reflected in the next snapshot you run.' },
  editTopic: { id: 'topic.edit', defaultMessage: 'Edit Topic' },
  editTopicCollectionsTitle: { id: 'topic.edit.editTopicCollectionsTitle', defaultMessage: 'Edit Sources and Collections' },
  editTopicCollectionsIntro: { id: 'topic.edit.editTopicCollectionsIntro', defaultMessage: 'The following are the Sources and Collections associated with this topic.' },
  feedback: { id: 'topic.edit.save.feedback', defaultMessage: 'We saved your changes' },
  failed: { id: 'topic.edit.save.failed', defaultMessage: 'Sorry, that didn\'t work!' },
};

const EditTopicContainer = (props) => {
  const { handleSave, topicInfo, topicId } = props;
  const { formatMessage } = props.intl;
  let initialValues = {};

  if (topicInfo) {
    // load sources and collections in a backwards compatible way
    const sources = topicInfo.media ? topicInfo.media.map(t => ({ ...t })) : [];
    const collections = topicInfo.media_tags ? topicInfo.media_tags.map(t => ({ ...t, name: t.label })) : [];
    const sourcesAndCollections = sources.concat(collections);
    initialValues = {
      buttonLabel: formatMessage(messages.save),
      ...topicInfo,
      sourcesAndCollections,
    };
  }
  return (

      <BackLinkingControlBar message={messages.backToTopic} linkTo={`/topics/${topicId}/summary`} />
      <Grid>
        <Title render={formatMessage(localMessages.editTopicTitle)} />
        <Row>
          <Col lg={12}>

<FormattedMessage {...localMessages.editTopicTitle} />

<FormattedMessage {...localMessages.editTopicText} />

          </Col>
        </Row>
        <Permissioned onlyTopic={PERMISSION_TOPIC_WRITE}>
          <TopicForm
            initialValues={initialValues}
            onSubmit={handleSave}
            title={formatMessage(localMessages.editTopicCollectionsTitle)}
            intro={formatMessage(localMessages.editTopicCollectionsIntro)}
            mode={TOPIC_FORM_MODE_EDIT}
          />
        </Permissioned>
      </Grid>

  );
};

EditTopicContainer.propTypes = {
  // from context
  location: PropTypes.object.isRequired,
  intl: PropTypes.object.isRequired,
  params: PropTypes.object,
  // from state
  timespan: PropTypes.object,
  filters: PropTypes.object.isRequired,
  topicId: PropTypes.number,
  topicInfo: PropTypes.object,
  handleSave: PropTypes.func.isRequired,
};

const mapStateToProps = (state, ownProps) => ({
  filters: state.topics.selected.filters,
  topicId: parseInt(ownProps.params.topicId, 10),
  topicInfo: state.topics.selected.info,
  timespan: state.topics.selected.timespans.selected,
  snapshots: state.topics.selected.snapshots.list,
  user: state.user,
});

const mapDispatchToProps = (dispatch, ownProps) => ({
  handleSave: (values) => {
    const infoToSave = {
      name: values.name,
      description: values.description,
      start_date: values.start_date,
      end_date: values.end_date,
      solr_seed_query: values.solr_seed_query,
      max_iterations: values.max_iterations,
      ch_monitor_id: values.ch_monitor_id,
      is_public: values.is_public,
      twitter_topics_id: values.twitter_topics_id,
    };
    infoToSave.is_public = infoToSave.is_public ? 1 : 0;
    if ('sourcesAndCollections' in values) {
      infoToSave['sources[]'] = values.sourcesAndCollections.filter(s => s.media_id).map(s => s.media_id);
      infoToSave['collections[]'] = values.sourcesAndCollections.filter(s => s.tags_id).map(s => s.tags_id);
    } else {
      infoToSave['sources[]'] = '';
      infoToSave['collections[]'] = '';
    }
    dispatch(updateTopic(ownProps.params.topicId, infoToSave))
      .then((results) => {
        if (results.topics_id) {
          // let them know it worked
          dispatch(updateFeedback({ open: true, message: ownProps.intl.formatMessage(localMessages.feedback) }));
          // if the dates changed tell them it needs a new snapshot
          if ((infoToSave.start_date !== ownProps.topicInfo.start_date) || (infoToSave.end_date !== ownProps.topicInfo.end_date)) {
            dispatch(setTopicNeedsNewSnapshot());
          }
          // update topic info and redirect back to topic summary (no filters because we lost them on edit)
          dispatch(fetchTopicSummary(results.topics_id))
            .then(() => dispatch(push(`/topics/${results.topics_id}/summary`)));
        } else {
          dispatch(updateFeedback({ open: true, message: ownProps.intl.formatMessage(localMessages.failed) }));
        }
      }
    );
  },
});

export default
  injectIntl(
    connect(mapStateToProps, mapDispatchToProps)(
      EditTopicContainer
    )
  );
<|endoftext|>


<commit_before>import { MlflowService } from './sdk/MlflowService';
import ErrorCodes from './sdk/ErrorCodes';

export const SEARCH_MAX_RESULTS = 100;

export const isPendingApi = (action) => {
  return action.type.endsWith("_PENDING");
};

export const pending = (apiActionType) => {
  return `${apiActionType}_PENDING`;
};

export const isFulfilledApi = (action) => {
  return action.type.endsWith("_FULFILLED");
};

export const fulfilled = (apiActionType) => {
  return `${apiActionType}_FULFILLED`;
};

export const isRejectedApi = (action) => {
  return action.type.endsWith("_REJECTED");
};

export const rejected = (apiActionType) => {
  return `${apiActionType}_REJECTED`;
};

export const LIST_EXPERIMENTS_API = 'LIST_EXPERIMENTS_API';
export const listExperimentsApi = (id = getUUID()) => {
  return {
    type: LIST_EXPERIMENTS_API,
    payload: wrapDeferred(MlflowService.listExperiments, {}),
    meta: { id: id },
  };
};

export const GET_EXPERIMENT_API = 'GET_EXPERIMENT_API';
export const getExperimentApi = (experimentId, id = getUUID()) => {
  return {
    type: GET_EXPERIMENT_API,
    payload: wrapDeferred(MlflowService.getExperiment, { experiment_id: experimentId }),
    meta: { id: id },
  };
};

export const GET_RUN_API = 'GET_RUN_API';
export const getRunApi = (runUuid, id = getUUID()) => {
  return {
    type: GET_RUN_API,
    payload: wrapDeferred(MlflowService.getRun, { run_uuid: runUuid }),
    meta: { id: id },
  };
};

export const DELETE_RUN_API = 'DELETE_RUN_API';
export const deleteRunApi = (runUuid, id = getUUID()) => {
  return (dispatch) => {
    const deleteResponse = dispatch({
      type: DELETE_RUN_API,
      payload: wrapDeferred(MlflowService.deleteRun, { run_id: runUuid }),
      meta: { id: getUUID() },
    });
    return deleteResponse.then(() => dispatch(getRunApi(runUuid, id)));
  };
};

export const RESTORE_RUN_API = 'RESTORE_RUN_API';
export const restoreRunApi = (runUuid, id = getUUID()) => {
  return (dispatch) => {
    const restoreResponse = dispatch({
      type: RESTORE_RUN_API,
      payload: wrapDeferred(MlflowService.restoreRun, { run_id: runUuid }),
      meta: { id: getUUID() },
    });
    return restoreResponse.then(() => dispatch(getRunApi(runUuid, id)));
  };
};

export const SEARCH_RUNS_API = 'SEARCH_RUNS_API';
export const searchRunsApi = (experimentIds, filter, runViewType, orderBy, id = getUUID()) => {
  return {
    type: SEARCH_RUNS_API,
    payload: wrapDeferred(MlflowService.searchRuns, {
      experiment_ids: experimentIds,
      filter: filter,
      run_view_type: runViewType,
      max_results: SEARCH_MAX_RESULTS,
      order_by: orderBy,
    }),
    meta: { id: id },
  };
};

export const LOAD_MORE_RUNS_API = 'LOAD_MORE_RUNS_API';
export const loadMoreRunsApi = (
  experimentIds,
  filter,
  runViewType,
  orderBy,
  pageToken,
  id = getUUID(),
) => ({
  type: LOAD_MORE_RUNS_API,
  payload: wrapDeferred(MlflowService.searchRuns, {
    experiment_ids: experimentIds,
    filter: filter,
    run_view_type: runViewType,
    max_results: SEARCH_MAX_RESULTS,
    order_by: orderBy,
    page_token: pageToken,
  }),
  meta: { id },
});

export const LIST_ARTIFACTS_API = 'LIST_ARTIFACTS_API';
export const listArtifactsApi = (runUuid, path, id = getUUID()) => {
  return {
    type: LIST_ARTIFACTS_API,
    payload: wrapDeferred(MlflowService.listArtifacts, {
      run_uuid: runUuid, path: path
    }),
    meta: { id: id, runUuid: runUuid, path: path },
  };
};

export const GET_METRIC_HISTORY_API = 'GET_METRIC_HISTORY_API';
export const getMetricHistoryApi = (runUuid, metricKey, id = getUUID()) => {
  return {
    type: GET_METRIC_HISTORY_API,
    payload: wrapDeferred(MlflowService.getMetricHistory, {
      run_uuid: runUuid, metric_key: metricKey
    }),
    meta: { id: id, runUuid: runUuid, key: metricKey },
  };
};

export const SET_TAG_API = 'SET_TAG_API';
export const setTagApi = (runUuid, tagName, tagValue, id = getUUID()) => {
  return {
    type: SET_TAG_API,
    payload: wrapDeferred(MlflowService.setTag, {
      run_uuid: runUuid, key: tagName, value: tagValue
    }),
    meta: { id: id, runUuid: runUuid, key: tagName, value: tagValue },
  };
};

export const SET_EXPERIMENT_TAG_API = 'SET_EXPERIMENT_TAG_API';
export const setExperimentTagApi = (experimentId, tagName, tagValue, id = getUUID()) => {
  return {
    type: SET_EXPERIMENT_TAG_API,
    payload: wrapDeferred(MlflowService.setExperimentTag, {
      experiment_id: experimentId, key: tagName, value: tagValue
    }),
    meta: { id, experimentId, key: tagName, value: tagValue },
  };
};

export const CLOSE_ERROR_MODAL = 'CLOSE_ERROR_MODAL';
export const closeErrorModal = () => {
  return {
    type: CLOSE_ERROR_MODAL,
  };
};

export const OPEN_ERROR_MODAL = 'OPEN_ERROR_MODAL';
export const openErrorModal = (text) => {
  return {
    type: OPEN_ERROR_MODAL,
    text,
  };
};

export const getUUID = () => {
  const randomPart = Math.random()
    .toString(36)
    .substring(2, 10);
  return new Date().getTime() + randomPart;
};

/**
 * Jquery's ajax promise is a bit weird so I chose to create a new Promise which resolves and
 * rejects using the ajax callbacks `success` and `error`.
 */
export const wrapDeferred = (deferred, data, timeLeftMs = 60000, sleepMs = 1000) => {
  return new Promise((resolve, reject) => {
    deferred({
      data,
      success: response => {
        resolve(response);
      },
      error: (xhr) => {
        if (xhr.status === 429) {
          if (timeLeftMs > 0) {
            console.warn("Request failed with status code 429, message " +
                new ErrorWrapper(xhr).getUserVisibleError() + ". Retrying after " +
                sleepMs + " ms. On additional 429 errors, will continue to retry for up " +
                "to " + timeLeftMs + " ms.");
            // Retry the request, subtracting the current sleep duration from the remaining time
            // and doubling the sleep duration
            const newTimeLeft = timeLeftMs - sleepMs;
            const newSleepMs = Math.min(newTimeLeft, sleepMs * 2);
            return new Promise(resolveRetry => setTimeout(resolveRetry, sleepMs)).then(() => {
              return wrapDeferred(deferred, data, newTimeLeft, newSleepMs);
            }).then(
                (successResponse) => resolve(successResponse),
                (failureResponse) => reject(failureResponse)
            );
          }
        }
        console.error("XHR failed", xhr);
        // We can't throw the XHR itself because it looks like a promise to the
        // redux-promise-middleware.
        return reject(new ErrorWrapper(xhr));
      }
    });
  });
};

export class ErrorWrapper {
  constructor(xhr) {
    this.xhr = xhr;
  }

  getErrorCode() {
    const responseText = this.xhr.responseText;
    if (responseText) {
      try {
        const parsed = JSON.parse(responseText);
        if (parsed.error_code) {
          return parsed.error_code;
        }
      } catch (e) {
        return ErrorCodes.INTERNAL_ERROR;
      }
    }
    return ErrorCodes.INTERNAL_ERROR;
  }

  // Return the responseText if it is in the
  // { error_code: ..., message: ...} format. Otherwise return "INTERNAL_SERVER_ERROR".
  getUserVisibleError() {
    const responseText = this.xhr.responseText;
    if (responseText) {
      try {
        const parsed = JSON.parse(responseText);
        if (parsed.error_code) {
          return responseText;
        }
      } catch (e) {
        return "INTERNAL_SERVER_ERROR";
      }
    }
    return "INTERNAL_SERVER_ERROR";
  }

  getMessageField() {
    const responseText = this.xhr.responseText;
    if (responseText) {
      try {
        const parsed = JSON.parse(responseText);
        if (parsed.error_code && parsed.message) {
          return parsed.message;
        }
      } catch (e) {
        return "INTERNAL_SERVER_ERROR";
      }
    }
    return "INTERNAL_SERVER_ERROR";
  }
}

<commit_msg>[UI] Enhance docstring for helper method used to make AJAX requests (#1852)<commit_after>import { MlflowService } from './sdk/MlflowService';
import ErrorCodes from './sdk/ErrorCodes';

export const SEARCH_MAX_RESULTS = 100;

export const isPendingApi = (action) => {
  return action.type.endsWith("_PENDING");
};

export const pending = (apiActionType) => {
  return `${apiActionType}_PENDING`;
};

export const isFulfilledApi = (action) => {
  return action.type.endsWith("_FULFILLED");
};

export const fulfilled = (apiActionType) => {
  return `${apiActionType}_FULFILLED`;
};

export const isRejectedApi = (action) => {
  return action.type.endsWith("_REJECTED");
};

export const rejected = (apiActionType) => {
  return `${apiActionType}_REJECTED`;
};

export const LIST_EXPERIMENTS_API = 'LIST_EXPERIMENTS_API';
export const listExperimentsApi = (id = getUUID()) => {
  return {
    type: LIST_EXPERIMENTS_API,
    payload: wrapDeferred(MlflowService.listExperiments, {}),
    meta: { id: id },
  };
};

export const GET_EXPERIMENT_API = 'GET_EXPERIMENT_API';
export const getExperimentApi = (experimentId, id = getUUID()) => {
  return {
    type: GET_EXPERIMENT_API,
    payload: wrapDeferred(MlflowService.getExperiment, { experiment_id: experimentId }),
    meta: { id: id },
  };
};

export const GET_RUN_API = 'GET_RUN_API';
export const getRunApi = (runUuid, id = getUUID()) => {
  return {
    type: GET_RUN_API,
    payload: wrapDeferred(MlflowService.getRun, { run_uuid: runUuid }),
    meta: { id: id },
  };
};

export const DELETE_RUN_API = 'DELETE_RUN_API';
export const deleteRunApi = (runUuid, id = getUUID()) => {
  return (dispatch) => {
    const deleteResponse = dispatch({
      type: DELETE_RUN_API,
      payload: wrapDeferred(MlflowService.deleteRun, { run_id: runUuid }),
      meta: { id: getUUID() },
    });
    return deleteResponse.then(() => dispatch(getRunApi(runUuid, id)));
  };
};

export const RESTORE_RUN_API = 'RESTORE_RUN_API';
export const restoreRunApi = (runUuid, id = getUUID()) => {
  return (dispatch) => {
    const restoreResponse = dispatch({
      type: RESTORE_RUN_API,
      payload: wrapDeferred(MlflowService.restoreRun, { run_id: runUuid }),
      meta: { id: getUUID() },
    });
    return restoreResponse.then(() => dispatch(getRunApi(runUuid, id)));
  };
};

export const SEARCH_RUNS_API = 'SEARCH_RUNS_API';
export const searchRunsApi = (experimentIds, filter, runViewType, orderBy, id = getUUID()) => {
  return {
    type: SEARCH_RUNS_API,
    payload: wrapDeferred(MlflowService.searchRuns, {
      experiment_ids: experimentIds,
      filter: filter,
      run_view_type: runViewType,
      max_results: SEARCH_MAX_RESULTS,
      order_by: orderBy,
    }),
    meta: { id: id },
  };
};

export const LOAD_MORE_RUNS_API = 'LOAD_MORE_RUNS_API';
export const loadMoreRunsApi = (
  experimentIds,
  filter,
  runViewType,
  orderBy,
  pageToken,
  id = getUUID(),
) => ({
  type: LOAD_MORE_RUNS_API,
  payload: wrapDeferred(MlflowService.searchRuns, {
    experiment_ids: experimentIds,
    filter: filter,
    run_view_type: runViewType,
    max_results: SEARCH_MAX_RESULTS,
    order_by: orderBy,
    page_token: pageToken,
  }),
  meta: { id },
});

export const LIST_ARTIFACTS_API = 'LIST_ARTIFACTS_API';
export const listArtifactsApi = (runUuid, path, id = getUUID()) => {
  return {
    type: LIST_ARTIFACTS_API,
    payload: wrapDeferred(MlflowService.listArtifacts, {
      run_uuid: runUuid, path: path
    }),
    meta: { id: id, runUuid: runUuid, path: path },
  };
};

export const GET_METRIC_HISTORY_API = 'GET_METRIC_HISTORY_API';
export const getMetricHistoryApi = (runUuid, metricKey, id = getUUID()) => {
  return {
    type: GET_METRIC_HISTORY_API,
    payload: wrapDeferred(MlflowService.getMetricHistory, {
      run_uuid: runUuid, metric_key: metricKey
    }),
    meta: { id: id, runUuid: runUuid, key: metricKey },
  };
};

export const SET_TAG_API = 'SET_TAG_API';
export const setTagApi = (runUuid, tagName, tagValue, id = getUUID()) => {
  return {
    type: SET_TAG_API,
    payload: wrapDeferred(MlflowService.setTag, {
      run_uuid: runUuid, key: tagName, value: tagValue
    }),
    meta: { id: id, runUuid: runUuid, key: tagName, value: tagValue },
  };
};

export const SET_EXPERIMENT_TAG_API = 'SET_EXPERIMENT_TAG_API';
export const setExperimentTagApi = (experimentId, tagName, tagValue, id = getUUID()) => {
  return {
    type: SET_EXPERIMENT_TAG_API,
    payload: wrapDeferred(MlflowService.setExperimentTag, {
      experiment_id: experimentId, key: tagName, value: tagValue
    }),
    meta: { id, experimentId, key: tagName, value: tagValue },
  };
};

export const CLOSE_ERROR_MODAL = 'CLOSE_ERROR_MODAL';
export const closeErrorModal = () => {
  return {
    type: CLOSE_ERROR_MODAL,
  };
};

export const OPEN_ERROR_MODAL = 'OPEN_ERROR_MODAL';
export const openErrorModal = (text) => {
  return {
    type: OPEN_ERROR_MODAL,
    text,
  };
};

export const getUUID = () => {
  const randomPart = Math.random()
    .toString(36)
    .substring(2, 10);
  return new Date().getTime() + randomPart;
};

/**
 * Wraps a Jquery AJAX request (passed via `deferred`) in a new Promise which resolves and
 * rejects using the ajax callbacks `success` and `error`. Retries with exponential backoff
 * if the server responds with a 429 (Too Many Requests).
 * @param {function} deferred - Function with signature ({data, success, error}) => Any, where
 *   data is a JSON payload for an AJAX request, success is a callback to execute on request
 *   success, and error is a callback to execute on request failure.
 * @param {object} data - Data argument to pass to `deferred`
 * @param {int} timeLeftMs - Time left to retry the AJAX request in ms, if we receive a 429
 * response from the server. Defaults to 60 seconds.
 * @param {int} sleepMs - Time to sleep before retrying the AJAX request if we receive a 429
 * response from the server. Defaults to 1 second.
 */
export const wrapDeferred = (deferred, data, timeLeftMs = 60000, sleepMs = 1000) => {
  return new Promise((resolve, reject) => {
    deferred({
      data,
      success: response => {
        resolve(response);
      },
      error: (xhr) => {
        if (xhr.status === 429) {
          if (timeLeftMs > 0) {
            console.warn("Request failed with status code 429, message " +
                new ErrorWrapper(xhr).getUserVisibleError() + ". Retrying after " +
                sleepMs + " ms. On additional 429 errors, will continue to retry for up " +
                "to " + timeLeftMs + " ms.");
            // Retry the request, subtracting the current sleep duration from the remaining time
            // and doubling the sleep duration
            const newTimeLeft = timeLeftMs - sleepMs;
            const newSleepMs = Math.min(newTimeLeft, sleepMs * 2);
            return new Promise(resolveRetry => setTimeout(resolveRetry, sleepMs)).then(() => {
              return wrapDeferred(deferred, data, newTimeLeft, newSleepMs);
            }).then(
                (successResponse) => resolve(successResponse),
                (failureResponse) => reject(failureResponse)
            );
          }
        }
        console.error("XHR failed", xhr);
        // We can't throw the XHR itself because it looks like a promise to the
        // redux-promise-middleware.
        return reject(new ErrorWrapper(xhr));
      }
    });
  });
};

export class ErrorWrapper {
  constructor(xhr) {
    this.xhr = xhr;
  }

  getErrorCode() {
    const responseText = this.xhr.responseText;
    if (responseText) {
      try {
        const parsed = JSON.parse(responseText);
        if (parsed.error_code) {
          return parsed.error_code;
        }
      } catch (e) {
        return ErrorCodes.INTERNAL_ERROR;
      }
    }
    return ErrorCodes.INTERNAL_ERROR;
  }

  // Return the responseText if it is in the
  // { error_code: ..., message: ...} format. Otherwise return "INTERNAL_SERVER_ERROR".
  getUserVisibleError() {
    const responseText = this.xhr.responseText;
    if (responseText) {
      try {
        const parsed = JSON.parse(responseText);
        if (parsed.error_code) {
          return responseText;
        }
      } catch (e) {
        return "INTERNAL_SERVER_ERROR";
      }
    }
    return "INTERNAL_SERVER_ERROR";
  }

  getMessageField() {
    const responseText = this.xhr.responseText;
    if (responseText) {
      try {
        const parsed = JSON.parse(responseText);
        if (parsed.error_code && parsed.message) {
          return parsed.message;
        }
      } catch (e) {
        return "INTERNAL_SERVER_ERROR";
      }
    }
    return "INTERNAL_SERVER_ERROR";
  }
}

<|endoftext|>