view moztree.js @ 16:5209578ac8a7

shift-clicking on a dir name opens the HG browser in that window.
author Atul Varma <varmaa@toolness.com>
date Sun, 13 Dec 2009 09:46:57 -0800
parents 75d0099bff9e
children 08af758510cf
line wrap: on
line source

const SVG_NS = "http://www.w3.org/2000/svg";

function getJSON(filename, cb) {
  var xhr = new XMLHttpRequest();
  xhr.open('GET', filename);
  xhr.overrideMimeType('text/plain');
  xhr.addEventListener("load",
                       function() { cb(JSON.parse(xhr.responseText)); },
                       false);
  xhr.send(null);
}

function SVGElement(name, attribs, options) {
  var element = document.createElementNS(SVG_NS, name);
  for (name in attribs)
    element.setAttribute(name, attribs[name]);
  if (options) {
    if (options.content)
      element.textContent = options.content;
    if (options.className)
      element.setAttribute("class", options.className);
  }
  return element;
}

function drawDir(svg, dir, depth, options) {
  if (depth == 0 || !dir.subdirs)
    return;

  dir.subdirs.sort(
    function(a, b) {
      return b[options.key] - a[options.key];
    });

  var total = 0;
  dir.subdirs.forEach(
    function(subdir) {
      total += subdir[options.key];
    });

  if (total < dir[options.key])
    total = dir[options.key];

  if (total == 0)
    return;

  var currY = options.y + options.height;
  dir.subdirs.forEach(
    function(subdir) {
      var heightScale = subdir[options.key] / total;
      var height = options.height * heightScale;
      var y = currY - height;

      if (height < options.minHeight)
        return;

      var fullPath;
      if (options.basePath)
        fullPath = options.basePath + "/" + subdir.name;
      else
        fullPath = subdir.name;

      var rect = SVGElement("rect", {x: options.x,
                                     y: y,
                                     width: options.width,
                                     height: height});
      rect.moztreePath = fullPath;
      svg.appendChild(rect);

      var text = SVGElement("text",
                            {x: options.x + options.textPadding,
                             y: y + height - options.textPadding});
      text.appendChild(SVGElement("tspan", {},
                                  {content: subdir.name}));
      text.appendChild(SVGElement("tspan",
                                  {x: (options.x + options.width -
                                       options.textPadding)},
                                  {content: subdir.changes,
                                   className: "changes"}));
      svg.appendChild(text);

      drawDir(svg, subdir, depth - 1, {x: options.x + options.width,
                                       y: y,
                                       key: options.key,
                                       basePath: fullPath,
                                       width: options.width,
                                       height: height,
                                       minHeight: options.minHeight,
                                       textPadding: options.textPadding});
      currY = y;
    });
}

function makeTree(tree, options) {
  var svg = document.createElementNS(SVG_NS, "svg");
  var group = document.createElementNS(SVG_NS, "g");
  var svgHeight = options.height;
  var svgWidth = options.dirWidth * options.dirDepth;

  if (options.landscape) {
    var temp = svgWidth;
    group.setAttribute("transform",
                       "translate(" + svgHeight + ", 0) rotate(90)");
    svgWidth = svgHeight;
    svgHeight = temp;
  }

  svg.setAttribute("width", svgWidth);
  svg.setAttribute("height", svgHeight);
  svg.appendChild(group);

  drawDir(group, tree, options.dirDepth,
          {x: 0,
           y: 0,
           key: options.key,
           basePath: options.basePath,
           width: options.dirWidth,
           height: options.height,
           textPadding: options.textPadding,
           minHeight: options.minHeight});
  return svg;
}

function getTreePath(tree, path) {
  var pathParts = path.split("/");
  pathParts.reverse();
  while (pathParts.length > 0) {
    var dirName = pathParts.pop();
    tree.subdirs.forEach(
      function(subdir) {
        if (subdir.name == dirName)
          tree = subdir;
      });
  }
  return tree;
}

var App = {
  tree: null,
  lastHash: null,
  HG_URL: "http://hg.mozilla.org/mozilla-central/",
  onIdle: function onIdle() {
    if (window.location.hash != this.lastHash)
      this.updateTree();
  },
  updateTree: function updateTree() {
    var dir = this.tree;
    var basePath = null;
    if (window.location.hash && window.location.hash.length > 1) {
      basePath = window.location.hash.slice(1);
      dir = getTreePath(dir, basePath);
    }
    this.lastHash = window.location.hash;

    var height = 0;
    if (dir.subdirs)
      height = dir.subdirs.length * 100;
    if (height < 600)
      height = 600;

    var svg = makeTree(dir,
                       {height: height,
                        key: 'changes',
                        basePath: basePath,
                        dirWidth: 120,
                        dirDepth: 6,
                        landscape: false,
                        textPadding: 4,
                        minHeight: 16});
    $("h1").text("mozilla-central/" +
                 (basePath || "")).hide().fadeIn("fast");
    $("#tree").empty().append(svg).hide().fadeIn("fast");
  },
  start: function start() {
    var self = this;
    $("#tree").click(
      function(event) {
        if (event.target.moztreePath) {
          if (event.shiftKey) {
            window.open(self.HG_URL + 'file/tip/' +
                        event.target.moztreePath);
          } else {
            window.location.hash = "#" + event.target.moztreePath;
            self.updateTree();
          }
        }
      });
    getJSON(
      'moztree.json',
      function(tree) {
        self.tree = tree;
        self.updateTree();
        window.setInterval(function() { self.onIdle(); }, 500);
      });
  }
};

$(window).ready(function() { App.start(); });