<commit_before>var TradeSocket = (function () {
    'use strict';

    var tradeSocket,
        socketUrl = "wss://www.devbin.io/websockets/contracts";

    var status = function () {
        return tradeSocket && tradeSocket.readyState;
    };

    var isReady = function () {
        return tradeSocket && tradeSocket.readyState == 1;
    };

    var onOpen = function (token) {
        tradeSocket.send(JSON.stringify({
            authorize: token
        }));

        tradeSocket.send(JSON.stringify({
            offerings: {hierarchy: 1, contracts: 0}
        }));
    };

    var onMessage = function (msg) {
        var response = JSON.parse(msg.data);
        Message.process(msg);
    };

    var onClose = function (e) {
        console.log('socket closed', e);
    };

    var onError = function (error) {
        console.log('socket error', error);
    };

    var init = function (token) {
        tradeSocket = new WebSocket(socketUrl);

        tradeSocket.onopen = onOpen(token);
        tradeSocket.onmessage = onMessage;
        tradeSocket.onclose = onClose;
        tradeSocket.onerror = onError;
    };

    var send = function(data) {
        if (isReady()) {
            tradeSocket.send(JSON.stringify(data));
        } else {
        }
    };

    return {
        init: init,
        status: status,
        socket: tradeSocket,
        send: send
    };

})();
<commit_msg>check for close and ready state of socket, implement buffersend<commit_after>var TradeSocket = (function () {
    'use strict';

    var tradeSocket,
        socketUrl = "wss://www.devbin.io/websockets/contracts",
        bufferedSends = [];

    var status = function () {
        return tradeSocket && tradeSocket.readyState;
    };

    var isReady = function () {
        return tradeSocket && tradeSocket.readyState === 1;
    };

    var isClose = function () {
        return tradeSocket && tradeSocket.readyState === 3;
    };

    var sendBufferedSends = function () {
        while (bufferedSends.length > 0) {
            tradeSocket.send(JSON.stringify(bufferedSends.shift()));
        }
    };

    var init = function (token) {
        tradeSocket = new WebSocket(socketUrl);

        tradeSocket.onopen = function (token){
            sendBufferedSends();
        };

        tradeSocket.onmessage = function (msg){
            Message.process(msg);
        };

        tradeSocket.onclose = function (e) {
            console.log('socket closed', e);
        };;

        tradeSocket.onerror = function (error) {
            console.log('socket error', error);
        };;
    };

    var send = function(data) {
        if (isClose()) {
            bufferedSends.push(data);
            tradeSocket.init();
        } else if (isReady()) {
            tradeSocket.send(JSON.stringify(data));
        } else {
            bufferedSends.push(data);
        }
    };

    var close = function () {
        if (tradeSocket) {
            tradeSocket.close();
        }
    };

    return {
        init: init,
        send: send,
        close: close,
        socket: function () { return tradeSocket; }
    };

})();
<|endoftext|>


<commit_before>/////////////////////////////////////////////////////////////////////////////////
// Copyright (c) Autodesk, Inc. All rights reserved
// Written by Jaime Rosales 2016 - Forge Developer Partner Services
//
// Permission to use, copy, modify, and distribute this software in
// object code form for any purpose and without fee is hereby granted,
// provided that the above copyright notice appears in all copies and
// that both that copyright notice and the limited warranty and
// restricted rights notice below appear in all supporting
// documentation.
//
// AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS.
// AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF
// MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE.  AUTODESK, INC.
// DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE
// UNINTERRUPTED OR ERROR FREE.
/////////////////////////////////////////////////////////////////////////////////

import Client from '../Client';
import ModelTransformerExtension from '../../Viewing.Extension.ModelTransformer';
import EventTool from '../Viewer.EventTool/Viewer.EventTool'

var viewer;
var pointer;

var getToken = { accessToken: Client.getaccesstoken()};
var pointData ={};

/// WHY I'M USING GLOBAL VARIABLES, SIMPLE I'M SETTING UP WITH REACT-SCRIPTS FOR EASIER 3RD PARTY DEVELOPER USE OF PROJECT
/// https://github.com/facebookincubator/create-react-app/blob/master/packages/react-scripts/template/README.md#using-global-variables

const Autodesk = window.Autodesk;
const THREE = window.THREE;

function launchViewer(documentId) {
 getToken.accessToken.then((token) => {
    var options = {
            env: 'AutodeskProduction',
            accessToken: token.access_token
    };

    var viewerDiv = document.getElementById('viewerDiv');
    viewer = new Autodesk.Viewing.Private.GuiViewer3D(viewerDiv);

    Autodesk.Viewing.Initializer(options, function onInitialized(){
        var errorCode = viewer.start();

        // Check for initialization errors.
        if (errorCode) {
            console.error('viewer.start() error - errorCode:' + errorCode);
            return;
        }
            Autodesk.Viewing.Document.load(documentId, onDocumentLoadSuccess, onDocumentLoadFailure);
        });
 })
}

/**
 * Autodesk.Viewing.Document.load() success callback.
 * Proceeds with model initialization.
 */
function onDocumentLoadSuccess(doc) {

    // A document contains references to 3D and 2D viewables.
    var viewables = Autodesk.Viewing.Document.getSubItemsWithProperties(doc.getRootItem(), {'type':'geometry'}, true);
    if (viewables.length === 0) {
        console.error('Document contains no viewables.');
        return;
    }

    var eventTool = new EventTool(viewer)
    eventTool.activate()
    eventTool.on('singleclick', (event) => {
        pointer = event
    })

    //load model.
    viewer.addEventListener(Autodesk.Viewing.GEOMETRY_LOADED_EVENT, onGeometryLoadedHandler);
    viewer.addEventListener(Autodesk.Viewing.AGGREGATE_SELECTION_CHANGED_EVENT,onSelection);
    viewer.prefs.tag('ignore-producer');
    viewer.impl.disableRollover(true);
    viewer.loadExtension(ModelTransformerExtension, {
         parentControl: 'modelTools',
         autoLoad: true
    })
    // Choose any of the available viewables.
    var indexViewable = 0;
    var lmvDoc = doc;

    // Everything is set up, load the model.
    loadModel(viewables, lmvDoc, indexViewable);
}

/**
* Autodesk.Viewing.Document.load() failuire callback.
**/
function onDocumentLoadFailure(viewerErrorCode) {
    console.error('onDocumentLoadFailure() - errorCode:' + viewerErrorCode);
}

//////////////////////////////////////////////////////////////////////////
// Model Geometry loaded callback
//
//////////////////////////////////////////////////////////////////////////
function onGeometryLoadedHandler(event) {
        var viewer = event.target;
        viewer.removeEventListener(
                Autodesk.Viewing.GEOMETRY_LOADED_EVENT,
                onGeometryLoadedHandler);
        viewer.fitToView();
}

function loadNextModel(documentId) {
     const extInstance = viewer.getExtension(ModelTransformerExtension);
     const pickVar = extInstance.panel;

     pickVar.tooltip.setContent(`

        Pick position ...

`, '#pickTooltipId')

    if (!pointData.point){
        alert('You need to select a point in your house to snap the AC first');
        pickVar.tooltip.activate();
    }
    else{
        alert('Attaching Ac Unit')
        Autodesk.Viewing.Document.load(documentId, onDocumentLoadSuccess, onDocumentLoadFailure);
        pickVar.tooltip.deactivate();
    }

}

function onSelection (event) {

    if (event.selections && event.selections.length) {
      // const selection = event.selections[0]
      // const dbIds = selection.dbIdArray
      pointData = viewer.clientToWorld(
        pointer.canvasX,
        pointer.canvasY,
        true)

    }
}

function matrixTransform(){

        var matrix = new THREE.Matrix4();
        var t;
        var euler;

        switch (pointData.face.normal.x * pointData.face.normal.y === 0){
        case (pointData.face.normal.x === 0 && pointData.face.normal.y === -1) :
            t = new THREE.Vector3(pointData.point.x , pointData.point.y+5, pointData.point.z);
            euler = new THREE.Euler(90 * Math.PI/180, 0, 0,'XYZ');
            console.log('Inside Y = -1 Wall');
            break;
        case (pointData.face.normal.x === 0 && pointData.face.normal.y === 1):
            t = new THREE.Vector3(pointData.point.x , pointData.point.y +5, pointData.point.z);
            euler = new THREE.Euler( 0, 0, 0, 'XYZ');
            console.log('Inside Y = 1 Wall');
            break;
        case (pointData.face.normal.x === -1 && pointData.face.normal.y === 0):
            t = new THREE.Vector3(pointData.point.x , pointData.point.y, pointData.point.z);
            euler = new THREE.Euler( 0, 0, 90 * Math.PI/180, 'XYZ');
            console.log('Inside X = -1 Wall');
            break;
        case (pointData.face.normal.x === 1 && pointData.face.normal.y === 0):
            t = new THREE.Vector3(pointData.point.x , pointData.point.y, pointData.point.z);
            euler = new THREE.Euler( 0, 0, 270 * Math.PI/180, 'XYZ');
            console.log('Inside X = 1 Wall');
            break;
        default:
            alert('You need to select one of the walls for this AC Unit');
        }

        var q = new THREE.Quaternion();
        q.setFromEuler(euler);
        var s = new THREE.Vector3(0.2, 0.2, 0.2);
        matrix.compose(t, q, s);

        return matrix

}

function loadModel(viewables, lmvDoc, indexViewable) {
    return new Promise(async(resolve, reject)=> {
        var initialViewable = viewables[indexViewable];
        var svfUrl = lmvDoc.getViewablePath(initialViewable);
        var modelOptions;
        var modelName;

        if (lmvDoc.myData.guid.toString() === "dXJuOmFkc2sub2JqZWN0czpvcy5vYmplY3Q6dmlld2VyLXJvY2tzLXJlYWN0L2NlaWxpbmctY29ybmVyLmYzZA"){

            modelOptions = {
                placementTransform: matrixTransform()
            };
            modelName = "Ac_unit.f3d"
        }
        else {
            modelOptions = {
                sharedPropertyDbPath: lmvDoc.getPropertyDbPath(),
            };
            modelName = "fabric.rvt"
        }

        viewer.loadModel(svfUrl, modelOptions, (model) => {
            model.name = modelName;
            resolve(model)
        })
    })
}

const Helpers = {
  launchViewer,
  loadNextModel
};

export default Helpers;<commit_msg>removing second alert message<commit_after>/////////////////////////////////////////////////////////////////////////////////
// Copyright (c) Autodesk, Inc. All rights reserved
// Written by Jaime Rosales 2016 - Forge Developer Partner Services
//
// Permission to use, copy, modify, and distribute this software in
// object code form for any purpose and without fee is hereby granted,
// provided that the above copyright notice appears in all copies and
// that both that copyright notice and the limited warranty and
// restricted rights notice below appear in all supporting
// documentation.
//
// AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS.
// AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF
// MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE.  AUTODESK, INC.
// DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE
// UNINTERRUPTED OR ERROR FREE.
/////////////////////////////////////////////////////////////////////////////////

import Client from '../Client';
import ModelTransformerExtension from '../../Viewing.Extension.ModelTransformer';
import EventTool from '../Viewer.EventTool/Viewer.EventTool'

var viewer;
var pointer;

var getToken = { accessToken: Client.getaccesstoken()};
var pointData ={};

/// WHY I'M USING GLOBAL VARIABLES, SIMPLE I'M SETTING UP WITH REACT-SCRIPTS FOR EASIER 3RD PARTY DEVELOPER USE OF PROJECT
/// https://github.com/facebookincubator/create-react-app/blob/master/packages/react-scripts/template/README.md#using-global-variables

const Autodesk = window.Autodesk;
const THREE = window.THREE;

function launchViewer(documentId) {
 getToken.accessToken.then((token) => {
    var options = {
            env: 'AutodeskProduction',
            accessToken: token.access_token
    };

    var viewerDiv = document.getElementById('viewerDiv');
    viewer = new Autodesk.Viewing.Private.GuiViewer3D(viewerDiv);

    Autodesk.Viewing.Initializer(options, function onInitialized(){
        var errorCode = viewer.start();

        // Check for initialization errors.
        if (errorCode) {
            console.error('viewer.start() error - errorCode:' + errorCode);
            return;
        }
            Autodesk.Viewing.Document.load(documentId, onDocumentLoadSuccess, onDocumentLoadFailure);
        });
 })
}

/**
 * Autodesk.Viewing.Document.load() success callback.
 * Proceeds with model initialization.
 */
function onDocumentLoadSuccess(doc) {

    // A document contains references to 3D and 2D viewables.
    var viewables = Autodesk.Viewing.Document.getSubItemsWithProperties(doc.getRootItem(), {'type':'geometry'}, true);
    if (viewables.length === 0) {
        console.error('Document contains no viewables.');
        return;
    }

    var eventTool = new EventTool(viewer)
    eventTool.activate()
    eventTool.on('singleclick', (event) => {
        pointer = event
    })

    //load model.
    viewer.addEventListener(Autodesk.Viewing.GEOMETRY_LOADED_EVENT, onGeometryLoadedHandler);
    viewer.addEventListener(Autodesk.Viewing.AGGREGATE_SELECTION_CHANGED_EVENT,onSelection);
    viewer.prefs.tag('ignore-producer');
    viewer.impl.disableRollover(true);
    viewer.loadExtension(ModelTransformerExtension, {
         parentControl: 'modelTools',
         autoLoad: true
    })
    // Choose any of the available viewables.
    var indexViewable = 0;
    var lmvDoc = doc;

    // Everything is set up, load the model.
    loadModel(viewables, lmvDoc, indexViewable);
}

/**
* Autodesk.Viewing.Document.load() failuire callback.
**/
function onDocumentLoadFailure(viewerErrorCode) {
    console.error('onDocumentLoadFailure() - errorCode:' + viewerErrorCode);
}

//////////////////////////////////////////////////////////////////////////
// Model Geometry loaded callback
//
//////////////////////////////////////////////////////////////////////////
function onGeometryLoadedHandler(event) {
        var viewer = event.target;
        viewer.removeEventListener(
                Autodesk.Viewing.GEOMETRY_LOADED_EVENT,
                onGeometryLoadedHandler);
        viewer.fitToView();
}

function loadNextModel(documentId) {
     const extInstance = viewer.getExtension(ModelTransformerExtension);
     const pickVar = extInstance.panel;

     pickVar.tooltip.setContent(`

        Pick position ...

`, '#pickTooltipId')

    if (!pointData.point){
        alert('You need to select a point in your house to snap the AC first');
        pickVar.tooltip.activate();
    }
    else{
        Autodesk.Viewing.Document.load(documentId, onDocumentLoadSuccess, onDocumentLoadFailure);
        pickVar.tooltip.deactivate();
    }

}

function onSelection (event) {

    if (event.selections && event.selections.length) {
      // const selection = event.selections[0]
      // const dbIds = selection.dbIdArray
      pointData = viewer.clientToWorld(
        pointer.canvasX,
        pointer.canvasY,
        true)

    }
}

function matrixTransform(){

        var matrix = new THREE.Matrix4();
        var t;
        var euler;

        switch (pointData.face.normal.x * pointData.face.normal.y === 0){
        case (pointData.face.normal.x === 0 && pointData.face.normal.y === -1) :
            t = new THREE.Vector3(pointData.point.x , pointData.point.y+5, pointData.point.z);
            euler = new THREE.Euler(90 * Math.PI/180, 0, 0,'XYZ');
            console.log('Inside Y = -1 Wall');
            break;
        case (pointData.face.normal.x === 0 && pointData.face.normal.y === 1):
            t = new THREE.Vector3(pointData.point.x , pointData.point.y +5, pointData.point.z);
            euler = new THREE.Euler( 0, 0, 0, 'XYZ');
            console.log('Inside Y = 1 Wall');
            break;
        case (pointData.face.normal.x === -1 && pointData.face.normal.y === 0):
            t = new THREE.Vector3(pointData.point.x , pointData.point.y, pointData.point.z);
            euler = new THREE.Euler( 0, 0, 90 * Math.PI/180, 'XYZ');
            console.log('Inside X = -1 Wall');
            break;
        case (pointData.face.normal.x === 1 && pointData.face.normal.y === 0):
            t = new THREE.Vector3(pointData.point.x , pointData.point.y, pointData.point.z);
            euler = new THREE.Euler( 0, 0, 270 * Math.PI/180, 'XYZ');
            console.log('Inside X = 1 Wall');
            break;
        default:
            alert('You need to select one of the walls for this AC Unit');
        }

        var q = new THREE.Quaternion();
        q.setFromEuler(euler);
        var s = new THREE.Vector3(0.2, 0.2, 0.2);
        matrix.compose(t, q, s);

        return matrix

}

function loadModel(viewables, lmvDoc, indexViewable) {
    return new Promise(async(resolve, reject)=> {
        var initialViewable = viewables[indexViewable];
        var svfUrl = lmvDoc.getViewablePath(initialViewable);
        var modelOptions;
        var modelName;

        if (lmvDoc.myData.guid.toString() === "dXJuOmFkc2sub2JqZWN0czpvcy5vYmplY3Q6dmlld2VyLXJvY2tzLXJlYWN0L2NlaWxpbmctY29ybmVyLmYzZA"){

            modelOptions = {
                placementTransform: matrixTransform()
            };
            modelName = "Ac_unit.f3d"
        }
        else {
            modelOptions = {
                sharedPropertyDbPath: lmvDoc.getPropertyDbPath(),
            };
            modelName = "fabric.rvt"
        }

        viewer.loadModel(svfUrl, modelOptions, (model) => {
            model.name = modelName;
            resolve(model)
        })
    })
}

const Helpers = {
  launchViewer,
  loadNextModel
};

export default Helpers;<|endoftext|>


<commit_before>function bindScrollbar(scrollbar, scrollable) {
	$(scrollbar).data('target',scrollable);
	$(scrollable).data('controller',scrollbar).mousemove(function(e){scroll(e)}).bind('mousewheel',
	    function(e, delta, deltaX,deltaY){
		targ = $(this);
		//alert(e.wheelDelta)
		newscroll = targ.scrollTop()+(e.wheelDelta/-30);
		maxscroll = getMaxScroll(targ);
		newscroll = Math.max(Math.min(maxscroll, newscroll),0)
		targ.scrollTop(newscroll)
		setScrollbarFromScrollable(targ)
	});
	setScrollbarFromScrollable($(scrollable));
}

function unbindScrollbar(scrollbar) {
	scrollable = $(scrollbar.data('target'))
	scrollable.unbind('mousemove').removeData('controller')
	scrollbar.removeData('target')
}

function getMaxScroll(scrollTarget) {
	lastkid = $($(scrollTarget).children().last())
	if (lastkid.length==0) {return 0}
	return lastkid.position().top+lastkid.outerHeight()+scrollTarget.scrollTop()-scrollTarget.height();
}

function setScrollbarFromScrollable(scrollable) {
	maxscroll = getMaxScroll(scrollable);
	percent = scrollable.scrollTop()/maxscroll*100;
	$(scrollable.data('controller')).css('top',""+percent+"%");
}

function setAllScrollbars() {
	$('.scrollbar').each(function(){
		setScrollbarFromScrollable($($(this).data('target')))
	});
}

$(window).resize(setAllScrollbars);

function reset_scroll(scrollable){
	scrollable.scrollTop(0);
	$(scrollable.data('controller')).css('top',"0%");
}

function scroll(e) {
	// this is where the magic happens
	if (scrollDriver != null) {
		// find the percentage at the mouse position
		percent = Math.max(Math.min((e.pageY-124)/scrollDriver.parent().height()*100,100),0)
		scrollDriver.css('top',""+percent+"%");
		if (scrollTarget != null) {
			scrollTarget = $(scrollTarget)
			maxscroll = getMaxScroll(scrollTarget)
			scrollTarget.scrollTop(maxscroll*percent/100)
		}
	}
}

scrollTarget = null;
scrollDriver = null;

$('.scrollregion').mousedown(function (e){
	// start a drag
	scrollDriver = $($(this).children('.scrollbar')[0]);
	scrollTarget = scrollDriver.data('target');
	scroll(e)
}).mouseup(function() {
	// end it
	scrollDriver = null;
	scrollTarget = null;
}).mousemove(function(e){
	scroll(e)
});

$('body').mouseup(function() {
	scrollDriver = null;
	scrollTarget = null;
})
<commit_msg>Made scrollwheel faster<commit_after>function bindScrollbar(scrollbar, scrollable) {
	$(scrollbar).data('target',scrollable);
	$(scrollable).data('controller',scrollbar).mousemove(function(e){scroll(e)}).bind('mousewheel',
	    function(e, delta, deltaX,deltaY){
		targ = $(this);
		//alert(e.wheelDelta)
		newscroll = targ.scrollTop()+(e.wheelDelta/-5);
		maxscroll = getMaxScroll(targ);
		newscroll = Math.max(Math.min(maxscroll, newscroll),0)
		targ.scrollTop(newscroll)
		setScrollbarFromScrollable(targ)
	});
	setScrollbarFromScrollable($(scrollable));
}

function unbindScrollbar(scrollbar) {
	scrollable = $(scrollbar.data('target'))
	scrollable.unbind('mousemove').removeData('controller')
	scrollbar.removeData('target')
}

function getMaxScroll(scrollTarget) {
	lastkid = $($(scrollTarget).children().last())
	if (lastkid.length==0) {return 0}
	return lastkid.position().top+lastkid.outerHeight()+scrollTarget.scrollTop()-scrollTarget.height();
}

function setScrollbarFromScrollable(scrollable) {
	maxscroll = getMaxScroll(scrollable);
	percent = scrollable.scrollTop()/maxscroll*100;
	$(scrollable.data('controller')).css('top',""+percent+"%");
}

function setAllScrollbars() {
	$('.scrollbar').each(function(){
		setScrollbarFromScrollable($($(this).data('target')))
	});
}

$(window).resize(setAllScrollbars);

function reset_scroll(scrollable){
	scrollable.scrollTop(0);
	$(scrollable.data('controller')).css('top',"0%");
}

function scroll(e) {
	// this is where the magic happens
	if (scrollDriver != null) {
		// find the percentage at the mouse position
		percent = Math.max(Math.min((e.pageY-124)/scrollDriver.parent().height()*100,100),0)
		scrollDriver.css('top',""+percent+"%");
		if (scrollTarget != null) {
			scrollTarget = $(scrollTarget)
			maxscroll = getMaxScroll(scrollTarget)
			scrollTarget.scrollTop(maxscroll*percent/100)
		}
	}
}

scrollTarget = null;
scrollDriver = null;

$('.scrollregion').mousedown(function (e){
	// start a drag
	scrollDriver = $($(this).children('.scrollbar')[0]);
	scrollTarget = scrollDriver.data('target');
	scroll(e)
}).mouseup(function() {
	// end it
	scrollDriver = null;
	scrollTarget = null;
}).mousemove(function(e){
	scroll(e)
});

$('body').mouseup(function() {
	scrollDriver = null;
	scrollTarget = null;
})
<|endoftext|>


<commit_before>TC.control = TC.control || {};

if (!TC.Control) {
    TC.syncLoadJS(TC.apiLocation + 'TC/Control.js');
}

TC.Consts.event.POPUP = 'popup.tc';
TC.Consts.event.POPUPHIDE = 'popuphide.tc';
TC.Consts.classes.DRAG = TC.Consts.classes.DRAG || 'tc-drag';
TC.Consts.classes.DRAGGED = TC.Consts.classes.DRAGGED || 'tc-dragged';
TC.Consts.classes.DRAGGABLE = TC.Consts.classes.DRAGGABLE || 'tc-draggable';

TC.control.Popup = function () {
    var self = this;

    TC.Control.apply(self, arguments);
    self.currentFeature = null;
    //self.wrap = { popup: null };
    self.wrap = new TC.wrap.control.Popup(self);
};

TC.inherit(TC.control.Popup, TC.Control);

(function () {
    var ctlProto = TC.control.Popup.prototype;

    ctlProto.CLASS = 'tc-ctl-popup';

    ctlProto.render = function () {
    };

    ctlProto.register = function (map) {
        var self = this;
        TC.Control.prototype.register.call(self, map);

        $.when(map.wrap.addPopup(self)).then(function () {

            map.on(TC.Consts.event.LAYERVISIBILITY, function (e) {
                if (self.currentFeature && self.currentFeature.layer === e.layer && !e.layer.getVisibility()) {
                    self.hide();
                }
            });

            map.on(TC.Consts.event.UPDATE, function () {
                if (!self.currentFeature || self.currentFeature._visibilityState === TC.Consts.visibility.NOT_VISIBLE) {
                    self.hide();
                }
            });

            map.on(TC.Consts.event.FEATUREREMOVE, function (e) {
                if (self.currentFeature === e.feature) {
                    self.hide();
                }
            });
        });
    };

    ctlProto.fitToView = function () {
        this.wrap.fitToView();
    };

    ctlProto.hide = function () {
        var self = this;
        if (self.map) {
            self.map.wrap.hidePopup(self);
            self.setDragged(false);
            self.map.$events.trigger($.Event(TC.Consts.event.POPUPHIDE, { control: self }));
        }
    };

    ctlProto.setDragged = function (dragged) {
        var self = this;
        self.dragged = dragged;
        if (self.$popupDiv) {
            self.$popupDiv.toggleClass(TC.Consts.classes.DRAGGED, dragged);
        }
        self.wrap.setDragged(dragged);
    };

    ctlProto.setDragging = function (dragging) {
        var self = this;
        if (dragging) {
            self.setDragged(true);
        }
        self.$popupDiv.toggleClass(TC.Consts.classes.DRAG, dragging);
    };

    ctlProto.isVisible = function () {
        var self = this;

        return self.$popupDiv.hasClass(TC.Consts.classes.VISIBLE);
    };

})();<commit_msg>Add event listener to layerremove<commit_after>TC.control = TC.control || {};

if (!TC.Control) {
    TC.syncLoadJS(TC.apiLocation + 'TC/Control.js');
}

TC.Consts.event.POPUP = 'popup.tc';
TC.Consts.event.POPUPHIDE = 'popuphide.tc';
TC.Consts.classes.DRAG = TC.Consts.classes.DRAG || 'tc-drag';
TC.Consts.classes.DRAGGED = TC.Consts.classes.DRAGGED || 'tc-dragged';
TC.Consts.classes.DRAGGABLE = TC.Consts.classes.DRAGGABLE || 'tc-draggable';

TC.control.Popup = function () {
    var self = this;

    TC.Control.apply(self, arguments);
    self.currentFeature = null;
    //self.wrap = { popup: null };
    self.wrap = new TC.wrap.control.Popup(self);
};

TC.inherit(TC.control.Popup, TC.Control);

(function () {
    var ctlProto = TC.control.Popup.prototype;

    ctlProto.CLASS = 'tc-ctl-popup';

    ctlProto.render = function () {
    };

    ctlProto.register = function (map) {
        var self = this;
        TC.Control.prototype.register.call(self, map);

        $.when(map.wrap.addPopup(self)).then(function () {

            map.on(TC.Consts.event.LAYERVISIBILITY, function (e) {
                if (self.currentFeature && self.currentFeature.layer === e.layer && !e.layer.getVisibility()) {
                    self.hide();
                }
            });

            map.on(TC.Consts.event.LAYERREMOVE, function (e) {
                if (self.currentFeature && self.currentFeature.layer === e.layer) {
                    self.hide();
                }
            });

            map.on(TC.Consts.event.UPDATE, function () {
                if (!self.currentFeature || self.currentFeature._visibilityState === TC.Consts.visibility.NOT_VISIBLE) {
                    self.hide();
                }
            });

            map.on(TC.Consts.event.FEATUREREMOVE, function (e) {
                if (self.currentFeature === e.feature) {
                    self.hide();
                }
            });
        });
    };

    ctlProto.fitToView = function (delayed) {
        var self = this;
        if (delayed) {
            setTimeout(function () {
                self.wrap.fitToView();
            }, 1000);
        }
        else {
            self.wrap.fitToView();
        }
    };

    ctlProto.hide = function () {
        var self = this;
        if (self.map) {
            self.map.wrap.hidePopup(self);
            self.setDragged(false);
            self.map.$events.trigger($.Event(TC.Consts.event.POPUPHIDE, { control: self }));
        }
    };

    ctlProto.setDragged = function (dragged) {
        var self = this;
        self.dragged = dragged;
        if (self.$popupDiv) {
            self.$popupDiv.toggleClass(TC.Consts.classes.DRAGGED, dragged);
        }
        self.wrap.setDragged(dragged);
    };

    ctlProto.setDragging = function (dragging) {
        var self = this;
        if (dragging) {
            self.setDragged(true);
        }
        self.$popupDiv.toggleClass(TC.Consts.classes.DRAG, dragging);
    };

    ctlProto.isVisible = function () {
        var self = this;

        return self.$popupDiv && self.$popupDiv.hasClass(TC.Consts.classes.VISIBLE);
    };

})();<|endoftext|>


<commit_before>const Promise = require('bluebird')
const WindowManager = require('./window_manager')
const { autoUpdater } = require('electron-updater')
const { translate } = require('./i18n')
const { dialog } = require('electron')
const path = require('path')

const log = require('../../core/app').logger({
  component: 'GUI:autoupdater'
})

/** The delay starting from the update info request after which it is skipped.
 *
 * Long enough so users with slow connection have chances to start downloading
 * the update before it is skipped (5s was tried but didn't seem sufficient in
 * some cases).
 *
 * App startup could be slower in case GitHub is down exactly at the same
 * time. But the delay still seems acceptable  for an app starting
 * automatically on boot. Even in case it is started by hand.
 *
 * Except for downtimes, users with fast connection should still get a fast
 * available or unavailable update answer anyway.
 */
const UPDATE_CHECK_TIMEOUT = 10000

const UPDATE_RETRY_DELAY = 1000

module.exports = class UpdaterWM extends WindowManager {
  windowOptions() {
    return {
      title: 'UPDATER',
      width: 500,
      height: 400
    }
  }

  humanError(err) {
    switch (err.code) {
      case 'EPERM':
        return translate('Updater Error EPERM')
      case 'ENOSP':
        return translate('Updater Error ENOSPC')
      default:
        return translate('Updater Error Other')
    }
  }

  constructor(...opts) {
    autoUpdater.logger = log
    autoUpdater.autoDownload = false
    autoUpdater.on('update-available', info => {
      this.clearTimeoutIfAny()
      log.info({ update: info, skipped: this.skipped }, 'Update available')
      // Make sure UI doesn't show up after timeout
      if (!this.skipped) {
        const shouldUpdate =
          dialog.showMessageBoxSync({
            icon: path.resolve(__dirname, '..', 'images', 'icon.png'),
            title: 'Cozy Drive',
            message: 'Cozy Drive',
            detail: translate(
              'A new version is available, do you want to update?'
            ),
            type: 'question',
            buttons: ['Update', 'Cancel'].map(translate)
          }) === 0

        if (shouldUpdate) {
          autoUpdater.downloadUpdate()
          this.show()
        } else {
          this.skipUpdate('refused update')
        }
      }
    })
    autoUpdater.on('update-not-available', info => {
      log.info({ update: info }, 'No update available')
      this.afterUpToDate()
    })
    autoUpdater.on('error', async err => {
      if (this.skipped) {
        return
      } else if (err.code === 'ENOENT') {
        this.skipUpdate('assuming development environment')
      } else {
        await Promise.delay(UPDATE_RETRY_DELAY)
        await autoUpdater.checkForUpdates()
      }
    })
    autoUpdater.on('download-progress', progressObj => {
      log.trace({ progress: progressObj }, 'Downloading...')
      this.send('update-downloading', progressObj)
    })
    autoUpdater.on('update-downloaded', info => {
      log.info({ update: info }, 'Update downloaded. Exit and install...')
      setImmediate(() =>
        this.desktop
          .stopSync()
          .then(() => this.desktop.pouch.db.close())
          .then(() => autoUpdater.quitAndInstall())
          .then(() => this.app.quit())
          .then(() => this.app.exit(0))
          .catch(err => this.send('error-updating', this.humanError(err)))
      )
    })

    super(...opts)
  }

  clearTimeoutIfAny() {
    if (this.timeout) {
      clearTimeout(this.timeout)
      this.timeout = null
    }
  }

  onUpToDate(handler) {
    this.afterUpToDate = () => {
      this.clearTimeoutIfAny()
      handler()
    }
  }

  async checkForUpdates() {
    this.skipped = false
    this.timeout = setTimeout(() => {
      this.skipUpdate(`check is taking more than ${UPDATE_CHECK_TIMEOUT} ms`)
    }, UPDATE_CHECK_TIMEOUT)

    try {
      await autoUpdater.checkForUpdates()
    } catch (err) {
      // Dealing with the error itself is alreay done via the listener on the
      // `error` event.
      log.error({ err })
    }
  }

  skipUpdate(reason) {
    log.info({ sentry: true }, `Not updating: ${reason}`)
    this.skipped = true

    // Disable handler & warn on future calls
    const handler = this.afterUpToDate
    this.afterUpToDate = () => {}

    if (typeof handler === 'function') handler()
  }

  hash() {
    return '#updater'
  }

  ipcEvents() {
    return {}
  }
}
<commit_msg>gui/updater: Stop retries after a few errors (#2233)<commit_after>const Promise = require('bluebird')
const WindowManager = require('./window_manager')
const { autoUpdater } = require('electron-updater')
const { translate } = require('./i18n')
const { dialog } = require('electron')
const path = require('path')

const log = require('../../core/app').logger({
  component: 'GUI:autoupdater'
})

/** The delay starting from the update info request after which it is skipped.
 *
 * Long enough so users with slow connection have chances to start downloading
 * the update before it is skipped (5s was tried but didn't seem sufficient in
 * some cases).
 *
 * App startup could be slower in case GitHub is down exactly at the same
 * time. But the delay still seems acceptable  for an app starting
 * automatically on boot. Even in case it is started by hand.
 *
 * Except for downtimes, users with fast connection should still get a fast
 * available or unavailable update answer anyway.
 */
const UPDATE_CHECK_TIMEOUT = 10000
const UPDATE_RETRY_DELAY = 1000
const UPDATE_RETRIES = 5

module.exports = class UpdaterWM extends WindowManager {
  /*::
  retriesLeft: number
  */

  windowOptions() {
    return {
      title: 'UPDATER',
      width: 500,
      height: 400
    }
  }

  humanError(err) {
    switch (err.code) {
      case 'EPERM':
        return translate('Updater Error EPERM')
      case 'ENOSP':
        return translate('Updater Error ENOSPC')
      default:
        return translate('Updater Error Other')
    }
  }

  constructor(...opts) {
    autoUpdater.logger = log
    autoUpdater.autoDownload = false
    autoUpdater.on('update-available', info => {
      this.clearTimeoutIfAny()
      log.info({ update: info, skipped: this.skipped }, 'Update available')
      // Make sure UI doesn't show up after timeout
      if (!this.skipped) {
        const shouldUpdate =
          dialog.showMessageBoxSync({
            icon: path.resolve(__dirname, '..', 'images', 'icon.png'),
            title: 'Cozy Drive',
            message: 'Cozy Drive',
            detail: translate(
              'A new version is available, do you want to update?'
            ),
            type: 'question',
            buttons: ['Update', 'Cancel'].map(translate)
          }) === 0

        if (shouldUpdate) {
          autoUpdater.downloadUpdate()
          this.show()
        } else {
          this.skipUpdate('refused update')
        }
      }
    })
    autoUpdater.on('update-not-available', info => {
      log.info({ update: info }, 'No update available')
      this.afterUpToDate()
    })
    autoUpdater.on('error', async err => {
      if (this.skipped) {
        return
      } else if (err.code === 'ENOENT') {
        this.skipUpdate('assuming development environment')
      } else if (this.retriesLeft > 0) {
        this.retriesLeft--
        await Promise.delay(UPDATE_RETRY_DELAY)
        await autoUpdater.checkForUpdates()
      } else {
        this.retriesLeft = UPDATE_RETRIES
        this.skipUpdate(err.message)
      }
    })
    autoUpdater.on('download-progress', progressObj => {
      log.trace({ progress: progressObj }, 'Downloading...')
      this.send('update-downloading', progressObj)
    })
    autoUpdater.on('update-downloaded', info => {
      log.info({ update: info }, 'Update downloaded. Exit and install...')
      setImmediate(() =>
        this.desktop
          .stopSync()
          .then(() => this.desktop.pouch.db.close())
          .then(() => autoUpdater.quitAndInstall())
          .then(() => this.app.quit())
          .then(() => this.app.exit(0))
          .catch(err => this.send('error-updating', this.humanError(err)))
      )
    })

    super(...opts)

    this.retriesLeft = UPDATE_RETRIES
  }

  clearTimeoutIfAny() {
    if (this.timeout) {
      clearTimeout(this.timeout)
      this.timeout = null
    }
  }

  onUpToDate(handler) {
    this.afterUpToDate = () => {
      this.clearTimeoutIfAny()
      handler()
    }
  }

  async checkForUpdates() {
    this.skipped = false
    this.timeout = setTimeout(() => {
      this.skipUpdate(`check is taking more than ${UPDATE_CHECK_TIMEOUT} ms`)
    }, UPDATE_CHECK_TIMEOUT)

    try {
      await autoUpdater.checkForUpdates()
    } catch (err) {
      // Dealing with the error itself is alreay done via the listener on the
      // `error` event.
      log.error({ err })
    }
  }

  skipUpdate(reason) {
    log.info({ sentry: true }, `Not updating: ${reason}`)
    this.skipped = true

    // Disable handler & warn on future calls
    const handler = this.afterUpToDate
    this.afterUpToDate = () => {}

    if (typeof handler === 'function') handler()
  }

  hash() {
    return '#updater'
  }

  ipcEvents() {
    return {}
  }
}
<|endoftext|>


<commit_before>var RichText = require('../../services/RichText')
var TimeText = require('../../services/TimeText')
var truncate = require('html-truncate')

module.exports = function ($scope, $state, $rootScope, $modal, $dialog, $analytics, growl, Post, User, UserCache, CurrentUser) {
  'ngInject'

  $scope.singlePost = $state.current.data && $state.current.data.singlePost
  $scope.isCommentsCollapsed = !$scope.singlePost
  $scope.voteTooltipText = ''
  $scope.followersNotMe = []
  $scope.isFollowing = false
  $scope.eventResponse = ''
  $scope.joinPostText = ''
  $scope.onlyAuthorFollowing = false

  var currentUser = CurrentUser.get()
  var voteText = "click to  me."
  var unvoteText = "click to un- me."
  var post = $scope.post

  $scope.community = Post.relevantCommunity(post, currentUser)

  $scope.isPostOwner = function () {
    return CurrentUser.is(post.user && post.user.id)
  }

  $scope.markFulfilled = function () {
    var modalInstance = $modal.open({
      templateUrl: '/ui/app/fulfillModal.tpl.html',
      controller: 'FulfillmentCtrl',
      keyboard: false,
      backdrop: 'static',
      scope: $scope
    })

    modalInstance.result.then(() => $analytics.eventTrack('Post: Fulfill', {post_id: post.id}))
  }

  // Voting is the same thing as "liking"
  $scope.vote = function () {
    post.myVote = !post.myVote
    post.votes += (post.myVote ? 1 : -1)
    $scope.voteTooltipText = post.myVote ? unvoteText : voteText

    Post.vote({id: post.id}, function () {
      $analytics.eventTrack('Post: Like', {
        post_id: post.id,
        state: (post.myVote ? 'on' : 'off')
      })
    }, function (resp) {
      if (_.contains([401, 403], resp.status)) {
        $scope.$emit('unauthorized', {context: 'like'})
      }
    })
  }

  $scope.toggleComments = function () {
    if ($scope.isCommentsCollapsed) {
      $analytics.eventTrack('Post: Comments: Show', {post_id: post.id})
    }
    $scope.isCommentsCollapsed = !$scope.isCommentsCollapsed
  }

  $scope.toggleFollow = function () {
    var user = currentUser
    if (!user) return

    if (!$scope.isFollowing) {
      $analytics.eventTrack('Post: Join', {post_id: post.id})
      post.followers.push({
        id: user.id,
        name: user.name,
        avatar_url: user.avatar
      })
      Post.follow({id: post.id})
      UserCache.followedPosts.clear(user.id)
    } else {
      $analytics.eventTrack('Post: Leave', {post_id: post.id})
      post.followers = _.without(post.followers, _.findWhere(post.followers, {id: user.id}))
      Post.follow({id: post.id})
      UserCache.followedPosts.remove(user.id, post.id)
    }
  }

  $scope['delete'] = function () {
    $dialog.confirm({
      message: 'Are you sure you want to remove "' + post.name + '"? This cannot be undone.'
    }).then(function () {
      $scope.removeFn({postToRemove: post})
      new Post(post).$remove({})
    })
  }

  $scope.showMore = function () {
    setText(true)
  }

  $scope.openFollowers = function (isOpen) {
    if (isOpen) {
      $analytics.eventTrack('Followers: Viewed List of Followers', {num_followers: $scope.followersNotMe.length})
    }
  }

  $scope.complain = function () {
    Post.complain({id: post.id}, function () {
      growl.addSuccessMessage('Thank you for reporting this. Moderators will address it within 24 hours.')
    })
  }

  $scope.postImage = function () {
    return _.find(post.media || [], m => m.type === 'image')
  }

  $scope.docs = function () {
    return _.filter(post.media || [], m => m.type === 'gdoc')
  }

  var setText = function (fullLength) {
    var text = post.description
    if (!text) text = ''

    text = RichText.present(text)

    if (!fullLength && angular.element(text).text().trim().length > 140) {
      text = truncate(text, 140)
      $scope.truncated = true
    } else {
      $scope.truncated = false
    }

    $scope.description = text
    $scope.hasDescription = angular.element(text).text().trim().length > 0
  }

  $scope.$watchCollection('post.followers', function () {
    var meInFollowers = (currentUser && _.findWhere(post.followers, {id: currentUser.id}))

    if (meInFollowers) {
      $scope.followersNotMe = _.without(post.followers, meInFollowers)
      $scope.isFollowing = true
    } else {
      $scope.followersNotMe = post.followers
      $scope.isFollowing = false
    }

    // If the only person following the post is the author we can hide the following status in the post
    var firstFollower = _.first(post.followers)
    $scope.onlyAuthorFollowing = (post.followers.length === 1 && firstFollower.name === post.user.name)
  })

  $scope.canEdit = currentUser && (post.user.id === currentUser.id || currentUser.canModerate(post.communities[0]))
  $scope.voteTooltipText = post.myVote ? unvoteText : voteText
  setText($scope.startExpanded)

  var now = new Date()
  $scope.showUpdateTime = (now - new Date(post.updated_at)) < (now - new Date(post.created_at)) * 0.8

  $scope.truncate = truncate

  $scope.showTime = function () {
    var start = new Date(post.start_time)
    var end = post.end_time && new Date(post.end_time)
    return TimeText.range(start, end)
  }

  $scope.showFullTime = function () {
    var start = new Date(post.start_time)
    var end = post.end_time && new Date(post.end_time)
    return TimeText.rangeFullText(start, end)
  }

  $scope.changeEventResponse = function (response) {
    var user = currentUser
    if (!user) return
    if ($scope.eventResponse === response) {
      $scope.eventResponse = ''
      $analytics.eventTrack('Event: Unrespond', {post_id: post.id})
      Post.respond({id: post.id, response: response})
    } else {
      $scope.eventResponse = response
      $analytics.eventTrack('Event: Respond', {post_id: post.id, response: response})
      Post.respond({id: post.id, response: response})
    }

    /*
    if (!$scope.isFollowing) {
      $analytics.eventTrack('Post: Join', {post_id: post.id})
      post.followers.push({
        id: user.id,
        name: user.name,
        avatar_url: user.avatar
      })
      Post.follow({id: post.id})
      UserCache.followedPosts.clear(user.id)
    } else {
      $analytics.eventTrack('Post: Leave', {post_id: post.id})
      post.followers = _.without(post.followers, _.findWhere(post.followers, {id: user.id}))
      Post.follow({id: post.id})
      UserCache.followedPosts.remove(user.id, post.id)
    }
    */
  }
}
<commit_msg>clean up<commit_after>var RichText = require('../../services/RichText')
var TimeText = require('../../services/TimeText')
var truncate = require('html-truncate')

module.exports = function ($scope, $state, $rootScope, $modal, $dialog, $analytics, growl, Post, User, UserCache, CurrentUser) {
  'ngInject'

  $scope.singlePost = $state.current.data && $state.current.data.singlePost
  $scope.isCommentsCollapsed = !$scope.singlePost
  $scope.voteTooltipText = ''
  $scope.followersNotMe = []
  $scope.isFollowing = false
  $scope.eventResponse = ''
  $scope.joinPostText = ''
  $scope.onlyAuthorFollowing = false

  var currentUser = CurrentUser.get()
  var voteText = "click to  me."
  var unvoteText = "click to un- me."
  var post = $scope.post

  $scope.community = Post.relevantCommunity(post, currentUser)

  $scope.isPostOwner = function () {
    return CurrentUser.is(post.user && post.user.id)
  }

  $scope.markFulfilled = function () {
    var modalInstance = $modal.open({
      templateUrl: '/ui/app/fulfillModal.tpl.html',
      controller: 'FulfillmentCtrl',
      keyboard: false,
      backdrop: 'static',
      scope: $scope
    })

    modalInstance.result.then(() => $analytics.eventTrack('Post: Fulfill', {post_id: post.id}))
  }

  // Voting is the same thing as "liking"
  $scope.vote = function () {
    post.myVote = !post.myVote
    post.votes += (post.myVote ? 1 : -1)
    $scope.voteTooltipText = post.myVote ? unvoteText : voteText

    Post.vote({id: post.id}, function () {
      $analytics.eventTrack('Post: Like', {
        post_id: post.id,
        state: (post.myVote ? 'on' : 'off')
      })
    }, function (resp) {
      if (_.contains([401, 403], resp.status)) {
        $scope.$emit('unauthorized', {context: 'like'})
      }
    })
  }

  $scope.toggleComments = function () {
    if ($scope.isCommentsCollapsed) {
      $analytics.eventTrack('Post: Comments: Show', {post_id: post.id})
    }
    $scope.isCommentsCollapsed = !$scope.isCommentsCollapsed
  }

  $scope.toggleFollow = function () {
    var user = currentUser
    if (!user) return

    if (!$scope.isFollowing) {
      $analytics.eventTrack('Post: Join', {post_id: post.id})
      post.followers.push({
        id: user.id,
        name: user.name,
        avatar_url: user.avatar
      })
      Post.follow({id: post.id})
      UserCache.followedPosts.clear(user.id)
    } else {
      $analytics.eventTrack('Post: Leave', {post_id: post.id})
      post.followers = _.without(post.followers, _.findWhere(post.followers, {id: user.id}))
      Post.follow({id: post.id})
      UserCache.followedPosts.remove(user.id, post.id)
    }
  }

  $scope['delete'] = function () {
    $dialog.confirm({
      message: 'Are you sure you want to remove "' + post.name + '"? This cannot be undone.'
    }).then(function () {
      $scope.removeFn({postToRemove: post})
      new Post(post).$remove({})
    })
  }

  $scope.showMore = function () {
    setText(true)
  }

  $scope.openFollowers = function (isOpen) {
    if (isOpen) {
      $analytics.eventTrack('Followers: Viewed List of Followers', {num_followers: $scope.followersNotMe.length})
    }
  }

  $scope.complain = function () {
    Post.complain({id: post.id}, function () {
      growl.addSuccessMessage('Thank you for reporting this. Moderators will address it within 24 hours.')
    })
  }

  $scope.postImage = function () {
    return _.find(post.media || [], m => m.type === 'image')
  }

  $scope.docs = function () {
    return _.filter(post.media || [], m => m.type === 'gdoc')
  }

  var setText = function (fullLength) {
    var text = post.description
    if (!text) text = ''

    text = RichText.present(text)

    if (!fullLength && angular.element(text).text().trim().length > 140) {
      text = truncate(text, 140)
      $scope.truncated = true
    } else {
      $scope.truncated = false
    }

    $scope.description = text
    $scope.hasDescription = angular.element(text).text().trim().length > 0
  }

  $scope.$watchCollection('post.followers', function () {
    var meInFollowers = (currentUser && _.findWhere(post.followers, {id: currentUser.id}))

    if (meInFollowers) {
      $scope.followersNotMe = _.without(post.followers, meInFollowers)
      $scope.isFollowing = true
    } else {
      $scope.followersNotMe = post.followers
      $scope.isFollowing = false
    }

    // If the only person following the post is the author we can hide the following status in the post
    var firstFollower = _.first(post.followers)
    $scope.onlyAuthorFollowing = (post.followers.length === 1 && firstFollower.name === post.user.name)
  })

  $scope.canEdit = currentUser && (post.user.id === currentUser.id || currentUser.canModerate(post.communities[0]))
  $scope.voteTooltipText = post.myVote ? unvoteText : voteText
  setText($scope.startExpanded)

  var now = new Date()
  $scope.showUpdateTime = (now - new Date(post.updated_at)) < (now - new Date(post.created_at)) * 0.8

  $scope.truncate = truncate

  $scope.showTime = function () {
    var start = new Date(post.start_time)
    var end = post.end_time && new Date(post.end_time)
    return TimeText.range(start, end)
  }

  $scope.showFullTime = function () {
    var start = new Date(post.start_time)
    var end = post.end_time && new Date(post.end_time)
    return TimeText.rangeFullText(start, end)
  }

  $scope.changeEventResponse = function (response) {
    var user = currentUser
    if (!user) return
    if ($scope.eventResponse === response) {
      $scope.eventResponse = ''
      $analytics.eventTrack('Event: Unrespond', {post_id: post.id})
      Post.respond({id: post.id, response: response})
    } else {
      $scope.eventResponse = response
      $analytics.eventTrack('Event: Respond', {post_id: post.id, response: response})
      Post.respond({id: post.id, response: response})
    }
  }
}
<|endoftext|>


<commit_before>import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { Link } from 'react-router-dom';
import ReactTooltip from 'react-tooltip';
import Masonry from 'react-masonry-component';
import Textfit from 'react-textfit';

import ChecklistIcon from 'Icons/Checklist';
import SuggestionIcon from 'Icons/Suggestion';
import AboutIcon from 'Icons/About';
import Wrapper from 'common/Wrapper';
import SPEC_ANALYSIS_COMPLETENESS, { getCompletenessColor, getCompletenessExplanation, getCompletenessLabel } from 'common/SPEC_ANALYSIS_COMPLETENESS';
import { getResultTab } from 'selectors/url/report';
import DevelopmentTab from 'Main/DevelopmentTab';
import EventsTab from 'Main/EventsTab';
import Tab from 'Main/Tab';
import Status from 'Main/Status';
import GithubButton from 'Main/GithubButton';
import DiscordButton from 'Main/DiscordButton';
import SuggestionsTab from 'Main/SuggestionsTab';
import Maintainer from 'Main/Maintainer';

import ItemsPanel from './ItemsPanel';
import AboutTab from './AboutTab';
import ResultsWarning from './ResultsWarning';
import Header from './Header';

import './Results.css';

const MAIN_TAB = {
  CHECKLIST: 'Checklist',
  SUGGESTIONS: 'Suggestions',
  ABOUT: 'About',
};
function mainTabLabel(tab) {
  switch (tab) {
    case MAIN_TAB.CHECKLIST:
      return (
        <Wrapper>
          <ChecklistIcon /> Checklist
        </Wrapper>
      );
    case MAIN_TAB.SUGGESTIONS:
      return (
        <Wrapper>
          <SuggestionIcon /> Suggestions
        </Wrapper>
      );
    case MAIN_TAB.ABOUT:
      return (
        <Wrapper>
          <AboutIcon /> About
        </Wrapper>
      );
    default: return tab;
  }
}

class Results extends React.Component {
  static childContextTypes = {
    updateResults: PropTypes.func.isRequired,
    parser: PropTypes.object.isRequired,
  };
  getChildContext() {
    return {
      updateResults: this.forceUpdate.bind(this),
      parser: this.props.parser,
    };
  }
  static contextTypes = {
    config: PropTypes.object.isRequired,
  };
  static propTypes = {
    parser: PropTypes.object.isRequired,
    tab: PropTypes.string,
    onChangeTab: PropTypes.func.isRequired,
  };

  constructor(props) {
    super(props);
    this.state = {
      mainTab: props.parser._modules.checklist.rules.length === 0 ? MAIN_TAB.SUGGESTIONS : MAIN_TAB.CHECKLIST,
    };
  }

  componentDidUpdate() {
    ReactTooltip.rebuild();
  }

  renderStatistics(statistics) {
    return (
      <Masonry className="row statistics">
        {statistics
          .filter(statistic => !!statistic) // filter optionals
          .map((statistic, index) => statistic.statistic ? statistic : { statistic, order: index }) // normalize
          .sort((a, b) => a.order - b.order)
          .map((statistic, i) => React.cloneElement(statistic.statistic, {
            key: `${statistic.order}-${i}`,
          }))}
      </Masonry>
    );
  }

  get warning() {
    const parser = this.props.parser;
    const boss = parser.boss;
    if (boss && boss.fight.resultsWarning) {
      return boss.fight.resultsWarning;
    }
    const config = this.context.config;
    if (config.completeness === SPEC_ANALYSIS_COMPLETENESS.NOT_ACTIVELY_MAINTAINED || config.completeness === SPEC_ANALYSIS_COMPLETENESS.NEEDS_MORE_WORK) {
      return 'The analysis for this spec is still under development. The information shown may be flawed, inaccurate, missing, or incomplete. Contact the spec maintainer for feature requests and bug reports, see the about tab for more information.';
    }
    if (parser.feedbackWarning) {
      return 'This spec is believed to be complete, but needs additional feedback. If there is something missing, incorrect, or inaccurate, please contact this specs maintainer so it can be fixed before being marked as "Good". Contact info can be found in the About Tab.';
    }
    return null;
  }

  render() {
    const { parser, tab, onChangeTab } = this.props;
    const report = parser.report;
    const fight = parser.fight;
    const config = this.context.config;
    const modules = parser._modules;
    const selectedCombatant = modules.combatants.selected;
    if (!selectedCombatant) {
      return (

              <Link to={`/report/${report.code}/${fight.id}`} data-tip="Back to player selection">

              </Link>

            Initializing report...

      );
    }

    const results = parser.generateResults();

    // if (process.env.NODE_ENV === 'development') {
    //   results.tabs.push({
    //     title: 'Development',
    //     url: 'development',
    //     order: 100000,
    //     render: () => (
    //       <DevelopmentTab
    //         parser={parser}
    //         results={results}
    //       />
    //     ),
    //   });
    //   results.tabs.push({
    //     title: 'Events',
    //     url: 'events',
    //     order: 100001,
    //     render: () => (
    //       <EventsTab
    //         parser={parser}
    //       />
    //     ),
    //   });
    //   results.tabs.push({
    //     title: 'Status',
    //     url: 'status',
    //     order: 100002,
    //     render: () => (
    //       <Tab title="Status" style={{ padding: '15px 22px' }}>
    //         <Status />
    //       </Tab>
    //     ),
    //   });
    // }

    const tabUrl = tab || results.tabs[0].url;
    const activeTab = results.tabs.find(tab => tab.url === tabUrl) || results.tabs[0];

    return (

          {config.completeness === SPEC_ANALYSIS_COMPLETENESS.NOT_ACTIVELY_MAINTAINED && (
            <Wrapper>

                This spec is not actively being maintained. In order to continue providing useful and accurate information we are looking for an active maintainer for this spec. See our GitHub page or join Discord for more information.

                <GithubButton /> <DiscordButton />

            </Wrapper>
          )}

              {modules.statsDisplay.render()}
              {modules.talentsDisplay.render()}
              <ItemsPanel items={results.items} selectedCombatant={selectedCombatant} />

                <a
                  href={`https://www.warcraftlogs.com/reports/${report.code}/#fight=${fight.id}&source=${parser.playerId}`}
                  target="_blank"
                  rel="noopener noreferrer"
                  style={{ fontSize: 24 }}
                >
                   View on Warcraft Logs

                      {Object.values(MAIN_TAB).map(tab => (
                        <button
                          key={tab}
                          className={this.state.mainTab === tab ? 'btn-link selected' : 'btn-link'}
                          onClick={() => {
                            this.setState({
                              mainTab: tab,
                            });
                          }}
                        >
                          {mainTabLabel(tab)}
                        </button>
                      ))}

                    <ResultsWarning warning={this.warning} />
                    {this.state.mainTab === MAIN_TAB.CHECKLIST && (
                      modules.checklist.render()
                    )}
                    {this.state.mainTab === MAIN_TAB.SUGGESTIONS && (
                      <SuggestionsTab issues={results.issues} />
                    )}
                    {this.state.mainTab === MAIN_TAB.ABOUT && (
                      <AboutTab config={config} />
                    )}

                    <Textfit mode="single" max={40}>
                    How It's

                    Made
                    </Textfit>

                  Curious how we're doing the analysis? Want to change something? You can find this spec's source <a href={`https://github.com/WoWAnalyzer/WoWAnalyzer/tree/master/${config.path}`}>here and a guide on contributing here.

                    <Textfit mode="single" max={40}>
                      Feedback

                      Welcome
                    </Textfit>

                  Do you have a really cool idea? Is a suggestion or checklist threshold off? Spotted a bug? Let us know on Discord.

              {this.renderStatistics(results.statistics)}

                    <Textfit mode="single" max={32}>
                      Spec

                      Maintainer
                    </Textfit>

                  The {config.spec.specName} {config.spec.className} analyzer is being maintained by
                  {config.maintainers.map(maintainer => <Maintainer key={maintainer.nickname} {...maintainer} />)}. New maintainers are always welcome.

                    <Textfit mode="single" max={32}>
                      State Of

                      The Spec
                    </Textfit>

                  The {config.spec.specName} {config.spec.className} analyzer is currently considered to be in <dfn data-tip={getCompletenessExplanation(config.completeness)} style={{ color: getCompletenessColor(config.completeness) }}>{getCompletenessLabel(config.completeness)}</dfn> state. The about tab at the top might have more information.

                  {results.tabs
                    .sort((a, b) => {
                      const aOrder = a.order !== undefined ? a.order : 100;
                      const bOrder = b.order !== undefined ? b.order : 100;

                      return aOrder - bOrder;
                    })
                    .map(tab => (
                      <button
                        key={tab.title}
                        className={activeTab.url === tab.url ? 'btn-link selected' : 'btn-link'}
                        onClick={() => onChangeTab(tab.url)}
                      >
                        {tab.title}
                      </button>
                    ))}

                {activeTab.render()}

    );
  }
}

const mapStateToProps = state => ({
  tab: getResultTab(state),
});

export default connect(
  mapStateToProps
)(Results);
<commit_msg>Uncomment development only tabs<commit_after>import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { Link } from 'react-router-dom';
import ReactTooltip from 'react-tooltip';
import Masonry from 'react-masonry-component';
import Textfit from 'react-textfit';

import ChecklistIcon from 'Icons/Checklist';
import SuggestionIcon from 'Icons/Suggestion';
import AboutIcon from 'Icons/About';
import Wrapper from 'common/Wrapper';
import SPEC_ANALYSIS_COMPLETENESS, { getCompletenessColor, getCompletenessExplanation, getCompletenessLabel } from 'common/SPEC_ANALYSIS_COMPLETENESS';
import { getResultTab } from 'selectors/url/report';
import DevelopmentTab from 'Main/DevelopmentTab';
import EventsTab from 'Main/EventsTab';
import Tab from 'Main/Tab';
import Status from 'Main/Status';
import GithubButton from 'Main/GithubButton';
import DiscordButton from 'Main/DiscordButton';
import SuggestionsTab from 'Main/SuggestionsTab';
import Maintainer from 'Main/Maintainer';

import ItemsPanel from './ItemsPanel';
import AboutTab from './AboutTab';
import ResultsWarning from './ResultsWarning';
import Header from './Header';

import './Results.css';

const MAIN_TAB = {
  CHECKLIST: 'Checklist',
  SUGGESTIONS: 'Suggestions',
  ABOUT: 'About',
};
function mainTabLabel(tab) {
  switch (tab) {
    case MAIN_TAB.CHECKLIST:
      return (
        <Wrapper>
          <ChecklistIcon /> Checklist
        </Wrapper>
      );
    case MAIN_TAB.SUGGESTIONS:
      return (
        <Wrapper>
          <SuggestionIcon /> Suggestions
        </Wrapper>
      );
    case MAIN_TAB.ABOUT:
      return (
        <Wrapper>
          <AboutIcon /> About
        </Wrapper>
      );
    default: return tab;
  }
}

class Results extends React.Component {
  static childContextTypes = {
    updateResults: PropTypes.func.isRequired,
    parser: PropTypes.object.isRequired,
  };
  getChildContext() {
    return {
      updateResults: this.forceUpdate.bind(this),
      parser: this.props.parser,
    };
  }
  static contextTypes = {
    config: PropTypes.object.isRequired,
  };
  static propTypes = {
    parser: PropTypes.object.isRequired,
    tab: PropTypes.string,
    onChangeTab: PropTypes.func.isRequired,
  };

  constructor(props) {
    super(props);
    this.state = {
      mainTab: props.parser._modules.checklist.rules.length === 0 ? MAIN_TAB.SUGGESTIONS : MAIN_TAB.CHECKLIST,
    };
  }

  componentDidUpdate() {
    ReactTooltip.rebuild();
  }

  renderStatistics(statistics) {
    return (
      <Masonry className="row statistics">
        {statistics
          .filter(statistic => !!statistic) // filter optionals
          .map((statistic, index) => statistic.statistic ? statistic : { statistic, order: index }) // normalize
          .sort((a, b) => a.order - b.order)
          .map((statistic, i) => React.cloneElement(statistic.statistic, {
            key: `${statistic.order}-${i}`,
          }))}
      </Masonry>
    );
  }

  get warning() {
    const parser = this.props.parser;
    const boss = parser.boss;
    if (boss && boss.fight.resultsWarning) {
      return boss.fight.resultsWarning;
    }
    const config = this.context.config;
    if (config.completeness === SPEC_ANALYSIS_COMPLETENESS.NOT_ACTIVELY_MAINTAINED || config.completeness === SPEC_ANALYSIS_COMPLETENESS.NEEDS_MORE_WORK) {
      return 'The analysis for this spec is still under development. The information shown may be flawed, inaccurate, missing, or incomplete. Contact the spec maintainer for feature requests and bug reports, see the about tab for more information.';
    }
    if (parser.feedbackWarning) {
      return 'This spec is believed to be complete, but needs additional feedback. If there is something missing, incorrect, or inaccurate, please contact this specs maintainer so it can be fixed before being marked as "Good". Contact info can be found in the About Tab.';
    }
    return null;
  }

  render() {
    const { parser, tab, onChangeTab } = this.props;
    const report = parser.report;
    const fight = parser.fight;
    const config = this.context.config;
    const modules = parser._modules;
    const selectedCombatant = modules.combatants.selected;
    if (!selectedCombatant) {
      return (

              <Link to={`/report/${report.code}/${fight.id}`} data-tip="Back to player selection">

              </Link>

            Initializing report...

      );
    }

    const results = parser.generateResults();

    if (process.env.NODE_ENV === 'development') {
      results.tabs.push({
        title: 'Development',
        url: 'development',
        order: 100000,
        render: () => (
          <DevelopmentTab
            parser={parser}
            results={results}
          />
        ),
      });
      results.tabs.push({
        title: 'Events',
        url: 'events',
        order: 100001,
        render: () => (
          <EventsTab
            parser={parser}
          />
        ),
      });
      results.tabs.push({
        title: 'Status',
        url: 'status',
        order: 100002,
        render: () => (
          <Tab title="Status" style={{ padding: '15px 22px' }}>
            <Status />
          </Tab>
        ),
      });
    }

    const tabUrl = tab || results.tabs[0].url;
    const activeTab = results.tabs.find(tab => tab.url === tabUrl) || results.tabs[0];

    return (

          {config.completeness === SPEC_ANALYSIS_COMPLETENESS.NOT_ACTIVELY_MAINTAINED && (
            <Wrapper>

                This spec is not actively being maintained. In order to continue providing useful and accurate information we are looking for an active maintainer for this spec. See our GitHub page or join Discord for more information.

                <GithubButton /> <DiscordButton />

            </Wrapper>
          )}

              {modules.statsDisplay.render()}
              {modules.talentsDisplay.render()}
              <ItemsPanel items={results.items} selectedCombatant={selectedCombatant} />

                <a
                  href={`https://www.warcraftlogs.com/reports/${report.code}/#fight=${fight.id}&source=${parser.playerId}`}
                  target="_blank"
                  rel="noopener noreferrer"
                  style={{ fontSize: 24 }}
                >
                   View on Warcraft Logs

                      {Object.values(MAIN_TAB).map(tab => (
                        <button
                          key={tab}
                          className={this.state.mainTab === tab ? 'btn-link selected' : 'btn-link'}
                          onClick={() => {
                            this.setState({
                              mainTab: tab,
                            });
                          }}
                        >
                          {mainTabLabel(tab)}
                        </button>
                      ))}

                    <ResultsWarning warning={this.warning} />
                    {this.state.mainTab === MAIN_TAB.CHECKLIST && (
                      modules.checklist.render()
                    )}
                    {this.state.mainTab === MAIN_TAB.SUGGESTIONS && (
                      <SuggestionsTab issues={results.issues} />
                    )}
                    {this.state.mainTab === MAIN_TAB.ABOUT && (
                      <AboutTab config={config} />
                    )}

                    <Textfit mode="single" max={40}>
                    How It's

                    Made
                    </Textfit>

                  Curious how we're doing the analysis? Want to change something? You can find this spec's source <a href={`https://github.com/WoWAnalyzer/WoWAnalyzer/tree/master/${config.path}`}>here and a guide on contributing here.

                    <Textfit mode="single" max={40}>
                      Feedback

                      Welcome
                    </Textfit>

                  Do you have a really cool idea? Is a suggestion or checklist threshold off? Spotted a bug? Let us know on Discord.

              {this.renderStatistics(results.statistics)}

                    <Textfit mode="single" max={32}>
                      Spec

                      Maintainer
                    </Textfit>

                  The {config.spec.specName} {config.spec.className} analyzer is being maintained by
                  {config.maintainers.map(maintainer => <Maintainer key={maintainer.nickname} {...maintainer} />)}. New maintainers are always welcome.

                    <Textfit mode="single" max={32}>
                      State Of

                      The Spec
                    </Textfit>

                  The {config.spec.specName} {config.spec.className} analyzer is currently considered to be in <dfn data-tip={getCompletenessExplanation(config.completeness)} style={{ color: getCompletenessColor(config.completeness) }}>{getCompletenessLabel(config.completeness)}</dfn> state. The about tab at the top might have more information.

                  {results.tabs
                    .sort((a, b) => {
                      const aOrder = a.order !== undefined ? a.order : 100;
                      const bOrder = b.order !== undefined ? b.order : 100;

                      return aOrder - bOrder;
                    })
                    .map(tab => (
                      <button
                        key={tab.title}
                        className={activeTab.url === tab.url ? 'btn-link selected' : 'btn-link'}
                        onClick={() => onChangeTab(tab.url)}
                      >
                        {tab.title}
                      </button>
                    ))}

                {activeTab.render()}

    );
  }
}

const mapStateToProps = state => ({
  tab: getResultTab(state),
});

export default connect(
  mapStateToProps
)(Results);
<|endoftext|>


<commit_before>var
// library for execute asynchronous methods
  async = require('async'),
  _ = require('lodash'),
  stringify = require('json-stable-stringify'),
  Router = require('router'),
  broker = require('../../services/broker'),
  // For parse a request sent by user
  bodyParser = require('body-parser'),
  // For final step to respond to HTTP request
  finalhandler = require('finalhandler'),
  // Used for hash into md5 the data for generate a requestId
  crypto = require('crypto');

module.exports = function RouterController (kuzzle) {

  this.router = null;
  this.controllers = ['write', 'read', 'subscribe'];

  this.initRouterHttp = function () {
    var routerCtrl = this;

    this.router = new Router();

    // create and mount a new router for our API
    var api = new Router();
    this.router.use('/api/', api);

    // add a body parsing middleware to our API
    api.use(bodyParser.json());

    // define the function that will be call in case of error
    var sendError = function (error, response) {
      response.writeHead(400, {'Content-Type': 'application/json'});
      response.end(stringify({error: error, result: null}));
      return false;
    };

    // define routes
    api.post('/:collection', function (request, response) {
      if (request.body) {
        var data = wrapObject(request.body, 'write', request.params.collection, 'create'),
            connection = {type: 'rest', id: request};

        kuzzle.funnel.execute(data, connection)
          .then(function onExecuteSuccess (result) {
            // Send response and close connection
            if (result.rooms) {
              async.each(result.rooms, function (roomName) {
                routerCtrl.notify(roomName, result.data);
              });
            }
            response.writeHead(200, {'Content-Type': 'application/json'});
            response.end(stringify({error: null, result: result.data}));
          })
          .catch(function onExecuteError (error) {
            return sendError(error, response);
          });
      }
      else {
        return sendError('Empty data', response);
      }
    });

  };

  this.routeHttp = function (request, response) {
    kuzzle.log.silly('Handle HTTP request');
    this.router(request, response, finalhandler(request, response));
  };

  /**
   * Create asynchronously listeners on all rooms defined by this.controllers
   *
   * @param {Object} socket
   */
  this.routeWebsocket = function (socket) {
    var routerCtrl = this,
        connection = {type: 'websocket', id: socket.id};

    async.each(routerCtrl.controllers, function recordSocketListener (controller) {

      socket.on(controller, function (data) {
        kuzzle.log.silly('Handle Websocket', controller, 'request');
        data = wrapObject(data, controller);

        // execute the funnel. If error occurred, notify users
        kuzzle.funnel.execute(data, connection)
          .then(function onExecuteSuccess (result) {
            if (result.rooms) {
              async.each(result.rooms, function (roomName) {
                routerCtrl.notify(roomName, result.data);
              });
            }
            routerCtrl.notify(data.requestId, result.data, connection);
          })
          .catch(function onExecuteError(error) {
            routerCtrl.notify(data.requestId, {error: error}, socket);
            kuzzle.log.verbose({error: error});
          });
      });

    });

    // add a specific disconnect event for websocket
    socket.on('disconnect', function () {
      kuzzle.hotelClerk.removeCustomerFromAllRooms(socket.id);
    });
  };

  this.routeMQListener = function () {
    var routerCtrl = this;
    async.each(this.controllers, function recordMQListener (controller) {
      broker.listenExchange(controller+'.*.*', function handleMQMessage(msg) {
        var connectionId = msg.properties.replyTo,
            connection = null,
            data = JSON.parse(msg.content.toString()),
            routingArray = msg.fields.routingKey.split('.'),
            controller = routingArray[0],
            collection = routingArray[1],
            action = routingArray[2];

        kuzzle.log.silly('Handle MQ input', msg.fields.routingKey , 'message');

        // For MQTT messages, we do not have a replyTo header like with AMQP or STOMP
        // => MQTT client has to send its mqtt client id and subscribe to the topic exchange mqtt.<clientId>
        //    to get feedback from Kuzzle.
        if (data.mqttClientId) {
          connectionId = "mqtt."+data.mqttClientId;
        }

        if (connectionId) {
          connection = {type: connectionId.split('.')[0], id: connectionId};
        }

        if (connectionId && ! data.requestId) {
          data.requestId = connectionId;
        }

        data = wrapObject(data, controller, collection, action);

        kuzzle.funnel.execute(data, connection)
          .then(function onExecuteSuccess (result) {
            if (result.rooms) {
              async.each(result.rooms, function (roomName) {
                routerCtrl.notify(roomName, result.data);
              });
            }
            routerCtrl.notify(data.requestId, result.data, connection);
          })
          .catch(function onExecuteError(error) {
            routerCtrl.notify(data.requestId, {error: error});
            kuzzle.log.verbose({error: error});
          });
      });
    });
  };

  /**
   * Notify by message data on the request Id channel
   * If socket is defined, we send the event only on this socket,
   * otherwise, we send to all sockets on the room
   *
   * @param {String} room
   * @param {Object} data
   * @param {Object} connections
   */
  this.notify = function (room, data, connection) {
    if (connection) {
      switch (connection.type) {
        case 'websocket':
          kuzzle.io.to(connection.id).emit(room, data);
          break;
        case 'amq':
          broker.replyTo(connection.id, data);
          break;
        case "mqtt":
          broker.addExchange(connection.id, data);
          break;
      }
    }
    else {
      kuzzle.io.emit(room, data);
      broker.addExchange(room, data);
    }
  };
};

function wrapObject (data, controller, collection, action) {
  if (data.content === 'undefined') {
    data = {content: data};
  }

  data.controller = controller;

  if (collection) {
    data.collection = collection;
  }

  if (action) {
    data.action = action;
  }

  // The request Id is optional, but we have to generate it if the user
  // not provide it. We need to return this id for let the user know
  // how to get real time information about his data
  if (!data.requestId) {
    var stringifyObject = stringify(data);
    data.requestId = crypto.createHash('md5').update(stringifyObject).digest('hex');
  }

  return data;
}
<commit_msg>notify error feedback only to the client that send the message.<commit_after>var
// library for execute asynchronous methods
  async = require('async'),
  _ = require('lodash'),
  stringify = require('json-stable-stringify'),
  Router = require('router'),
  broker = require('../../services/broker'),
  // For parse a request sent by user
  bodyParser = require('body-parser'),
  // For final step to respond to HTTP request
  finalhandler = require('finalhandler'),
  // Used for hash into md5 the data for generate a requestId
  crypto = require('crypto');

module.exports = function RouterController (kuzzle) {

  this.router = null;
  this.controllers = ['write', 'read', 'subscribe'];

  this.initRouterHttp = function () {
    var routerCtrl = this;

    this.router = new Router();

    // create and mount a new router for our API
    var api = new Router();
    this.router.use('/api/', api);

    // add a body parsing middleware to our API
    api.use(bodyParser.json());

    // define the function that will be call in case of error
    var sendError = function (error, response) {
      response.writeHead(400, {'Content-Type': 'application/json'});
      response.end(stringify({error: error, result: null}));
      return false;
    };

    // define routes
    api.post('/:collection', function (request, response) {
      if (request.body) {
        var data = wrapObject(request.body, 'write', request.params.collection, 'create'),
            connection = {type: 'rest', id: request};

        kuzzle.funnel.execute(data, connection)
          .then(function onExecuteSuccess (result) {
            // Send response and close connection
            if (result.rooms) {
              async.each(result.rooms, function (roomName) {
                routerCtrl.notify(roomName, result.data);
              });
            }
            response.writeHead(200, {'Content-Type': 'application/json'});
            response.end(stringify({error: null, result: result.data}));
          })
          .catch(function onExecuteError (error) {
            return sendError(error, response);
          });
      }
      else {
        return sendError('Empty data', response);
      }
    });

  };

  this.routeHttp = function (request, response) {
    kuzzle.log.silly('Handle HTTP request');
    this.router(request, response, finalhandler(request, response));
  };

  /**
   * Create asynchronously listeners on all rooms defined by this.controllers
   *
   * @param {Object} socket
   */
  this.routeWebsocket = function (socket) {
    var routerCtrl = this,
        connection = {type: 'websocket', id: socket.id};

    async.each(routerCtrl.controllers, function recordSocketListener (controller) {

      socket.on(controller, function (data) {
        kuzzle.log.silly('Handle Websocket', controller, 'request');
        data = wrapObject(data, controller);

        // execute the funnel. If error occurred, notify users
        kuzzle.funnel.execute(data, connection)
          .then(function onExecuteSuccess (result) {
            if (result.rooms) {
              async.each(result.rooms, function (roomName) {
                routerCtrl.notify(roomName, result.data);
              });
            }
            routerCtrl.notify(data.requestId, result.data, connection);
          })
          .catch(function onExecuteError(error) {
            routerCtrl.notify(data.requestId, {error: error}, connection);
            kuzzle.log.verbose({error: error});
          });
      });

    });

    // add a specific disconnect event for websocket
    socket.on('disconnect', function () {
      kuzzle.hotelClerk.removeCustomerFromAllRooms(socket.id);
    });
  };

  this.routeMQListener = function () {
    var routerCtrl = this;
    async.each(this.controllers, function recordMQListener (controller) {
      broker.listenExchange(controller+'.*.*', function handleMQMessage(msg) {
        var connectionId = msg.properties.replyTo,
            connection = null,
            data = JSON.parse(msg.content.toString()),
            routingArray = msg.fields.routingKey.split('.'),
            controller = routingArray[0],
            collection = routingArray[1],
            action = routingArray[2];

        kuzzle.log.silly('Handle MQ input', msg.fields.routingKey , 'message');

        // For MQTT messages, we do not have a replyTo header like with AMQP or STOMP
        // => MQTT client has to send its mqtt client id and subscribe to the topic exchange mqtt.<clientId>
        //    to get feedback from Kuzzle.
        if (data.mqttClientId) {
          connectionId = "mqtt."+data.mqttClientId;
        }

        if (connectionId) {
          connection = {type: connectionId.split('.')[0], id: connectionId};
        }

        if (connectionId && ! data.requestId) {
          data.requestId = connectionId;
        }

        data = wrapObject(data, controller, collection, action);

        kuzzle.funnel.execute(data, connection)
          .then(function onExecuteSuccess (result) {
            if (result.rooms) {
              async.each(result.rooms, function (roomName) {
                routerCtrl.notify(roomName, result.data);
              });
            }
            routerCtrl.notify(data.requestId, result.data, connection);
          })
          .catch(function onExecuteError(error) {
            routerCtrl.notify(data.requestId, {error: error}, connection);
            kuzzle.log.verbose({error: error});
          });
      });
    });
  };

  /**
   * Notify by message data on the request Id channel
   * If socket is defined, we send the event only on this socket,
   * otherwise, we send to all sockets on the room
   *
   * @param {String} room
   * @param {Object} data
   * @param {Object} connections
   */
  this.notify = function (room, data, connection) {
    if (connection) {
      switch (connection.type) {
        case 'websocket':
          kuzzle.io.to(connection.id).emit(room, data);
          break;
        case 'amq':
          broker.replyTo(connection.id, data);
          break;
        case "mqtt":
          broker.addExchange(connection.id, data);
          break;
      }
    }
    else {
      kuzzle.io.emit(room, data);
      broker.addExchange(room, data);
    }
  };
};

function wrapObject (data, controller, collection, action) {
  if (data.content === 'undefined') {
    data = {content: data};
  }

  data.controller = controller;

  if (collection) {
    data.collection = collection;
  }

  if (action) {
    data.action = action;
  }

  // The request Id is optional, but we have to generate it if the user
  // not provide it. We need to return this id for let the user know
  // how to get real time information about his data
  if (!data.requestId) {
    var stringifyObject = stringify(data);
    data.requestId = crypto.createHash('md5').update(stringifyObject).digest('hex');
  }

  return data;
}
<|endoftext|>


<commit_before>import React from "react"
import { render } from "react-dom"
import { AppContainer } from "react-hot-loader"
import Router from "react-router/lib/Router"
import browserHistory from "react-router/lib/browserHistory"
import match from "react-router/lib/match"
import routes from "../routes"

// Get the DOM Element that will host our React application.
const container = document.querySelector("#app")

function routerError(error)
{
  // TODO: Error handling.
  console.error("React Router match failed.") // eslint-disable-line no-console
  if (error) { console.error(error) } // eslint-disable-line no-console
}

function renderApp()
{
  // As we are using dynamic react-router routes we have to use the following
  // asynchronous routing mechanism supported by the `match` function.
  // @see https://github.com/reactjs/react-router/blob/master/docs/guides/ServerRendering.md
  match({ history: browserHistory, routes }, (error, redirectLocation, renderProps) =>
  {
    if (error) {
      routerError(error)
    } else if (redirectLocation) {
      return
    } else if (renderProps) {
      render(
        <AppContainer>
          {/*
          We need to explicly render the Router component here instead of have
          this embedded within a shared App type of component as we use different
          router base components for client vs server rendering.
          */}
          <Router {...renderProps} />
        </AppContainer>,
        container
      )
    } else {
      routerError()
    }
  })
}

// The following is needed so that we can hot reload our App.
if (process.env.NODE_ENV === "development" && module.hot) {
  // Accept changes to this file for hot reloading.
  module.hot.accept("./index.js")

  // Any changes to our routes will cause a hotload re-render.
  module.hot.accept("../routes", renderApp)
}

renderApp()
<commit_msg>Formatting<commit_after>import React from "react"
import { render } from "react-dom"
import browserHistory from "react-router/lib/browserHistory"
import match from "react-router/lib/match"
import routes from "../routes"

// Get the DOM Element that will host our React application.
const container = document.querySelector("#app")

function routerError(error)
{
  // TODO: Error handling.
  console.error("React Router match failed.") // eslint-disable-line no-console
  if (error) { console.error(error) } // eslint-disable-line no-console
}

function renderApp()
{
  // As we are using dynamic react-router routes we have to use the following
  // asynchronous routing mechanism supported by the `match` function.
  // @see https://github.com/reactjs/react-router/blob/master/docs/guides/ServerRendering.md
  match({ history: browserHistory, routes }, (error, redirectLocation, renderProps) =>
  {
    if (error)
    {
      routerError(error)
    }
    else if (redirectLocation)
    {
      return
    }
    else if (renderProps)
    {
      render(
        <AppContainer>
          {/*
          We need to explicly render the Router component here instead of have
          this embedded within a shared App type of component as we use different
          router base components for client vs server rendering.
          */}
          <Router {...renderProps} />
        </AppContainer>,
        container
      )
    }
    else
    {
      routerError()
    }
  })
}

// The following is needed so that we can hot reload our App.
if (process.env.NODE_ENV === "development" && module.hot)
{
  // Accept changes to this file for hot reloading.
  module.hot.accept("./index.js")

  // Any changes to our routes will cause a hotload re-render.
  module.hot.accept("../routes", renderApp)
}

renderApp()
<|endoftext|>