<commit_before>Object.subclass("GameObject", {
	initialize: function(world, name, pos, extent, radius) {
	    this.world = world;
		this.name = name;

		this.position = pos;
        this.prevPosition = pos.copy();

		this.coordinates = Vector2.Zero.copy().sub(new Vector2(-1,-1));
		this.collisionTiles = {
    		upperLeft:false,
    		bottomLeft:true,
    		upperRight:true,
    		bottomRight:true
		};

        this.velocity = Vector2.Zero.copy();

		this.radius = radius;
		this.extent = extent;
		this.speed = 3;

		this.constraints = [];
		this.alive = true;
	},

	update: function(dt) {
	    this.prevPosition.set(this.position);

        var deltaPos = this.velocity.normalizedCopy().mulFloat(dt*this.speed);
        this.position.addSelf(deltaPos);

        if(typeof this.animation !== "undefined")
            this.animation.update(dt);
	},

	draw: function(renderer) {
		if(typeof this.animation !== "undefined") {
			this.animation.draw(renderer, this.getWorldAABB(), this.velocity.getDirectedAngle(Vector2.Zero) * 180/Math.PI);
		}
	},

	getTile: function(pos) {
	    return this.world.map.get(this.world.map.positionToCoordinates(pos));
	},

	getWorldAABB: function() {
        var halfSize = this.extent.divFloat(2);
        var aabb = new AABB(
            this.position.sub(halfSize),
            this.position.add(halfSize)
        );
        return aabb;
	},

    // define a callback that should be triggered when this object is colliding with a second game object
    onCollisionWith: function(other, callback) {
        var that = this;

        var onCollision = bbb.trigger({
            callback: function() {
                callback.call(this, that, other);
            },
            ctx: {
                that: that,
                other: other
            }
        }, function() {
            // use simple spheres for collision
            return that.position.distance(other.position) <= that.radius + other.radius;
        });

        // track this constraint on both game objects
        this.constraints.push(onCollision);
        other.constraints.push(onCollision);
    },

    destroy: function() {
        this.constraints.each(function(constraint) {
            constraint.disable();
        });
        this.alive = false;
        this.world.gameObjects.remove(this);
    }
});

// possible constraints:
// ---
// - your direction should be normalized
// - your speed is limited to maxSpeed (cannot adjust speed?)
// - your velocity is direction.mulFloat(dt*speed)
GameObject.subclass("Tank", {
	initialize: function($super, world, pos) {
	    $super(world, "tank", pos, new Vector2(2, 2), 1);

		this.speed = 16 / 6 * world.map.tileSize.x;

        this.turretAngle = 40;
        this.turretAnimation = new Animation(new AnimationSheet("assets/turret.png", 18, 18), 0.4, [0,1,2,3]);

		this.initConstraints(world);
    },
    initConstraints: function(world) {
        var that = this,
            map = world.map;

        // constraint:
        // - do not be on a wall tile
        bbb.assert({
            onError: function(error) {
                if(!error instanceof ContinuousAssertError) {
                    throw error;
                }
            },
            ctx: {
                that: that,
                map: map
            }
        }, function() {
            var pos = that.position.divVector(map.tileSize).floor();
            return map.tiles[pos.y][pos.x].canWalkThrough();
        });

        // assumption: tanks are inserted first into the world
        world.getGameObjects().each(function(tank) {
            // constraint:
            // - solve collisions
            that.onCollisionWith(tank, function(that, tank) {
                var desiredDistance = that.radius + tank.radius,
                    distVector = tank.position.sub(that.position),
                    realDistance = distVector.length(),
                    moveVector = distVector.mulFloat((desiredDistance - realDistance) / 1.9);
                tank.position.addSelf(moveVector);
                that.position.subSelf(moveVector);
                //console.log(realDistance, "post", that.position.distance(tank.position, that.radius + tank.radius));
            });
        });

        /*
        // pos -> coords
        bbb.always({
            solver: db,
            allowUnsolvableOperations: true,
            ctx: {
                world: world,
                that: this
            }, methods: function() {
                that.coordinates.formula([that.position, that.position.x, that.position.y], function(pos) {
                    return pos.divVector(world.map.tileSize).sub(new Vector2(0.5, 0.5)).floor();
                });
            }
        }, function() {
            return that.position.divVector(world.map.tileSize).sub(new Vector2(0.5, 0.5)).floor().equals(that.coordinates);
        });

        // coords -> collisionTiles
        bbb.always({
            solver: db,
            allowUnsolvableOperations: true,
            ctx: {
                map: world.map,
                that: this
            }, methods: function() {
                that.collisionTiles.formula([that.coordinates], function(coords) {
                    return {
                        upperLeft: map.get(coords),
                        bottomLeft: map.get(coords.add(new Vector2(0,1))),
                        upperRight: map.get(coords.add(new Vector2(1,0))),
                        bottomRight: map.get(coords.add(new Vector2(1,1)))
                    }
                });
            }
        }, function() {
            return that.collisionTiles.upperLeft === map.get(that.coordinates) &&
                   that.collisionTiles.bottomLeft === map.get(that.coordinates.add(new Vector2(0,1))) &&
                   that.collisionTiles.upperRight === map.get(that.coordinates.add(new Vector2(1,0))) &&
                   that.collisionTiles.bottomRight === map.get(that.coordinates.add(new Vector2(1,1)));
        });
        console.log(that.coordinates, map.get(that.coordinates),that.collisionTiles.upperLeft.index)

        var cassowary = new ClSimplexSolver();
        bbb.always({
            solver: cassowary,
            ctx: {
                that: that
            }
        }, function() {
            return ((that.collisionTiles).upperLeft).index  == 0 &&
                   ((that.collisionTiles).bottomLeft).index  == 0 &&
                   ((that.collisionTiles).upperRight).index  == 0 &&
                   ((that.collisionTiles).bottomRight).index  == 0;
        });
        */
	},

	update: function($super, dt) {
	    $super(dt);
        this.turretAnimation.update(dt);
	},

	draw: function($super, renderer) {
	    $super(renderer);
		this.turretAnimation.draw(renderer, this.getWorldAABB(), this.turretAngle);
	}
});

Tank.subclass("PlayerTank", {
	initialize: function($super, world, pos, input) {
	    $super(world, pos);

		this.animation = new Animation(new AnimationSheet("assets/tank.png", 18, 18), 0.4, [0,1,2,3]);
    },

	update: function($super, dt) {
	    this.controls && this.controls.update(dt);

   	    $super(dt);
    }
});

Object.subclass("PlayerControls", {
    initialize: function(player, world, input, viewport) {
        this.player = player;
        this.world = world;
        this.input = input;
        this.viewport = viewport;
    },
    update: function(dt) {
        // move player tank
        player.velocity.set(Vector2.Zero);
        if(this.input.state("up")) this.player.velocity.addSelf(new Vector2(0, -1));
        if(this.input.state("left")) this.player.velocity.addSelf(new Vector2(-1, 0));
        if(this.input.state("down")) this.player.velocity.addSelf(new Vector2(0, 1));
        if(this.input.state("right")) this.player.velocity.addSelf(new Vector2(1, 0));

        // player fires a bullet
        if(this.input.pressed("leftclick")) {
            var direction = this.viewport.screenToWorldCoordinates(this.input.mouse)
                .sub(player.position)
                .normalizedCopy();
            var bullet = new Bullet(this.world,
                player.position.add(direction.mulFloat(player.radius + 0.25 + player.speed * dt)),
                direction);
            this.world.getGameObjects().each(function(other) {
                bullet.onCollisionWith(other, function(bullet, other) {
                    // {
                        // 3 possibilities to avoid this to happen more than one time:
                        // 1. in collision callback:
                        //     if(bullet.alive && other.alive)
                        //     but get not rid of the actual constraint -> slow with more and more bullets
                        // 2. layer each game object
                        //     layer.activeOn(bullet in world)
                        //     but collision callback is linked to 2 game objects
                        // 3. in collision callback
                        //     bullet.unconstrainAND_DISABLE_ALL() to disable all linked constraints
                        //     very general; we instead keep track of these manually and disable all constraints on destroy
                        bullet.destroy();
                        other.destroy();
                    //}
                });
            });
            this.world.spawn(bullet);
        }
    }
});

Tank.subclass("CPUTank", {
    initialize: function($super, world, pos) {
        $super(world, pos);

		this.animation = new Animation(new AnimationSheet("assets/tank.png", 18, 18), 0.4, [4,5,6,7]);

        this.velocity.set(new Vector2(-1,1));
    },

	update: function($super, dt) {
	    // adjust direction randomly
	    this.velocity.rotateSelf(Math.PI / 180 * (Math.random() - 0.5) * 50);
        //this.velocity.set(player.position.sub(this.position));
	    $super(dt);
	}
});

GameObject.subclass("Bullet", {
	initialize: function($super, world, pos, vel, maxReflections) {
	    $super(world, "bullet", pos, new Vector2(0.5, 0.5), 0.25);

        this.velocity.set(vel);
		this.animation = new Animation(new AnimationSheet("assets/bullet.png", 7, 7), 0.2, [0,1,2,3,2,1]);

        this.maxReflections = maxReflections || 2;
        this.reflectionCount = 0;

   		this.speed = 16 / 3.7 * world.map.tileSize.x;

		this.initConstraints(world);
	},

	initConstraints: function(world) {
        var that = this,
            map = this.world.map,
            db = new DBPlanner();

        // constraint idea:
        // separate this into 2 constraints
        // one constraint that triggers the vertical reflection
        // the other just listens on the y-coordinate for the horizontal reflection
        var vertical = bbb.trigger({
            callback: function() {
                if(that.reflectionCount++ == that.maxReflections) {
                    this.disable();
                    that.destroy();
                } else {
                    that.velocity.x *= -1;
                }
            },
            ctx: {
                that: that,
                map: map
            }
        }, function() {
            var pp = that.prevPosition.divVector(map.tileSize).floor();
            var x = that.position.divVector(map.tileSize).floor().x;
            return map.tiles[pp.y][pp.x].canFlyThrough() && !(map.tiles[pp.y][x].canFlyThrough());
        });
        var horizontal = bbb.trigger({
            callback: function() {
                if(that.reflectionCount++ == that.maxReflections) {
                    this.disable();
                    that.destroy();
                } else {
                    that.velocity.y *= -1;
                }
            },
            ctx: {
                that: that,
                map: map
            }
        }, function() {
            var pp = that.prevPosition.divVector(map.tileSize).floor();
            var y = that.position.divVector(map.tileSize).floor().y;
            return map.tiles[pp.y][pp.x].canFlyThrough() && !(map.tiles[y][pp.x].canFlyThrough());
        });

        this.constraints.push(vertical, horizontal);
	}
});
<commit_msg>draw game objects rotated by their current velocity vector<commit_after>Object.subclass("GameObject", {
	initialize: function(world, name, pos, extent, radius) {
	    this.world = world;
		this.name = name;

		this.position = pos;
        this.prevPosition = pos.copy();

		this.coordinates = Vector2.Zero.copy().sub(new Vector2(-1,-1));
		this.collisionTiles = {
    		upperLeft:false,
    		bottomLeft:true,
    		upperRight:true,
    		bottomRight:true
		};

        this.velocity = Vector2.Zero.copy();

		this.radius = radius;
		this.extent = extent;
		this.speed = 3;

		this.constraints = [];
		this.alive = true;
	},

	update: function(dt) {
	    this.prevPosition.set(this.position);

        var deltaPos = this.velocity.normalizedCopy().mulFloat(dt*this.speed);
        this.position.addSelf(deltaPos);

        if(typeof this.animation !== "undefined")
            this.animation.update(dt);
	},

	draw: function(renderer) {
		if(typeof this.animation !== "undefined") {
			this.animation.draw(renderer, this.getWorldAABB(), this.velocity.getDirectedAngle(new Vector2(1,0)));
		}
	},

	getTile: function(pos) {
	    return this.world.map.get(this.world.map.positionToCoordinates(pos));
	},

	getWorldAABB: function() {
        var halfSize = this.extent.divFloat(2);
        var aabb = new AABB(
            this.position.sub(halfSize),
            this.position.add(halfSize)
        );
        return aabb;
	},

    // define a callback that should be triggered when this object is colliding with a second game object
    onCollisionWith: function(other, callback) {
        var that = this;

        var onCollision = bbb.trigger({
            callback: function() {
                callback.call(this, that, other);
            },
            ctx: {
                that: that,
                other: other
            }
        }, function() {
            // use simple spheres for collision
            return that.position.distance(other.position) <= that.radius + other.radius;
        });

        // track this constraint on both game objects
        this.constraints.push(onCollision);
        other.constraints.push(onCollision);
    },

    destroy: function() {
        this.constraints.each(function(constraint) {
            constraint.disable();
        });
        this.alive = false;
        this.world.gameObjects.remove(this);
    }
});

// possible constraints:
// ---
// - your direction should be normalized
// - your speed is limited to maxSpeed (cannot adjust speed?)
// - your velocity is direction.mulFloat(dt*speed)
GameObject.subclass("Tank", {
	initialize: function($super, world, pos) {
	    $super(world, "tank", pos, new Vector2(2, 2), 1);

		this.speed = 16 / 6 * world.map.tileSize.x;

        this.turretDirection = new Vector2(1,1);
        this.turretAnimation = new Animation(new AnimationSheet("assets/turret.png", 18, 18), 0.4, [0,1,2,3]);

		this.initConstraints(world);
    },
    initConstraints: function(world) {
        var that = this,
            map = world.map;

        // constraint:
        // - do not be on a wall tile
        bbb.assert({
            onError: function(error) {
                if(!error instanceof ContinuousAssertError) {
                    throw error;
                }
            },
            ctx: {
                that: that,
                map: map
            }
        }, function() {
            var pos = that.position.divVector(map.tileSize).floor();
            return map.tiles[pos.y][pos.x].canWalkThrough();
        });

        // assumption: tanks are inserted first into the world
        world.getGameObjects().each(function(tank) {
            // constraint:
            // - solve collisions
            that.onCollisionWith(tank, function(that, tank) {
                var desiredDistance = that.radius + tank.radius,
                    distVector = tank.position.sub(that.position),
                    realDistance = distVector.length(),
                    moveVector = distVector.mulFloat((desiredDistance - realDistance) / 1.9);
                tank.position.addSelf(moveVector);
                that.position.subSelf(moveVector);
                //console.log(realDistance, "post", that.position.distance(tank.position, that.radius + tank.radius));
            });
        });

        /*
        // pos -> coords
        bbb.always({
            solver: db,
            allowUnsolvableOperations: true,
            ctx: {
                world: world,
                that: this
            }, methods: function() {
                that.coordinates.formula([that.position, that.position.x, that.position.y], function(pos) {
                    return pos.divVector(world.map.tileSize).sub(new Vector2(0.5, 0.5)).floor();
                });
            }
        }, function() {
            return that.position.divVector(world.map.tileSize).sub(new Vector2(0.5, 0.5)).floor().equals(that.coordinates);
        });

        // coords -> collisionTiles
        bbb.always({
            solver: db,
            allowUnsolvableOperations: true,
            ctx: {
                map: world.map,
                that: this
            }, methods: function() {
                that.collisionTiles.formula([that.coordinates], function(coords) {
                    return {
                        upperLeft: map.get(coords),
                        bottomLeft: map.get(coords.add(new Vector2(0,1))),
                        upperRight: map.get(coords.add(new Vector2(1,0))),
                        bottomRight: map.get(coords.add(new Vector2(1,1)))
                    }
                });
            }
        }, function() {
            return that.collisionTiles.upperLeft === map.get(that.coordinates) &&
                   that.collisionTiles.bottomLeft === map.get(that.coordinates.add(new Vector2(0,1))) &&
                   that.collisionTiles.upperRight === map.get(that.coordinates.add(new Vector2(1,0))) &&
                   that.collisionTiles.bottomRight === map.get(that.coordinates.add(new Vector2(1,1)));
        });
        console.log(that.coordinates, map.get(that.coordinates),that.collisionTiles.upperLeft.index)

        var cassowary = new ClSimplexSolver();
        bbb.always({
            solver: cassowary,
            ctx: {
                that: that
            }
        }, function() {
            return ((that.collisionTiles).upperLeft).index  == 0 &&
                   ((that.collisionTiles).bottomLeft).index  == 0 &&
                   ((that.collisionTiles).upperRight).index  == 0 &&
                   ((that.collisionTiles).bottomRight).index  == 0;
        });
        */
	},

	update: function($super, dt) {
	    $super(dt);
        this.turretAnimation.update(dt);
	},

	draw: function($super, renderer) {
	    $super(renderer);
		this.turretAnimation.draw(
		    renderer, this.getWorldAABB(), this.turretDirection.getDirectedAngle(new Vector2(1,0))
		);
	}
});

Tank.subclass("PlayerTank", {
	initialize: function($super, world, pos, input) {
	    $super(world, pos);

		this.animation = new Animation(new AnimationSheet("assets/tank.png", 18, 18), 0.4, [0,1,2,3]);
    },

	update: function($super, dt) {
	    this.controls && this.controls.update(dt);

   	    $super(dt);
    }
});

Object.subclass("PlayerControls", {
    initialize: function(player, world, input, viewport) {
        this.player = player;
        this.world = world;
        this.input = input;
        this.viewport = viewport;
    },
    update: function(dt) {
        // move player tank
        player.velocity.set(Vector2.Zero);
        if(this.input.state("up")) this.player.velocity.addSelf(new Vector2(0, -1));
        if(this.input.state("left")) this.player.velocity.addSelf(new Vector2(-1, 0));
        if(this.input.state("down")) this.player.velocity.addSelf(new Vector2(0, 1));
        if(this.input.state("right")) this.player.velocity.addSelf(new Vector2(1, 0));

        // player fires a bullet
        if(this.input.pressed("leftclick")) {
            var direction = this.viewport.screenToWorldCoordinates(this.input.mouse)
                .sub(player.position)
                .normalizedCopy();
            var bullet = new Bullet(this.world,
                player.position.add(direction.mulFloat(player.radius + 0.25 + player.speed * dt)),
                direction);
            this.world.getGameObjects().each(function(other) {
                bullet.onCollisionWith(other, function(bullet, other) {
                    // 3 possibilities to avoid this to happen more than one time:
                    // 1. in collision callback:
                    //     if(bullet.alive && other.alive)
                    //     but get not rid of the actual constraint -> slow with more and more bullets
                    // 2. layer each game object
                    //     layer.activeOn(bullet in world)
                    //     but collision callback is linked to 2 game objects
                    // 3. in collision callback
                    //     bullet.unconstrainAND_DISABLE_ALL() to disable all linked constraints
                    //     very general; we instead keep track of these manually and disable all constraints on destroy
                    bullet.destroy();
                    other.destroy();
                });
            });
            this.world.spawn(bullet);
        }
    }
});

Tank.subclass("CPUTank", {
    initialize: function($super, world, pos) {
        $super(world, pos);

		this.animation = new Animation(new AnimationSheet("assets/tank.png", 18, 18), 0.4, [4,5,6,7]);

        this.velocity.set(new Vector2(-1,1));
    },

	update: function($super, dt) {
	    // adjust direction randomly
	    this.velocity.rotateSelf(Math.PI / 180 * (Math.random() - 0.5) * 50);
        //this.velocity.set(player.position.sub(this.position));
	    $super(dt);
	}
});

GameObject.subclass("Bullet", {
	initialize: function($super, world, pos, vel, maxReflections) {
	    $super(world, "bullet", pos, new Vector2(0.5, 0.5), 0.25);

        this.velocity.set(vel);
		this.animation = new Animation(new AnimationSheet("assets/bullet.png", 7, 7), 0.2, [0,1,2,3,2,1]);

        this.maxReflections = maxReflections || 2;
        this.reflectionCount = 0;

   		this.speed = 16 / 3.7 * world.map.tileSize.x;

		this.initConstraints(world);
	},

	initConstraints: function(world) {
        var that = this,
            map = this.world.map,
            db = new DBPlanner();

        // constraint idea:
        // separate this into 2 constraints
        // one constraint that triggers the vertical reflection
        // the other just listens on the y-coordinate for the horizontal reflection
        var vertical = bbb.trigger({
            callback: function() {
                if(that.reflectionCount++ == that.maxReflections) {
                    this.disable();
                    that.destroy();
                } else {
                    that.velocity.x *= -1;
                }
            },
            ctx: {
                that: that,
                map: map
            }
        }, function() {
            var pp = that.prevPosition.divVector(map.tileSize).floor();
            var x = that.position.divVector(map.tileSize).floor().x;
            return map.tiles[pp.y][pp.x].canFlyThrough() && !(map.tiles[pp.y][x].canFlyThrough());
        });
        var horizontal = bbb.trigger({
            callback: function() {
                if(that.reflectionCount++ == that.maxReflections) {
                    this.disable();
                    that.destroy();
                } else {
                    that.velocity.y *= -1;
                }
            },
            ctx: {
                that: that,
                map: map
            }
        }, function() {
            var pp = that.prevPosition.divVector(map.tileSize).floor();
            var y = that.position.divVector(map.tileSize).floor().y;
            return map.tiles[pp.y][pp.x].canFlyThrough() && !(map.tiles[y][pp.x].canFlyThrough());
        });

        this.constraints.push(vertical, horizontal);
	}
});
<|endoftext|>


<commit_before>import PropTypes from 'prop-types';
import React from 'react';
import { injectIntl, FormattedMessage } from 'react-intl';
import { reduxForm, Field, propTypes } from 'redux-form';
import Link from 'react-router/lib/Link';
// import MenuItem from 'material-ui/MenuItem';
import { Grid, Row, Col } from 'react-flexbox-grid/lib';
import withIntlForm from '../../common/hocs/IntlForm';
import AppButton from '../../common/AppButton';
import withHelp from '../../common/hocs/HelpfulContainer';
import CopyAllComponent from '../../common/CopyAllComponent';
import SourceCollectionsFieldList from '../../common/mediaPicker/SourceCollectionsFieldList';
import MediaPickerDialog from '../../common/mediaPicker/MediaPickerDialog';
import QueryHelpDialog from '../../common/help/QueryHelpDialog';
import MediaHelpDialog from '../../common/help/MediaHelpDialog';
import SavedSearchControls from './SavedSearchControls';
import { emptyString, validDate } from '../../../lib/formValidators';
import { isStartDateAfterEndDate, isValidSolrDate } from '../../../lib/dateUtil';
import { KEYWORD, MEDIA, DATES } from '../../../lib/explorerUtil';

const localMessages = {
  mainTitle: { id: 'explorer.queryBuilder.maintitle', defaultMessage: 'Create Query' },
  addButton: { id: 'explorer.queryBuilder.saveAll', defaultMessage: 'Search' },
  feedback: { id: 'explorer.queryBuilder.feedback', defaultMessage: 'We saved your new source' },
  query: { id: 'explorer.queryBuilder.query', defaultMessage: '1. Enter search terms' },
  queryDesc: { id: 'explorer.queryBuilder.query.desc', defaultMessage: 'Media Cloud will return stories that match your search query. We use standard boolean search syntax.' },
  queryDescLink: { id: 'explorer.queryBuilder.query.descLink', defaultMessage: 'Learn more about writing boolean search queries.' },
  selectSandC: { id: 'explorer.queryBuilder.selectSAndC', defaultMessage: '2. Select your media' },
  selectSandCDesc: { id: 'explorer.queryBuilder.selectSAndCDesc', defaultMessage: 'Choose individual sources or collections to be searched.' },
  selectSandCDescLink: { id: 'explorer.queryBuilder.selectSAndCDescLink', defaultMessage: 'Learn more about choosing media.' },
  SandC: { id: 'explorer.queryBuilder.sAndC', defaultMessage: 'Media' },
  color: { id: 'explorer.queryBuilder.color', defaultMessage: 'Choose a color' },
  dates: { id: 'explorer.queryBuilder.dates', defaultMessage: '3. Enter dates' },
  datesDesc: { id: 'explorer.queryBuilder.datesDesc', defaultMessage: 'Our database goes back to 2011, however for the most accurate and comprehensive results, we recommend staying within one year of the current date.' },
  dateTo: { id: 'explorer.queryBuilder.dateTo', defaultMessage: 'to' },
  queryHelpTitle: { id: 'explorer.queryBuilder.queryHelp.title', defaultMessage: 'Building Query Strings' },
  queryHelpContent: { id: 'explorer.queryBuilder.queryHelp.content', defaultMessage: '
You can write boolean queries to search against out database. To search for a single word, just enter that word:
gender
You can also use boolean and phrase searches like this:
"gender equality" OR "gender equity"' },
  saveSearch: { id: 'explorer.queryBuilder.saveQueries', defaultMessage: 'Save Search...' },
  queryStringError: { id: 'explorer.queryBuilder.queryStringError', defaultMessage: 'Your {name} query is missing keywords.' },
  startDateWarning: { id: 'explorer.queryBuilder.warning.startDate', defaultMessage: 'Start Date must be before End Date' },
  invalidDateWarning: { id: 'explorer.queryBuilder.warning.invalidDate', defaultMessage: 'Use the YYYY-MM-DD format' },
  noMediaSpecified: { id: 'explorer.queryBuilder.warning.noMediaSpecified', defaultMessage: 'Searching all media - generally not a great idea' },
  copyQueryKeywordTitle: { id: 'explorer.queryform.copyQueryQ', defaultMessage: 'Copy Query Keywords' },
  copyQueryDatesTitle: { id: 'explorer.queryform.copyQueryDates', defaultMessage: 'Copy Query Dates' },
  copyQueryMediaTitle: { id: 'explorer.queryform.copyQueryMedia', defaultMessage: 'Copy Query Media' },
  copyQueryKeywordMsg: { id: 'explorer.queryform.title.copyQueryQ', defaultMessage: 'Are you sure you want to copy these keywords to all your queries? This will replace the keyword for all your queries.' },
  copyQueryDatesMsg: { id: 'explorer.queryform.title.copyQueryDates', defaultMessage: 'Are you sure you want to copy these dates to all your queries? This will replace the dates for all your queries.' },
  copyQueryMediaMsg: { id: 'explorer.queryform.title.copyQueryMedia', defaultMessage: 'Are you sure you want to copy these media to all your queries? This will replace the media for all your queries.' },
};

class QueryForm extends React.Component {
  state = { // do not focus on primary textfield if we have a dialog open
    childDialogOpen: false,
  }

  setQueryFormChildDialogOpen = () => {
    this.setState({ childDialogOpen: !this.state.childDialogOpen });
  }

  render() {
    const { initialValues, onWillSearch, isEditable, selected, buttonLabel, onMediaDelete, onDateChange, handleLoadSearches, handleDeleteSearch, handleLoadSelectedSearch, savedSearches, searchNickname, handleSaveSearch,
      submitting, handleSubmit, onSave, onMediaChange, renderTextField, renderTextFieldWithFocus, handleCopyAll } = this.props;
    const { formatMessage } = this.props.intl;
    const cleanedInitialValues = initialValues ? { ...initialValues } : {};
    if (cleanedInitialValues.disabled === undefined) {
      cleanedInitialValues.disabled = false;
    }
    cleanedInitialValues.media = [  // merge intial sources and collections into one list for display with `renderFields`
      ...initialValues.sources,
      ...initialValues.collections,
    ];
    selected.media = [  // merge sources and collections into one list for display with `renderFields`
      ...selected.sources,
      ...selected.collections,
    ];
    const currentQ = selected.q;
    let mediaPicker = null;
    let mediaLabel = formatMessage(localMessages.SandC);
    if (isEditable) {
      mediaPicker = (
        <MediaPickerDialog
          initMedia={selected.media ? selected.media : cleanedInitialValues.media}
          onConfirmSelection={selections => onMediaChange(selections)}
          setQueryFormChildDialogOpen={this.setQueryFormChildDialogOpen}
        />
      );
      mediaLabel = formatMessage(localMessages.selectSandC);
    }
    if (!selected) { return null; }
    return (
      <form className="app-form query-form" name="queryForm" onSubmit={handleSubmit(onSave)}>

          <Grid>
            <Row>
              <Col lg={5}>

                  <CopyAllComponent label={formatMessage(localMessages.query)} title={formatMessage(localMessages.copyQueryKeywordTitle)} msg={formatMessage(localMessages.copyQueryKeywordMsg)} onOk={() => handleCopyAll(KEYWORD)} />

                  <FormattedMessage {...localMessages.queryDesc} />
                  <QueryHelpDialog
                    trigger={formatMessage(localMessages.queryDescLink)}
                    title={formatMessage(localMessages.query)}
                  />
                  <Field
                    className="query-field"
                    name="q"
                    type="text"
                    value={currentQ}
                    multiline
                    rows={3}
                    rowsMax={4}
                    fullWidth
                    onChange={this.focusSelect}
                    component={renderTextFieldWithFocus}
                  />

              </Col>
              <Col lg={1} />
              <Col lg={6}>

                  <CopyAllComponent label={mediaLabel} title={formatMessage(localMessages.copyQueryMediaTitle)} msg={formatMessage(localMessages.copyQueryMediaMsg)} onOk={() => handleCopyAll(MEDIA)} />

                  <FormattedMessage {...localMessages.selectSandCDesc} />
                  <MediaHelpDialog
                    trigger={formatMessage(localMessages.selectSandCDescLink)}
                  />
                  <SourceCollectionsFieldList
                    className="query-field"
                    form="queryForm"
                    destroyOnUnmount={false}
                    enableReinitialize
                    onDelete={onMediaDelete}
                    initialValues={cleanedInitialValues}
                    allowRemoval={isEditable}
                    showWarningIfEmpty
                  />
                  {mediaPicker}

                  <CopyAllComponent label={formatMessage(localMessages.dates)} title={formatMessage(localMessages.copyQueryDatesTitle)} msg={formatMessage(localMessages.copyQueryDatesMsg)} onOk={() => handleCopyAll(DATES)} />

                  <FormattedMessage {...localMessages.datesDesc} />

                  <Field
                    className="query-field start-date-wrapper"
                    maxLength="12"
                    name="startDate"
                    type="inline"
                    component={renderTextField}
                    disableunderline="true"
                    disabled={!isEditable}
                    onChange={onDateChange}
                  />

<FormattedMessage {...localMessages.dateTo} />

                  <Field
                    className="query-field end-date-wrapper"
                    maxLength="12"
                    name="endDate"
                    type="inline"
                    component={renderTextField}
                    disableunderline="true"
                    disabled={!isEditable}
                    onChange={onDateChange}
                  />

              </Col>
            </Row>
          </Grid>

          <Grid>
            <Row>
              <Col lg={6} />
              <Col lg={6} >

                  <SavedSearchControls
                    searchNickname={searchNickname}
                    savedSearches={savedSearches}
                    handleLoadSearches={handleLoadSearches}
                    handleLoadSelectedSearch={handleLoadSelectedSearch}
                    handleSaveSearch={l => handleSaveSearch(l)}
                    handleDeleteSearch={handleDeleteSearch}
                    submitting={submitting}
                    setQueryFormChildDialogOpen={this.setQueryFormChildDialogOpen}
                  />
                  <AppButton
                    type="submit"
                    label={buttonLabel}
                    disabled={submitting}
                    onClick={onWillSearch}
                    primary
                  />

              </Col>
            </Row>
          </Grid>

      </form>
    );
  }
}

QueryForm.propTypes = {
  // from context
  intl: PropTypes.object.isRequired,
  renderTextField: PropTypes.func.isRequired,
  renderSelect: PropTypes.func.isRequired,
  renderTextFieldWithFocus: PropTypes.func.isRequired,
  searchNickname: PropTypes.string.isRequired,
  savedSearches: PropTypes.array,

  // from parent
  selected: PropTypes.object.isRequired,
  onSave: PropTypes.func.isRequired,
  onColorChange: PropTypes.func,
  onMediaChange: PropTypes.func,
  buttonLabel: PropTypes.string.isRequired,
  initialValues: PropTypes.object,
  onWillSearch: PropTypes.func,
  handleLoadSearches: PropTypes.func.isRequired,
  handleLoadSelectedSearch: PropTypes.func.isRequired,
  handleSaveSearch: PropTypes.func.isRequired,
  handleDeleteSearch: PropTypes.func.isRequired,
  handleCopyAll: PropTypes.func.isRequired,
  onMediaDelete: PropTypes.func.isRequired,
  onDateChange: PropTypes.func.isRequired,
  // from form healper
  updateQuery: PropTypes.func,
  handleSubmit: PropTypes.func,
  pristine: PropTypes.bool.isRequired,
  submitting: PropTypes.bool.isRequired,
  isEditable: PropTypes.bool.isRequired,
  focusRequested: PropTypes.func.isRequired,
};

function validate(values, props) {
  const { formatMessage } = props.intl;
  const errors = {};
  if (emptyString(values.q)) {
    const errString = formatMessage(localMessages.queryStringError, { name: values.label });
    errors.q = { _error: errString };
  }
  if (!validDate(values.startDate) || !isValidSolrDate(values.startDate)) {
    errors.startDate = { _error: formatMessage(localMessages.invalidDateWarning) };
  }
  if (!validDate(values.endDate) || !isValidSolrDate(values.endDate)) {
    errors.endDate = { _error: formatMessage(localMessages.invalidDateWarning) };
  }
  if (validDate(values.startDate) && validDate(values.endDate) && isStartDateAfterEndDate(values.startDate, values.endDate)) {
    errors.startDate = { _error: formatMessage(localMessages.startDateWarning) };
  }

  return errors;
}

function warn(values, props) {
  const { formatMessage } = props.intl;
  const warnings = {};
  if ((!values.collections || !values.collections.length) &&
    (!values.sources || !values.sources.length) &&
    (!values.media || !values.media.length)) {
    warnings.media = { _warning: formatMessage(localMessages.noMediaSpecified) };
  }
  return warnings;
}

export default
  injectIntl(
    withIntlForm(
      withHelp(localMessages.queryHelpTitle, localMessages.queryHelpContent)(
        reduxForm({ propTypes, validate, warn })(
          QueryForm
        ),
      ),
    ),
  );
<commit_msg>I think we now have all the updates properly integrated<commit_after>import PropTypes from 'prop-types';
import React from 'react';
import { injectIntl, FormattedMessage } from 'react-intl';
import { reduxForm, Field, propTypes } from 'redux-form';
import { Grid, Row, Col } from 'react-flexbox-grid/lib';
import withIntlForm from '../../common/hocs/IntlForm';
import AppButton from '../../common/AppButton';
import withHelp from '../../common/hocs/HelpfulContainer';
import CopyAllComponent from '../../common/CopyAllComponent';
import SourceCollectionsFieldList from '../../common/mediaPicker/SourceCollectionsFieldList';
import MediaPickerDialog from '../../common/mediaPicker/MediaPickerDialog';
import QueryHelpDialog from '../../common/help/QueryHelpDialog';
import MediaHelpDialog from '../../common/help/MediaHelpDialog';
import SavedSearchControls from './SavedSearchControls';
import { emptyString, validDate } from '../../../lib/formValidators';
import { isStartDateAfterEndDate, isValidSolrDate } from '../../../lib/dateUtil';
import { KEYWORD, MEDIA, DATES } from '../../../lib/explorerUtil';

const localMessages = {
  mainTitle: { id: 'explorer.queryBuilder.maintitle', defaultMessage: 'Create Query' },
  addButton: { id: 'explorer.queryBuilder.saveAll', defaultMessage: 'Search' },
  feedback: { id: 'explorer.queryBuilder.feedback', defaultMessage: 'We saved your new source' },
  query: { id: 'explorer.queryBuilder.query', defaultMessage: '1. Enter search terms' },
  queryDesc: { id: 'explorer.queryBuilder.query.desc', defaultMessage: 'Media Cloud will return stories that match your search query. We use standard boolean search syntax.' },
  queryDescLink: { id: 'explorer.queryBuilder.query.descLink', defaultMessage: 'Learn more about writing boolean search queries.' },
  selectSandC: { id: 'explorer.queryBuilder.selectSAndC', defaultMessage: '2. Select your media' },
  selectSandCDesc: { id: 'explorer.queryBuilder.selectSAndCDesc', defaultMessage: 'Choose individual sources or collections to be searched.' },
  selectSandCDescLink: { id: 'explorer.queryBuilder.selectSAndCDescLink', defaultMessage: 'Learn more about choosing media.' },
  SandC: { id: 'explorer.queryBuilder.sAndC', defaultMessage: 'Media' },
  color: { id: 'explorer.queryBuilder.color', defaultMessage: 'Choose a color' },
  dates: { id: 'explorer.queryBuilder.dates', defaultMessage: '3. Enter dates' },
  datesDesc: { id: 'explorer.queryBuilder.datesDesc', defaultMessage: 'Our database goes back to 2011, however for the most accurate and comprehensive results, we recommend staying within one year of the current date.' },
  dateTo: { id: 'explorer.queryBuilder.dateTo', defaultMessage: 'to' },
  queryHelpTitle: { id: 'explorer.queryBuilder.queryHelp.title', defaultMessage: 'Building Query Strings' },
  queryHelpContent: { id: 'explorer.queryBuilder.queryHelp.content', defaultMessage: '
You can write boolean queries to search against out database. To search for a single word, just enter that word:
gender
You can also use boolean and phrase searches like this:
"gender equality" OR "gender equity"' },
  saveSearch: { id: 'explorer.queryBuilder.saveQueries', defaultMessage: 'Save Search...' },
  queryStringError: { id: 'explorer.queryBuilder.queryStringError', defaultMessage: 'Your {name} query is missing keywords.' },
  startDateWarning: { id: 'explorer.queryBuilder.warning.startDate', defaultMessage: 'Start Date must be before End Date' },
  invalidDateWarning: { id: 'explorer.queryBuilder.warning.invalidDate', defaultMessage: 'Use the YYYY-MM-DD format' },
  noMediaSpecified: { id: 'explorer.queryBuilder.warning.noMediaSpecified', defaultMessage: 'Searching all media - generally not a great idea' },
  copyQueryKeywordTitle: { id: 'explorer.queryform.copyQueryQ', defaultMessage: 'Copy Query Keywords' },
  copyQueryDatesTitle: { id: 'explorer.queryform.copyQueryDates', defaultMessage: 'Copy Query Dates' },
  copyQueryMediaTitle: { id: 'explorer.queryform.copyQueryMedia', defaultMessage: 'Copy Query Media' },
  copyQueryKeywordMsg: { id: 'explorer.queryform.title.copyQueryQ', defaultMessage: 'Are you sure you want to copy these keywords to all your queries? This will replace the keyword for all your queries.' },
  copyQueryDatesMsg: { id: 'explorer.queryform.title.copyQueryDates', defaultMessage: 'Are you sure you want to copy these dates to all your queries? This will replace the dates for all your queries.' },
  copyQueryMediaMsg: { id: 'explorer.queryform.title.copyQueryMedia', defaultMessage: 'Are you sure you want to copy these media to all your queries? This will replace the media for all your queries.' },
};

class QueryForm extends React.Component {
  state = { // do not focus on primary textfield if we have a dialog open
    childDialogOpen: false,
  }

  setQueryFormChildDialogOpen = () => {
    this.setState({ childDialogOpen: !this.state.childDialogOpen });
  }

  render() {
    const { initialValues, onWillSearch, isEditable, selected, buttonLabel, onMediaDelete, onDateChange, handleLoadSearches, handleDeleteSearch, handleLoadSelectedSearch, savedSearches, searchNickname, handleSaveSearch,
      submitting, handleSubmit, onSave, onMediaChange, renderTextField, renderTextFieldWithFocus, handleCopyAll } = this.props;
    const { formatMessage } = this.props.intl;
    const cleanedInitialValues = initialValues ? { ...initialValues } : {};
    if (cleanedInitialValues.disabled === undefined) {
      cleanedInitialValues.disabled = false;
    }
    cleanedInitialValues.media = [  // merge intial sources and collections into one list for display with `renderFields`
      ...initialValues.sources,
      ...initialValues.collections,
    ];
    selected.media = [  // merge sources and collections into one list for display with `renderFields`
      ...selected.sources,
      ...selected.collections,
    ];
    const currentQ = selected.q;
    let mediaPicker = null;
    let mediaLabel = formatMessage(localMessages.SandC);
    if (isEditable) {
      mediaPicker = (
        <MediaPickerDialog
          initMedia={selected.media ? selected.media : cleanedInitialValues.media}
          onConfirmSelection={selections => onMediaChange(selections)}
          setQueryFormChildDialogOpen={this.setQueryFormChildDialogOpen}
        />
      );
      mediaLabel = formatMessage(localMessages.selectSandC);
    }
    if (!selected) { return null; }
    return (
      <form className="app-form query-form" name="queryForm" onSubmit={handleSubmit(onSave)}>

          <Grid>
            <Row>
              <Col lg={5}>

                  <CopyAllComponent label={formatMessage(localMessages.query)} title={formatMessage(localMessages.copyQueryKeywordTitle)} msg={formatMessage(localMessages.copyQueryKeywordMsg)} onOk={() => handleCopyAll(KEYWORD)} />

                  <FormattedMessage {...localMessages.queryDesc} />
                  <QueryHelpDialog
                    trigger={formatMessage(localMessages.queryDescLink)}
                    title={formatMessage(localMessages.query)}
                  />
                  <Field
                    className="query-field"
                    name="q"
                    type="text"
                    value={currentQ}
                    multiline
                    rows={3}
                    rowsMax={4}
                    fullWidth
                    onChange={this.focusSelect}
                    component={renderTextFieldWithFocus}
                  />

              </Col>
              <Col lg={1} />
              <Col lg={6}>

                  <CopyAllComponent label={mediaLabel} title={formatMessage(localMessages.copyQueryMediaTitle)} msg={formatMessage(localMessages.copyQueryMediaMsg)} onOk={() => handleCopyAll(MEDIA)} />

                  <FormattedMessage {...localMessages.selectSandCDesc} />
                  <MediaHelpDialog
                    trigger={formatMessage(localMessages.selectSandCDescLink)}
                  />
                  <SourceCollectionsFieldList
                    className="query-field"
                    form="queryForm"
                    destroyOnUnmount={false}
                    enableReinitialize
                    onDelete={onMediaDelete}
                    initialValues={cleanedInitialValues}
                    allowRemoval={isEditable}
                    showWarningIfEmpty
                  />
                  {mediaPicker}

                  <CopyAllComponent label={formatMessage(localMessages.dates)} title={formatMessage(localMessages.copyQueryDatesTitle)} msg={formatMessage(localMessages.copyQueryDatesMsg)} onOk={() => handleCopyAll(DATES)} />

                  <FormattedMessage {...localMessages.datesDesc} />

                  <Field
                    className="query-field start-date-wrapper"
                    maxLength="12"
                    name="startDate"
                    type="inline"
                    component={renderTextField}
                    disableunderline="true"
                    disabled={!isEditable}
                    onChange={onDateChange}
                  />

<FormattedMessage {...localMessages.dateTo} />

                  <Field
                    className="query-field end-date-wrapper"
                    maxLength="12"
                    name="endDate"
                    type="inline"
                    component={renderTextField}
                    disableunderline="true"
                    disabled={!isEditable}
                    onChange={onDateChange}
                  />

              </Col>
            </Row>
          </Grid>

          <Grid>
            <Row>
              <Col lg={6} />
              <Col lg={6} >

                  <SavedSearchControls
                    searchNickname={searchNickname}
                    savedSearches={savedSearches}
                    handleLoadSearches={handleLoadSearches}
                    handleLoadSelectedSearch={handleLoadSelectedSearch}
                    handleSaveSearch={l => handleSaveSearch(l)}
                    handleDeleteSearch={handleDeleteSearch}
                    submitting={submitting}
                    setQueryFormChildDialogOpen={this.setQueryFormChildDialogOpen}
                  />
                  <AppButton
                    type="submit"
                    label={buttonLabel}
                    disabled={submitting}
                    onClick={onWillSearch}
                    primary
                  />

              </Col>
            </Row>
          </Grid>

      </form>
    );
  }
}

QueryForm.propTypes = {
  // from context
  intl: PropTypes.object.isRequired,
  renderTextField: PropTypes.func.isRequired,
  renderSelect: PropTypes.func.isRequired,
  renderTextFieldWithFocus: PropTypes.func.isRequired,
  searchNickname: PropTypes.string.isRequired,
  savedSearches: PropTypes.array,

  // from parent
  selected: PropTypes.object.isRequired,
  onSave: PropTypes.func.isRequired,
  onColorChange: PropTypes.func,
  onMediaChange: PropTypes.func,
  buttonLabel: PropTypes.string.isRequired,
  initialValues: PropTypes.object,
  onWillSearch: PropTypes.func,
  handleLoadSearches: PropTypes.func.isRequired,
  handleLoadSelectedSearch: PropTypes.func.isRequired,
  handleSaveSearch: PropTypes.func.isRequired,
  handleDeleteSearch: PropTypes.func.isRequired,
  handleCopyAll: PropTypes.func.isRequired,
  onMediaDelete: PropTypes.func.isRequired,
  onDateChange: PropTypes.func.isRequired,
  // from form healper
  updateQuery: PropTypes.func,
  handleSubmit: PropTypes.func,
  pristine: PropTypes.bool.isRequired,
  submitting: PropTypes.bool.isRequired,
  isEditable: PropTypes.bool.isRequired,
  focusRequested: PropTypes.func.isRequired,
};

function validate(values, props) {
  const { formatMessage } = props.intl;
  const errors = {};
  if (emptyString(values.q)) {
    const errString = formatMessage(localMessages.queryStringError, { name: values.label });
    errors.q = { _error: errString };
  }
  if (!validDate(values.startDate) || !isValidSolrDate(values.startDate)) {
    errors.startDate = { _error: formatMessage(localMessages.invalidDateWarning) };
  }
  if (!validDate(values.endDate) || !isValidSolrDate(values.endDate)) {
    errors.endDate = { _error: formatMessage(localMessages.invalidDateWarning) };
  }
  if (validDate(values.startDate) && validDate(values.endDate) && isStartDateAfterEndDate(values.startDate, values.endDate)) {
    errors.startDate = { _error: formatMessage(localMessages.startDateWarning) };
  }

  return errors;
}

function warn(values, props) {
  const { formatMessage } = props.intl;
  const warnings = {};
  if ((!values.collections || !values.collections.length) &&
    (!values.sources || !values.sources.length) &&
    (!values.media || !values.media.length)) {
    warnings.media = { _warning: formatMessage(localMessages.noMediaSpecified) };
  }
  return warnings;
}

export default
  injectIntl(
    withIntlForm(
      withHelp(localMessages.queryHelpTitle, localMessages.queryHelpContent)(
        reduxForm({ propTypes, validate, warn })(
          QueryForm
        ),
      ),
    ),
  );
<|endoftext|>


<commit_before>view = Backbone.View.extend();

view.prototype.events = {
    'click input[type=submit]': 'save',
    'click a[href=#open]': 'browse',
    'click a[href=#favorite]': 'favoriteToggle',
    'keyup input[name=file], input[name=connection]': 'favoriteUpdate',
    'change input[name=file], input[name=connection]': 'favoriteUpdate',
    'click a[href=#cacheFlush]': 'cacheFlush',
    'change select[name=srs-name]': 'nameToSrs',
    'keyup input[name=srs]': 'srsToName'
};

view.prototype.initialize = function(options) {
    if (!options.favorites) throw new Error('options.favorites required.');

    _(this).bindAll(
        'render',
        'save',
        'browse',
        'favoriteToggle',
        'favoriteUpdate',
        'cacheFlush',
        'nameToSrs',
        'srsToName'
    );
    this.favorites = options.favorites;
    this.render();
};

view.prototype.render = function() {
    this.$('.content').html(templates.Layer(this.model));

    // Quick easy way to check and set whether input
    // URI is favorited.
    this.$('input[name=file], input[name=connection]').change();

    // Set active tab.
    var type = this.model.get('Datasource') && this.model.get('Datasource').type;
    this.$('a[href=#layer-' + (type||'file') + ']').click();

    // Autofocus first field for new layers.
    if (!this.model.id) this.$('input[type=text]:first').focus();
    return this;
};

view.prototype.nameToSrs = function(ev) {
    var el = $(ev.currentTarget);
    var name = $(ev.currentTarget).val();
    if (this.model.SRS[name]) {
        el.siblings('input[name=srs]').val(this.model.SRS[name]);
    } else if (name === 'autodetect') {
        el.siblings('input[name=srs]').val('');
    }
};

view.prototype.srsToName = function(ev) {
    var el = $(ev.currentTarget);
    var srs = $(ev.currentTarget).val();
    el.siblings('select[name=srs-name]').val(this.model.srsName(srs));
};

view.prototype.favoriteToggle = function(ev) {
    var form = $(ev.currentTarget).parents('form');
    var uri = $('input[name=file], input[name=connection]', form).val();
    // @TODO wait for 'success'? Throw errors?
    if (this.favorites.get(uri)) {
        var model = this.favorites.get(uri);
        this.favorites.remove(uri);
        model.destroy();
        $(ev.currentTarget).removeClass('active');
    } else if (uri) {
        var model = new models.Favorite({id:uri});
        this.favorites.add(model);
        model.save();
        $(ev.currentTarget).addClass('active');
    }
    return false;
};

view.prototype.favoriteUpdate = function(ev) {
    var target = $(ev.currentTarget);
    var favorite = target.siblings('a.favorite');
    var uri = target.val();
    if (uri.match(/^(\/|http:\/\/|(.+\s)?dbname=[\w]+)/)) {
        favorite.removeClass('hidden');
        if (this.favorites.isFavorite(uri)) {
            favorite.addClass('active');
        } else {
            favorite.removeClass('active');
        }
    } else {
        favorite.addClass('hidden');
    }

    // Show cache clear link if datasource points to a URL and we're editing
    // an existing layer.
    var cache = target.siblings('.cache');
    if (this.model.id && uri.match(/^http:\/\/|^https:\/\//)) {
        cache.removeClass('hidden');
    } else {
        cache.addClass('hidden');
    }
    return false;
};

view.prototype.browse = function(ev) {
    var target = $(ev.currentTarget);
    var form = target.parents('form');
    var id = (function(form) {
        if (form.hasClass('layer-file')) return 'file';
        if (form.hasClass('layer-sqlite')) return 'sqlite';
        if (form.hasClass('layer-postgis')) return 'favoritesPostGIS';
    })(form);
    var components = $('input.browsable', form).val().split('/');
    var location = components.slice(0, components.length - 1).join('/');

    target
        .toggleClass('active')
        .text(target.hasClass('active') ? 'Done' : 'Browse');
    $('ul.form', form).toggleClass('expand');

    if (target.is('.active')) (new models.Library({
        id:id,
        location:location,
        project: this.model.collection.parent.id
    })).fetch({
        success: _(function(model, resp) {
            new views.Library({
                model: model,
                favorites: this.favorites,
                change: function(uri) {
                    $('input.browsable', form).val(uri);
                    if ($('input[name=id]', form).val() == '') {
                        // Get the 'basename' of the file, minus anything
                        // after the first dot, in alphanumeric
                        // and lowercase.
                        $('input[name=id]', form).val(
                            _(uri.split('/')).last().split('.')[0]
                                .replace(/[^a-z0-9]/gi,'').toLowerCase());
                    }
                },
                el: $('.browser', form)
            });
        }).bind(this),
        error: function(model, err) { new views.Modal(err) }
    });
    return false;
};

view.prototype.autostyle = function() {
    var root = this.model.collection.parent;
    var stylesheets = root.get('Stylesheet');
    if (stylesheets.length !== 0) {
        var cm = stylesheets.models[0].codemirror;
        var coord = cm.coordsFromIndex(Infinity);
        cm.replaceRange(
            templates.Autostyle(this.model),
            coord,
            coord);
        $('.actions a[href=#save]').click();
    }
};

view.prototype.save = function(e) {
    var form = $(e.target).parents('form');
    var autostyle = $(e.target).hasClass('with-style');
    var parseOptions = function (o) {
        return _(o.match(/([\d\w]*)\=(\"[^\"]*\"|[^\s]*)/g)).reduce(function(memo,pair) {
            pair = pair.replace(/"|'/g, '').split('=');
            memo[pair[0]] = pair[1];
            return memo;
        }, {});
    };
    var deepSet = function(attr, keys, val) {
        if (!keys.length) return val;
        var key = keys.shift();
        attr[key] = deepSet(attr[key]||{}, keys, val);
        return attr;
    };
    var attr = _($('input[name]:not(.parsable),textarea',form)).reduce(function(memo, el) {
        return deepSet(memo, $(el).attr('name').split('.'), $(el).val());
    }, {});
    attr['name'] = (attr['id']||'').replace('#','');
    attr['id'] = (attr['id']||'').replace('#','');
    attr['class'] = (attr['class']||'').replace('.','');
    attr['srs'] = attr['srs'] || this.model.SRS['900913'];
    attr['Datasource'] = _(attr['Datasource']||{})
        .defaults(parseOptions($('input[name=advanced]', form).val()));

    // Parse PostGIS connection options.
    if ($('input[name=connection]', form).size()) {
        var allowedArgs = ['user', 'password', 'dbname', 'port', 'host'];
        var connection = parseOptions($('input[name=connection]', form).val());
        for (var key in connection) if (!_(allowedArgs).include(key)) {
            new views.Modal(new Error('Invalid argument ' + key + ' in PostgreSQL connection string.'));
            return false;
        }
        if (!_(connection).size()) {
            new views.Modal(new Error('Invalid PostgreSQL connection string.'));
            return false;
        }
        if (!connection.dbname) {
            new views.Modal(new Error('dbname is required in PostgreSQL connection string.'));
            return false;
        }
        attr.Datasource = _(attr.Datasource).defaults(connection);
    }

    $(this.el).addClass('loading').addClass('restartable');
    var error = _(function(m, e) {
        if ($(this.el).hasClass('restarting')) return false;
        $(this.el).removeClass('loading').removeClass('restartable');
        new views.Modal(e);
    }).bind(this);
    this.model.validateAsync(attr, {
        success: _(function() {
            $(this.el).removeClass('loading').removeClass('restartable');
            if (!this.model.set(attr, {error:error})) return;
            if (!this.model.collection.include(this.model)) {
                this.model.collection.add(this.model);
                if (autostyle) this.autostyle();
            }
            this.$('.close').click();
        }).bind(this),
        error: error
    });
    return false;
};

view.prototype.cacheFlush = function(ev) {
    $(this.el).addClass('loading');
    var url = this.$('form.layer-file input[name=file]').val();
    this.model.collection.parent.flush(this.model.id, url, {
        success: _(function(m, resp) {
            $(this.el).removeClass('loading');
        }).bind(this),
        error: _(function(m, err) {
            $(this.el).removeClass('loading');
            new views.Modal(err);
        }).bind(this)
    });
    return false;
};

<commit_msg>Preserve empty string srs for file type layers. Refs #1081.<commit_after>view = Backbone.View.extend();

view.prototype.events = {
    'click input[type=submit]': 'save',
    'click a[href=#open]': 'browse',
    'click a[href=#favorite]': 'favoriteToggle',
    'keyup input[name=file], input[name=connection]': 'favoriteUpdate',
    'change input[name=file], input[name=connection]': 'favoriteUpdate',
    'click a[href=#cacheFlush]': 'cacheFlush',
    'change select[name=srs-name]': 'nameToSrs',
    'keyup input[name=srs]': 'srsToName'
};

view.prototype.initialize = function(options) {
    if (!options.favorites) throw new Error('options.favorites required.');

    _(this).bindAll(
        'render',
        'save',
        'browse',
        'favoriteToggle',
        'favoriteUpdate',
        'cacheFlush',
        'nameToSrs',
        'srsToName'
    );
    this.favorites = options.favorites;
    this.render();
};

view.prototype.render = function() {
    this.$('.content').html(templates.Layer(this.model));

    // Quick easy way to check and set whether input
    // URI is favorited.
    this.$('input[name=file], input[name=connection]').change();

    // Set active tab.
    var type = this.model.get('Datasource') && this.model.get('Datasource').type;
    this.$('a[href=#layer-' + (type||'file') + ']').click();

    // Autofocus first field for new layers.
    if (!this.model.id) this.$('input[type=text]:first').focus();
    return this;
};

view.prototype.nameToSrs = function(ev) {
    var el = $(ev.currentTarget);
    var name = $(ev.currentTarget).val();
    if (this.model.SRS[name]) {
        el.siblings('input[name=srs]').val(this.model.SRS[name]);
    } else if (name === 'autodetect') {
        el.siblings('input[name=srs]').val('');
    }
};

view.prototype.srsToName = function(ev) {
    var el = $(ev.currentTarget);
    var srs = $(ev.currentTarget).val();
    el.siblings('select[name=srs-name]').val(this.model.srsName(srs));
};

view.prototype.favoriteToggle = function(ev) {
    var form = $(ev.currentTarget).parents('form');
    var uri = $('input[name=file], input[name=connection]', form).val();
    // @TODO wait for 'success'? Throw errors?
    if (this.favorites.get(uri)) {
        var model = this.favorites.get(uri);
        this.favorites.remove(uri);
        model.destroy();
        $(ev.currentTarget).removeClass('active');
    } else if (uri) {
        var model = new models.Favorite({id:uri});
        this.favorites.add(model);
        model.save();
        $(ev.currentTarget).addClass('active');
    }
    return false;
};

view.prototype.favoriteUpdate = function(ev) {
    var target = $(ev.currentTarget);
    var favorite = target.siblings('a.favorite');
    var uri = target.val();
    if (uri.match(/^(\/|http:\/\/|(.+\s)?dbname=[\w]+)/)) {
        favorite.removeClass('hidden');
        if (this.favorites.isFavorite(uri)) {
            favorite.addClass('active');
        } else {
            favorite.removeClass('active');
        }
    } else {
        favorite.addClass('hidden');
    }

    // Show cache clear link if datasource points to a URL and we're editing
    // an existing layer.
    var cache = target.siblings('.cache');
    if (this.model.id && uri.match(/^http:\/\/|^https:\/\//)) {
        cache.removeClass('hidden');
    } else {
        cache.addClass('hidden');
    }
    return false;
};

view.prototype.browse = function(ev) {
    var target = $(ev.currentTarget);
    var form = target.parents('form');
    var id = (function(form) {
        if (form.hasClass('layer-file')) return 'file';
        if (form.hasClass('layer-sqlite')) return 'sqlite';
        if (form.hasClass('layer-postgis')) return 'favoritesPostGIS';
    })(form);
    var components = $('input.browsable', form).val().split('/');
    var location = components.slice(0, components.length - 1).join('/');

    target
        .toggleClass('active')
        .text(target.hasClass('active') ? 'Done' : 'Browse');
    $('ul.form', form).toggleClass('expand');

    if (target.is('.active')) (new models.Library({
        id:id,
        location:location,
        project: this.model.collection.parent.id
    })).fetch({
        success: _(function(model, resp) {
            new views.Library({
                model: model,
                favorites: this.favorites,
                change: function(uri) {
                    $('input.browsable', form).val(uri);
                    if ($('input[name=id]', form).val() == '') {
                        // Get the 'basename' of the file, minus anything
                        // after the first dot, in alphanumeric
                        // and lowercase.
                        $('input[name=id]', form).val(
                            _(uri.split('/')).last().split('.')[0]
                                .replace(/[^a-z0-9]/gi,'').toLowerCase());
                    }
                },
                el: $('.browser', form)
            });
        }).bind(this),
        error: function(model, err) { new views.Modal(err) }
    });
    return false;
};

view.prototype.autostyle = function() {
    var root = this.model.collection.parent;
    var stylesheets = root.get('Stylesheet');
    if (stylesheets.length !== 0) {
        var cm = stylesheets.models[0].codemirror;
        var coord = cm.coordsFromIndex(Infinity);
        cm.replaceRange(
            templates.Autostyle(this.model),
            coord,
            coord);
        $('.actions a[href=#save]').click();
    }
};

view.prototype.save = function(e) {
    var form = $(e.target).parents('form');
    var autostyle = $(e.target).hasClass('with-style');
    var parseOptions = function (o) {
        return _(o.match(/([\d\w]*)\=(\"[^\"]*\"|[^\s]*)/g)).reduce(function(memo,pair) {
            pair = pair.replace(/"|'/g, '').split('=');
            memo[pair[0]] = pair[1];
            return memo;
        }, {});
    };
    var deepSet = function(attr, keys, val) {
        if (!keys.length) return val;
        var key = keys.shift();
        attr[key] = deepSet(attr[key]||{}, keys, val);
        return attr;
    };
    var attr = _($('input[name]:not(.parsable),textarea',form)).reduce(function(memo, el) {
        return deepSet(memo, $(el).attr('name').split('.'), $(el).val());
    }, {});
    attr['name'] = (attr['id']||'').replace('#','');
    attr['id'] = (attr['id']||'').replace('#','');
    attr['class'] = (attr['class']||'').replace('.','');
    attr['srs'] = attr['srs']||'';
    attr['Datasource'] = _(attr['Datasource']||{})
        .defaults(parseOptions($('input[name=advanced]', form).val()));

    // Database datasources do not have the luxury of SRS autodetection.
    // Fallback to web mercator if unset.
    if (attr['type'] === 'sqlite' || attr['type'] === 'postgis')
        attr['srs'] = attr['srs'] || this.model.SRS['900913'];

    // Parse PostGIS connection options.
    if ($('input[name=connection]', form).size()) {
        var allowedArgs = ['user', 'password', 'dbname', 'port', 'host'];
        var connection = parseOptions($('input[name=connection]', form).val());
        for (var key in connection) if (!_(allowedArgs).include(key)) {
            new views.Modal(new Error('Invalid argument ' + key + ' in PostgreSQL connection string.'));
            return false;
        }
        if (!_(connection).size()) {
            new views.Modal(new Error('Invalid PostgreSQL connection string.'));
            return false;
        }
        if (!connection.dbname) {
            new views.Modal(new Error('dbname is required in PostgreSQL connection string.'));
            return false;
        }
        attr.Datasource = _(attr.Datasource).defaults(connection);
    }

    $(this.el).addClass('loading').addClass('restartable');
    var error = _(function(m, e) {
        if ($(this.el).hasClass('restarting')) return false;
        $(this.el).removeClass('loading').removeClass('restartable');
        new views.Modal(e);
    }).bind(this);
    this.model.validateAsync(attr, {
        success: _(function() {
            $(this.el).removeClass('loading').removeClass('restartable');
            if (!this.model.set(attr, {error:error})) return;
            if (!this.model.collection.include(this.model)) {
                this.model.collection.add(this.model);
                if (autostyle) this.autostyle();
            }
            this.$('.close').click();
        }).bind(this),
        error: error
    });
    return false;
};

view.prototype.cacheFlush = function(ev) {
    $(this.el).addClass('loading');
    var url = this.$('form.layer-file input[name=file]').val();
    this.model.collection.parent.flush(this.model.id, url, {
        success: _(function(m, resp) {
            $(this.el).removeClass('loading');
        }).bind(this),
        error: _(function(m, err) {
            $(this.el).removeClass('loading');
            new views.Modal(err);
        }).bind(this)
    });
    return false;
};

<|endoftext|>


<commit_before>'use strict';

var JX = require('./javelin').JX;

require('./AphlictListenerList');
require('./AphlictLog');

var util = require('util');
var WebSocket = require('ws');

JX.install('AphlictClientServer', {

  construct: function(server) {
    this.setListenerList(new JX.AphlictListenerList());
    this.setLogger(new JX.AphlictLog());
    this._server = server;
  },

  members: {
    _server: null,

    listen: function() {
      var self = this;
      var server = this._server.listen.apply(this._server, arguments);
      var wss = new WebSocket.Server({server: server});

      wss.on('connection', function(ws) {
        var listener = self.getListenerList().addListener(ws);

        function log() {
          self.getLogger().log(
            util.format('<%s>', listener.getDescription()) +
            ' ' +
            util.format.apply(null, arguments));
        }

        log('Connected from %s.', ws._socket.remoteAddress);

        ws.on('message', function(data) {
          log('Received message: %s', data);

          var message;
          try {
            message = JSON.parse(data);
          } catch (err) {
            log('Message is invalid: %s', err.message);
            return;
          }

          switch (message.command) {
            case 'subscribe':
              log(
                'Subscribed to: %s',
                JSON.stringify(message.data));
              listener.subscribe(message.data);
              break;

            case 'unsubscribe':
              log(
                'Unsubscribed from: %s',
                JSON.stringify(message.data));
              listener.unsubscribe(message.data);
              break;

            default:
              log(
                'Unrecognized command "%s".',
                message.command || '<undefined>');
          }
        });

        wss.on('close', function() {
          self.getListenerList().removeListener(listener);
          log('Disconnected.');
        });

        wss.on('error', function(err) {
          log('Error: %s', err.message);
        });

      });

    },

  },

  properties: {
    listenerList: null,
    logger: null,
  }

});
<commit_msg>Aphlict - remove listeners when clients close the connection<commit_after>'use strict';

var JX = require('./javelin').JX;

require('./AphlictListenerList');
require('./AphlictLog');

var util = require('util');
var WebSocket = require('ws');

JX.install('AphlictClientServer', {

  construct: function(server) {
    this.setListenerList(new JX.AphlictListenerList());
    this.setLogger(new JX.AphlictLog());
    this._server = server;
  },

  members: {
    _server: null,

    listen: function() {
      var self = this;
      var server = this._server.listen.apply(this._server, arguments);
      var wss = new WebSocket.Server({server: server});

      wss.on('connection', function(ws) {
        var listener = self.getListenerList().addListener(ws);

        function log() {
          self.getLogger().log(
            util.format('<%s>', listener.getDescription()) +
            ' ' +
            util.format.apply(null, arguments));
        }

        log('Connected from %s.', ws._socket.remoteAddress);

        ws.on('message', function(data) {
          log('Received message: %s', data);

          var message;
          try {
            message = JSON.parse(data);
          } catch (err) {
            log('Message is invalid: %s', err.message);
            return;
          }

          switch (message.command) {
            case 'subscribe':
              log(
                'Subscribed to: %s',
                JSON.stringify(message.data));
              listener.subscribe(message.data);
              break;

            case 'unsubscribe':
              log(
                'Unsubscribed from: %s',
                JSON.stringify(message.data));
              listener.unsubscribe(message.data);
              break;

            default:
              log(
                'Unrecognized command "%s".',
                message.command || '<undefined>');
          }
        });

        ws.on('close', function() {
          self.getListenerList().removeListener(listener);
          log('Disconnected.');
        });

        wss.on('close', function() {
          self.getListenerList().removeListener(listener);
          log('Disconnected.');
        });

        wss.on('error', function(err) {
          log('Error: %s', err.message);
        });

      });

    },

  },

  properties: {
    listenerList: null,
    logger: null,
  }

});
<|endoftext|>


<commit_before>view = Backbone.View.extend();

view.prototype.events = {
    'click .layerFile input[type=submit]': 'saveFile',
    'click .layerPostGIS input[type=submit]': 'savePostGIS',
    'click .layerSqlite input[type=submit]': 'saveSqlite',
    'click a[href=#open]': 'browse',
    'click a[href=#favorite]': 'favoriteToggle',
    'keyup input[name=file], input[name=connection]': 'favoriteUpdate',
    'change input[name=file], input[name=connection]': 'favoriteUpdate',
    'click a[href=#cacheFlush]': 'cacheFlush',
    'change select[name=srs-name]': 'nameToSrs',
    'keyup input[name=srs]': 'srsToName'
};

view.prototype.initialize = function(options) {
    if (!options.favorites) throw new Error('options.favorites required.');

    _(this).bindAll(
        'render',
        'saveFile',
        'savePostGIS',
        'browse',
        'favoriteToggle',
        'favoriteUpdate',
        'cacheFlush',
        'nameToSrs',
        'srsToName'
    );
    this.favorites = options.favorites;
    this.render();
};

view.prototype.render = function() {
    this.$('.content').html(templates.Layer(this.model));

    // Quick easy way to check and set whether input
    // URI is favorited.
    this.$('input[name=file], input[name=connection]').change();

    if (this.model.get('Datasource')) {
        if (this.model.get('Datasource').type == 'sqlite') {
            this.$('a[href=#layerSqlite]').click();
        } else if (this.model.get('Datasource').file) {
            this.$('a[href=#layerFile]').click();
        } else if (this.model.get('Datasource').type == 'postgis') {
            this.$('a[href=#layerPostGIS]').click();
        }
    }

    // Autofocus first field for new layers.
    if (!this.model.id) this.$('input[type=text]:first').focus();
    return this;
};

view.prototype.nameToSrs = function(ev) {
    var el = $(ev.currentTarget);
    var name = $(ev.currentTarget).val();
    if (this.model.SRS[name]) {
        el.siblings('input[name=srs]').val(this.model.SRS[name]);
    } else if (name === 'autodetect') {
        el.siblings('input[name=srs]').val('');
    }
};

view.prototype.srsToName = function(ev) {
    var el = $(ev.currentTarget);
    var srs = $(ev.currentTarget).val();
    el.siblings('select[name=srs-name]').val(this.model.srsName(srs));
};

view.prototype.favoriteToggle = function(ev) {
    var form = $(ev.currentTarget).parents('form');
    var uri = $('input[name=file], input[name=connection]', form).val();
    // @TODO wait for 'success'? Throw errors?
    if (this.favorites.get(uri)) {
        var model = this.favorites.get(uri);
        this.favorites.remove(uri);
        model.destroy();
        $(ev.currentTarget).removeClass('active');
    } else if (uri) {
        var model = new models.Favorite({id:uri});
        this.favorites.add(model);
        model.save();
        $(ev.currentTarget).addClass('active');
    }
    return false;
};

view.prototype.favoriteUpdate = function(ev) {
    var target = $(ev.currentTarget);
    var favorite = target.siblings('a.favorite');
    var uri = target.val();
    if (uri.match(/^(\/|http:\/\/|(.+\s)?dbname=[\w]+)/)) {
        favorite.removeClass('hidden');
        if (this.favorites.isFavorite(uri)) {
            favorite.addClass('active');
        } else {
            favorite.removeClass('active');
        }
    } else {
        favorite.addClass('hidden');
    }

    // Show cache clear link if datasource points to a URL and we're editing
    // an existing layer.
    var cache = target.siblings('.cache');
    if (this.model.id && uri.match(/^http:\/\/|^https:\/\//)) {
        cache.removeClass('hidden');
    } else {
        cache.addClass('hidden');
    }
    return false;
};

view.prototype.browse = function(ev) {
    var target = $(ev.currentTarget);
    target
        .toggleClass('active')
        .text(target.hasClass('active') ? 'Done' : 'Browse');

    var id;
    var form = target.parents('form');
    if (form.is('.layerFile')) {
        id = 'file';
    } else if (form.is('.layerSqlite')) {
        id = 'sqlite';
    } else if (form.is('.layerPostGIS')) {
        id = 'favoritesPostGIS';
    }
    $('ul.form', form).toggleClass('expand');

    var components = $('input[name=file], input[name=connection]', form)
        .val()
        .split('/');
    var location = components.slice(0, components.length - 1).join('/');

    if (target.is('.active')) (new models.Library({
        id:id,
        location:location,
        project: this.model.collection.parent.id
    })).fetch({
        success: _(function(model, resp) {
            new views.Library({
                model: model,
                favorites: this.favorites,
                change: function(uri) {
                    $('input[name=file], input[name=connection]', form).val(val);
                },
                el: $('.browser', form)
            });
        }).bind(this),
        error: function(model, err) { new views.Modal(err) }
    });
    return false;
};

view.prototype.saveFile = function() {
    $(this.el).addClass('loading');
    var attr = {
        'name':  this.$('input[name=id]').val().replace('#', ''),
        'id':    this.$('input[name=id]').val().replace('#', ''),
        'srs':   this.$('input[name=srs]').val(),
        'class': this.$('input[name=class]').val().replace('.', ''),
        'Datasource': {
            'file': this.$('input[name=file]').val()
        }
    };
    _(attr['Datasource']).defaults(this.parseOptions(this.$('input[name=advanced]').val()));
    var error = _(function(m, e) {
        $(this.el).removeClass('loading');
        new views.Modal(e);
    }).bind(this);
    this.model.validateAsync(attr, { success:_(function() {
        $(this.el).removeClass('loading');
        if (!this.model.set(attr, {error:error})) return;
        if (!this.model.collection.include(this.model))
            this.model.collection.add(this.model);
        this.$('.close').click();
    }).bind(this), error:error });
    return false;
};

view.prototype.savePostGIS = function() {
    $(this.el).addClass('loading');
    var attr = {
        'name':  this.$('form.layerPostGIS input[name=id]').val().replace('#', ''),
        'id':    this.$('form.layerPostGIS input[name=id]').val().replace('#', ''),
        'srs':   this.$('form.layerPostGIS input[name=srs]').val()
            || this.model.SRS['900913'],
        'class': this.$('form.layerPostGIS input[name=class]').val().replace('.', ''),
        'Datasource': {
            'table':    this.$('textarea[name=table]', this.el).val(),
            'key_field': this.$('input[name=key_field]', this.el).val(),
            'geometry_field': this.$('input[name=geometry_field]', this.el).val(),
            'extent':   this.$('input[name=extent]', this.el).val(),
            'type': 'postgis'
        }
    };
    _(attr['Datasource']).defaults(this.parseOptions(this.$('form.layerPostGIS input[name=advanced]').val()));

    // Special parseing around PostGIS connection.
    var error;
    var allowedArgs = ['user', 'password', 'dbname', 'port', 'host'];
    var connection = this.parseOptions(this.$('form.layerPostGIS input[name=connection]').val());
    _(connection).each(function(val, key) {
        if (allowedArgs.indexOf(key) === -1) {
            error = new Error('Invalid argument ' + key + ' in PostgreSQL connection string.');
        }
    });
    if (!error && !_(connection).size()) {
        error = new Error('Invalid PostgreSQL connection string.');
    } else if (!error && !connection.dbname) {
        error = new Error('dbname is required in PostgreSQL connection string.');
    }
    if (error) {
        $(this.el).removeClass('loading');
        new views.Modal(error);
        return false;
    }
    var error = _(function(m, e) {
        $(this.el).removeClass('loading');
        new views.Modal(e);
    }).bind(this);
    _(attr['Datasource']).defaults(connection);

    this.model.validateAsync(attr, { success:_(function() {
        $(this.el).removeClass('loading');
        if (!this.model.set(attr, {error:error})) return;
        if (!this.model.collection.include(this.model))
            this.model.collection.add(this.model);
        this.$('.close').click();
    }).bind(this), error:error });
    return false;
};

view.prototype.saveSqlite = function() {
    $(this.el).addClass('loading');
    var attr = {
        'name':  this.$('form.layerSqlite input[name=id]').val().replace('#', ''),
        'id':    this.$('form.layerSqlite input[name=id]').val().replace('#', ''),
        'srs':   this.$('form.layerSqlite input[name=srs]').val()
            || this.model.SRS['900913'],
        'class': this.$('form.layerSqlite input[name=class]').val().replace('.', ''),
        'Datasource': {
            'file': this.$('form.layerSqlite input[name=file]').val(),
            'table':     this.$('form.layerSqlite textarea[name=table]', this.el).val(),
            'attachdb':  this.$('input[name=attachdb]', this.el).val(),
            'extent':    this.$('form.layerSqlite input[name=extent]', this.el).val(),
            'type': 'sqlite'
        }
    };
    _(attr['Datasource']).defaults(this.parseOptions(this.$('form.layerSqlite input[name=advanced]').val()));
    var error = _(function(m, e) {
        $(this.el).removeClass('loading');
        new views.Modal(e);
    }).bind(this);
    this.model.validateAsync(attr, { success:_(function() {
        $(this.el).removeClass('loading');
        if (!this.model.set(attr, {error:error})) return;
        if (!this.model.collection.include(this.model))
            this.model.collection.add(this.model);
        this.$('.close').click();
    }).bind(this), error:error });
    return false;
};

view.prototype.parseOptions = function (o) {
    var options = {};
    _(o.match(/([\d\w]*)\=(\"[^\"]*\"|[^\s]*)/g)).each(function(pair) {
        pair = pair.replace(/"|'/g, '').split('=');
        options[pair[0]] = pair[1];
    });
    return options;
}

view.prototype.cacheFlush = function(ev) {
    $(this.el).addClass('loading');
    var url = this.$('form.layerFile input[name=file]').val();
    this.model.collection.parent.flush(this.model.id, url, {
        success: _(function(m, resp) {
            $(this.el).removeClass('loading');
        }).bind(this),
        error: _(function(m, err) {
            $(this.el).removeClass('loading');
            new views.Modal(err);
        }).bind(this)
    });
    return false;
};

<commit_msg>Getting alphanum, lowercase titles.<commit_after>view = Backbone.View.extend();

view.prototype.events = {
    'click .layerFile input[type=submit]': 'saveFile',
    'click .layerPostGIS input[type=submit]': 'savePostGIS',
    'click .layerSqlite input[type=submit]': 'saveSqlite',
    'click a[href=#open]': 'browse',
    'click a[href=#favorite]': 'favoriteToggle',
    'keyup input[name=file], input[name=connection]': 'favoriteUpdate',
    'change input[name=file], input[name=connection]': 'favoriteUpdate',
    'click a[href=#cacheFlush]': 'cacheFlush',
    'change select[name=srs-name]': 'nameToSrs',
    'keyup input[name=srs]': 'srsToName'
};

view.prototype.initialize = function(options) {
    if (!options.favorites) throw new Error('options.favorites required.');

    _(this).bindAll(
        'render',
        'saveFile',
        'savePostGIS',
        'browse',
        'favoriteToggle',
        'favoriteUpdate',
        'cacheFlush',
        'nameToSrs',
        'srsToName'
    );
    this.favorites = options.favorites;
    this.render();
};

view.prototype.render = function() {
    this.$('.content').html(templates.Layer(this.model));

    // Quick easy way to check and set whether input
    // URI is favorited.
    this.$('input[name=file], input[name=connection]').change();

    if (this.model.get('Datasource')) {
        if (this.model.get('Datasource').type == 'sqlite') {
            this.$('a[href=#layerSqlite]').click();
        } else if (this.model.get('Datasource').file) {
            this.$('a[href=#layerFile]').click();
        } else if (this.model.get('Datasource').type == 'postgis') {
            this.$('a[href=#layerPostGIS]').click();
        }
    }

    // Autofocus first field for new layers.
    if (!this.model.id) this.$('input[type=text]:first').focus();
    return this;
};

view.prototype.nameToSrs = function(ev) {
    var el = $(ev.currentTarget);
    var name = $(ev.currentTarget).val();
    if (this.model.SRS[name]) {
        el.siblings('input[name=srs]').val(this.model.SRS[name]);
    } else if (name === 'autodetect') {
        el.siblings('input[name=srs]').val('');
    }
};

view.prototype.srsToName = function(ev) {
    var el = $(ev.currentTarget);
    var srs = $(ev.currentTarget).val();
    el.siblings('select[name=srs-name]').val(this.model.srsName(srs));
};

view.prototype.favoriteToggle = function(ev) {
    var form = $(ev.currentTarget).parents('form');
    var uri = $('input[name=file], input[name=connection]', form).val();
    // @TODO wait for 'success'? Throw errors?
    if (this.favorites.get(uri)) {
        var model = this.favorites.get(uri);
        this.favorites.remove(uri);
        model.destroy();
        $(ev.currentTarget).removeClass('active');
    } else if (uri) {
        var model = new models.Favorite({id:uri});
        this.favorites.add(model);
        model.save();
        $(ev.currentTarget).addClass('active');
    }
    return false;
};

view.prototype.favoriteUpdate = function(ev) {
    var target = $(ev.currentTarget);
    var favorite = target.siblings('a.favorite');
    var uri = target.val();
    if (uri.match(/^(\/|http:\/\/|(.+\s)?dbname=[\w]+)/)) {
        favorite.removeClass('hidden');
        if (this.favorites.isFavorite(uri)) {
            favorite.addClass('active');
        } else {
            favorite.removeClass('active');
        }
    } else {
        favorite.addClass('hidden');
    }

    // Show cache clear link if datasource points to a URL and we're editing
    // an existing layer.
    var cache = target.siblings('.cache');
    if (this.model.id && uri.match(/^http:\/\/|^https:\/\//)) {
        cache.removeClass('hidden');
    } else {
        cache.addClass('hidden');
    }
    return false;
};

view.prototype.browse = function(ev) {
    var target = $(ev.currentTarget);
    target
        .toggleClass('active')
        .text(target.hasClass('active') ? 'Done' : 'Browse');

    var id;
    var form = target.parents('form');
    if (form.is('.layerFile')) {
        id = 'file';
    } else if (form.is('.layerSqlite')) {
        id = 'sqlite';
    } else if (form.is('.layerPostGIS')) {
        id = 'favoritesPostGIS';
    }
    $('ul.form', form).toggleClass('expand');

    var components = $('input[name=file], input[name=connection]', form)
        .val()
        .split('/');
    var location = components.slice(0, components.length - 1).join('/');

    if (target.is('.active')) (new models.Library({
        id:id,
        location:location,
        project: this.model.collection.parent.id
    })).fetch({
        success: _(function(model, resp) {
            new views.Library({
                model: model,
                favorites: this.favorites,
                change: function(uri) {
                    $('input[name=file], input[name=connection]', form).val(uri);
                    if ($('input[name=id]', form).val() == '') {
                        // Get the 'basename' of the file, minus anything
                        // after the first dot, in alphanumeric
                        // and lowercase.
                        $('input[name=id]', form).val(
                            _(uri.split('/')).last().split('.')[0]
                                .replace(/[^a-z0-9]/gi,'').toLowerCase());
                    }
                },
                el: $('.browser', form)
            });
        }).bind(this),
        error: function(model, err) { new views.Modal(err) }
    });
    return false;
};

view.prototype.saveFile = function() {
    $(this.el).addClass('loading');
    var attr = {
        'name':  this.$('input[name=id]').val().replace('#', ''),
        'id':    this.$('input[name=id]').val().replace('#', ''),
        'srs':   this.$('input[name=srs]').val(),
        'class': this.$('input[name=class]').val().replace('.', ''),
        'Datasource': {
            'file': this.$('input[name=file]').val()
        }
    };
    _(attr['Datasource']).defaults(this.parseOptions(this.$('input[name=advanced]').val()));
    var error = _(function(m, e) {
        $(this.el).removeClass('loading');
        new views.Modal(e);
    }).bind(this);
    this.model.validateAsync(attr, { success:_(function() {
        $(this.el).removeClass('loading');
        if (!this.model.set(attr, {error:error})) return;
        if (!this.model.collection.include(this.model))
            this.model.collection.add(this.model);
        this.$('.close').click();
    }).bind(this), error:error });
    return false;
};

view.prototype.savePostGIS = function() {
    $(this.el).addClass('loading');
    var attr = {
        'name':  this.$('form.layerPostGIS input[name=id]').val().replace('#', ''),
        'id':    this.$('form.layerPostGIS input[name=id]').val().replace('#', ''),
        'srs':   this.$('form.layerPostGIS input[name=srs]').val()
            || this.model.SRS['900913'],
        'class': this.$('form.layerPostGIS input[name=class]').val().replace('.', ''),
        'Datasource': {
            'table':    this.$('textarea[name=table]', this.el).val(),
            'key_field': this.$('input[name=key_field]', this.el).val(),
            'geometry_field': this.$('input[name=geometry_field]', this.el).val(),
            'extent':   this.$('input[name=extent]', this.el).val(),
            'type': 'postgis'
        }
    };
    _(attr['Datasource']).defaults(this.parseOptions(this.$('form.layerPostGIS input[name=advanced]').val()));

    // Special parseing around PostGIS connection.
    var error;
    var allowedArgs = ['user', 'password', 'dbname', 'port', 'host'];
    var connection = this.parseOptions(this.$('form.layerPostGIS input[name=connection]').val());
    _(connection).each(function(val, key) {
        if (allowedArgs.indexOf(key) === -1) {
            error = new Error('Invalid argument ' + key + ' in PostgreSQL connection string.');
        }
    });
    if (!error && !_(connection).size()) {
        error = new Error('Invalid PostgreSQL connection string.');
    } else if (!error && !connection.dbname) {
        error = new Error('dbname is required in PostgreSQL connection string.');
    }
    if (error) {
        $(this.el).removeClass('loading');
        new views.Modal(error);
        return false;
    }
    var error = _(function(m, e) {
        $(this.el).removeClass('loading');
        new views.Modal(e);
    }).bind(this);
    _(attr['Datasource']).defaults(connection);

    this.model.validateAsync(attr, { success:_(function() {
        $(this.el).removeClass('loading');
        if (!this.model.set(attr, {error:error})) return;
        if (!this.model.collection.include(this.model))
            this.model.collection.add(this.model);
        this.$('.close').click();
    }).bind(this), error:error });
    return false;
};

view.prototype.saveSqlite = function() {
    $(this.el).addClass('loading');
    var attr = {
        'name':  this.$('form.layerSqlite input[name=id]').val().replace('#', ''),
        'id':    this.$('form.layerSqlite input[name=id]').val().replace('#', ''),
        'srs':   this.$('form.layerSqlite input[name=srs]').val()
            || this.model.SRS['900913'],
        'class': this.$('form.layerSqlite input[name=class]').val().replace('.', ''),
        'Datasource': {
            'file': this.$('form.layerSqlite input[name=file]').val(),
            'table':     this.$('form.layerSqlite textarea[name=table]', this.el).val(),
            'attachdb':  this.$('input[name=attachdb]', this.el).val(),
            'extent':    this.$('form.layerSqlite input[name=extent]', this.el).val(),
            'type': 'sqlite'
        }
    };
    _(attr['Datasource']).defaults(this.parseOptions(this.$('form.layerSqlite input[name=advanced]').val()));
    var error = _(function(m, e) {
        $(this.el).removeClass('loading');
        new views.Modal(e);
    }).bind(this);
    this.model.validateAsync(attr, { success:_(function() {
        $(this.el).removeClass('loading');
        if (!this.model.set(attr, {error:error})) return;
        if (!this.model.collection.include(this.model))
            this.model.collection.add(this.model);
        this.$('.close').click();
    }).bind(this), error:error });
    return false;
};

view.prototype.parseOptions = function (o) {
    var options = {};
    _(o.match(/([\d\w]*)\=(\"[^\"]*\"|[^\s]*)/g)).each(function(pair) {
        pair = pair.replace(/"|'/g, '').split('=');
        options[pair[0]] = pair[1];
    });
    return options;
}

view.prototype.cacheFlush = function(ev) {
    $(this.el).addClass('loading');
    var url = this.$('form.layerFile input[name=file]').val();
    this.model.collection.parent.flush(this.model.id, url, {
        success: _(function(m, resp) {
            $(this.el).removeClass('loading');
        }).bind(this),
        error: _(function(m, err) {
            $(this.el).removeClass('loading');
            new views.Modal(err);
        }).bind(this)
    });
    return false;
};

<|endoftext|>


<commit_before>import React, {PropTypes} from 'react';
import {connect} from 'react-redux';
import PropertiesContainer from './properties-container';

class PropertiesPage extends React.Component {
  constructor(props, context) {
    super(props, context);
  }

  render() {
    const {properties} = this.props;

    return (
      <PropertiesContainer properties={properties}/>
    );
  }
}

function mapStateToProps(state, ownProps) {
  return {
    properties: state.properties
  };
}

export default connect(mapStateToProps)(PropertiesPage);<commit_msg>Filtering<commit_after>import React, {PropTypes} from 'react';
import {connect} from 'react-redux';
import PropertiesContainer from './properties-container';

class PropertiesPage extends React.Component {
  constructor(props, context) {
    super(props, context);
  }

  render() {
    const {residential, commercial, hotels } = this.props;

    return (
      <div>
        <PropertiesContainer properties={residential}/>
        <PropertiesContainer properties={commercial}/>
        <PropertiesContainer properties={hotels}/>
      </div>
    );
  }
}

function mapStateToProps(state, ownProps) {
  return {
    residential: state.properties.filter(property => property.propertyType == 'residential'),
    commercial: state.properties.filter(property => property.propertyType == 'business'),
    hotels: state.properties.filter(property => property.propertyType == 'hotel')
  };
}

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