<commit_before>import * as d3 from "d3";

export default function forceInABox() {
  // d3 style
  function constant(_) {
    return () => _;
  }

  function index(d) {
    return d.index;
  }

  var id = index,
    nodes = [],
    links = [], //needed for the force version
    tree,
    size = [100, 100],
    forceNodeSize = constant(1), // The expected node size used for computing the cluster node
    forceCharge = constant(-1),
    forceLinkDistance = constant(100),
    forceLinkStrength = constant(0.1),
    foci = {},
    // oldStart = force.start,
    linkStrengthIntraCluster = 0.1,
    linkStrengthInterCluster = 0.001,
    // oldGravity = force.gravity(),
    templateNodes = [],
    offset = [0, 0],
    templateForce,
    groupBy = function(d) {
      return d.cluster;
    },
    template = "treemap",
    enableGrouping = true,
    strength = 0.1;
  // showingTemplate = false;

  function force(alpha) {
    if (!enableGrouping) {
      return force;
    }
    if (template === "force") {
      //Do the tick of the template force and get the new focis
      templateForce.tick();
      getFocisFromTemplate();
    }

    for (var i = 0, n = nodes.length, node, k = alpha * strength; i < n; ++i) {
      node = nodes[i];
      node.vx += (foci[groupBy(node)].x - node.x) * k;
      node.vy += (foci[groupBy(node)].y - node.y) * k;
    }
  }

  function initialize() {
    if (!nodes) return;

    // var i,
    //     n = nodes.length,
    //     m = links.length,
    //     nodeById = map(nodes, id),
    //     link;

    if (template === "treemap") {
      initializeWithTreemap();
    } else {
      initializeWithForce();
    }
  }

  force.initialize = function(_) {
    nodes = _;
    initialize();
  };

  function getLinkKey(l) {
    var sourceID = groupBy(l.source),
      targetID = groupBy(l.target);

    return sourceID <= targetID
      ? sourceID + "~" + targetID
      : targetID + "~" + sourceID;
  }

  function computeClustersNodeCounts(nodes) {
    var clustersCounts = d3.map(),
      tmpCount = {};

    nodes.forEach(function(d) {
      if (!clustersCounts.has(groupBy(d))) {
        clustersCounts.set(groupBy(d), { count: 0, sumforceNodeSize: 0 });
      }
    });

    nodes.forEach(function(d) {
      // if (!d.show) { return; }
      tmpCount = clustersCounts.get(groupBy(d));
      tmpCount.count = tmpCount.count + 1;
      tmpCount.sumforceNodeSize =
        tmpCount.sumforceNodeSize + Math.PI * (forceNodeSize(d) * forceNodeSize(d)) * 1.3;
      clustersCounts.set(groupBy(d), tmpCount);
    });

    return clustersCounts;
  }

  //Returns
  function computeClustersLinkCounts(links) {
    var dClusterLinks = d3.map(),
      clusterLinks = [];
    links.forEach(function(l) {
      var key = getLinkKey(l),
        count;
      if (dClusterLinks.has(key)) {
        count = dClusterLinks.get(key);
      } else {
        count = 0;
      }
      count += 1;
      dClusterLinks.set(key, count);
    });

    dClusterLinks.entries().forEach(function(d) {
      var source, target;
      source = d.key.split("~")[0];
      target = d.key.split("~")[1];
      if (source !== undefined && target !== undefined) {
        clusterLinks.push({
          source: source,
          target: target,
          count: d.value
        });
      }
    });
    return clusterLinks;
  }

  //Returns the metagraph of the clusters
  function getGroupsGraph() {
    var gnodes = [],
      glinks = [],
      // edges = [],
      dNodes = d3.map(),
      // totalSize = 0,
      clustersList,
      c,
      i,
      cc,
      clustersCounts,
      clustersLinks;

    clustersCounts = computeClustersNodeCounts(nodes);
    clustersLinks = computeClustersLinkCounts(links);

    //map.keys() is really slow, it"s crucial to have it outside the loop
    clustersList = clustersCounts.keys();
    for (i = 0; i < clustersList.length; i += 1) {
      c = clustersList[i];
      cc = clustersCounts.get(c);
      gnodes.push({
        id: c,
        size: cc.count,
        r: Math.sqrt(cc.sumforceNodeSize / Math.PI)
      }); // Uses approx meta-node size
      dNodes.set(c, i);
      // totalSize += size;
    }

    clustersLinks.forEach(function(l) {
      var source = dNodes.get(l.source),
        target = dNodes.get(l.target);
      if (source !== undefined && target !== undefined) {
        glinks.push({
          source: source,
          target: target,
          count: l.count
        });
      } else {
        // console.log("Force in a box error, couldn"t find the link source or target on the list of nodes");
      }
    });

    return { nodes: gnodes, links: glinks };
  }

  function getGroupsTree() {
    var children = [],
      // totalSize = 0,
      clustersList,
      c,
      i,
      cc,
      clustersCounts;

    clustersCounts = computeClustersNodeCounts(force.nodes());

    //map.keys() is really slow, it"s crucial to have it outside the loop
    clustersList = clustersCounts.keys();
    for (i = 0; i < clustersList.length; i += 1) {
      c = clustersList[i];
      cc = clustersCounts.get(c);
      children.push({ id: c, size: cc.count });
      // totalSize += size;
    }
    return { id: "clustersTree", children: children };
  }

  function getFocisFromTemplate() {
    //compute foci
    foci.none = { x: 0, y: 0 };
    templateNodes.forEach(function(d) {
      if (template === "treemap") {
        foci[d.data.id] = {
          x: d.x0 + (d.x1 - d.x0) / 2 - offset[0],
          y: d.y0 + (d.y1 - d.y0) / 2 - offset[1]
        };
      } else {
        foci[d.id] = {
          x: d.x - offset[0],
          y: d.y - offset[1]
        };
      }
    });
  }
  function initializeWithTreemap() {
    var treemap = d3.treemap().size(force.size());

    tree = d3
      .hierarchy(getGroupsTree())
      .sum(function(d) {
        return d.size;
      })
      .sort(function(a, b) {
        return b.height - a.height || b.value - a.value;
      });

    templateNodes = treemap(tree).leaves();
    getFocisFromTemplate();
  }

  function checkLinksAsObjects() {
    // Check if links come in the format of indexes instead of objects
    var linkCount = 0;
    if (nodes.length === 0) return;

    links.forEach(function(link) {
      var source, target;
      if (!nodes) return;
      source = link.source;
      target = link.target;
      if (typeof link.source !== "object") source = nodes[link.source];
      if (typeof link.target !== "object") target = nodes[link.target];
      if (source === undefined || target === undefined) {
        // console.error(link);
        throw Error(
          "Error setting links, couldnt find nodes for a link (see it on the console)"
        );
      }
      link.source = source;
      link.target = target;
      link.index = linkCount++;
    });
  }

  function initializeWithForce() {
    var net;

    if (!nodes || !nodes.length ) { return; }

    if (nodes && nodes.length > 0) {
      if (groupBy(nodes[0]) === undefined) {
        throw Error(
          "Couldnt find the grouping attribute for the nodes. Make sure to set it up with forceInABox.groupBy('clusterAttr') before calling .links()"
        );
      }
    }

    checkLinksAsObjects();

    net = getGroupsGraph();
    templateForce = d3
      .forceSimulation(net.nodes)
      .force("x", d3.forceX(size[0] / 2).strength(0.1))
      .force("y", d3.forceY(size[1] / 2).strength(0.1))
      .force(
        "collide",
        d3.forceCollide(function(d) {
          return d.r;
        }).iterations(4)
      )
      .force("charge", d3.forceManyBody().strength(forceCharge))
      .force(
        "links",
        d3.forceLink(net.nodes.length ? net.links : [])
          .distance(forceLinkDistance)
          .strength(forceLinkStrength)
      );

    // console.log("Initialize with force ", templateForce.nodes().length, " ", templateForce.force("links").links().length);

    // var i = 0;
    // while (i++ < 500) templateForce.tick();

    templateNodes = templateForce.nodes();

    getFocisFromTemplate();
  }

  function drawTreemap(container) {
    // Delete the circle Template if it exists
    container.selectAll("circle.cell").remove();
    container.selectAll("line.cell").remove();
    container
      .selectAll("rect.cell")
      .data(templateNodes)
      .enter()
      .append("svg:rect")
      .attr("class", "cell")
      .attr("x", function(d) {
        return d.x0;
      })
      .attr("y", function(d) {
        return d.y0;
      })
      .attr("width", function(d) {
        return d.x1 - d.x0;
      })
      .attr("height", function(d) {
        return d.y1 - d.y0;
      });
  }

  function drawGraph(container) {
    // Delete the treemap if any
    container.selectAll("rect.cell").remove();
    var templateLinksSel = container.selectAll("line.cell").data(templateForce.force("links").links());
    templateLinksSel
      .enter()
      .append("line")
      .attr("class", "cell")
      .merge(templateLinksSel)
      .attr("x2", function(d) { return d.source.x; })
      .attr("y2", function(d) { return d.source.y; })
      .attr("x1", function(d) { return d.target.x; })
      .attr("y1", function(d) { return d.target.y; })
      .style("stroke-width", "1px")
      .style("stroke-opacity", "0.5");

    var templateNodesSel = container.selectAll("circle.cell").data(templateForce.nodes());
    templateNodesSel
      .enter()
      .append("svg:circle")
      .attr("class", "cell")
      .merge(templateNodesSel)
      .attr("cx", function(d) {
        return d.x;
      })
      .attr("cy", function(d) {
        return d.y;
      })
      .attr("r", function(d) {
        return d.r;
      });

    templateForce
      .on("tick", () => {
         // console.log("tick");
         drawGraph(container);
      })
      .restart();

    templateNodesSel.exit().remove();
    templateLinksSel.exit().remove();
  }

  force.drawTemplate = function(container) {
    // showingTemplate = true;
    if (template === "treemap") {
      drawTreemap(container);
    } else {
      drawGraph(container);
    }
    return force;
  };

  //Backwards compatibility
  force.drawTreemap = force.drawTemplate;

  force.deleteTemplate = function(container) {
    // showingTemplate = false;
    container.selectAll(".cell").remove();

    if (templateForce) {
      templateForce
        .on("tick", null)
        .restart();
    }

    return force;
  };

  force.template = function(x) {
    if (!arguments.length) return template;
    template = x;
    initialize();
    return force;
  };

  force.groupBy = function(x) {
    if (!arguments.length) return groupBy;
    if (typeof x === "string") {
      groupBy = function(d) {
        return d[x];
      };
      return force;
    }
    groupBy = x;
    return force;
  };

  force.enableGrouping = function(x) {
    if (!arguments.length) return enableGrouping;
    enableGrouping = x;
    // update();
    return force;
  };

  force.strength = function(x) {
    if (!arguments.length) return strength;
    strength = x;
    return force;
  };

  force.getLinkStrength = function(e) {
    if (enableGrouping) {
      if (groupBy(e.source) === groupBy(e.target)) {
        if (typeof linkStrengthIntraCluster === "function") {
          return linkStrengthIntraCluster(e);
        } else {
          return linkStrengthIntraCluster;
        }
      } else {
        if (typeof linkStrengthInterCluster === "function") {
          return linkStrengthInterCluster(e);
        } else {
          return linkStrengthInterCluster;
        }
      }
    } else {
      // Not grouping return the intracluster
      if (typeof linkStrengthIntraCluster === "function") {
        return linkStrengthIntraCluster(e);
      } else {
        return linkStrengthIntraCluster;
      }
    }
  };

  force.id = function(_) {
    return arguments.length ? ((id = _), force) : id;
  };

  force.size = function(_) {
    return arguments.length ? ((size = _), force) : size;
  };

  force.linkStrengthInterCluster = function(_) {
    return arguments.length
      ? ((linkStrengthInterCluster = _), force)
      : linkStrengthInterCluster;
  };

  force.linkStrengthIntraCluster = function(_) {
    return arguments.length
      ? ((linkStrengthIntraCluster = _), force)
      : linkStrengthIntraCluster;
  };

  force.nodes = function(_) {
    return arguments.length ? ((nodes = _), force) : nodes;
  };

  force.links = function(_) {
    if (!arguments.length) return links;
    if (_ === null) links = [];
    else links = _;
    initialize();
    return force;
  };

  force.forceNodeSize = function(_) {
    return arguments.length
      ? ((forceNodeSize = typeof _ === "function" ? _ : constant(+_)), initialize(), force)
      : forceNodeSize;
  };

  // Legacy support
  force.nodeSize = force.forceNodeSize;

  force.forceCharge = function(_) {
    return arguments.length
      ? ((forceCharge = typeof _ === "function" ? _ : constant(+_)), initialize(), force)
      : forceCharge;
  };

  force.forceLinkDistance = function(_) {
    return arguments.length
      ? ((forceLinkDistance = typeof _ === "function" ? _ : constant(+_)), initialize(), force)
      : forceLinkDistance;
  };

  force.forceLinkStrength = function(_) {
    return arguments.length
      ? ((forceLinkStrength = typeof _ === "function" ? _ : constant(+_)), initialize(), force)
      : forceLinkStrength;
  };

  force.offset = function(_) {
    return arguments.length
      ? ((offset = typeof _ === "function" ? _ : constant(+_)), force)
      : offset;
  };

  return force;
}

// module.exports = forceInABox;<commit_msg>Adding a function to export the focis<commit_after>import * as d3 from "d3";

export default function forceInABox() {
  // d3 style
  function constant(_) {
    return () => _;
  }

  function index(d) {
    return d.index;
  }

  var id = index,
    nodes = [],
    links = [], //needed for the force version
    tree,
    size = [100, 100],
    forceNodeSize = constant(1), // The expected node size used for computing the cluster node
    forceCharge = constant(-1),
    forceLinkDistance = constant(100),
    forceLinkStrength = constant(0.1),
    foci = {},
    // oldStart = force.start,
    linkStrengthIntraCluster = 0.1,
    linkStrengthInterCluster = 0.001,
    // oldGravity = force.gravity(),
    templateNodes = [],
    offset = [0, 0],
    templateForce,
    groupBy = function(d) {
      return d.cluster;
    },
    template = "treemap",
    enableGrouping = true,
    strength = 0.1;
  // showingTemplate = false;

  function force(alpha) {
    if (!enableGrouping) {
      return force;
    }
    if (template === "force") {
      //Do the tick of the template force and get the new focis
      templateForce.tick();
      getFocisFromTemplate();
    }

    for (var i = 0, n = nodes.length, node, k = alpha * strength; i < n; ++i) {
      node = nodes[i];
      node.vx += (foci[groupBy(node)].x - node.x) * k;
      node.vy += (foci[groupBy(node)].y - node.y) * k;
    }
  }

  function initialize() {
    if (!nodes) return;

    // var i,
    //     n = nodes.length,
    //     m = links.length,
    //     nodeById = map(nodes, id),
    //     link;

    if (template === "treemap") {
      initializeWithTreemap();
    } else {
      initializeWithForce();
    }
  }

  force.initialize = function(_) {
    nodes = _;
    initialize();
  };

  function getLinkKey(l) {
    var sourceID = groupBy(l.source),
      targetID = groupBy(l.target);

    return sourceID <= targetID
      ? sourceID + "~" + targetID
      : targetID + "~" + sourceID;
  }

  function computeClustersNodeCounts(nodes) {
    var clustersCounts = d3.map(),
      tmpCount = {};

    nodes.forEach(function(d) {
      if (!clustersCounts.has(groupBy(d))) {
        clustersCounts.set(groupBy(d), { count: 0, sumforceNodeSize: 0 });
      }
    });

    nodes.forEach(function(d) {
      // if (!d.show) { return; }
      tmpCount = clustersCounts.get(groupBy(d));
      tmpCount.count = tmpCount.count + 1;
      tmpCount.sumforceNodeSize =
        tmpCount.sumforceNodeSize + Math.PI * (forceNodeSize(d) * forceNodeSize(d)) * 1.3;
      clustersCounts.set(groupBy(d), tmpCount);
    });

    return clustersCounts;
  }

  //Returns
  function computeClustersLinkCounts(links) {
    var dClusterLinks = d3.map(),
      clusterLinks = [];
    links.forEach(function(l) {
      var key = getLinkKey(l),
        count;
      if (dClusterLinks.has(key)) {
        count = dClusterLinks.get(key);
      } else {
        count = 0;
      }
      count += 1;
      dClusterLinks.set(key, count);
    });

    dClusterLinks.entries().forEach(function(d) {
      var source, target;
      source = d.key.split("~")[0];
      target = d.key.split("~")[1];
      if (source !== undefined && target !== undefined) {
        clusterLinks.push({
          source: source,
          target: target,
          count: d.value
        });
      }
    });
    return clusterLinks;
  }

  //Returns the metagraph of the clusters
  function getGroupsGraph() {
    var gnodes = [],
      glinks = [],
      // edges = [],
      dNodes = d3.map(),
      // totalSize = 0,
      clustersList,
      c,
      i,
      cc,
      clustersCounts,
      clustersLinks;

    clustersCounts = computeClustersNodeCounts(nodes);
    clustersLinks = computeClustersLinkCounts(links);

    //map.keys() is really slow, it"s crucial to have it outside the loop
    clustersList = clustersCounts.keys();
    for (i = 0; i < clustersList.length; i += 1) {
      c = clustersList[i];
      cc = clustersCounts.get(c);
      gnodes.push({
        id: c,
        size: cc.count,
        r: Math.sqrt(cc.sumforceNodeSize / Math.PI)
      }); // Uses approx meta-node size
      dNodes.set(c, i);
      // totalSize += size;
    }

    clustersLinks.forEach(function(l) {
      var source = dNodes.get(l.source),
        target = dNodes.get(l.target);
      if (source !== undefined && target !== undefined) {
        glinks.push({
          source: source,
          target: target,
          count: l.count
        });
      } else {
        // console.log("Force in a box error, couldn"t find the link source or target on the list of nodes");
      }
    });

    return { nodes: gnodes, links: glinks };
  }

  function getGroupsTree() {
    var children = [],
      // totalSize = 0,
      clustersList,
      c,
      i,
      cc,
      clustersCounts;

    clustersCounts = computeClustersNodeCounts(force.nodes());

    //map.keys() is really slow, it"s crucial to have it outside the loop
    clustersList = clustersCounts.keys();
    for (i = 0; i < clustersList.length; i += 1) {
      c = clustersList[i];
      cc = clustersCounts.get(c);
      children.push({ id: c, size: cc.count });
      // totalSize += size;
    }
    return { id: "clustersTree", children: children };
  }

  function getFocisFromTemplate() {
    //compute foci
    foci.none = { x: 0, y: 0 };
    templateNodes.forEach(function(d) {
      if (template === "treemap") {
        foci[d.data.id] = {
          x: d.x0 + (d.x1 - d.x0) / 2 - offset[0],
          y: d.y0 + (d.y1 - d.y0) / 2 - offset[1]
        };
      } else {
        foci[d.id] = {
          x: d.x - offset[0],
          y: d.y - offset[1]
        };
      }
    });
    return foci;
  }
  function initializeWithTreemap() {
    var treemap = d3.treemap().size(force.size());

    tree = d3
      .hierarchy(getGroupsTree())
      .sum(function(d) {
        return d.size;
      })
      .sort(function(a, b) {
        return b.height - a.height || b.value - a.value;
      });

    templateNodes = treemap(tree).leaves();
    getFocisFromTemplate();
  }

  function checkLinksAsObjects() {
    // Check if links come in the format of indexes instead of objects
    var linkCount = 0;
    if (nodes.length === 0) return;

    links.forEach(function(link) {
      var source, target;
      if (!nodes) return;
      source = link.source;
      target = link.target;
      if (typeof link.source !== "object") source = nodes[link.source];
      if (typeof link.target !== "object") target = nodes[link.target];
      if (source === undefined || target === undefined) {
        // console.error(link);
        throw Error(
          "Error setting links, couldnt find nodes for a link (see it on the console)"
        );
      }
      link.source = source;
      link.target = target;
      link.index = linkCount++;
    });
  }

  function initializeWithForce() {
    var net;

    if (!nodes || !nodes.length ) { return; }

    if (nodes && nodes.length > 0) {
      if (groupBy(nodes[0]) === undefined) {
        throw Error(
          "Couldnt find the grouping attribute for the nodes. Make sure to set it up with forceInABox.groupBy('clusterAttr') before calling .links()"
        );
      }
    }

    checkLinksAsObjects();

    net = getGroupsGraph();
    templateForce = d3
      .forceSimulation(net.nodes)
      .force("x", d3.forceX(size[0] / 2).strength(0.1))
      .force("y", d3.forceY(size[1] / 2).strength(0.1))
      .force(
        "collide",
        d3.forceCollide(function(d) {
          return d.r;
        }).iterations(4)
      )
      .force("charge", d3.forceManyBody().strength(forceCharge))
      .force(
        "links",
        d3.forceLink(net.nodes.length ? net.links : [])
          .distance(forceLinkDistance)
          .strength(forceLinkStrength)
      );

    // console.log("Initialize with force ", templateForce.nodes().length, " ", templateForce.force("links").links().length);

    // var i = 0;
    // while (i++ < 500) templateForce.tick();

    templateNodes = templateForce.nodes();

    getFocisFromTemplate();
  }

  function drawTreemap(container) {
    // Delete the circle Template if it exists
    container.selectAll("circle.cell").remove();
    container.selectAll("line.cell").remove();
    container
      .selectAll("rect.cell")
      .data(templateNodes)
      .enter()
      .append("svg:rect")
      .attr("class", "cell")
      .attr("x", function(d) {
        return d.x0;
      })
      .attr("y", function(d) {
        return d.y0;
      })
      .attr("width", function(d) {
        return d.x1 - d.x0;
      })
      .attr("height", function(d) {
        return d.y1 - d.y0;
      });
  }

  function drawGraph(container) {
    // Delete the treemap if any
    container.selectAll("rect.cell").remove();
    var templateLinksSel = container.selectAll("line.cell").data(templateForce.force("links").links());
    templateLinksSel
      .enter()
      .append("line")
      .attr("class", "cell")
      .merge(templateLinksSel)
      .attr("x2", function(d) { return d.source.x; })
      .attr("y2", function(d) { return d.source.y; })
      .attr("x1", function(d) { return d.target.x; })
      .attr("y1", function(d) { return d.target.y; })
      .style("stroke-width", "1px")
      .style("stroke-opacity", "0.5");

    var templateNodesSel = container.selectAll("circle.cell").data(templateForce.nodes());
    templateNodesSel
      .enter()
      .append("svg:circle")
      .attr("class", "cell")
      .merge(templateNodesSel)
      .attr("cx", function(d) {
        return d.x;
      })
      .attr("cy", function(d) {
        return d.y;
      })
      .attr("r", function(d) {
        return d.r;
      });

    templateForce
      .on("tick", () => {
         // console.log("tick");
         drawGraph(container);
      })
      .restart();

    templateNodesSel.exit().remove();
    templateLinksSel.exit().remove();
  }

  force.drawTemplate = function(container) {
    // showingTemplate = true;
    if (template === "treemap") {
      drawTreemap(container);
    } else {
      drawGraph(container);
    }
    return force;
  };

  //Backwards compatibility
  force.drawTreemap = force.drawTemplate;

  force.deleteTemplate = function(container) {
    // showingTemplate = false;
    container.selectAll(".cell").remove();

    if (templateForce) {
      templateForce
        .on("tick", null)
        .restart();
    }

    return force;
  };

  force.template = function(x) {
    if (!arguments.length) return template;
    template = x;
    initialize();
    return force;
  };

  force.groupBy = function(x) {
    if (!arguments.length) return groupBy;
    if (typeof x === "string") {
      groupBy = function(d) {
        return d[x];
      };
      return force;
    }
    groupBy = x;
    return force;
  };

  force.enableGrouping = function(x) {
    if (!arguments.length) return enableGrouping;
    enableGrouping = x;
    // update();
    return force;
  };

  force.strength = function(x) {
    if (!arguments.length) return strength;
    strength = x;
    return force;
  };

  force.getLinkStrength = function(e) {
    if (enableGrouping) {
      if (groupBy(e.source) === groupBy(e.target)) {
        if (typeof linkStrengthIntraCluster === "function") {
          return linkStrengthIntraCluster(e);
        } else {
          return linkStrengthIntraCluster;
        }
      } else {
        if (typeof linkStrengthInterCluster === "function") {
          return linkStrengthInterCluster(e);
        } else {
          return linkStrengthInterCluster;
        }
      }
    } else {
      // Not grouping return the intracluster
      if (typeof linkStrengthIntraCluster === "function") {
        return linkStrengthIntraCluster(e);
      } else {
        return linkStrengthIntraCluster;
      }
    }
  };

  force.id = function(_) {
    return arguments.length ? ((id = _), force) : id;
  };

  force.size = function(_) {
    return arguments.length ? ((size = _), force) : size;
  };

  force.linkStrengthInterCluster = function(_) {
    return arguments.length
      ? ((linkStrengthInterCluster = _), force)
      : linkStrengthInterCluster;
  };

  force.linkStrengthIntraCluster = function(_) {
    return arguments.length
      ? ((linkStrengthIntraCluster = _), force)
      : linkStrengthIntraCluster;
  };

  force.nodes = function(_) {
    return arguments.length ? ((nodes = _), force) : nodes;
  };

  force.links = function(_) {
    if (!arguments.length) return links;
    if (_ === null) links = [];
    else links = _;
    initialize();
    return force;
  };

  force.forceNodeSize = function(_) {
    return arguments.length
      ? ((forceNodeSize = typeof _ === "function" ? _ : constant(+_)), initialize(), force)
      : forceNodeSize;
  };

  // Legacy support
  force.nodeSize = force.forceNodeSize;

  force.forceCharge = function(_) {
    return arguments.length
      ? ((forceCharge = typeof _ === "function" ? _ : constant(+_)), initialize(), force)
      : forceCharge;
  };

  force.forceLinkDistance = function(_) {
    return arguments.length
      ? ((forceLinkDistance = typeof _ === "function" ? _ : constant(+_)), initialize(), force)
      : forceLinkDistance;
  };

  force.forceLinkStrength = function(_) {
    return arguments.length
      ? ((forceLinkStrength = typeof _ === "function" ? _ : constant(+_)), initialize(), force)
      : forceLinkStrength;
  };

  force.offset = function(_) {
    return arguments.length
      ? ((offset = typeof _ === "function" ? _ : constant(+_)), force)
      : offset;
  };

  force.getFocis = getFocisFromTemplate;

  return force;
}

// module.exports = forceInABox;<|endoftext|>


<commit_before>define([
  'underscore'
],
function (_) {
  'use strict';

  var index = [];
  var categories = {
    Combine: [],
    Transform: [],
    Calculate: [],
    Filter: [],
    Special: []
  };

  function addFuncDef(funcDef) {
    funcDef.params = funcDef.params || [];
    funcDef.defaultParams = funcDef.defaultParams || [];

    if (funcDef.category) {
      funcDef.category.push(funcDef);
    }
    index[funcDef.name] = funcDef;
    index[funcDef.shortName || funcDef.name] = funcDef;
  }

  addFuncDef({
    name: 'scaleToSeconds',
    category: categories.Transform,
    params: [ { name: 'seconds', type: 'int' } ],
    defaultParams: [1],
  });

  addFuncDef({
    name: "holtWintersForecast",
    category: categories.Calculate,
  });

  addFuncDef({
    name: "holtWintersConfidenceBands",
    category: categories.Calculate,
    params: [ { name: "delta", type: 'int' } ],
    defaultParams: [3]
  });

  addFuncDef({
    name: "holtWintersAberration",
    category: categories.Calculate,
    params: [ { name: "delta", type: 'int' } ],
    defaultParams: [3]
  });

  addFuncDef({
    name: "nPercentile",
    category: categories.Calculate,
    params: [ { name: "Nth percentile", type: 'int' } ],
    defaultParams: [95]
  });

  addFuncDef({
    name: 'sumSeries',
    shortName: 'sum',
    category: categories.Combine,
  });

  addFuncDef({
    name: 'averageSeries',
    shortName: 'avg',
    category: categories.Combine,
  });

  addFuncDef({
    name: 'percentileOfSeries',
    category: categories.Combine,
    params: [ { name: "n", type: "int" }, { name: "interpolate", type: "select", options: ["true", "false"] } ],
    defaultParams: [95, "false"]
  });

  addFuncDef({
    name: 'sumSeriesWithWildcards',
    category: categories.Combine,
    params: [ { name: "node", type: "int" } ],
    defaultParams: [3]
  });

  addFuncDef({
    name: 'maxSeries',
    shortName: 'max',
    category: categories.Combine,
  });

  addFuncDef({
    name: 'minSeries',
    shortName: 'min',
    category: categories.Combine,
  });

  addFuncDef({
    name: 'averageSeriesWithWildcards',
    category: categories.Combine,
    params: [ { name: "node", type: "int" } ],
    defaultParams: [3]
  });

  addFuncDef({
    name: "alias",
    category: categories.Special,
    params: [ { name: "alias", type: 'string' } ],
    defaultParams: ['alias']
  });

  addFuncDef({
    name: "aliasSub",
    category: categories.Special,
    params: [ { name: "search", type: 'string' }, { name: "replace", type: 'string' } ],
    defaultParams: ['', '']
  });

  addFuncDef({
    name: "stacked",
    category: categories.Special,
    params: [ { name: "stack", type: 'string' } ],
    defaultParams: ['stacked']
  });

  addFuncDef({
    name: "consolidateBy",
    category: categories.Special,
    params: [
      {
        name: 'function',
        type: 'string',
        options: ['sum', 'average', 'min', 'max']
      }
    ],
    defaultParams: ['max']
  });

  addFuncDef({
    name: "groupByNode",
    category: categories.Special,
    params: [
      {
        name: "node",
        type: "int",
        options: [1,2,3,4,5,6,7,8,9,10,12]
      },
      {
        name: "function",
        type: "string",
        options: ['sum', 'avg']
      }
    ],
    defaultParams: [3, "sum"]
  });

  addFuncDef({
    name: 'aliasByNode',
    category: categories.Special,
    params: [
      { name: "node", type: "int", options: [0,1,2,3,4,5,6,7,8,9,10,12] },
      { name: "node", type: "int", options: [0,-1,-2,-3,-4,-5,-6,-7], optional: true },
    ],
    defaultParams: [3]
  });

  addFuncDef({
    name: 'substr',
    category: categories.Special,
    params: [
      { name: "start", type: "int", options: [-6,-5,-4,-3,-2,-1,0,1,2,3,4,5,6,7,8,9,10,12] },
      { name: "stop", type: "int", options: [-6,-5,-4,-3,-2,-1,0,1,2,3,4,5,6,7,8,9,10,12] },
    ],
    defaultParams: [0, 0]
  });

  addFuncDef({
    name: 'sortByName',
    category: categories.Special
  });

  addFuncDef({
    name: 'sortByMaxima',
    category: categories.Special
  });

  addFuncDef({
    name: 'sortByMinima',
    category: categories.Special
  });

  addFuncDef({
    name: 'aliasByMetric',
    category: categories.Special,
  });

  addFuncDef({
    name: 'randomWalk',
    category: categories.Special,
    params: [ { name: "name", type: "string", } ],
    defaultParams: ['randomWalk']
  });

  addFuncDef({
    name: 'countSeries',
    category: categories.Special
  });

  addFuncDef({
    name: 'constantLine',
    category: categories.Special,
    params: [ { name: "value", type: "int", } ],
    defaultParams: [10]
  });

  addFuncDef({
    name: 'cactiStyle',
    category: categories.Special,
  });

  addFuncDef({
    name: 'keepLastValue',
    category: categories.Special,
  });

  addFuncDef({
    name: 'scale',
    category: categories.Transform,
    params: [ { name: "factor", type: "int", } ],
    defaultParams: [1]
  });

  addFuncDef({
    name: 'offset',
    category: categories.Transform,
    params: [ { name: "amount", type: "int", } ],
    defaultParams: [10]
  });

  addFuncDef({
    name: 'transformNull',
    category: categories.Transform,
    params: [ { name: "amount", type: "int", } ],
    defaultParams: [0]
  });

  addFuncDef({
    name: 'integral',
    category: categories.Transform,
  });

  addFuncDef({
    name: 'derivative',
    category: categories.Transform,
  });

  addFuncDef({
    name: 'nonNegativeDerivative',
    category: categories.Transform,
    params: [ { name: "max value or 0", type: "int", } ],
    defaultParams: [0]
  });

  addFuncDef({
    name: 'timeShift',
    category: categories.Transform,
    params: [ { name: "amount", type: "select", options: ['1h', '6h', '12h', '1d', '2d', '7d', '14d', '30d'] }],
    defaultParams: ['1d']
  });

  addFuncDef({
    name: 'summarize',
    category: categories.Transform,
    params: [ { name: "interval", type: "string" }, { name: "func", type: "select", options: ['sum', 'avg', 'min', 'max', 'last'] }],
    defaultParams: ['1h', 'sum']
  });

  addFuncDef({
    name: 'absolute',
    category: categories.Transform,
  });

  addFuncDef({
    name: 'hitcount',
    category: categories.Transform,
    params: [ { name: "interval", type: "string" }],
    defaultParams: ['10s']
  });

  addFuncDef({
    name: 'log',
    category: categories.Transform,
    params: [ { name: "base", type: "int" }],
    defaultParams: ['10']
  });

  addFuncDef({
    name: 'averageAbove',
    category: categories.Filter,
    params: [ { name: "n", type: "int", } ],
    defaultParams: [25]
  });

  addFuncDef({
    name: 'averageBelow',
    category: categories.Filter,
    params: [ { name: "n", type: "int", } ],
    defaultParams: [25]
  });

  addFuncDef({
    name: 'currentAbove',
    category: categories.Filter,
    params: [ { name: "n", type: "int", } ],
    defaultParams: [25]
  });

  addFuncDef({
    name: 'currentBelow',
    category: categories.Filter,
    params: [ { name: "n", type: "int", } ],
    defaultParams: [25]
  });

  addFuncDef({
    name: 'maximumAbove',
    category: categories.Filter,
    params: [ { name: "value", type: "int" } ],
    defaultParams: [0]
  });

  addFuncDef({
    name: 'maximumBelow',
    category: categories.Filter,
    params: [ { name: "value", type: "int" } ],
    defaultParams: [0]
  });

  addFuncDef({
    name: 'minimumAbove',
    category: categories.Filter,
    params: [ { name: "value", type: "int" } ],
    defaultParams: [0]
  });

  addFuncDef({
    name: 'limit',
    category: categories.Filter,
    params: [ { name: "n", type: "int" } ],
    defaultParams: [5]
  });

  addFuncDef({
    name: 'mostDeviant',
    category: categories.Filter,
    params: [ { name: "n", type: "int" } ],
    defaultParams: [10]
  });

  addFuncDef({
    name: "exclude",
    category: categories.Filter,
    params: [ { name: "exclude", type: 'string' } ],
    defaultParams: ['exclude']
  });

  addFuncDef({
    name: 'highestCurrent',
    category: categories.Filter,
    params: [ { name: "count", type: "int" } ],
    defaultParams: [5]
  });

  addFuncDef({
    name: 'highestMax',
    category: categories.Filter,
    params: [ { name: "count", type: "int" } ],
    defaultParams: [5]
  });

  addFuncDef({
    name: 'lowestCurrent',
    category: categories.Filter,
    params: [ { name: "count", type: "int" } ],
    defaultParams: [5]
  });

  addFuncDef({
    name: 'movingAverage',
    category: categories.Filter,
    params: [ { name: "window size", type: "int" } ],
    defaultParams: [10]
  });

  addFuncDef({
    name: 'movingMedian',
    category: categories.Filter,
    params: [ { name: "windowSize", type: "select", options: ['1min', '5min', '15min', '30min', '1hour'] } ],
    defaultParams: ['1min']
  });

  addFuncDef({
    name: 'stdev',
    category: categories.Filter,
    params: [ { name: "n", type: "int" }, { name: "tolerance", type: "int" } ],
    defaultParams: [5,0.1]
  });

  addFuncDef({
    name: 'highestAverage',
    category: categories.Filter,
    params: [ { name: "count", type: "int" } ],
    defaultParams: [5]
  });

  addFuncDef({
    name: 'lowestAverage',
    category: categories.Filter,
    params: [ { name: "count", type: "int" } ],
    defaultParams: [5]
  });

  _.each(categories, function(funcList, catName) {
    categories[catName] = _.sortBy(funcList, 'name');
  });

  function FuncInstance(funcDef) {
    this.def = funcDef;
    this.params = funcDef.defaultParams.slice(0);
    this.updateText();
  }

  FuncInstance.prototype.render = function(metricExp) {
    var str = this.def.name + '(';
    var parameters = _.map(this.params, function(value) {
      return _.isString(value) ? "'" + value + "'" : value;
    });

    if (metricExp !== undefined) {
      parameters.unshift(metricExp);
    }

    return str + parameters.join(',') + ')';
  };

  FuncInstance.prototype._hasMultipleParamsInString = function(strValue, index) {
    if (strValue.indexOf(',') === -1) {
      return false;
    }

    return this.def.params[index + 1] && this.def.params[index + 1].optional;
  };

  FuncInstance.prototype.updateParam = function(strValue, index) {
    // handle optional parameters
    // if string contains ',' and next param is optional, split and update both
    if (this._hasMultipleParamsInString(strValue, index)) {
      _.each(strValue.split(','), function(partVal, idx) {
        this.updateParam(partVal.trim(), idx);
      }, this);
      return;
    }

    if (strValue === '' && this.def.params[index].optional) {
      this.params.splice(index, 1);
    }
    else if (this.def.params[index].type === 'int') {
      this.params[index] = parseFloat(strValue, 10);
    }
    else {
      this.params[index] = strValue;
    }

    this.updateText();
  };

  FuncInstance.prototype.updateText = function () {
    if (this.params.length === 0) {
      this.text = this.def.name + '()';
      return;
    }

    var text = this.def.name + '(';
    _.each(this.def.params, function(param, index) {
      if (param.optional && this.params[index] === undefined) {
        return;
      }

      text += this.params[index] + ', ';
    }, this);
    text = text.substring(0, text.length - 2);
    text += ')';
    this.text = text;
  };

  return {
    createFuncInstance: function(funcDef) {
      if (_.isString(funcDef)) {
        if (!index[funcDef]) {
          throw { message: 'Method not found ' + name };
        }
        funcDef = index[funcDef];
      }
      return new FuncInstance(funcDef);
    },

    getFuncDef: function(name) {
      return index[name];
    },

    getCategories: function() {
      return categories;
    }
  };

});
<commit_msg>Add rangeOfSeries,sortByTotal,removeAbove*,removeBelow*<commit_after>define([
  'underscore'
],
function (_) {
  'use strict';

  var index = [];
  var categories = {
    Combine: [],
    Transform: [],
    Calculate: [],
    Filter: [],
    Special: []
  };

  function addFuncDef(funcDef) {
    funcDef.params = funcDef.params || [];
    funcDef.defaultParams = funcDef.defaultParams || [];

    if (funcDef.category) {
      funcDef.category.push(funcDef);
    }
    index[funcDef.name] = funcDef;
    index[funcDef.shortName || funcDef.name] = funcDef;
  }

  addFuncDef({
    name: 'scaleToSeconds',
    category: categories.Transform,
    params: [ { name: 'seconds', type: 'int' } ],
    defaultParams: [1],
  });

  addFuncDef({
    name: "holtWintersForecast",
    category: categories.Calculate,
  });

  addFuncDef({
    name: "holtWintersConfidenceBands",
    category: categories.Calculate,
    params: [ { name: "delta", type: 'int' } ],
    defaultParams: [3]
  });

  addFuncDef({
    name: "holtWintersAberration",
    category: categories.Calculate,
    params: [ { name: "delta", type: 'int' } ],
    defaultParams: [3]
  });

  addFuncDef({
    name: "nPercentile",
    category: categories.Calculate,
    params: [ { name: "Nth percentile", type: 'int' } ],
    defaultParams: [95]
  });

  addFuncDef({
    name: 'sumSeries',
    shortName: 'sum',
    category: categories.Combine,
  });

  addFuncDef({
    name: 'averageSeries',
    shortName: 'avg',
    category: categories.Combine,
  });

  addFuncDef({
    name: 'rangeOfSeries',
    category: categories.Combine
  });

  addFuncDef({
    name: 'percentileOfSeries',
    category: categories.Combine,
    params: [ { name: "n", type: "int" }, { name: "interpolate", type: "select", options: ["true", "false"] } ],
    defaultParams: [95, "false"]
  });

  addFuncDef({
    name: 'sumSeriesWithWildcards',
    category: categories.Combine,
    params: [ { name: "node", type: "int" } ],
    defaultParams: [3]
  });

  addFuncDef({
    name: 'maxSeries',
    shortName: 'max',
    category: categories.Combine,
  });

  addFuncDef({
    name: 'minSeries',
    shortName: 'min',
    category: categories.Combine,
  });

  addFuncDef({
    name: 'averageSeriesWithWildcards',
    category: categories.Combine,
    params: [ { name: "node", type: "int" } ],
    defaultParams: [3]
  });

  addFuncDef({
    name: "alias",
    category: categories.Special,
    params: [ { name: "alias", type: 'string' } ],
    defaultParams: ['alias']
  });

  addFuncDef({
    name: "aliasSub",
    category: categories.Special,
    params: [ { name: "search", type: 'string' }, { name: "replace", type: 'string' } ],
    defaultParams: ['', '']
  });

  addFuncDef({
    name: "stacked",
    category: categories.Special,
    params: [ { name: "stack", type: 'string' } ],
    defaultParams: ['stacked']
  });

  addFuncDef({
    name: "consolidateBy",
    category: categories.Special,
    params: [
      {
        name: 'function',
        type: 'string',
        options: ['sum', 'average', 'min', 'max']
      }
    ],
    defaultParams: ['max']
  });

  addFuncDef({
    name: "groupByNode",
    category: categories.Special,
    params: [
      {
        name: "node",
        type: "int",
        options: [1,2,3,4,5,6,7,8,9,10,12]
      },
      {
        name: "function",
        type: "string",
        options: ['sum', 'avg']
      }
    ],
    defaultParams: [3, "sum"]
  });

  addFuncDef({
    name: 'aliasByNode',
    category: categories.Special,
    params: [
      { name: "node", type: "int", options: [0,1,2,3,4,5,6,7,8,9,10,12] },
      { name: "node", type: "int", options: [0,-1,-2,-3,-4,-5,-6,-7], optional: true },
    ],
    defaultParams: [3]
  });

  addFuncDef({
    name: 'substr',
    category: categories.Special,
    params: [
      { name: "start", type: "int", options: [-6,-5,-4,-3,-2,-1,0,1,2,3,4,5,6,7,8,9,10,12] },
      { name: "stop", type: "int", options: [-6,-5,-4,-3,-2,-1,0,1,2,3,4,5,6,7,8,9,10,12] },
    ],
    defaultParams: [0, 0]
  });

  addFuncDef({
    name: 'sortByName',
    category: categories.Special
  });

  addFuncDef({
    name: 'sortByMaxima',
    category: categories.Special
  });

  addFuncDef({
    name: 'sortByMinima',
    category: categories.Special
  });

  addFuncDef({
    name: 'sortByTotal',
    category: categories.Special
  });

  addFuncDef({
    name: 'aliasByMetric',
    category: categories.Special,
  });

  addFuncDef({
    name: 'randomWalk',
    category: categories.Special,
    params: [ { name: "name", type: "string", } ],
    defaultParams: ['randomWalk']
  });

  addFuncDef({
    name: 'countSeries',
    category: categories.Special
  });

  addFuncDef({
    name: 'constantLine',
    category: categories.Special,
    params: [ { name: "value", type: "int", } ],
    defaultParams: [10]
  });

  addFuncDef({
    name: 'cactiStyle',
    category: categories.Special,
  });

  addFuncDef({
    name: 'keepLastValue',
    category: categories.Special,
  });

  addFuncDef({
    name: 'scale',
    category: categories.Transform,
    params: [ { name: "factor", type: "int", } ],
    defaultParams: [1]
  });

  addFuncDef({
    name: 'offset',
    category: categories.Transform,
    params: [ { name: "amount", type: "int", } ],
    defaultParams: [10]
  });

  addFuncDef({
    name: 'transformNull',
    category: categories.Transform,
    params: [ { name: "amount", type: "int", } ],
    defaultParams: [0]
  });

  addFuncDef({
    name: 'integral',
    category: categories.Transform,
  });

  addFuncDef({
    name: 'derivative',
    category: categories.Transform,
  });

  addFuncDef({
    name: 'nonNegativeDerivative',
    category: categories.Transform,
    params: [ { name: "max value or 0", type: "int", } ],
    defaultParams: [0]
  });

  addFuncDef({
    name: 'timeShift',
    category: categories.Transform,
    params: [ { name: "amount", type: "select", options: ['1h', '6h', '12h', '1d', '2d', '7d', '14d', '30d'] }],
    defaultParams: ['1d']
  });

  addFuncDef({
    name: 'summarize',
    category: categories.Transform,
    params: [ { name: "interval", type: "string" }, { name: "func", type: "select", options: ['sum', 'avg', 'min', 'max', 'last'] }],
    defaultParams: ['1h', 'sum']
  });

  addFuncDef({
    name: 'absolute',
    category: categories.Transform,
  });

  addFuncDef({
    name: 'hitcount',
    category: categories.Transform,
    params: [ { name: "interval", type: "string" }],
    defaultParams: ['10s']
  });

  addFuncDef({
    name: 'log',
    category: categories.Transform,
    params: [ { name: "base", type: "int" }],
    defaultParams: ['10']
  });

  addFuncDef({
    name: 'averageAbove',
    category: categories.Filter,
    params: [ { name: "n", type: "int", } ],
    defaultParams: [25]
  });

  addFuncDef({
    name: 'averageBelow',
    category: categories.Filter,
    params: [ { name: "n", type: "int", } ],
    defaultParams: [25]
  });

  addFuncDef({
    name: 'currentAbove',
    category: categories.Filter,
    params: [ { name: "n", type: "int", } ],
    defaultParams: [25]
  });

  addFuncDef({
    name: 'currentBelow',
    category: categories.Filter,
    params: [ { name: "n", type: "int", } ],
    defaultParams: [25]
  });

  addFuncDef({
    name: 'maximumAbove',
    category: categories.Filter,
    params: [ { name: "value", type: "int" } ],
    defaultParams: [0]
  });

  addFuncDef({
    name: 'maximumBelow',
    category: categories.Filter,
    params: [ { name: "value", type: "int" } ],
    defaultParams: [0]
  });

  addFuncDef({
    name: 'minimumAbove',
    category: categories.Filter,
    params: [ { name: "value", type: "int" } ],
    defaultParams: [0]
  });

  addFuncDef({
    name: 'limit',
    category: categories.Filter,
    params: [ { name: "n", type: "int" } ],
    defaultParams: [5]
  });

  addFuncDef({
    name: 'mostDeviant',
    category: categories.Filter,
    params: [ { name: "n", type: "int" } ],
    defaultParams: [10]
  });

  addFuncDef({
    name: "exclude",
    category: categories.Filter,
    params: [ { name: "exclude", type: 'string' } ],
    defaultParams: ['exclude']
  });

  addFuncDef({
    name: 'highestCurrent',
    category: categories.Filter,
    params: [ { name: "count", type: "int" } ],
    defaultParams: [5]
  });

  addFuncDef({
    name: 'highestMax',
    category: categories.Filter,
    params: [ { name: "count", type: "int" } ],
    defaultParams: [5]
  });

  addFuncDef({
    name: 'lowestCurrent',
    category: categories.Filter,
    params: [ { name: "count", type: "int" } ],
    defaultParams: [5]
  });

  addFuncDef({
    name: 'movingAverage',
    category: categories.Filter,
    params: [ { name: "window size", type: "int" } ],
    defaultParams: [10]
  });

  addFuncDef({
    name: 'movingMedian',
    category: categories.Filter,
    params: [ { name: "windowSize", type: "select", options: ['1min', '5min', '15min', '30min', '1hour'] } ],
    defaultParams: ['1min']
  });

  addFuncDef({
    name: 'stdev',
    category: categories.Filter,
    params: [ { name: "n", type: "int" }, { name: "tolerance", type: "int" } ],
    defaultParams: [5,0.1]
  });

  addFuncDef({
    name: 'highestAverage',
    category: categories.Filter,
    params: [ { name: "count", type: "int" } ],
    defaultParams: [5]
  });

  addFuncDef({
    name: 'lowestAverage',
    category: categories.Filter,
    params: [ { name: "count", type: "int" } ],
    defaultParams: [5]
  });

  addFuncDef({
    name: 'removeAbovePercentile',
    category: categories.Filter,
    params: [ { name: "n", type: "int" } ],
    defaultParams: [5]
  });

  addFuncDef({
    name: 'removeAboveValue',
    category: categories.Filter,
    params: [ { name: "n", type: "int" } ],
    defaultParams: [5]
  });

  addFuncDef({
    name: 'removeBelowPercentile',
    category: categories.Filter,
    params: [ { name: "n", type: "int" } ],
    defaultParams: [5]
  });

  addFuncDef({
    name: 'removeBelowValue',
    category: categories.Filter,
    params: [ { name: "n", type: "int" } ],
    defaultParams: [5]
  });

  _.each(categories, function(funcList, catName) {
    categories[catName] = _.sortBy(funcList, 'name');
  });

  function FuncInstance(funcDef) {
    this.def = funcDef;
    this.params = funcDef.defaultParams.slice(0);
    this.updateText();
  }

  FuncInstance.prototype.render = function(metricExp) {
    var str = this.def.name + '(';
    var parameters = _.map(this.params, function(value) {
      return _.isString(value) ? "'" + value + "'" : value;
    });

    if (metricExp !== undefined) {
      parameters.unshift(metricExp);
    }

    return str + parameters.join(',') + ')';
  };

  FuncInstance.prototype._hasMultipleParamsInString = function(strValue, index) {
    if (strValue.indexOf(',') === -1) {
      return false;
    }

    return this.def.params[index + 1] && this.def.params[index + 1].optional;
  };

  FuncInstance.prototype.updateParam = function(strValue, index) {
    // handle optional parameters
    // if string contains ',' and next param is optional, split and update both
    if (this._hasMultipleParamsInString(strValue, index)) {
      _.each(strValue.split(','), function(partVal, idx) {
        this.updateParam(partVal.trim(), idx);
      }, this);
      return;
    }

    if (strValue === '' && this.def.params[index].optional) {
      this.params.splice(index, 1);
    }
    else if (this.def.params[index].type === 'int') {
      this.params[index] = parseFloat(strValue, 10);
    }
    else {
      this.params[index] = strValue;
    }

    this.updateText();
  };

  FuncInstance.prototype.updateText = function () {
    if (this.params.length === 0) {
      this.text = this.def.name + '()';
      return;
    }

    var text = this.def.name + '(';
    _.each(this.def.params, function(param, index) {
      if (param.optional && this.params[index] === undefined) {
        return;
      }

      text += this.params[index] + ', ';
    }, this);
    text = text.substring(0, text.length - 2);
    text += ')';
    this.text = text;
  };

  return {
    createFuncInstance: function(funcDef) {
      if (_.isString(funcDef)) {
        if (!index[funcDef]) {
          throw { message: 'Method not found ' + name };
        }
        funcDef = index[funcDef];
      }
      return new FuncInstance(funcDef);
    },

    getFuncDef: function(name) {
      return index[name];
    },

    getCategories: function() {
      return categories;
    }
  };

});
<|endoftext|>


<commit_before>/*
 * Inspired and mostly coming from MartijnBoland's MopidyService.js
 * https://github.com/martijnboland/moped/blob/master/src/app/services/mopidyservice.js
 */
'use strict';

angular.module('mopify.services.mopidy', [
    "mopify.services.settings",
    "mopify.services.queuemanager",
    'llNotifier'
])

.factory("mopidyservice", function($q, $rootScope, $cacheFactory, $location, Settings, notifier, QueueManager){
	// Create consolelog object for Mopidy to log it's logs on
    var consoleError = console.error.bind(console);

    /*
     * Wrap calls to the Mopidy API and convert the promise to Angular $q's promise.
     *
     * @param String functionNameToWrap
     * @param Object thisObj
     */
	function wrapMopidyFunc(functionNameToWrap, thisObj) {
		return function() {
			var deferred = $q.defer();
			var args = Array.prototype.slice.call(arguments);
			var self = thisObj || this;

			$rootScope.$broadcast('mopify:callingmopidy', { name: functionNameToWrap, args: args });

			if (self.isConnected) {
				executeFunctionByName(functionNameToWrap, self, args).then(function(data) {
					deferred.resolve(data);
					$rootScope.$broadcast('mopify:calledmopidy', { name: functionNameToWrap, args: args });
				}, function(err) {
					deferred.reject(err);
					$rootScope.$broadcast('mopify:errormopidy', { name: functionNameToWrap, args: args, err: err });
				});
			}
			else{
				executeFunctionByName(functionNameToWrap, self, args).then(function(data) {
					deferred.resolve(data);
					$rootScope.$broadcast('mopify:calledmopidy', { name: functionNameToWrap, args: args });
				}, function(err) {
					deferred.reject(err);
					$rootScope.$broadcast('mopify:errormopidy', { name: functionNameToWrap, args: args, err: err });
				});
			}
			return deferred.promise;
		};
	}

	/*
     * Execute the given function
     *
     * @param String functionName
     * @param Object thisObj
	 * @param Array args
     */
	function executeFunctionByName(functionName, context, args) {
		var namespaces = functionName.split(".");
		var func = namespaces.pop();

		for(var i = 0; i < namespaces.length; i++) {
			context = context[namespaces[i]];
		}

		return context[func].apply(context, args);
	}

	return {
		mopidy: {},
		isConnected: false,
		currentTlTracks: [],

		/*
		 * Method to start the Mopidy conneciton
		 */
		start: function(){
            var self = this;

			// Emit message that we're starting the Mopidy service
			$rootScope.$broadcast("mopify:startingmopidy");

            // Get mopidy ip and port from settigns
            var mopidyip = Settings.get("mopidyip", $location.host());
            var mopidyport = Settings.get("mopidyport", "6680");

			// Initialize mopidy
            try{
    			this.mopidy = new Mopidy({
    				webSocketUrl: "ws://" + mopidyip + ":" + mopidyport + "/mopidy/ws", // FOR DEVELOPING
    				callingConvention: 'by-position-or-by-name'
    			});
            }
			catch(e){
                notifier.notify({type: "custom", template: "Connecting with Mopidy failed with the following error message:
" + e, delay: 15000});

                // Try to connect without a given url
                this.mopidy = new Mopidy({
                    callingConvention: 'by-position-or-by-name'
                });
            }

			// Convert Mopidy events to Angular events
			this.mopidy.on(function(ev, args) {
				$rootScope.$broadcast('mopidy:' + ev, args);
				if (ev === 'state:online') {
					self.isConnected = true;
				}
				if (ev === 'state:offline') {
					self.isConnected = false;
				}
			});

			$rootScope.$broadcast('mopify:mopidystarted');
		},

		/*
		 * Close the connection with mopidy
		 */
		stop: function() {
			$rootScope.$broadcast('mopify:stoppingmopidy');

			this.mopidy.close();
			this.mopidy.off();
			this.mopidy = null;

			$rootScope.$broadcast('mopify:stoppedmopidy');
		},

		/*
	 	 * Restart mopidy
		 */
		restart: function() {
			this.stop();
			this.start();
		},

        getPlaylists: function() {
            return wrapMopidyFunc("mopidy.playlists.getPlaylists", this)();
        },

        getPlaylist: function(uri) {
            return wrapMopidyFunc("mopidy.playlists.lookup", this)({ uri: uri });
        },

        getTrack: function(uri) {
            return wrapMopidyFunc("mopidy.library.lookup", this)({ uri: uri });
        },

        getAlbum: function(uri) {
            return wrapMopidyFunc("mopidy.library.lookup", this)({ uri: uri });
        },

        getArtist: function(uri) {
            return wrapMopidyFunc("mopidy.library.lookup", this)({ uri: uri });
        },

        search: function(query) {
            return wrapMopidyFunc("mopidy.library.search", this)({ any : [ query ] });
        },

        getCurrentTrack: function() {
            return wrapMopidyFunc("mopidy.playback.getCurrentTrack", this)();
        },

        getTimePosition: function() {
            return wrapMopidyFunc("mopidy.playback.getTimePosition", this)();
        },

        seek: function(timePosition) {
            return wrapMopidyFunc("mopidy.playback.seek", this)({ time_position: timePosition });
        },

        getVolume: function() {
            return wrapMopidyFunc("mopidy.mixer.getVolume", this)();
        },

        setVolume: function(volume) {
            return wrapMopidyFunc("mopidy.mixer.setVolume", this)({ volume: volume });
        },

        getState: function() {
            return wrapMopidyFunc("mopidy.playback.getState", this)();
        },

        lookup: function(uris){
            if(typeof(uris) === "string")
                uris = [uris];

            return wrapMopidyFunc("mopidy.library.lookup", this)({ uris: uris });
        },

        playTrack: function(track, surroundingTracks) {
            var self = this;
            var deferred = $q.defer();

            if(surroundingTracks === undefined)
                surroundingTracks = [];

            // Get the current queue
            QueueManager.all().then(function(queuedata){

                // Clear full list
                self.mopidy.tracklist.clear().then(function(){
                    var uris = [track.uri];

                    // Collect all uris from the queue, track to play and follow up tracks
                    _.forEach(queuedata.queue, function(tl){
                        uris.push(tl.track.uri);
                    });

                    if(surroundingTracks.length > 0){
                        var trackindex = 0;

                        // find the selected track's index
                        _.find(surroundingTracks, function(surtrack, index){
                            if(track.uri === surtrack.uri)
                                trackindex = index + 1;
                        });

                        // Get all uris from the tracks after the selected track
                        var trackstoadd = surroundingTracks.slice(trackindex, surroundingTracks.length);
                        _.forEach(trackstoadd, function(tta){
                            uris.push(tta.uri);
                        });
                    }

                    // Add the selected track as next
                    self.mopidy.tracklist.add({ uris: uris }).then(function(tltracks){
                        var start = queuedata.queue.length + 1;
                        var end = tltracks.length;

                        // Send data to QueueManager
                        var queuetracks = tltracks.slice(0, start);
                        var playlisttracks = tltracks.slice(start, end);

                        QueueManager.replace({
                            playlist: playlisttracks,
                            queue: queuetracks
                        }).then(function(){

                            // Start playing the track
                            self.mopidy.playback.play({ tl_track: tltracks[0] }).then(function(track){
                                deferred.resolve(track);
                            });
                        });
                    });
                });
            });

            return deferred.promise;
        },

        playTrackAtIndex: function(index){
            var self = this;

            self.mopidy.tracklist.getTlTracks().then(function(tlTracks) {
                index = (index < tlTracks.length) ? index : tlTracks.length - 1;
                var tlTrackToPlay = tlTracks[index];

                self.mopidy.playback.play({ tl_track: tlTrackToPlay }).then(function() {
                    $rootScope.$broadcast("mopidy:event:trackPlaybackStarted", tlTrackToPlay);
                });
            }, consoleError);
        },

        clearTracklist: function(){
            var deferred = $q.defer();

            this.mopidy.tracklist.clear().then(function(){
                QueueManager.replace({
                    queue: [],
                    playlist: []
                }).then(function(){
                    deferred.resolve();
                });
            });

            return deferred.promise;
        },

        addToTracklist: function(obj){
            // Add the tracks at the end of the queue
            obj.at_position = QueueManager.queue.length + 1;

            return wrapMopidyFunc("mopidy.tracklist.add", this)(obj).then(function(tltracks){
               // Sync with queuemanager
                QueueManager.add(tltracks);
            });
        },

        getTracklist: function(){
            return wrapMopidyFunc("mopidy.tracklist.getTlTracks", this)();
        },

        playNext: function(uris){
            var deferred = $q.defer();

            if(typeof uris === "string")
                uris = [uris];

            this.mopidy.tracklist.add({uris: uris, at_position: 1}).then(function(response){
                // Add to QueueManager
                QueueManager.next(response).then(function(){
                    // Resolve
                    deferred.resolve(response);

                    // Broadcast change
                    $rootScope.$broadcast("mopidy:event:tracklistChanged");
                });
            });

            return deferred.promise;
        },

        play: function(tltrack) {
            if(tltrack !== undefined){
                return wrapMopidyFunc("mopidy.playback.play", this)({ tl_track: tltrack });
            }
            else{
                return wrapMopidyFunc("mopidy.playback.play", this)();
            }
        },

        pause: function() {
            return wrapMopidyFunc("mopidy.playback.pause", this)();
        },

        previous: function() {
            return wrapMopidyFunc("mopidy.playback.previous", this)();
        },

        next: function() {
            var self = this;
            var deferred = $q.defer();

            // Start playing when the next track gets called and te state doesn't equal play
            self.mopidy.playback.getState().then(function(state){
                if(state === 'playing'){
                    self.mopidy.playback.next().then(function(response){
                        deferred.resolve(response);
                    });
                }
                else{
                    self.mopidy.playback.play().then(function(){
                        self.mopidy.playback.next().then(function(response){
                            deferred.resolve(response);
                        });
                    });
                }
            });

            return deferred.promise;
        },

        setConsume: function(){
            return wrapMopidyFunc("mopidy.tracklist.setConsume", this)([ true ]);
        },

        getRandom: function () {
            return QueueManager.getShuffle();
        },

        setRandom: function (setShuffle) {
            var self = this;
            var deferred = $q.defer();

            // Always set mopidy's random mode to false
            self.mopidy.tracklist.setRandom([false]);

            if(setShuffle === false){
                // Disable shuffle and reset the tracklist to its original state
                QueueManager.setShuffle(false).then(function(data){

                    // Get current tracklist
                    self.mopidy.tracklist.getTlTracks().then(function(tltracks){
                        // Get all tltracks ids except for the currently playing one (which is the first)
                        var tlids = _.pluck(tltracks.slice(1), "tlid");

                        // Remove the selected tracks
                        self.mopidy.tracklist.remove({ criteria: {tlid: tlids }}).then(function(){
                            var trackstoadd = data.queue.concat(data.playlist);

                            // Get the uris
                            var uris = _.map(trackstoadd, function(tltrack){
                                return tltrack.track.uri;
                            });

                            // Add the uris to the tracklist
                            self.mopidy.tracklist.add({ uris: uris }).then(function(tltracks){
                                // Since all the tracks have a new tlid we have to
                                // replace all values in the queuemanager
                                var queue = tltracks.slice(0, QueueManager.queue.length);
                                var playlist = tltracks.slice(QueueManager.queue.length);

                                QueueManager.replace({
                                    queue: queue,
                                    playlist: playlist
                                });

                                deferred.resolve(tltracks);
                            });
                        });
                    });
                });
            }
            else{
                // Shuffle
                // Get the track data from the queuemanager
                QueueManager.all().then(function(response){
                    var start = response.queue.length + 1;
                    var end = response.playlist.length + 1;

                    if(end >= start){
                        // Send shuffle to mopidy
                        self.mopidy.tracklist.shuffle({ start: start, end: end }).then(function(resp){

                            // Get new tracklist and send the shuffle part to the QueueManager
                            self.mopidy.tracklist.getTlTracks().then(function(response){
                                // Get tltracks and send to the queuemanager
                                var tltracks = response.slice(start);
                                QueueManager.setShuffle(true, tltracks);

                                deferred.resolve(tltracks);
                            });
                        });
                    }
                    else{
                        deferred.reject();
                    }

                });

            }

            return deferred.promise;
        },

        getRepeat: function () {
            return wrapMopidyFunc("mopidy.tracklist.getRepeat", this)();
        },

        setRepeat: function (isRepeat) {
            return wrapMopidyFunc("mopidy.tracklist.setRepeat", this)([ isRepeat ]);
        },

        removeFromTracklist: function(dict){
            return wrapMopidyFunc("mopidy.tracklist.remove", this)(dict).then(function(tltracks){
                var tlids = _.pluck(tltracks, 'tlid');
                QueueManager.remove(tlids);
            });
        }

	};
});<commit_msg>Shuffle new playlists when random is enabled<commit_after>/*
 * Inspired and mostly coming from MartijnBoland's MopidyService.js
 * https://github.com/martijnboland/moped/blob/master/src/app/services/mopidyservice.js
 */
'use strict';

angular.module('mopify.services.mopidy', [
    "mopify.services.settings",
    "mopify.services.queuemanager",
    'llNotifier'
])

.factory("mopidyservice", function($q, $rootScope, $cacheFactory, $location, Settings, notifier, QueueManager){
	// Create consolelog object for Mopidy to log it's logs on
    var consoleError = console.error.bind(console);

    /*
     * Wrap calls to the Mopidy API and convert the promise to Angular $q's promise.
     *
     * @param String functionNameToWrap
     * @param Object thisObj
     */
	function wrapMopidyFunc(functionNameToWrap, thisObj) {
		return function() {
			var deferred = $q.defer();
			var args = Array.prototype.slice.call(arguments);
			var self = thisObj || this;

			$rootScope.$broadcast('mopify:callingmopidy', { name: functionNameToWrap, args: args });

			if (self.isConnected) {
				executeFunctionByName(functionNameToWrap, self, args).then(function(data) {
					deferred.resolve(data);
					$rootScope.$broadcast('mopify:calledmopidy', { name: functionNameToWrap, args: args });
				}, function(err) {
					deferred.reject(err);
					$rootScope.$broadcast('mopify:errormopidy', { name: functionNameToWrap, args: args, err: err });
				});
			}
			else{
				executeFunctionByName(functionNameToWrap, self, args).then(function(data) {
					deferred.resolve(data);
					$rootScope.$broadcast('mopify:calledmopidy', { name: functionNameToWrap, args: args });
				}, function(err) {
					deferred.reject(err);
					$rootScope.$broadcast('mopify:errormopidy', { name: functionNameToWrap, args: args, err: err });
				});
			}
			return deferred.promise;
		};
	}

	/*
     * Execute the given function
     *
     * @param String functionName
     * @param Object thisObj
	 * @param Array args
     */
	function executeFunctionByName(functionName, context, args) {
		var namespaces = functionName.split(".");
		var func = namespaces.pop();

		for(var i = 0; i < namespaces.length; i++) {
			context = context[namespaces[i]];
		}

		return context[func].apply(context, args);
	}

	return {
		mopidy: {},
		isConnected: false,
		currentTlTracks: [],

		/*
		 * Method to start the Mopidy conneciton
		 */
		start: function(){
            var self = this;

			// Emit message that we're starting the Mopidy service
			$rootScope.$broadcast("mopify:startingmopidy");

            // Get mopidy ip and port from settigns
            var mopidyip = Settings.get("mopidyip", $location.host());
            var mopidyport = Settings.get("mopidyport", "6680");

			// Initialize mopidy
            try{
    			this.mopidy = new Mopidy({
    				webSocketUrl: "ws://" + mopidyip + ":" + mopidyport + "/mopidy/ws", // FOR DEVELOPING
    				callingConvention: 'by-position-or-by-name'
    			});
            }
			catch(e){
                notifier.notify({type: "custom", template: "Connecting with Mopidy failed with the following error message:
" + e, delay: 15000});

                // Try to connect without a given url
                this.mopidy = new Mopidy({
                    callingConvention: 'by-position-or-by-name'
                });
            }

			// Convert Mopidy events to Angular events
			this.mopidy.on(function(ev, args) {
				$rootScope.$broadcast('mopidy:' + ev, args);
				if (ev === 'state:online') {
					self.isConnected = true;
				}
				if (ev === 'state:offline') {
					self.isConnected = false;
				}
			});

			$rootScope.$broadcast('mopify:mopidystarted');
		},

		/*
		 * Close the connection with mopidy
		 */
		stop: function() {
			$rootScope.$broadcast('mopify:stoppingmopidy');

			this.mopidy.close();
			this.mopidy.off();
			this.mopidy = null;

			$rootScope.$broadcast('mopify:stoppedmopidy');
		},

		/*
	 	 * Restart mopidy
		 */
		restart: function() {
			this.stop();
			this.start();
		},

        getPlaylists: function() {
            return wrapMopidyFunc("mopidy.playlists.getPlaylists", this)();
        },

        getPlaylist: function(uri) {
            return wrapMopidyFunc("mopidy.playlists.lookup", this)({ uri: uri });
        },

        getTrack: function(uri) {
            return wrapMopidyFunc("mopidy.library.lookup", this)({ uri: uri });
        },

        getAlbum: function(uri) {
            return wrapMopidyFunc("mopidy.library.lookup", this)({ uri: uri });
        },

        getArtist: function(uri) {
            return wrapMopidyFunc("mopidy.library.lookup", this)({ uri: uri });
        },

        search: function(query) {
            return wrapMopidyFunc("mopidy.library.search", this)({ any : [ query ] });
        },

        getCurrentTrack: function() {
            return wrapMopidyFunc("mopidy.playback.getCurrentTrack", this)();
        },

        getTimePosition: function() {
            return wrapMopidyFunc("mopidy.playback.getTimePosition", this)();
        },

        seek: function(timePosition) {
            return wrapMopidyFunc("mopidy.playback.seek", this)({ time_position: timePosition });
        },

        getVolume: function() {
            return wrapMopidyFunc("mopidy.mixer.getVolume", this)();
        },

        setVolume: function(volume) {
            return wrapMopidyFunc("mopidy.mixer.setVolume", this)({ volume: volume });
        },

        getState: function() {
            return wrapMopidyFunc("mopidy.playback.getState", this)();
        },

        lookup: function(uris){
            if(typeof(uris) === "string")
                uris = [uris];

            return wrapMopidyFunc("mopidy.library.lookup", this)({ uris: uris });
        },

        playTrack: function(track, surroundingTracks) {
            var self = this;
            var deferred = $q.defer();

            if(surroundingTracks === undefined)
                surroundingTracks = [];

            // Get the current queue
            QueueManager.all().then(function(queuedata){

                // Clear full list
                self.mopidy.tracklist.clear().then(function(){
                    var uris = [track.uri];

                    // Collect all uris from the queue, track to play and follow up tracks
                    _.forEach(queuedata.queue, function(tl){
                        uris.push(tl.track.uri);
                    });

                    if(surroundingTracks.length > 0){
                        var trackindex = 0;

                        // find the selected track's index
                        _.find(surroundingTracks, function(surtrack, index){
                            if(track.uri === surtrack.uri)
                                trackindex = index + 1;
                        });

                        // Get all uris from the tracks after the selected track
                        var trackstoadd = surroundingTracks.splice(trackindex, surroundingTracks.length);
                        var trackstoskip = surroundingTracks;

                        _.forEach(trackstoadd, function(tta){
                            uris.push(tta.uri);
                        });

                        // Get tracklist IDs from trackstoskip and parse to queuemanager
                        if(trackstoskip.length > 1)
                            QueueManager.remove(_.pluck(trackstoskip, "tlid"));
                    }

                    // Add the selected track as next
                    self.mopidy.tracklist.add({ uris: uris }).then(function(tltracks){
                        var start = queuedata.queue.length + 1;
                        var end = tltracks.length;

                        // Send data to QueueManager
                        var queuetracks = tltracks.slice(0, start);
                        var playlisttracks = tltracks.slice(start, end);

                        QueueManager.replace({
                            playlist: playlisttracks,
                            queue: queuetracks
                        }).then(function(){

                            // Start playing the track
                            self.mopidy.playback.play({ tl_track: tltracks[0] }).then(function(track){

                                QueueManager.getShuffle().then(function(shuffle){

                                    if(shuffle){
                                        self.setRandom(true).then(function(){
                                            deferred.resolve(track);
                                        });
                                    }
                                    else{
                                        deferred.resolve(track);
                                    }
                                });

                            });
                        });
                    });
                });
            });

            return deferred.promise;
        },

        playTrackAtIndex: function(index){
            var self = this;

            self.mopidy.tracklist.getTlTracks().then(function(tlTracks) {
                index = (index < tlTracks.length) ? index : tlTracks.length - 1;
                var tlTrackToPlay = tlTracks[index];

                self.mopidy.playback.play({ tl_track: tlTrackToPlay }).then(function() {
                    $rootScope.$broadcast("mopidy:event:trackPlaybackStarted", tlTrackToPlay);
                });
            }, consoleError);
        },

        clearTracklist: function(){
            var deferred = $q.defer();

            this.mopidy.tracklist.clear().then(function(){
                QueueManager.replace({
                    queue: [],
                    playlist: []
                }).then(function(){
                    deferred.resolve();
                });
            });

            return deferred.promise;
        },

        addToTracklist: function(obj){
            // Add the tracks at the end of the queue
            obj.at_position = QueueManager.queue.length + 1;

            return wrapMopidyFunc("mopidy.tracklist.add", this)(obj).then(function(tltracks){
               // Sync with queuemanager
                QueueManager.add(tltracks);
            });
        },

        getTracklist: function(){
            return wrapMopidyFunc("mopidy.tracklist.getTlTracks", this)();
        },

        playNext: function(uris){
            var deferred = $q.defer();

            if(typeof uris === "string")
                uris = [uris];

            this.mopidy.tracklist.add({uris: uris, at_position: 1}).then(function(response){
                // Add to QueueManager
                QueueManager.next(response).then(function(){
                    // Resolve
                    deferred.resolve(response);

                    // Broadcast change
                    $rootScope.$broadcast("mopidy:event:tracklistChanged");
                });
            });

            return deferred.promise;
        },

        play: function(tltrack) {
            if(tltrack !== undefined){
                return wrapMopidyFunc("mopidy.playback.play", this)({ tl_track: tltrack });
            }
            else{
                return wrapMopidyFunc("mopidy.playback.play", this)();
            }
        },

        pause: function() {
            return wrapMopidyFunc("mopidy.playback.pause", this)();
        },

        previous: function() {
            return wrapMopidyFunc("mopidy.playback.previous", this)();
        },

        next: function() {
            var self = this;
            var deferred = $q.defer();

            // Start playing when the next track gets called and te state doesn't equal play
            self.mopidy.playback.getState().then(function(state){
                if(state === 'playing'){
                    self.mopidy.playback.next().then(function(response){
                        deferred.resolve(response);
                    });
                }
                else{
                    self.mopidy.playback.play().then(function(){
                        self.mopidy.playback.next().then(function(response){
                            deferred.resolve(response);
                        });
                    });
                }
            });

            return deferred.promise;
        },

        setConsume: function(){
            return wrapMopidyFunc("mopidy.tracklist.setConsume", this)([ true ]);
        },

        getRandom: function () {
            return QueueManager.getShuffle();
        },

        setRandom: function (setShuffle) {
            var self = this;
            var deferred = $q.defer();

            // Always set mopidy's random mode to false
            self.mopidy.tracklist.setRandom([false]);

            if(setShuffle === false){
                // Disable shuffle and reset the tracklist to its original state
                QueueManager.setShuffle(false).then(function(data){

                    // Get current tracklist
                    self.mopidy.tracklist.getTlTracks().then(function(tltracks){
                        // Get all tltracks ids except for the currently playing one (which is the first)
                        var tlids = _.pluck(tltracks.slice(1), "tlid");

                        // Remove the selected tracks
                        self.mopidy.tracklist.remove({ criteria: {tlid: tlids }}).then(function(){
                            var trackstoadd = data.queue.concat(data.playlist);

                            // Get the uris
                            var uris = _.map(trackstoadd, function(tltrack){
                                return tltrack.track.uri;
                            });

                            // Add the uris to the tracklist
                            self.mopidy.tracklist.add({ uris: uris }).then(function(tltracks){
                                // Since all the tracks have a new tlid we have to
                                // replace all values in the queuemanager
                                var queue = tltracks.slice(0, QueueManager.queue.length);
                                var playlist = tltracks.slice(QueueManager.queue.length);

                                QueueManager.replace({
                                    queue: queue,
                                    playlist: playlist
                                });

                                deferred.resolve(tltracks);
                            });
                        });
                    });
                });
            }
            else{
                // Shuffle
                // Get the track data from the queuemanager
                QueueManager.all().then(function(response){
                    var start = response.queue.length + 1;
                    var end = response.playlist.length + 1;

                    if(end >= start){
                        // Send shuffle to mopidy
                        self.mopidy.tracklist.shuffle({ start: start, end: end }).then(function(resp){

                            // Get new tracklist and send the shuffle part to the QueueManager
                            self.mopidy.tracklist.getTlTracks().then(function(response){
                                // Get tltracks and send to the queuemanager
                                var tltracks = response.slice(start);
                                QueueManager.setShuffle(true, tltracks);

                                deferred.resolve(tltracks);
                            });
                        });
                    }
                    else{
                        deferred.reject();
                    }

                });

            }

            return deferred.promise;
        },

        getRepeat: function () {
            return wrapMopidyFunc("mopidy.tracklist.getRepeat", this)();
        },

        setRepeat: function (isRepeat) {
            return wrapMopidyFunc("mopidy.tracklist.setRepeat", this)([ isRepeat ]);
        },

        removeFromTracklist: function(dict){
            return wrapMopidyFunc("mopidy.tracklist.remove", this)(dict).then(function(tltracks){
                var tlids = _.pluck(tltracks, 'tlid');
                QueueManager.remove(tlids);
            });
        }

	};
});<|endoftext|>


<commit_before>if (["Special:短页面", "Special:无跨wiki", "Special:断链页面"].indexOf(mw.config.get('wgPageName')) != -1) {
	var items = document.getElementsByClassName("special")[0].children;
	function getpage(idx, link, check) {
		$.ajax({
			type: 'GET',
			url: link,
			success: function success(data) {
				check(idx, data);
			},
			error: function error(e) {
				write(idx, "fetch fail");
			}
		});
	}
	function write(idx, text) {
		items[idx].innerHTML += text;
	}
}
if (mw.config.get('wgPageName') === "Special:短页面") {
	function getlink(idx) {
		link = "https://zh.wikipedia.org/w/index.php?title="+items[idx].children[1].href.substr(30)+"&action=raw";
		return link;
	}
	function checkshort(idx, data) {
		write(idx, "["+data.length+" 字]");
		if (data.match(/{{(d\||delete|db)/i)) {
			write(idx, "[CSD]");
		}
		if (data.match(/{{[av]fd/i)) {
			write(idx, "[VFD]");
		}
		if (data.match(/{{(substub|小小作品)/i)) {
			write(idx, "[Substub]");
		}
		if (data.match(/{{Copyvio/i)) {
			write(idx, "[Copyvio]");
		}
		if (data.match(/{{Expand/i)) {
			write(idx, "[擴充]");
		}
		if (data.match(/{{notability/i)) {
			write(idx, "[關注度]");
		}
		if (data.match(/#(重定向|REDIRECT)/i)) {
			write(idx, "[重定向]");
		}
		if (data.match(/{{disambig}}/i)) {
			write(idx, "[消歧義]");
		}
	}
	for (var i = 0; i < items.length; i++) {
		getpage(i, getlink(i), checkshort);
	}
}
if (mw.config.get('wgPageName') === "Special:无跨wiki") {
	function getlink(idx) {
		link = "https://zh.wikipedia.org/w/index.php?title="+items[idx].children[0].href.substr(30)+"&action=raw";
		return link;
	}
	function checkinterwiki(idx, data) {
		write(idx, "["+data.length+" 字]");
		if (data.match(/{{Interlanguage links/i)) {
			write(idx, "[跨語言]");
		}
		if (data.match(/{{disambig}}/i)) {
			write(idx, "[消歧義]");
		}
	}
	for (var i = 0; i < items.length; i++) {
		getpage(i, getlink(i), checkinterwiki);
	}
}
if (mw.config.get('wgPageName') === "Special:断链页面") {
	function getlink(idx) {
		link = "https://zh.wikipedia.org/w/index.php?title="+items[idx].children[0].href.substr(30)+"&action=raw";
		return link;
	}
	function checkinterwiki(idx, data) {
		write(idx, "["+data.length+" 字]");
		if (data.match(/{{Dead end/i)) {
			write(idx, "[無連結]");
		}
		if (data.match(/{{disambig}}/i)) {
			write(idx, "[消歧義]");
		}
	}
	for (var i = 0; i < items.length; i++) {
		getpage(i, getlink(i), checkinterwiki);
	}
}
<commit_msg>PP add rfd<commit_after>if (["Special:短页面", "Special:无跨wiki", "Special:断链页面"].indexOf(mw.config.get('wgPageName')) != -1) {
	var items = document.getElementsByClassName("special")[0].children;
	function getpage(idx, link, check) {
		$.ajax({
			type: 'GET',
			url: link,
			success: function success(data) {
				check(idx, data);
			},
			error: function error(e) {
				write(idx, "fetch fail");
			}
		});
	}
	function write(idx, text) {
		items[idx].innerHTML += text;
	}
}
if (mw.config.get('wgPageName') === "Special:短页面") {
	function getlink(idx) {
		link = "https://zh.wikipedia.org/w/index.php?title="+items[idx].children[1].href.substr(30)+"&action=raw";
		return link;
	}
	function checkshort(idx, data) {
		write(idx, "["+data.length+" 字]");
		if (data.match(/{{(d\||delete|db)/i)) {
			write(idx, "[CSD]");
		}
		if (data.match(/{{[avr]fd/i)) {
			write(idx, "[VFD]");
		}
		if (data.match(/{{(substub|小小作品)/i)) {
			write(idx, "[Substub]");
		}
		if (data.match(/{{Copyvio/i)) {
			write(idx, "[Copyvio]");
		}
		if (data.match(/{{Expand/i)) {
			write(idx, "[擴充]");
		}
		if (data.match(/{{notability/i)) {
			write(idx, "[關注度]");
		}
		if (data.match(/#(重定向|REDIRECT)/i)) {
			write(idx, "[重定向]");
		}
		if (data.match(/{{disambig}}/i)) {
			write(idx, "[消歧義]");
		}
	}
	for (var i = 0; i < items.length; i++) {
		getpage(i, getlink(i), checkshort);
	}
}
if (mw.config.get('wgPageName') === "Special:无跨wiki") {
	function getlink(idx) {
		link = "https://zh.wikipedia.org/w/index.php?title="+items[idx].children[0].href.substr(30)+"&action=raw";
		return link;
	}
	function checkinterwiki(idx, data) {
		write(idx, "["+data.length+" 字]");
		if (data.match(/{{Interlanguage links/i)) {
			write(idx, "[跨語言]");
		}
		if (data.match(/{{disambig}}/i)) {
			write(idx, "[消歧義]");
		}
	}
	for (var i = 0; i < items.length; i++) {
		getpage(i, getlink(i), checkinterwiki);
	}
}
if (mw.config.get('wgPageName') === "Special:断链页面") {
	function getlink(idx) {
		link = "https://zh.wikipedia.org/w/index.php?title="+items[idx].children[0].href.substr(30)+"&action=raw";
		return link;
	}
	function checkinterwiki(idx, data) {
		write(idx, "["+data.length+" 字]");
		if (data.match(/{{Dead end/i)) {
			write(idx, "[無連結]");
		}
		if (data.match(/{{disambig}}/i)) {
			write(idx, "[消歧義]");
		}
	}
	for (var i = 0; i < items.length; i++) {
		getpage(i, getlink(i), checkinterwiki);
	}
}
<|endoftext|>


<commit_before>
  window.onload = function(){

    [].forEach.call( document.getElementsByClassName("Mantis_ImagePasteOnComment_Insert"),function(x){
      x.addEventListener("click",mantis_imagepasteoncomment_insert_textarea);
    });

  } // window onload

  function mantis_imagepasteoncomment_insert_textarea(mouseevent){
    var return_flg = false;
    var text_area_obj = document.getElementsByName("bugnote_text");
    text_area_obj[0].focus();

    var selected_obj  = document.activeElement;
    if(selected_obj.selectionStart <= selected_obj.selectionEnd ){
      string_start = selected_obj.selectionStart;
      string_end   = selected_obj.selectionEnd;
    } else {
      string_start = selected_obj.selectionEnd;
      string_end   = selected_obj.selectionStart;
    } // if

    before_range  = selected_obj.value.substring(
                    0, string_start);
    range         = selected_obj.value.substring(
                    string_start , string_end);
    after_range   = selected_obj.value.substring(
                    string_end );

  /*   debug
      console.log(mouseevent);

      console.log(e);
      console.log("string_start:" + string_start);
      console.log("string_end:" + string_end);
      console.log("before_range:" + before_range);
      console.log("range:" + range);
      console.log("after_range:" + after_range);
      console.log("text_num:" + (range.match(/\r\n|\n/g)||[]).length);
      console.log(mouseevent.srcElement.id);
      console.log(image_id);
    */

    // 改行数の確認

    if( 1 < (range.match(/\r\n|\n/g)||[]).length ){
      return_flg = true;
    } // if match

    id_str   = mouseevent.srcElement.id;
    image_id = id_str.substring(id_str.indexOf("_")+1,id_str.length);

    if(image_id === "undefined"){
      before_insert_tag = '\n%[],rate100';
    }else{
      before_insert_tag = '\n%[' + image_id + '],rate100';
    }
    after_insert_tag  = '';

    if(return_flg){
      text_area_obj[0].value = before_range + before_insert_tag + '\n' + range + after_insert_tag + '\n' + after_range ;
      var CaretPosition = string_start + before_insert_tag.length + range.length + after_insert_tag.length + 1;
    }else{
      text_area_obj[0].value = before_range + before_insert_tag + range + after_insert_tag + after_range ;
      var CaretPosition = string_start + before_insert_tag.length + range.length + after_insert_tag.length ;
    } // if return_flg something

    if(!range){
      text_area_obj[0].value = before_range + before_insert_tag + '\n' + after_insert_tag + '\n' + after_range ;
      var CaretPosition = string_start + before_insert_tag.length + 1;
    } // if range noting

    text_area_obj[0].setSelectionRange( CaretPosition , CaretPosition);

  } // f insert_textarea
<commit_msg>window.onloadを指定ではなく、addEventListenerで追加に変更した。 modified:   files/Mantis_ImagePasteOnComment.js<commit_after>  window.addEventListener("load", function(){

      [].forEach.call( document.getElementsByClassName("Mantis_ImagePasteOnComment_Insert"),function(x){
        x.addEventListener("click",mantis_imagepasteoncomment_insert_textarea);
      });

    } // window onload
  , false); // window addEventListener

  function mantis_imagepasteoncomment_insert_textarea(mouseevent){
    var return_flg = false;
    var text_area_obj = document.getElementsByName("bugnote_text");
    text_area_obj[0].focus();

    var selected_obj  = document.activeElement;
    if(selected_obj.selectionStart <= selected_obj.selectionEnd ){
      string_start = selected_obj.selectionStart;
      string_end   = selected_obj.selectionEnd;
    } else {
      string_start = selected_obj.selectionEnd;
      string_end   = selected_obj.selectionStart;
    } // if

    before_range  = selected_obj.value.substring(
                    0, string_start);
    range         = selected_obj.value.substring(
                    string_start , string_end);
    after_range   = selected_obj.value.substring(
                    string_end );

  /*   debug
      console.log(mouseevent);

      console.log(e);
      console.log("string_start:" + string_start);
      console.log("string_end:" + string_end);
      console.log("before_range:" + before_range);
      console.log("range:" + range);
      console.log("after_range:" + after_range);
      console.log("text_num:" + (range.match(/\r\n|\n/g)||[]).length);
      console.log(mouseevent.srcElement.id);
      console.log(image_id);
    */

    // 改行数の確認

    if( 1 < (range.match(/\r\n|\n/g)||[]).length ){
      return_flg = true;
    } // if match

    id_str   = mouseevent.srcElement.id;
    image_id = id_str.substring(id_str.indexOf("_")+1,id_str.length);

    if(image_id === "undefined"){
      before_insert_tag = '\n%[],rate100';
    }else{
      before_insert_tag = '\n%[' + image_id + '],rate100';
    }
    after_insert_tag  = '';

    if(return_flg){
      text_area_obj[0].value = before_range + before_insert_tag + '\n' + range + after_insert_tag + '\n' + after_range ;
      var CaretPosition = string_start + before_insert_tag.length + range.length + after_insert_tag.length + 1;
    }else{
      text_area_obj[0].value = before_range + before_insert_tag + range + after_insert_tag + after_range ;
      var CaretPosition = string_start + before_insert_tag.length + range.length + after_insert_tag.length ;
    } // if return_flg something

    if(!range){
      text_area_obj[0].value = before_range + before_insert_tag + '\n' + after_insert_tag + '\n' + after_range ;
      var CaretPosition = string_start + before_insert_tag.length + 1;
    } // if range noting

    text_area_obj[0].setSelectionRange( CaretPosition , CaretPosition);

  } // f insert_textarea
<|endoftext|>