<commit_before>if (Meteor.isClient) {
  Template.navigation.helpers({
    "active": function(path) {
      var router = Router.current();
      if (router && router.route.name === path) {
        return "active";
      }
      return "";
    }
  });

  window.huddle = undefined;
  var huddleHost = Meteor.settings.public.huddle.host;
  var huddlePort = Meteor.settings.public.huddle.port;

  var firstProximityData = true;
  var needCheckColor = true;

  var transformDeviceData = function(data) {
    var newData = {
      id: data.Identity.toString(),
      center: {
        x: data.Location[0] || 0,
        y: data.Location[1] || 0,
      },
      // topLeft: {
      //   x: data.Location[0] || 0,
      //   y: data.Location[1] || 0,
      //   // x: Math.round(data.Location[0] * 10) / 10,
      //   // y: Math.round(data.Location[1] * 10) / 10
      // },
      ratio: {
        // x: (Math.round(data.RgbImageToDisplayRatio.X * 10) / 10) || 1,
        // y: (Math.round(data.RgbImageToDisplayRatio.Y * 10) / 10) || 1
        x: data.RgbImageToDisplayRatio.X || 1,
        y: data.RgbImageToDisplayRatio.Y || 1
      },
      angle: data.Orientation || 0,
    };

    newData.width = 1.0/newData.ratio.x;
    newData.height = 1.0/newData.ratio.y;

    newData.topLeft = {
      x: newData.center.x - newData.width/2.0,
      y: newData.center.y - newData.height/2.0
    };

    newData.topRight = {
      x: newData.topLeft.x + newData.width,
      y: newData.topLeft.y
    };

    newData.bottomRight = {
      x: newData.topLeft.x + newData.width,
      y: newData.topLeft.y + newData.height
    };

    newData.bottomLeft = {
      x: newData.topLeft.x,
      y: newData.topLeft.y + newData.height
    };

    // newData.center = {
    //   x: newData.topLeft.x + newData.width/2.0,
    //   y: newData.topLeft.y + newData.height/2.0
    // };

    return newData;
  }; //end transformDeviceData

  var existingID = amplify.store("huddleid");
  if (existingID === undefined) existingID = null;
  console.log("EXISTING ID: "+existingID);

  // var start = Date.now();
  huddle = Huddle.client({ glyphId: existingID+"" })
  .on("devicelost", function(a) {
    console.log("DEVICE WAS LOST");
    console.log(a);
  })
  .on("proximity", function(data) {
    // if (capFPS(1)) return;

    if (firstProximityData && data.Identity) {
      console.log("MY ID: "+data.Identity);
      amplify.store("huddleid", data.Identity);
      firstProximityData = false;

      determineDeviceColor(data.Identity.toString());

      //Because two devices loading at the same time can get the same color, we
      //check the color after a random amount of time. If it's not unique, it will
      //be changed
      if (needCheckColor) {
        needCheckColor = false;

        var timeout = getRandomInt(3000, 8000);
        Meteor.setTimeout(function() {
          determineDeviceColor(data.Identity.toString());
        }, timeout);
      }
    }

    // console.log(data);
    Session.set('thisDevice', transformDeviceData(data));
    // console.log(Session.get('thisDevice'));

    var otherDevices = [];
    data.Presences.forEach(function(presence) {
      otherDevices.push(transformDeviceData(presence));
    });
    Session.set('otherDevices', otherDevices);

  }); // end .on("proximity")

  // huddle.reconnect = false;
  huddle.connect(huddleHost, huddlePort);

  Huddle.on("showdocument", function(data) {
    //If this page has no detail document template, we are screwed :-)
    if (!Template.detailDocumentTemplate) return;

    var thisDevice = Session.get('thisDevice');
    if (data.target !== thisDevice.id) return;

    ElasticSearch.get(data.documentID, function(err, result) {
      if (err) {
        console.error(err);
      }
      else {
        Template.detailDocumentTemplate.open(result.data);
      }
    });
  });

  Huddle.on("addtextsnippet", function(data) {
    var thisDevice = Session.get('thisDevice');
    if (data.target !== thisDevice.id) return;

    //TODO also insert source document and the device that sent the snippet
    Snippets.insert({ device: thisDevice.id, sourcedoc: data.doc, text: data.snippet });
  });

  Huddle.on("dosearch", function(data) {
    var thisDevice = Session.get('thisDevice');
    if (data.target !== thisDevice.id) return;

    // if (!data.page) data.page = 1;
    search(data.query, data.page);
  });

  Huddle.on("go", function(data) {
    var thisDevice = Session.get('thisDevice');
    if (data.target !== thisDevice.id) return;

    Router.go(data.template, data.params);
    Template.searchIndex.reflectURL();
  });
}

function determineDeviceColor(deviceID) {
  //If the device has a unique color already, we don't need to look for a new one
  var needColor = true;
  var thisInfos = DeviceInfo.findOne({ _id: deviceID });
  if (thisInfos !== undefined) {
    var thisColorInfos = DeviceInfo.find({ colorDeg: thisInfos.colorDeg}).fetch();
    if (thisColorInfos.length === 1) {
      needColor = false;
    }
  }

  if (needColor) {
    //First time we got some proximity data, it's time to figure out this
    //devices color. Sounds easy, right? It's not.
    //In general, we assume HSV colorspace with saturation and value both being 1,
    //which gives us a 2D color wheel. In order to calculate as different colors as
    //possible, we now try to place colors evenly distributed on that wheel.
    //To do so, we divide the wheel into four even sections (90º each) and place
    //our color in the section that has been used least. Inside each section, we
    //furthermore evenly distribute the colors. This also makes sure that after
    //a color has been given to a device, the next device receives the
    //complementary color

    //Grab all colors that have been distributed already and divide them by the
    //four sectors of the color wheel
    var infos = DeviceInfo.find({}, {sort: ["colorDeg", "asc"]}).fetch();
    var existingColors = { 0: [], 1: [], 2: [], 3: [] };
    for (var i=0; i<infos.length; i++) {
      var currentColor = infos[i].colorDeg;
      var sector;
      if (currentColor >= 0   && currentColor < 90)  sector = 0;
      if (currentColor >= 90  && currentColor < 180) sector = 1;
      if (currentColor >= 180 && currentColor < 270) sector = 2;
      if (currentColor >= 270 && currentColor < 369) sector = 3;

      //Don't count duplicates in the sectors
      if (existingColors[sector].indexOf(currentColor) === -1) {
        existingColors[sector].push(currentColor);
      }//
    }

    //Figure out the largest sector (that has the most colors). If multiple sectors
    //are equally large, all of them are considered the largest sector
    var largestSectorSize = Math.max(
      existingColors[0].length,
      existingColors[1].length,
      existingColors[2].length,
      existingColors[3].length
    );

    var largestSectors = [];
    if (existingColors[0].length === largestSectorSize) largestSectors.push(0);
    if (existingColors[1].length === largestSectorSize) largestSectors.push(1);
    if (existingColors[2].length === largestSectorSize) largestSectors.push(2);
    if (existingColors[3].length === largestSectorSize) largestSectors.push(3);

    //Now that we have the largest sector(s) we can figure out the sectors that
    //the new color should be in, so that our colors get evenly distributed among
    //the sectors.
    var targetSector;

    //If all sectors are equal in size, we just target the first sector
    if (largestSectors.length === 0 || largestSectors.length === 4) {
      targetSector = 0;
    }

    //If one sector is larger than all others, we need to pick the opposite
    //sector to balance the color wheel
    if (largestSectors.length === 1) {
      targetSector = (largestSectors[0] + 2) % 4;
    }

    //If two sectors are larger than the others, we can pick any of the remaining
    //two (and just pick the first of them)
    if (largestSectors.length === 2) {
      targetSector = (largestSectors[0] + 1) % 4;
    }

    //If three sectors are larger than a fourth, we must pick the remaining one
    if (largestSectors.length === 3) {
      var sectorsLeft = [0, 1, 2, 3];
      for (var i=0; i<largestSectors.length; i++) {
        var index = sectorsLeft.indexOf(largestSectors[i]);
        if (index > -1) {
          sectorsLeft.splice(index, 1);
        }
      }
      targetSector = sectorsLeft[0];
    }

    //Now that we have the sector our color must be in, we need to balance colors
    //inside of that sector. For that, we look for the largest difference between
    //two colors inside that sector and place the new color right in the middle
    //of those
    //Exception: The first and second color are "hardcoded" for the algorithm
    //to work. The first color is the first color in that sector, the second color
    //is the middle color of that sector (because it's in the middle between this and
    //the next sector)
    var smallestSectorColorDeg = targetSector * 90;
    var largestSectorColorDeg = smallestSectorColorDeg + 90;

    var newColorDeg;
    if (existingColors[targetSector].length === 0) {
      newColorDeg = smallestSectorColorDeg;
    } else if (existingColors[targetSector].length === 1) {
      newColorDeg = smallestSectorColorDeg + 45;
    } else {
      var maxDiff = 0;
      for (var i=0; i<existingColors[targetSector].length; i++) {
        var currentColorDeg = existingColors[targetSector][i];

        var nextColor = largestSectorColorDeg;
        if ((i+1) < existingColors[targetSector].length) {
          nextColor = existingColors[targetSector][i+1];
        }

        var diff = nextColor-currentColorDeg;
        if (diff > maxDiff) {
          newColorDeg = currentColorDeg + diff/2.0;
          maxDiff = diff;
        }
      }
    }

    DeviceInfo._upsert(deviceID, { $set: { colorDeg: newColorDeg } });
  }
}

var cachedColorDegs = {};
window.getDeviceColorDeg = function(deviceID) {
  if (cachedColorDegs[deviceID]) {
    return cachedColorDegs[deviceID];
  }

  // var info = DeviceInfo.findOne({ _id: deviceID });
  // if (info === undefined || info.colorDeg === undefined) return 0;

  var cursor = DeviceInfo.find({_id: deviceID });
  var info = cursor.fetch()[0];
  if (info === undefined || info.colorDeg === undefined) return 0;

  var observer = function(deviceID) {
    return function(newDocument) {
      if (newDocument === undefined || newDocument.colorDeg === undefined) return;
      cachedColorDegs[deviceID] = newDocument.colorDeg;
    };
  };

  cursor.observe({
    added   : observer(deviceID),
    changed : observer(deviceID),
    removed : observer(deviceID),
  });

  cachedColorDegs[deviceID] = info.colorDeg;
  return info.colorDeg;
};

window.degreesToColor = function(deg, ensureVisibility) {
  if (ensureVisibility === undefined) ensureVisibility = true;

  var color = new tinycolor({
    s: 1,
    v: 1,
    h: deg
  });

  //If requested, make sure the color is visible on white background
  if (ensureVisibility) {
    while (color.getBrightness() > 127) {
      color = color.darken(1);
    }
  }

  color = color.toRgb();
  return {
    r: Math.round(color.r),
    g: Math.round(color.g),
    b: Math.round(color.b)
  };
};

function getRandomInt(min, max) {
  return Math.floor(Math.random() * (max - min + 1)) + min;
}

var lastFPSCap = new Date();
function capFPS(fps) {
  var now = new Date();
  var timeDiff = now.getTime() - lastFPSCap.getTime();

  if (timeDiff < (1000.0/fps)) {
    return true;
  } else {
    lastFPSCap = now;
    return false;
  }
}
<commit_msg>Added host, port and color URL parameter<commit_after>if (Meteor.isClient) {
   var getURLParameter = function(name) {
    name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]");
    var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"),
        results = regex.exec(location.search);
    return results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
  };

  Template.navigation.helpers({
    "active": function(path) {
      var router = Router.current();
      if (router && router.route.name === path) {
        return "active";
      }
      return "";
    }
  });

  window.huddle = undefined;
  var huddleHost = getURLParameter("host");
  var huddlePort = getURLParameter("port");

  if (!huddleHost) huddleHost = Meteor.settings.public.huddle.host;
  if (!huddlePort) huddlePort = Meteor.settings.public.huddle.port;

  var firstProximityData = true;
  var needCheckColor = true;

  var transformDeviceData = function(data) {
    var newData = {
      id: data.Identity.toString(),
      center: {
        x: data.Location[0] || 0,
        y: data.Location[1] || 0,
      },
      // topLeft: {
      //   x: data.Location[0] || 0,
      //   y: data.Location[1] || 0,
      //   // x: Math.round(data.Location[0] * 10) / 10,
      //   // y: Math.round(data.Location[1] * 10) / 10
      // },
      ratio: {
        // x: (Math.round(data.RgbImageToDisplayRatio.X * 10) / 10) || 1,
        // y: (Math.round(data.RgbImageToDisplayRatio.Y * 10) / 10) || 1
        x: data.RgbImageToDisplayRatio.X || 1,
        y: data.RgbImageToDisplayRatio.Y || 1
      },
      angle: data.Orientation || 0,
    };

    newData.width = 1.0/newData.ratio.x;
    newData.height = 1.0/newData.ratio.y;

    newData.topLeft = {
      x: newData.center.x - newData.width/2.0,
      y: newData.center.y - newData.height/2.0
    };

    newData.topRight = {
      x: newData.topLeft.x + newData.width,
      y: newData.topLeft.y
    };

    newData.bottomRight = {
      x: newData.topLeft.x + newData.width,
      y: newData.topLeft.y + newData.height
    };

    newData.bottomLeft = {
      x: newData.topLeft.x,
      y: newData.topLeft.y + newData.height
    };

    // newData.center = {
    //   x: newData.topLeft.x + newData.width/2.0,
    //   y: newData.topLeft.y + newData.height/2.0
    // };

    return newData;
  }; //end transformDeviceData

  var existingID = amplify.store("huddleid");
  if (existingID === undefined) existingID = null;
  console.log("EXISTING ID: "+existingID);

  // var start = Date.now();
  huddle = Huddle.client({ glyphId: existingID+"" })
  .on("devicelost", function(a) {
    console.log("DEVICE WAS LOST");
    console.log(a);
  })
  .on("proximity", function(data) {
    // if (capFPS(1)) return;

    if (firstProximityData && data.Identity) {
      console.log("MY ID: "+data.Identity);
      amplify.store("huddleid", data.Identity);
      firstProximityData = false;

      if (getURLParameter("color")) {
        DeviceInfo._upsert(data.Identity.toString(), { $set: { colorDeg: getURLParameter("color") } });
      } else {
        determineDeviceColor(data.Identity.toString());

        //Because two devices loading at the same time can get the same color, we
        //check the color after a random amount of time. If it's not unique, it will
        //be changed
        if (needCheckColor) {
          needCheckColor = false;

          var timeout = getRandomInt(3000, 8000);
          Meteor.setTimeout(function() {
            determineDeviceColor(data.Identity.toString());
          }, timeout);
        }
      }
    }

    // console.log(data);
    Session.set('thisDevice', transformDeviceData(data));
    // console.log(Session.get('thisDevice'));

    var otherDevices = [];
    data.Presences.forEach(function(presence) {
      otherDevices.push(transformDeviceData(presence));
    });
    Session.set('otherDevices', otherDevices);

  }); // end .on("proximity")

  // huddle.reconnect = false;
  huddle.connect(huddleHost, huddlePort);

  Huddle.on("showdocument", function(data) {
    //If this page has no detail document template, we are screwed :-)
    if (!Template.detailDocumentTemplate) return;

    var thisDevice = Session.get('thisDevice');
    if (data.target !== thisDevice.id) return;

    ElasticSearch.get(data.documentID, function(err, result) {
      if (err) {
        console.error(err);
      }
      else {
        Template.detailDocumentTemplate.open(result.data);
      }
    });
  });

  Huddle.on("addtextsnippet", function(data) {
    var thisDevice = Session.get('thisDevice');
    if (data.target !== thisDevice.id) return;

    //TODO also insert source document and the device that sent the snippet
    Snippets.insert({ device: thisDevice.id, sourcedoc: data.doc, text: data.snippet });
  });

  Huddle.on("dosearch", function(data) {
    var thisDevice = Session.get('thisDevice');
    if (data.target !== thisDevice.id) return;

    // if (!data.page) data.page = 1;
    search(data.query, data.page);
  });

  Huddle.on("go", function(data) {
    var thisDevice = Session.get('thisDevice');
    if (data.target !== thisDevice.id) return;

    Router.go(data.template, data.params);
    Template.searchIndex.reflectURL();
  });
}

function determineDeviceColor(deviceID) {
  //If the device has a unique color already, we don't need to look for a new one
  var needColor = true;
  var thisInfos = DeviceInfo.findOne({ _id: deviceID });
  if (thisInfos !== undefined) {
    var thisColorInfos = DeviceInfo.find({ colorDeg: thisInfos.colorDeg}).fetch();
    if (thisColorInfos.length === 1) {
      needColor = false;
    }
  }

  if (needColor) {
    //First time we got some proximity data, it's time to figure out this
    //devices color. Sounds easy, right? It's not.
    //In general, we assume HSV colorspace with saturation and value both being 1,
    //which gives us a 2D color wheel. In order to calculate as different colors as
    //possible, we now try to place colors evenly distributed on that wheel.
    //To do so, we divide the wheel into four even sections (90º each) and place
    //our color in the section that has been used least. Inside each section, we
    //furthermore evenly distribute the colors. This also makes sure that after
    //a color has been given to a device, the next device receives the
    //complementary color

    //Grab all colors that have been distributed already and divide them by the
    //four sectors of the color wheel
    var infos = DeviceInfo.find({}, {sort: ["colorDeg", "asc"]}).fetch();
    var existingColors = { 0: [], 1: [], 2: [], 3: [] };
    for (var i=0; i<infos.length; i++) {
      var currentColor = infos[i].colorDeg;
      var sector;
      if (currentColor >= 0   && currentColor < 90)  sector = 0;
      if (currentColor >= 90  && currentColor < 180) sector = 1;
      if (currentColor >= 180 && currentColor < 270) sector = 2;
      if (currentColor >= 270 && currentColor < 369) sector = 3;

      //Don't count duplicates in the sectors
      if (existingColors[sector].indexOf(currentColor) === -1) {
        existingColors[sector].push(currentColor);
      }//
    }

    //Figure out the largest sector (that has the most colors). If multiple sectors
    //are equally large, all of them are considered the largest sector
    var largestSectorSize = Math.max(
      existingColors[0].length,
      existingColors[1].length,
      existingColors[2].length,
      existingColors[3].length
    );

    var largestSectors = [];
    if (existingColors[0].length === largestSectorSize) largestSectors.push(0);
    if (existingColors[1].length === largestSectorSize) largestSectors.push(1);
    if (existingColors[2].length === largestSectorSize) largestSectors.push(2);
    if (existingColors[3].length === largestSectorSize) largestSectors.push(3);

    //Now that we have the largest sector(s) we can figure out the sectors that
    //the new color should be in, so that our colors get evenly distributed among
    //the sectors.
    var targetSector;

    //If all sectors are equal in size, we just target the first sector
    if (largestSectors.length === 0 || largestSectors.length === 4) {
      targetSector = 0;
    }

    //If one sector is larger than all others, we need to pick the opposite
    //sector to balance the color wheel
    if (largestSectors.length === 1) {
      targetSector = (largestSectors[0] + 2) % 4;
    }

    //If two sectors are larger than the others, we can pick any of the remaining
    //two (and just pick the first of them)
    if (largestSectors.length === 2) {
      targetSector = (largestSectors[0] + 1) % 4;
    }

    //If three sectors are larger than a fourth, we must pick the remaining one
    if (largestSectors.length === 3) {
      var sectorsLeft = [0, 1, 2, 3];
      for (var i=0; i<largestSectors.length; i++) {
        var index = sectorsLeft.indexOf(largestSectors[i]);
        if (index > -1) {
          sectorsLeft.splice(index, 1);
        }
      }
      targetSector = sectorsLeft[0];
    }

    //Now that we have the sector our color must be in, we need to balance colors
    //inside of that sector. For that, we look for the largest difference between
    //two colors inside that sector and place the new color right in the middle
    //of those
    //Exception: The first and second color are "hardcoded" for the algorithm
    //to work. The first color is the first color in that sector, the second color
    //is the middle color of that sector (because it's in the middle between this and
    //the next sector)
    var smallestSectorColorDeg = targetSector * 90;
    var largestSectorColorDeg = smallestSectorColorDeg + 90;

    var newColorDeg;
    if (existingColors[targetSector].length === 0) {
      newColorDeg = smallestSectorColorDeg;
    } else if (existingColors[targetSector].length === 1) {
      newColorDeg = smallestSectorColorDeg + 45;
    } else {
      var maxDiff = 0;
      for (var i=0; i<existingColors[targetSector].length; i++) {
        var currentColorDeg = existingColors[targetSector][i];

        var nextColor = largestSectorColorDeg;
        if ((i+1) < existingColors[targetSector].length) {
          nextColor = existingColors[targetSector][i+1];
        }

        var diff = nextColor-currentColorDeg;
        if (diff > maxDiff) {
          newColorDeg = currentColorDeg + diff/2.0;
          maxDiff = diff;
        }
      }
    }

    DeviceInfo._upsert(deviceID, { $set: { colorDeg: newColorDeg } });
  }
}

var cachedColorDegs = {};
window.getDeviceColorDeg = function(deviceID) {
  if (cachedColorDegs[deviceID]) {
    return cachedColorDegs[deviceID];
  }

  // var info = DeviceInfo.findOne({ _id: deviceID });
  // if (info === undefined || info.colorDeg === undefined) return 0;

  var cursor = DeviceInfo.find({_id: deviceID });
  var info = cursor.fetch()[0];
  if (info === undefined || info.colorDeg === undefined) return 0;

  var observer = function(deviceID) {
    return function(newDocument) {
      if (newDocument === undefined || newDocument.colorDeg === undefined) return;
      cachedColorDegs[deviceID] = newDocument.colorDeg;
    };
  };

  cursor.observe({
    added   : observer(deviceID),
    changed : observer(deviceID),
    removed : observer(deviceID),
  });

  cachedColorDegs[deviceID] = info.colorDeg;
  return info.colorDeg;
};

window.degreesToColor = function(deg, ensureVisibility) {
  if (ensureVisibility === undefined) ensureVisibility = true;

  var color = new tinycolor({
    s: 1,
    v: 1,
    h: deg
  });

  //If requested, make sure the color is visible on white background
  if (ensureVisibility) {
    while (color.getBrightness() > 127) {
      color = color.darken(1);
    }
  }

  color = color.toRgb();
  return {
    r: Math.round(color.r),
    g: Math.round(color.g),
    b: Math.round(color.b)
  };
};

function getRandomInt(min, max) {
  return Math.floor(Math.random() * (max - min + 1)) + min;
}

var lastFPSCap = new Date();
function capFPS(fps) {
  var now = new Date();
  var timeDiff = now.getTime() - lastFPSCap.getTime();

  if (timeDiff < (1000.0/fps)) {
    return true;
  } else {
    lastFPSCap = now;
    return false;
  }
}
<|endoftext|>


<commit_before>/*
Helper functions, &c., for a comet server by Jacob Rus.

--------------------

Copyright (c) 2009 Jacob Rus

Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
*/

var myClearTimeout = this.myClearTimeout = function (timer) {
	return timer ? clearTimeout(timer) : null;
};

var debug = this.debug = function () {
	var message = [];
	for (var i=0; i < arguments.length; i++) {
		var arg = arguments[i];
		arg = (typeof arg === 'string') ? arg : JSON.stringify(arg)
		message.push(arg);
	};
	node.stdio.writeError(message.join(' ') + '\n');
};

var bind = exports.bind = function(context, method/*, arg1, arg2, ... */){
	var args = Array.prototype.slice.call(arguments, 2);
	return function() {
		method = (typeof method === 'string' ? context[method] : method);
		return method.apply(context, args.concat(Array.prototype.slice.call(arguments, 0)));
	};
};

var Class = exports.Class = function(parent, proto) {
	if (!parent) { throw new Error('parent or prototype not provided'); };
	if (!proto) {
		proto = parent;
	} else if (parent instanceof Array) { // multiple inheritance, use at your own risk =)
		proto.prototype = {};
		for (var i = 0, p; p = parent[i]; ++i) {
			for (var item in p.prototype) {
				if (!(item in proto.prototype)) {
					proto.prototype[item] = p.prototype[item];
				};
			};
		};
		parent = parent[0];
	} else {
		proto.prototype = parent.prototype;
	};

	var cls = function() { if(this.init) { this.init.apply(this, arguments); }}
	cls.prototype = new proto(function(context, method, args) {
		var args = args || [];
		var target = parent;
		while(target = target.prototype) {
			if(target[method]) {
				return target[method].apply(context, args);
			}
		}
		throw new Error('method ' + method + ' does not exist');
	});
	cls.prototype.constructor = cls;
	return cls;
};

// helper to make a hash table from the arguments for membership testing
// Use like: 'a' in Set('a', 'b', 'c')
var Set = this.Set = function () {
	var set = {};
	var len = arguments.length;
	for (var i=0; i < len; i++) {
		set[arguments[i]] = true;
	};
	return set;
};

// helper to test if string 2 is at the beginning of string 1
var startswith = this.startswith = function (str1, str2) {
	return str1.slice(0, str2.length) == str2;
};

var JSIOError = this.JSIOError = Class(Error, function () {
	this.name = 'JSIOError';
	this.toString = Error.prototype.toString;
	this.init = function (message, fileName, lineNumber) {
		this.name = this.name; // promote class property to instance
    this.message = message || '';
		this.fileName = fileName || '«filename»'; // location.href; // XXX what should go here?
		this.lineNumber = isNaN(+lineNumber) ? 0 : +lineNumber
	};
})

var AssertionError = this.AssertionError = Class(JSIOError, function (supr) {
	this.name = 'AssertionError'
	this.init = function () {supr(this, 'init', arguments)}
});
var assert = this.assert = function (exp, message) {
	if (!exp) {
		throw new AssertionError(message)
	};
};

// schedule a callback to run at the next available moment,
// equivalent to setTimeout(callback, 0)
var reschedule = this.reschedule = function (callback) {
	var timer = new node.Timer();
	timer.addListener('timeout', callback);
	timer.start(0, 0);
	return timer;
}

// cached static files
var staticFile = this.staticFile = function (){
	var cache = {} // static file content indexed by filename
	var getfile = function(path) {
		promise = new node.Promise();
		cacheContent = cache[path];
		if (cacheContent !== undefined) {
			// the file is in the cache, return it
			reschedule(function(){
				promise.emitSuccess([cacheContent]);
			});
		} else {
			// load file from disk, save it in the cache, and return it
			node.fs.cat(path, 'utf8')
				.addCallback(function(fileContent) {
					cache[path] = fileContent;
					promise.emitSuccess([fileContent]);
				})
				.addErrback(function(){
					promise.emitError();
				});
		};
		return promise;
	};
	return getfile;
}();

var CreateClass = this.CreateClass = function(target) {
	target.init.prototype = target;
	return target.init;
}<commit_msg>use a settimeout with error catching rather than a node timer so the server doesn't crash<commit_after>/*
Helper functions, &c., for a comet server by Jacob Rus.

--------------------

Copyright (c) 2009 Jacob Rus

Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
*/

var myClearTimeout = this.myClearTimeout = function (timer) {
	return timer ? clearTimeout(timer) : null;
};

var debug = this.debug = function () {
	var message = [];
	for (var i=0; i < arguments.length; i++) {
		var arg = arguments[i];
		arg = (typeof arg === 'string') ? arg : JSON.stringify(arg)
		message.push(arg);
	};
	node.stdio.writeError(message.join(' ') + '\n');
};

var bind = exports.bind = function(context, method/*, arg1, arg2, ... */){
	var args = Array.prototype.slice.call(arguments, 2);
	return function() {
		method = (typeof method === 'string' ? context[method] : method);
		return method.apply(context, args.concat(Array.prototype.slice.call(arguments, 0)));
	};
};

var Class = exports.Class = function(parent, proto) {
	if (!parent) { throw new Error('parent or prototype not provided'); };
	if (!proto) {
		proto = parent;
	} else if (parent instanceof Array) { // multiple inheritance, use at your own risk =)
		proto.prototype = {};
		for (var i = 0, p; p = parent[i]; ++i) {
			for (var item in p.prototype) {
				if (!(item in proto.prototype)) {
					proto.prototype[item] = p.prototype[item];
				};
			};
		};
		parent = parent[0];
	} else {
		proto.prototype = parent.prototype;
	};

	var cls = function() { if(this.init) { this.init.apply(this, arguments); }}
	cls.prototype = new proto(function(context, method, args) {
		var args = args || [];
		var target = parent;
		while(target = target.prototype) {
			if(target[method]) {
				return target[method].apply(context, args);
			}
		}
		throw new Error('method ' + method + ' does not exist');
	});
	cls.prototype.constructor = cls;
	return cls;
};

// helper to make a hash table from the arguments for membership testing
// Use like: 'a' in Set('a', 'b', 'c')
var Set = this.Set = function () {
	var set = {};
	var len = arguments.length;
	for (var i=0; i < len; i++) {
		set[arguments[i]] = true;
	};
	return set;
};

// helper to test if string 2 is at the beginning of string 1
var startswith = this.startswith = function (str1, str2) {
	return str1.slice(0, str2.length) == str2;
};

var JSIOError = this.JSIOError = Class(Error, function () {
	this.name = 'JSIOError';
	this.toString = Error.prototype.toString;
	this.init = function (message, fileName, lineNumber) {
		this.name = this.name; // promote class property to instance
    this.message = message || '';
		this.fileName = fileName || '«filename»'; // location.href; // XXX what should go here?
		this.lineNumber = isNaN(+lineNumber) ? 0 : +lineNumber
	};
})

var AssertionError = this.AssertionError = Class(JSIOError, function (supr) {
	this.name = 'AssertionError'
	this.init = function () {supr(this, 'init', arguments)}
});
var assert = this.assert = function (exp, message) {
	if (!exp) {
		throw new AssertionError(message)
	};
};

// schedule a callback to run at the next available moment,
// equivalent to setTimeout(callback, 0)
var reschedule = this.reschedule = function (callback) {
	return $setTimeout(callback, 0);
}

// cached static files
var staticFile = this.staticFile = function (){
	var cache = {} // static file content indexed by filename
	var getfile = function(path) {
		promise = new node.Promise();
		cacheContent = cache[path];
		if (cacheContent !== undefined) {
			// the file is in the cache, return it
			reschedule(function(){
				promise.emitSuccess([cacheContent]);
			});
		} else {
			// load file from disk, save it in the cache, and return it
			node.fs.cat(path, 'utf8')
				.addCallback(function(fileContent) {
					cache[path] = fileContent;
					promise.emitSuccess([fileContent]);
				})
				.addErrback(function(){
					promise.emitError();
				});
		};
		return promise;
	};
	return getfile;
}();

var CreateClass = this.CreateClass = function(target) {
	target.init.prototype = target;
	return target.init;
}<|endoftext|>


<commit_before>var failchat = {
    "maxMessages": 50
};

$(function () {
    var socket = new WebSocket("ws://localhost:10880");
    var messageCount = 0;
    var messageContainer = $("#message-container");
    var smileTemplate = $("#smile-template");
    var linkTemplate = $("#link-template");
    var messageTemplate = $("#message-template");
    var infoMessageTemplate = $("#info-message-template");
    var scroller = $(failchat.baronParams.scroller);
    var scrollBar = $(failchat.baronParams.bar);
    var autoScroll = true;
    var autoScrolled = false;

    scrollBar.css("visibility", "hidden");
    baron(failchat.baronParams);

    socket.onopen = function () {
        //var connectedMessage = {"source": "failchat","text":"connected"};
        //handleInfoMessage(connectedMessage);
        //appendToMessageContainer(connectedMessage);
    };
    socket.onclose = function () {
        var disconnectedMessage =  {"source": "failchat","text":"disconnected"};
        handleInfoMessage(disconnectedMessage);
        appendToMessageContainer(disconnectedMessage);
    };
    socket.onmessage = function (event) {
        var wsm = JSON.parse(event.data);

        if (wsm.type === "message") {
            handleMessage(wsm.content);
        }
        else if (wsm.type === "info") {
            handleInfoMessage(wsm.content);
        }

        if (wsm.content.text !== undefined) {
            appendToMessageContainer(wsm.content);
        }
    };

    function handleMessage(message) {
        //smiles
        if (message.smiles != undefined) {
            var imgHtml;
            for (var i = 0; i < message.smiles.length; i++) {
                imgHtml = smileTemplate.render(message.smiles[i]);
                message.text = message.text.replace("{!" + message.smiles[i].objectNumber + "}", imgHtml);
            }
        }

        //links
        if (message.links != undefined) {
            var linkHtml;
            for (i = 0; i < message.links.length; i++) {
                linkHtml = linkTemplate.render(message.links[i]);
                message.text = message.text.replace("{!" + message.links[i].objectNumber + "}", linkHtml);
            }
        }

        message.text = messageTemplate.render(message);
    }

    function handleInfoMessage(infoMessage) {
        infoMessage.text =  infoMessageTemplate.render(infoMessage);
    }

    function appendToMessageContainer(message) {
        messageCount++;
        messageContainer.append(message.text);
        if (messageCount > failchat.maxMessages && autoScroll) {
            while (messageCount > failchat.maxMessages) {
                messageContainer.find("> :first").remove();
                messageCount--;
            }
        }
        if (autoScroll) {
            if (message.smiles !== undefined && failchat.scrollHookSelector !== undefined) {
                autoScrolled = true;
                scroller.scrollTop(messageContainer.height());
                scrollHook();
            } else {
                autoScrolled = true;
                scroller.scrollTop(messageContainer.height());
            }
        }
        //console.log("messages: " + messageCount);
    }

    scroller.scroll( function() {
        //console.log("scrolled");
        if (autoScrolled) {
            autoScrolled = false;
        }
        else {
            autoScroll = scroller.scrollTop() + scroller.height() >= messageContainer.height();
            if (autoScroll) {
                scrollBar.css("visibility", "hidden");
            }
            else {
                scrollBar.css("visibility", "visible");
            }
            //console.log("autoScroll: " + autoScroll);
        }
    });

    // scroll when last smile in message loaded
    function scrollHook() {
        $(failchat.scrollHookSelector + ":last").imagesLoaded(function() {
            //console.log("last smile loaded");
            if (autoScroll) {
                autoScrolled = true;
                scroller.scrollTop(messageContainer.height());
            }
        });
    }
});<commit_msg>Disable autoscroll on specified events<commit_after>var failchat = {
    "maxMessages": 50
};

$(function () {
    var socket = new WebSocket("ws://localhost:10880");
    var messageCount = 0;
    var messageContainer = $("#message-container");
    var smileTemplate = $("#smile-template");
    var linkTemplate = $("#link-template");
    var messageTemplate = $("#message-template");
    var infoMessageTemplate = $("#info-message-template");
    var scroller = $(failchat.baronParams.scroller);
    var scrollBar = $(failchat.baronParams.bar);
    var autoScroll = true;

    scrollBar.css("visibility", "hidden");
    baron(failchat.baronParams);

    socket.onopen = function () {
        //var connectedMessage = {"source": "failchat","text":"connected"};
        //handleInfoMessage(connectedMessage);
        //appendToMessageContainer(connectedMessage);
    };
    socket.onclose = function () {
        var disconnectedMessage =  {"source": "failchat","text":"disconnected"};
        handleInfoMessage(disconnectedMessage);
        appendToMessageContainer(disconnectedMessage);
    };
    socket.onmessage = function (event) {
        var wsm = JSON.parse(event.data);

        if (wsm.type === "message") {
            handleMessage(wsm.content);
        }
        else if (wsm.type === "info") {
            handleInfoMessage(wsm.content);
        }

        if (wsm.content.text !== undefined) {
            appendToMessageContainer(wsm.content);
        }
    };

    function handleMessage(message) {
        //smiles
        if (message.smiles != undefined) {
            var imgHtml;
            for (var i = 0; i < message.smiles.length; i++) {
                imgHtml = smileTemplate.render(message.smiles[i]);
                message.text = message.text.replace("{!" + message.smiles[i].objectNumber + "}", imgHtml);
            }
        }

        //links
        if (message.links != undefined) {
            var linkHtml;
            for (i = 0; i < message.links.length; i++) {
                linkHtml = linkTemplate.render(message.links[i]);
                message.text = message.text.replace("{!" + message.links[i].objectNumber + "}", linkHtml);
            }
        }

        message.text = messageTemplate.render(message);
    }

    function handleInfoMessage(infoMessage) {
        infoMessage.text =  infoMessageTemplate.render(infoMessage);
    }

    function appendToMessageContainer(message) {
        messageCount++;
        messageContainer.append(message.text);
        if (messageCount > failchat.maxMessages && autoScroll) {
            while (messageCount > failchat.maxMessages) {
                messageContainer.find("> :first").remove();
                messageCount--;
            }
        }
        if (autoScroll) {
            if (message.smiles !== undefined && failchat.scrollHookSelector !== undefined) {
                scrollHook();
            } else {
                scroller.scrollTop(messageContainer.height());
            }
        }
        //console.log("messages: " + messageCount);
    }

    $("body,html").bind("keydown wheel mousewheel", function(e){
        //console.log(e);
        if (autoScroll) {
            if (e.originalEvent.deltaY < 0 ||
                (e.type == "keydown" && (e.keyCode == 38||e.keyCode == 36||e.keyCode == 33)) // 38-up;36-home;33-pageup
            ) {
                autoScroll = false;
                scrollBar.css("visibility", "visible");
            }
            if (e.type == "mousewheel") { // for old browsers
                autoScroll = scroller.scrollTop() + scroller.height() >= messageContainer.height();
                if (!autoScroll) {
                    scrollBar.css("visibility", "visible");
                }
            }
        }
    });

    scroller.scroll(function (e) {
        if (!autoScroll) {
            autoScroll = scroller.scrollTop() + scroller.height() >= messageContainer.height();
            if (autoScroll) {
                scrollBar.css("visibility", "hidden");
            }
        }
    });

    // scroll when last smile in message loaded
    function scrollHook() {
        $(failchat.scrollHookSelector + ":last").imagesLoaded(function() {
            //console.log("last smile loaded");
            if (autoScroll) {
                scroller.scrollTop(messageContainer.height());
            }
        });
    }
});<|endoftext|>


<commit_before>define(['backbone', 'marionette', 'underscore', 'jquery',

    'views/header',
    'views/sidebar',
    'views/dialogregion',

    'models/proposal',

    'utils',
    'json!config.json',
    'jquery.cookie', 'jquery-ui',
        ],
function(Backbone, Marionette, _, $, HeaderView, SideBarView, DialogRegion, Proposal, utils, config) {

  window.app = new Marionette.Application()

  console.log('CONFIG', config)

  // Base url for the api
  app.apiurl = config.apiurl

  // Base url for the app
  app.appurl = config.appurl

  // reference to config
  app.config = config

  // Allow us to set a global base url for the API
  var oldSync = Backbone.sync;
  Backbone.sync = function(method, model, options) {
    var url = _.isFunction(model.url) ? model.url() : model.url;

    if (url) {
        options = options || {};
        options.url = app.apiurl+url
    }

    return oldSync.call(this, method, model, options);
  }

  app.addRegions({
    content: '#container',
    header: '#header',
    sidebar: '#sidebar',
    dialog: DialogRegion,
  })

  /*
   Monitor all ajax requests to detect when user needs to login and/or
   is disconnected from the interwebs
  */
  $(document).ajaxError(function(event, xhr, settings, error) {
    console.log('ajax error', 'status', xhr.status, 'code', xhr.statusCode())
    if (xhr.status == 0) app.login(xhr)
  })

  /*
   I'd love to know a better way to do this.
   We try and load a page from the API, when we can read the href of
   the iframe we know we've been reidrected to the resource from CAS
   and the user is logged in.
  */
  app.login = function(xhr) {
    $('iframe').attr('src', app.apiurl+'/proposal/login').load(function() {
        $('#login').dialog('open')
        var poll = true
        var refresh_thread = null
        var refresh = function() {
            if (poll) {
                clearTimeout(refresh_thread)
                /*
                 This will throw an exception if the page is not on the same origin
                 as the client
                */
                try {
                    var loc = $('#login iframe').contents().get(0).location.href
                    poll = false
                    console.log('logged in', loc)
                    $('#login').dialog('close')
                    app.getuser()
                    //if (xhr) Backbone.ajax(xhr)
                } catch (e) {

                }
                refresh_thread = setTimeout(refresh, 1000)
            }

        }
        refresh()
    })
  }

  /*
    Load module routers and startup application
  */
  app.addInitializer(function(options){
    $('#login').dialog({
      resizable: false,
      width: '100%', // : '80%',
      height: $(window).height(),
      modal: true,
      autoOpen: false,
      closeOnEscape: false,
      draggable: false,
      title: 'Login',
      dialogClass: 'no-close'
    })

    require([
        'modules/dc/router',
        'modules/proposal/router',
        'modules/shipment/router',
        'modules/samples/router',
        'modules/projects/router',
        'modules/calendar/router',
        'modules/contact/router',
        'modules/cell/router',
        'modules/assign/router',
        'modules/fault/router',
        'modules/stats/router',
        'modules/blstats/router',
        'modules/status/router',
        'modules/docs/router',
        'modules/feedback/router',
        'modules/mc/router',
        'modules/admin/router',
        ], function() {

        this.sidebarview = new SideBarView()
        app.sidebar.show(this.sidebarview)

        app.getuser()
    })
  })

  /*
   Get the fedid for the logged in user to read cookie
  */
  app.getuser = function() {
    Backbone.ajax({
      url: app.apiurl+'/proposal/user',
      success: function(resp) {
          app.user = resp.user
          app.staff = resp.is_staff
          app.type = resp.ty

          // Breadcrumbs collection
          if (!app.bc) {
            app.bc = new Backbone.Collection()
            if (!this.headerview) this.headerview = new HeaderView({ bc: app.bc })
            app.header.show(this.headerview)
          }

          app.cookie(null, function() {
              if (!Backbone.History.started) app.starthistory()
              console.log('success', resp)
          })
      },
    })
  }

  /*
   Shortcut to navigate
  */
  app.navigate = function(route,  options){
    options || (options = {});
    Backbone.history.navigate(route, options);
    log()
  }

  /*
   Cookie the selected proposal
  */
  app.cookie = function(prop, callback) {
    if (prop) $.cookie('ispyb_prop_'+app.user, prop, { path: '/' })
    else prop = $.cookie('ispyb_prop_'+app.user)

    //console.log('prop', prop, callback)
    if (prop) {
        var proposal = new Proposal({ PROPOSAL: prop })
        proposal.fetch({
            success: function() {
                require(['modules/types/'+proposal.get('TYPE')+'/menu'], function(menus) {
                    proposal.set('MENUS', menus)
                    app.prop = prop
                    app.proposal = proposal
                    app.trigger('proposal:change', proposal)
                    if (callback) callback()
                })
            }
        })
    } else if (callback) callback()
    //app.trigger('proposal:change', prop)
  },

  /*
   Log message to console
  */
  app.log = function() {
    console.log.apply(console, arguments)
  },

  /*
    Show an alert message at the top of the page and highlight
  */
  app.alert = function(options) {
      if (options && options.persist) {
          app.content.$el.find('.content .'+options.persist).remove()
      }

      app.content.$el.find('.content').prepend(new utils.alert(options).render().$el)
  }

  /*
   Show the generic message page (for errors, etc)
  */
  app.message = function(options) {
    app.content.show(new utils.generic_msg(options))
  }

  /*
   Check screen width
  */
  app.mobile = function() {
    return $(window).width() <= 600
  }

  /*
   Show blank loading view
  */
  app.loading = function() {
    var LoadingView = Marionette.ItemView.extend({ className: 'loading-container', template: '

' })
    app.content.show(new LoadingView())
  }

  /*
   Single Event for window scrolling
  */
  $(window).scroll(_.debounce(function() {
    app.trigger('window:scroll')
  }, 10))

  app.starthistory = function() {
      if(Backbone.history){
          Backbone.history.start({ pushState: true, root: app.appurl });

          // Only need this for pushState enabled browsers
          if (Backbone.history && Backbone.history._hasPushState) {
              var $document = $(window.document);
              var openLinkInTab = false;

              var is_relative_to_page = function(href) {
                  return href.match(/^\/|(http:|https:|ftp:|mailto:|javascript:)/) === null;
              };

              var is_routable = function(href) {
                  console.log('routable', href.indexOf('/'))
                  return href.indexOf("#") === -1 && (is_relative_to_page(href) || href.indexOf(Backbone.history.root) > -1 || href.indexOf('/') == 0) && (href.indexOf(app.apiurl) != 0);
              };

              $document.keydown(function(e) {
                  if (e.ctrlKey || e.keyCode === 91) {
                      openLinkInTab = true;
                  }
              });

              $document.keyup(function(e) {
                  openLinkInTab = false;
              });

              $document.on("click", "a", function(e) {
                  var href =  $(this).attr("href");
                  if (!href) return
                  if (is_routable(href)) {
                      e.preventDefault();
                      Backbone.history.navigate(href, true);
                  }
              });
          }

          Backbone.history.on('route', function(route, params) {
              console.log('routing', arguments)
              log()
          })

          if(Backbone.history.fragment === ""){
            //if (!app.prop) app.trigger('proposals:show')
            //else app.trigger('current:show')
          }
      }
  }

  function log() {
      var data = { location: window.location.pathname }
      Backbone.ajax({
          url: app.apiurl+'/proposal/log/',
          type: 'POST',
          data: data
      })
  }

  log()

  return app

})<commit_msg>return personid to app<commit_after>define(['backbone', 'marionette', 'underscore', 'jquery',

    'views/header',
    'views/sidebar',
    'views/dialogregion',

    'models/proposal',

    'utils',
    'json!config.json',
    'jquery.cookie', 'jquery-ui',
        ],
function(Backbone, Marionette, _, $, HeaderView, SideBarView, DialogRegion, Proposal, utils, config) {

  window.app = new Marionette.Application()

  console.log('CONFIG', config)

  // Base url for the api
  app.apiurl = config.apiurl

  // Base url for the app
  app.appurl = config.appurl

  // reference to config
  app.config = config

  // Allow us to set a global base url for the API
  var oldSync = Backbone.sync;
  Backbone.sync = function(method, model, options) {
    var url = _.isFunction(model.url) ? model.url() : model.url;

    if (url) {
        options = options || {};
        options.url = app.apiurl+url
    }

    return oldSync.call(this, method, model, options);
  }

  app.addRegions({
    content: '#container',
    header: '#header',
    sidebar: '#sidebar',
    dialog: DialogRegion,
  })

  /*
   Monitor all ajax requests to detect when user needs to login and/or
   is disconnected from the interwebs
  */
  $(document).ajaxError(function(event, xhr, settings, error) {
    console.log('ajax error', 'status', xhr.status, 'code', xhr.statusCode())
    if (xhr.status == 0) app.login(xhr)
  })

  /*
   I'd love to know a better way to do this.
   We try and load a page from the API, when we can read the href of
   the iframe we know we've been reidrected to the resource from CAS
   and the user is logged in.
  */
  app.login = function(xhr) {
    $('iframe').attr('src', app.apiurl+'/proposal/login').load(function() {
        $('#login').dialog('open')
        var poll = true
        var refresh_thread = null
        var refresh = function() {
            if (poll) {
                clearTimeout(refresh_thread)
                /*
                 This will throw an exception if the page is not on the same origin
                 as the client
                */
                try {
                    var loc = $('#login iframe').contents().get(0).location.href
                    poll = false
                    console.log('logged in', loc)
                    $('#login').dialog('close')
                    app.getuser()
                    //if (xhr) Backbone.ajax(xhr)
                } catch (e) {

                }
                refresh_thread = setTimeout(refresh, 1000)
            }

        }
        refresh()
    })
  }

  /*
    Load module routers and startup application
  */
  app.addInitializer(function(options){
    $('#login').dialog({
      resizable: false,
      width: '100%', // : '80%',
      height: $(window).height(),
      modal: true,
      autoOpen: false,
      closeOnEscape: false,
      draggable: false,
      title: 'Login',
      dialogClass: 'no-close'
    })

    require([
        'modules/dc/router',
        'modules/proposal/router',
        'modules/shipment/router',
        'modules/samples/router',
        'modules/projects/router',
        'modules/calendar/router',
        'modules/contact/router',
        'modules/cell/router',
        'modules/assign/router',
        'modules/fault/router',
        'modules/stats/router',
        'modules/blstats/router',
        'modules/status/router',
        'modules/docs/router',
        'modules/feedback/router',
        'modules/mc/router',
        'modules/admin/router',
        ], function() {

        this.sidebarview = new SideBarView()
        app.sidebar.show(this.sidebarview)

        app.getuser()
    })
  })

  /*
   Get the fedid for the logged in user to read cookie
  */
  app.getuser = function() {
    Backbone.ajax({
      url: app.apiurl+'/proposal/user',
      success: function(resp) {
          app.user = resp.user
          app.personid = resp.personid
          app.staff = resp.is_staff
          app.type = resp.ty

          // Breadcrumbs collection
          if (!app.bc) {
            app.bc = new Backbone.Collection()
            if (!this.headerview) this.headerview = new HeaderView({ bc: app.bc })
            app.header.show(this.headerview)
          }

          app.cookie(null, function() {
              if (!Backbone.History.started) app.starthistory()
              console.log('success', resp)
          })
      },
    })
  }

  /*
   Shortcut to navigate
  */
  app.navigate = function(route,  options){
    options || (options = {});
    Backbone.history.navigate(route, options);
    log()
  }

  /*
   Cookie the selected proposal
  */
  app.cookie = function(prop, callback) {
    if (prop) $.cookie('ispyb_prop_'+app.user, prop, { path: '/' })
    else prop = $.cookie('ispyb_prop_'+app.user)

    //console.log('prop', prop, callback)
    if (prop) {
        var proposal = new Proposal({ PROPOSAL: prop })
        proposal.fetch({
            success: function() {
                require(['modules/types/'+proposal.get('TYPE')+'/menu'], function(menus) {
                    proposal.set('MENUS', menus)
                    app.prop = prop
                    app.proposal = proposal
                    app.trigger('proposal:change', proposal)
                    if (callback) callback()
                })
            }
        })
    } else if (callback) callback()
    //app.trigger('proposal:change', prop)
  },

  /*
   Log message to console
  */
  app.log = function() {
    console.log.apply(console, arguments)
  },

  /*
    Show an alert message at the top of the page and highlight
  */
  app.alert = function(options) {
      if (options && options.persist) {
          app.content.$el.find('.content .'+options.persist).remove()
      }

      app.content.$el.find('.content').prepend(new utils.alert(options).render().$el)
  }

  /*
   Show the generic message page (for errors, etc)
  */
  app.message = function(options) {
    app.content.show(new utils.generic_msg(options))
  }

  /*
   Check screen width
  */
  app.mobile = function() {
    return $(window).width() <= 600
  }

  /*
   Show blank loading view
  */
  app.loading = function() {
    var LoadingView = Marionette.ItemView.extend({ className: 'loading-container', template: '

' })
    app.content.show(new LoadingView())
  }

  /*
   Single Event for window scrolling
  */
  $(window).scroll(_.debounce(function() {
    app.trigger('window:scroll')
  }, 10))

  app.starthistory = function() {
      if(Backbone.history){
          Backbone.history.start({ pushState: true, root: app.appurl });

          // Only need this for pushState enabled browsers
          if (Backbone.history && Backbone.history._hasPushState) {
              var $document = $(window.document);
              var openLinkInTab = false;

              var is_relative_to_page = function(href) {
                  return href.match(/^\/|(http:|https:|ftp:|mailto:|javascript:)/) === null;
              };

              var is_routable = function(href) {
                  console.log('routable', href.indexOf('/'))
                  return href.indexOf("#") === -1 && (is_relative_to_page(href) || href.indexOf(Backbone.history.root) > -1 || href.indexOf('/') == 0) && (href.indexOf(app.apiurl) != 0);
              };

              $document.keydown(function(e) {
                  if (e.ctrlKey || e.keyCode === 91) {
                      openLinkInTab = true;
                  }
              });

              $document.keyup(function(e) {
                  openLinkInTab = false;
              });

              $document.on("click", "a", function(e) {
                  var href =  $(this).attr("href");
                  if (!href) return
                  if (is_routable(href)) {
                      e.preventDefault();
                      Backbone.history.navigate(href, true);
                  }
              });
          }

          Backbone.history.on('route', function(route, params) {
              console.log('routing', arguments)
              log()
          })

          if(Backbone.history.fragment === ""){
            //if (!app.prop) app.trigger('proposals:show')
            //else app.trigger('current:show')
          }
      }
  }

  function log() {
      var data = { location: window.location.pathname }
      Backbone.ajax({
          url: app.apiurl+'/proposal/log/',
          type: 'POST',
          data: data
      })
  }

  log()

  return app

})<|endoftext|>


<commit_before>(function() {
	app = angular.module('root', ['ui.bootstrap', 'app.layout', 'angular-loading-bar', 'app.services']);

	app.controller('RootController', ['$scope', '$window', '$http', '$resource', 'qualifierService', 'lang'
	                                  , function($scope, $window, $http, $resource, qualifierService, lang) {

		$scope.baseName = "root";

		var baseUrl = '/api/root/';

		/**
		 * Resources
		 */
		$scope.rootResource = $resource(baseUrl + "entity", {entityType: "@entityType", rootUserId: "@rootUserId"}, {
			save: { method: 'PUT' }
			, create: { method: 'POST' }
			, search : { method: 'POST', url: baseUrl+'search'}
			, authorize : { method: 'GET'}
		});

		/**
		 * Entity Resource.
		 */
		$scope.entityResource = $resource("/api/entity/:path", { stateId : "@stateId"}, {
			save: { method: 'PUT' }
			, create: { method: 'POST' }
		});

		$scope.root;

		/**
		 * Qualifier
		 */
		$scope.setQualifier = function(value, data) {
			if (Array.isArray(data)) {
				$scope.qualifiers = data;
			}
			$scope.qualifierValue = value;
			$scope.rootList = [];
			$scope.listRoots(value);
		}
		qualifierService.run($resource(baseUrl + "qualifier"), $scope.setQualifier, 0);

		/**
		 * Root entities
		 */
		$scope.rootValue = 0;
		// list
		$scope.listRoots = function(qualifierValue) {
			$scope.rootList = $scope.rootResource.get({entityType: qualifierValue});
			$scope.rootList.$promise.then(function(data) {
				if ($scope.rootValue === 0 && data.length>0) {
					$scope.rootValue = $scope.rootList[0].id;
				}
			})
		};

		/**
		 * get states.
		 */
		$scope.getStates = function(){
			$scope.entityResource.query({path: 'state'}).$promise.then(function(data){
				$scope.states = data;
				if(data.length>0){
					$scope.stateId = data[0].id;
					$scope.getCities($scope.stateId);
				}
			})
		};

		/**
		 * get cities.
		 */
		$scope.getCities = function(val){
			$scope.entityResource.query({path: 'city', stateId:val}).$promise.then(function(data){
				$scope.cities = data;
			})
		};

		/**
		 * Faz  a pesquisa
		 */
		$scope.search = function(page, searchUrl) {
			$scope.searchString = $("#searchString").val();
			search(page, $scope.searchString, searchUrl) ;
		}

		function search(page, searchString, searchUrl) {
			var dataObj = {
					"searchString" : searchString ,
					"qualifierValue" : $scope.categoryId,
			};
			var res =  $http.post(searchUrl+'/'+$scope.sectionTab+'/'+page
					, dataObj
					, {});
			res.success(function(data, status, headers, config) {
				$scope.resultFromSearch = data;
				$scope.searchBool = true;
				$scope.page = page;
				$scope.nextAndPrevious = genericServices.getNextAndPreviousLinkByList(data);
				if(data.totalElements == 1){
					$scope.root = data.content[0];
				}
				else if(data.totalElements>1) {
					$scope.rootList = data;
				}
				else {
					$scope.rootList = [];
				}
			})
		}
		// get
		$scope.getRoot = function(id) {
			console.log("USER ID = "+id);
			if (id==0) {
				$scope.root = $scope.rootResource.create({categoryId:$scope.qualifierValue});
			}
			else {
				$scope.root = $scope.rootResource.get({userId: id});
			}
			$scope.root.$promise.then(
					function(data, getReponseHeaders) {
						if (data.length>0) {
							$scope.rootValue = data.userId;
						}
					}
			);
		};
		// authorize
		$scope.authorize = function() {
			$scope.newUser = $scope.rootResource.authorize({rootUserId:$scope.root.userId});
			$scope.newUser.$promise.then(
					function(data, getReponseHeaders) {
						if (data.success==1) {
							$window.location = "/";
						}
					}
			);
		}
		/**
		 * Entity
		 */
		$scope.createEntity = function(){
			$scope.entityResource.create().$promise.then(
					function(data){
						$scope.entity = data;
						$scope.getStates();
						$scope.openForm('form-root-entity')
					});
		}

		$scope.saveEntity = function(){
			$scope.entityResource.save($scope.entity).$promise.then(
					function(data){
						$scope.entity = data;
						$("#modalBody").modal('hide');
					});
		}

		/**
		 * Abre um modal.
		 *
		 * @param formName Nome do Fragmento (form-YYYY)
		 *
		 */
		$scope.openForm = function(formName){
			$scope.message =[];
			//inicialização em form-report
			//$scope.createPart = false;
			//$('#save-report input[type="text"]').removeAttr('readonly').val('');

			$scope.formUrl = '/ng/entity/'+formName+'.html';
			console.log($scope.formUrl);
			$("#modalBody").modal('show');
		}

		/**
		 * Retorna o form a ser mostrado no Modal
		 */
		$scope.getFormUrl = function(){
			return $scope.formUrl;
		}

		$scope.open = function($event,value) {
			$event.preventDefault();
			$event.stopPropagation();
			$scope.datePicker = [];
			$scope.datePicker[value]=true;
		};
		//formato do combobox
		$scope.formats = ['ativo', 'inativo'];
		$scope.format = $scope.formats[0];

		$scope.openForm = function(formName){
			openForm(formName);
		}

		function openForm(formName){
			$scope.message = [];
			//inicialização em form
			$scope.formUrl = '/assets/'+$scope.baseName+'/'+formName+'.html';
			$("#modalBody").modal('show');

		}

	}])

	.directive('rootWrapper', function() {
		return {
			restrict: 'EA',
			scope: { root: '='},
			transclude: true,
			templateUrl: '/assets/root/ng-root-template-entity.html'
		}

	})

})();
<commit_msg>Added location resource.<commit_after>(function() {
	app = angular.module('root', ['ui.bootstrap', 'app.layout', 'angular-loading-bar', 'app.services']);

	app.controller('RootController', ['$scope', '$window', '$http', '$resource', 'qualifierService', 'lang'
	                                  , function($scope, $window, $http, $resource, qualifierService, lang) {

		$scope.baseName = "root";

		var baseUrl = '/api/root/';

		/**
		 * Resources
		 */
		$scope.rootResource = $resource(baseUrl + "entity", {entityType: "@entityType", rootUserId: "@rootUserId"}, {
			save: { method: 'PUT' }
			, create: { method: 'POST' }
			, search : { method: 'POST', url: baseUrl+'search'}
			, authorize : { method: 'GET'}
		});

		/**
		 * Entity Resource.
		 */
		$scope.entityResource = $resource("/api/entity/:path", {}, {
			save: { method: 'PUT' }
			, create: { method: 'POST' }
		});

		/**
		 * Location Resource.
		 */
		$scope.locationResource = $resource("/api/location/:path", { stateId : "@stateId"}, {
			save: { method: 'PUT' }
			, create: { method: 'POST' }
		});

		$scope.root;

		/**
		 * Qualifier
		 */
		$scope.setQualifier = function(value, data) {
			if (Array.isArray(data)) {
				$scope.qualifiers = data;
			}
			$scope.qualifierValue = value;
			$scope.rootList = [];
			$scope.listRoots(value);
		}
		qualifierService.run($resource(baseUrl + "qualifier"), $scope.setQualifier, 0);

		/**
		 * Root entities
		 */
		$scope.rootValue = 0;
		// list
		$scope.listRoots = function(qualifierValue) {
			$scope.rootList = $scope.rootResource.get({entityType: qualifierValue});
			$scope.rootList.$promise.then(function(data) {
				if ($scope.rootValue === 0 && data.length>0) {
					$scope.rootValue = $scope.rootList[0].id;
				}
			})
		};

		/**
		 * get states.
		 */
		$scope.getStates = function(){
			$scope.locationResource.query({path: 'state'}).$promise.then(function(data){
				$scope.states = data;
				if(data.length>0){
					$scope.stateId = data[0].id;
					$scope.getCities($scope.stateId);
				}
			})
		};

		/**
		 * get cities.
		 */
		$scope.getCities = function(val){
			$scope.locationResource.query({path: 'city', stateId:val}).$promise.then(function(data){
				$scope.cities = data;
			})
		};

		/**
		 * Faz  a pesquisa
		 */
		$scope.search = function(page, searchUrl) {
			$scope.searchString = $("#searchString").val();
			search(page, $scope.searchString, searchUrl) ;
		}

		function search(page, searchString, searchUrl) {
			var dataObj = {
					"searchString" : searchString ,
					"qualifierValue" : $scope.categoryId,
			};
			var res =  $http.post(searchUrl+'/'+$scope.sectionTab+'/'+page
					, dataObj
					, {});
			res.success(function(data, status, headers, config) {
				$scope.resultFromSearch = data;
				$scope.searchBool = true;
				$scope.page = page;
				$scope.nextAndPrevious = genericServices.getNextAndPreviousLinkByList(data);
				if(data.totalElements == 1){
					$scope.root = data.content[0];
				}
				else if(data.totalElements>1) {
					$scope.rootList = data;
				}
				else {
					$scope.rootList = [];
				}
			})
		}
		// get
		$scope.getRoot = function(id) {
			console.log("USER ID = "+id);
			if (id==0) {
				$scope.root = $scope.rootResource.create({categoryId:$scope.qualifierValue});
			}
			else {
				$scope.root = $scope.rootResource.get({userId: id});
			}
			$scope.root.$promise.then(
					function(data, getReponseHeaders) {
						if (data.length>0) {
							$scope.rootValue = data.userId;
						}
					}
			);
		};
		// authorize
		$scope.authorize = function() {
			$scope.newUser = $scope.rootResource.authorize({rootUserId:$scope.root.userId});
			$scope.newUser.$promise.then(
					function(data, getReponseHeaders) {
						if (data.success==1) {
							$window.location = "/";
						}
					}
			);
		}
		/**
		 * Entity
		 */
		$scope.createEntity = function(){
			$scope.entityResource.create().$promise.then(
					function(data){
						$scope.entity = data;
						$scope.getStates();
						$scope.openForm('form-root-entity')
					});
		}

		$scope.saveEntity = function(){
			$scope.entityResource.save($scope.entity).$promise.then(
					function(data){
						$scope.entity = data;
						$("#modalBody").modal('hide');
					});
		}

		/**
		 * Abre um modal.
		 *
		 * @param formName Nome do Fragmento (form-YYYY)
		 *
		 */
		$scope.openForm = function(formName){
			$scope.message =[];
			//inicialização em form-report
			//$scope.createPart = false;
			//$('#save-report input[type="text"]').removeAttr('readonly').val('');

			$scope.formUrl = '/ng/entity/'+formName+'.html';
			console.log($scope.formUrl);
			$("#modalBody").modal('show');
		}

		/**
		 * Retorna o form a ser mostrado no Modal
		 */
		$scope.getFormUrl = function(){
			return $scope.formUrl;
		}

		$scope.open = function($event,value) {
			$event.preventDefault();
			$event.stopPropagation();
			$scope.datePicker = [];
			$scope.datePicker[value]=true;
		};
		//formato do combobox
		$scope.formats = ['ativo', 'inativo'];
		$scope.format = $scope.formats[0];

		$scope.openForm = function(formName){
			openForm(formName);
		}

		function openForm(formName){
			$scope.message = [];
			//inicialização em form
			$scope.formUrl = '/assets/'+$scope.baseName+'/'+formName+'.html';
			$("#modalBody").modal('show');

		}

	}])

	.directive('rootWrapper', function() {
		return {
			restrict: 'EA',
			scope: { root: '='},
			transclude: true,
			templateUrl: '/assets/root/ng-root-template-entity.html'
		}

	})

})();
<|endoftext|>


<commit_before>/*globals qq, document*/
qq.DragAndDrop = function(o) {
    "use strict";

    var options,
        HIDE_BEFORE_ENTER_ATTR = "qq-hide-dropzone",
        uploadDropZones = [],
        droppedFiles = [],
        disposeSupport = new qq.DisposeSupport();

    options = {
        dropZoneElements: [],
        allowMultipleItems: true,
        classes: {
            dropActive: null
        },
        callbacks: new qq.DragAndDrop.callbacks()
    };

    qq.extend(options, o, true);

    function uploadDroppedFiles(files, uploadDropZone) {
        options.callbacks.dropLog("Grabbed " + files.length + " dropped files.");
        uploadDropZone.dropDisabled(false);
        options.callbacks.processingDroppedFilesComplete(files);
    }

    function traverseFileTree(entry) {
        var dirReader,
            parseEntryPromise = new qq.Promise();

        if (entry.isFile) {
            entry.file(function(file) {
                droppedFiles.push(file);
                parseEntryPromise.success();
            },
            function(fileError) {
                options.callbacks.dropLog("Problem parsing '" + entry.fullPath + "'.  FileError code " + fileError.code + ".", "error");
                parseEntryPromise.failure();
            });
        }
        else if (entry.isDirectory) {
            dirReader = entry.createReader();
            dirReader.readEntries(function(entries) {
                var entriesLeft = entries.length;

                qq.each(entries, function(idx, entry) {
                    traverseFileTree(entry).done(function() {
                        entriesLeft-=1;

                        if (entriesLeft === 0) {
                            parseEntryPromise.success();
                        }
                    });
                });

                if (!entries.length) {
                    parseEntryPromise.success();
                }
            }, function(fileError) {
                options.callbacks.dropLog("Problem parsing '" + entry.fullPath + "'.  FileError code " + fileError.code + ".", "error");
                parseEntryPromise.failure();
            });
        }

        return parseEntryPromise;
    }

    function handleDataTransfer(dataTransfer, uploadDropZone) {
        var pendingFolderPromises = [],
            handleDataTransferPromise = new qq.Promise();

        options.callbacks.processingDroppedFiles();
        uploadDropZone.dropDisabled(true);

        if (dataTransfer.files.length > 1 && !options.allowMultipleItems) {
            options.callbacks.processingDroppedFilesComplete([]);
            options.callbacks.dropError("tooManyFilesError", "");
            uploadDropZone.dropDisabled(false);
            handleDataTransferPromise.failure();
        }
        else {
            droppedFiles = [];

            if (qq.isFolderDropSupported(dataTransfer)) {
                qq.each(dataTransfer.items, function(idx, item) {
                    var entry = item.webkitGetAsEntry();

                    if (entry) {
                        //due to a bug in Chrome's File System API impl - #149735
                        if (entry.isFile) {
                            droppedFiles.push(item.getAsFile());
                        }

                        else {
                            pendingFolderPromises.push(traverseFileTree(entry).done(function() {
                                pendingFolderPromises.pop();
                                if (pendingFolderPromises.length === 0) {
                                    handleDataTransferPromise.success();
                                }
                            }));
                        }
                    }
                });
            }
            else {
                droppedFiles = dataTransfer.files;
            }

            if (pendingFolderPromises.length === 0) {
                handleDataTransferPromise.success();
            }
        }

        return handleDataTransferPromise;
    }

    function setupDropzone(dropArea) {
        var dropZone = new qq.UploadDropZone({
            element: dropArea,
            onEnter: function(e){
                qq(dropArea).addClass(options.classes.dropActive);
                e.stopPropagation();
            },
            onLeaveNotDescendants: function(e){
                qq(dropArea).removeClass(options.classes.dropActive);
            },
            onDrop: function(e){
                qq(dropArea).hasAttribute(HIDE_BEFORE_ENTER_ATTR) && qq(dropArea).hide();
                qq(dropArea).removeClass(options.classes.dropActive);

                handleDataTransfer(e.dataTransfer, dropZone).done(function() {
                    uploadDroppedFiles(droppedFiles, dropZone);
                });
            }
        });

        disposeSupport.addDisposer(function() {
            dropZone.dispose();
        });

        qq(dropArea).hasAttribute(HIDE_BEFORE_ENTER_ATTR) && qq(dropArea).hide();

        uploadDropZones.push(dropZone);

        return dropZone;
    }

    function isFileDrag(dragEvent) {
        var fileDrag;

        qq.each(dragEvent.dataTransfer.types, function(key, val) {
            if (val === "Files") {
                fileDrag = true;
                return false;
            }
        });

        return fileDrag;
    }

    function leavingDocumentOut(e) {
        /* jshint -W041, eqeqeq:false */
            // null coords for Chrome and Safari Windows
        return ((qq.chrome() || (qq.safari() && qq.windows())) && e.clientX == 0 && e.clientY == 0) ||
            // null e.relatedTarget for Firefox
            (qq.firefox() && !e.relatedTarget);
    }

    function setupDragDrop() {
        var dropZones = options.dropZoneElements;

        qq.each(dropZones, function(idx, dropZone) {
            var uploadDropZone = setupDropzone(dropZone);

            // IE <= 9 does not support the File API used for drag+drop uploads
            if (dropZones.length && (!qq.ie() || qq.ie10())) {
                disposeSupport.attach(document, "dragenter", function(e) {
                    if (!uploadDropZone.dropDisabled() && isFileDrag(e)) {
                        qq.each(dropZones, function(idx, dropZone) {
                            // We can't apply styles to non-HTMLElements, since they lack the `style` property
                            if (dropZone instanceof HTMLElement) {
                                qq(dropZone).css({display: "block"});
                            }
                        });
                    }
                });
            }
        });

        disposeSupport.attach(document, "dragleave", function(e) {
            if (leavingDocumentOut(e)) {
                qq.each(dropZones, function(idx, dropZone) {
                    qq(dropZone).hasAttribute(HIDE_BEFORE_ENTER_ATTR) && qq(dropZone).hide();
                });
            }
        });
        disposeSupport.attach(document, "drop", function(e){
            qq.each(dropZones, function(idx, dropZone) {
                qq(dropZone).hasAttribute(HIDE_BEFORE_ENTER_ATTR) && qq(dropZone).hide();
            });
            e.preventDefault();
        });
    }

    setupDragDrop();

    qq.extend(this, {
        setupExtraDropzone: function(element) {
            options.dropZoneElements.push(element);
            setupDropzone(element);
        },

        removeDropzone: function(element) {
            var i,
                dzs = options.dropZoneElements;

            for(i in dzs) {
                if (dzs[i] === element) {
                    return dzs.splice(i, 1);
                }
            }
        },

        dispose: function() {
            disposeSupport.dispose();
            qq.each(uploadDropZones, function(idx, dropZone) {
                dropZone.dispose();
            });
        }
    });
};

qq.DragAndDrop.callbacks = function() {
    "use strict";

    return {
        processingDroppedFiles: function() {},
        processingDroppedFilesComplete: function(files) {},
        dropError: function(code, errorSpecifics) {
            qq.log("Drag & drop error code '" + code + " with these specifics: '" + errorSpecifics + "'", "error");
        },
        dropLog: function(message, level) {
            qq.log(message, level);
        }
    };
};

qq.UploadDropZone = function(o){
    "use strict";

    var options, element, preventDrop, dropOutsideDisabled, disposeSupport = new qq.DisposeSupport();

    options = {
        element: null,
        onEnter: function(e){},
        onLeave: function(e){},
        // is not fired when leaving element by hovering descendants
        onLeaveNotDescendants: function(e){},
        onDrop: function(e){}
    };

    qq.extend(options, o);
    element = options.element;

    function dragover_should_be_canceled(){
        return qq.safari() || (qq.firefox() && qq.windows());
    }

    function disableDropOutside(e){
        // run only once for all instances
        if (!dropOutsideDisabled ){

            // for these cases we need to catch onDrop to reset dropArea
            if (dragover_should_be_canceled){
                disposeSupport.attach(document, "dragover", function(e){
                    e.preventDefault();
                });
            } else {
                disposeSupport.attach(document, "dragover", function(e){
                    if (e.dataTransfer){
                        e.dataTransfer.dropEffect = "none";
                        e.preventDefault();
                    }
                });
            }

            dropOutsideDisabled = true;
        }
    }

    function isValidFileDrag(e){
        // e.dataTransfer currently causing IE errors
        // IE9 does NOT support file API, so drag-and-drop is not possible
        if (qq.ie() && !qq.ie10()) {
            return false;
        }

        var effectTest, dt = e.dataTransfer,
        // do not check dt.types.contains in webkit, because it crashes safari 4
        isSafari = qq.safari();

        // dt.effectAllowed is none in Safari 5
        // dt.types.contains check is for firefox

        // dt.effectAllowed crashes IE11 when files have been dragged from
        // the filesystem
        effectTest = (qq.ie10() || qq.ie11()) ? true : dt.effectAllowed !== "none";
        return dt && effectTest && (dt.files || (!isSafari && dt.types.contains && dt.types.contains("Files")));
    }

    function isOrSetDropDisabled(isDisabled) {
        if (isDisabled !== undefined) {
            preventDrop = isDisabled;
        }
        return preventDrop;
    }

    function attachEvents(){
        disposeSupport.attach(element, "dragover", function(e){
            if (!isValidFileDrag(e)) {
                return;
            }

            // dt.effectAllowed crashes IE11 when files have been dragged from
            // the filesystem
            var effect = (qq.ie() || qq.ie11()) ? null : e.dataTransfer.effectAllowed;
            if (effect === "move" || effect === "linkMove"){
                e.dataTransfer.dropEffect = "move"; // for FF (only move allowed)
            } else {
                e.dataTransfer.dropEffect = "copy"; // for Chrome
            }

            e.stopPropagation();
            e.preventDefault();
        });

        disposeSupport.attach(element, "dragenter", function(e){
            if (!isOrSetDropDisabled()) {
                if (!isValidFileDrag(e)) {
                    return;
                }
                options.onEnter(e);
            }
        });

        disposeSupport.attach(element, "dragleave", function(e){
            if (!isValidFileDrag(e)) {
                return;
            }

            options.onLeave(e);

            var relatedTarget = document.elementFromPoint(e.clientX, e.clientY);
            // do not fire when moving a mouse over a descendant
            if (qq(this).contains(relatedTarget)) {
                return;
            }

            options.onLeaveNotDescendants(e);
        });

        disposeSupport.attach(element, "drop", function(e){
            if (!isOrSetDropDisabled()) {
                if (!isValidFileDrag(e)) {
                    return;
                }

                e.preventDefault();
                options.onDrop(e);
            }
        });
    }

    disableDropOutside();
    attachEvents();

    qq.extend(this, {
        dropDisabled: function(isDisabled) {
            return isOrSetDropDisabled(isDisabled);
        },

        dispose: function() {
            disposeSupport.dispose();
        }
    });
};
<commit_msg>support for nested dropzones<commit_after>/*globals qq, document*/
qq.DragAndDrop = function(o) {
    "use strict";

    var options,
        HIDE_BEFORE_ENTER_ATTR = "qq-hide-dropzone",
        uploadDropZones = [],
        droppedFiles = [],
        disposeSupport = new qq.DisposeSupport();

    options = {
        dropZoneElements: [],
        allowMultipleItems: true,
        classes: {
            dropActive: null
        },
        callbacks: new qq.DragAndDrop.callbacks()
    };

    qq.extend(options, o, true);

    function uploadDroppedFiles(files, uploadDropZone) {
        options.callbacks.dropLog("Grabbed " + files.length + " dropped files.");
        uploadDropZone.dropDisabled(false);
        options.callbacks.processingDroppedFilesComplete(files);
    }

    function traverseFileTree(entry) {
        var dirReader,
            parseEntryPromise = new qq.Promise();

        if (entry.isFile) {
            entry.file(function(file) {
                droppedFiles.push(file);
                parseEntryPromise.success();
            },
            function(fileError) {
                options.callbacks.dropLog("Problem parsing '" + entry.fullPath + "'.  FileError code " + fileError.code + ".", "error");
                parseEntryPromise.failure();
            });
        }
        else if (entry.isDirectory) {
            dirReader = entry.createReader();
            dirReader.readEntries(function(entries) {
                var entriesLeft = entries.length;

                qq.each(entries, function(idx, entry) {
                    traverseFileTree(entry).done(function() {
                        entriesLeft-=1;

                        if (entriesLeft === 0) {
                            parseEntryPromise.success();
                        }
                    });
                });

                if (!entries.length) {
                    parseEntryPromise.success();
                }
            }, function(fileError) {
                options.callbacks.dropLog("Problem parsing '" + entry.fullPath + "'.  FileError code " + fileError.code + ".", "error");
                parseEntryPromise.failure();
            });
        }

        return parseEntryPromise;
    }

    function handleDataTransfer(dataTransfer, uploadDropZone) {
        var pendingFolderPromises = [],
            handleDataTransferPromise = new qq.Promise();

        options.callbacks.processingDroppedFiles();
        uploadDropZone.dropDisabled(true);

        if (dataTransfer.files.length > 1 && !options.allowMultipleItems) {
            options.callbacks.processingDroppedFilesComplete([]);
            options.callbacks.dropError("tooManyFilesError", "");
            uploadDropZone.dropDisabled(false);
            handleDataTransferPromise.failure();
        }
        else {
            droppedFiles = [];

            if (qq.isFolderDropSupported(dataTransfer)) {
                qq.each(dataTransfer.items, function(idx, item) {
                    var entry = item.webkitGetAsEntry();

                    if (entry) {
                        //due to a bug in Chrome's File System API impl - #149735
                        if (entry.isFile) {
                            droppedFiles.push(item.getAsFile());
                        }

                        else {
                            pendingFolderPromises.push(traverseFileTree(entry).done(function() {
                                pendingFolderPromises.pop();
                                if (pendingFolderPromises.length === 0) {
                                    handleDataTransferPromise.success();
                                }
                            }));
                        }
                    }
                });
            }
            else {
                droppedFiles = dataTransfer.files;
            }

            if (pendingFolderPromises.length === 0) {
                handleDataTransferPromise.success();
            }
        }

        return handleDataTransferPromise;
    }

    function setupDropzone(dropArea) {
        var dropZone = new qq.UploadDropZone({
            element: dropArea,
            onEnter: function(e){
                qq(dropArea).addClass(options.classes.dropActive);
                e.stopPropagation();
            },
            onLeaveNotDescendants: function(e){
                qq(dropArea).removeClass(options.classes.dropActive);
            },
            onDrop: function(e){
                qq(dropArea).hasAttribute(HIDE_BEFORE_ENTER_ATTR) && qq(dropArea).hide();
                qq(dropArea).removeClass(options.classes.dropActive);

                handleDataTransfer(e.dataTransfer, dropZone).done(function() {
                    uploadDroppedFiles(droppedFiles, dropZone);
                });
            }
        });

        disposeSupport.addDisposer(function() {
            dropZone.dispose();
        });

        qq(dropArea).hasAttribute(HIDE_BEFORE_ENTER_ATTR) && qq(dropArea).hide();

        uploadDropZones.push(dropZone);

        return dropZone;
    }

    function isFileDrag(dragEvent) {
        var fileDrag;

        qq.each(dragEvent.dataTransfer.types, function(key, val) {
            if (val === "Files") {
                fileDrag = true;
                return false;
            }
        });

        return fileDrag;
    }

    function leavingDocumentOut(e) {
        /* jshint -W041, eqeqeq:false */
            // null coords for Chrome and Safari Windows
        return ((qq.chrome() || (qq.safari() && qq.windows())) && e.clientX == 0 && e.clientY == 0) ||
            // null e.relatedTarget for Firefox
            (qq.firefox() && !e.relatedTarget);
    }

    function setupDragDrop() {
        var dropZones = options.dropZoneElements;

        qq.each(dropZones, function(idx, dropZone) {
            var uploadDropZone = setupDropzone(dropZone);

            // IE <= 9 does not support the File API used for drag+drop uploads
            if (dropZones.length && (!qq.ie() || qq.ie10())) {
                disposeSupport.attach(document, "dragenter", function(e) {
                    if (!uploadDropZone.dropDisabled() && isFileDrag(e)) {
                        qq.each(dropZones, function(idx, dropZone) {
                            // We can't apply styles to non-HTMLElements, since they lack the `style` property
                            if (dropZone instanceof HTMLElement) {
                                qq(dropZone).css({display: "block"});
                            }
                        });
                    }
                });
            }
        });

        disposeSupport.attach(document, "dragleave", function(e) {
            if (leavingDocumentOut(e)) {
                qq.each(dropZones, function(idx, dropZone) {
                    qq(dropZone).hasAttribute(HIDE_BEFORE_ENTER_ATTR) && qq(dropZone).hide();
                });
            }
        });
        disposeSupport.attach(document, "drop", function(e){
            qq.each(dropZones, function(idx, dropZone) {
                qq(dropZone).hasAttribute(HIDE_BEFORE_ENTER_ATTR) && qq(dropZone).hide();
            });
            e.preventDefault();
        });
    }

    setupDragDrop();

    qq.extend(this, {
        setupExtraDropzone: function(element) {
            options.dropZoneElements.push(element);
            setupDropzone(element);
        },

        removeDropzone: function(element) {
            var i,
                dzs = options.dropZoneElements;

            for(i in dzs) {
                if (dzs[i] === element) {
                    return dzs.splice(i, 1);
                }
            }
        },

        dispose: function() {
            disposeSupport.dispose();
            qq.each(uploadDropZones, function(idx, dropZone) {
                dropZone.dispose();
            });
        }
    });
};

qq.DragAndDrop.callbacks = function() {
    "use strict";

    return {
        processingDroppedFiles: function() {},
        processingDroppedFilesComplete: function(files) {},
        dropError: function(code, errorSpecifics) {
            qq.log("Drag & drop error code '" + code + " with these specifics: '" + errorSpecifics + "'", "error");
        },
        dropLog: function(message, level) {
            qq.log(message, level);
        }
    };
};

qq.UploadDropZone = function(o){
    "use strict";

    var options, element, preventDrop, dropOutsideDisabled, disposeSupport = new qq.DisposeSupport();

    options = {
        element: null,
        onEnter: function(e){},
        onLeave: function(e){},
        // is not fired when leaving element by hovering descendants
        onLeaveNotDescendants: function(e){},
        onDrop: function(e){}
    };

    qq.extend(options, o);
    element = options.element;

    function dragover_should_be_canceled(){
        return qq.safari() || (qq.firefox() && qq.windows());
    }

    function disableDropOutside(e){
        // run only once for all instances
        if (!dropOutsideDisabled ){

            // for these cases we need to catch onDrop to reset dropArea
            if (dragover_should_be_canceled){
                disposeSupport.attach(document, "dragover", function(e){
                    e.preventDefault();
                });
            } else {
                disposeSupport.attach(document, "dragover", function(e){
                    if (e.dataTransfer){
                        e.dataTransfer.dropEffect = "none";
                        e.preventDefault();
                    }
                });
            }

            dropOutsideDisabled = true;
        }
    }

    function isValidFileDrag(e){
        // e.dataTransfer currently causing IE errors
        // IE9 does NOT support file API, so drag-and-drop is not possible
        if (qq.ie() && !qq.ie10()) {
            return false;
        }

        var effectTest, dt = e.dataTransfer,
        // do not check dt.types.contains in webkit, because it crashes safari 4
        isSafari = qq.safari();

        // dt.effectAllowed is none in Safari 5
        // dt.types.contains check is for firefox

        // dt.effectAllowed crashes IE11 when files have been dragged from
        // the filesystem
        effectTest = (qq.ie10() || qq.ie11()) ? true : dt.effectAllowed !== "none";
        return dt && effectTest && (dt.files || (!isSafari && dt.types.contains && dt.types.contains("Files")));
    }

    function isOrSetDropDisabled(isDisabled) {
        if (isDisabled !== undefined) {
            preventDrop = isDisabled;
        }
        return preventDrop;
    }

    function attachEvents(){
        disposeSupport.attach(element, "dragover", function(e){
            if (!isValidFileDrag(e)) {
                return;
            }

            // dt.effectAllowed crashes IE11 when files have been dragged from
            // the filesystem
            var effect = (qq.ie() || qq.ie11()) ? null : e.dataTransfer.effectAllowed;
            if (effect === "move" || effect === "linkMove"){
                e.dataTransfer.dropEffect = "move"; // for FF (only move allowed)
            } else {
                e.dataTransfer.dropEffect = "copy"; // for Chrome
            }

            e.stopPropagation();
            e.preventDefault();
        });

        disposeSupport.attach(element, "dragenter", function(e){
            if (!isOrSetDropDisabled()) {
                if (!isValidFileDrag(e)) {
                    return;
                }
                options.onEnter(e);
            }
        });

        disposeSupport.attach(element, "dragleave", function(e){
            if (!isValidFileDrag(e)) {
                return;
            }

            options.onLeave(e);

            var relatedTarget = document.elementFromPoint(e.clientX, e.clientY);
            // do not fire when moving a mouse over a descendant
            if (qq(this).contains(relatedTarget)) {
                return;
            }

            options.onLeaveNotDescendants(e);
        });

        disposeSupport.attach(element, "drop", function(e){
            if (!isOrSetDropDisabled()) {
                if (!isValidFileDrag(e)) {
                    return;
                }

                e.preventDefault();
                e.stopPropagation();
                options.onDrop(e);
            }
        });
    }

    disableDropOutside();
    attachEvents();

    qq.extend(this, {
        dropDisabled: function(isDisabled) {
            return isOrSetDropDisabled(isDisabled);
        },

        dispose: function() {
            disposeSupport.dispose();
        }
    });
};
<|endoftext|>