<commit_before>var SSI = SSI || {};

SSI.Universe = function(options, container) {
    var controller = new SSI.UniverseController({});
    var core = new SSI.Core3D(container);
    var objectLibrary = new SSI.ObjectLibrary();

    // constants
    var earthSphereRadius = 6371;

    // options

    var currentUniverseTime = options.currentUniverseTime;
    var playbackSpeed = 1;

    // function to call when we have a new state object
    var stateChangedCallback = function() {
    };
    // milliseconds between updating our state object that we broadcast
    // to any listeners
    var timeBetweenStateUpdatesMs = 1000;

    // timeout for updating state
    var updateStateTimeout;

    var isShowOrbitLines = true;

    controller.addGraphicsObject({
        id : "simState",
        objectName : "simState",
        update : function(elapsedTime) {
            console.log("current playback speed: " + playbackSpeed);
            currentUniverseTime.setTime(currentUniverseTime.getTime() + playbackSpeed * elapsedTime);
        },
        draw : function() {
        }
    });

    // fires a state changed event to the callback
    function fireStateChanged(state) {
        if(stateChangedCallback != null) {
            stateChangedCallback(state);
        }
    }

    this.updateState = function() {
        // create our state object and notify our listener
        var universe = this;
        var state = {};
        state.currentUniverseTime = new Date(currentUniverseTime);

        fireStateChanged(state);

        // call update() again in a certain number of milliseconds
        updateStateTimeout = setTimeout(function() {
            universe.updateState();
        }, timeBetweenStateUpdatesMs);
    }
    // play the universe
    this.play = function(options) {
        currentUniverseTime = new Date(options.startTime);
        playbackSpeed = options.playbackSpeed;
        stateChangedCallback = options.stateChangedCallback;
        logger.debug("Universe.play() called with time [" + currentUniverseTime + "], speed: [" + playbackSpeed + "]");

        // update state our first time
        this.updateState();

        controller.play();
    };
    // pause the universe
    this.pause = function() {
        clearTimeout(updateStateTimeout);
        controller.pause();
    };
    this.setPlaybackSpeed = function(speed) {
        playbackSpeed = speed;
    }
    // earthOptions:
    // image
    //
    this.addEarth = function(earthOptions) {
        var earthSphereSegments = 40, earthSphereRings = 30;

        // Create the sphere
        var geometry = new THREE.SphereGeometry(earthSphereRadius, earthSphereSegments, earthSphereRings);

        // Define the material to be used for the sphere surface by pulling the image and wrapping it around the sphere
        var shader = {
            uniforms : {
                'texture' : {
                    type : 't',
                    value : 0,
                    texture : null
                }
            },
            vertexShader : ['varying vec3 vNormal;', 'varying vec2 vUv;', 'void main() {', 'gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );', 'vNormal = normalize( normalMatrix * normal );', 'vUv = uv;', '}'].join('\n'),
            fragmentShader : ['uniform sampler2D texture;', 'varying vec3 vNormal;', 'varying vec2 vUv;', 'void main() {', 'vec3 diffuse = texture2D( texture, vUv ).xyz;', 'float intensity = 1.05 - dot( vNormal, vec3( 0.0, 0.0, 1.0 ) );', 'vec3 atmosphere = vec3( 1.0, 1.0, 1.0 ) * pow( intensity, 3.0 );', 'gl_FragColor = vec4( diffuse + atmosphere, 1.0 );', '}'].join('\n')
        };
        var uniforms = THREE.UniformsUtils.clone(shader.uniforms);

        uniforms['texture'].texture = THREE.ImageUtils.loadTexture(earthOptions.image);

        var material = new THREE.ShaderMaterial({
            uniforms : uniforms,
            vertexShader : shader.vertexShader,
            fragmentShader : shader.fragmentShader
        });

        var earthMesh = new THREE.Mesh(geometry, material);

        controller.addGraphicsObject({
            id : "earth",
            objectName : "earth",
            update : function(elapsedTime) {
                // TODO: retrieve earth rotation at a time and change rotation
                var rotationAngle = convertTimeToGMST(currentUniverseTime);
                earthMesh.rotation.y = rotationAngle * (2 * Math.PI / 360);
            },
            draw : function() {
                core.draw(this.id, earthMesh, false);
            }
        });
    };
    // adds a model to the universe with an ID and url to retrieve
    // the model's geometry
    this.addJsonMeshModel = function(modelId, modelUrl, material, callback) {
        logger.debug("Adding mesh model to universe; id: [" + modelId + "] url: [" + modelUrl + "], material: [" + material + "]");
        if (modelId != undefined){
            objectLibrary.addMeshObjectFromUrl(modelId, modelUrl, material, callback);
        } else {
            callback();
        }
    };

    // spaceObject:
    // id
    // stateVector
    //   time
    //   x, y, z
    // objectName
    // propogator
    // modelId
    // showPropogationLine
    // showGroundTrackPoint
    this.addSpaceObject = function(spaceObject) {
        var objectModel = objectLibrary.getObjectById(spaceObject.modelId);

        controller.addGraphicsObject({
            id : spaceObject.id,
            objectName : spaceObject.objectName,
            update : function(elapsedTime) {
                // need to pass a time to the propogator
                var location = eciTo3DCoordinates(spaceObject.propogator());
                objectModel.position.set(location.x, location.y, location.z);
            },
            draw : function() {
                core.draw(this.id, objectModel, true);
            }
        });

        if(spaceObject.showPropogationLine == true) {
            this.addPropogationLineForObject(spaceObject);
        }

        if(spaceObject.showGroundTrackPoint) {
            this.addGroundTrackPointForObject(spaceObject);
        }
    };
    // groundObject:
    // id
    // propogator
    // object
    this.addGroundObject = function(groundObject) {
        var geometry = new THREE.SphereGeometry(100, 20, 10);

        var sphereMaterial = new THREE.MeshLambertMaterial({
            color : 0xCC0000
        });

        var groundObjectMesh = new THREE.Mesh(geometry, sphereMaterial);

        controller.addGraphicsObject({
            id : groundObject.id,
            objectName : groundObject.objectName,
            update : function(elapsedTime) {
                // check earth rotation and update location
                var position = eciTo3DCoordinates(groundObject.propogator());
                groundObjectMesh.position.set(position.x, position.y, position.z);
            },
            draw : function() {
                core.draw(this.id, groundObjectMesh, true);
            }
        });
    };
    // method to add an orbit line
    this.addPropogationLineForObject = function(object) {
        var geometry = new THREE.Geometry();

        var timeToPropogate = new Date(currentUniverseTime);

        // draw a vertex for each minute in a 24 hour period
        for(var j = 0; j < 1440; j++) {
            var location = eciTo3DCoordinates(object.propogator(timeToPropogate));
            var vector = new THREE.Vector3(location.x, location.y, location.z);
            geometry.vertices.push(new THREE.Vertex(vector));

            timeToPropogate.setMinutes(timeToPropogate.getMinutes() + 1);
        }
        var lineS = new THREE.Line(geometry, new THREE.LineBasicMaterial({
            color : 0x990000,
            opacity : 1
        }));

        controller.addGraphicsObject({
            id : object.id + "_propogation",
            objectName : object.objectName,
            update : function(elapsedTime) {
            // add points onto the end of the track?
            },
            draw : function() {
                core.draw(this.id, lineS, false);
            }
        })
    }

    this.addGroundTrackPointForObject = function(object) {
        var geometry = new THREE.SphereGeometry(300, 20, 10);

        var sphereMaterial = new THREE.MeshLambertMaterial({
            color : 0xCC0000
        });

        var groundObjectMesh = new THREE.Mesh(geometry, sphereMaterial);

        controller.addGraphicsObject({
            id : object.id + "_groundPoint",
            objectName : object.objectName,
            update : function(elapsedTime) {
                var objectLocation = eciTo3DCoordinates(object.propogator());

                var vector = new THREE.Vector3(objectLocation.x, objectLocation.y, objectLocation.z);

                // move the vector to the surface of the earth
                vector.multiplyScalar(earthSphereRadius / vector.length())

                // give it a random x and y position between -500 and 500
                groundObjectMesh.position.x = vector.x;
                groundObjectMesh.position.y = vector.y;
                groundObjectMesh.position.z = vector.z;

            },
            draw : function() {
                core.draw(this.id, groundObjectMesh, true);
            }
        })
    }

    this.showOrbitLines = function() {
        logger.debug("in show orbit lines");
        if (isShowOrbitLines) {
            logger.debug("hidding all orbit lines");
            core.hideObject("space_station_propogation");
            core.hideObject("dsp_propogation");
            core.hideObject("molniya_propogation");
            isShowOrbitLines = false;
        } else {
            logger.debug("showing all orbit lines");
            core.showObject("space_station_propogation");
            core.showObject("dsp_propogation");
            core.showObject("molniya_propogation");
            isShowOrbitLines = true;
        }
    }

    this.showVehicles = function() {
        logger.debug("in show vehicles");
        if (isShowOrbitLines) {
            logger.debug("hidding all vehicles");
            core.hideObject("space_station");
            core.hideObject("dsp");
            core.hideObject("molniya");
            isShowOrbitLines = false;
        } else {
            logger.debug("showing all vehicles");
            core.showObject("space_station");
            core.showObject("dsp");
            core.showObject("molniya");
            isShowOrbitLines = true;
        }
    }

    this.showGroundTracks = function() {
        logger.debug("in show ground tracks");
        if (isShowOrbitLines) {
            logger.debug("hidding all ground tracks");
            core.hideObject("space_station_groundPoint");
            core.hideObject("dsp_groundPoint");
            core.hideObject("molniya_groundPoint");
            isShowOrbitLines = false;
        } else {
            logger.debug("showing all ground tracks");
            core.showObject("space_station_groundPoint");
            core.showObject("dsp_groundPoint");
            core.showObject("molniya_groundPoint");
            isShowOrbitLines = true;
        }
    }

    this.addGroundTrackForObject = function(object) {
    // This needs to be written to take into account earth rotation
    }
    // Compare these two websites for details on why we have to do this:
    // http://celestrak.com/columns/v02n01/
    // http://stackoverflow.com/questions/7935209/three-js-3d-coordinates-system
    function eciTo3DCoordinates(location) {
        return {
            x : -location.x,
            y : location.z,
            z : location.y
        };
    }

    this.getCurrentUniverseTime = function() {
        return currentUniverseTime;
    }
};

SSI.Universe.prototype.goToTime = function(time) {
    };
// TODO refactor these to wherever Josh puts the common math libs
function convertCurrentEpochToJulianDate(time) {
    var JD = 0;
    var year = time.getFullYear();
    var month = time.getUTCMonth();
    var day = time.getUTCDate();
    var hour = time.getUTCHours();
    var minute = time.getUTCMinutes();
    var second = time.getUTCSeconds();
    JD = 367 * year - (7 * (year + ((month + 9) / 12)) / 4) + (275 * month / 9) + (day) + 1721013.5 + ((second / 60 + minute) / 60 + hour) / 24;
    return JD;
}

function convertTimeToGMST(time) {
    var JD = convertCurrentEpochToJulianDate(time);

    var TUT = (JD - 2451545.0) / 36525.0;
    //julian centuries since January 1, 2000 12h UT1
    var GMST = 67310.54841 + (876600.0 * 3600 + 8640184.812866) * TUT + 0.093104 * TUT * TUT - (0.0000062) * TUT * TUT * TUT;
    //this is in seconds

    var multiples = Math.floor(GMST / 86400.0);
    GMST = GMST - multiples * 86400.00;
    //reduce it to be within the range of one day

    GMST = GMST / 240.0;
    //convert to degrees
    if(GMST < 0) {
        GMST = GMST + 360;
    }
    return GMST;
//degrees
}<commit_msg>removed console log<commit_after>var SSI = SSI || {};

SSI.Universe = function(options, container) {
    var controller = new SSI.UniverseController({});
    var core = new SSI.Core3D(container);
    var objectLibrary = new SSI.ObjectLibrary();

    // constants
    var earthSphereRadius = 6371;

    // options

    var currentUniverseTime = options.currentUniverseTime;
    var playbackSpeed = 1;

    // function to call when we have a new state object
    var stateChangedCallback = function() {
    };
    // milliseconds between updating our state object that we broadcast
    // to any listeners
    var timeBetweenStateUpdatesMs = 1000;

    // timeout for updating state
    var updateStateTimeout;

    var isShowOrbitLines = true;

    controller.addGraphicsObject({
        id : "simState",
        objectName : "simState",
        update : function(elapsedTime) {
            currentUniverseTime.setTime(currentUniverseTime.getTime() + playbackSpeed * elapsedTime);
        },
        draw : function() {
        }
    });

    // fires a state changed event to the callback
    function fireStateChanged(state) {
        if(stateChangedCallback != null) {
            stateChangedCallback(state);
        }
    }

    this.updateState = function() {
        // create our state object and notify our listener
        var universe = this;
        var state = {};
        state.currentUniverseTime = new Date(currentUniverseTime);

        fireStateChanged(state);

        // call update() again in a certain number of milliseconds
        updateStateTimeout = setTimeout(function() {
            universe.updateState();
        }, timeBetweenStateUpdatesMs);
    }
    // play the universe
    this.play = function(options) {
        currentUniverseTime = new Date(options.startTime);
        playbackSpeed = options.playbackSpeed;
        stateChangedCallback = options.stateChangedCallback;
        logger.debug("Universe.play() called with time [" + currentUniverseTime + "], speed: [" + playbackSpeed + "]");

        // update state our first time
        this.updateState();

        controller.play();
    };
    // pause the universe
    this.pause = function() {
        clearTimeout(updateStateTimeout);
        controller.pause();
    };
    this.setPlaybackSpeed = function(speed) {
        playbackSpeed = speed;
    }
    // earthOptions:
    // image
    //
    this.addEarth = function(earthOptions) {
        var earthSphereSegments = 40, earthSphereRings = 30;

        // Create the sphere
        var geometry = new THREE.SphereGeometry(earthSphereRadius, earthSphereSegments, earthSphereRings);

        // Define the material to be used for the sphere surface by pulling the image and wrapping it around the sphere
        var shader = {
            uniforms : {
                'texture' : {
                    type : 't',
                    value : 0,
                    texture : null
                }
            },
            vertexShader : ['varying vec3 vNormal;', 'varying vec2 vUv;', 'void main() {', 'gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );', 'vNormal = normalize( normalMatrix * normal );', 'vUv = uv;', '}'].join('\n'),
            fragmentShader : ['uniform sampler2D texture;', 'varying vec3 vNormal;', 'varying vec2 vUv;', 'void main() {', 'vec3 diffuse = texture2D( texture, vUv ).xyz;', 'float intensity = 1.05 - dot( vNormal, vec3( 0.0, 0.0, 1.0 ) );', 'vec3 atmosphere = vec3( 1.0, 1.0, 1.0 ) * pow( intensity, 3.0 );', 'gl_FragColor = vec4( diffuse + atmosphere, 1.0 );', '}'].join('\n')
        };
        var uniforms = THREE.UniformsUtils.clone(shader.uniforms);

        uniforms['texture'].texture = THREE.ImageUtils.loadTexture(earthOptions.image);

        var material = new THREE.ShaderMaterial({
            uniforms : uniforms,
            vertexShader : shader.vertexShader,
            fragmentShader : shader.fragmentShader
        });

        var earthMesh = new THREE.Mesh(geometry, material);

        controller.addGraphicsObject({
            id : "earth",
            objectName : "earth",
            update : function(elapsedTime) {
                // TODO: retrieve earth rotation at a time and change rotation
                var rotationAngle = convertTimeToGMST(currentUniverseTime);
                earthMesh.rotation.y = rotationAngle * (2 * Math.PI / 360);
            },
            draw : function() {
                core.draw(this.id, earthMesh, false);
            }
        });
    };
    // adds a model to the universe with an ID and url to retrieve
    // the model's geometry
    this.addJsonMeshModel = function(modelId, modelUrl, material, callback) {
        logger.debug("Adding mesh model to universe; id: [" + modelId + "] url: [" + modelUrl + "], material: [" + material + "]");
        if (modelId != undefined){
            objectLibrary.addMeshObjectFromUrl(modelId, modelUrl, material, callback);
        } else {
            callback();
        }
    };

    // spaceObject:
    // id
    // stateVector
    //   time
    //   x, y, z
    // objectName
    // propogator
    // modelId
    // showPropogationLine
    // showGroundTrackPoint
    this.addSpaceObject = function(spaceObject) {
        var objectModel = objectLibrary.getObjectById(spaceObject.modelId);

        controller.addGraphicsObject({
            id : spaceObject.id,
            objectName : spaceObject.objectName,
            update : function(elapsedTime) {
                // need to pass a time to the propogator
                var location = eciTo3DCoordinates(spaceObject.propogator());
                objectModel.position.set(location.x, location.y, location.z);
            },
            draw : function() {
                core.draw(this.id, objectModel, true);
            }
        });

        if(spaceObject.showPropogationLine == true) {
            this.addPropogationLineForObject(spaceObject);
        }

        if(spaceObject.showGroundTrackPoint) {
            this.addGroundTrackPointForObject(spaceObject);
        }
    };
    // groundObject:
    // id
    // propogator
    // object
    this.addGroundObject = function(groundObject) {
        var geometry = new THREE.SphereGeometry(100, 20, 10);

        var sphereMaterial = new THREE.MeshLambertMaterial({
            color : 0xCC0000
        });

        var groundObjectMesh = new THREE.Mesh(geometry, sphereMaterial);

        controller.addGraphicsObject({
            id : groundObject.id,
            objectName : groundObject.objectName,
            update : function(elapsedTime) {
                // check earth rotation and update location
                var position = eciTo3DCoordinates(groundObject.propogator());
                groundObjectMesh.position.set(position.x, position.y, position.z);
            },
            draw : function() {
                core.draw(this.id, groundObjectMesh, true);
            }
        });
    };
    // method to add an orbit line
    this.addPropogationLineForObject = function(object) {
        var geometry = new THREE.Geometry();

        var timeToPropogate = new Date(currentUniverseTime);

        // draw a vertex for each minute in a 24 hour period
        for(var j = 0; j < 1440; j++) {
            var location = eciTo3DCoordinates(object.propogator(timeToPropogate));
            var vector = new THREE.Vector3(location.x, location.y, location.z);
            geometry.vertices.push(new THREE.Vertex(vector));

            timeToPropogate.setMinutes(timeToPropogate.getMinutes() + 1);
        }
        var lineS = new THREE.Line(geometry, new THREE.LineBasicMaterial({
            color : 0x990000,
            opacity : 1
        }));

        controller.addGraphicsObject({
            id : object.id + "_propogation",
            objectName : object.objectName,
            update : function(elapsedTime) {
            // add points onto the end of the track?
            },
            draw : function() {
                core.draw(this.id, lineS, false);
            }
        })
    }

    this.addGroundTrackPointForObject = function(object) {
        var geometry = new THREE.SphereGeometry(300, 20, 10);

        var sphereMaterial = new THREE.MeshLambertMaterial({
            color : 0xCC0000
        });

        var groundObjectMesh = new THREE.Mesh(geometry, sphereMaterial);

        controller.addGraphicsObject({
            id : object.id + "_groundPoint",
            objectName : object.objectName,
            update : function(elapsedTime) {
                var objectLocation = eciTo3DCoordinates(object.propogator());

                var vector = new THREE.Vector3(objectLocation.x, objectLocation.y, objectLocation.z);

                // move the vector to the surface of the earth
                vector.multiplyScalar(earthSphereRadius / vector.length())

                // give it a random x and y position between -500 and 500
                groundObjectMesh.position.x = vector.x;
                groundObjectMesh.position.y = vector.y;
                groundObjectMesh.position.z = vector.z;

            },
            draw : function() {
                core.draw(this.id, groundObjectMesh, true);
            }
        })
    }

    this.showOrbitLines = function() {
        logger.debug("in show orbit lines");
        if (isShowOrbitLines) {
            logger.debug("hidding all orbit lines");
            core.hideObject("space_station_propogation");
            core.hideObject("dsp_propogation");
            core.hideObject("molniya_propogation");
            isShowOrbitLines = false;
        } else {
            logger.debug("showing all orbit lines");
            core.showObject("space_station_propogation");
            core.showObject("dsp_propogation");
            core.showObject("molniya_propogation");
            isShowOrbitLines = true;
        }
    }

    this.showVehicles = function() {
        logger.debug("in show vehicles");
        if (isShowOrbitLines) {
            logger.debug("hidding all vehicles");
            core.hideObject("space_station");
            core.hideObject("dsp");
            core.hideObject("molniya");
            isShowOrbitLines = false;
        } else {
            logger.debug("showing all vehicles");
            core.showObject("space_station");
            core.showObject("dsp");
            core.showObject("molniya");
            isShowOrbitLines = true;
        }
    }

    this.showGroundTracks = function() {
        logger.debug("in show ground tracks");
        if (isShowOrbitLines) {
            logger.debug("hidding all ground tracks");
            core.hideObject("space_station_groundPoint");
            core.hideObject("dsp_groundPoint");
            core.hideObject("molniya_groundPoint");
            isShowOrbitLines = false;
        } else {
            logger.debug("showing all ground tracks");
            core.showObject("space_station_groundPoint");
            core.showObject("dsp_groundPoint");
            core.showObject("molniya_groundPoint");
            isShowOrbitLines = true;
        }
    }

    this.addGroundTrackForObject = function(object) {
    // This needs to be written to take into account earth rotation
    }
    // Compare these two websites for details on why we have to do this:
    // http://celestrak.com/columns/v02n01/
    // http://stackoverflow.com/questions/7935209/three-js-3d-coordinates-system
    function eciTo3DCoordinates(location) {
        return {
            x : -location.x,
            y : location.z,
            z : location.y
        };
    }

    this.getCurrentUniverseTime = function() {
        return currentUniverseTime;
    }
};

SSI.Universe.prototype.goToTime = function(time) {
    };
// TODO refactor these to wherever Josh puts the common math libs
function convertCurrentEpochToJulianDate(time) {
    var JD = 0;
    var year = time.getFullYear();
    var month = time.getUTCMonth();
    var day = time.getUTCDate();
    var hour = time.getUTCHours();
    var minute = time.getUTCMinutes();
    var second = time.getUTCSeconds();
    JD = 367 * year - (7 * (year + ((month + 9) / 12)) / 4) + (275 * month / 9) + (day) + 1721013.5 + ((second / 60 + minute) / 60 + hour) / 24;
    return JD;
}

function convertTimeToGMST(time) {
    var JD = convertCurrentEpochToJulianDate(time);

    var TUT = (JD - 2451545.0) / 36525.0;
    //julian centuries since January 1, 2000 12h UT1
    var GMST = 67310.54841 + (876600.0 * 3600 + 8640184.812866) * TUT + 0.093104 * TUT * TUT - (0.0000062) * TUT * TUT * TUT;
    //this is in seconds

    var multiples = Math.floor(GMST / 86400.0);
    GMST = GMST - multiples * 86400.00;
    //reduce it to be within the range of one day

    GMST = GMST / 240.0;
    //convert to degrees
    if(GMST < 0) {
        GMST = GMST + 360;
    }
    return GMST;
//degrees
}<|endoftext|>


<commit_before>import MetadataItem from './MetadataItem';
import { Set } from '../shims/SetOps.js';
let girder = window.girder;

let UserPreferences = MetadataItem.extend({
  /*
    resetToDefaults doesn't work unless defaults
    is a function. If defaults are a simple object,
    changes to the model mutate the defaults object.
  */
  defaults: () => {
    return {
      name: 'rra_preferences',
      description: `
Contains your preferences for the Resonant Laboratory application. If
you move or delete this item, your preferences will be lost.`,
      meta: {
        showHelp: false,
        seenTips: {},
        achievements: {}
      }
    };
  },
  initialize: function () {
    let seenTips = window.localStorage.getItem('seenTips');
    if (seenTips) {
      this.setMeta('seenTips', JSON.parse(seenTips));
    }
    let achievements = window.localStorage.getItem('achievements');
    if (achievements) {
      this.setMeta('achievements', JSON.parse(achievements));
    }
  },
  save: function () {
    return MetadataItem.prototype.save.apply(this, arguments)
      .catch(() => {
        // Fallback: store the user's preferences in localStorage
        let seenTips = this.getMeta('seenTips');
        if (seenTips) {
          window.localStorage.setItem('seenTips', JSON.stringify(seenTips));
        }
        let achievements = this.getMeta('achievements');
        if (achievements) {
          window.localStorage.setItem('achievements', JSON.stringify(achievements));
        }
      });
  },
  addListeners: function () {
    this.listenTo(window.mainPage.currentUser, 'rra:login',
      this.adoptScratchProjects);
    this.listenTo(window.mainPage, 'rra:createProject',
      this.claimProject);
  },
  claimProject: function () {
    if (!window.mainPage.currentUser.isLoggedIn()) {
      // Because we created this project while not logged in,
      // store the project's ID in window.localStorage so that we
      // can claim "ownership" when we log in / visit this page again
      // (of course, this is easy to hack, but that's the assumption
      // with public scratch space)
      let scratchProjects = window.localStorage.getItem('scratchProjects');
      if (!scratchProjects) {
        scratchProjects = [];
      } else {
        scratchProjects = JSON.parse(scratchProjects);
      }
      scratchProjects.push(window.mainPage.project.getId());
      window.localStorage.setItem('scratchProjects',
        JSON.stringify(scratchProjects));
    }
  },
  adoptScratchProjects: function () {
    if (!window.mainPage.currentUser.isLoggedIn()) {
      return;
    }
    // Attempt to adopt any projects that this browser
    // created in the public scratch space into the
    // now-logged-in user's Private folder

    let scratchProjects = window.localStorage.getItem('scratchProjects');

    if (scratchProjects) {
      new Promise((resolve, reject) => {
        girder.restRequest({
          path: 'item/adoptScratchItems',
          data: {
            'ids': scratchProjects // already JSON.stringified
          },
          error: reject,
          type: 'PUT'
        }).done(resolve).error(reject);
      }).then(successfulAdoptions => {
        // Now we need to adopt any datasets that these projects refer to
        let datasetIds = new Set();
        successfulAdoptions.forEach(adoptedProject => {
          if (adoptedProject.meta && adoptedProject.meta.datasets) {
            adoptedProject.meta.datasets.forEach(datasetId => {
              datasetIds.add(datasetId);
            });
          }
        });

        new Promise((resolve, reject) => {
          girder.restRequest({
            path: 'item/adoptScratchItems',
            data: {
              'ids': JSON.stringify([...datasetIds])
            },
            error: reject,
            type: 'PUT'
          });
        }).catch(() => {
          // For now, silently ignore failures to adopt datasets
          window.localStorage.clear('scratchProjects');
        }).then(() => {
          window.mainPage.currentUser.trigger('rra:updateLibrary');
          // In addition to changing the user's library, the current
          // project will (pretty much always) have just changed
          // as well
          if (window.mainPage.project) {
            window.mainPage.project.updateStatus();
          }
        });
      }).catch(() => {
        // For now, silently ignore failures to adopt projects
        window.localStorage.clear('scratchProjects');
      });
    }
  },
  hasSeenAllTips: function (tips) {
    let seenTips = this.getMeta('seenTips');
    for (let selector of Object.keys(tips)) {
      let tipId = selector; // + tips[selector];
      // Make the id a valid / nice mongo id
      tipId = tipId.replace(/[^a-zA-Z\d]/g, '').toLowerCase();
      if (seenTips[tipId] !== true) {
        return false;
      }
    }
    return true;
  },
  observeTips: function (tips) {
    let seenTips = this.getMeta('seenTips');
    for (let selector of Object.keys(tips)) {
      let tipId = tips[selector].tipId;
      // Make the id a valid / nice mongo id
      tipId = tipId.replace(/[^a-zA-Z\d]/g, '').toLowerCase();
      seenTips[tipId] = true;
    }
    this.setMeta('seenTips', seenTips);
    this.trigger('rra:observeTips');
    this.save();
  },
  resetToDefaults: function () {
    // The user has logged out, or some other authentication
    // problem is going on. This sets the app to the initial
    // empty state as if no one is logged in
    this.clear({
      silent: true
    });
    this.set(this.defaults());
    window.localStorage.removeItem('seenTips');
    window.localStorage.removeItem('achievements');
    window.localStorage.removeItem('scratchProjects');
  },
  levelUp: function (achievement) {
    let achievements = this.getMeta('achievements');
    if (achievements[achievement] !== true) {
      achievements[achievement] = true;
      this.setMeta('achievements', achievements);
      this.save().catch(() => {
      }); // fail silently
      this.trigger('rra:levelUp');
    }
  }
});

export default UserPreferences;
<commit_msg>Change name of prefs object - Fixes #173<commit_after>import MetadataItem from './MetadataItem';
import { Set } from '../shims/SetOps.js';
let girder = window.girder;

let UserPreferences = MetadataItem.extend({
  /*
    resetToDefaults doesn't work unless defaults
    is a function. If defaults are a simple object,
    changes to the model mutate the defaults object.
  */
  defaults: () => {
    return {
      name: 'rl_preferences',
      description: `
Contains your preferences for the Resonant Laboratory application. If
you move or delete this item, your preferences will be lost.`,
      meta: {
        showHelp: false,
        seenTips: {},
        achievements: {}
      }
    };
  },
  initialize: function () {
    let seenTips = window.localStorage.getItem('seenTips');
    if (seenTips) {
      this.setMeta('seenTips', JSON.parse(seenTips));
    }
    let achievements = window.localStorage.getItem('achievements');
    if (achievements) {
      this.setMeta('achievements', JSON.parse(achievements));
    }
  },
  save: function () {
    return MetadataItem.prototype.save.apply(this, arguments)
      .catch(() => {
        // Fallback: store the user's preferences in localStorage
        let seenTips = this.getMeta('seenTips');
        if (seenTips) {
          window.localStorage.setItem('seenTips', JSON.stringify(seenTips));
        }
        let achievements = this.getMeta('achievements');
        if (achievements) {
          window.localStorage.setItem('achievements', JSON.stringify(achievements));
        }
      });
  },
  addListeners: function () {
    this.listenTo(window.mainPage.currentUser, 'rra:login',
      this.adoptScratchProjects);
    this.listenTo(window.mainPage, 'rra:createProject',
      this.claimProject);
  },
  claimProject: function () {
    if (!window.mainPage.currentUser.isLoggedIn()) {
      // Because we created this project while not logged in,
      // store the project's ID in window.localStorage so that we
      // can claim "ownership" when we log in / visit this page again
      // (of course, this is easy to hack, but that's the assumption
      // with public scratch space)
      let scratchProjects = window.localStorage.getItem('scratchProjects');
      if (!scratchProjects) {
        scratchProjects = [];
      } else {
        scratchProjects = JSON.parse(scratchProjects);
      }
      scratchProjects.push(window.mainPage.project.getId());
      window.localStorage.setItem('scratchProjects',
        JSON.stringify(scratchProjects));
    }
  },
  adoptScratchProjects: function () {
    if (!window.mainPage.currentUser.isLoggedIn()) {
      return;
    }
    // Attempt to adopt any projects that this browser
    // created in the public scratch space into the
    // now-logged-in user's Private folder

    let scratchProjects = window.localStorage.getItem('scratchProjects');

    if (scratchProjects) {
      new Promise((resolve, reject) => {
        girder.restRequest({
          path: 'item/adoptScratchItems',
          data: {
            'ids': scratchProjects // already JSON.stringified
          },
          error: reject,
          type: 'PUT'
        }).done(resolve).error(reject);
      }).then(successfulAdoptions => {
        // Now we need to adopt any datasets that these projects refer to
        let datasetIds = new Set();
        successfulAdoptions.forEach(adoptedProject => {
          if (adoptedProject.meta && adoptedProject.meta.datasets) {
            adoptedProject.meta.datasets.forEach(datasetId => {
              datasetIds.add(datasetId);
            });
          }
        });

        new Promise((resolve, reject) => {
          girder.restRequest({
            path: 'item/adoptScratchItems',
            data: {
              'ids': JSON.stringify([...datasetIds])
            },
            error: reject,
            type: 'PUT'
          });
        }).catch(() => {
          // For now, silently ignore failures to adopt datasets
          window.localStorage.clear('scratchProjects');
        }).then(() => {
          window.mainPage.currentUser.trigger('rra:updateLibrary');
          // In addition to changing the user's library, the current
          // project will (pretty much always) have just changed
          // as well
          if (window.mainPage.project) {
            window.mainPage.project.updateStatus();
          }
        });
      }).catch(() => {
        // For now, silently ignore failures to adopt projects
        window.localStorage.clear('scratchProjects');
      });
    }
  },
  hasSeenAllTips: function (tips) {
    let seenTips = this.getMeta('seenTips');
    for (let selector of Object.keys(tips)) {
      let tipId = selector; // + tips[selector];
      // Make the id a valid / nice mongo id
      tipId = tipId.replace(/[^a-zA-Z\d]/g, '').toLowerCase();
      if (seenTips[tipId] !== true) {
        return false;
      }
    }
    return true;
  },
  observeTips: function (tips) {
    let seenTips = this.getMeta('seenTips');
    for (let selector of Object.keys(tips)) {
      let tipId = tips[selector].tipId;
      // Make the id a valid / nice mongo id
      tipId = tipId.replace(/[^a-zA-Z\d]/g, '').toLowerCase();
      seenTips[tipId] = true;
    }
    this.setMeta('seenTips', seenTips);
    this.trigger('rra:observeTips');
    this.save();
  },
  resetToDefaults: function () {
    // The user has logged out, or some other authentication
    // problem is going on. This sets the app to the initial
    // empty state as if no one is logged in
    this.clear({
      silent: true
    });
    this.set(this.defaults());
    window.localStorage.removeItem('seenTips');
    window.localStorage.removeItem('achievements');
    window.localStorage.removeItem('scratchProjects');
  },
  levelUp: function (achievement) {
    let achievements = this.getMeta('achievements');
    if (achievements[achievement] !== true) {
      achievements[achievement] = true;
      this.setMeta('achievements', achievements);
      this.save().catch(() => {
      }); // fail silently
      this.trigger('rra:levelUp');
    }
  }
});

export default UserPreferences;
<|endoftext|>


<commit_before>// We have to remove node_modules/react to avoid having multiple copies loaded.
// eslint-disable-next-line import/no-unresolved
import React, { PropTypes } from 'react';
import Paneset from '@folio/stripes-components/lib/Paneset';
import Pane from '@folio/stripes-components/lib/Pane';
import PaneMenu from '@folio/stripes-components/lib/PaneMenu';
import { Row, Col } from 'react-bootstrap';
import Button from '@folio/stripes-components/lib/Button';
import TextField from '@folio/stripes-components/lib/TextField';
import Select from '@folio/stripes-components/lib/Select';
import RadioButtonGroup from '@folio/stripes-components/lib/RadioButtonGroup';
import RadioButton from '@folio/stripes-components/lib/RadioButton';
import fetch from 'isomorphic-fetch';

import { Field, reduxForm } from 'redux-form';

const sys = require('stripes-loader'); // eslint-disable-line
const okapiUrl = sys.okapi.url;
const tenant = sys.okapi.tenant;
let okapiToken = '';

function validate(values) {
  const errors = {};

  if (formProps.personal && !formProps.personal.last_name) {
  if (values.personal && !values.personal.last_name) {
    errors.personal = { last_name: 'Please fill this in to continue' };
  }

  if (!values.username) {
    errors.username = 'Please fill this in to continue';
  }
  return errors;
}

function asyncValidate(values, dispatch, props) {
  return new Promise((resolve, reject) => {
    fetch(`${okapiUrl}/users?query=(username="${values.username}")`,
      { headers: Object.assign({}, {
        'X-Okapi-Tenant': tenant,
        'X-Okapi-Token': okapiToken,
        'Content-Type': 'application/json' }),
      },
    ).then((response) => {
      if (response.status >= 400) {
        console.log('Error fetching user');
      } else {
        response.json().then((json) => {
          if (json.total_records > 0)
            reject({ username: 'This User ID has already been taken' });
          else
            resolve();
        });
      }
    });
  });
}

class UserForm extends React.Component {

  static contextTypes = {
    store: PropTypes.object,
  };

  static propTypes = {
    onClose: PropTypes.func, // eslint-disable-line react/no-unused-prop-types
    newUser: PropTypes.bool, // eslint-disable-line react/no-unused-prop-types
    handleSubmit: PropTypes.func.isRequired,
    reset: PropTypes.func,
    pristine: PropTypes.bool,
    submitting: PropTypes.bool,
    onCancel: PropTypes.func,
    initialValues: PropTypes.object,
  };

  constructor(props, context) {
    super(props);
    okapiToken = context.store.getState().okapi.token;
  }

  render() {
    const {
      handleSubmit,
      reset,  // eslint-disable-line no-unused-vars
      pristine,
      submitting,
      onCancel,
      initialValues,
    } = this.props;

    /* Menues for Add User workflow */
    const addUserFirstMenu = <PaneMenu><button onClick={onCancel} title="close" aria-label="Close New User Dialog">×</button></PaneMenu>;
    const addUserLastMenu = <PaneMenu><Button type="submit" title="Create New User" disabled={pristine || submitting} onClick={handleSubmit}>Create User</Button></PaneMenu>;
    const editUserLastMenu = <PaneMenu><Button type="submit" title="Update User" disabled={pristine || submitting} onClick={handleSubmit}>Update User</Button></PaneMenu>;
    const patronGroupOptions = initialValues.available_patron_groups ? initialValues.available_patron_groups.map((g) => { return { label: g.group, value: g._id, selected: initialValues.patron_group === g._id }; }) : [];

    return (
      <form>
        <Paneset>
          <Pane defaultWidth="100%" firstMenu={addUserFirstMenu} lastMenu={initialValues.username ? editUserLastMenu : addUserLastMenu} paneTitle={initialValues.username ? 'Edit User' : 'New User'}>
            <Row>
              <Col sm={5} smOffset={1}>

User Record

                <Field label="UserName" name="username" id="adduser_username" component={TextField} required fullWidth />
                {!initialValues.id ? <Field label="Password" name="creds.password" id="pw" component={TextField} required fullWidth /> : null}
                <Field label="Status" name="active" component={RadioButtonGroup}>
                  <RadioButton label="Active" id="useractiveYesRB" value="true" inline />
                  <RadioButton label="Inactive" id="useractiveNoRB" value="false" inline />
                </Field>
                <fieldset>
                  <legend>Personal Info</legend>
                  <Field label="First Name" name="personal.first_name" id="adduser_firstname" component={TextField} required fullWidth />
                  <Field label="Last Name" name="personal.last_name" id="adduser_lastname" component={TextField} fullWidth />
                  <Field label="Email" name="personal.email" id="adduser_email" component={TextField} required fullWidth />
                </fieldset>
                {/* <Field
                  label="Type"
                  name="type"
                  id="adduser_type"
                  component={Select}
                  fullWidth
                  dataOptions={[{ label: 'Select user type', value: '' },
                                { label: 'Patron', value: 'Patron', selected: 'selected' }]}
                /> */}
                <Field
                  label="Patron Group"
                  name="patron_group"
                  id="adduser_group"
                  component={Select}
                  fullWidth
                  dataOptions={[{ label: 'Select patron group', value: null }, ...patronGroupOptions]}
                />
              </Col>
            </Row>
          </Pane>

        </Paneset>
      </form>
    );
  }
}

export default reduxForm({
  form: 'userForm',
  validate,
  asyncValidate,
})(UserForm);
<commit_msg>Remove incorrectly-resolved conflict<commit_after>// We have to remove node_modules/react to avoid having multiple copies loaded.
// eslint-disable-next-line import/no-unresolved
import React, { PropTypes } from 'react';
import Paneset from '@folio/stripes-components/lib/Paneset';
import Pane from '@folio/stripes-components/lib/Pane';
import PaneMenu from '@folio/stripes-components/lib/PaneMenu';
import { Row, Col } from 'react-bootstrap';
import Button from '@folio/stripes-components/lib/Button';
import TextField from '@folio/stripes-components/lib/TextField';
import Select from '@folio/stripes-components/lib/Select';
import RadioButtonGroup from '@folio/stripes-components/lib/RadioButtonGroup';
import RadioButton from '@folio/stripes-components/lib/RadioButton';
import fetch from 'isomorphic-fetch';

import { Field, reduxForm } from 'redux-form';

const sys = require('stripes-loader'); // eslint-disable-line
const okapiUrl = sys.okapi.url;
const tenant = sys.okapi.tenant;
let okapiToken = '';

function validate(values) {
  const errors = {};

  if (values.personal && !values.personal.last_name) {
    errors.personal = { last_name: 'Please fill this in to continue' };
  }

  if (!values.username) {
    errors.username = 'Please fill this in to continue';
  }
  return errors;
}

function asyncValidate(values, dispatch, props) {
  return new Promise((resolve, reject) => {
    fetch(`${okapiUrl}/users?query=(username="${values.username}")`,
      { headers: Object.assign({}, {
        'X-Okapi-Tenant': tenant,
        'X-Okapi-Token': okapiToken,
        'Content-Type': 'application/json' }),
      },
    ).then((response) => {
      if (response.status >= 400) {
        console.log('Error fetching user');
      } else {
        response.json().then((json) => {
          if (json.total_records > 0)
            reject({ username: 'This User ID has already been taken' });
          else
            resolve();
        });
      }
    });
  });
}

class UserForm extends React.Component {

  static contextTypes = {
    store: PropTypes.object,
  };

  static propTypes = {
    onClose: PropTypes.func, // eslint-disable-line react/no-unused-prop-types
    newUser: PropTypes.bool, // eslint-disable-line react/no-unused-prop-types
    handleSubmit: PropTypes.func.isRequired,
    reset: PropTypes.func,
    pristine: PropTypes.bool,
    submitting: PropTypes.bool,
    onCancel: PropTypes.func,
    initialValues: PropTypes.object,
  };

  constructor(props, context) {
    super(props);
    okapiToken = context.store.getState().okapi.token;
  }

  render() {
    const {
      handleSubmit,
      reset,  // eslint-disable-line no-unused-vars
      pristine,
      submitting,
      onCancel,
      initialValues,
    } = this.props;

    /* Menues for Add User workflow */
    const addUserFirstMenu = <PaneMenu><button onClick={onCancel} title="close" aria-label="Close New User Dialog">×</button></PaneMenu>;
    const addUserLastMenu = <PaneMenu><Button type="submit" title="Create New User" disabled={pristine || submitting} onClick={handleSubmit}>Create User</Button></PaneMenu>;
    const editUserLastMenu = <PaneMenu><Button type="submit" title="Update User" disabled={pristine || submitting} onClick={handleSubmit}>Update User</Button></PaneMenu>;
    const patronGroupOptions = initialValues.available_patron_groups ? initialValues.available_patron_groups.map((g) => { return { label: g.group, value: g._id, selected: initialValues.patron_group === g._id }; }) : [];

    return (
      <form>
        <Paneset>
          <Pane defaultWidth="100%" firstMenu={addUserFirstMenu} lastMenu={initialValues.username ? editUserLastMenu : addUserLastMenu} paneTitle={initialValues.username ? 'Edit User' : 'New User'}>
            <Row>
              <Col sm={5} smOffset={1}>

User Record

                <Field label="UserName" name="username" id="adduser_username" component={TextField} required fullWidth />
                {!initialValues.id ? <Field label="Password" name="creds.password" id="pw" component={TextField} required fullWidth /> : null}
                <Field label="Status" name="active" component={RadioButtonGroup}>
                  <RadioButton label="Active" id="useractiveYesRB" value="true" inline />
                  <RadioButton label="Inactive" id="useractiveNoRB" value="false" inline />
                </Field>
                <fieldset>
                  <legend>Personal Info</legend>
                  <Field label="First Name" name="personal.first_name" id="adduser_firstname" component={TextField} required fullWidth />
                  <Field label="Last Name" name="personal.last_name" id="adduser_lastname" component={TextField} fullWidth />
                  <Field label="Email" name="personal.email" id="adduser_email" component={TextField} required fullWidth />
                </fieldset>
                {/* <Field
                  label="Type"
                  name="type"
                  id="adduser_type"
                  component={Select}
                  fullWidth
                  dataOptions={[{ label: 'Select user type', value: '' },
                                { label: 'Patron', value: 'Patron', selected: 'selected' }]}
                /> */}
                <Field
                  label="Patron Group"
                  name="patron_group"
                  id="adduser_group"
                  component={Select}
                  fullWidth
                  dataOptions={[{ label: 'Select patron group', value: null }, ...patronGroupOptions]}
                />
              </Col>
            </Row>
          </Pane>

        </Paneset>
      </form>
    );
  }
}

export default reduxForm({
  form: 'userForm',
  validate,
  asyncValidate,
})(UserForm);
<|endoftext|>


<commit_before>import React from 'react';
import PropTypes from 'prop-types';
import Paneset from '@folio/stripes-components/lib/Paneset';
import Pane from '@folio/stripes-components/lib/Pane';
import PaneMenu from '@folio/stripes-components/lib/PaneMenu';
import { Row, Col } from 'react-bootstrap';
import Button from '@folio/stripes-components/lib/Button';
import TextField from '@folio/stripes-components/lib/TextField';
import Select from '@folio/stripes-components/lib/Select';
import RadioButtonGroup from '@folio/stripes-components/lib/RadioButtonGroup';
import RadioButton from '@folio/stripes-components/lib/RadioButton';
import Datepicker from '@folio/stripes-components/lib/Datepicker';
import AddressEditList from '@folio/stripes-components/lib/structures/AddressFieldGroup/AddressEdit/AddressEditList';
import fetch from 'isomorphic-fetch';
import { Field } from 'redux-form';
import stripesForm from '@folio/stripes-form';

import { countriesOptions } from './data/countries';
import Autocomplete from './lib/Autocomplete';
import { toAddressTypeOptions } from './converters/address_type';

const sys = require('stripes-loader'); // eslint-disable-line
const okapiUrl = sys.okapi.url;
const tenant = sys.okapi.tenant;
let okapiToken = '';

function validate(values) {
  const errors = {};

  if (!values.personal || !values.personal.lastName) {
    errors.personal = { lastName: 'Please fill this in to continue' };
  }

  if (!values.username) {
    errors.username = 'Please fill this in to continue';
  }

  if (!values.patronGroup) {
    errors.patronGroup = 'Please select a patron group';
  }

  if (!values.personal || !values.personal.preferredContactTypeId) {
    errors.personal = { preferredContactTypeId: 'Please select a preferred form of contact' };
  }
  return errors;
}

function checkUniqueUserID(username) {
  return fetch(`${okapiUrl}/users?query=(username="${username}")`,
    { headers: Object.assign({}, {
      'X-Okapi-Tenant': tenant,
      'X-Okapi-Token': okapiToken,
      'Content-Type': 'application/json' }),
    },
  );
}

function asyncValidate(values, dispatch, props, blurredField) {
  if (blurredField === 'username' && values.username !== props.initialValues.username) {
    return new Promise((resolve, reject) => {
      // TODO: Should use stripes-connect (dispatching an action and update state)
      checkUniqueUserID(values.username).then((response) => {
        if (response.status >= 400) {
          console.log('Error fetching user');
        } else {
          response.json().then((json) => {
            if (json.total_records > 0)
              reject({ username: 'This User ID has already been taken' });
            else
              resolve();
          });
        }
      });
    });
  }
  return new Promise(resolve => resolve());
}

class UserForm extends React.Component {

  static propTypes = {
    onClose: PropTypes.func, // eslint-disable-line react/no-unused-prop-types
    newUser: PropTypes.bool, // eslint-disable-line react/no-unused-prop-types
    handleSubmit: PropTypes.func.isRequired,
    reset: PropTypes.func,
    pristine: PropTypes.bool,
    submitting: PropTypes.bool,
    onCancel: PropTypes.func,
    initialValues: PropTypes.object,
    okapi: PropTypes.object,
    optionLists: PropTypes.shape({
      userGroups: PropTypes.arrayOf(PropTypes.object),
      contactTypes: PropTypes.arrayOf(PropTypes.object),
    }),
    addressTypes: PropTypes.arrayOf(PropTypes.object),
  };

  constructor(props) {
    super(props);
    okapiToken = props.okapi.token;
  }

  render() {
    const {
      handleSubmit,
      reset,  // eslint-disable-line no-unused-vars
      pristine,
      submitting,
      onCancel,
      initialValues,
      optionLists,
      addressTypes,
    } = this.props;

    /* Menues for Add User workflow */
    const addUserFirstMenu = <PaneMenu><button id="clickable-closenewuserdialog" onClick={onCancel} title="close" aria-label="Close New User Dialog">×</button></PaneMenu>;
    const addUserLastMenu = <PaneMenu><Button id="clickable-createnewuser" type="submit" title="Create New User" disabled={pristine || submitting} onClick={handleSubmit}>Create User</Button></PaneMenu>;
    const editUserLastMenu = <PaneMenu><Button id="clickable-updateuser" type="submit" title="Update User" disabled={pristine || submitting} onClick={handleSubmit}>Update User</Button></PaneMenu>;
    const patronGroupOptions = (optionLists.patronGroups || []).map(g => ({
      label: `${g.group} (${g.desc})`, value: g.id, selected: initialValues.patronGroup === g.id }));
    const contactTypeOptions = (optionLists.contactTypes || []).map(g => ({
      label: g.desc, value: g.id, selected: initialValues.preferredContactTypeId === g.id }));

    const addressFields = {
      country: { component: Autocomplete, props: { dataOptions: countriesOptions } },
      addressType: { component: Select, props: { dataOptions: toAddressTypeOptions(addressTypes), fullWidth: true, placeholder: 'Select address type' } },
    };

    return (
      <form id="form-user" style={{ height: '100%', overflow: 'auto' }}>
        <Paneset isRoot>
          <Pane defaultWidth="100%" firstMenu={addUserFirstMenu} lastMenu={initialValues.username ? editUserLastMenu : addUserLastMenu} paneTitle={initialValues.username ? 'Edit User' : 'New User'}>
            <Row>
              <Col sm={5} smOffset={1}>

User Record

                <Field label="User ID *" name="username" id="adduser_username" component={TextField} required fullWidth />
                {!initialValues.id ? <Field label="Password" name="creds.password" id="pw" component={TextField} required fullWidth /> : null}
                <Field label="Status *" name="active" component={RadioButtonGroup}>
                  <RadioButton label="Active" id="useractiveYesRB" value="true" inline />
                  <RadioButton label="Inactive" id="useractiveNoRB" value="false" inline />
                </Field>
                <fieldset>
                  <legend>Personal Info</legend>
                  <Field label="First Name" name="personal.firstName" id="adduser_firstname" component={TextField} required fullWidth />
                  <Field label="Middle Name" name="personal.middleName" id="adduser_middlename" component={TextField} fullWidth />
                  <Field label="Last Name *" name="personal.lastName" id="adduser_lastname" component={TextField} fullWidth />
                  <Field label="Email" name="personal.email" id="adduser_email" component={TextField} required fullWidth />
                  <Field label="Phone" name="personal.phone" id="adduser_phone" component={TextField} fullWidth />
                  <Field label="Mobile Phone" name="personal.mobilePhone" id="adduser_mobilePhone" component={TextField} fullWidth />
                  <Field label="Preferred Contact *" name="personal.preferredContactTypeId" id="adduser_preferredcontact" component={Select} dataOptions={[{ label: 'Select contact type', value: '' }, ...contactTypeOptions]} fullWidth />
                </fieldset>
                <Field
                  component={Datepicker}
                  label="Date of Birth"
                  dateFormat="YYYY-MM-DD"
                  name="personal.dateOfBirth"
                  id="adduser_dateofbirth"
                />
                {/* <Field
                  label="Type"
                  name="type"
                  id="adduser_type"
                  component={Select}
                  fullWidth
                  dataOptions={[{ label: 'Select user type', value: '' },
                                { label: 'Patron', value: 'Patron', selected: 'selected' }]}
                /> */}
                <Field
                  label="Patron Group *"
                  name="patronGroup"
                  id="adduser_group"
                  component={Select}
                  fullWidth
                  dataOptions={[{ label: 'Select patron group', value: '' }, ...patronGroupOptions]}
                />
                <Field
                  component={Datepicker}
                  label="Date Enrolled"
                  dateFormat="YYYY-MM-DD"
                  name="enrollmentDate"
                  id="adduser_enrollmentdate"
                />
                <Field
                  component={Datepicker}
                  label="Expiration Date"
                  dateFormat="YYYY-MM-DD"
                  name="expirationDate"
                  id="adduser_expirationdate"
                />
                <Field label="Bar Code" name="barcode" id="adduser_barcode" component={TextField} fullWidth />
                <Field label="FOLIO Record Number" name="id" id="adduser_id" readOnly component={TextField} fullWidth />
                <Field label="External System ID" name="externalSystemId" id="adduser_externalsystemid" component={TextField} fullWidth />

                <AddressEditList name="personal.addresses" fieldComponents={addressFields} canDelete />
              </Col>
            </Row>
          </Pane>
        </Paneset>
      </form>
    );
  }
}

export default stripesForm({
  form: 'userForm',
  validate,
  asyncValidate,
  asyncBlurFields: ['username'],
  navigationCheck: true,
})(UserForm);
<commit_msg>Label 'barcode' as a single word<commit_after>import React from 'react';
import PropTypes from 'prop-types';
import Paneset from '@folio/stripes-components/lib/Paneset';
import Pane from '@folio/stripes-components/lib/Pane';
import PaneMenu from '@folio/stripes-components/lib/PaneMenu';
import { Row, Col } from 'react-bootstrap';
import Button from '@folio/stripes-components/lib/Button';
import TextField from '@folio/stripes-components/lib/TextField';
import Select from '@folio/stripes-components/lib/Select';
import RadioButtonGroup from '@folio/stripes-components/lib/RadioButtonGroup';
import RadioButton from '@folio/stripes-components/lib/RadioButton';
import Datepicker from '@folio/stripes-components/lib/Datepicker';
import AddressEditList from '@folio/stripes-components/lib/structures/AddressFieldGroup/AddressEdit/AddressEditList';
import fetch from 'isomorphic-fetch';
import { Field } from 'redux-form';
import stripesForm from '@folio/stripes-form';

import { countriesOptions } from './data/countries';
import Autocomplete from './lib/Autocomplete';
import { toAddressTypeOptions } from './converters/address_type';

const sys = require('stripes-loader'); // eslint-disable-line
const okapiUrl = sys.okapi.url;
const tenant = sys.okapi.tenant;
let okapiToken = '';

function validate(values) {
  const errors = {};

  if (!values.personal || !values.personal.lastName) {
    errors.personal = { lastName: 'Please fill this in to continue' };
  }

  if (!values.username) {
    errors.username = 'Please fill this in to continue';
  }

  if (!values.patronGroup) {
    errors.patronGroup = 'Please select a patron group';
  }

  if (!values.personal || !values.personal.preferredContactTypeId) {
    errors.personal = { preferredContactTypeId: 'Please select a preferred form of contact' };
  }
  return errors;
}

function checkUniqueUserID(username) {
  return fetch(`${okapiUrl}/users?query=(username="${username}")`,
    { headers: Object.assign({}, {
      'X-Okapi-Tenant': tenant,
      'X-Okapi-Token': okapiToken,
      'Content-Type': 'application/json' }),
    },
  );
}

function asyncValidate(values, dispatch, props, blurredField) {
  if (blurredField === 'username' && values.username !== props.initialValues.username) {
    return new Promise((resolve, reject) => {
      // TODO: Should use stripes-connect (dispatching an action and update state)
      checkUniqueUserID(values.username).then((response) => {
        if (response.status >= 400) {
          console.log('Error fetching user');
        } else {
          response.json().then((json) => {
            if (json.total_records > 0)
              reject({ username: 'This User ID has already been taken' });
            else
              resolve();
          });
        }
      });
    });
  }
  return new Promise(resolve => resolve());
}

class UserForm extends React.Component {

  static propTypes = {
    onClose: PropTypes.func, // eslint-disable-line react/no-unused-prop-types
    newUser: PropTypes.bool, // eslint-disable-line react/no-unused-prop-types
    handleSubmit: PropTypes.func.isRequired,
    reset: PropTypes.func,
    pristine: PropTypes.bool,
    submitting: PropTypes.bool,
    onCancel: PropTypes.func,
    initialValues: PropTypes.object,
    okapi: PropTypes.object,
    optionLists: PropTypes.shape({
      userGroups: PropTypes.arrayOf(PropTypes.object),
      contactTypes: PropTypes.arrayOf(PropTypes.object),
    }),
    addressTypes: PropTypes.arrayOf(PropTypes.object),
  };

  constructor(props) {
    super(props);
    okapiToken = props.okapi.token;
  }

  render() {
    const {
      handleSubmit,
      reset,  // eslint-disable-line no-unused-vars
      pristine,
      submitting,
      onCancel,
      initialValues,
      optionLists,
      addressTypes,
    } = this.props;

    /* Menues for Add User workflow */
    const addUserFirstMenu = <PaneMenu><button id="clickable-closenewuserdialog" onClick={onCancel} title="close" aria-label="Close New User Dialog">×</button></PaneMenu>;
    const addUserLastMenu = <PaneMenu><Button id="clickable-createnewuser" type="submit" title="Create New User" disabled={pristine || submitting} onClick={handleSubmit}>Create User</Button></PaneMenu>;
    const editUserLastMenu = <PaneMenu><Button id="clickable-updateuser" type="submit" title="Update User" disabled={pristine || submitting} onClick={handleSubmit}>Update User</Button></PaneMenu>;
    const patronGroupOptions = (optionLists.patronGroups || []).map(g => ({
      label: `${g.group} (${g.desc})`, value: g.id, selected: initialValues.patronGroup === g.id }));
    const contactTypeOptions = (optionLists.contactTypes || []).map(g => ({
      label: g.desc, value: g.id, selected: initialValues.preferredContactTypeId === g.id }));

    const addressFields = {
      country: { component: Autocomplete, props: { dataOptions: countriesOptions } },
      addressType: { component: Select, props: { dataOptions: toAddressTypeOptions(addressTypes), fullWidth: true, placeholder: 'Select address type' } },
    };

    return (
      <form id="form-user" style={{ height: '100%', overflow: 'auto' }}>
        <Paneset isRoot>
          <Pane defaultWidth="100%" firstMenu={addUserFirstMenu} lastMenu={initialValues.username ? editUserLastMenu : addUserLastMenu} paneTitle={initialValues.username ? 'Edit User' : 'New User'}>
            <Row>
              <Col sm={5} smOffset={1}>

User Record

                <Field label="User ID *" name="username" id="adduser_username" component={TextField} required fullWidth />
                {!initialValues.id ? <Field label="Password" name="creds.password" id="pw" component={TextField} required fullWidth /> : null}
                <Field label="Status *" name="active" component={RadioButtonGroup}>
                  <RadioButton label="Active" id="useractiveYesRB" value="true" inline />
                  <RadioButton label="Inactive" id="useractiveNoRB" value="false" inline />
                </Field>
                <fieldset>
                  <legend>Personal Info</legend>
                  <Field label="First Name" name="personal.firstName" id="adduser_firstname" component={TextField} required fullWidth />
                  <Field label="Middle Name" name="personal.middleName" id="adduser_middlename" component={TextField} fullWidth />
                  <Field label="Last Name *" name="personal.lastName" id="adduser_lastname" component={TextField} fullWidth />
                  <Field label="Email" name="personal.email" id="adduser_email" component={TextField} required fullWidth />
                  <Field label="Phone" name="personal.phone" id="adduser_phone" component={TextField} fullWidth />
                  <Field label="Mobile Phone" name="personal.mobilePhone" id="adduser_mobilePhone" component={TextField} fullWidth />
                  <Field label="Preferred Contact *" name="personal.preferredContactTypeId" id="adduser_preferredcontact" component={Select} dataOptions={[{ label: 'Select contact type', value: '' }, ...contactTypeOptions]} fullWidth />
                </fieldset>
                <Field
                  component={Datepicker}
                  label="Date of Birth"
                  dateFormat="YYYY-MM-DD"
                  name="personal.dateOfBirth"
                  id="adduser_dateofbirth"
                />
                {/* <Field
                  label="Type"
                  name="type"
                  id="adduser_type"
                  component={Select}
                  fullWidth
                  dataOptions={[{ label: 'Select user type', value: '' },
                                { label: 'Patron', value: 'Patron', selected: 'selected' }]}
                /> */}
                <Field
                  label="Patron Group *"
                  name="patronGroup"
                  id="adduser_group"
                  component={Select}
                  fullWidth
                  dataOptions={[{ label: 'Select patron group', value: '' }, ...patronGroupOptions]}
                />
                <Field
                  component={Datepicker}
                  label="Date Enrolled"
                  dateFormat="YYYY-MM-DD"
                  name="enrollmentDate"
                  id="adduser_enrollmentdate"
                />
                <Field
                  component={Datepicker}
                  label="Expiration Date"
                  dateFormat="YYYY-MM-DD"
                  name="expirationDate"
                  id="adduser_expirationdate"
                />
                <Field label="Barcode" name="barcode" id="adduser_barcode" component={TextField} fullWidth />
                <Field label="FOLIO Record Number" name="id" id="adduser_id" readOnly component={TextField} fullWidth />
                <Field label="External System ID" name="externalSystemId" id="adduser_externalsystemid" component={TextField} fullWidth />

                <AddressEditList name="personal.addresses" fieldComponents={addressFields} canDelete />
              </Col>
            </Row>
          </Pane>
        </Paneset>
      </form>
    );
  }
}

export default stripesForm({
  form: 'userForm',
  validate,
  asyncValidate,
  asyncBlurFields: ['username'],
  navigationCheck: true,
})(UserForm);
<|endoftext|>


<commit_before>import React, {Component} from 'react';
import {Alert, Button, Modal, FormControls, Input, Label} from 'react-bootstrap';
import ManagerApi from 'utils/ManagerApi';

export default class ProvisionActivity extends Component {
  constructor(props) {
    super(props);

    this.state = {
      stationId: parseInt(this.props.params.id),
      batchId: 0,
      batches: [],
      curBatchActivities: [],
      showAlert: false,
      showSuccessAlert: false,
      showModal: false
    };

    this.handleAlertDismiss = this.handleAlertDismiss.bind(this);
    this.handleSuccessAlertDismiss = this.handleSuccessAlertDismiss.bind(this);
    this.handleCloseModal = this.handleCloseModal.bind(this);
    this.handleClickBatch = this.handleClickBatch.bind(this);
    this.handleUpdateCount = this.handleUpdateCount.bind(this);
    this.handleUpdateSuccess = this.handleUpdateSuccess.bind(this);
    this.handleUpdateFail = this.handleUpdateFail.bind(this);
  }

  handleAlertDismiss() {
    this.setState({
      showAlert: false
    });
  }

  handleSuccessAlertDismiss() {
    this.setState({
      showSuccessAlert: false
    });
  }

  handleCloseModal() {
    this.setState({
      showModal: false
    });
  }

  handleClickBatch(id) {
    const batch = this.props.batches[id];

    if (!batch || !batch.provisionActivities) {
      return;
    }

    this.setState({
      showModal: true,
      batchId: id,
      curBatchActivities: batch.provisionActivities
    });
  }

  handleUpdateCount() {
    let body = [];

    Object.keys(this.props.provisionRequirements).forEach( key => {
      const shipped = parseInt(this.refs[`act${key}`].getValue());
      const obj = {
        shipped: shipped,
        provisionRequirementId: parseInt(key),
        batchId: this.state.batchId,
        stationId: this.state.stationId
      };

      body.push(obj);
    });

    ManagerApi.updateProvisionAvtivity(body, this.handleUpdateSuccess, this.handleUpdateFail);

    this.handleCloseModal();
  }

  handleUpdateSuccess() {
    this.setState({
      showSuccessAlert: true
    });
  }

  handleUpdateFail() {
    this.setState({
      showAlert: true
    });
  }

  renderBatches() {
    const batches = this.props.batches;
    let batchesTable = Object.keys(batches).map(key => {
      const item = batches[key];
      const contact = item._contact;

      return (

          {item.trackingNumber}
          {contact.name}
          {contact.email}
          {contact.phone}
          {item.createdDate}

      );
    });

    return batchesTable;
  }

  renderAlert() {
    if (!this.state.showAlert) {
      return;
    }

    return (
      <Alert bsStyle="danger" onDismiss={this.handleAlertDismiss}>

Oh snap! You got an error!

Please wait a moment, and try again!

      </Alert>
    );
  }

  renderSuccessAlert() {
    if (!this.state.showSuccessAlert) {
      return;
    }

    return (
      <Alert bsStyle="success" onDismiss={this.handleSuccessAlertDismiss}>

Update Success!

      </Alert>
    );
  }

  renderActivity() {
    if (!this.state.showModal) {
      return;
    }

    const labelCol = 'col-xs-4';
    const inputCol = 'col-xs-8';
    const curActivities = this.state.curBatchActivities;
    const requirements = this.props.provisionRequirements;
    let reqCount = {};

    curActivities.forEach( item => {
      const pId = item.provisionRequirementId;
      const shipped = item.shipped || 0;
      const promised = item.promised || 0;

      if (reqCount[pId]) {
        reqCount[pId] = {
            shipped: reqCount[pId].shipped + shipped,
            promised: reqCount[pId].promised + promised
        };
      } else {
        reqCount[pId] = {shipped: shipped, promised: promised};
      }
    });

    const activities = Object.keys(requirements).map( key => {
      const requirement = requirements[key];
      const promised = reqCount[key] && reqCount[key].promised || 0;
      const shipped = reqCount[key] && reqCount[key].shipped || 0;

      return (
        <FormControls.Static
          key={key}
          labelClassName={labelCol}
          wrapperClassName={inputCol}
          label={requirement && requirement.name}
          value={`${promised} / (已收到：${shipped})`} />
      );
    });

    const comfirmForm = Object.keys(requirements).map( key => {
      const requirement = requirements[key];

      return (
        <Input
          key={key}
          type="text"
          ref={`act${key}`}
          labelClassName={labelCol}
          wrapperClassName={inputCol}
          label={requirement && requirement.name} />
      );
    });

    return (

        <Modal show={this.state.showModal} onHide={this.handleCloseModal}>
          <Modal.Header closeButton>
            <Modal.Title>物資確認</Modal.Title>
          </Modal.Header>
          <Modal.Body>

預計收到物資數量

            {activities}

實際收到物資數量

            <form className="form-horizontal">
              {comfirmForm}
            </form>
          </Modal.Body>
          <Modal.Footer>
            <Button onClick={this.handleCloseModal}>取消</Button>
            <Button bsStyle="primary" onClick={this.handleUpdateCount}>確認</Button>
          </Modal.Footer>
        </Modal>

    );
  }

  render() {
    return (

        {this.renderAlert()}
        {this.renderSuccessAlert()}

             追蹤編號
             聯絡人
             信箱
             電話
             日期

           {this.renderBatches()}

        {this.renderActivity()}

    );
  }
}
<commit_msg>remove parseInt for provisionRequirement.id<commit_after>import React, {Component} from 'react';
import {Alert, Button, Modal, FormControls, Input, Label} from 'react-bootstrap';
import ManagerApi from 'utils/ManagerApi';

export default class ProvisionActivity extends Component {
  constructor(props) {
    super(props);

    this.state = {
      stationId: parseInt(this.props.params.id),
      batchId: 0,
      batches: [],
      curBatchActivities: [],
      showAlert: false,
      showSuccessAlert: false,
      showModal: false
    };

    this.handleAlertDismiss = this.handleAlertDismiss.bind(this);
    this.handleSuccessAlertDismiss = this.handleSuccessAlertDismiss.bind(this);
    this.handleCloseModal = this.handleCloseModal.bind(this);
    this.handleClickBatch = this.handleClickBatch.bind(this);
    this.handleUpdateCount = this.handleUpdateCount.bind(this);
    this.handleUpdateSuccess = this.handleUpdateSuccess.bind(this);
    this.handleUpdateFail = this.handleUpdateFail.bind(this);
  }

  handleAlertDismiss() {
    this.setState({
      showAlert: false
    });
  }

  handleSuccessAlertDismiss() {
    this.setState({
      showSuccessAlert: false
    });
  }

  handleCloseModal() {
    this.setState({
      showModal: false
    });
  }

  handleClickBatch(id) {
    const batch = this.props.batches[id];

    if (!batch || !batch.provisionActivities) {
      return;
    }

    this.setState({
      showModal: true,
      batchId: id,
      curBatchActivities: batch.provisionActivities
    });
  }

  handleUpdateCount() {
    let body = [];

    Object.keys(this.props.provisionRequirements).forEach( key => {
      const shipped = parseInt(this.refs[`act${key}`].getValue());
      const obj = {
        shipped: shipped,
        provisionRequirementId: key,
        batchId: this.state.batchId,
        stationId: this.state.stationId
      };

      body.push(obj);
    });

    ManagerApi.updateProvisionAvtivity(body, this.handleUpdateSuccess, this.handleUpdateFail);

    this.handleCloseModal();
  }

  handleUpdateSuccess() {
    this.setState({
      showSuccessAlert: true
    });
  }

  handleUpdateFail() {
    this.setState({
      showAlert: true
    });
  }

  renderBatches() {
    const batches = this.props.batches;
    let batchesTable = Object.keys(batches).map(key => {
      const item = batches[key];
      const contact = item._contact;

      return (

          {item.trackingNumber}
          {contact.name}
          {contact.email}
          {contact.phone}
          {item.createdDate}

      );
    });

    return batchesTable;
  }

  renderAlert() {
    if (!this.state.showAlert) {
      return;
    }

    return (
      <Alert bsStyle="danger" onDismiss={this.handleAlertDismiss}>

Oh snap! You got an error!

Please wait a moment, and try again!

      </Alert>
    );
  }

  renderSuccessAlert() {
    if (!this.state.showSuccessAlert) {
      return;
    }

    return (
      <Alert bsStyle="success" onDismiss={this.handleSuccessAlertDismiss}>

Update Success!

      </Alert>
    );
  }

  renderActivity() {
    if (!this.state.showModal) {
      return;
    }

    const labelCol = 'col-xs-4';
    const inputCol = 'col-xs-8';
    const curActivities = this.state.curBatchActivities;
    const requirements = this.props.provisionRequirements;
    let reqCount = {};

    curActivities.forEach( item => {
      const pId = item.provisionRequirementId;
      const shipped = item.shipped || 0;
      const promised = item.promised || 0;

      if (reqCount[pId]) {
        reqCount[pId] = {
            shipped: reqCount[pId].shipped + shipped,
            promised: reqCount[pId].promised + promised
        };
      } else {
        reqCount[pId] = {shipped: shipped, promised: promised};
      }
    });

    const activities = Object.keys(requirements).map( key => {
      const requirement = requirements[key];
      const promised = reqCount[key] && reqCount[key].promised || 0;
      const shipped = reqCount[key] && reqCount[key].shipped || 0;

      return (
        <FormControls.Static
          key={key}
          labelClassName={labelCol}
          wrapperClassName={inputCol}
          label={requirement && requirement.name}
          value={`${promised} / (已收到：${shipped})`} />
      );
    });

    const comfirmForm = Object.keys(requirements).map( key => {
      const requirement = requirements[key];

      return (
        <Input
          key={key}
          type="text"
          ref={`act${key}`}
          labelClassName={labelCol}
          wrapperClassName={inputCol}
          label={requirement && requirement.name} />
      );
    });

    return (

        <Modal show={this.state.showModal} onHide={this.handleCloseModal}>
          <Modal.Header closeButton>
            <Modal.Title>物資確認</Modal.Title>
          </Modal.Header>
          <Modal.Body>

預計收到物資數量

            {activities}

實際收到物資數量

            <form className="form-horizontal">
              {comfirmForm}
            </form>
          </Modal.Body>
          <Modal.Footer>
            <Button onClick={this.handleCloseModal}>取消</Button>
            <Button bsStyle="primary" onClick={this.handleUpdateCount}>確認</Button>
          </Modal.Footer>
        </Modal>

    );
  }

  render() {
    return (

        {this.renderAlert()}
        {this.renderSuccessAlert()}

             追蹤編號
             聯絡人
             信箱
             電話
             日期

           {this.renderBatches()}

        {this.renderActivity()}

    );
  }
}
<|endoftext|>


<commit_before>// VERSION 1.0.1

this.FavIcons = {
	get defaultFavicon() {
		return this._favIconService.defaultFavicon.spec;
	},

	init: function() {
		XPCOMUtils.defineLazyServiceGetter(this, "_favIconService", "@mozilla.org/browser/favicon-service;1", "nsIFaviconService");
	},

	// Gets the "favicon link URI" for the given xul:tab, or null if unavailable.
	getFavIconUrlForTab: function(tab, callback) {
		this._isImageDocument(tab).then((isImageDoc) => {
			if(isImageDoc) {
				callback(tab.pinned ? tab.image : null);
			} else {
				this._getFavIconForNonImageDocument(tab, callback);
			}
		});
	},

	// Retrieves the favicon for a tab containing a non-image document.
	_getFavIconForNonImageDocument: function(tab, callback) {
		if(tab.image) {
			this._getFavIconFromTabImage(tab, callback);
		} else if(this._shouldLoadFavIcon(tab)) {
			this._getFavIconForHttpDocument(tab, callback);
		} else {
			callback(null);
		}
	},

	// Retrieves the favicon for tab with a tab image.
	_getFavIconFromTabImage: function(tab, callback) {
		let tabImage = gBrowser.getIcon(tab);

		// If the tab image's url starts with http(s), fetch icon from favicon service via the moz-anno protocol.
		if(/^https?:/.test(tabImage)) {
			let tabImageURI = gWindow.makeURI(tabImage);
			tabImage = this._favIconService.getFaviconLinkForIcon(tabImageURI).spec;
		}

		callback(tabImage);
	},

	// Retrieves the favicon for tab containg a http(s) document.
	_getFavIconForHttpDocument: function(tab, callback) {
		let {currentURI} = tab.linkedBrowser;
		this._favIconService.getFaviconURLForPage(currentURI, (uri) => {
			if(uri) {
				let icon = this._favIconService.getFaviconLinkForIcon(uri).spec;
				callback(icon);
			} else {
				callback(this.defaultFavicon);
			}
		});
	},

	// Checks whether an image is loaded into the given tab.
	_isImageDocument: function(tab, callback) {
		return new Promise(function(resolve, reject) {
			let repeat;

			let receiver = function(m) {
				if(repeat) {
					repeat.cancel();
				}
				Messenger.unlistenBrowser(tab.linkedBrowser, "isImageDocument", receiver);
				resolve(m.data);
			};
			Messenger.listenBrowser(tab.linkedBrowser, "isImageDocument", receiver);

			// sometimes on first open, we don't get a response right away because the message isn't actually sent, although I have no clue why...
			let ask = function() {
				Messenger.messageBrowser(tab.linkedBrowser, "isImageDocument");
				repeat = aSync(ask, 1000);
			};
			ask();
		});
	},

	// Checks whether fav icon should be loaded for a given tab.
	_shouldLoadFavIcon: function(tab) {
		// No need to load a favicon if the user doesn't want site or favicons.
		if(!Prefs.site_icons || !Prefs.favicons) {
			return false;
		}

		let uri = tab.linkedBrowser.currentURI;

		// Stop here if we don't have a valid nsIURI.
		if(!uri || !(uri instanceof Ci.nsIURI)) {
			return false;
		}

		// Load favicons for http(s) pages only.
		return uri.schemeIs("http") || uri.schemeIs("https");
	}
};

Modules.LOADMODULE = function() {
	Prefs.setDefaults({
		site_icons: true,
		favicons: true
	}, 'chrome', 'browser');
};
<commit_msg>fix infinite async chains on addon reload<commit_after>// VERSION 1.0.1

this.FavIcons = {
	get defaultFavicon() {
		return this._favIconService.defaultFavicon.spec;
	},

	init: function() {
		XPCOMUtils.defineLazyServiceGetter(this, "_favIconService", "@mozilla.org/browser/favicon-service;1", "nsIFaviconService");
	},

	// Gets the "favicon link URI" for the given xul:tab, or null if unavailable.
	getFavIconUrlForTab: function(tab, callback) {
		this._isImageDocument(tab).then((isImageDoc) => {
			if(isImageDoc) {
				callback(tab.pinned ? tab.image : null);
			} else {
				this._getFavIconForNonImageDocument(tab, callback);
			}
		}).catch(() => {
		  callback(null)
		});
	},

	// Retrieves the favicon for a tab containing a non-image document.
	_getFavIconForNonImageDocument: function(tab, callback) {
		if(tab.image) {
			this._getFavIconFromTabImage(tab, callback);
		} else if(this._shouldLoadFavIcon(tab)) {
			this._getFavIconForHttpDocument(tab, callback);
		} else {
			callback(null);
		}
	},

	// Retrieves the favicon for tab with a tab image.
	_getFavIconFromTabImage: function(tab, callback) {
		let tabImage = gBrowser.getIcon(tab);

		// If the tab image's url starts with http(s), fetch icon from favicon service via the moz-anno protocol.
		if(/^https?:/.test(tabImage)) {
			let tabImageURI = gWindow.makeURI(tabImage);
			tabImage = this._favIconService.getFaviconLinkForIcon(tabImageURI).spec;
		}

		callback(tabImage);
	},

	// Retrieves the favicon for tab containg a http(s) document.
	_getFavIconForHttpDocument: function(tab, callback) {
		let {currentURI} = tab.linkedBrowser;
		this._favIconService.getFaviconURLForPage(currentURI, (uri) => {
			if(uri) {
				let icon = this._favIconService.getFaviconLinkForIcon(uri).spec;
				callback(icon);
			} else {
				callback(this.defaultFavicon);
			}
		});
	},

	// Checks whether an image is loaded into the given tab.
	_isImageDocument: function(tab) {
		return new Promise(function(resolve, reject) {
			let repeat;

			let receiver = function(m) {
				if(repeat) {
					repeat.cancel();
				}
				Messenger.unlistenBrowser(tab.linkedBrowser, "isImageDocument", receiver);
				resolve(m.data);
			};
			Messenger.listenBrowser(tab.linkedBrowser, "isImageDocument", receiver);

			// sometimes on first open, we don't get a response right away because the message isn't actually sent, although I have no clue why...
			let ask = function() {
				Messenger.messageBrowser(tab.linkedBrowser, "isImageDocument");
				if(!repeat) { // only repeat once
				  repeat = aSync(ask, 1000);
				} else {
				  reject("isImageDocument response timeout");
				}
			};
			ask();
		});
	},

	// Checks whether fav icon should be loaded for a given tab.
	_shouldLoadFavIcon: function(tab) {
		// No need to load a favicon if the user doesn't want site or favicons.
		if(!Prefs.site_icons || !Prefs.favicons) {
			return false;
		}

		let uri = tab.linkedBrowser.currentURI;

		// Stop here if we don't have a valid nsIURI.
		if(!uri || !(uri instanceof Ci.nsIURI)) {
			return false;
		}

		// Load favicons for http(s) pages only.
		return uri.schemeIs("http") || uri.schemeIs("https");
	}
};

Modules.LOADMODULE = function() {
	Prefs.setDefaults({
		site_icons: true,
		favicons: true
	}, 'chrome', 'browser');
};
<|endoftext|>


<commit_before>	"gengarite": {
		id: "gengarite",
		name: "Gengarite",
		spritenum: 588,
		megaStone: "Gengar-Mega",
		megaEvolves: "Gengar",
		onTakeItem: function(item, source) {
			if (item.megaEvolves === source.baseTemplate.baseSpecies) return false;
			return true;
		},
		num: 656,
		gen: 3,
		desc: "Mega-evolves Gengar."
	}
};
<commit_msg>Update items.js<commit_after>exports.BattleItems = {
	"gengarite": {
		id: "gengarite",
		name: "Gengarite",
		spritenum: 588,
		megaStone: "Gengar-Mega",
		megaEvolves: "Gengar",
		onTakeItem: function(item, source) {
			if (item.megaEvolves === source.baseTemplate.baseSpecies) return false;
			return true;
		},
		num: 656,
		gen: 3,
		desc: "Mega-evolves Gengar."
	}
};
<|endoftext|>