<commit_before>var createWhiteboard = function(containerElement) {
  var containerZ,
    prevContainerWidth, prevContainerHeight;

  var socket;

  var mouse2dPosTracker; // relative to container

  var DRAW_LAYER_RELATIVE_Z = 0; // bottom layer
  var drawCanvas, drawCtx,
    colorHex, penSize, // colorHex is a hex string
    cacheCanvas, cacheCtx, // hidden, used to redraw canvas on resize
    isDrawing;

  var GFX_LAYER_RELATIVE_Z = 1; // middle layer
  var camera, scene, renderer,
    clock, tick,
    particleSystem,
    spawnerOpts,
    unanimatedLines;

  var OVERLAY_RELATIVE_Z = 2; // top layer
  var pickerShape,
    uCountSVGText;

  var DEFAULT_COLORHEX = 'aa88ff'; // alternatively, 0xffffff

  // ---------------- end shared var declarations

  socket = io(); // WAOW! AMAZING!

  recordContainerDimensions();
  initPosTrackers();

  initDrawLayer();
  initGFXLayer();
  initOverlay();

  animate();
  initEventHandlers();

  function recordContainerDimensions() {
    containerZ = getZIndex(containerElement);
    prevContainerWidth = containerElement.offsetWidth;
    prevContainerHeight = containerElement.offsetHeight;
  }

  // ---------------- events

  function initEventHandlers() {
    containerElement.addEventListener('mousedown', handleMousePress, true);
    containerElement.addEventListener('mousemove', handleMouseMove, true);
    containerElement.addEventListener('mouseup', handleMouseRelease, true);

    containerElement.addEventListener('touchstart', handleTouch, true);
    containerElement.addEventListener('touchmove', handleTouch, true);
    containerElement.addEventListener('touchend', handleTouch, true);
    containerElement.addEventListener('touchcancel', handleTouch, true);

    // Detect mouse release outside the window
    document.addEventListener('mouseup', handleMouseRelease, true);
    // Clear position when mouse outside window but continue drawing when come back
    document.onmouseout = handleMouseOut;

    // TODO make it only work when whiteboard container is active
    document.addEventListener('keypress', handleKeypress, true);

    var RESIZE_HANDLING_RATE = 4; // 4fps resizing responsiveness
    window.addEventListener('resize', createThrottledResizeHandler(RESIZE_HANDLING_RATE));
  }

  function handleTouch(e) {
    var touches = e.changedTouches;
    var first = touches[0];
    var type = '';
    switch (e.type) {
      case 'touchstart':
        type = 'mousedown';
        break;
      case 'touchmove':
        type = 'mousemove';
        break;
      case 'touchend':
        type = 'mouseup';
        break;
      default:
        return;
    }
    var simulatedMouseEvent = document.createEvent('MouseEvent');
    simulatedMouseEvent.initMouseEvent(type, true, true, window, 1,
      first.screenX, first.screenY,
      first.clientX, first.clientY, false,
      false, false, false, 0 /* left*/, null);

    first.target.dispatchEvent(simulatedMouseEvent);
    // Don't prevent default for color picker
    // event.preventDefault();
  }

  function handleKeypress(e) {
    var key = e.which || e.keyCode;
    if (key === 99) {
      // "c" pressed
      clearScreen();
    }
  }

  function handleMousePress(e) {
    e.preventDefault();
    var targetPagePos = getPagePosition(e.target);
    var containerPagePos = getPagePosition(containerElement);
    var x = e.offsetX + targetPagePos.x - containerPagePos.x;
    var y = e.offsetY + targetPagePos.y - containerPagePos.y;
    updateTracker(mouse2dPosTracker, x, y);
    isDrawing = true;
  }

  function handleMouseRelease() {
    isDrawing = false;
    resetPosTrackers();
  }

  function handleMouseMove(e) {
    if (!isDrawing) return;

    var targetPagePos = getPagePosition(e.target);
    var containerPagePos = getPagePosition(containerElement);
    var x = e.offsetX + targetPagePos.x - containerPagePos.x;
    var y = e.offsetY + targetPagePos.y - containerPagePos.y;
    updateTracker(mouse2dPosTracker, x, y);
    var newLine = {
      startX: mouse2dPosTracker.prevX,
      startY: mouse2dPosTracker.prevY,
      endX: mouse2dPosTracker.newX,
      endY: mouse2dPosTracker.newY,
      width: penSize,
      colorHex: colorHex
    };

    drawLine(newLine);
    unanimatedLines.push(newLine);
    socket.emit('draw line', newLine);
  }

  function handleMouseOut(e) {
    e = e || window.event;
    var from = e.relatedTarget || e.toElement;
    if (!from || from.nodeName === 'HTML') {
      // handleMouseRelease();
      resetPosTrackers();
    }
  }

  /*
   wraps actual resize handling code inside closure and throttles rate of resize
   events triggering and being handled.
   @param targetRate: Number, target rate of resize event handling (per second)
   */
  function createThrottledResizeHandler(targetRate) {
    var resizeTimeout = null; // closure var to track and control resize rate

    return function() {
      if (resizeTimeout != null) return; // resize control timer has not cooled down
      resizeTimeout = setTimeout(
        function() {
          resizeTimeout = null;
          resizeHandler();
        },
        1000 / targetRate
      );
    };
  }

  function resizeHandler() {
    var w = containerElement.offsetWidth;
    var h = containerElement.offsetHeight;
    if (w === prevContainerWidth && h === prevContainerHeight) return;

    camera.aspect = w / h;
    camera.updateProjectionMatrix();
    renderer.setSize(w, h);

    // resize draw canvas and reload image data from cache
    fitCanvasToContainer(drawCanvas);
    var cached = cacheCtx.getImageData(0, 0, w, h);
    drawCtx.putImageData(cached, 0, 0);

    recordContainerDimensions();
  }

  // ---------------- coordinate trackers

  function initPosTrackers() {
    mouse2dPosTracker = {};
    resetPosTrackers();
  }

  function resetPosTrackers() {
    var m = mouse2dPosTracker;
    m.newX =
      m.newY =
      m.prevX =
      m.prevY =
      null;
  }

  function updateTracker(tracker, x, y) {
    tracker.prevX = tracker.newX === null ? x : tracker.newX;
    tracker.prevY = tracker.newY === null ? y : tracker.newY;
    tracker.newX = x;
    tracker.newY = y;
  }

  // ---------------- overlay (color picker, connection count etc)

  function initOverlay() {
    initUserCount();
    initColorPicker();
  }

  function initColorPicker() {
    var pickerElem = initPickerElement();
    var hoverRule = '#' + pickerElem.id + ':hover{cursor:pointer;}';
    addRuleCSS(hoverRule);

    $(pickerElem).spectrum({
      color: '#' + colorHex,
      showButtons: false,
      clickoutFiresChange: true,
      change: function(newColor) {
        colorHex = newColor.toHex();
        pickerElem.setAttribute('fill', '#' + colorHex);
      },
      hide: function(color) {},
      show: function(color) {}
    });
  }

  function initPickerElement() {
    var s;
    colorHex = DEFAULT_COLORHEX;

    var pickerPosDiv = document.createElement('div');
    containerElement.appendChild(pickerPosDiv);
    s = pickerPosDiv.style;
    s.position = 'absolute';
    s.width = '100%';

    var pickerSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
    pickerSvg.id = 'wb-overlay-color-picker-svg';
    // pickerSvg.setAttribute('height', '30');
    // pickerSvg.setAttribute('width', '30');
    pickerPosDiv.appendChild(pickerSvg);
    s = pickerSvg.style;
    s.height = '30px';
    s.width = '30px';
    s.display = 'block';
    s.margin = '0 auto';
    s.top = '35px';
    s.position = 'relative';
    s.zIndex = containerZ + OVERLAY_RELATIVE_Z;

    pickerShape = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
    pickerShape.id = 'wb-overlay-color-picker-shape';
    pickerShape.setAttribute('cx', '15');
    pickerShape.setAttribute('cy', '15');
    pickerShape.setAttribute('r', '15');
    pickerShape.setAttribute('fill', '#' + colorHex);
    pickerSvg.appendChild(pickerShape);
    return pickerShape;
  }

  function initUserCount() {
    var s;

    var uCountSVG = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
    uCountSVG.id = 'wb-overlay-usercount-svg';
    s = uCountSVG.style;
    s.width = '100%';
    s.height = '30px';
    s.position = 'absolute';
    s.left = '10px';
    s.bottom = '10px';
    s.zIndex = containerZ + OVERLAY_RELATIVE_Z;
    containerElement.appendChild(uCountSVG);

    uCountSVGText = document.createElementNS('http://www.w3.org/2000/svg', 'text');
    uCountSVGText.id = 'wb-overlay-usercount-text';
    uCountSVGText.textContent = 'Loading user count...';
    uCountSVGText.setAttribute('x', '0');
    uCountSVGText.setAttribute('y', '30');
    s = uCountSVGText.style;
    s.textAnchor = 'start';
    s.fill = '#00D5B0';
    s.fontSize = '24px';
    s.fontFamily = 'sans-serif';
    uCountSVG.appendChild(uCountSVGText);

    socket.on('user count', updateUserCount);
    socket.emit('get user count');
  }

  function updateUserCount(newCount) {
    uCountSVGText.textContent = 'CONNECTED: ' + newCount;
  }

  // ---------------- draw canvas

  function initDrawLayer() {
    drawCanvas = document.createElement('canvas');
    drawCanvas.id = 'wb-draw-layer-canvas';
    containerElement.appendChild(drawCanvas);
    fitCanvasToContainer(drawCanvas);

    drawCanvas.style.position = 'absolute';
    drawCanvas.style.zIndex = containerZ + DRAW_LAYER_RELATIVE_Z;

    drawCtx = drawCanvas.getContext('2d');
    drawCtx.lineCap = 'round';
    drawCtx.lineJoin = 'round';

    colorHex = DEFAULT_COLORHEX;
    penSize = 2;
    isDrawing = false;

    cacheCanvas = document.createElement('canvas'); // this is not in DOM body
    fitCanvasToContainer(cacheCanvas);
    cacheCtx = cacheCanvas.getContext('2d');

    socket.on('buffered lines', function(lines) {
      lines.forEach(drawLine);
    });
    socket.on('draw line', function(line) {
      drawLine(line);
      unanimatedLines.push(line);
    });
  }

  function drawLine(line) {
    // board update
    drawLineTo2dCtx(line, drawCtx);
    // cache update
    expandCache(
        Math.max(line.startX, line.endX),
        Math.max(line.startY, line.endY)
    );
    drawLineTo2dCtx(line, cacheCtx);
  }

  function drawLineTo2dCtx(line, ctx) {
    ctx.strokeStyle = '#' + line.colorHex;
    ctx.lineWidth = line.width;
    ctx.beginPath();
    ctx.moveTo(line.startX, line.startY);
    ctx.lineTo(line.endX, line.endY);
    ctx.stroke();
  }

  // expands cache if necessary. will not shrink unless cleared (see clearScreen)
  function expandCache(maxX, maxY) {
    var width = cacheCanvas.width;
    var height = cacheCanvas.height;
    // only resize if new line out of bounds
    if (height >= maxY && width >= maxX) return;

    var snapshot = cacheCtx.getImageData(0, 0, cacheCanvas.width, cacheCanvas.height);
    // double dimensions (don't keep resizing for small movements)
    while (maxX > width || maxY > height) {
      width *= 2;
      height *= 2;
    }
    cacheCanvas.width = width;
    cacheCanvas.height = height;
    cacheCtx.putImageData(snapshot, 0, 0);
  }

  function clearScreen() {
    fitCanvasToContainer(cacheCanvas); // reset cache size to match draw canvas size
    cacheCtx.clearRect(0, 0, cacheCanvas.width, cacheCanvas.height);
    drawCtx.clearRect(0, 0, drawCanvas.width, drawCanvas.height);
  }

  // ---------------- 3d effects

  function initGFXLayer() {
    tick = 0;
    clock = new THREE.Clock(true);
    unanimatedLines = [];

    camera = new THREE.PerspectiveCamera(28, containerElement.offsetWidth / containerElement.offsetHeight, 1, 10000);
    camera.position.z = 100;

    renderer = new THREE.WebGLRenderer({
      antialias: true,
      alpha: true
    });
    renderer.setSize(containerElement.offsetWidth, containerElement.offsetHeight);
    renderer.setClearColor(0x000000, 0);

    // See THREE.js github gpuparticle example
    particleSystem = new THREE.GPUParticleSystem({
      maxParticles: 250000
    });
    scene = new THREE.Scene();
    scene.add(particleSystem);

    containerElement.appendChild(renderer.domElement);
    fitCanvasToContainer(renderer.domElement);
    renderer.domElement.id = 'wb-gfx-layer-canvas';

    renderer.domElement.style.zIndex = containerZ + GFX_LAYER_RELATIVE_Z;
    renderer.domElement.style.position = 'absolute';

    spawnerOpts = {
      spawnRate: 3000,
      horizontalSpeed: 0,
      verticalSpeed: 0,
      timeScale: 1
    };
  }

  function animate() {
    requestAnimationFrame(animate);
    var delta = clock.getDelta() * spawnerOpts.timeScale;
    var maxSpawn = spawnerOpts.spawnRate * delta;
    tick += delta;

    unanimatedLines.forEach(function(line) {
      spawnParticlesAlongLine(maxSpawn / unanimatedLines.length, line);
    });
    unanimatedLines = [];

    if (tick < 0) tick = 0;
    particleSystem.update(tick);
    renderer.render(scene, camera);
  }

  function spawnParticlesAlongLine(number, line) {
    for (var i = 0; i < number; i++) {
      var percent = i / number;
      var pos = getWorldPosFromCameraPos(
          line.startX * (1 - percent) + line.endX * percent,
          line.startY * (1 - percent) + line.endY * percent
      );
      particleSystem.spawnParticle({
        position: new THREE.Vector3(pos.x, pos.y, 0),
        positionRandomness: 0.5,
        velocity: new THREE.Vector3(),
        velocityRandomness: 0.5,
        color: parseInt(line.colorHex, 16),
        colorRandomness: 0.2,
        turbulence: 0.4,
        lifetime: 0.8,
        size: 16,
        sizeRandomness: 1
      });
    }
  }

  function getWorldPosFromCameraPos(x, y) {
    var vector = new THREE.Vector3();
    vector.set(
        (x / window.innerWidth) * 2 - 1, -(y / window.innerHeight) * 2 + 1,
        0.5
    );

    vector.unproject(camera);

    var dir = vector.sub(camera.position).normalize();
    var distance = -camera.position.z / dir.z;
    var pos = camera.position.clone().add(dir.multiplyScalar(distance));
    return {
      x: pos.x,
      y: pos.y,
      z: pos.z
    };
  }

  // ---------------- utility

  function fitCanvasToContainer(canvas) {
    canvas.width = containerElement.offsetWidth;
    canvas.height = containerElement.offsetHeight;
  }

  function getZIndex(element) {
    var z = window.document.defaultView.getComputedStyle(element).getPropertyValue('z-index');
    if (element.nodeName === 'BODY') return 0;
    if (isNaN(z)) {
      return getZIndex(element.parentNode);
    }
    return z;
  }

  function getPagePosition(element) {
    var clientRect = element.getBoundingClientRect();
    return {
      x: clientRect.left + window.scrollX,
      y: clientRect.top + window.scrollY
    };
  }

  function addRuleCSS(ruleText) {
    var styleElem = document.createElement('style');
    if (styleElem.styleSheet) {
      styleElem.styleSheet.cssText = ruleText;
    } else {
      styleElem.appendChild(document.createTextNode(ruleText));
    }
    document.getElementsByTagName('head')[0].appendChild(styleElem);
  }
};

$(function() {
  createWhiteboard(document.getElementById('whiteboard'));
});
<commit_msg>fix mouse position for firefox<commit_after>var createWhiteboard = function(containerElement) {
  var containerZ,
    prevContainerWidth, prevContainerHeight;

  var socket;

  var mouse2dPosTracker; // relative to container

  var DRAW_LAYER_RELATIVE_Z = 0; // bottom layer
  var drawCanvas, drawCtx,
    colorHex, penSize, // colorHex is a hex string
    cacheCanvas, cacheCtx, // hidden, used to redraw canvas on resize
    isDrawing;

  var GFX_LAYER_RELATIVE_Z = 1; // middle layer
  var camera, scene, renderer,
    clock, tick,
    particleSystem,
    spawnerOpts,
    unanimatedLines;

  var OVERLAY_RELATIVE_Z = 2; // top layer
  var pickerShape,
    uCountSVGText;

  var DEFAULT_COLORHEX = 'aa88ff'; // alternatively, 0xffffff

  // ---------------- end shared var declarations

  socket = io(); // WAOW! AMAZING!

  recordContainerDimensions();
  initPosTrackers();

  initDrawLayer();
  initGFXLayer();
  initOverlay();

  animate();
  initEventHandlers();

  function recordContainerDimensions() {
    containerZ = getZIndex(containerElement);
    prevContainerWidth = containerElement.offsetWidth;
    prevContainerHeight = containerElement.offsetHeight;
  }

  // ---------------- events

  function initEventHandlers() {
    containerElement.addEventListener('mousedown', handleMousePress, true);
    containerElement.addEventListener('mousemove', handleMouseMove, true);
    containerElement.addEventListener('mouseup', handleMouseRelease, true);

    containerElement.addEventListener('touchstart', handleTouch, true);
    containerElement.addEventListener('touchmove', handleTouch, true);
    containerElement.addEventListener('touchend', handleTouch, true);
    containerElement.addEventListener('touchcancel', handleTouch, true);

    // Detect mouse release outside the window
    document.addEventListener('mouseup', handleMouseRelease, true);
    // Clear position when mouse outside window but continue drawing when come back
    document.onmouseout = handleMouseOut;

    // TODO make it only work when whiteboard container is active
    document.addEventListener('keypress', handleKeypress, true);

    var RESIZE_HANDLING_RATE = 4; // 4fps resizing responsiveness
    window.addEventListener('resize', createThrottledResizeHandler(RESIZE_HANDLING_RATE));
  }

  function handleTouch(e) {
    var touches = e.changedTouches;
    var first = touches[0];
    var type = '';
    switch (e.type) {
      case 'touchstart':
        type = 'mousedown';
        break;
      case 'touchmove':
        type = 'mousemove';
        break;
      case 'touchend':
        type = 'mouseup';
        break;
      default:
        return;
    }
    var simulatedMouseEvent = document.createEvent('MouseEvent');
    simulatedMouseEvent.initMouseEvent(type, true, true, window, 1,
      first.screenX, first.screenY,
      first.clientX, first.clientY, false,
      false, false, false, 0 /* left*/, null);

    first.target.dispatchEvent(simulatedMouseEvent);
    // Don't prevent default for color picker
    // event.preventDefault();
  }

  function handleKeypress(e) {
    var key = e.which || e.keyCode;
    if (key === 99) {
      // "c" pressed
      clearScreen();
    }
  }

  function handleMousePress(e) {
    e.preventDefault();
    // Compatibility fix for firefox
    var xpos = e.offsetX === undefined
      ? (e.originalEvent === undefined ? e.layerX : e.originalEvent.layerX) : e.offsetX;
    var ypos = e.offsetY === undefined
      ? (e.originalEvent === undefined ? e.layerY : e.originalEvent.layerY) : e.offsetY;
    var targetPagePos = getPagePosition(e.target);
    var containerPagePos = getPagePosition(containerElement);
    var x = xpos + targetPagePos.x - containerPagePos.x;
    var y = ypos + targetPagePos.y - containerPagePos.y;
    updateTracker(mouse2dPosTracker, x, y);
    isDrawing = true;
  }

  function handleMouseRelease() {
    isDrawing = false;
    resetPosTrackers();
  }

  function handleMouseMove(e) {
    if (!isDrawing) return;
    // Compatibility fix for firefox
    var xpos = e.offsetX === undefined
      ? (e.originalEvent === undefined ? e.layerX : e.originalEvent.layerX) : e.offsetX;
    var ypos = e.offsetY === undefined
      ? (e.originalEvent === undefined ? e.layerY : e.originalEvent.layerY) : e.offsetY;
    var targetPagePos = getPagePosition(e.target);
    var containerPagePos = getPagePosition(containerElement);
    var x = xpos + targetPagePos.x - containerPagePos.x;
    var y = ypos + targetPagePos.y - containerPagePos.y;
    updateTracker(mouse2dPosTracker, x, y);
    var newLine = {
      startX: mouse2dPosTracker.prevX,
      startY: mouse2dPosTracker.prevY,
      endX: mouse2dPosTracker.newX,
      endY: mouse2dPosTracker.newY,
      width: penSize,
      colorHex: colorHex
    };

    drawLine(newLine);
    unanimatedLines.push(newLine);
    socket.emit('draw line', newLine);
  }

  function handleMouseOut(e) {
    e = e || window.event;
    var from = e.relatedTarget || e.toElement;
    if (!from || from.nodeName === 'HTML') {
      // handleMouseRelease();
      resetPosTrackers();
    }
  }

  /*
   wraps actual resize handling code inside closure and throttles rate of resize
   events triggering and being handled.
   @param targetRate: Number, target rate of resize event handling (per second)
   */
  function createThrottledResizeHandler(targetRate) {
    var resizeTimeout = null; // closure var to track and control resize rate

    return function() {
      if (resizeTimeout != null) return; // resize control timer has not cooled down
      resizeTimeout = setTimeout(
        function() {
          resizeTimeout = null;
          resizeHandler();
        },
        1000 / targetRate
      );
    };
  }

  function resizeHandler() {
    var w = containerElement.offsetWidth;
    var h = containerElement.offsetHeight;
    if (w === prevContainerWidth && h === prevContainerHeight) return;

    camera.aspect = w / h;
    camera.updateProjectionMatrix();
    renderer.setSize(w, h);

    // resize draw canvas and reload image data from cache
    fitCanvasToContainer(drawCanvas);
    var cached = cacheCtx.getImageData(0, 0, w, h);
    drawCtx.putImageData(cached, 0, 0);

    recordContainerDimensions();
  }

  // ---------------- coordinate trackers

  function initPosTrackers() {
    mouse2dPosTracker = {};
    resetPosTrackers();
  }

  function resetPosTrackers() {
    var m = mouse2dPosTracker;
    m.newX =
      m.newY =
      m.prevX =
      m.prevY =
      null;
  }

  function updateTracker(tracker, x, y) {
    tracker.prevX = tracker.newX === null ? x : tracker.newX;
    tracker.prevY = tracker.newY === null ? y : tracker.newY;
    tracker.newX = x;
    tracker.newY = y;
  }

  // ---------------- overlay (color picker, connection count etc)

  function initOverlay() {
    initUserCount();
    initColorPicker();
  }

  function initColorPicker() {
    var pickerElem = initPickerElement();
    var hoverRule = '#' + pickerElem.id + ':hover{cursor:pointer;}';
    addRuleCSS(hoverRule);

    $(pickerElem).spectrum({
      color: '#' + colorHex,
      showButtons: false,
      clickoutFiresChange: true,
      change: function(newColor) {
        colorHex = newColor.toHex();
        pickerElem.setAttribute('fill', '#' + colorHex);
      },
      hide: function(color) {},
      show: function(color) {}
    });
  }

  function initPickerElement() {
    var s;
    colorHex = DEFAULT_COLORHEX;

    var pickerPosDiv = document.createElement('div');
    containerElement.appendChild(pickerPosDiv);
    s = pickerPosDiv.style;
    s.position = 'absolute';
    s.width = '100%';

    var pickerSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
    pickerSvg.id = 'wb-overlay-color-picker-svg';
    // pickerSvg.setAttribute('height', '30');
    // pickerSvg.setAttribute('width', '30');
    pickerPosDiv.appendChild(pickerSvg);
    s = pickerSvg.style;
    s.height = '30px';
    s.width = '30px';
    s.display = 'block';
    s.margin = '0 auto';
    s.top = '35px';
    s.position = 'relative';
    s.zIndex = containerZ + OVERLAY_RELATIVE_Z;

    pickerShape = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
    pickerShape.id = 'wb-overlay-color-picker-shape';
    pickerShape.setAttribute('cx', '15');
    pickerShape.setAttribute('cy', '15');
    pickerShape.setAttribute('r', '15');
    pickerShape.setAttribute('fill', '#' + colorHex);
    pickerSvg.appendChild(pickerShape);
    return pickerShape;
  }

  function initUserCount() {
    var s;

    var uCountSVG = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
    uCountSVG.id = 'wb-overlay-usercount-svg';
    s = uCountSVG.style;
    s.width = '100%';
    s.height = '30px';
    s.position = 'absolute';
    s.left = '10px';
    s.bottom = '10px';
    s.zIndex = containerZ + OVERLAY_RELATIVE_Z;
    containerElement.appendChild(uCountSVG);

    uCountSVGText = document.createElementNS('http://www.w3.org/2000/svg', 'text');
    uCountSVGText.id = 'wb-overlay-usercount-text';
    uCountSVGText.textContent = 'Loading user count...';
    uCountSVGText.setAttribute('x', '0');
    uCountSVGText.setAttribute('y', '30');
    s = uCountSVGText.style;
    s.textAnchor = 'start';
    s.fill = '#00D5B0';
    s.fontSize = '24px';
    s.fontFamily = 'sans-serif';
    uCountSVG.appendChild(uCountSVGText);

    socket.on('user count', updateUserCount);
    socket.emit('get user count');
  }

  function updateUserCount(newCount) {
    uCountSVGText.textContent = 'CONNECTED: ' + newCount;
  }

  // ---------------- draw canvas

  function initDrawLayer() {
    drawCanvas = document.createElement('canvas');
    drawCanvas.id = 'wb-draw-layer-canvas';
    containerElement.appendChild(drawCanvas);
    fitCanvasToContainer(drawCanvas);

    drawCanvas.style.position = 'absolute';
    drawCanvas.style.zIndex = containerZ + DRAW_LAYER_RELATIVE_Z;

    drawCtx = drawCanvas.getContext('2d');
    drawCtx.lineCap = 'round';
    drawCtx.lineJoin = 'round';

    colorHex = DEFAULT_COLORHEX;
    penSize = 2;
    isDrawing = false;

    cacheCanvas = document.createElement('canvas'); // this is not in DOM body
    fitCanvasToContainer(cacheCanvas);
    cacheCtx = cacheCanvas.getContext('2d');

    socket.on('buffered lines', function(lines) {
      lines.forEach(drawLine);
    });
    socket.on('draw line', function(line) {
      drawLine(line);
      unanimatedLines.push(line);
    });
  }

  function drawLine(line) {
    // board update
    drawLineTo2dCtx(line, drawCtx);
    // cache update
    expandCache(
        Math.max(line.startX, line.endX),
        Math.max(line.startY, line.endY)
    );
    drawLineTo2dCtx(line, cacheCtx);
  }

  function drawLineTo2dCtx(line, ctx) {
    ctx.strokeStyle = '#' + line.colorHex;
    ctx.lineWidth = line.width;
    ctx.beginPath();
    ctx.moveTo(line.startX, line.startY);
    ctx.lineTo(line.endX, line.endY);
    ctx.stroke();
  }

  // expands cache if necessary. will not shrink unless cleared (see clearScreen)
  function expandCache(maxX, maxY) {
    var width = cacheCanvas.width;
    var height = cacheCanvas.height;
    // only resize if new line out of bounds
    if (height >= maxY && width >= maxX) return;

    var snapshot = cacheCtx.getImageData(0, 0, cacheCanvas.width, cacheCanvas.height);
    // double dimensions (don't keep resizing for small movements)
    while (maxX > width || maxY > height) {
      width *= 2;
      height *= 2;
    }
    cacheCanvas.width = width;
    cacheCanvas.height = height;
    cacheCtx.putImageData(snapshot, 0, 0);
  }

  function clearScreen() {
    fitCanvasToContainer(cacheCanvas); // reset cache size to match draw canvas size
    cacheCtx.clearRect(0, 0, cacheCanvas.width, cacheCanvas.height);
    drawCtx.clearRect(0, 0, drawCanvas.width, drawCanvas.height);
  }

  // ---------------- 3d effects

  function initGFXLayer() {
    tick = 0;
    clock = new THREE.Clock(true);
    unanimatedLines = [];

    camera = new THREE.PerspectiveCamera(28, containerElement.offsetWidth / containerElement.offsetHeight, 1, 10000);
    camera.position.z = 100;

    renderer = new THREE.WebGLRenderer({
      antialias: true,
      alpha: true
    });
    renderer.setSize(containerElement.offsetWidth, containerElement.offsetHeight);
    renderer.setClearColor(0x000000, 0);

    // See THREE.js github gpuparticle example
    particleSystem = new THREE.GPUParticleSystem({
      maxParticles: 250000
    });
    scene = new THREE.Scene();
    scene.add(particleSystem);

    containerElement.appendChild(renderer.domElement);
    fitCanvasToContainer(renderer.domElement);
    renderer.domElement.id = 'wb-gfx-layer-canvas';

    renderer.domElement.style.zIndex = containerZ + GFX_LAYER_RELATIVE_Z;
    renderer.domElement.style.position = 'absolute';

    spawnerOpts = {
      spawnRate: 3000,
      horizontalSpeed: 0,
      verticalSpeed: 0,
      timeScale: 1
    };
  }

  function animate() {
    requestAnimationFrame(animate);
    var delta = clock.getDelta() * spawnerOpts.timeScale;
    var maxSpawn = spawnerOpts.spawnRate * delta;
    tick += delta;

    unanimatedLines.forEach(function(line) {
      spawnParticlesAlongLine(maxSpawn / unanimatedLines.length, line);
    });
    unanimatedLines = [];

    if (tick < 0) tick = 0;
    particleSystem.update(tick);
    renderer.render(scene, camera);
  }

  function spawnParticlesAlongLine(number, line) {
    for (var i = 0; i < number; i++) {
      var percent = i / number;
      var pos = getWorldPosFromCameraPos(
          line.startX * (1 - percent) + line.endX * percent,
          line.startY * (1 - percent) + line.endY * percent
      );
      particleSystem.spawnParticle({
        position: new THREE.Vector3(pos.x, pos.y, 0),
        positionRandomness: 0.5,
        velocity: new THREE.Vector3(),
        velocityRandomness: 0.5,
        color: parseInt(line.colorHex, 16),
        colorRandomness: 0.2,
        turbulence: 0.4,
        lifetime: 0.8,
        size: 16,
        sizeRandomness: 1
      });
    }
  }

  function getWorldPosFromCameraPos(x, y) {
    var vector = new THREE.Vector3();
    vector.set(
        (x / window.innerWidth) * 2 - 1, -(y / window.innerHeight) * 2 + 1,
        0.5
    );

    vector.unproject(camera);

    var dir = vector.sub(camera.position).normalize();
    var distance = -camera.position.z / dir.z;
    var pos = camera.position.clone().add(dir.multiplyScalar(distance));
    return {
      x: pos.x,
      y: pos.y,
      z: pos.z
    };
  }

  // ---------------- utility

  function fitCanvasToContainer(canvas) {
    canvas.width = containerElement.offsetWidth;
    canvas.height = containerElement.offsetHeight;
  }

  function getZIndex(element) {
    var z = window.document.defaultView.getComputedStyle(element).getPropertyValue('z-index');
    if (element.nodeName === 'BODY') return 0;
    if (isNaN(z)) {
      return getZIndex(element.parentNode);
    }
    return z;
  }

  function getPagePosition(element) {
    var clientRect = element.getBoundingClientRect();
    return {
      x: clientRect.left + window.scrollX,
      y: clientRect.top + window.scrollY
    };
  }

  function addRuleCSS(ruleText) {
    var styleElem = document.createElement('style');
    if (styleElem.styleSheet) {
      styleElem.styleSheet.cssText = ruleText;
    } else {
      styleElem.appendChild(document.createTextNode(ruleText));
    }
    document.getElementsByTagName('head')[0].appendChild(styleElem);
  }
};

$(function() {
  createWhiteboard(document.getElementById('whiteboard'));
});
<|endoftext|>


<commit_before>/* eslint-env node */
'use strict';

const proxyPath = '/cable';

module.exports = function(app, options) {
  let server = options.httpServer;
  // For options, see:
  // https://github.com/nodejitsu/node-http-proxy
  let proxy = require('http-proxy').createProxyServer({
    target: 'http://localhost:3000',
    ws: true,
    changeOrigin: true
  });

  proxy.on('error', function(err, req) {
    console.error(err, req.url);
  });

  server.on('upgrade', function (req, socket, head) {
    proxy.ws(req, socket, head);
  });

  app.use(proxyPath, function(req, res){
    req.url = proxyPath + '/' + req.url;
    proxy.web(req, res);
  });
};
<commit_msg>Change rails port<commit_after>/* eslint-env node */
'use strict';

const proxyPath = '/cable';

module.exports = function(app, options) {
  let server = options.httpServer;
  // For options, see:
  // https://github.com/nodejitsu/node-http-proxy
  let proxy = require('http-proxy').createProxyServer({
    target: 'http://localhost:3006',
    ws: true,
    changeOrigin: true
  });

  proxy.on('error', function(err, req) {
    console.error(err, req.url);
  });

  server.on('upgrade', function (req, socket, head) {
    proxy.ws(req, socket, head);
  });

  app.use(proxyPath, function(req, res){
    req.url = proxyPath + '/' + req.url;
    proxy.web(req, res);
  });
};
<|endoftext|>


<commit_before>'use strict';

exports.reject = function (args, message) {
  var companyName = args[0];
  console.log(message._client.Client.users[message.user], " was rejected from ", companyName)
};
<commit_msg>Potentially wrong structure<commit_after>'use strict';

exports.reject = function (args, message) {
  var companyName = args[0];
  console.log(message._client.users[message.user], " was rejected from ", companyName)
};
<|endoftext|>


<commit_before>var http = require('http');
var EventProxy = require('eventproxy');
var ObjectID = require('mongodb').ObjectID;
var DBRef = require('mongodb').DBRef;
var us = require('underscore');

var config = require('../config');
var ERR = require('../errorcode');
var db = require('../models/db');
var mGroup = require('../models/group');
var mFile = require('../models/file');
var mFolder = require('../models/folder');
var mUser = require('../models/user');
var U = require('../util');
var Logger = require('../logger');

exports.listGroups = function(req, res) {
    var params = req.parameter;
    var loginUser = req.loginUser;

    var query = {};

    if ('status' in params) {
        query.status = params.status;
    }
    if ('type' in params) {
        query.type = params.type;
    }

    if ('parent' in params) {
        if (params.parent) {
            query.parent = { $exists: true };
        } else {
            query.parent = null;
        }
    }

    params.extendQuery = query;

    params.isDeref = true;

    mGroup.search(params, function(err, total, result) {

        if (err) {
            return res.json({
                err: ERR.SERVER_ERROR,
                msg: err
            });
        }

        if (!total) {
            return res.json({
                err: ERR.SUCCESS,
                result: {
                    total: 0,
                    list: []
                }
            });
        }

        var ep = new EventProxy();
        ep.fail(function(err, errCode) {
            return res.json({
                err: errCode || ERR.SERVER_ERROR,
                msg: err
            });
        });

        ep.after('getMemberCountDone', result.length, function() {
            res.json({
                err: ERR.SUCCESS,
                result: {
                    total: total,
                    list: result
                }
            });

        });

        result.forEach(function(doc) {

            mGroup.getGroupMemberCount(doc._id, ep.group('getMemberCountDone', function(count) {
                doc.memberCount = count;
                return count;
            }));
        });

    });
};

exports.approveGroup = function(req, res) {
    var params = req.parameter;
    var group = params.groupId;

    var loginUser = req.loginUser;

    var doc = {
        status: 0,
        validateText: params.validateText || '', //审核评语
        validateStatus: Number(params.validateStatus) || 0, //0 不通过 1 通过
        validateTime: Date.now(), //审核时间
        validator: new DBRef('user', loginUser._id)
    };

    mGroup.modify({
        _id: group._id
    }, doc, function(err, doc) {
        if (err) {
            res.json({
                err: ERR.SERVER_ERROR,
                msg: err
            });
        } else if (!doc) {
            res.json({
                err: ERR.NOT_FOUND,
                msg: 'no such group'
            });
        } else {
            res.json({
                err: ERR.SUCCESS
            });
        }
    });
};

exports.approveFile = function(req, res) {
    var params = req.parameter;
    var file = params.fileId;

    var loginUser = req.loginUser;
    // 这里的文件只有个人提交到学校的
    var validateStatus = Number(params.validateStatus) || 0;
    if (validateStatus === 0) {
        // 審核不通過的刪除掉
        // 这里不用更新学校的空间大小, 文件在没有被审核之前, 是不占空间的
        mFile.delete({
            _id: file._id
        }, { /*updateUsed: true*/ }, function(err /*, doc*/ ) {
            if (err) {
                res.json({
                    err: ERR.SERVER_ERROR,
                    msg: err
                });
            } else {
                res.json({
                    err: ERR.SUCCESS
                });
            }
        });
        return;
    }

    mGroup.getGroup({
        type: 0
    }, function(err, school) {
        if (err) {
            return res.json({
                err: ERR.SERVER_ERROR,
                msg: err
            });
        }

        //这里应该增加学校的空间使用
        mGroup.updateUsed(school._id, file.size, function() {});

        var doc = {
            status: 0,
            validateText: params.validateText || '', //审核评语
            validateStatus: validateStatus, //0 不通过 1 通过
            validateTime: Date.now(), //审核时间
            validator: new DBRef('user', loginUser._id)
        };

        mFile.modify({
            _id: file._id
        }, doc, function(err, doc) {
            if (err) {
                res.json({
                    err: ERR.SERVER_ERROR,
                    msg: err
                });
            } else if (!doc) {
                res.json({
                    err: ERR.NOT_FOUND,
                    msg: 'no such group'
                });
            } else {
                res.json({
                    err: ERR.SUCCESS
                });
            }
        });
    });
};

function fetchGroupMembers(group, callback) {
    mGroup.getGroupMembers(group._id, true, function(err, list) {
        if (err) {
            callback(err);
        } else {
            group.members = list;

            mFolder.statistics(group.rootFolder.oid, function(err, result) {
                if (!err) {
                    group.folderCount = result.total;
                }
                callback(null);
            });
        }
    });

}

function fetchGroupDetail(group, callback) {
    // 处理归档逻辑
    mGroup.archiveCheck(group, function(err, group) {

        db.group.find({
            'parent.$id': group._id
        }, function(err, result) {
            group.list = result;
            if (result && result.length) {
                var ep = new EventProxy();
                ep.after('fetchGroupMembers', result.length, function( /*list*/ ) {
                    callback(null, group);
                });
                ep.fail(function() {
                    callback(null, group);
                });
                result.forEach(function(doc) {
                    fetchGroupMembers(doc, ep.group('fetchGroupMembers'));
                });
            } else {
                callback(null, group);
            }
        });
    });
}

/**
 * 列出所有备课
 * @param  {[type]} req [description]
 * @param  {[type]} res [description]
 * @return {[type]}     [description]
 */
exports.listPrepares = function(req, res) {
    // var loginUser = req.loginUser;
    var parameter = req.parameter;
    var fetchChild = parameter.fetchChild || false;

    var ep = new EventProxy();
    ep.fail(function(err, errCode) {
        res.json({
            err: errCode || ERR.SERVER_ERROR,
            msg: err
        });
    });

    db.group.find({
        type: 3, // type=3 是备课学年
        parent: null
    }, function(result) {
        if (result && result.length) {
            if (fetchChild) {
                ep.after('fetchGroupDetail', result.length, function( /*list*/ ) {
                    ep.emit('fetchGroups', result);
                });
                result.forEach(function(group) {
                    fetchGroupDetail(group, ep.done('fetchGroupDetail'));
                });

            } else {
                ep.emit('fetchGroups', result);
            }
        } else {
            ep.emit('fetchGroups', []);
        }
    });

    ep.on('fetchGroups', function(list) {
        res.json({
            err: ERR.SUCCESS,
            result: {
                list: list
            }
        });
    });
};

/**
 * 列出学校的未审核文件
 * @param  {[type]} req [description]
 * @param  {[type]} res [description]
 * @return {[type]}     [description]
 */
exports.listFiles = function(req, res) {
    // var loginUser = req.loginUser;
    // var params = req.parameter;

    var ep = new EventProxy();
    ep.fail(function(err, errCode) {
        res.json({
            err: errCode || ERR.SERVER_ERROR,
            msg: err
        });
    });

    mGroup.getGroup({
        type: 0
    }, ep.doneLater('getSchoolDone'));

    ep.on('getSchoolDone', function(school) {
        if (!school) {
            return ep.emit('error', 'system error: no school');
        }
        var query = {
            'group.$id': school._id,
            status: 1
        };

        mFile.search({
            extendQuery: query,
            isDeref: true
        }, ep.done('search'));

    });
    ep.on('search', function(total, docs) {
        res.json({
            err: ERR.SUCCESS,
            result: {
                total: total,
                list: docs
            }
        });
    });
};

exports.modifyUser = function(req, res) {

    var params = req.parameter;

    var sizegroup = params.sizegroupId;
    var user = params.userId;
    var doc = {};
    if (sizegroup) {
        doc.sizegroup = new DBRef('sizegroup', sizegroup._id);
        doc.size = sizegroup.size;
    }
    if ('status' in params) {
        doc.status = params.status;
    }

    mUser.update({
        _id: user._id
    }, doc, function(err, result) {
        if (err) {
            return res.json({
                err: result || ERR.SERVER_ERROR,
                msg: err
            });
        }
        res.json({
            err: ERR.SUCCESS
        });

    });

};

/**
 * 统计系统所有文件和小组的数量
 * @param  {[type]} req [description]
 * @param  {[type]} res [description]
 * @return {[type]}     [description]
 */
exports.statistics = function(req, res) {
    // var parameter = req.parameter;

    var ep = new EventProxy();
    ep.fail(function(err, errCode) {
        res.json({
            err: errCode || ERR.SERVER_ERROR,
            msg: err
        });
    });

    var result = {};

    // 所有用户
    db.user.count({}, function(err, num) {
        if (err) {
            Logger.error('[manage.statistics]', err);
        }
        result.totalUser = num || 0;
        ep.emit('totalUser');
    });

    db.group.count({
        type: 1
    }, function(err, num) {
        if (err) {
            Logger.error('[manage.statistics]', err);
        }
        result.totalGroup = num || 0;
        ep.emit('totalGroup');
    });

    db.group.count({
        type: 2
    }, function(err, num) {
        if (err) {
            Logger.error('[manage.statistics]', err);
        }
        result.totalDepartment = num || 0;
        ep.emit('totalDepartment');
    });

    db.folder.count({}, function(err, num) {
        if (err) {
            Logger.error('[manage.statistics]', err);
        }
        result.totalFolder = num || 0;
        ep.emit('totalFolder');
    });

    db.file.find({}, function(err, docs) {
        var totalSize = 0;
        var list = {};
        docs.forEach(function(file) {
            totalSize += file.size || 0;
            var obj = list[file.type];
            if (!obj) {
                list[file.type] = obj = {
                    type: file.type,
                    size: 0,
                    count: 0
                };
            }
            obj.size += file.size || 0;
            obj.count++;
        });

        result.totalFile = docs.length;
        result.totalSize = totalSize;
        result.fileStatistics = list;

        ep.emit('totalFile');
    });

    ep.all('totalUser', 'totalGroup', 'totalDepartment', 'totalFile', 'totalFolder', function() {
        res.json({
            err: ERR.SUCCESS,
            result: result
        });
    });
};<commit_msg>update<commit_after>var http = require('http');
var EventProxy = require('eventproxy');
var ObjectID = require('mongodb').ObjectID;
var DBRef = require('mongodb').DBRef;
var us = require('underscore');

var config = require('../config');
var ERR = require('../errorcode');
var db = require('../models/db');
var mGroup = require('../models/group');
var mFile = require('../models/file');
var mFolder = require('../models/folder');
var mUser = require('../models/user');
var U = require('../util');
var Logger = require('../logger');

exports.listGroups = function(req, res) {
    var params = req.parameter;
    var loginUser = req.loginUser;

    var query = {};

    if ('status' in params) {
        query.status = params.status;
    }
    if ('type' in params) {
        query.type = params.type;
    }

    if ('parent' in params) {
        if (params.parent) {
            query.parent = { $ne: null };
        } else {
            query.parent = null;
        }
    }

    params.extendQuery = query;

    params.isDeref = true;

    mGroup.search(params, function(err, total, result) {

        if (err) {
            return res.json({
                err: ERR.SERVER_ERROR,
                msg: err
            });
        }

        if (!total) {
            return res.json({
                err: ERR.SUCCESS,
                result: {
                    total: 0,
                    list: []
                }
            });
        }

        var ep = new EventProxy();
        ep.fail(function(err, errCode) {
            return res.json({
                err: errCode || ERR.SERVER_ERROR,
                msg: err
            });
        });

        ep.after('getMemberCountDone', result.length, function() {
            res.json({
                err: ERR.SUCCESS,
                result: {
                    total: total,
                    list: result
                }
            });

        });

        result.forEach(function(doc) {

            mGroup.getGroupMemberCount(doc._id, ep.group('getMemberCountDone', function(count) {
                doc.memberCount = count;
                return count;
            }));
        });

    });
};

exports.approveGroup = function(req, res) {
    var params = req.parameter;
    var group = params.groupId;

    var loginUser = req.loginUser;

    var doc = {
        status: 0,
        validateText: params.validateText || '', //审核评语
        validateStatus: Number(params.validateStatus) || 0, //0 不通过 1 通过
        validateTime: Date.now(), //审核时间
        validator: new DBRef('user', loginUser._id)
    };

    mGroup.modify({
        _id: group._id
    }, doc, function(err, doc) {
        if (err) {
            res.json({
                err: ERR.SERVER_ERROR,
                msg: err
            });
        } else if (!doc) {
            res.json({
                err: ERR.NOT_FOUND,
                msg: 'no such group'
            });
        } else {
            res.json({
                err: ERR.SUCCESS
            });
        }
    });
};

exports.approveFile = function(req, res) {
    var params = req.parameter;
    var file = params.fileId;

    var loginUser = req.loginUser;
    // 这里的文件只有个人提交到学校的
    var validateStatus = Number(params.validateStatus) || 0;
    if (validateStatus === 0) {
        // 審核不通過的刪除掉
        // 这里不用更新学校的空间大小, 文件在没有被审核之前, 是不占空间的
        mFile.delete({
            _id: file._id
        }, { /*updateUsed: true*/ }, function(err /*, doc*/ ) {
            if (err) {
                res.json({
                    err: ERR.SERVER_ERROR,
                    msg: err
                });
            } else {
                res.json({
                    err: ERR.SUCCESS
                });
            }
        });
        return;
    }

    mGroup.getGroup({
        type: 0
    }, function(err, school) {
        if (err) {
            return res.json({
                err: ERR.SERVER_ERROR,
                msg: err
            });
        }

        //这里应该增加学校的空间使用
        mGroup.updateUsed(school._id, file.size, function() {});

        var doc = {
            status: 0,
            validateText: params.validateText || '', //审核评语
            validateStatus: validateStatus, //0 不通过 1 通过
            validateTime: Date.now(), //审核时间
            validator: new DBRef('user', loginUser._id)
        };

        mFile.modify({
            _id: file._id
        }, doc, function(err, doc) {
            if (err) {
                res.json({
                    err: ERR.SERVER_ERROR,
                    msg: err
                });
            } else if (!doc) {
                res.json({
                    err: ERR.NOT_FOUND,
                    msg: 'no such group'
                });
            } else {
                res.json({
                    err: ERR.SUCCESS
                });
            }
        });
    });
};

function fetchGroupMembers(group, callback) {
    mGroup.getGroupMembers(group._id, true, function(err, list) {
        if (err) {
            callback(err);
        } else {
            group.members = list;

            mFolder.statistics(group.rootFolder.oid, function(err, result) {
                if (!err) {
                    group.folderCount = result.total;
                }
                callback(null);
            });
        }
    });

}

function fetchGroupDetail(group, callback) {
    // 处理归档逻辑
    mGroup.archiveCheck(group, function(err, group) {

        db.group.find({
            'parent.$id': group._id
        }, function(err, result) {
            group.list = result;
            if (result && result.length) {
                var ep = new EventProxy();
                ep.after('fetchGroupMembers', result.length, function( /*list*/ ) {
                    callback(null, group);
                });
                ep.fail(function() {
                    callback(null, group);
                });
                result.forEach(function(doc) {
                    fetchGroupMembers(doc, ep.group('fetchGroupMembers'));
                });
            } else {
                callback(null, group);
            }
        });
    });
}

/**
 * 列出所有备课
 * @param  {[type]} req [description]
 * @param  {[type]} res [description]
 * @return {[type]}     [description]
 */
exports.listPrepares = function(req, res) {
    // var loginUser = req.loginUser;
    var parameter = req.parameter;
    var fetchChild = parameter.fetchChild || false;

    var ep = new EventProxy();
    ep.fail(function(err, errCode) {
        res.json({
            err: errCode || ERR.SERVER_ERROR,
            msg: err
        });
    });

    db.group.find({
        type: 3, // type=3 是备课学年
        parent: null
    }, function(result) {
        if (result && result.length) {
            if (fetchChild) {
                ep.after('fetchGroupDetail', result.length, function( /*list*/ ) {
                    ep.emit('fetchGroups', result);
                });
                result.forEach(function(group) {
                    fetchGroupDetail(group, ep.done('fetchGroupDetail'));
                });

            } else {
                ep.emit('fetchGroups', result);
            }
        } else {
            ep.emit('fetchGroups', []);
        }
    });

    ep.on('fetchGroups', function(list) {
        res.json({
            err: ERR.SUCCESS,
            result: {
                list: list
            }
        });
    });
};

/**
 * 列出学校的未审核文件
 * @param  {[type]} req [description]
 * @param  {[type]} res [description]
 * @return {[type]}     [description]
 */
exports.listFiles = function(req, res) {
    // var loginUser = req.loginUser;
    // var params = req.parameter;

    var ep = new EventProxy();
    ep.fail(function(err, errCode) {
        res.json({
            err: errCode || ERR.SERVER_ERROR,
            msg: err
        });
    });

    mGroup.getGroup({
        type: 0
    }, ep.doneLater('getSchoolDone'));

    ep.on('getSchoolDone', function(school) {
        if (!school) {
            return ep.emit('error', 'system error: no school');
        }
        var query = {
            'group.$id': school._id,
            status: 1
        };

        mFile.search({
            extendQuery: query,
            isDeref: true
        }, ep.done('search'));

    });
    ep.on('search', function(total, docs) {
        res.json({
            err: ERR.SUCCESS,
            result: {
                total: total,
                list: docs
            }
        });
    });
};

exports.modifyUser = function(req, res) {

    var params = req.parameter;

    var sizegroup = params.sizegroupId;
    var user = params.userId;
    var doc = {};
    if (sizegroup) {
        doc.sizegroup = new DBRef('sizegroup', sizegroup._id);
        doc.size = sizegroup.size;
    }
    if ('status' in params) {
        doc.status = params.status;
    }

    mUser.update({
        _id: user._id
    }, doc, function(err, result) {
        if (err) {
            return res.json({
                err: result || ERR.SERVER_ERROR,
                msg: err
            });
        }
        res.json({
            err: ERR.SUCCESS
        });

    });

};

/**
 * 统计系统所有文件和小组的数量
 * @param  {[type]} req [description]
 * @param  {[type]} res [description]
 * @return {[type]}     [description]
 */
exports.statistics = function(req, res) {
    // var parameter = req.parameter;

    var ep = new EventProxy();
    ep.fail(function(err, errCode) {
        res.json({
            err: errCode || ERR.SERVER_ERROR,
            msg: err
        });
    });

    var result = {};

    // 所有用户
    db.user.count({}, function(err, num) {
        if (err) {
            Logger.error('[manage.statistics]', err);
        }
        result.totalUser = num || 0;
        ep.emit('totalUser');
    });

    db.group.count({
        type: 1
    }, function(err, num) {
        if (err) {
            Logger.error('[manage.statistics]', err);
        }
        result.totalGroup = num || 0;
        ep.emit('totalGroup');
    });

    db.group.count({
        type: 2
    }, function(err, num) {
        if (err) {
            Logger.error('[manage.statistics]', err);
        }
        result.totalDepartment = num || 0;
        ep.emit('totalDepartment');
    });

    db.folder.count({}, function(err, num) {
        if (err) {
            Logger.error('[manage.statistics]', err);
        }
        result.totalFolder = num || 0;
        ep.emit('totalFolder');
    });

    db.file.find({}, function(err, docs) {
        var totalSize = 0;
        var list = {};
        docs.forEach(function(file) {
            totalSize += file.size || 0;
            var obj = list[file.type];
            if (!obj) {
                list[file.type] = obj = {
                    type: file.type,
                    size: 0,
                    count: 0
                };
            }
            obj.size += file.size || 0;
            obj.count++;
        });

        result.totalFile = docs.length;
        result.totalSize = totalSize;
        result.fileStatistics = list;

        ep.emit('totalFile');
    });

    ep.all('totalUser', 'totalGroup', 'totalDepartment', 'totalFile', 'totalFolder', function() {
        res.json({
            err: ERR.SUCCESS,
            result: result
        });
    });
};<|endoftext|>


<commit_before>var UI = require("/lib/UI/UI");
var i18n = require("/lib/i18n/Remote");
var TiDate = require("/lib/TiDate/TiDate");

exports.talk = function(obj) {
	var self = UI.createTableViewRow({
		className: "talk",
		hasChild: true,
		height: Ti.UI.SIZE,
		obj: obj
	});

	var viewLabels = UI.createView({
		bottom: 10,
		layout: "vertical",
		left: 8,
		top: 10
	});
	self.add(viewLabels);

	// Talk title
	viewLabels.add( UI.createLabel({
		font: { fontSize: 16, fontWeight: "bold" },
		height: Ti.UI.SIZE,
		left: 2,
		text: i18n.getValue(obj.name),
		right: 45
	}) );

	var speakersName = obj.speaker.map(function(speaker) {
		return speaker.name
	});

	viewLabels.add( UI.createLabel({
		color: "#999",
		font: { fontSize: 14 },
		height: Ti.UI.SIZE,
		left: 2,
		text: speakersName.join(" " + L("and") + " "),
		right: 45
	}) );

	self.add( UI.createLabel({
		color: "#999",
		font: { fontSize: 12 },
		height: Ti.UI.SIZE,
		right: 5,
		text: TiDate.getHours(obj.startAt),
		textAlign: "right"
	}) );

	return self;
};

exports.developer = function(name, twitter) {
	var self = UI.createTableViewRow();

	self.add( UI.createLabel({
		left: 10,
		text: name,
		width: "47%"
	}) );

	self.add( UI.createLabel({
		right: 10,
		text: "@" + twitter,
		textAlign: "right",
		width: "47%"
	}) );

	return self;
};
<commit_msg>Adjustments in TableViewRow when have not Speakers<commit_after>var UI = require("/lib/UI/UI");
var i18n = require("/lib/i18n/Remote");
var TiDate = require("/lib/TiDate/TiDate");

exports.talk = function(obj) {
	var hasSpeakers = (obj.speaker.length > 0);

	var self = UI.createTableViewRow({
		className: "talk",
		hasChild: hasSpeakers,
		height: (hasSpeakers) ? Ti.UI.SIZE : 50,
		obj: obj
	});

	var viewLabels = UI.createView({
		layout: "vertical",
		left: 8
	});
	self.add(viewLabels);

	if (!hasSpeakers) {
		self.selectionStyle = Ti.UI.iPhone.TableViewCellSelectionStyle.NONE;
	} else {
		viewLabels.applyProperties({
			bottom: 10,
			top: 10
		});
	}

	// Talk title
	viewLabels.add( UI.createLabel({
		font: { fontSize: 16, fontWeight: "bold" },
		height: Ti.UI.SIZE,
		left: 2,
		text: i18n.getValue(obj.name),
		right: 45
	}) );

	if (hasSpeakers) {
		var speakersName = obj.speaker.map(function(speaker) {
			return speaker.name
		});

		viewLabels.add( UI.createLabel({
			color: "#999",
			font: { fontSize: 14 },
			height: Ti.UI.SIZE,
			left: 2,
			text: speakersName.join(" " + L("and") + " "),
			right: 45
		}) );
	}

	self.add( UI.createLabel({
		color: "#999",
		font: { fontSize: 12 },
		height: Ti.UI.SIZE,
		right: (hasSpeakers) ? 5 : 25,
		text: TiDate.getHours(obj.startAt),
		textAlign: "right"
	}) );

	return self;
};

exports.developer = function(name, twitter) {
	var self = UI.createTableViewRow();

	self.add( UI.createLabel({
		left: 10,
		text: name,
		width: "47%"
	}) );

	self.add( UI.createLabel({
		right: 10,
		text: "@" + twitter,
		textAlign: "right",
		width: "47%"
	}) );

	return self;
};
<|endoftext|>


<commit_before>var CheckBoxOption, NonEmptyTextOption, NumberOption, TextOption,
initOptionsPage, importSettings, exportSetting;

Option.saveOptions = function() {
  Option.all.forEach(function(option) {
    option.save();
  });
};

Option.needSaveOptions = function() {
  return !Option.all.every(function(option) {
    return option.areEqual(option.readValueFromElement(), option.previous);
  });
};

NumberOption = (function(_super) {
  __extends(NumberOption, _super);

  function NumberOption() {
    NumberOption.__super__.constructor.apply(this, arguments);
    this.element.addEventListener("input", this.onUpdated);
  }

  NumberOption.prototype.populateElement = function(value) {
    return this.element.value = value;
  };

  NumberOption.prototype.readValueFromElement = function() {
    return parseFloat(this.element.value);
  };

  return NumberOption;

})(Option);

TextOption = (function(_super) {
  __extends(TextOption, _super);

  function TextOption() {
    TextOption.__super__.constructor.apply(this, arguments);
    this.element.addEventListener("input", this.onUpdated);
  }

  TextOption.prototype.populateElement = function(value) {
    return this.element.value = value.replace(/\n /g, '\n\xa0');
  };

  TextOption.prototype.readValueFromElement = function() {
    return this.element.value.trim().replace(/\xa0/g, ' ');
  };

  return TextOption;

})(Option);

NonEmptyTextOption = (function(_super) {
  __extends(NonEmptyTextOption, _super);

  function NonEmptyTextOption() {
    NonEmptyTextOption.__super__.constructor.apply(this, arguments);
  }

  NonEmptyTextOption.prototype.readValueFromElement = function() {
    var value = NonEmptyTextOption.__super__.readValueFromElement.call(this);
    if (value) {
      return value;
    } else {
      bgSettings.set(this.field, bgSettings.defaults[this.field]);
      this.fetch();
      return this.previous;
    }
  };

  return NonEmptyTextOption;

})(TextOption);

CheckBoxOption = (function(_super) {
  __extends(CheckBoxOption, _super);

  function CheckBoxOption() {
    CheckBoxOption.__super__.constructor.apply(this, arguments);
    this.element.addEventListener("change", this.onUpdated);
  }

  CheckBoxOption.prototype.populateElement = function(value) {
    return this.element.checked = value;
  };

  CheckBoxOption.prototype.readValueFromElement = function() {
    return this.element.checked;
  };

  return CheckBoxOption;

})(Option);

initPage = function() {
  var activateHelpDialog, element, maintainLinkHintsView, name, onUpdated //
    , options, saveOptions, toggleAdvancedOptions, type, _i, _ref, status = 0;
  initPage = null;

  onUpdated = function() {
    if (status == 1) { return; }
    status = 1;
    var saveBtn = $("saveOptions");
    saveBtn.removeAttribute("disabled");
    saveBtn.innerHTML = "Save Changes";
    $("exportButton").disabled = true;
  };

  maintainLinkHintsView = function() {
    var set = function(el, stat) {
      $(el).parentNode.parentNode.style.display = stat ? "" : "none";
    }, checked = $("filterLinkHints").checked;
    set("linkHintCharacters", !checked);
    set("linkHintNumbers", checked);
  };

  var advancedMode = !bgSettings.get("showAdvancedOptions");
  toggleAdvancedOptions = function(event) {
    if (advancedMode) {
      $("advancedOptions").style.display = "none";
      $("advancedOptionsButton").innerHTML = "Show Advanced Options";
    } else {
      $("advancedOptions").style.display = "";
      $("advancedOptionsButton").innerHTML = "Hide Advanced Options";
    }
    advancedMode = !advancedMode;
    bgSettings.set("showAdvancedOptions", advancedMode);
    $("advancedOptionsButton").blur();
  };

  activateHelpDialog = function(event) {
    var node;
    DomUtils.suppressEvent(event);
    if (node = document.querySelector('.vimHelpCommandName')) {
      node.click();
      return;
    }
    MainPort.sendMessage({
      handler: "initHelp",
      unbound: true,
      names: true,
      title: "Command Listing"
    }, function(response) {
      if (node = $("vimHelpDialog")) {
        MainPort.Listener({
          name: "executePageCommand",
          command: "showHelp",
          count: 1
        });
      }
      MainPort.Listener(response);
    });
  };

  saveOptions = function() {
    var btn = $("saveOptions");
    if (!btn.disabled) {
      Option.saveOptions();
    }
    btn.disabled = true;
    btn.innerHTML = "No Changes";
    $("exportButton").disabled = false;
    status = 0;
    setTimeout(function () {
      window.onfocus();
      if (Option.syncToFrontend) {
        bgSettings.postUpdate("bufferToLoad", null);
        bgSettings.postUpdate("broadcast", {
          name: "settings",
          load: bgSettings.bufferToLoad
        });
        Option.syncToFrontend = false;
      }
    }, 100);
  };

  $("saveOptions").onclick = saveOptions;
  $("advancedOptionsButton").onclick = toggleAdvancedOptions;
  toggleAdvancedOptions({preventDefault: function() {}});
  $("showCommands").onclick = activateHelpDialog;
  $("filterLinkHints").onclick = maintainLinkHintsView;
  _ref = document.getElementsByClassName("nonEmptyTextOption");
  for (_i = _ref.length; 0 <= --_i; ) {
    element = _ref[_i];
    element.className = element.className + " example info";
    element.innerHTML = "Leave empty to reset this option.";
  }
  maintainLinkHintsView();
  $("settingsFile").onchange = function() {
    var file = this.files[0], reader;
    this.value = "";
    if (!file) { return; }
    reader = new FileReader();
    reader.onload = importSettings;
    reader.readAsText(file);
  };
  $("importButton").onclick = function() {
    $("settingsFile").click();
  };
  $("exportButton").onclick = exportSetting;
  window.onbeforeunload = function() {
    if (status !== 0 && Option.needSaveOptions()) {
      return "You have unsaved changes to options.";
    }
  };
  document.addEventListener("keyup", function(event) {
    if (event.ctrlKey && event.keyCode === 13) {
      if (document.activeElement && document.activeElement.blur) {
        document.activeElement.blur();
      }
      if (status != 0) {
        saveOptions();
      }
    }
  });

  options = {
    exclusionRules: ExclusionRulesOption,
    filterLinkHints: CheckBoxOption,
    hideHud: CheckBoxOption,
    keyMappings: TextOption,
    linkHintCharacters: NonEmptyTextOption,
    linkHintNumbers: NonEmptyTextOption,
    newTabUrl: NonEmptyTextOption,
    nextPatterns: NonEmptyTextOption,
    previousPatterns: NonEmptyTextOption,
    regexFindMode: CheckBoxOption,
    scrollStepSize: NumberOption,
    smoothScroll: CheckBoxOption,
    searchEngines: TextOption,
    searchUrl: NonEmptyTextOption,
    userDefinedCss: TextOption
  };
  for (name in options) {
    type = options[name];
    new type(name, onUpdated);
  }
};

exportSetting = function() {
  var exported_object, exported_data, file_name, force2, d, nodeA;
  exported_object = {name: "Vimium++", time: 0};
  exported_object.__proto__ = null;
  (function() {
    var storage = localStorage, i, len, key, mark_head, all = bgSettings.defaults;
    mark_head = BG.Marks.getMarkKey("");
    for (i = 0, len = storage.length; i < len; i++) {
      key = storage.key(i);
      if (key === "name" || key === "time" || key.startsWith(mark_head)) {
        continue;
      }
      exported_object[key] = (key in all) ? bgSettings.get(key) : storage.getItem(key);
    }
  })();
  delete exported_object.findModeRawQuery;
  d = new Date();
  exported_object.time = d.getTime();
  exported_data = JSON.stringify(exported_object, null, '\t');
  exported_object = null;
  force2 = function(i) { return ((i <= 9) ? '0'  : '') + i; }
  file_name = 'vimium++_' + d.getFullYear() + force2(d.getMonth() + 1) + force2(d.getDate())
    + '_' + force2(d.getHours()) + force2(d.getMinutes()) + force2(d.getSeconds()) + '.json';

  nodeA = document.createElement("a");
  nodeA.download = file_name;
  nodeA.href = "data:application/json;charset=UTF-8," + encodeURIComponent(exported_data);
  nodeA.click();
  console.log("EXPORT settings to", file_name, "at", d);
};

importSettings = function() {
  var new_data;
  try {
    new_data = JSON.parse(this.result);
  } catch (e) {}
  if (!new_data || new_data.name !== "Vimium++" || !(new_data.time > 10000)) {
    VHUD.showForDuration("No settings data found!", 2000);
    return;
  } else if (!confirm(
    "You are loading a settings copy exported at:\n        "
    + new Date(new_data.time - new Date().getTimezoneOffset() * 1000 * 60
      ).toJSON().substring(0, 16).replace('T', ' ')
    + "\n\nAre you sure you want to continue?"
  )) {
    VHUD.showForDuration("You cancelled importing.", 1000);
    return;
  }

  var storage = localStorage, i, key, new_value, func, all = bgSettings.defaults;
  func = function(val) {
    return typeof val !== "string" ? val : val.substring(0, 72);
  };
  console.log("IMPORT settings at", new Date(new_data.time));
  delete new_data.name;
  delete new_data.time;
  delete new_data.findModeRawQuery;
  for (i = storage.length; 0 <= --i; ) {
    key = storage.key(i);
    if (!(key in new_data)) {
      new_data[key] = null;
    }
  }
  Option.all.forEach(function(item) {
    var key = item.field, new_value = new_data[key];
    delete new_data[key];
    if (new_value == null) {
      new_value = all[key];
    }
    if (!item.areEqual(bgSettings.get(key), new_value)) {
      console.log("import", key, func(new_value));
      bgSettings.set(key, new_value);
    }
    item.fetch();
  });
  for (key in new_data) {
    new_value = new_data[key];
    if (new_value == null) {
      if (key in all) {
        new_value = all[key];
        if (bgSettings.get(key) !== new_value) {
          bgSettings.set(key, new_value);
          console.log("reset", key, func(new_value));
          continue;
        }
        new_value = bgSettings.get(key);
      } else {
        new_value = storage.getItem(key);
      }
      storage.removeItem(key);
      console.log("remove", key, "<=", func(new_value));
    } else if (key in all) {
      if (bgSettings.get(key) !== new_value) {
        bgSettings.set(key, new_value);
        console.log("update", key, func(new_value));
      }
    } else {
      storage.setItem(key, new_value);
      console.log("save", key, func(new_value));
    }
  }
  var btn = $("saveOptions");
  btn.disabled = true;
  btn.click();
  VHUD.showForDuration("Import settings data: OK!", 1000);
};
<commit_msg>in onUpdated, check if we need to save<commit_after>var CheckBoxOption, NonEmptyTextOption, NumberOption, TextOption,
initOptionsPage, importSettings, exportSetting;

Option.saveOptions = function() {
  Option.all.forEach(function(option) {
    option.save();
  });
};

Option.needSaveOptions = function() {
  return !Option.all.every(function(option) {
    return option.areEqual(option.readValueFromElement(), option.previous);
  });
};

NumberOption = (function(_super) {
  __extends(NumberOption, _super);

  function NumberOption() {
    NumberOption.__super__.constructor.apply(this, arguments);
    this.element.addEventListener("input", this.onUpdated);
  }

  NumberOption.prototype.populateElement = function(value) {
    return this.element.value = value;
  };

  NumberOption.prototype.readValueFromElement = function() {
    return parseFloat(this.element.value);
  };

  return NumberOption;

})(Option);

TextOption = (function(_super) {
  __extends(TextOption, _super);

  function TextOption() {
    TextOption.__super__.constructor.apply(this, arguments);
    this.element.addEventListener("input", this.onUpdated);
  }

  TextOption.prototype.populateElement = function(value) {
    return this.element.value = value.replace(/\n /g, '\n\xa0');
  };

  TextOption.prototype.readValueFromElement = function() {
    return this.element.value.trim().replace(/\xa0/g, ' ');
  };

  return TextOption;

})(Option);

NonEmptyTextOption = (function(_super) {
  __extends(NonEmptyTextOption, _super);

  function NonEmptyTextOption() {
    NonEmptyTextOption.__super__.constructor.apply(this, arguments);
  }

  NonEmptyTextOption.prototype.readValueFromElement = function() {
    var value = NonEmptyTextOption.__super__.readValueFromElement.call(this);
    if (value) {
      return value;
    } else {
      bgSettings.set(this.field, bgSettings.defaults[this.field]);
      this.fetch();
      return this.previous;
    }
  };

  return NonEmptyTextOption;

})(TextOption);

CheckBoxOption = (function(_super) {
  __extends(CheckBoxOption, _super);

  function CheckBoxOption() {
    CheckBoxOption.__super__.constructor.apply(this, arguments);
    this.element.addEventListener("change", this.onUpdated);
  }

  CheckBoxOption.prototype.populateElement = function(value) {
    return this.element.checked = value;
  };

  CheckBoxOption.prototype.readValueFromElement = function() {
    return this.element.checked;
  };

  return CheckBoxOption;

})(Option);

initPage = function() {
  var activateHelpDialog, element, maintainLinkHintsView, name, onUpdated //
    , options, saveOptions, toggleAdvancedOptions, type, _i, _ref, status = 0;
  initPage = null;

  onUpdated = function() {
    if (this.areEqual(this.readValueFromElement(), this.previous)) {
      if (status == 1 && !Option.needSaveOptions()) {
        var btn = $("saveOptions");
        btn.disabled = true;
        btn.innerHTML = "No Changes";
        $("exportButton").disabled = false;
        status = 0;
      }
      return;
    } else if (status == 1) {
      return;
    }
    status = 1;
    var saveBtn = $("saveOptions");
    saveBtn.removeAttribute("disabled");
    saveBtn.innerHTML = "Save Changes";
    $("exportButton").disabled = true;
  };

  maintainLinkHintsView = function() {
    var set = function(el, stat) {
      $(el).parentNode.parentNode.style.display = stat ? "" : "none";
    }, checked = $("filterLinkHints").checked;
    set("linkHintCharacters", !checked);
    set("linkHintNumbers", checked);
  };

  var advancedMode = !bgSettings.get("showAdvancedOptions");
  toggleAdvancedOptions = function(event) {
    if (advancedMode) {
      $("advancedOptions").style.display = "none";
      $("advancedOptionsButton").innerHTML = "Show Advanced Options";
    } else {
      $("advancedOptions").style.display = "";
      $("advancedOptionsButton").innerHTML = "Hide Advanced Options";
    }
    advancedMode = !advancedMode;
    bgSettings.set("showAdvancedOptions", advancedMode);
    $("advancedOptionsButton").blur();
  };

  activateHelpDialog = function(event) {
    var node;
    DomUtils.suppressEvent(event);
    if (node = document.querySelector('.vimHelpCommandName')) {
      node.click();
      return;
    }
    MainPort.sendMessage({
      handler: "initHelp",
      unbound: true,
      names: true,
      title: "Command Listing"
    }, function(response) {
      if (node = $("vimHelpDialog")) {
        MainPort.Listener({
          name: "executePageCommand",
          command: "showHelp",
          count: 1
        });
      }
      MainPort.Listener(response);
    });
  };

  saveOptions = function() {
    var btn = $("saveOptions");
    if (!btn.disabled) {
      Option.saveOptions();
    }
    btn.disabled = true;
    btn.innerHTML = "No Changes";
    $("exportButton").disabled = false;
    status = 0;
    setTimeout(function () {
      window.onfocus();
      if (Option.syncToFrontend) {
        bgSettings.postUpdate("bufferToLoad", null);
        bgSettings.postUpdate("broadcast", {
          name: "settings",
          load: bgSettings.bufferToLoad
        });
        Option.syncToFrontend = false;
      }
    }, 100);
  };

  $("saveOptions").onclick = saveOptions;
  $("advancedOptionsButton").onclick = toggleAdvancedOptions;
  toggleAdvancedOptions({preventDefault: function() {}});
  $("showCommands").onclick = activateHelpDialog;
  $("filterLinkHints").onclick = maintainLinkHintsView;
  _ref = document.getElementsByClassName("nonEmptyTextOption");
  for (_i = _ref.length; 0 <= --_i; ) {
    element = _ref[_i];
    element.className = element.className + " example info";
    element.innerHTML = "Leave empty to reset this option.";
  }
  maintainLinkHintsView();
  $("settingsFile").onchange = function() {
    var file = this.files[0], reader;
    this.value = "";
    if (!file) { return; }
    reader = new FileReader();
    reader.onload = importSettings;
    reader.readAsText(file);
  };
  $("importButton").onclick = function() {
    $("settingsFile").click();
  };
  $("exportButton").onclick = exportSetting;
  window.onbeforeunload = function() {
    if (status !== 0 && Option.needSaveOptions()) {
      return "You have unsaved changes to options.";
    }
  };
  document.addEventListener("keyup", function(event) {
    if (event.ctrlKey && event.keyCode === 13) {
      if (document.activeElement && document.activeElement.blur) {
        document.activeElement.blur();
      }
      if (status != 0) {
        saveOptions();
      }
    }
  });

  options = {
    exclusionRules: ExclusionRulesOption,
    filterLinkHints: CheckBoxOption,
    hideHud: CheckBoxOption,
    keyMappings: TextOption,
    linkHintCharacters: NonEmptyTextOption,
    linkHintNumbers: NonEmptyTextOption,
    newTabUrl: NonEmptyTextOption,
    nextPatterns: NonEmptyTextOption,
    previousPatterns: NonEmptyTextOption,
    regexFindMode: CheckBoxOption,
    scrollStepSize: NumberOption,
    smoothScroll: CheckBoxOption,
    searchEngines: TextOption,
    searchUrl: NonEmptyTextOption,
    userDefinedCss: TextOption
  };
  for (name in options) {
    type = options[name];
    new type(name, onUpdated);
  }
};

exportSetting = function() {
  var exported_object, exported_data, file_name, force2, d, nodeA;
  exported_object = {name: "Vimium++", time: 0};
  exported_object.__proto__ = null;
  (function() {
    var storage = localStorage, i, len, key, mark_head, all = bgSettings.defaults;
    mark_head = BG.Marks.getMarkKey("");
    for (i = 0, len = storage.length; i < len; i++) {
      key = storage.key(i);
      if (key === "name" || key === "time" || key.startsWith(mark_head)) {
        continue;
      }
      exported_object[key] = (key in all) ? bgSettings.get(key) : storage.getItem(key);
    }
  })();
  delete exported_object.findModeRawQuery;
  d = new Date();
  exported_object.time = d.getTime();
  exported_data = JSON.stringify(exported_object, null, '\t');
  exported_object = null;
  force2 = function(i) { return ((i <= 9) ? '0'  : '') + i; }
  file_name = 'vimium++_' + d.getFullYear() + force2(d.getMonth() + 1) + force2(d.getDate())
    + '_' + force2(d.getHours()) + force2(d.getMinutes()) + force2(d.getSeconds()) + '.json';

  nodeA = document.createElement("a");
  nodeA.download = file_name;
  nodeA.href = "data:application/json;charset=UTF-8," + encodeURIComponent(exported_data);
  nodeA.click();
  console.log("EXPORT settings to", file_name, "at", d);
};

importSettings = function() {
  var new_data;
  try {
    new_data = JSON.parse(this.result);
  } catch (e) {}
  if (!new_data || new_data.name !== "Vimium++" || !(new_data.time > 10000)) {
    VHUD.showForDuration("No settings data found!", 2000);
    return;
  } else if (!confirm(
    "You are loading a settings copy exported at:\n        "
    + new Date(new_data.time - new Date().getTimezoneOffset() * 1000 * 60
      ).toJSON().substring(0, 16).replace('T', ' ')
    + "\n\nAre you sure you want to continue?"
  )) {
    VHUD.showForDuration("You cancelled importing.", 1000);
    return;
  }

  var storage = localStorage, i, key, new_value, func, all = bgSettings.defaults;
  func = function(val) {
    return typeof val !== "string" ? val : val.substring(0, 72);
  };
  console.log("IMPORT settings at", new Date(new_data.time));
  delete new_data.name;
  delete new_data.time;
  delete new_data.findModeRawQuery;
  for (i = storage.length; 0 <= --i; ) {
    key = storage.key(i);
    if (!(key in new_data)) {
      new_data[key] = null;
    }
  }
  Option.all.forEach(function(item) {
    var key = item.field, new_value = new_data[key];
    delete new_data[key];
    if (new_value == null) {
      new_value = all[key];
    }
    if (!item.areEqual(bgSettings.get(key), new_value)) {
      console.log("import", key, func(new_value));
      bgSettings.set(key, new_value);
    }
    item.fetch();
  });
  for (key in new_data) {
    new_value = new_data[key];
    if (new_value == null) {
      if (key in all) {
        new_value = all[key];
        if (bgSettings.get(key) !== new_value) {
          bgSettings.set(key, new_value);
          console.log("reset", key, func(new_value));
          continue;
        }
        new_value = bgSettings.get(key);
      } else {
        new_value = storage.getItem(key);
      }
      storage.removeItem(key);
      console.log("remove", key, "<=", func(new_value));
    } else if (key in all) {
      if (bgSettings.get(key) !== new_value) {
        bgSettings.set(key, new_value);
        console.log("update", key, func(new_value));
      }
    } else {
      storage.setItem(key, new_value);
      console.log("save", key, func(new_value));
    }
  }
  var btn = $("saveOptions");
  btn.disabled = true;
  btn.click();
  VHUD.showForDuration("Import settings data: OK!", 1000);
};
<|endoftext|>


<commit_before>module.exports = function(r) {
    const dbExec = require('./run');
    const db = require('./db')(r);
    const model = require('./model')(r);

    return {
        getAllForProject,
        get,
        create,
        update,
        createStep,
        getStep,
        experimentExistsInProject,
        experimentStepExistsInExperiment
    };

    function* getAllForProject(projectID) {
        let rql = r.table('project2experiment').getAll(projectID, {index: 'project_id'})
            .eqJoin('experiment_id', r.table('experiments')).zip()
            .merge((experiment) => {
                return {
                    steps: r.table('experiment2experiment_step')
                        .getAll(experiment('id'), {index: 'experiment_id'})
                        .eqJoin('experiment_step_id', r.table('experiment_steps')).zip()
                        .filter({parent_id: ''})
                        .coerceTo('array')
                }
            });
        let experiments = yield dbExec(rql);
        return {val: experiments};
    }

    // Get assumes that validating the experiment for the project has already occured.
    function* get(experimentID) {
        let rql = r.table('experiments').get(experimentID)
            .merge((experiment) => {
                return {
                    steps: r.table('experiment2experiment_step')
                        .getAll(experiment('id'), {index: 'experiment_id'})
                        .eqJoin('experiment_step_id', r.table('experiment_steps')).zip()
                        .filter({parent_id: ''})
                        .coerceTo('array')
                }
            });
        let experiment = yield dbExec(rql);
        return {val: experiment};
    }

    function* create(experiment, owner) {
        let e = new model.Experiment(experiment.name, owner);
        e.description = experiment.description;
        let newExperiment = yield db.insert('experiments', e);
        let proj2experiment = new model.Project2Experiment(experiment.project_id, newExperiment.id);
        yield db.insert('project2experiment', proj2experiment);
        let estep = new model.ExperimentStep('', owner);
        yield createStep(newExperiment.id, estep, owner);
        return yield get(newExperiment.id);
    }

    function* update(updateArgs, experimentID) {
        let steps = updateArgs.steps;
        delete updateArgs.steps;

        yield db.update('experiments', experimentID, updateArgs);
        if (steps.length) {
            console.log('going to update steps');
        }
    }

    function* createStep(experimentID, step, owner) {
        let estep = new model.ExperimentStep(step.name, owner);
        estep.description = step.description;
        estep.parent_id = step.parent_id;
        let createdStep = yield db.insert('experiment_steps', estep);
        console.log('createStep', createdStep);
        let e2estep = new model.Experiment2ExperimentStep(experimentID, createdStep.id);
        yield db.insert('experiment2experiment_step', e2estep);
        return yield getStep(createdStep.id);
    }

    function* getStep(stepID) {
        let rql = r.table('experiment_steps').get(stepID)
            .merge((step) => {
                return {
                    processes: r.table('experiment_step2process').getAll(step('id'), {index: 'experiment_step_id'})
                        .eqJoin('process_id', r.table('processes')).zip().coerceTo('array'),
                    steps: r.table('experiment_steps').getAll(step('id'), {index: 'parent_id'}).coerceTo('array')
                }
            });
        let s = yield dbExec(rql);

        // merge processes into steps and then remove the entry
        s.steps.concat(s.processes);
        delete s.processes;

        return {val: s};
    }

    function* experimentExistsInProject(projectID, experimentID) {
        let rql = r.table('project2experiment').getAll([projectID, experimentID], {index: 'project_experiment'});
        let matches = yield dbExec(rql);
        return matches.length !== 0;
    }

    function* experimentStepExistsInExperiment(experimentID, experimentStepID) {
        let rql = r.table('experiment2experiment_step')
            .getAll([experimentID, experimentStepID], {index: 'experiment_experiment_step'});
        let matches = yield dbExec(rql);
        return matches.length !== 0;
    }
};
<commit_msg>Return the updated experiment.<commit_after>module.exports = function(r) {
    const dbExec = require('./run');
    const db = require('./db')(r);
    const model = require('./model')(r);

    return {
        getAllForProject,
        get,
        create,
        update,
        createStep,
        getStep,
        experimentExistsInProject,
        experimentStepExistsInExperiment
    };

    function* getAllForProject(projectID) {
        let rql = r.table('project2experiment').getAll(projectID, {index: 'project_id'})
            .eqJoin('experiment_id', r.table('experiments')).zip()
            .merge((experiment) => {
                return {
                    steps: r.table('experiment2experiment_step')
                        .getAll(experiment('id'), {index: 'experiment_id'})
                        .eqJoin('experiment_step_id', r.table('experiment_steps')).zip()
                        .filter({parent_id: ''})
                        .coerceTo('array')
                }
            });
        let experiments = yield dbExec(rql);
        return {val: experiments};
    }

    // Get assumes that validating the experiment for the project has already occured.
    function* get(experimentID) {
        let rql = r.table('experiments').get(experimentID)
            .merge((experiment) => {
                return {
                    steps: r.table('experiment2experiment_step')
                        .getAll(experiment('id'), {index: 'experiment_id'})
                        .eqJoin('experiment_step_id', r.table('experiment_steps')).zip()
                        .filter({parent_id: ''})
                        .coerceTo('array')
                }
            });
        let experiment = yield dbExec(rql);
        return {val: experiment};
    }

    function* create(experiment, owner) {
        let e = new model.Experiment(experiment.name, owner);
        e.description = experiment.description;
        let newExperiment = yield db.insert('experiments', e);
        let proj2experiment = new model.Project2Experiment(experiment.project_id, newExperiment.id);
        yield db.insert('project2experiment', proj2experiment);
        let estep = new model.ExperimentStep('', owner);
        yield createStep(newExperiment.id, estep, owner);
        return yield get(newExperiment.id);
    }

    function* update(experimentID, updateArgs) {
        let steps = updateArgs.steps;
        delete updateArgs.steps;

        yield db.update('experiments', experimentID, updateArgs);
        if (steps.length) {
            console.log('going to update steps');
        }
        return yield get(experimentID);
    }

    function* createStep(experimentID, step, owner) {
        let estep = new model.ExperimentStep(step.name, owner);
        estep.description = step.description;
        estep.parent_id = step.parent_id;
        let createdStep = yield db.insert('experiment_steps', estep);
        let e2estep = new model.Experiment2ExperimentStep(experimentID, createdStep.id);
        yield db.insert('experiment2experiment_step', e2estep);
        return yield getStep(createdStep.id);
    }

    function* getStep(stepID) {
        let rql = r.table('experiment_steps').get(stepID)
            .merge((step) => {
                return {
                    processes: r.table('experiment_step2process').getAll(step('id'), {index: 'experiment_step_id'})
                        .eqJoin('process_id', r.table('processes')).zip().coerceTo('array'),
                    steps: r.table('experiment_steps').getAll(step('id'), {index: 'parent_id'}).coerceTo('array')
                }
            });
        let s = yield dbExec(rql);

        // merge processes into steps and then remove the entry
        s.steps.concat(s.processes);
        delete s.processes;

        return {val: s};
    }

    function* experimentExistsInProject(projectID, experimentID) {
        let rql = r.table('project2experiment').getAll([projectID, experimentID], {index: 'project_experiment'});
        let matches = yield dbExec(rql);
        return matches.length !== 0;
    }

    function* experimentStepExistsInExperiment(experimentID, experimentStepID) {
        let rql = r.table('experiment2experiment_step')
            .getAll([experimentID, experimentStepID], {index: 'experiment_experiment_step'});
        let matches = yield dbExec(rql);
        return matches.length !== 0;
    }
};
<|endoftext|>


<commit_before>Hero = function(game, characterName, team, isLocal, initX, initY, textureName) {
    //Phaser.Sprite.call(this, game, initX, initY, 'hero');
    Phaser.Sprite.call(this, game, initX, initY, textureName);

    this.game = game;
    this.characterName = characterName;
    this.team = team;
    this.isLocal = isLocal;

    // If local player, listen for keys.
    if (this.isLocal) {
        //game.input.keyboard.onDownCallback(this.handleKeyDown.bind(this));
        this.moveDirectionKeys = {
            up: this.game.input.keyboard.addKey(Phaser.Keyboard.W),
            left: this.game.input.keyboard.addKey(Phaser.Keyboard.A),
            down: this.game.input.keyboard.addKey(Phaser.Keyboard.S),
            right: this.game.input.keyboard.addKey(Phaser.Keyboard.D)
        };
        this.actionDirectionKeys = this.game.input.keyboard.createCursorKeys();
        this.actionSwitchKey = this.game.input.keyboard.addKey(Phaser.Keyboard.SPACEBAR);
    }

    this.lastPos = {
        x: -1,
        y: -1
    }

    this.vel = {
        x: 0,
        y: 0
    }

    this.health = this.maxHealth;
    this.actionCooldown = 0;
    this.moveDelay = 0;
    this.currentAction = 1;

    this.animations.add('UP', [9, 10, 11]);
    this.animations.add('DOWN', [0,1,2]);
    this.animations.add('LEFT', [3,4,5]);
    this.animations.add('RIGHT', [6, 7, 8]);
    this.animations.play('DOWN', 5, true);
    this.anchor.set(0.5, 0.5);

    // Init collision detection stuff
    game.add.existing(this);
    var style = {
        font: "12px Arial",
        align: "center"
    };
    if(this.team === 'red') {
        style.fill = '#FF0000';
    } else {
        style.fill = '#0000FF';
    }
    this.nameText = game.add.text(this.x - 8, this.y + 8, this.characterName, style);
    this.nameText.x = this.x - this.nameText.width/2;
    game.physics.enable(this, Phaser.Physics.ARCADE);

    this.body.collideWorldBounds = true;

    this.bringToTop();
};

Hero.prototype = Object.create(Phaser.Sprite.prototype);
Hero.prototype.constructor = Hero;

Hero.prototype.updateTo = function() {
    if (this.moveDelay === 0) {
        var moveMessage = {
            type: 'FE_HERO_MOVE',
            content: {
                name: this.characterName,
                speed: 8,
                direction: ''
            }
        };

        if (this.moveDirectionKeys.left.isDown) {
            moveMessage.content.direction = 'LEFT'
            this.animations.play('LEFT', 5, true);
        } else if (this.moveDirectionKeys.right.isDown) {
            moveMessage.content.direction = 'RIGHT'
            this.animations.play('RIGHT', 5, true);
        } else if (this.moveDirectionKeys.up.isDown) {
            moveMessage.content.direction = 'UP'
            this.animations.play('UP', 5, true);
        } else if (this.moveDirectionKeys.down.isDown) {
            moveMessage.content.direction = 'DOWN'
            this.animations.play('DOWN', 5, true);
        }

        if (moveMessage.content.direction !== '') {
            socket.send(JSON.stringify(moveMessage));
        }

        this.moveDelay = this.maxMoveDelay;
    } else {
        this.moveDelay--;
    }

    if (this.actionSwitchKey.isDown && this.actionSwitchKey.duration < 1000/60) {
        if (this.currentAction === 1) {
            this.currentAction = 2;
        } else {
            this.currentAction = 1;
        }
    }

    if (this.actionCooldown === 0) {
        var actionMessage = {
            type: 'FE_HERO_SPELL',
            content: {
                position_x: this.x,
                position_y: this.y,
                spell_type: this.actions[this.currentAction],
                direction: ''
            }
        }

        if (this.actionDirectionKeys.left.isDown) {
            actionMessage.content.direction = 'LEFT'
        } else if (this.actionDirectionKeys.right.isDown) {
            actionMessage.content.direction = 'RIGHT'
        } else if (this.actionDirectionKeys.up.isDown) {
            actionMessage.content.direction = 'UP'
        } else if (this.actionDirectionKeys.down.isDown) {
            actionMessage.content.direction = 'DOWN'
        }

        if (actionMessage.content.direction !== '') {
            //TODO this.messageCallback(moveMessage);
            socket.send(JSON.stringify(actionMessage));
        }
    }
}

Hero.prototype.destroy = function() {
    this.nameText.destroy();
    this.kill();
}

Hero.prototype.receiveMessage = function(message) {
    this.x = message['x'] * 16;
    this.y = message['y'] * 16;

    this.nameText.x = this.x - this.nameText.width/2;
    this.nameText.y = this.y + 8;

    this.lastPos.x = this.x;
    this.lastPos.y = this.y;
}

Hero.prototype.update_smooth = function() {
    if (this.moveDelay === 0) {
        this.vel.x = 0;
        this.vel.y = 0;

        if (this.moveDirectionKeys.left.isDown) {
            this.vel.x = -8;
        } else if (this.moveDirectionKeys.right.isDown) {
            this.vel.x = 8;
        } else if (this.moveDirectionKeys.up.isDown) {
            this.vel.y = -8;
        } else if (this.moveDirectionKeys.down.isDown) {
            this.vel.y = 8;
        }

        this.x += this.vel.x;
        this.y += this.vel.y;

        // Check if there's been a change
        if (this.x !== this.lastPos.x || this.y !== this.lastPos.y) {
            /*this.messageCallback({
                type: 'FE_HERO_POSITION',
                content: {
                    name: this.characterName,
                    x: this.pos.x,
                    y: this.pos.y
                }
            })*/

            this.moveDelay = this.maxMoveDelay;

            this.lastPos.x = this.x;
            this.lastPos.y = this.y;
        }
    } else {
        this.moveDelay--;
    }
}<commit_msg>Heroes stop walking when they, er, stop moving :)<commit_after>Hero = function(game, characterName, team, isLocal, initX, initY, textureName) {
    //Phaser.Sprite.call(this, game, initX, initY, 'hero');
    Phaser.Sprite.call(this, game, initX, initY, textureName);

    this.game = game;
    this.characterName = characterName;
    this.team = team;
    this.isLocal = isLocal;

    // If local player, listen for keys.
    if (this.isLocal) {
        //game.input.keyboard.onDownCallback(this.handleKeyDown.bind(this));
        this.moveDirectionKeys = {
            up: this.game.input.keyboard.addKey(Phaser.Keyboard.W),
            left: this.game.input.keyboard.addKey(Phaser.Keyboard.A),
            down: this.game.input.keyboard.addKey(Phaser.Keyboard.S),
            right: this.game.input.keyboard.addKey(Phaser.Keyboard.D)
        };
        this.actionDirectionKeys = this.game.input.keyboard.createCursorKeys();
        this.actionSwitchKey = this.game.input.keyboard.addKey(Phaser.Keyboard.SPACEBAR);
    }

    this.lastPos = {
        x: -1,
        y: -1
    }

    this.vel = {
        x: 0,
        y: 0
    }

    this.health = this.maxHealth;
    this.actionCooldown = 0;
    this.moveDelay = 0;
    this.currentAction = 1;

    this.animations.add('UP', [9, 10, 11]);
    this.animations.add('DOWN', [0,1,2]);
    this.animations.add('LEFT', [3,4,5]);
    this.animations.add('RIGHT', [6, 7, 8]);
    this.animations.play('DOWN', 5, true);
    this.anchor.set(0.5, 0.5);

    // Init collision detection stuff
    game.add.existing(this);
    var style = {
        font: "12px Arial",
        align: "center"
    };
    if(this.team === 'red') {
        style.fill = '#FF0000';
    } else {
        style.fill = '#0000FF';
    }
    this.nameText = game.add.text(this.x - 8, this.y + 8, this.characterName, style);
    this.nameText.x = this.x - this.nameText.width/2;
    game.physics.enable(this, Phaser.Physics.ARCADE);

    this.body.collideWorldBounds = true;

    this.bringToTop();
};

Hero.prototype = Object.create(Phaser.Sprite.prototype);
Hero.prototype.constructor = Hero;

Hero.prototype.updateTo = function() {
    if (this.moveDelay === 0) {
        var moveMessage = {
            type: 'FE_HERO_MOVE',
            content: {
                name: this.characterName,
                speed: 8,
                direction: ''
            }
        };

        if (this.moveDirectionKeys.left.isDown) {
            moveMessage.content.direction = 'LEFT'
            this.animations.play('LEFT', 5, true);
        } else if (this.moveDirectionKeys.right.isDown) {
            moveMessage.content.direction = 'RIGHT'
            this.animations.play('RIGHT', 5, true);
        } else if (this.moveDirectionKeys.up.isDown) {
            moveMessage.content.direction = 'UP'
            this.animations.play('UP', 5, true);
        } else if (this.moveDirectionKeys.down.isDown) {
            moveMessage.content.direction = 'DOWN'
            this.animations.play('DOWN', 5, true);
        } else {
            this.animations.stop();
        }

        if (moveMessage.content.direction !== '') {
            socket.send(JSON.stringify(moveMessage));
        }

        this.moveDelay = this.maxMoveDelay;
    } else {
        this.moveDelay--;
    }

    if (this.actionSwitchKey.isDown && this.actionSwitchKey.duration < 1000/60) {
        if (this.currentAction === 1) {
            this.currentAction = 2;
        } else {
            this.currentAction = 1;
        }
    }

    if (this.actionCooldown === 0) {
        var actionMessage = {
            type: 'FE_HERO_SPELL',
            content: {
                position_x: this.x,
                position_y: this.y,
                spell_type: this.actions[this.currentAction],
                direction: ''
            }
        }

        if (this.actionDirectionKeys.left.isDown) {
            actionMessage.content.direction = 'LEFT'
        } else if (this.actionDirectionKeys.right.isDown) {
            actionMessage.content.direction = 'RIGHT'
        } else if (this.actionDirectionKeys.up.isDown) {
            actionMessage.content.direction = 'UP'
        } else if (this.actionDirectionKeys.down.isDown) {
            actionMessage.content.direction = 'DOWN'
        }

        if (actionMessage.content.direction !== '') {
            //TODO this.messageCallback(moveMessage);
            socket.send(JSON.stringify(actionMessage));
        }
    }
}

Hero.prototype.destroy = function() {
    this.nameText.destroy();
    this.kill();
}

Hero.prototype.receiveMessage = function(message) {
    this.x = message['x'] * 16;
    this.y = message['y'] * 16;

    this.nameText.x = this.x - this.nameText.width/2;
    this.nameText.y = this.y + 8;

    this.lastPos.x = this.x;
    this.lastPos.y = this.y;
}

Hero.prototype.update_smooth = function() {
    if (this.moveDelay === 0) {
        this.vel.x = 0;
        this.vel.y = 0;

        if (this.moveDirectionKeys.left.isDown) {
            this.vel.x = -8;
        } else if (this.moveDirectionKeys.right.isDown) {
            this.vel.x = 8;
        } else if (this.moveDirectionKeys.up.isDown) {
            this.vel.y = -8;
        } else if (this.moveDirectionKeys.down.isDown) {
            this.vel.y = 8;
        }

        this.x += this.vel.x;
        this.y += this.vel.y;

        // Check if there's been a change
        if (this.x !== this.lastPos.x || this.y !== this.lastPos.y) {
            /*this.messageCallback({
                type: 'FE_HERO_POSITION',
                content: {
                    name: this.characterName,
                    x: this.pos.x,
                    y: this.pos.y
                }
            })*/

            this.moveDelay = this.maxMoveDelay;

            this.lastPos.x = this.x;
            this.lastPos.y = this.y;
        }
    } else {
        this.moveDelay--;
    }
}<|endoftext|>