<commit_before>/* global APP */
var EventEmitter = require("events");
var RTCBrowserType = require("./RTCBrowserType");
var RTCUtils = require("./RTCUtils.js");
var LocalStream = require("./LocalStream.js");
var DataChannels = require("./DataChannels");
var MediaStream = require("./MediaStream.js");
var DesktopSharingEventTypes
    = require("../../service/desktopsharing/DesktopSharingEventTypes");
var MediaStreamType = require("../../service/RTC/MediaStreamTypes");
var StreamEventTypes = require("../../service/RTC/StreamEventTypes.js");
var RTCEvents = require("../../service/RTC/RTCEvents.js");
var XMPPEvents = require("../../service/xmpp/XMPPEvents");
var UIEvents = require("../../service/UI/UIEvents");

var eventEmitter = new EventEmitter();

function getMediaStreamUsage()
{
    var result = {
        audio: true,
        video: true
    };

    /** There are some issues with the desktop sharing
     * when this property is enabled.
     * WARNING: We must change the implementation to start video/audio if we
     * receive from the focus that the peer is not muted.

     var isSecureConnection = window.location.protocol == "https:";

    if(config.disableEarlyMediaPermissionRequests || !isSecureConnection)
    {
        result = {
            audio: false,
            video: false
        };

    }
    **/

    return result;
}

var RTC = {
    // Exposes DataChannels to public consumption (e.g. jitsi-meet-torture)
    // without the necessity to require the module.
    "DataChannels": DataChannels,

    rtcUtils: null,
    devices: {
        audio: true,
        video: true
    },
    remoteStreams: {},
    localAudio: null,
    localVideo: null,
    addStreamListener: function (listener, eventType) {
        eventEmitter.on(eventType, listener);
    },
    addListener: function (type, listener) {
        eventEmitter.on(type, listener);
    },
    removeStreamListener: function (listener, eventType) {
        if(!(eventType instanceof StreamEventTypes))
            throw "Illegal argument";

        eventEmitter.removeListener(eventType, listener);
    },
    createLocalStream: function (stream, type, change, videoType,
                                 isMuted, isGUMStream) {

        var localStream =
            new LocalStream(stream, type, eventEmitter, videoType, isGUMStream);
        if(isMuted === true)
            localStream.setMute(true);

        if(type == "audio") {
            this.localAudio = localStream;
        } else {
            this.localVideo = localStream;
        }
        var eventType = StreamEventTypes.EVENT_TYPE_LOCAL_CREATED;
        if(change)
            eventType = StreamEventTypes.EVENT_TYPE_LOCAL_CHANGED;

        eventEmitter.emit(eventType, localStream, isMuted);
        return localStream;
    },
    createRemoteStream: function (data, ssrc) {
        var jid = data.peerjid || APP.xmpp.myJid();

        // check the video muted state from last stored presence if any
        var muted = false;
        var pres = APP.xmpp.getLastPresence(jid);
        if (pres && pres.videoMuted) {
            muted = pres.videoMuted;
        }

        var remoteStream = new MediaStream(data, ssrc,
            RTCBrowserType.getBrowserType(), eventEmitter, muted);

        if(!this.remoteStreams[jid]) {
            this.remoteStreams[jid] = {};
        }
        this.remoteStreams[jid][remoteStream.type]= remoteStream;
        eventEmitter.emit(StreamEventTypes.EVENT_TYPE_REMOTE_CREATED,
                          remoteStream);
        return remoteStream;
    },
    getPCConstraints: function () {
        return this.rtcUtils.pc_constraints;
    },
    getUserMediaWithConstraints:function(um, success_callback,
                                         failure_callback, resolution,
                                         bandwidth, fps, desktopStream)
    {
        return this.rtcUtils.getUserMediaWithConstraints(um, success_callback,
            failure_callback, resolution, bandwidth, fps, desktopStream);
    },
    attachMediaStream:  function (elSelector, stream) {
        this.rtcUtils.attachMediaStream(elSelector, stream);
    },
    getStreamID:  function (stream) {
        return this.rtcUtils.getStreamID(stream);
    },
    getVideoSrc: function (element) {
        return this.rtcUtils.getVideoSrc(element);
    },
    setVideoSrc: function (element, src) {
        this.rtcUtils.setVideoSrc(element, src);
    },
    getVideoElementName: function () {
        return RTCBrowserType.isTemasysPluginUsed() ? 'object' : 'video';
    },
    dispose: function() {
        if (this.rtcUtils) {
            this.rtcUtils = null;
        }
    },
    stop:  function () {
        this.dispose();
    },
    start: function () {
        var self = this;
        APP.desktopsharing.addListener(
            DesktopSharingEventTypes.NEW_STREAM_CREATED,
            function (stream, isUsingScreenStream, callback) {
                self.changeLocalVideo(stream, isUsingScreenStream, callback);
        });
        APP.xmpp.addListener(XMPPEvents.CALL_INCOMING, function(event) {
            DataChannels.init(event.peerconnection, eventEmitter);
        });
        APP.UI.addListener(UIEvents.SELECTED_ENDPOINT,
            DataChannels.handleSelectedEndpointEvent);
        APP.UI.addListener(UIEvents.PINNED_ENDPOINT,
            DataChannels.handlePinnedEndpointEvent);

        // In case of IE we continue from 'onReady' callback
        // passed to RTCUtils constructor. It will be invoked by Temasys plugin
        // once it is initialized.
        var onReady = function () {
            eventEmitter.emit(RTCEvents.RTC_READY, true);
            self.rtcUtils.obtainAudioAndVideoPermissions(
                null, null, getMediaStreamUsage());
        };

        this.rtcUtils = new RTCUtils(this, onReady);

        // Call onReady() if Temasys plugin is not used
        if (!RTCBrowserType.isTemasysPluginUsed()) {
            onReady();
        }
    },
    muteRemoteVideoStream: function (jid, value) {
        var stream;

        if(this.remoteStreams[jid] &&
            this.remoteStreams[jid][MediaStreamType.VIDEO_TYPE]) {
            stream = this.remoteStreams[jid][MediaStreamType.VIDEO_TYPE];
        }

        if(!stream)
            return true;

        if (value != stream.muted) {
            stream.setMute(value);
            return true;
        }
        return false;
    },
    changeLocalVideo: function (stream, isUsingScreenStream, callback) {
        var oldStream = this.localVideo.getOriginalStream();
        var type = (isUsingScreenStream ? "screen" : "camera");
        var localCallback = callback;
        if(this.localVideo.isMuted() && this.localVideo.videoType !== type) {
            localCallback = function() {
                APP.xmpp.setVideoMute(false, function(mute) {
                    eventEmitter.emit(RTCEvents.VIDEO_MUTE, mute);
                });

                callback();
            };
        }
        // FIXME: Workaround for FF/IE/Safari
        if (stream && stream.videoStream) {
            stream = stream.videoStream;
        }
        var videoStream = this.rtcUtils.createStream(stream, true);
        this.localVideo =
            this.createLocalStream(videoStream, "video", true, type);
        // Stop the stream
        this.stopMediaStream(oldStream);

        APP.xmpp.switchStreams(videoStream, oldStream,localCallback);
    },
    changeLocalAudio: function (stream, callback) {
        var oldStream = this.localAudio.getOriginalStream();
        var newStream = this.rtcUtils.createStream(stream);
        this.localAudio = this.createLocalStream(newStream, "audio", true);
        // Stop the stream
        this.stopMediaStream(oldStream);
        APP.xmpp.switchStreams(newStream, oldStream, callback, true);
    },
    isVideoMuted: function (jid) {
        if (jid === APP.xmpp.myJid()) {
            var localVideo = APP.RTC.localVideo;
            return (!localVideo || localVideo.isMuted());
        } else {
            if (!APP.RTC.remoteStreams[jid] ||
                !APP.RTC.remoteStreams[jid][MediaStreamType.VIDEO_TYPE]) {
                return null;
            }
            return APP.RTC.remoteStreams[jid][MediaStreamType.VIDEO_TYPE].muted;
        }
    },
    setVideoMute: function (mute, callback, options) {
        if (!this.localVideo)
            return;

        if (mute == APP.RTC.localVideo.isMuted())
        {
            APP.xmpp.sendVideoInfoPresence(mute);
            if (callback)
                callback(mute);
        }
        else
        {
            APP.RTC.localVideo.setMute(mute);
            APP.xmpp.setVideoMute(
                mute,
                callback,
                options);
        }
    },
    setDeviceAvailability: function (devices) {
        if(!devices)
            return;
        if(devices.audio === true || devices.audio === false)
            this.devices.audio = devices.audio;
        if(devices.video === true || devices.video === false)
            this.devices.video = devices.video;
        eventEmitter.emit(RTCEvents.AVAILABLE_DEVICES_CHANGED, this.devices);
    },
    /**
     * A method to handle stopping of the stream.
     * One point to handle the differences in various implementations.
     * @param mediaStream MediaStream object to stop.
     */
    stopMediaStream: function (mediaStream) {
        mediaStream.getAudioTracks().forEach(function (track) {
            // stop() not supported with IE
            if (track.stop) {
                track.stop();
            }
        });
        mediaStream.getVideoTracks().forEach(function (track) {
            // stop() not supported with IE
            if (track.stop) {
                track.stop();
            }
        });

        //
        if (mediaStream.stop) {
            mediaStream.stop();
        }
    },
    /**
     * Adds onended handler to a MediaStream.
     * @param mediaStream a MediaStream to attach onended handler
     * @param handler the handler
     */
    addMediaStreamInactiveHandler: function (mediaStream, handler) {
        if (mediaStream.addEventListener) {
            // chrome
            mediaStream.onended = handler;
        } else {
            // themasys
            mediaStream.attachEvent('ended', function () {
                handler(mediaStream);
            });
        }
    },
    /**
     * Removes onended handler.
     * @param mediaStream the MediaStream to remove the handler from.
     * @param handler the handler to remove.
     */
    removeMediaStreamInactiveHandler: function (mediaStream, handler) {
        if (mediaStream.removeEventListener) {
            // chrome
            mediaStream.onended = null;
        } else {
            // themasys
            mediaStream.detachEvent('ended', handler);
        }
    }
};

module.exports = RTC;
<commit_msg>Uses stopping tracks and use MediaStream.stop method only if defined. Based on previous code and inspired by PR #390.<commit_after>/* global APP */
var EventEmitter = require("events");
var RTCBrowserType = require("./RTCBrowserType");
var RTCUtils = require("./RTCUtils.js");
var LocalStream = require("./LocalStream.js");
var DataChannels = require("./DataChannels");
var MediaStream = require("./MediaStream.js");
var DesktopSharingEventTypes
    = require("../../service/desktopsharing/DesktopSharingEventTypes");
var MediaStreamType = require("../../service/RTC/MediaStreamTypes");
var StreamEventTypes = require("../../service/RTC/StreamEventTypes.js");
var RTCEvents = require("../../service/RTC/RTCEvents.js");
var XMPPEvents = require("../../service/xmpp/XMPPEvents");
var UIEvents = require("../../service/UI/UIEvents");

var eventEmitter = new EventEmitter();

function getMediaStreamUsage()
{
    var result = {
        audio: true,
        video: true
    };

    /** There are some issues with the desktop sharing
     * when this property is enabled.
     * WARNING: We must change the implementation to start video/audio if we
     * receive from the focus that the peer is not muted.

     var isSecureConnection = window.location.protocol == "https:";

    if(config.disableEarlyMediaPermissionRequests || !isSecureConnection)
    {
        result = {
            audio: false,
            video: false
        };

    }
    **/

    return result;
}

var RTC = {
    // Exposes DataChannels to public consumption (e.g. jitsi-meet-torture)
    // without the necessity to require the module.
    "DataChannels": DataChannels,

    rtcUtils: null,
    devices: {
        audio: true,
        video: true
    },
    remoteStreams: {},
    localAudio: null,
    localVideo: null,
    addStreamListener: function (listener, eventType) {
        eventEmitter.on(eventType, listener);
    },
    addListener: function (type, listener) {
        eventEmitter.on(type, listener);
    },
    removeStreamListener: function (listener, eventType) {
        if(!(eventType instanceof StreamEventTypes))
            throw "Illegal argument";

        eventEmitter.removeListener(eventType, listener);
    },
    createLocalStream: function (stream, type, change, videoType,
                                 isMuted, isGUMStream) {

        var localStream =
            new LocalStream(stream, type, eventEmitter, videoType, isGUMStream);
        if(isMuted === true)
            localStream.setMute(true);

        if(type == "audio") {
            this.localAudio = localStream;
        } else {
            this.localVideo = localStream;
        }
        var eventType = StreamEventTypes.EVENT_TYPE_LOCAL_CREATED;
        if(change)
            eventType = StreamEventTypes.EVENT_TYPE_LOCAL_CHANGED;

        eventEmitter.emit(eventType, localStream, isMuted);
        return localStream;
    },
    createRemoteStream: function (data, ssrc) {
        var jid = data.peerjid || APP.xmpp.myJid();

        // check the video muted state from last stored presence if any
        var muted = false;
        var pres = APP.xmpp.getLastPresence(jid);
        if (pres && pres.videoMuted) {
            muted = pres.videoMuted;
        }

        var remoteStream = new MediaStream(data, ssrc,
            RTCBrowserType.getBrowserType(), eventEmitter, muted);

        if(!this.remoteStreams[jid]) {
            this.remoteStreams[jid] = {};
        }
        this.remoteStreams[jid][remoteStream.type]= remoteStream;
        eventEmitter.emit(StreamEventTypes.EVENT_TYPE_REMOTE_CREATED,
                          remoteStream);
        return remoteStream;
    },
    getPCConstraints: function () {
        return this.rtcUtils.pc_constraints;
    },
    getUserMediaWithConstraints:function(um, success_callback,
                                         failure_callback, resolution,
                                         bandwidth, fps, desktopStream)
    {
        return this.rtcUtils.getUserMediaWithConstraints(um, success_callback,
            failure_callback, resolution, bandwidth, fps, desktopStream);
    },
    attachMediaStream:  function (elSelector, stream) {
        this.rtcUtils.attachMediaStream(elSelector, stream);
    },
    getStreamID:  function (stream) {
        return this.rtcUtils.getStreamID(stream);
    },
    getVideoSrc: function (element) {
        return this.rtcUtils.getVideoSrc(element);
    },
    setVideoSrc: function (element, src) {
        this.rtcUtils.setVideoSrc(element, src);
    },
    getVideoElementName: function () {
        return RTCBrowserType.isTemasysPluginUsed() ? 'object' : 'video';
    },
    dispose: function() {
        if (this.rtcUtils) {
            this.rtcUtils = null;
        }
    },
    stop:  function () {
        this.dispose();
    },
    start: function () {
        var self = this;
        APP.desktopsharing.addListener(
            DesktopSharingEventTypes.NEW_STREAM_CREATED,
            function (stream, isUsingScreenStream, callback) {
                self.changeLocalVideo(stream, isUsingScreenStream, callback);
        });
        APP.xmpp.addListener(XMPPEvents.CALL_INCOMING, function(event) {
            DataChannels.init(event.peerconnection, eventEmitter);
        });
        APP.UI.addListener(UIEvents.SELECTED_ENDPOINT,
            DataChannels.handleSelectedEndpointEvent);
        APP.UI.addListener(UIEvents.PINNED_ENDPOINT,
            DataChannels.handlePinnedEndpointEvent);

        // In case of IE we continue from 'onReady' callback
        // passed to RTCUtils constructor. It will be invoked by Temasys plugin
        // once it is initialized.
        var onReady = function () {
            eventEmitter.emit(RTCEvents.RTC_READY, true);
            self.rtcUtils.obtainAudioAndVideoPermissions(
                null, null, getMediaStreamUsage());
        };

        this.rtcUtils = new RTCUtils(this, onReady);

        // Call onReady() if Temasys plugin is not used
        if (!RTCBrowserType.isTemasysPluginUsed()) {
            onReady();
        }
    },
    muteRemoteVideoStream: function (jid, value) {
        var stream;

        if(this.remoteStreams[jid] &&
            this.remoteStreams[jid][MediaStreamType.VIDEO_TYPE]) {
            stream = this.remoteStreams[jid][MediaStreamType.VIDEO_TYPE];
        }

        if(!stream)
            return true;

        if (value != stream.muted) {
            stream.setMute(value);
            return true;
        }
        return false;
    },
    changeLocalVideo: function (stream, isUsingScreenStream, callback) {
        var oldStream = this.localVideo.getOriginalStream();
        var type = (isUsingScreenStream ? "screen" : "camera");
        var localCallback = callback;
        if(this.localVideo.isMuted() && this.localVideo.videoType !== type) {
            localCallback = function() {
                APP.xmpp.setVideoMute(false, function(mute) {
                    eventEmitter.emit(RTCEvents.VIDEO_MUTE, mute);
                });

                callback();
            };
        }
        // FIXME: Workaround for FF/IE/Safari
        if (stream && stream.videoStream) {
            stream = stream.videoStream;
        }
        var videoStream = this.rtcUtils.createStream(stream, true);
        this.localVideo =
            this.createLocalStream(videoStream, "video", true, type);
        // Stop the stream
        this.stopMediaStream(oldStream);

        APP.xmpp.switchStreams(videoStream, oldStream,localCallback);
    },
    changeLocalAudio: function (stream, callback) {
        var oldStream = this.localAudio.getOriginalStream();
        var newStream = this.rtcUtils.createStream(stream);
        this.localAudio = this.createLocalStream(newStream, "audio", true);
        // Stop the stream
        this.stopMediaStream(oldStream);
        APP.xmpp.switchStreams(newStream, oldStream, callback, true);
    },
    isVideoMuted: function (jid) {
        if (jid === APP.xmpp.myJid()) {
            var localVideo = APP.RTC.localVideo;
            return (!localVideo || localVideo.isMuted());
        } else {
            if (!APP.RTC.remoteStreams[jid] ||
                !APP.RTC.remoteStreams[jid][MediaStreamType.VIDEO_TYPE]) {
                return null;
            }
            return APP.RTC.remoteStreams[jid][MediaStreamType.VIDEO_TYPE].muted;
        }
    },
    setVideoMute: function (mute, callback, options) {
        if (!this.localVideo)
            return;

        if (mute == APP.RTC.localVideo.isMuted())
        {
            APP.xmpp.sendVideoInfoPresence(mute);
            if (callback)
                callback(mute);
        }
        else
        {
            APP.RTC.localVideo.setMute(mute);
            APP.xmpp.setVideoMute(
                mute,
                callback,
                options);
        }
    },
    setDeviceAvailability: function (devices) {
        if(!devices)
            return;
        if(devices.audio === true || devices.audio === false)
            this.devices.audio = devices.audio;
        if(devices.video === true || devices.video === false)
            this.devices.video = devices.video;
        eventEmitter.emit(RTCEvents.AVAILABLE_DEVICES_CHANGED, this.devices);
    },
    /**
     * A method to handle stopping of the stream.
     * One point to handle the differences in various implementations.
     * @param mediaStream MediaStream object to stop.
     */
    stopMediaStream: function (mediaStream) {
        mediaStream.getTracks().forEach(function (track) {
            // stop() not supported with IE
            if (track.stop) {
                track.stop();
            }
        });

        // leave stop for implementation still using it
        if (mediaStream.stop) {
            mediaStream.stop();
        }
    },
    /**
     * Adds onended handler to a MediaStream.
     * @param mediaStream a MediaStream to attach onended handler
     * @param handler the handler
     */
    addMediaStreamInactiveHandler: function (mediaStream, handler) {
        if (mediaStream.addEventListener) {
            // chrome
            mediaStream.onended = handler;
        } else {
            // themasys
            mediaStream.attachEvent('ended', function () {
                handler(mediaStream);
            });
        }
    },
    /**
     * Removes onended handler.
     * @param mediaStream the MediaStream to remove the handler from.
     * @param handler the handler to remove.
     */
    removeMediaStreamInactiveHandler: function (mediaStream, handler) {
        if (mediaStream.removeEventListener) {
            // chrome
            mediaStream.onended = null;
        } else {
            // themasys
            mediaStream.detachEvent('ended', handler);
        }
    }
};

module.exports = RTC;
<|endoftext|>


<commit_before>/**
 * concern.js
 *
 * !concern
 * Toggles whether the bot should interrupt charged conversations
 * with an unhelpful message.
 */
var bot = require( '..' );
var isEmotional = require( 'emotional_alert' );
var nox = false;

bot.addListener( 'message', function ( nick, to, text ) {
	if ( !nox ) {
		var x = isEmotional( text );
		if ( x.emotional) {
			if ( x.winner ) {
				var adj = {
					0: '',
					1: 'slightly ',
					2: 'rather ',
					3: 'quite ',
					4: 'very ',
					5: 'extremely ',
					6: 'copiously ',
					7: 'agonizingly '
				};
				x.adj = adj[x.emotional] === undefined ? 'a tad ' : adj[x.emotional];
				switch (x.winner) {
					case 'anger':
						x.hwinner = 'angry';
						break;
					case 'stress':
						x.hwinner = 'stressed';
						break;
					case 'sad':
						/* falls through */
					default:
						x.hwinner = x.winner;
				}

				bot.say( to, nick + ': you seem ' + x.adj + x.hwinner + ' (score: ' + x.emotional + ')');
			} else {
				// danger phrase
				bot.say( to, nick + ': that is a worrying thing to say' );
			}
		}
	}
	if ( text === '!concern' ) {
		if ( !nox ) {
			nox = true;
			bot.say( to, nick + ': adopting air of unconcern' );
		} else {
			nox = false;
			bot.say( to, nick + ': concerning myself with matters' );
		}
	}
} );
<commit_msg>hide 'danger' concern<commit_after>/**
 * concern.js
 *
 * !concern
 * Toggles whether the bot should interrupt charged conversations
 * with an unhelpful message.
 */
var bot = require( '..' );
var isEmotional = require( 'emotional_alert' );
var nox = false;
var verbose = false; // TODO: cvar

bot.addListener( 'message', function ( nick, to, text ) {
	if ( !nox ) {
		var x = isEmotional( text );
		if ( x.emotional) {
			if ( x.winner ) {
				var adj = {
					0: '',
					1: 'slightly ',
					2: 'rather ',
					3: 'quite ',
					4: 'very ',
					5: 'extremely ',
					6: 'copiously ',
					7: 'agonizingly '
				};
				x.adj = adj[x.emotional] === undefined ? 'a tad ' : adj[x.emotional];
				switch (x.winner) {
					case 'anger':
						x.hwinner = 'angry';
						break;
					case 'stress':
						x.hwinner = 'stressed';
						break;
					case 'sad':
						/* falls through */
					default:
						x.hwinner = x.winner;
				}

				bot.say( to, nick + ': you seem ' + x.adj + x.hwinner + ' (score: ' + x.emotional + ')');
			} else {
				// danger phrase
				if ( verbose ) {
					bot.say( to, nick + ': that is a worrying thing to say' );
				}
			}
		}
	}
	if ( text === '!concern' ) {
		if ( !nox ) {
			nox = true;
			bot.say( to, nick + ': adopting air of unconcern' );
		} else {
			nox = false;
			bot.say( to, nick + ': concerning myself with matters' );
		}
	}
} );
<|endoftext|>


<commit_before>/* See license.txt for terms of usage */

// ********************************************************************************************* //
// Constants

var EXPORTED_SYMBOLS = [];

// ********************************************************************************************* //
// Firebug Trace - FBTrace

var Cu = Components.utils;
var scope = {};

try
{
    Cu.import("resource://fbtrace/firebug-trace-service.js", scope);
}
catch (err)
{
    scope.traceConsoleService =
    {
        getTracer: function(prefDomain)
        {
            var TraceAPI = ["dump", "sysout", "setScope", "matchesNode", "time", "timeEnd"];
            var TraceObj = {};
            for (var i=0; i<TraceAPI.length; i++)
                TraceObj[TraceAPI[i]] = function() {};

            TraceObj.sysout = function(msg)
            {
                try
                {
                    Cu.import("resource://fbtrace/firebug-trace-service.js", scope);
                    var FBTrace = scope.traceConsoleService.getTracer("extensions.firebug");
                    FBTrace.sysout.apply(FBTrace, arguments);
                }
                catch (err)
                {
                    //Cu.reportError(getStackDump());
                    Cu.reportError(msg);
                }

            }

            return TraceObj;
        }
    };
}

function getStackDump()
{
    var lines = [];
    for (var frame = Components.stack; frame; frame = frame.caller)
        lines.push(frame.filename + " (" + frame.lineNumber + ")");

    return lines.join("\n");
};

var FBTrace = scope.traceConsoleService.getTracer("extensions.firebug");

// xxxHonza
//FBTrace.DBG_NET = true;
//FBTrace.DBG_INITIALIZE = true;
//FBTrace.DBG_NET_EVENTS = true;
FBTrace.DBG_ERRORS = true;
//FBTrace.DBG_ACTIVITYOBSERVER = true;
//FBTrace.DBG_HTTPOBSERVER = true;
//FBTrace.DBG_OPTIONS = true;
//FBTrace.DBG_CACHE = true;
//FBTrace.DBG_REMOTENETMONITOR = true;
//FBTrace.DBG_REMOTEBUG = true;
//FBTrace.DBG_NETACTOR = true;

// ********************************************************************************************* //
<commit_msg>New tracing type<commit_after>/* See license.txt for terms of usage */

// ********************************************************************************************* //
// Constants

var EXPORTED_SYMBOLS = [];

// ********************************************************************************************* //
// Firebug Trace - FBTrace

var Cu = Components.utils;
var scope = {};

try
{
    Cu.import("resource://fbtrace/firebug-trace-service.js", scope);
}
catch (err)
{
    scope.traceConsoleService =
    {
        getTracer: function(prefDomain)
        {
            var TraceAPI = ["dump", "sysout", "setScope", "matchesNode", "time", "timeEnd"];
            var TraceObj = {};
            for (var i=0; i<TraceAPI.length; i++)
                TraceObj[TraceAPI[i]] = function() {};

            TraceObj.sysout = function(msg)
            {
                try
                {
                    Cu.import("resource://fbtrace/firebug-trace-service.js", scope);
                    var FBTrace = scope.traceConsoleService.getTracer("extensions.firebug");
                    FBTrace.sysout.apply(FBTrace, arguments);
                }
                catch (err)
                {
                    //Cu.reportError(getStackDump());
                    Cu.reportError(msg);
                }

            }

            return TraceObj;
        }
    };
}

function getStackDump()
{
    var lines = [];
    for (var frame = Components.stack; frame; frame = frame.caller)
        lines.push(frame.filename + " (" + frame.lineNumber + ")");

    return lines.join("\n");
};

var FBTrace = scope.traceConsoleService.getTracer("extensions.firebug");

// xxxHonza
//FBTrace.DBG_NET = true;
//FBTrace.DBG_INITIALIZE = true;
//FBTrace.DBG_NET_EVENTS = true;
FBTrace.DBG_ERRORS = true;
//FBTrace.DBG_ACTIVITYOBSERVER = true;
//FBTrace.DBG_HTTPOBSERVER = true;
//FBTrace.DBG_OPTIONS = true;
//FBTrace.DBG_CACHE = true;
//FBTrace.DBG_REMOTENETMONITOR = true;
//FBTrace.DBG_REMOTEBUG = true;
//FBTrace.DBG_NETACTOR = true;
//FBTrace.DBG_WINDOW_EVENT_OBSERVER = true;

// ********************************************************************************************* //
<|endoftext|>


<commit_before>var networking = require("./networking");
var logging = require("./logging");
var config = require("./config");
var cache = require("./cache");
var skins = require("./skins");
var renders = require("./renders");
var fs = require("fs");

// 0098cb60-fa8e-427c-b299-793cbd302c9a
var valid_uuid = /^([0-9a-f-A-F-]{32,36}|[a-zA-Z0-9_]{1,16})$/; // uuid|username
var hash_pattern = /[0-9a-f]+$/;

function get_hash(url) {
  return hash_pattern.exec(url)[0].toLowerCase();
}

// requests skin for +uuid+ and extracts face/helm if image hash in +details+ changed
// callback contains error, image hash
function store_images(uuid, details, callback) {
  // get skin_url for +uuid+
  networking.get_skin_url(uuid, function(err, skin_url) {
    if (err) {
      callback(err, null);
    } else {
      if (skin_url) {
        logging.log(uuid + " " + skin_url);
        // set file paths
        var hash = get_hash(skin_url);
        if (details && details.hash == hash) {
          // hash hasn't changed
          logging.log(uuid + " hash has not changed");
          cache.update_timestamp(uuid, hash);
          callback(null, hash);
        } else {
          // hash has changed
          logging.log(uuid + " new hash: " + hash);
          var facepath = __dirname + "/../" + config.faces_dir + hash + ".png";
          var helmpath = __dirname + "/../" + config.helms_dir + hash + ".png";

          fs.exists(facepath, function (exists) {
            if (exists) {
              logging.log(uuid + " Avatar already exists, not downloading");
              cache.save_hash(uuid, hash);
              callback(null, hash);
            } else {
              // download skin
              networking.get_skin(skin_url, function(err, img) {
                if (err || !img) {
                  callback(err, null);
                } else {
                  // extract face / helm
                  skins.extract_face(img, facepath, function(err) {
                    if (err) {
                      callback(err);
                    } else {
                      logging.log(uuid + " face extracted");
                      logging.debug(facepath);
                      skins.extract_helm(facepath, img, helmpath, function(err) {
                        logging.log(uuid + " helm extracted");
                        logging.debug(helmpath);
                        cache.save_hash(uuid, hash);
                        callback(err, hash);
                      });
                    }
                  });
                }
              });
            }
          });
        }
      } else {
        // profile found, but has no skin
        cache.save_hash(uuid, null);
        callback(null, null);
      }
    }
  });
}

var exp = {};

// returns true if the +uuid+ is a valid uuid or username
// the uuid may be not exist, however
exp.uuid_valid = function(uuid) {
  return valid_uuid.test(uuid);
};

// decides whether to get an image from disk or to download it
// callback contains error, status, hash
// the status gives information about how the image was received
//  -1: "error"
//   0: "none" - cached as null
//   1: "cached" - found on disk
//   2: "downloaded" - profile downloaded, skin downloaded from mojang servers
//   3: "checked" - profile re-downloaded (was too old), but it has either not changed or has no skin
exp.get_image_hash = function(uuid, callback) {
  cache.get_details(uuid, function(err, details) {
    if (err) {
      callback(err, -1, null);
    } else {
      if (details && details.time + config.local_cache_time * 1000 >= new Date().getTime()) {
        // uuid known + recently updated
        logging.log(uuid + " uuid cached & recently updated");
        callback(null, (details.hash ? 1 : 0), details.hash);
      } else {
        if (details) {
          logging.log(uuid + " uuid cached, but too old");
        } else {
          logging.log(uuid + " uuid not cached");
        }
        store_images(uuid, details, function(err, hash) {
          if (err) {
            callback(err, -1, details && details.hash);
          } else {
            // skin is only checked (3) when uuid known AND hash didn't change
            // in all other cases the skin is downloaded (2)
            var status = details && (details.hash == hash) ? 3 : 2;
            logging.debug(uuid + " old hash: " + (details && details.hash));
            logging.log(uuid + " hash: " + hash);
            callback(null, status, hash);
          }
        });
      }
    }
  });
};

// handles requests for +uuid+ avatars with +size+
// callback contains error, status, image buffer, hash
// image is the user's face+helm when helm is true, or the face otherwise
// for status, see get_image_hash
exp.get_avatar = function(uuid, helm, size, callback) {
  logging.log("request: " + uuid);
  exp.get_image_hash(uuid, function(err, status, hash) {
    if (hash) {
      var facepath = __dirname + "/../" + config.faces_dir + hash + ".png";
      var helmpath = __dirname + "/../" + config.helms_dir + hash + ".png";
      var filepath = facepath;

      fs.exists(helmpath, function (exists) {
        if (helm && exists) {
          filepath = helmpath;
        }

        skins.resize_img(filepath, size, function(img_err, result) {
          if (img_err) {
            callback(img_err, -1, null, hash);
          } else {
            // we might have a hash although an error occured
            // (e.g. Mojang servers not reachable, using outdated hash)
            callback(err, (err ? -1 : status), result, hash);
          }
        });
      });
    } else {
      // hash is null when uuid has no skin
      callback(err, status, null, null);
    }
  });
};

// handles requests for +uuid+ skins
// callback contains error, hash, image buffer
exp.get_skin = function(uuid, callback) {
  logging.log(uuid + " skin request");
  exp.get_image_hash(uuid, function(err, status, hash) {
    var skinpath = __dirname + "/../" + config.skins_dir + hash + ".png";
    fs.exists(skinpath, function (exists) {
      if (exists) {
        logging.log("skin already exists, not downloading");
        skins.open_skin(skinpath, function(err, img) {
          callback(err, hash, img);
        });
      } else {
        networking.save_skin(uuid, hash, skinpath, function(err, img) {
          callback(err, hash, img);
        });
      }
    });
  });
};

function get_type(helm, body) {
  var text = body ? "body" : "head";
  return helm ? text+"helm" : text;
}

// handles creations of skin renders
// callback contanis error, hash, image buffer
exp.get_render = function(uuid, scale, helm, body, callback) {
  logging.log(uuid + " render request");
  exp.get_skin(uuid, function(err, hash, img) {
    if (!hash) {
      callback(err, -1, hash, null);
      return;
    }
    var renderpath = __dirname + "/../" + config.renders_dir + hash + "-" + scale + "-" + get_type(helm, body) + ".png";
    fs.exists(renderpath, function (exists) {
      if (exists) {
        renders.open_render(renderpath, function(err, img) {
          callback(err, 1, hash, img);
        });
        return;
      }
      if (!img) {
        callback(err, 0, hash, null);
        return;
      }
      renders.draw_model(uuid, img, scale, helm, body, function(err, img) {
        if (err) {
          callback(err, -1, hash, null);
        } else if (!img) {
          callback(null, 0, hash, null);
        } else {
          fs.writeFile(renderpath, img, "binary", function(err){
            if (err) {
              logging.log(err);
            }
            callback(null, 2, hash, img);
          });
        }
      });
    });
  });
};

module.exports = exp;
<commit_msg>remove trailing newline<commit_after>var networking = require("./networking");
var logging = require("./logging");
var config = require("./config");
var cache = require("./cache");
var skins = require("./skins");
var renders = require("./renders");
var fs = require("fs");

// 0098cb60-fa8e-427c-b299-793cbd302c9a
var valid_uuid = /^([0-9a-f-A-F-]{32,36}|[a-zA-Z0-9_]{1,16})$/; // uuid|username
var hash_pattern = /[0-9a-f]+$/;

function get_hash(url) {
  return hash_pattern.exec(url)[0].toLowerCase();
}

// requests skin for +uuid+ and extracts face/helm if image hash in +details+ changed
// callback contains error, image hash
function store_images(uuid, details, callback) {
  // get skin_url for +uuid+
  networking.get_skin_url(uuid, function(err, skin_url) {
    if (err) {
      callback(err, null);
    } else {
      if (skin_url) {
        logging.log(uuid + " " + skin_url);
        // set file paths
        var hash = get_hash(skin_url);
        if (details && details.hash == hash) {
          // hash hasn't changed
          logging.log(uuid + " hash has not changed");
          cache.update_timestamp(uuid, hash);
          callback(null, hash);
        } else {
          // hash has changed
          logging.log(uuid + " new hash: " + hash);
          var facepath = __dirname + "/../" + config.faces_dir + hash + ".png";
          var helmpath = __dirname + "/../" + config.helms_dir + hash + ".png";

          fs.exists(facepath, function (exists) {
            if (exists) {
              logging.log(uuid + " Avatar already exists, not downloading");
              cache.save_hash(uuid, hash);
              callback(null, hash);
            } else {
              // download skin
              networking.get_skin(skin_url, function(err, img) {
                if (err || !img) {
                  callback(err, null);
                } else {
                  // extract face / helm
                  skins.extract_face(img, facepath, function(err) {
                    if (err) {
                      callback(err);
                    } else {
                      logging.log(uuid + " face extracted");
                      logging.debug(facepath);
                      skins.extract_helm(facepath, img, helmpath, function(err) {
                        logging.log(uuid + " helm extracted");
                        logging.debug(helmpath);
                        cache.save_hash(uuid, hash);
                        callback(err, hash);
                      });
                    }
                  });
                }
              });
            }
          });
        }
      } else {
        // profile found, but has no skin
        cache.save_hash(uuid, null);
        callback(null, null);
      }
    }
  });
}

var exp = {};

// returns true if the +uuid+ is a valid uuid or username
// the uuid may be not exist, however
exp.uuid_valid = function(uuid) {
  return valid_uuid.test(uuid);
};

// decides whether to get an image from disk or to download it
// callback contains error, status, hash
// the status gives information about how the image was received
//  -1: "error"
//   0: "none" - cached as null
//   1: "cached" - found on disk
//   2: "downloaded" - profile downloaded, skin downloaded from mojang servers
//   3: "checked" - profile re-downloaded (was too old), but it has either not changed or has no skin
exp.get_image_hash = function(uuid, callback) {
  cache.get_details(uuid, function(err, details) {
    if (err) {
      callback(err, -1, null);
    } else {
      if (details && details.time + config.local_cache_time * 1000 >= new Date().getTime()) {
        // uuid known + recently updated
        logging.log(uuid + " uuid cached & recently updated");
        callback(null, (details.hash ? 1 : 0), details.hash);
      } else {
        if (details) {
          logging.log(uuid + " uuid cached, but too old");
        } else {
          logging.log(uuid + " uuid not cached");
        }
        store_images(uuid, details, function(err, hash) {
          if (err) {
            callback(err, -1, details && details.hash);
          } else {
            // skin is only checked (3) when uuid known AND hash didn't change
            // in all other cases the skin is downloaded (2)
            var status = details && (details.hash == hash) ? 3 : 2;
            logging.debug(uuid + " old hash: " + (details && details.hash));
            logging.log(uuid + " hash: " + hash);
            callback(null, status, hash);
          }
        });
      }
    }
  });
};

// handles requests for +uuid+ avatars with +size+
// callback contains error, status, image buffer, hash
// image is the user's face+helm when helm is true, or the face otherwise
// for status, see get_image_hash
exp.get_avatar = function(uuid, helm, size, callback) {
  logging.log("request: " + uuid);
  exp.get_image_hash(uuid, function(err, status, hash) {
    if (hash) {
      var facepath = __dirname + "/../" + config.faces_dir + hash + ".png";
      var helmpath = __dirname + "/../" + config.helms_dir + hash + ".png";
      var filepath = facepath;

      fs.exists(helmpath, function (exists) {
        if (helm && exists) {
          filepath = helmpath;
        }

        skins.resize_img(filepath, size, function(img_err, result) {
          if (img_err) {
            callback(img_err, -1, null, hash);
          } else {
            // we might have a hash although an error occured
            // (e.g. Mojang servers not reachable, using outdated hash)
            callback(err, (err ? -1 : status), result, hash);
          }
        });
      });
    } else {
      // hash is null when uuid has no skin
      callback(err, status, null, null);
    }
  });
};

// handles requests for +uuid+ skins
// callback contains error, hash, image buffer
exp.get_skin = function(uuid, callback) {
  logging.log(uuid + " skin request");
  exp.get_image_hash(uuid, function(err, status, hash) {
    var skinpath = __dirname + "/../" + config.skins_dir + hash + ".png";
    fs.exists(skinpath, function (exists) {
      if (exists) {
        logging.log("skin already exists, not downloading");
        skins.open_skin(skinpath, function(err, img) {
          callback(err, hash, img);
        });
      } else {
        networking.save_skin(uuid, hash, skinpath, function(err, img) {
          callback(err, hash, img);
        });
      }
    });
  });
};

function get_type(helm, body) {
  var text = body ? "body" : "head";
  return helm ? text+"helm" : text;
}

// handles creations of skin renders
// callback contanis error, hash, image buffer
exp.get_render = function(uuid, scale, helm, body, callback) {
  logging.log(uuid + " render request");
  exp.get_skin(uuid, function(err, hash, img) {
    if (!hash) {
      callback(err, -1, hash, null);
      return;
    }
    var renderpath = __dirname + "/../" + config.renders_dir + hash + "-" + scale + "-" + get_type(helm, body) + ".png";
    fs.exists(renderpath, function (exists) {
      if (exists) {
        renders.open_render(renderpath, function(err, img) {
          callback(err, 1, hash, img);
        });
        return;
      }
      if (!img) {
        callback(err, 0, hash, null);
        return;
      }
      renders.draw_model(uuid, img, scale, helm, body, function(err, img) {
        if (err) {
          callback(err, -1, hash, null);
        } else if (!img) {
          callback(null, 0, hash, null);
        } else {
          fs.writeFile(renderpath, img, "binary", function(err){
            if (err) {
              logging.log(err);
            }
            callback(null, 2, hash, img);
          });
        }
      });
    });
  });
};

module.exports = exp;<|endoftext|>


<commit_before>(function(root) {

var fs = null
var path = null
var app = null

if (typeof require != 'undefined') {
    fs = require('fs')
    path = require('path')
    app = require('electron').app
}

var context = typeof module != 'undefined' ? module.exports : (window.setting = {})

var settingspath = ''

if (path && app)
    settingspath = path.join(app.getPath('userData'), 'settings.json')

var settings = {}
var engines = []

var defaults = {
    'app.startup_check_updates': true,
    'app.startup_check_updates_delay': 100,
    'app.loadgame_delay': 100,
    'app.hide_busy_delay': 200,
    'autoscroll.max_interval': 200,
    'autoscroll.min_interval': 50,
    'autoscroll.diff': 10,
    'console.blocked_commands': [
        'boardsize', 'clear_board', 'play',
        'genmove', 'undo', 'fixed_handicap',
        'place_free_handicap', 'set_free_handicap',
        'loadsgf'
    ],
    'console.max_history_count': 30,
    'edit.click_currentvertex_to_remove': true,
    'edit.show_removenode_warning': true,
    'edit.undo_delay': 100,
    'engines.list': engines,
    'find.delay': 100,
    'game.default_board_size': 19,
    'game.default_komi': 6.5,
    'game.goto_end_after_loading': false,
    'game.show_ko_warning': true,
    'game.show_suicide_warning': true,
    'graph.collapse_min_depth': 1,
    'graph.collapse_tokens_count': 100000,
    'graph.delay': 300,
    'graph.grid_size': 25,
    'graph.node_active_color': '#f76047',
    'graph.node_bookmark_color': '#c678dd',
    'graph.node_collapsed_color': '#333',
    'graph.node_inactive_color': '#777',
    'graph.node_color': '#eee',
    'graph.node_comment_color': '#6bb1ff',
    'graph.node_size': 4,
    'gtp.attach_delay': 300,
    'gtp.move_delay': 300,
    'scoring.method': 'territory',
    'sound.capture_delay_max': 500,
    'sound.capture_delay_min': 300,
    'sound.enable': true,
    'view.properties_height': 50,
    'view.properties_minheight': 20,
    'view.fuzzy_stone_placement': true,
    'view.show_leftsidebar': false,
    'view.show_comments': false,
    'view.show_coordinates': false,
    'view.show_graph': false,
    'view.show_show_next_moves': true,
    'view.leftsidebar_width': 250,
    'view.leftsidebar_minwidth': 100,
    'view.sidebar_width': 200,
    'view.sidebar_minwidth': 100,
    'window.height': 622,
    'window.minheight': 590,
    'window.minwidth': 550,
    'window.width': 577
}

context.load = function() {
    if (!fs) return settings = defaults

    settings = JSON.parse(fs.readFileSync(settingspath, { encoding: 'utf8' }))

    // Load default settings

    for (var key in defaults) {
        if (key in settings) continue
        settings[key] = defaults[key]
    }

    engines = settings['engines.list']
    engines.sort(function(x, y) { return x.name >= y.name })

    return context
}

context.save = function() {
    if (!fs) return context

    fs.writeFileSync(settingspath, JSON.stringify(settings, null, '  '))
    return context
}

context.get = function(key) {
    if (key in settings) return settings[key]
    if (key in defaults) return defaults[key]
    return null
}

context.set = function(key, value) {
    settings[key] = value
    return context.save()
}

context.addEngine = function(name, path, args) {
    engines.push({
        name: name,
        path: path,
        args: args
    })
    engines.sort(function(x, y) { return x.name >= y.name })
    return context
}

context.getEngines = function() {
    return engines.slice(0)
}

context.clearEngines = function() {
    engines.length = 0
    return context
}

try {
    fs.accessSync(settingspath, fs.F_OK)
} catch(err) {
    context.save()
}

context.load()

}).call(null, typeof module != 'undefined' ? module : window)
<commit_msg>Fix typo<commit_after>(function(root) {

var fs = null
var path = null
var app = null

if (typeof require != 'undefined') {
    fs = require('fs')
    path = require('path')
    app = require('electron').app
}

var context = typeof module != 'undefined' ? module.exports : (window.setting = {})

var settingspath = ''

if (path && app)
    settingspath = path.join(app.getPath('userData'), 'settings.json')

var settings = {}
var engines = []

var defaults = {
    'app.startup_check_updates': true,
    'app.startup_check_updates_delay': 100,
    'app.loadgame_delay': 100,
    'app.hide_busy_delay': 200,
    'autoscroll.max_interval': 200,
    'autoscroll.min_interval': 50,
    'autoscroll.diff': 10,
    'console.blocked_commands': [
        'boardsize', 'clear_board', 'play',
        'genmove', 'undo', 'fixed_handicap',
        'place_free_handicap', 'set_free_handicap',
        'loadsgf'
    ],
    'console.max_history_count': 30,
    'edit.click_currentvertex_to_remove': true,
    'edit.show_removenode_warning': true,
    'edit.undo_delay': 100,
    'engines.list': engines,
    'find.delay': 100,
    'game.default_board_size': 19,
    'game.default_komi': 6.5,
    'game.goto_end_after_loading': false,
    'game.show_ko_warning': true,
    'game.show_suicide_warning': true,
    'graph.collapse_min_depth': 1,
    'graph.collapse_tokens_count': 100000,
    'graph.delay': 300,
    'graph.grid_size': 25,
    'graph.node_active_color': '#f76047',
    'graph.node_bookmark_color': '#c678dd',
    'graph.node_collapsed_color': '#333',
    'graph.node_inactive_color': '#777',
    'graph.node_color': '#eee',
    'graph.node_comment_color': '#6bb1ff',
    'graph.node_size': 4,
    'gtp.attach_delay': 300,
    'gtp.move_delay': 300,
    'scoring.method': 'territory',
    'sound.capture_delay_max': 500,
    'sound.capture_delay_min': 300,
    'sound.enable': true,
    'view.properties_height': 50,
    'view.properties_minheight': 20,
    'view.fuzzy_stone_placement': true,
    'view.show_leftsidebar': false,
    'view.show_comments': false,
    'view.show_coordinates': false,
    'view.show_graph': false,
    'view.show_next_moves': true,
    'view.leftsidebar_width': 250,
    'view.leftsidebar_minwidth': 100,
    'view.sidebar_width': 200,
    'view.sidebar_minwidth': 100,
    'window.height': 622,
    'window.minheight': 590,
    'window.minwidth': 550,
    'window.width': 577
}

context.load = function() {
    if (!fs) return settings = defaults

    settings = JSON.parse(fs.readFileSync(settingspath, { encoding: 'utf8' }))

    // Load default settings

    for (var key in defaults) {
        if (key in settings) continue
        settings[key] = defaults[key]
    }

    engines = settings['engines.list']
    engines.sort(function(x, y) { return x.name >= y.name })

    return context
}

context.save = function() {
    if (!fs) return context

    fs.writeFileSync(settingspath, JSON.stringify(settings, null, '  '))
    return context
}

context.get = function(key) {
    if (key in settings) return settings[key]
    if (key in defaults) return defaults[key]
    return null
}

context.set = function(key, value) {
    settings[key] = value
    return context.save()
}

context.addEngine = function(name, path, args) {
    engines.push({
        name: name,
        path: path,
        args: args
    })
    engines.sort(function(x, y) { return x.name >= y.name })
    return context
}

context.getEngines = function() {
    return engines.slice(0)
}

context.clearEngines = function() {
    engines.length = 0
    return context
}

try {
    fs.accessSync(settingspath, fs.F_OK)
} catch(err) {
    context.save()
}

context.load()

}).call(null, typeof module != 'undefined' ? module : window)
<|endoftext|>


<commit_before>export class MenuUpdate {
  constructor(pm, events, prepare) {
    this.pm = pm
    this.prepare = prepare

    this.mustUpdate = false
    this.updateInfo = null

    this.events = events.split(" ")
    this.onEvent = () => this.mustUpdate = true
    this.events.forEach(event => pm.on(event, this.onEvent))
    pm.on("flush", this.onFlush = this.onFlush.bind(this))
    pm.on("flushed", this.onFlushed = this.onFlushed.bind(this))
  }

  detach() {
    this.events.forEach(event => this.pm.off(event, this.onEvent))
    this.pm.off("flush", this.onFlush)
    this.pm.off("flushed", this.onFlushed)
  }

  onFlush() {
    if (this.mustUpdate) {
      this.mustUpdate = false
      this.update = this.prepare()
    }
  }

  onFlushed() {
    if (this.update) {
      this.update()
      this.update = null
    }
  }

  force() {
    this.mustUpdate = false
    this.updateInfo = null
    let update = this.prepare()
    if (update) update()
  }
}
<commit_msg>Make menu updates fire even when no flush happens, debounce flushes<commit_after>const MIN_FLUSH_DELAY = 200
const UPDATE_TIMEOUT = 200

export class MenuUpdate {
  constructor(pm, events, prepare) {
    this.pm = pm
    this.prepare = prepare

    this.mustUpdate = false
    this.updateInfo = null
    this.timeout = null
    this.lastFlush = 0

    this.events = events.split(" ")
    this.onEvent = this.onEvent.bind(this)
    this.force = this.force.bind(this)
    this.events.forEach(event => pm.on(event, this.onEvent))
    pm.on("flush", this.onFlush = this.onFlush.bind(this))
    pm.on("flushed", this.onFlushed = this.onFlushed.bind(this))
  }

  detach() {
    clearTimeout(this.timeout)
    this.events.forEach(event => this.pm.off(event, this.onEvent))
    this.pm.off("flush", this.onFlush)
    this.pm.off("flushed", this.onFlushed)
  }

  onFlush() {
    let now = Date.now()
    if (this.mustUpdate && (now - this.lastFlush) >= MIN_FLUSH_DELAY) {
      this.lastFlush = now
      clearTimeout(this.timeout)
      this.mustUpdate = false
      this.update = this.prepare()
    }
  }

  onFlushed() {
    if (this.update) {
      this.update()
      this.update = null
    }
  }

  onEvent() {
    this.mustUpdate = true
    clearTimeout(this.timeout)
    this.timeout = setTimeout(this.force, UPDATE_TIMEOUT)
  }

  force() {
    this.mustUpdate = false
    this.updateInfo = null
    this.lastFlush = Date.now()
    clearTimeout(this.timeout)
    let update = this.prepare()
    if (update) update()
  }
}
<|endoftext|>


<commit_before>/**
 * @chalk
 * @name MacGyver fileupload
 * @description
 * Module for proxying jQuery File Upload
 * NOTE: Additional options can be found at
 * https://github.com/blueimp/jQuery-File-Upload/wiki/Options
 *
 * @dependencies
 * - jQuery
 * - [jQuery file upload](https://github.com/blueimp/jQuery-File-Upload)
 *
 * @param {String} mac-upload-url      File upload url
 * @param {Expr}   mac-upload-files    Array to store uploaded files
 *
 * @param {Expr}   mac-upload-add      Function to call on add
 * @param {Expr}   mac-upload-submit   Function to call on submit
 * @param {Expr}   mac-upload-success  Upload success callback
 * @param {Expr}   mac-upload-error    Upload error callback
 * @param {Expr}   mac-upload-always   Callback for completed (success, abort or error) requests
 * @param {Expr}   mac-upload-progress Callback for upload progress
 * @param {Expr}   mac-upload-stop     Callback for uploads stop
 *
 * @param {Expr}   mac-upload-processstart
 * Callback for the start of the fileupload processing queue
 *
 * @param {Expr}   mac-upload-processstop
 * Callback for the stop of the fileupload processing queue.
 *
 * @param {Expr}   mac-upload-options Additional options to pass to jquery fileupload
 *
 * @param {*} mac-upload-progress Add progress to each file
 * @param {*} mac-upload-previews Add file data to each file using File Reader API
 * @param {String} mac-upload-drop-zone Add drop zone support with CSS selector
 */

(function(window, document, undefined) {'use strict';

angular.module("Mac.Upload", []).
  /**
   * @chalk
   * @name macUploadProvider
   * @description
   * mac-upload provider allowing application to configurate mac-upload defaults
   * in `config` block
   */
  provider("macUpload", function() {
    var defaults = {
      dragoverClass: "droppable",
      emitEvents: [
        'fileuploadadd',
        'fileuploadsubmit',
        'fileuploadsend',
        'fileuploaddone',
        'fileuploadfail',
        'fileuploadalways',
        'fileuploadprogress',
        'fileuploadprogressall',
        'fileuploadstart',
        'fileuploadstop',
        'fileuploadchange',
        'fileuploadpaste',
        'fileuploaddrop',
        'fileuploaddragover',
        'fileuploadchunksend',
        'fileuploadchunkdone',
        'fileuploadchunkfail',
        'fileuploadchunkalways',
        'fileuploadprocessstart',
        'fileuploadprocess',
        'fileuploadprocessdone',
        'fileuploadprocessfail',
        'fileuploadprocessalways',
        'fileuploadprocessstop'
      ]
    };

    /**
     * @chalk
     * @name setDefaults
     * @function
     * @description
     * Setting mac-upload defaults
     * @param {String | Object} key Key to update
     */
    this.setDefaults = function(key, value) {
      if (angular.isObject(key)) {
        angular.extend(defaults, key);
      } else {
        defaults[key] = value;
      }
    };

    this.$get = [function(){
      return {
        defaults: defaults
      };
    }];
  }).

  /**
   * @chalk
   * @name macUploadController
   * @description
   * Controller for mac-upload directive. Expose callback functions for easier
   * unit testings
   */
  controller("macUploadController", [
    "$scope",
    "$element",
    "$attrs",
    "$parse",
    function($scope, element, attrs, $parse) {
      var ctrl = {
        adders: [],
        progressors: [],
        uploadOption: function(key, value) {
          // If passed in only an object, travse through each object
          // property
          if (angular.isObject(key)) {
            for (var prop in key) {
              ctrl.uploadOption(prop, key[prop]);
            }

          //Read option
          } else if (angular.isString(key) && value === undefined) {
            return element.fileupload("option", key);

          //Set option based on key and value
          } else {
            element.fileupload("option", key, value);
          }
        },
        invokeCallback: function(action, $event, $data, invokeApply) {
          var normalized = attrs.$normalize("mac-upload-" + action);

          // `invokeApply` default to true
          if (invokeApply === undefined) {
            invokeApply = true;
          }

          if (attrs[normalized]) {
            var $xhr = $data.jqXHR,
                responseText = "",
                locals = {
                  $event: $event,
                  $data: $data
                };

            // `invokeCallback` is designed to be generic to handle most
            // fileupload callbacks. Since $data.jqXHR only comes back
            // with upload request callbacks, $xhr is checked before
            // getting status and response
            if ($xhr !== undefined) {
              locals.$xhr = $xhr;
              locals.$status = $xhr.$status;
              responseText = $xhr.responseText || "";

              try {
                locals.$response = JSON.parse(responseText);
              } catch (err) {
                locals.$response = responseText;
              }
            }

            $parse(attrs[normalized])($scope, locals);

            if (invokeApply) {
              $scope.$apply();
            }
          }
        },
        // Multiple file upload callbacks
        callbacks: {
          add: function($event, $data) {
            if ($event.isDefaultPrevented()){
              return false;
            }

            ctrl.invokeCallback("add", $event, $data);

            angular.forEach(ctrl.adders, function(fn) {
              fn($data);
            });

            $data.process().then(function(){
              if ((ctrl.uploadOption("autoUpload") ||
                  $data.autoUpload) &&
                  $data.autoUpload !== false){
                $data.submit();
              }
            });
          },
          submit: function($event, $data) {
            if ($event.isDefaultPrevented()){
              return false;
            }

            ctrl.invokeCallback("submit", $event, $data);
          },
          fail: function($event, $data) {
            if ($event.isDefaultPrevented()){
              return false;
            }

            ctrl.invokeCallback("fail", $event, $data);
          },
          done: function($event, $data) {
            if ($event.isDefaultPrevented()){
              return false;
            }

            ctrl.invokeCallback("success", $event, $data);
          },
          always: function($event, $data) {
            if ($event.isDefaultPrevented()){
              return false;
            }

            ctrl.invokeCallback("always", $event, $data);
          },
          progress: function($event, $data) {
            if ($event.isDefaultPrevented()){
              return false;
            }

            angular.forEach(ctrl.progressors, function(fn){
              fn($data);
            });

            ctrl.invokeCallback("progress", $event, $data);
          },
          stop: function($event) {
            ctrl.invokeCallback("stop", $event);
          },
          processstart: function($event) {
            ctrl.invokeCallback("processstart", $event);
          },
          processstop: function($event) {
            ctrl.invokeCallback("processstop", $event);
          }
        }
      };
      return ctrl;
    }
  ]).

  /**
   * @chalk
   * @name macUpload
   * @description
   * Main upload directive for initializing file upload on file type input
   */
  directive("macUpload", [
    "$parse",
    "macUpload",
    function($parse, macUpload) {
      return {
        require:    "macUpload",
        controller: "macUploadController",
        link: function($scope, element, attrs, uploadCtrl) {
          var options = {},
              defaults = macUpload.defaults;

          options.url = $parse(attrs.macUploadUrl)($scope) || "";

          element.fileupload(angular.extend(options, uploadCtrl.callbacks, {
            pasteZone: null,
            replaceFileInput: false
          })).
          on(defaults.emitEvents.join(' '), function(event, data) {
            $scope.$emit(event.type, data);
          });
        }
      };
    }
  ]).

  /**
   * @chalk
   * @name macUploadPreviewsController
   * @description
   * Controller for mac-upload-previews directive used for reading file content
   * using FileReader API
   */
  controller("macUploadPreviewsController", [
    function() {
      /**
       * @name type
       * @function
       * @description
       * Get the class of the object
       * @param {Object} object Any object
       * @returns {String} Object class name
       */
      this.class = function(object) {
        return Object.prototype.toString.call(object).slice(8, -1);
      };

      /**
       * @name isFileSupported
       * @function
       * @description
       * Check if browser support file reader
       * @returns {Boolean} if file reader is supported
       */
      this.isFileSupported = function() {
        return window.FileReader && window.File && window.FileList;
      };

      /**
       * @name readFiles
       * @function
       * @description
       * Read file data using file reader API
       * @param {Array} files Array of files
       */
      this.readFiles = function(files) {
        var reader, readFile, self = this;

        if (!this.sFileSupported()) {
          return false;
        }

        readFile = function(file) {
          reader = new FileReader();
          reader.onload = function(event) {
            file.data = event.target.result;
          };

          reader.readAsDataURL(file);
        };

        angular.forEach(files, function(file){
          // Make sure items in the array are File class
          if (self.class(file) === "File") {
            readFile(file);
          }
        });

      };
    }
  ]).

  directive("macUploadPreviews", [
    "$rootScope",
    function($rootScope) {
      return {
        restrict: "A",
        require: ["macUploadPreviews", "macUpload"],
        controller: "macUploadPreviewsController",
        link: function($scope, element, attrs, ctrls) {
          var previewsCtrl = ctrls[0],
              uploadCtrl = ctrls[1],
              adder = function($data) {
                previewsCtrl.readFiles($data.files);
              };

          uploadCtrl.adders.push(adder);
        }
      };
    }
  ]).

  /*
  @chalk
  @name macUploadProgressController
  @description
  Controller for mac-upload-progress directive. This controller currently has
  one function `updateProgress` to calculate file upload progress.

  @param {Function} updateProgress Update progress
    - {Object} data File upload data from jQuery file upload
    - {Object} file File object stored on preview controller
    - returns {Integer} Upload percentage
  */
  controller("macUploadProgressController", [
    function() {
      this.updateProgress = function(data, file) {
        file.progress = parseInt(data.loaded / data.total * 100, 10);
        return file.progress;
      };
    }
  ]).

  /*
  @chalk
  @name macUploadProgress
  @description
  mac-upload-progress directive. Mostly used for binding controller onto scope
  */
  directive("macUploadProgress", [
    function() {
      return {
        restrict: "A",
        require: ["macUpload", "macUploadProgress", "macUploadFiles"],
        controller: "macUploadProgressController",
        link: function($scope, element, attrs, ctrls) {
          var uploadCtrl = ctrls[0],
              progressCtrl = ctrls[1],
              filesCtrl = ctrls[2],
              progress = function($data) {
                var file = filesCtrl.getFileByFilename($data.files[0].name);

                if (file !== null) {
                  progressCtrl.updateProgress($data, file);
                }
              };

          uploadCtrl.progressors.push(progress);
        }
      };
    }
  ]).

  /**
   * @chalk
   * @name macUploadFilesController
   * @description
   * Controller for mac-upload-files directive to add files capability to mac-upload
   */
  controller("macUploadFilesController", [
    "$scope",
    "$attrs",
    "$parse",
    function($scope, attrs, $parse) {
      var filesGet, filesSet, ctrl = {};

      filesGet = $parse(attrs.macUploadFiles);
      // Similar to how AngularJS handle unassignable variable
      filesSet = filesGet.assign || function() {
        throw new Error(attrs.macUploadFiles + "used with mac-upload is non-assignable");
      };

      ctrl.files = filesGet($scope);

      /**
       * @name getFileByFilename
       * @function
       * @description
       * Get the file in the file list
       * @param {String} filename File name
       */
      ctrl.getFileByFilename = function(filename) {
        var i;

        for (i = ctrl.files.length - 1; i >= 0 ; i--) {
          if (ctrl.files[i].name === filename) {
            return ctrl.files[i];
          }
        }
        return null;
      };

      return ctrl;
    }
  ]).

  directive("macUploadFiles", [
    function() {
      return {
        restrict: "A",
        require: ["macUpload", "macUploadFiles"],
        controller: "macUploadFilesController",
        link: function($scope, element, attrs, ctrls) {
          var uploadCtrl = ctrls[0],
              filesCtrl = ctrls[1],
              adder = function($data) {
                var method = uploadCtrl.uploadOption("prependFiles") ?
                  "unshift" : "push";

                Array.prototype[method].apply(filesCtrl.files, $data.files);
              };

          uploadCtrl.adders.push(adder);
        }
      };
    }
  ]).

  /**
   * @name mac-upload-options directive
   * @description
   * Set additional fileupload options
   */
  directive("macUploadOptions", [
    function() {
      return {
        restrict: "A",
        require: "macUpload",
        priority: 100,
        link: function($scope, element, attrs, ctrls) {
          // Fail silently if options is not defined
          if (!attrs.macUploadOptions) {
            return;
          }

          $scope.$watch(attrs.macUploadOptions, function(newOptions) {
            element.fileupload("option", newOptions);
          }, true);
        }
      };
    }
  ]).

  /**
   * @name mac-upload-drop-zone directive
   * @description
   * Adding dropzone support
   * @param {String} mac-upload-drop-zone CSS selector
   */
  directive("macUploadDropZone", [
    "$document",
    "$timeout",
    "macUpload",
    function($document, $timeout, macUpload) {
      return {
        restrict: "A",
        require: ["macUpload"],
        priority: 100,
        link: function($scope, element, attrs, ctrls) {
          var preventDefault, dragoverCallback, dragoverTimeout, dropZone,
              selector;

          if (!attrs.macUploadDropZone) {
            throw new Error("mac-upload-drop-zone missing selector");
          }

          selector = attrs.macUploadDropZone;
          dropZone = angular.element(selector);

          /**
           * @name preventDefault
           * @function
           * @description
           * Callback for drop and dragover to disable default behavior
           * @param {Event} event jQuery event
           */
          preventDefault = function(event) {
            event.preventDefault();
          };

          /**
           * @name dragoverCallback
           * @function
           * @description
           * Callback for drag zone dragover event
           * @param {Event} event jQuery event
           */
          dragoverCallback = function(event) {
            var node, target, className = macUpload.defaults.dragoverClass;

            if (dragoverTimeout !== null) {
              $timeout.cancel(dragoverTimeout);
            }

            target = angular.element(event.target);
            node = target.is(dropZone) ? dropZone : target.parents(selector);
            if (node.length > 0) {
              dropZone.addClass(className);

              dragoverTimeout = $timeout(function() {
                dropZone.removeClass(className);
              }, 150, false);
            }
          };

          $document.bind("drop dragover", preventDefault);
          dropZone.bind("dragover", dragoverCallback);

          // Remove all bindings when fileupload element is removed
          $scope.$on("$destroy", function() {
            $document.unbind("drop dragover", preventDefault);
            dropZone.unbind("dragover", dragoverCallback);
          });

          // Initialize dropZone support on jQuery file upload
          element.fileupload("option", "dropZone", dropZone);
        }
      };
    }
  ]);

})(window, document);
<commit_msg>fix(uploadPreviews): Fixed typo and referencing the wrong function<commit_after>/**
 * @chalk
 * @name MacGyver fileupload
 * @description
 * Module for proxying jQuery File Upload
 * NOTE: Additional options can be found at
 * https://github.com/blueimp/jQuery-File-Upload/wiki/Options
 *
 * @dependencies
 * - jQuery
 * - [jQuery file upload](https://github.com/blueimp/jQuery-File-Upload)
 *
 * @param {String} mac-upload-url      File upload url
 * @param {Expr}   mac-upload-files    Array to store uploaded files
 *
 * @param {Expr}   mac-upload-add      Function to call on add
 * @param {Expr}   mac-upload-submit   Function to call on submit
 * @param {Expr}   mac-upload-success  Upload success callback
 * @param {Expr}   mac-upload-error    Upload error callback
 * @param {Expr}   mac-upload-always   Callback for completed (success, abort or error) requests
 * @param {Expr}   mac-upload-progress Callback for upload progress
 * @param {Expr}   mac-upload-stop     Callback for uploads stop
 *
 * @param {Expr}   mac-upload-processstart
 * Callback for the start of the fileupload processing queue
 *
 * @param {Expr}   mac-upload-processstop
 * Callback for the stop of the fileupload processing queue.
 *
 * @param {Expr}   mac-upload-options Additional options to pass to jquery fileupload
 *
 * @param {*} mac-upload-progress Add progress to each file
 * @param {*} mac-upload-previews Add file data to each file using File Reader API
 * @param {String} mac-upload-drop-zone Add drop zone support with CSS selector
 */

(function(window, document, undefined) {'use strict';

angular.module("Mac.Upload", []).
  /**
   * @chalk
   * @name macUploadProvider
   * @description
   * mac-upload provider allowing application to configurate mac-upload defaults
   * in `config` block
   */
  provider("macUpload", function() {
    var defaults = {
      dragoverClass: "droppable",
      emitEvents: [
        'fileuploadadd',
        'fileuploadsubmit',
        'fileuploadsend',
        'fileuploaddone',
        'fileuploadfail',
        'fileuploadalways',
        'fileuploadprogress',
        'fileuploadprogressall',
        'fileuploadstart',
        'fileuploadstop',
        'fileuploadchange',
        'fileuploadpaste',
        'fileuploaddrop',
        'fileuploaddragover',
        'fileuploadchunksend',
        'fileuploadchunkdone',
        'fileuploadchunkfail',
        'fileuploadchunkalways',
        'fileuploadprocessstart',
        'fileuploadprocess',
        'fileuploadprocessdone',
        'fileuploadprocessfail',
        'fileuploadprocessalways',
        'fileuploadprocessstop'
      ]
    };

    /**
     * @chalk
     * @name setDefaults
     * @function
     * @description
     * Setting mac-upload defaults
     * @param {String | Object} key Key to update
     */
    this.setDefaults = function(key, value) {
      if (angular.isObject(key)) {
        angular.extend(defaults, key);
      } else {
        defaults[key] = value;
      }
    };

    this.$get = [function(){
      return {
        defaults: defaults
      };
    }];
  }).

  /**
   * @chalk
   * @name macUploadController
   * @description
   * Controller for mac-upload directive. Expose callback functions for easier
   * unit testings
   */
  controller("macUploadController", [
    "$scope",
    "$element",
    "$attrs",
    "$parse",
    function($scope, element, attrs, $parse) {
      var ctrl = {
        adders: [],
        progressors: [],
        uploadOption: function(key, value) {
          // If passed in only an object, travse through each object
          // property
          if (angular.isObject(key)) {
            for (var prop in key) {
              ctrl.uploadOption(prop, key[prop]);
            }

          //Read option
          } else if (angular.isString(key) && value === undefined) {
            return element.fileupload("option", key);

          //Set option based on key and value
          } else {
            element.fileupload("option", key, value);
          }
        },
        invokeCallback: function(action, $event, $data, invokeApply) {
          var normalized = attrs.$normalize("mac-upload-" + action);

          // `invokeApply` default to true
          if (invokeApply === undefined) {
            invokeApply = true;
          }

          if (attrs[normalized]) {
            var $xhr = $data.jqXHR,
                responseText = "",
                locals = {
                  $event: $event,
                  $data: $data
                };

            // `invokeCallback` is designed to be generic to handle most
            // fileupload callbacks. Since $data.jqXHR only comes back
            // with upload request callbacks, $xhr is checked before
            // getting status and response
            if ($xhr !== undefined) {
              locals.$xhr = $xhr;
              locals.$status = $xhr.$status;
              responseText = $xhr.responseText || "";

              try {
                locals.$response = JSON.parse(responseText);
              } catch (err) {
                locals.$response = responseText;
              }
            }

            $parse(attrs[normalized])($scope, locals);

            if (invokeApply) {
              $scope.$apply();
            }
          }
        },
        // Multiple file upload callbacks
        callbacks: {
          add: function($event, $data) {
            if ($event.isDefaultPrevented()){
              return false;
            }

            ctrl.invokeCallback("add", $event, $data);

            angular.forEach(ctrl.adders, function(fn) {
              fn($data);
            });

            $data.process().then(function(){
              if ((ctrl.uploadOption("autoUpload") ||
                  $data.autoUpload) &&
                  $data.autoUpload !== false){
                $data.submit();
              }
            });
          },
          submit: function($event, $data) {
            if ($event.isDefaultPrevented()){
              return false;
            }

            ctrl.invokeCallback("submit", $event, $data);
          },
          fail: function($event, $data) {
            if ($event.isDefaultPrevented()){
              return false;
            }

            ctrl.invokeCallback("fail", $event, $data);
          },
          done: function($event, $data) {
            if ($event.isDefaultPrevented()){
              return false;
            }

            ctrl.invokeCallback("success", $event, $data);
          },
          always: function($event, $data) {
            if ($event.isDefaultPrevented()){
              return false;
            }

            ctrl.invokeCallback("always", $event, $data);
          },
          progress: function($event, $data) {
            if ($event.isDefaultPrevented()){
              return false;
            }

            angular.forEach(ctrl.progressors, function(fn){
              fn($data);
            });

            ctrl.invokeCallback("progress", $event, $data);
          },
          stop: function($event) {
            ctrl.invokeCallback("stop", $event);
          },
          processstart: function($event) {
            ctrl.invokeCallback("processstart", $event);
          },
          processstop: function($event) {
            ctrl.invokeCallback("processstop", $event);
          }
        }
      };
      return ctrl;
    }
  ]).

  /**
   * @chalk
   * @name macUpload
   * @description
   * Main upload directive for initializing file upload on file type input
   */
  directive("macUpload", [
    "$parse",
    "macUpload",
    function($parse, macUpload) {
      return {
        require:    "macUpload",
        controller: "macUploadController",
        link: function($scope, element, attrs, uploadCtrl) {
          var options = {},
              defaults = macUpload.defaults;

          options.url = $parse(attrs.macUploadUrl)($scope) || "";

          element.fileupload(angular.extend(options, uploadCtrl.callbacks, {
            pasteZone: null,
            replaceFileInput: false
          })).
          on(defaults.emitEvents.join(' '), function(event, data) {
            $scope.$emit(event.type, data);
          });
        }
      };
    }
  ]).

  /**
   * @chalk
   * @name macUploadPreviewsController
   * @description
   * Controller for mac-upload-previews directive used for reading file content
   * using FileReader API
   */
  controller("macUploadPreviewsController", [
    function() {
      /**
       * @name type
       * @function
       * @description
       * Get the class of the object
       * @param {Object} object Any object
       * @returns {String} Object class name
       */
      this.class = function(object) {
        return Object.prototype.toString.call(object).slice(8, -1);
      };

      /**
       * @name isFileSupported
       * @function
       * @description
       * Check if browser support file reader
       * @returns {Boolean} if file reader is supported
       */
      this.isFileSupported = function() {
        return window.FileReader && window.File && window.FileList;
      };

      /**
       * @name readFiles
       * @function
       * @description
       * Read file data using file reader API
       * @param {Array} files Array of files
       */
      this.readFiles = function(files) {
        var reader, readFile, self = this;

        if (!this.isFileSupported()) {
          return false;
        }

        readFile = function(file) {
          reader = new FileReader();
          reader.onload = function(event) {
            file.data = event.target.result;
          };

          reader.readAsDataURL(file);
        };

        angular.forEach(files, function(file){
          // Make sure items in the array are File class
          if (self.class(file) === "File") {
            readFile(file);
          }
        });

      };
    }
  ]).

  directive("macUploadPreviews", [
    "$rootScope",
    function($rootScope) {
      return {
        restrict: "A",
        require: ["macUploadPreviews", "macUpload"],
        controller: "macUploadPreviewsController",
        link: function($scope, element, attrs, ctrls) {
          var previewsCtrl = ctrls[0],
              uploadCtrl = ctrls[1],
              adder = function($data) {
                previewsCtrl.readFiles($data.files);
              };

          uploadCtrl.adders.push(adder);
        }
      };
    }
  ]).

  /*
  @chalk
  @name macUploadProgressController
  @description
  Controller for mac-upload-progress directive. This controller currently has
  one function `updateProgress` to calculate file upload progress.

  @param {Function} updateProgress Update progress
    - {Object} data File upload data from jQuery file upload
    - {Object} file File object stored on preview controller
    - returns {Integer} Upload percentage
  */
  controller("macUploadProgressController", [
    function() {
      this.updateProgress = function(data, file) {
        file.progress = parseInt(data.loaded / data.total * 100, 10);
        return file.progress;
      };
    }
  ]).

  /*
  @chalk
  @name macUploadProgress
  @description
  mac-upload-progress directive. Mostly used for binding controller onto scope
  */
  directive("macUploadProgress", [
    function() {
      return {
        restrict: "A",
        require: ["macUpload", "macUploadProgress", "macUploadFiles"],
        controller: "macUploadProgressController",
        link: function($scope, element, attrs, ctrls) {
          var uploadCtrl = ctrls[0],
              progressCtrl = ctrls[1],
              filesCtrl = ctrls[2],
              progress = function($data) {
                var file = filesCtrl.getFileByFilename($data.files[0].name);

                if (file !== null) {
                  progressCtrl.updateProgress($data, file);
                }
              };

          uploadCtrl.progressors.push(progress);
        }
      };
    }
  ]).

  /**
   * @chalk
   * @name macUploadFilesController
   * @description
   * Controller for mac-upload-files directive to add files capability to mac-upload
   */
  controller("macUploadFilesController", [
    "$scope",
    "$attrs",
    "$parse",
    function($scope, attrs, $parse) {
      var filesGet, filesSet, ctrl = {};

      filesGet = $parse(attrs.macUploadFiles);
      // Similar to how AngularJS handle unassignable variable
      filesSet = filesGet.assign || function() {
        throw new Error(attrs.macUploadFiles + "used with mac-upload is non-assignable");
      };

      ctrl.files = filesGet($scope);

      /**
       * @name getFileByFilename
       * @function
       * @description
       * Get the file in the file list
       * @param {String} filename File name
       */
      ctrl.getFileByFilename = function(filename) {
        var i;

        for (i = ctrl.files.length - 1; i >= 0 ; i--) {
          if (ctrl.files[i].name === filename) {
            return ctrl.files[i];
          }
        }
        return null;
      };

      return ctrl;
    }
  ]).

  directive("macUploadFiles", [
    function() {
      return {
        restrict: "A",
        require: ["macUpload", "macUploadFiles"],
        controller: "macUploadFilesController",
        link: function($scope, element, attrs, ctrls) {
          var uploadCtrl = ctrls[0],
              filesCtrl = ctrls[1],
              adder = function($data) {
                var method = uploadCtrl.uploadOption("prependFiles") ?
                  "unshift" : "push";

                Array.prototype[method].apply(filesCtrl.files, $data.files);
              };

          uploadCtrl.adders.push(adder);
        }
      };
    }
  ]).

  /**
   * @name mac-upload-options directive
   * @description
   * Set additional fileupload options
   */
  directive("macUploadOptions", [
    function() {
      return {
        restrict: "A",
        require: "macUpload",
        priority: 100,
        link: function($scope, element, attrs, ctrls) {
          // Fail silently if options is not defined
          if (!attrs.macUploadOptions) {
            return;
          }

          $scope.$watch(attrs.macUploadOptions, function(newOptions) {
            element.fileupload("option", newOptions);
          }, true);
        }
      };
    }
  ]).

  /**
   * @name mac-upload-drop-zone directive
   * @description
   * Adding dropzone support
   * @param {String} mac-upload-drop-zone CSS selector
   */
  directive("macUploadDropZone", [
    "$document",
    "$timeout",
    "macUpload",
    function($document, $timeout, macUpload) {
      return {
        restrict: "A",
        require: ["macUpload"],
        priority: 100,
        link: function($scope, element, attrs, ctrls) {
          var preventDefault, dragoverCallback, dragoverTimeout, dropZone,
              selector;

          if (!attrs.macUploadDropZone) {
            throw new Error("mac-upload-drop-zone missing selector");
          }

          selector = attrs.macUploadDropZone;
          dropZone = angular.element(selector);

          /**
           * @name preventDefault
           * @function
           * @description
           * Callback for drop and dragover to disable default behavior
           * @param {Event} event jQuery event
           */
          preventDefault = function(event) {
            event.preventDefault();
          };

          /**
           * @name dragoverCallback
           * @function
           * @description
           * Callback for drag zone dragover event
           * @param {Event} event jQuery event
           */
          dragoverCallback = function(event) {
            var node, target, className = macUpload.defaults.dragoverClass;

            if (dragoverTimeout !== null) {
              $timeout.cancel(dragoverTimeout);
            }

            target = angular.element(event.target);
            node = target.is(dropZone) ? dropZone : target.parents(selector);
            if (node.length > 0) {
              dropZone.addClass(className);

              dragoverTimeout = $timeout(function() {
                dropZone.removeClass(className);
              }, 150, false);
            }
          };

          $document.bind("drop dragover", preventDefault);
          dropZone.bind("dragover", dragoverCallback);

          // Remove all bindings when fileupload element is removed
          $scope.$on("$destroy", function() {
            $document.unbind("drop dragover", preventDefault);
            dropZone.unbind("dragover", dragoverCallback);
          });

          // Initialize dropZone support on jQuery file upload
          element.fileupload("option", "dropZone", dropZone);
        }
      };
    }
  ]);

})(window, document);
<|endoftext|>


<commit_before>apiurl = "api.py";
loadedtransactions = [];
showing = -1;
editedfields = []

function loginsuccess() {
  $("#login").hide();
  $("#bottomlinks").show();
  loadaccounts();
  loadtransactions();
}

function clearpage() {
  $("#login").show();
  $("#transactions").hide();
  $("#transactiondetail").hide();
  $("#accounts").hide();
  $("#bottomlinks").hide();

  $.ajax({
    type: "POST",
    url: apiurl,
    data: { "action": "checklogin" },
    success: function(data) {
      if (data) {
        loginsuccess();
      } else {
        $("#login").show();
      }
    },
    error: function() {
      $("#login").show();
    }
  });
}

function decoratedollar() {
  if ($(this).text().substring(0,1) == "$")
    return;
  amount = parseInt($(this).text());
  if (amount > 0)
    $(this).addClass("posnum");
  $(this).text("$"+Math.abs(amount/100).toFixed(2));
}

function loadaccounts() {
  $.ajax({
    type: "POST",
    url: apiurl,
    data: { "action": "accounts" },
    success: function(data) {
      for (i in data) {
        for (j in data[i]["subaccounts"]) {
          $("#accountstablebody").append("
"+data[i]["name"]+"	"+data[i]["subaccounts"][j]["name"]+"	"+data[i]["subaccounts"][j]["amount"]+"	"+data[i]["subaccounts"][j]["date"]+"
");
        }
      }
      $(".dollar").each(decoratedollar);
      $("#accounts").show();
    },
    error: function() {
      alert("Error loading accounts");
    }
  });
}

function savetransaction() {
  if (editedfields.length > 0) {
    //alert("Save " + editedfields.join() + " for " + loadedtransactions[showing]["id"]);
    updatejson = new Object();
    for (f in editedfields)
      updatejson[editedfields[f]] = $("#transactiondetail > #"+editedfields[f]).text();
    $.ajax({
      type: "POST",
      url: apiurl,
      data: { "action": "updatetransaction", "id": loadedtransactions[showing]["id"], "data" : JSON.stringify(updatejson) },
      success: function(data) {
        if (data) {
          showtransaction(showing);
          loadtransactions();
        } else {
          alert("Error saving transaction");
        }
      },
      error: function() {
        alert("HTTP Error saving transaction");
      }
    });
  }
}

function showtransaction(t) {
  if (showing == t) {
    $("#transactiondetail").hide();
    showing = -1;
    return;
  }
  showing = t;
  editedfields = [];
  $("#transactiondetail > #file").hide();
  $("#transactiondetail > div").each(function () {
    name = $(this).attr("id");
    $("#transactiondetail > #"+name).text();
    if (loadedtransactions[t][name] != undefined)
      $("#transactiondetail > #"+name).text(loadedtransactions[t][name]);
  });
  if (loadedtransactions[t]["file"] != undefined) {
    $("#transactiondetail > #file").attr("src",apiurl+"?image="+loadedtransactions[t]["id"]);
    $("#transactiondetail > #file").show();
  }
  $(".dollar").each(decoratedollar);
  $("#transactiondetail").show();
}

function loadtransactions() {
  $("#transtablebody").html("");
  $.ajax({
    type: "POST",
    url: apiurl,
    data: { "action": "search", "limit": 25, "query": JSON.stringify({"amount": "$ne:0" }) },
    success: function(data) {
      total = 0;
      loadedtransactions = data;
      for (t=0; t<data.length; t++) {
        $("#transtablebody").append("
"+data[t]["date"]+"	"+data[t]["desc"]+"	"+data[t]["amount"]+"
\n");
        total += data[t]["amount"];
      }
      $("#transactionsum").text(total);
      $(".dollar").each(decoratedollar);
      $(".transaction").click(function() {
        showtransaction(parseInt($(this).attr("id").substring(5)));
      });
      $("#transactions").show();
    },
    error: function() {
      alert("Transaction loading error");
    }
  });
}

$(document).ready(function () {
  $('[contenteditable]').live('focus', function() {
    var $this = $(this);
    $this.data('before', $this.text());
      return $this;
  }).live('blur keyup paste', function() {
    var $this = $(this);
    if ($this.data('before') !== $this.text()) {
      $this.data('before', $this.text());
      if (editedfields.indexOf($(this).attr("id")) == -1)
        editedfields.push($(this).attr("id"));
    }
    return $this;
  });

  $("#transactiondetail > #save").click(savetransaction);
  $("#transactiondetail > #dateselect").datepicker({
    dateFormat: "yy-mm-dd",
    altField: "#transactiondetail > #date",
    onSelect: function(dateText, inst) {
      $("#transactiondetail > #date").text(dateText);
      if (editedfields.indexOf("date") == -1)
        editedfields.push("date");
    }
  });
  $("#transactiondetail > #date").click(function() {
    $("#transactiondetail > #dateselect").datepicker("show");
  });

  $("#logout").click(function(event) {
    $.ajax({
      type: "POST",
      url: apiurl,
      data: { "action": "logout" },
      success: function() {
        clearpage();
      },
      error: function() {
        clearpage();
      }
    });
  });

  $("#loginsubmit").click(function () {
    $.ajax({
      type: "POST",
      url: apiurl,
      data: { "action": "login", "username": $("#username").val(), "password": $("#password").val() },
      success: function(data) {
        if (data) {
          loginsuccess();
        } else {
          alert("Login Failed");
        }
      },
      error: function() {
        alert("Login Error");
      }
    });
  });

  clearpage();

});
<commit_msg>Don't flash login box if not needed.<commit_after>apiurl = "api.py";
loadedtransactions = [];
showing = -1;
editedfields = []

function loginsuccess() {
  $("#login").hide();
  $("#bottomlinks").show();
  loadaccounts();
  loadtransactions();
}

function clearpage() {
  $("#login").hide();
  $("#transactions").hide();
  $("#transactiondetail").hide();
  $("#accounts").hide();
  $("#bottomlinks").hide();

  $.ajax({
    type: "POST",
    url: apiurl,
    data: { "action": "checklogin" },
    success: function(data) {
      if (data) {
        loginsuccess();
      } else {
        $("#login").show();
      }
    },
    error: function() {
      $("#login").show();
    }
  });
}

function decoratedollar() {
  if ($(this).text().substring(0,1) == "$")
    return;
  amount = parseInt($(this).text());
  if (amount > 0)
    $(this).addClass("posnum");
  $(this).text("$"+Math.abs(amount/100).toFixed(2));
}

function loadaccounts() {
  $.ajax({
    type: "POST",
    url: apiurl,
    data: { "action": "accounts" },
    success: function(data) {
      for (i in data) {
        for (j in data[i]["subaccounts"]) {
          $("#accountstablebody").append("
"+data[i]["name"]+"	"+data[i]["subaccounts"][j]["name"]+"	"+data[i]["subaccounts"][j]["amount"]+"	"+data[i]["subaccounts"][j]["date"]+"
");
        }
      }
      $(".dollar").each(decoratedollar);
      $("#accounts").show();
    },
    error: function() {
      alert("Error loading accounts");
    }
  });
}

function savetransaction() {
  if (editedfields.length > 0) {
    //alert("Save " + editedfields.join() + " for " + loadedtransactions[showing]["id"]);
    updatejson = new Object();
    for (f in editedfields)
      updatejson[editedfields[f]] = $("#transactiondetail > #"+editedfields[f]).text();
    $.ajax({
      type: "POST",
      url: apiurl,
      data: { "action": "updatetransaction", "id": loadedtransactions[showing]["id"], "data" : JSON.stringify(updatejson) },
      success: function(data) {
        if (data) {
          showtransaction(showing);
          loadtransactions();
        } else {
          alert("Error saving transaction");
        }
      },
      error: function() {
        alert("HTTP Error saving transaction");
      }
    });
  }
}

function showtransaction(t) {
  if (showing == t) {
    $("#transactiondetail").hide();
    showing = -1;
    return;
  }
  showing = t;
  editedfields = [];
  $("#transactiondetail > #file").hide();
  $("#transactiondetail > div").each(function () {
    name = $(this).attr("id");
    $("#transactiondetail > #"+name).text();
    if (loadedtransactions[t][name] != undefined)
      $("#transactiondetail > #"+name).text(loadedtransactions[t][name]);
  });
  if (loadedtransactions[t]["file"] != undefined) {
    $("#transactiondetail > #file").attr("src",apiurl+"?image="+loadedtransactions[t]["id"]);
    $("#transactiondetail > #file").show();
  }
  $(".dollar").each(decoratedollar);
  $("#transactiondetail").show();
}

function loadtransactions() {
  $("#transtablebody").html("");
  $.ajax({
    type: "POST",
    url: apiurl,
    data: { "action": "search", "limit": 25, "query": JSON.stringify({"amount": "$ne:0" }) },
    success: function(data) {
      total = 0;
      loadedtransactions = data;
      for (t=0; t<data.length; t++) {
        $("#transtablebody").append("
"+data[t]["date"]+"	"+data[t]["desc"]+"	"+data[t]["amount"]+"
\n");
        total += data[t]["amount"];
      }
      $("#transactionsum").text(total);
      $(".dollar").each(decoratedollar);
      $(".transaction").click(function() {
        showtransaction(parseInt($(this).attr("id").substring(5)));
      });
      $("#transactions").show();
    },
    error: function() {
      alert("Transaction loading error");
    }
  });
}

$(document).ready(function () {
  $('[contenteditable]').live('focus', function() {
    var $this = $(this);
    $this.data('before', $this.text());
      return $this;
  }).live('blur keyup paste', function() {
    var $this = $(this);
    if ($this.data('before') !== $this.text()) {
      $this.data('before', $this.text());
      if (editedfields.indexOf($(this).attr("id")) == -1)
        editedfields.push($(this).attr("id"));
    }
    return $this;
  });

  $("#transactiondetail > #save").click(savetransaction);
  $("#transactiondetail > #dateselect").datepicker({
    dateFormat: "yy-mm-dd",
    altField: "#transactiondetail > #date",
    onSelect: function(dateText, inst) {
      $("#transactiondetail > #date").text(dateText);
      if (editedfields.indexOf("date") == -1)
        editedfields.push("date");
    }
  });
  $("#transactiondetail > #date").click(function() {
    $("#transactiondetail > #dateselect").datepicker("show");
  });

  $("#logout").click(function(event) {
    $.ajax({
      type: "POST",
      url: apiurl,
      data: { "action": "logout" },
      success: function() {
        clearpage();
      },
      error: function() {
        clearpage();
      }
    });
  });

  $("#loginsubmit").click(function () {
    $.ajax({
      type: "POST",
      url: apiurl,
      data: { "action": "login", "username": $("#username").val(), "password": $("#password").val() },
      success: function(data) {
        if (data) {
          loginsuccess();
        } else {
          alert("Login Failed");
        }
      },
      error: function() {
        alert("Login Error");
      }
    });
  });

  clearpage();

});
<|endoftext|>