<commit_before>function BetterHistoryBuffer() {
	var self = this;
	var times = [];
	var datums = [];
	var len = 0;
	self.add = function (t, data) {
		if (t < times[len - 1]) {
			console.warn("Not adding old data to history buffer.")
		}
		times.push(t);
		datums.push(data);
		len++;
		if (len > 100) {
			times.shift();
			datums.shift();
			len--;
		}
	};
	function lerp(a, b, frac) {
		if (typeof a === 'number') {
			return a*(1 - frac) + b*frac;
		} else if (typeof a === 'object') {
			var res = {};
			for (var key in a) {
				if (!a.hasOwnProperty(a)) continue;
				res[key] = lerp(a[key], b[key], frac);
			}
			return res;
		} else {
			throw "I don't know how to lerp that: " + a;
		}
	}
	self.at = function (t) /* data */ {
		var newest_t = times[len - 1];
		if (newest_t <= t) {
			return datums[len - 1];
		}

		var oldest_t = times[0];
		if (oldest_t >= t) {
			return datums[0];
		}

		var older_i = len - 1;
		var newer_i = len - 1;
		for (var i = 1; i < len; i++) {
			older_i = i;
			if (times[older_i] <= t) break;
			newer_i = older_i;
		}
		if (times[older_i] === t) {
			return datums[older_i];
		}
		return lerp(
			datums[older_i],
			datums[newer_i],
			(t - times[older_i])/(times[newer_i] - times[older_i]));
	};
}
function NetworkEntityController(entity, clock) {
	var self = this;
	var history = new BetterHistoryBuffer();

	self.update = function () {
		entity.update(history.at(clock.time()), clock);
	};

	self.message = function (data) {
		history.add(data.time, data.data);
	};
}
function PredictiveEntityController(entity, clock, controls, predictor) {

	var self = this;
	var controlStates = [];
	var times = [];
	var lastConfirmed = {
		time: 0,
		data: {
			pos: new THREE.DVector3(0, 0, 0),
			look: new THREE.DVector3(0, 0, 0),
			vy: 0.0,
		},
	};

	self.update = function () {
		var latest = predictMovement();
		entity.update(latest, clock);
	};

	self.message = function (data) {
		// FUUUUU QUENTIN I WILL FIND YOU
		if (data.time === 0) return;
		if (times.length < 1) throw "No matching prediction data for given prediction.";
		var time = times.shift();
		var state = controlStates.shift();
		if (time != data.time) {
			return self.message(data);
			// TODO: Handle this more gracefully.
			throw "Recieved player-position packet from server with timestamp that does not match our oldest non-confirmed packet. This means the server is either processing packets out of order, or dropped one.";
		}

		lastConfirmed = data;
	};

	function predictMovement() {
		var c = controls.sample();
		var t = clock.time();
		controlStates.push(c);
		times.push(t);

		// TODO: Optimize this.
		return predictAll(t)
	}

	function predictAll(t) {
		var prev_t = lastConfirmed.time;
		var state = lastConfirmed.data;
		for (var i = 0; i < controlStates.length; i++) {
			var c = controlStates[i];
			var t = times[i];
			var dt = t - prev_t;
			state = predictor(state, c, dt);
		}
		return state;
	}
}
function PlayerEntity() {
	var self = this;

	var pos = new THREE.Vector3(0, 0, 0);
	var vy;
	var isMoving = false;

	var bodyParts = new THREE.Object3D();
	var headMesh = createHead();
	bodyParts.add(headMesh);
	var bodyMesh = createBody();
	bodyParts.add(bodyMesh);
	var leftArm = createArm(1);
	bodyParts.add(leftArm);
	var rightArm = createArm(-1);
	bodyParts.add(rightArm);

	var jumpAngle = 0;
	var maxJumpAngle = 4 * Math.PI / 5;
	var jumpSpeed = maxJumpAngle / 300;

	var swingAngle = 0;
	var maxSwingAngle = Math.PI / 2;
	var swingSpeed = 2 * Math.PI / 1000;
	var totalSwingTime = 0;
	self.update = function (state, clock) {
		self.setPos(state.pos);
		self.setLook(state.look);
		self.setVy(state.Vy);
		var dt = clock.dt();
		// Jump animation
		jumpAngle = clamp(jumpAngle + signum(vy)*dt*jumpSpeed, 0, maxJumpAngle);
		leftArm.rotation.z = jumpAngle;
		rightArm.rotation.z = -jumpAngle;

		// Arm swinging animation (as you walk)
		if (!isMoving) return;
		totalSwingTime += dt;
		var swingAngle = maxSwingAngle * sin(totalSwingTime * swingSpeed)
		if (swingAngle > -0.1 && swingAngle < 0.1) {
			isMoving = false;
			swingAngle = 0;
		}
		leftArm.rotation.x = -swingAngle;
		rightArm.rotation.x = swingAngle;
	}

	self.setVy = function (newVy) {
		vy = newVy;
	};

	self.setPos = function (newPos) {
		pos = newPos;
		var co = PLAYER_CENTER_OFFSET;
		var c = new THREE.Vector3(
			pos.x + co.x,
			pos.y + co.y,
			pos.z + co.z
		);

		var diffX = bodyParts.position.x - c.x;
		var diffZ = bodyParts.position.z - c.z;
		if (diffX !== 0 || diffZ !== 0) isMoving = true;

		bodyParts.position.set(c.x, c.y, c.z);

		return self;
	};
	self.pos = function () { return pos };

	self.setLook = function (newLook) {
		function lookAt(obj, x, y, z) {
			var headTarget = new THREE.Vector3(
				obj.position.x + x,
				obj.position.y + y,
				obj.position.z + z
			);
			obj.lookAt(headTarget);
		}
		lookAt(bodyParts, newLook.x, 0, newLook.z);
		lookAt(headMesh, 0, newLook.y, 1);
	};

	self.contains = function (x, y, z) {
		if (!pos) return;
		var box = new Box(pos, PLAYER_HALF_EXTENTS, PLAYER_CENTER_OFFSET);
		return box.contains(x, y, z);
	};

	self.addTo = function (scene) {
		scene.add(bodyParts);
	};

	self.removeFrom = function (scene) {
		scene.remove(bodyParts);
	};

	// Model/geometry
	function createHitbox() {
		var hitboxMaterial = new THREE.MeshBasicMaterial({
			color: 0xff0000,
			wireframe: true
		});
		var he = PLAYER_HALF_EXTENTS;
		var hitboxGeometry = new THREE.CubeGeometry(he.x*2, he.y*2, he.z*2);
		var hitboxMesh = new THREE.Mesh(hitboxGeometry, hitboxMaterial);
		return hitboxMesh;
	}

	function createHead() {
		var headMat = new THREE.MeshBasicMaterial({
			color: 0x0000ff
		});
		var faceMat = new THREE.MeshBasicMaterial({
			color: 0x00ff00
		});

		var geometry = new THREE.CubeGeometry(0.3, 0.3, 0.3);
		geometry.materials = [headMat, faceMat];
		geometry.faces[0].materialIndex = 0;
		geometry.faces[1].materialIndex = 0;
		geometry.faces[2].materialIndex = 0;
		geometry.faces[3].materialIndex = 0;
		geometry.faces[4].materialIndex = 1;
		geometry.faces[5].materialIndex = 0;

		var mesh = new THREE.Mesh(geometry, new THREE.MeshFaceMaterial(geometry.materials));

		var peh = PLAYER_EYE_HEIGHT;
		var pbh = PLAYER_BODY_HEIGHT;
		mesh.position.y = peh - pbh / 2;

		return mesh;
	}

	function createBody() {
		var material = new THREE.MeshBasicMaterial({
			color: 0x0000ff,
			wireframe: true
		});
		var geometry = new THREE.CubeGeometry(0.6, PLAYER_BODY_HEIGHT, 0.4);
		var mesh = new THREE.Mesh(geometry, material);
		mesh.position.y = 0;
		return mesh;
	}

	function createArm(offset) {
		var armMat = new THREE.MeshBasicMaterial({
			color: 0xff0000,
			wireframe: true
		});
		var geometry = new THREE.CubeGeometry(0.2, 0.6, 0.2);
		var mesh = new THREE.Mesh(geometry, armMat);
		mesh.position.y = -0.3;

		var arm = new THREE.Object3D();
		arm.add(mesh);
		arm.position.x = offset * 0.4;
		arm.position.y = PLAYER_BODY_HEIGHT / 2;
		return arm;
	}

	// Utils
	// Clamp n between [a, b]. Behaviour is
	// undefined if a > b.
	function clamp(n, a, b) {
		return n < a ? a : n > b ? b : n;
	}
	// Return the sign of n, -1, 1, or 0.
	function signum(n) {
		return n < 0 ? -1 : n > 0 ? 1 : 0;
	}
}
function Entity(id, world, clock, scene) {
	var self = this;

	var hp = 0;

	var posPrediction = new PosPrediction(world, clock, {
			pos: new THREE.Vector3(0, 0, 0),
			dy: 0,
			look: new THREE.Vector3(0, 0, 0),
		});

	var _isLagInduced = true;
	posPrediction.lagInduce(_isLagInduced);

	self.posDebugData = posPrediction.posDebugData;

	//We forward a lot, as we are basically embedding posPrediction
	self.predictMovement = posPrediction.predictMovement;
	self.posMessage = posPrediction.posMessage;
	self.pos = function() {
		return posPrediction.posState().pos;
	};
	self.look = function() {
		return posPrediction.posState().look;
	};
	self.dy = function() {
		return posPrediction.posState().dy;
	};
	self.posState = posPrediction.posState;

	self.contains = posPrediction.contains;
	self.lag = posPrediction.lag;

	//QTODO: Move body stuff into a view
	var isMoving = false;

	var bodyParts = new THREE.Object3D();
	var headMesh = createHead();
	bodyParts.add(headMesh);
	var bodyMesh = createBody();
	bodyParts.add(bodyMesh);
	var leftArm = createArm(1);
	bodyParts.add(leftArm);
	var rightArm = createArm(-1);
	bodyParts.add(rightArm);

	var jumpAngle = 0;
	var maxJumpAngle = 4 * Math.PI / 5;
	var jumpSpeed = maxJumpAngle / 300;

	var swingAngle = 0;
	var maxSwingAngle = Math.PI / 2;
	var swingSpeed = 2 * Math.PI / 1000;
	var totalSwingTime = 0;

	var UIViews = [];

	self.initViews = function() {
		if (localStorage.posHistoryBar) {
			UIViews.push(new PosHistoryBar(self, posPrediction, clock));
		}

		return self;
	};

	self.lagInduce = function(yes) {
		_isLagInduced = yes;
		posPrediction.lagInduce(_isLagInduced);
	}
	self.isLagInduced = function() {
		return _isLagInduced;
	}

	self.setViewVisibility = function(visible) {
		if(!visible) {
			self.removeFromScene();
		} else {
			self.addToScene();
		}
	}

	self.setHealth = function(health) {
		hp = health;
	};

	self.health = function() {
		return hp;
	};

	self.maxHealth = function() {
		//QTODO: Actually sync this with the server
		return 100;
	}

	self.addToScene = function () {
		scene.add(bodyParts);

		UIViews.forEach(function (view) {
			scene.add(view.mesh());
		});
	};

	self.removeFromScene = function () {
		scene.remove(bodyParts);

		UIViews.forEach(function (view) {
			scene.remove(view.mesh());
		});
	};

	self.id = function () {
		return id;
	};

	self.update = function(dt, playerPos) {
		UIViews.forEach(function (view) {
			view.update(playerPos);
		});

		self.setLook(self.look());

		// Jump animation
		var dy = self.dy();
		jumpAngle = clamp(jumpAngle + signum(dy)*dt*jumpSpeed, 0, maxJumpAngle);
		leftArm.rotation.z = jumpAngle;
		rightArm.rotation.z = -jumpAngle;

		// Arm swinging animation (as you walk)
		if (isMoving) {
			totalSwingTime += dt;
			var swingAngle = maxSwingAngle * sin(totalSwingTime * swingSpeed)
			if (swingAngle > -0.1 && swingAngle < 0.1) {
				isMoving = false;
				swingAngle = 0;
			}
			leftArm.rotation.x = -swingAngle;
			rightArm.rotation.x = swingAngle;
		}

		updatePos(playerPos);
	};

	self.setLook = function (newLook) {
		function lookAt(obj, x, y, z) {
			var headTarget = new THREE.Vector3(
				obj.position.x + x,
				obj.position.y + y,
				obj.position.z + z
			);
			obj.lookAt(headTarget);
		}
		lookAt(bodyParts, newLook.x, 0, newLook.z);
		lookAt(headMesh, 0, newLook.y, 1);
	};

	function updatePos(playerPos) {
		var pos = self.pos();
		var co = PLAYER_CENTER_OFFSET;
		var c = new THREE.Vector3(
			pos.x + co.x,
			pos.y + co.y,
			pos.z + co.z
		);

		var velocity = posPrediction.getVelocity();

		if(velocity.x !== 0 || velocity.z !== 0) {
			isMoving = true;
		}

		bodyParts.position.set(c.x, c.y, c.z);
	}

	function createHitbox() {
		var hitboxMaterial = new THREE.MeshBasicMaterial({
			color: 0xff0000,
			wireframe: true
		});
		var he = PLAYER_HALF_EXTENTS;
		var hitboxGeometry = new THREE.CubeGeometry(he.x*2, he.y*2, he.z*2);
		var hitboxMesh = new THREE.Mesh(hitboxGeometry, hitboxMaterial);
		return hitboxMesh;
	}

	function createHead() {
		var headMat = new THREE.MeshBasicMaterial({
			color: 0x0000ff
		});
		var faceMat = new THREE.MeshBasicMaterial({
			color: 0x00ff00
		});

		var geometry = new THREE.CubeGeometry(0.3, 0.3, 0.3);
		geometry.materials = [headMat, faceMat];
		geometry.faces[0].materialIndex = 0;
		geometry.faces[1].materialIndex = 0;
		geometry.faces[2].materialIndex = 0;
		geometry.faces[3].materialIndex = 0;
		geometry.faces[4].materialIndex = 1;
		geometry.faces[5].materialIndex = 0;

		var mesh = new THREE.Mesh(geometry, new THREE.MeshFaceMaterial(geometry.materials));

		var peh = PLAYER_EYE_HEIGHT;
		var pbh = PLAYER_BODY_HEIGHT;
		mesh.position.y = peh - pbh / 2;

		return mesh;
	}

	function createBody() {
		var material = new THREE.MeshBasicMaterial({
			color: 0x0000ff,
			wireframe: true
		});
		var geometry = new THREE.CubeGeometry(0.6, PLAYER_BODY_HEIGHT, 0.4);
		var mesh = new THREE.Mesh(geometry, material);
		mesh.position.y = 0;
		return mesh;
	}

	function createArm(offset) {
		var armMat = new THREE.MeshBasicMaterial({
			color: 0xff0000,
			wireframe: true
		});
		var geometry = new THREE.CubeGeometry(0.2, 0.6, 0.2);
		var mesh = new THREE.Mesh(geometry, armMat);
		mesh.position.y = -0.3;

		var arm = new THREE.Object3D();
		arm.add(mesh);
		arm.position.x = offset * 0.4;
		arm.position.y = PLAYER_BODY_HEIGHT / 2;
		return arm;
	}

	// Utils
	// Clamp n between [a, b]. Behaviour is
	// undefined if a > b.
	function clamp(n, a, b) {
		return n < a ? a : n > b ? b : n;
	}
	// Return the sign of n, -1, 1, or 0.
	function signum(n) {
		return n < 0 ? -1 : n > 0 ? 1 : 0;
	}
}<commit_msg>Remove old Entity code.<commit_after>function BetterHistoryBuffer() {
	var self = this;
	var times = [];
	var datums = [];
	var len = 0;
	self.add = function (t, data) {
		if (t < times[len - 1]) {
			console.warn("Not adding old data to history buffer.")
		}
		times.push(t);
		datums.push(data);
		len++;
		if (len > 100) {
			times.shift();
			datums.shift();
			len--;
		}
	};
	function lerp(a, b, frac) {
		if (typeof a === 'number') {
			return a*(1 - frac) + b*frac;
		} else if (typeof a === 'object') {
			var res = {};
			for (var key in a) {
				if (!a.hasOwnProperty(a)) continue;
				res[key] = lerp(a[key], b[key], frac);
			}
			return res;
		} else {
			throw "I don't know how to lerp that: " + a;
		}
	}
	self.at = function (t) /* data */ {
		var newest_t = times[len - 1];
		if (newest_t <= t) {
			return datums[len - 1];
		}

		var oldest_t = times[0];
		if (oldest_t >= t) {
			return datums[0];
		}

		var older_i = len - 1;
		var newer_i = len - 1;
		for (var i = 1; i < len; i++) {
			older_i = i;
			if (times[older_i] <= t) break;
			newer_i = older_i;
		}
		if (times[older_i] === t) {
			return datums[older_i];
		}
		return lerp(
			datums[older_i],
			datums[newer_i],
			(t - times[older_i])/(times[newer_i] - times[older_i]));
	};
}
function NetworkEntityController(entity, clock) {
	var self = this;
	var history = new BetterHistoryBuffer();

	self.update = function () {
		entity.update(history.at(clock.time()), clock);
	};

	self.message = function (data) {
		history.add(data.time, data.data);
	};
}
function PredictiveEntityController(entity, clock, controls, predictor) {

	var self = this;
	var controlStates = [];
	var times = [];
	var lastConfirmed = {
		time: 0,
		data: {
			pos: new THREE.DVector3(0, 0, 0),
			look: new THREE.DVector3(0, 0, 0),
			vy: 0.0,
		},
	};

	self.update = function () {
		var latest = predictMovement();
		entity.update(latest, clock);
	};

	self.message = function (data) {
		// FUUUUU QUENTIN I WILL FIND YOU
		if (data.time === 0) return;
		if (times.length < 1) throw "No matching prediction data for given prediction.";
		var time = times.shift();
		var state = controlStates.shift();
		if (time != data.time) {
			return self.message(data);
			// TODO: Handle this more gracefully.
			throw "Recieved player-position packet from server with timestamp that does not match our oldest non-confirmed packet. This means the server is either processing packets out of order, or dropped one.";
		}

		lastConfirmed = data;
	};

	function predictMovement() {
		var c = controls.sample();
		var t = clock.time();
		controlStates.push(c);
		times.push(t);

		// TODO: Optimize this.
		return predictAll(t)
	}

	function predictAll(t) {
		var prev_t = lastConfirmed.time;
		var state = lastConfirmed.data;
		for (var i = 0; i < controlStates.length; i++) {
			var c = controlStates[i];
			var t = times[i];
			var dt = t - prev_t;
			state = predictor(state, c, dt);
		}
		return state;
	}
}
function PlayerEntity() {
	var self = this;

	var pos = new THREE.Vector3(0, 0, 0);
	var vy;
	var isMoving = false;

	var bodyParts = new THREE.Object3D();
	var headMesh = createHead();
	bodyParts.add(headMesh);
	var bodyMesh = createBody();
	bodyParts.add(bodyMesh);
	var leftArm = createArm(1);
	bodyParts.add(leftArm);
	var rightArm = createArm(-1);
	bodyParts.add(rightArm);

	var jumpAngle = 0;
	var maxJumpAngle = 4 * Math.PI / 5;
	var jumpSpeed = maxJumpAngle / 300;

	var swingAngle = 0;
	var maxSwingAngle = Math.PI / 2;
	var swingSpeed = 2 * Math.PI / 1000;
	var totalSwingTime = 0;
	self.update = function (state, clock) {
		self.setPos(state.pos);
		self.setLook(state.look);
		self.setVy(state.Vy);
		var dt = clock.dt();
		// Jump animation
		jumpAngle = clamp(jumpAngle + signum(vy)*dt*jumpSpeed, 0, maxJumpAngle);
		leftArm.rotation.z = jumpAngle;
		rightArm.rotation.z = -jumpAngle;

		// Arm swinging animation (as you walk)
		if (!isMoving) return;
		totalSwingTime += dt;
		var swingAngle = maxSwingAngle * sin(totalSwingTime * swingSpeed)
		if (swingAngle > -0.1 && swingAngle < 0.1) {
			isMoving = false;
			swingAngle = 0;
		}
		leftArm.rotation.x = -swingAngle;
		rightArm.rotation.x = swingAngle;
	}

	self.setVy = function (newVy) {
		vy = newVy;
	};

	self.setPos = function (newPos) {
		pos = newPos;
		var co = PLAYER_CENTER_OFFSET;
		var c = new THREE.Vector3(
			pos.x + co.x,
			pos.y + co.y,
			pos.z + co.z
		);

		var diffX = bodyParts.position.x - c.x;
		var diffZ = bodyParts.position.z - c.z;
		if (diffX !== 0 || diffZ !== 0) isMoving = true;

		bodyParts.position.set(c.x, c.y, c.z);

		return self;
	};
	self.pos = function () { return pos };

	self.setLook = function (newLook) {
		function lookAt(obj, x, y, z) {
			var headTarget = new THREE.Vector3(
				obj.position.x + x,
				obj.position.y + y,
				obj.position.z + z
			);
			obj.lookAt(headTarget);
		}
		lookAt(bodyParts, newLook.x, 0, newLook.z);
		lookAt(headMesh, 0, newLook.y, 1);
	};

	self.contains = function (x, y, z) {
		if (!pos) return;
		var box = new Box(pos, PLAYER_HALF_EXTENTS, PLAYER_CENTER_OFFSET);
		return box.contains(x, y, z);
	};

	self.addTo = function (scene) {
		scene.add(bodyParts);
	};

	self.removeFrom = function (scene) {
		scene.remove(bodyParts);
	};

	// Model/geometry
	function createHitbox() {
		var hitboxMaterial = new THREE.MeshBasicMaterial({
			color: 0xff0000,
			wireframe: true
		});
		var he = PLAYER_HALF_EXTENTS;
		var hitboxGeometry = new THREE.CubeGeometry(he.x*2, he.y*2, he.z*2);
		var hitboxMesh = new THREE.Mesh(hitboxGeometry, hitboxMaterial);
		return hitboxMesh;
	}

	function createHead() {
		var headMat = new THREE.MeshBasicMaterial({
			color: 0x0000ff
		});
		var faceMat = new THREE.MeshBasicMaterial({
			color: 0x00ff00
		});

		var geometry = new THREE.CubeGeometry(0.3, 0.3, 0.3);
		geometry.materials = [headMat, faceMat];
		geometry.faces[0].materialIndex = 0;
		geometry.faces[1].materialIndex = 0;
		geometry.faces[2].materialIndex = 0;
		geometry.faces[3].materialIndex = 0;
		geometry.faces[4].materialIndex = 1;
		geometry.faces[5].materialIndex = 0;

		var mesh = new THREE.Mesh(geometry, new THREE.MeshFaceMaterial(geometry.materials));

		var peh = PLAYER_EYE_HEIGHT;
		var pbh = PLAYER_BODY_HEIGHT;
		mesh.position.y = peh - pbh / 2;

		return mesh;
	}

	function createBody() {
		var material = new THREE.MeshBasicMaterial({
			color: 0x0000ff,
			wireframe: true
		});
		var geometry = new THREE.CubeGeometry(0.6, PLAYER_BODY_HEIGHT, 0.4);
		var mesh = new THREE.Mesh(geometry, material);
		mesh.position.y = 0;
		return mesh;
	}

	function createArm(offset) {
		var armMat = new THREE.MeshBasicMaterial({
			color: 0xff0000,
			wireframe: true
		});
		var geometry = new THREE.CubeGeometry(0.2, 0.6, 0.2);
		var mesh = new THREE.Mesh(geometry, armMat);
		mesh.position.y = -0.3;

		var arm = new THREE.Object3D();
		arm.add(mesh);
		arm.position.x = offset * 0.4;
		arm.position.y = PLAYER_BODY_HEIGHT / 2;
		return arm;
	}

	// Utils
	// Clamp n between [a, b]. Behaviour is
	// undefined if a > b.
	function clamp(n, a, b) {
		return n < a ? a : n > b ? b : n;
	}
	// Return the sign of n, -1, 1, or 0.
	function signum(n) {
		return n < 0 ? -1 : n > 0 ? 1 : 0;
	}
}<|endoftext|>


<commit_before>if (Meteor.isClient) {

  // update title and navigation when visiting a space
  Deps.autorun(function () {
    var space = Spaces.findOne(Session.get("currentSpace"));
    if (space) {
      document.title = space.name;
      Router.navigate(space._id + '/' + slugify(space.name))
    } else
      document.title = "smallspace";
  });

  Deps.autorun(function() {
    var spaceId = Session.get('currentSpace');
    if (spaceId) {
      Meteor.subscribe('messages', spaceId);
      Meteor.subscribe('links', spaceId);
      Meteor.subscribe('space-invites', spaceId);
      Meteor.subscribe('space-memberships', spaceId);
    }
  });

  Deps.autorun(function() {
    var ms = Memberships.find().fetch();
    var is = Invites.find().fetch();
    var spaceIds = _.pluck(ms.concat(is), 'spaceId');
    Meteor.subscribe('spaces', spaceIds);
  });

  Deps.autorun(function() {
    var inviteId = Session.get('currentInviteId');
    if (inviteId) {
      var userId = Meteor.userId();
      // make sure this current invite is set to this user
      Invites.update(inviteId, { $set: { userId: Meteor.userId() } });
    }
  });

  Meteor.subscribe('my-memberships');
  Meteor.subscribe('my-invites');

  // XXX this is BAD
  Meteor.subscribe('allUserData');

  Accounts.ui.config({
    passwordSignupFields: 'USERNAME_AND_EMAIL'
  });

  Template.header.currentSpace = function() {
    var space = Spaces.findOne({_id: Session.get('currentSpace')})
    return space && space.name;
  }
  Template.header.events = {
    'click .title': function(e) {
      Router.setSpace(null);
      return false;
    }
  }

  Template.space.isMember = function() {
    return Memberships.findOne({spaceId: Session.get('currentSpace'), userId: Meteor.userId()});
  }

  Template.myInvitiationItem.events = {
    'click a.accept': function(e) {
      var invitation = this;
      Meteor.call('acceptInvite', invitation, function(err, result) {
        Router.setSpace(invitation.spaceId);
      });
      return false;
    }
  }

  Template.spaceList.memberSpaces = function() {
    var spaceIds = _.pluck(Memberships.find().fetch(), 'spaceId');
    return Spaces.find({_id: {$in: spaceIds}});
  }

  Template.spaceList.invitations = function() {
    return Invites.find({ $or: [ { email: Meteor.user().emails[0].address },
                                 { userId: Meteor.userId() } ]
                        });
  }
  Template.spaceList.events = {
    'click .new': function(e) {
      var name = prompt('new space name:');
      Meteor.call('createSpace', {name: name}, function(err, result) {
        console.log(err, result);
        Router.setSpace(result);
      });
      return false;
    }
  }
  Template.spaceListItem.image = function() {
    return this.image || 'http://www.gravatar.com/avatar/'+md5(this._id)+'.jpg?d=monsterid&s=200'
  }
  Template.spaceListItem.createdBy = function() {
    // XXX generalize this
    var user = Meteor.users.findOne(this.userId);
    return user && user.username;
  }
  Template.spaceListItem.events = {
    'click': function(e) {
      Router.setSpace(this._id);
    }
  }

  Template.chatWindow.messages = function() {
    return Messages.find({spaceId: Session.get('currentSpace')});
  }
  Template.chatWindow.events = {
    "submit form": function(e) {
      var $input = $(e.target).find('input');
      $input.attr('disabled', true);
      Meteor.call('post', {text: $input.val(), spaceId: Session.get('currentSpace')},
                  function(err, result) {
                    if (!err) {
                      $input.attr('disabled', false).val('');
                    }
      });
      return false;
    }
  }
  Template.chatWindow.rendered = function() {
    resizeChat();
    scrollChat();
  }

  Template.rightSideNav.rendered = function() {
    resizeChat();
  }

  Template.message.when = function() {
    return moment(this.created).format("HH:mm");
  }
  Template.message.user = function() {
    // XXX generalize this
    var user = Meteor.users.findOne(this.userId);
    return user && user.username;
  }

  Template.rightSideNav.userId = function() {
    var space = Spaces.findOne(Session.get('currentSpace'));
    return space && space.userId;
  }
  Template.admin.events = {
    'click .delete': function(e) {
      if (confirm('delete space permanently?')) {
        Meteor.call('deleteSpace', Session.get('currentSpace'));
        Router.setSpace(null);
      }
    }
  }

  Template.links.links = function() {
    return Links.find({spaceId: Session.get('currentSpace')}, {sort: {created: -1}, limit: 30});
  }
  Template.link.isOwner = function() {
    return this.userId === Meteor.userId();
  }
  Template.link.user = function() {
    // XXX generalize this
    var user = Meteor.users.findOne(this.userId);
    return user && user.username;
  }
  Template.link.when = function() {
    return moment(this.created).format("dddd, MMMM Do YYYY, HH:mm");
  }
  Template.link.link = function() {
    return ''+this.url+'';
  }
  Template.link.inline = function() {
    var match = this.url.match(/youtube.com\/watch\?.*v=(.+)/)
    if (match) {
      var video_id = match[1];
      return '';
    } else {
      var match = this.url.match(/(jpg|gif|png)$/);
      if (match) {
        return ''
      } else {
        return '';
      }
    }
  }
  Template.link.events = {
    'click .delete': function(e) {
      if (confirm('Delete this link? (NOTE: It will remain in the chat log)'))
        Links.remove(this._id);
    }
  };

  // XXX this is a hack to redirect back to home page after signing up from invite link
  // need to figure out how to catch signin/signup event
  Template.invite.rendered = function() {
    if (Meteor.userId()) {
      Router.setSpace(null);
    }
  }

  // XXX use a generic date helper for this:
  Template.inviteListItem.when = function() {
    return moment(this.created).fromNow();
  }

  Template.inviteListItem.events = {
    'click .cancel': function(e) {
      if (confirm('cancel invite for ' + this.email + '?'))
        Invites.remove(this._id);
      return false;
    }
  }

  Template.members.memberList = function() {
    return Memberships.find({spaceId: Session.get('currentSpace')});
  }
  Template.memberListItem.username = function() {
    // XXX generalize this
    var user = Meteor.users.findOne(this.userId);
    return user && user.username;
  }
  Template.members.invites = function() {
    return Invites.find({spaceId: Session.get('currentSpace')});
  }
  Template.members.events = {
    "submit form.invite": function(e) {
      e.preventDefault();
      var $inp = $(e.target).find('input');
      var addr = $inp.val();
      var space = Spaces.findOne(Session.get('currentSpace'));

      // XXX for now, just return if form is empty.  Should validate
      // email also, but server will throw exception if it cannot
      // deliver.
      if (!addr) return false;

      Meteor.call('inviteByEmail', addr, space, function(err, result) {
        console.log('returned from sending invite', err, result);
        if (err) {
          alert('error sending email');
        } else {
          $inp.val('');
        }
      });
      return false;
    }
  }

  Meteor.startup(function() {
    $(window).resize(function(evt) {
      resizeChat();
      scrollChat();
    });
    Backbone.history.start({pushState: true});
  });

  resizeChat = function() {
    var top = $('.chat-container .chat').position().top;
    var bot = $('.chat-container form').position().top;
    $('.chat-container .chat').height(bot - top);
    $('.rightSideNav').height(bot-top);
  }

  scrollChat = function() {
    var $chat = $('.chat-container .chat');
    if (($chat.scrollTop() === 0)
        || (50 + $chat.scrollTop()) >= ($chat.prop('scrollHeight') - $chat.prop('offsetHeight'))) {
      $chat.scrollTop(1000000);
    }
  }

  // router
  var SpaceRouter = Backbone.Router.extend({
    routes: {
      "": "menu",
      ":spaceId/invite/:inviteId": "invite",

      ":spaceId": "main",
      ":spaceId/:slug": "main",
    },

    menu: function() {
      Session.set('currentSpace', null);
      Session.set('page', 'home');
    },

    invite: function(spaceId, inviteId) {
      Session.set('currentSpace', null);
      if (Meteor.userId()) {
        Session.set('page', 'home');
      } else {
        Session.set('currentInviteId', inviteId);
        Session.set('page', 'invite');
      }
    },

    main: function(spaceId) {
      var oldSpace = Session.get('currentSpace');
      if (oldSpace !== spaceId) {
        Session.set('currentSpace', spaceId);
      }
      Session.set('page', 'space');
    },

    setSpace: function(spaceId) {
      var path = spaceId;
      var space = Spaces.findOne(spaceId);
      if (space)
        path += '/' + slugify(space.name);

      this.navigate(path, true);
    }
  });
  Router = new SpaceRouter;
}

if (Meteor.isServer) {
  Meteor.publish('messages', function(spaceId) {
    return Messages.find({spaceId: spaceId});
  });
  Meteor.publish('links', function(spaceId) {
    return Links.find({spaceId: spaceId});
  });
  Meteor.publish('space-invites', function(spaceId) {
    return Invites.find({spaceId: spaceId});
  });
  Meteor.publish('space-memberships', function(spaceId) {
    return Memberships.find({spaceId: spaceId});
  });
  Meteor.publish('my-memberships', function() {
    console.log('publish my-memberships')
    return Memberships.find({userId: this.userId});
  });
  Meteor.publish('my-invites', function() {
    console.log('publish my-invites', this.userId)
    var user = Meteor.users.findOne(this.userId);
    if (user)
      return Invites.find({ $or: [ { email: user.emails[0].address },
                                   { userId: user._id } ]
                          });
  });

  Meteor.publish('spaces', function(spaceIds) {
    return Spaces.find({_id: {$in: spaceIds}, deleted: {$ne : true} } );
  });
  Meteor.publish('allUserData', function() {
    return Meteor.users.find();
  });

  Meteor.startup(function () {
    // code to run on server at startup
    //console.log(process.env);
  });

  Invites.allow({
    update: function() {
      return true;
    }
  });
}
<commit_msg>limit user data returned.  todo: hide emails<commit_after>if (Meteor.isClient) {

  // update title and navigation when visiting a space
  Deps.autorun(function () {
    var space = Spaces.findOne(Session.get("currentSpace"));
    if (space) {
      document.title = space.name;
      Router.navigate(space._id + '/' + slugify(space.name))
    } else
      document.title = "smallspace";
  });

  Deps.autorun(function() {
    var spaceId = Session.get('currentSpace');
    if (spaceId) {
      Meteor.subscribe('messages', spaceId);
      Meteor.subscribe('links', spaceId);
      Meteor.subscribe('space-invites', spaceId);
      Meteor.subscribe('space-memberships', spaceId);
    }
  });

  Deps.autorun(function() {
    var ms = Memberships.find().fetch();
    var is = Invites.find().fetch();
    var spaceIds = _.pluck(ms.concat(is), 'spaceId');
    Meteor.subscribe('spaces', spaceIds);
  });

  Deps.autorun(function() {
    var inviteId = Session.get('currentInviteId');
    if (inviteId) {
      var userId = Meteor.userId();
      // make sure this current invite is set to this user
      Invites.update(inviteId, { $set: { userId: Meteor.userId() } });
    }
  });

  Meteor.subscribe('my-memberships');
  Meteor.subscribe('my-invites');

  // XXX this is BAD
  Meteor.subscribe('allUserData');

  Accounts.ui.config({
    passwordSignupFields: 'USERNAME_AND_EMAIL'
  });

  Template.header.currentSpace = function() {
    var space = Spaces.findOne({_id: Session.get('currentSpace')})
    return space && space.name;
  }
  Template.header.events = {
    'click .title': function(e) {
      Router.setSpace(null);
      return false;
    }
  }

  Template.space.isMember = function() {
    return Memberships.findOne({spaceId: Session.get('currentSpace'), userId: Meteor.userId()});
  }

  Template.myInvitiationItem.events = {
    'click a.accept': function(e) {
      var invitation = this;
      Meteor.call('acceptInvite', invitation, function(err, result) {
        Router.setSpace(invitation.spaceId);
      });
      return false;
    }
  }

  Template.spaceList.memberSpaces = function() {
    var spaceIds = _.pluck(Memberships.find().fetch(), 'spaceId');
    return Spaces.find({_id: {$in: spaceIds}});
  }

  Template.spaceList.invitations = function() {
    return Invites.find({ $or: [ { email: Meteor.user().emails[0].address },
                                 { userId: Meteor.userId() } ]
                        });
  }
  Template.spaceList.events = {
    'click .new': function(e) {
      var name = prompt('new space name:');
      Meteor.call('createSpace', {name: name}, function(err, result) {
        console.log(err, result);
        Router.setSpace(result);
      });
      return false;
    }
  }
  Template.spaceListItem.image = function() {
    return this.image || 'http://www.gravatar.com/avatar/'+md5(this._id)+'.jpg?d=monsterid&s=200'
  }
  Template.spaceListItem.createdBy = function() {
    // XXX generalize this
    var user = Meteor.users.findOne(this.userId);
    return user && user.username;
  }
  Template.spaceListItem.events = {
    'click': function(e) {
      Router.setSpace(this._id);
    }
  }

  Template.chatWindow.messages = function() {
    return Messages.find({spaceId: Session.get('currentSpace')});
  }
  Template.chatWindow.events = {
    "submit form": function(e) {
      var $input = $(e.target).find('input');
      $input.attr('disabled', true);
      Meteor.call('post', {text: $input.val(), spaceId: Session.get('currentSpace')},
                  function(err, result) {
                    if (!err) {
                      $input.attr('disabled', false).val('');
                    }
      });
      return false;
    }
  }
  Template.chatWindow.rendered = function() {
    resizeChat();
    scrollChat();
  }

  Template.rightSideNav.rendered = function() {
    resizeChat();
  }

  Template.message.when = function() {
    return moment(this.created).format("HH:mm");
  }
  Template.message.user = function() {
    // XXX generalize this
    var user = Meteor.users.findOne(this.userId);
    return user && user.username;
  }

  Template.rightSideNav.userId = function() {
    var space = Spaces.findOne(Session.get('currentSpace'));
    return space && space.userId;
  }
  Template.admin.events = {
    'click .delete': function(e) {
      if (confirm('delete space permanently?')) {
        Meteor.call('deleteSpace', Session.get('currentSpace'));
        Router.setSpace(null);
      }
    }
  }

  Template.links.links = function() {
    return Links.find({spaceId: Session.get('currentSpace')}, {sort: {created: -1}, limit: 30});
  }
  Template.link.isOwner = function() {
    return this.userId === Meteor.userId();
  }
  Template.link.user = function() {
    // XXX generalize this
    var user = Meteor.users.findOne(this.userId);
    return user && user.username;
  }
  Template.link.when = function() {
    return moment(this.created).format("dddd, MMMM Do YYYY, HH:mm");
  }
  Template.link.link = function() {
    return ''+this.url+'';
  }
  Template.link.inline = function() {
    var match = this.url.match(/youtube.com\/watch\?.*v=(.+)/)
    if (match) {
      var video_id = match[1];
      return '';
    } else {
      var match = this.url.match(/(jpg|gif|png)$/);
      if (match) {
        return ''
      } else {
        return '';
      }
    }
  }
  Template.link.events = {
    'click .delete': function(e) {
      if (confirm('Delete this link? (NOTE: It will remain in the chat log)'))
        Links.remove(this._id);
    }
  };

  // XXX this is a hack to redirect back to home page after signing up from invite link
  // need to figure out how to catch signin/signup event
  Template.invite.rendered = function() {
    if (Meteor.userId()) {
      Router.setSpace(null);
    }
  }

  // XXX use a generic date helper for this:
  Template.inviteListItem.when = function() {
    return moment(this.created).fromNow();
  }

  Template.inviteListItem.events = {
    'click .cancel': function(e) {
      if (confirm('cancel invite for ' + this.email + '?'))
        Invites.remove(this._id);
      return false;
    }
  }

  Template.members.memberList = function() {
    return Memberships.find({spaceId: Session.get('currentSpace')});
  }
  Template.memberListItem.username = function() {
    // XXX generalize this
    var user = Meteor.users.findOne(this.userId);
    return user && user.username;
  }
  Template.members.invites = function() {
    return Invites.find({spaceId: Session.get('currentSpace')});
  }
  Template.members.events = {
    "submit form.invite": function(e) {
      e.preventDefault();
      var $inp = $(e.target).find('input');
      var addr = $inp.val();
      var space = Spaces.findOne(Session.get('currentSpace'));

      // XXX for now, just return if form is empty.  Should validate
      // email also, but server will throw exception if it cannot
      // deliver.
      if (!addr) return false;

      Meteor.call('inviteByEmail', addr, space, function(err, result) {
        console.log('returned from sending invite', err, result);
        if (err) {
          alert('error sending email');
        } else {
          $inp.val('');
        }
      });
      return false;
    }
  }

  Meteor.startup(function() {
    $(window).resize(function(evt) {
      resizeChat();
      scrollChat();
    });
    Backbone.history.start({pushState: true});
  });

  resizeChat = function() {
    var top = $('.chat-container .chat').position().top;
    var bot = $('.chat-container form').position().top;
    $('.chat-container .chat').height(bot - top);
    $('.rightSideNav').height(bot-top);
  }

  scrollChat = function() {
    var $chat = $('.chat-container .chat');
    if (($chat.scrollTop() === 0)
        || (50 + $chat.scrollTop()) >= ($chat.prop('scrollHeight') - $chat.prop('offsetHeight'))) {
      $chat.scrollTop(1000000);
    }
  }

  // router
  var SpaceRouter = Backbone.Router.extend({
    routes: {
      "": "menu",
      ":spaceId/invite/:inviteId": "invite",

      ":spaceId": "main",
      ":spaceId/:slug": "main",
    },

    menu: function() {
      Session.set('currentSpace', null);
      Session.set('page', 'home');
    },

    invite: function(spaceId, inviteId) {
      Session.set('currentSpace', null);
      if (Meteor.userId()) {
        Session.set('page', 'home');
      } else {
        Session.set('currentInviteId', inviteId);
        Session.set('page', 'invite');
      }
    },

    main: function(spaceId) {
      var oldSpace = Session.get('currentSpace');
      if (oldSpace !== spaceId) {
        Session.set('currentSpace', spaceId);
      }
      Session.set('page', 'space');
    },

    setSpace: function(spaceId) {
      var path = spaceId;
      var space = Spaces.findOne(spaceId);
      if (space)
        path += '/' + slugify(space.name);

      this.navigate(path, true);
    }
  });
  Router = new SpaceRouter;
}

if (Meteor.isServer) {
  Meteor.publish('messages', function(spaceId) {
    return Messages.find({spaceId: spaceId});
  });
  Meteor.publish('links', function(spaceId) {
    return Links.find({spaceId: spaceId});
  });
  Meteor.publish('space-invites', function(spaceId) {
    return Invites.find({spaceId: spaceId});
  });
  Meteor.publish('space-memberships', function(spaceId) {
    return Memberships.find({spaceId: spaceId});
  });
  Meteor.publish('my-memberships', function() {
    console.log('publish my-memberships')
    return Memberships.find({userId: this.userId});
  });
  Meteor.publish('my-invites', function() {
    console.log('publish my-invites', this.userId)
    var user = Meteor.users.findOne(this.userId);
    if (user)
      return Invites.find({ $or: [ { email: user.emails[0].address },
                                   { userId: user._id } ]
                          });
  });

  Meteor.publish('spaces', function(spaceIds) {
    return Spaces.find({_id: {$in: spaceIds}, deleted: {$ne : true} } );
  });
  Meteor.publish('allUserData', function() {
    return Meteor.users.find({}, {fields: {username: 1, emails: 1}});
  });

  Meteor.startup(function () {
    // code to run on server at startup
    //console.log(process.env);
  });

  Invites.allow({
    update: function() {
      return true;
    }
  });
}
<|endoftext|>


<commit_before>var mapnik = require('mapnik');

var mercator = new mapnik.Projection('+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m +nadgrids=@null +no_defs +over');

/**
 * SphericalMercator constructor: precaches calculations
 * for fast tile lookups
 */
function SphericalMercator() {
    var size = 256;
    this.Bc = [];
    this.Cc = [];
    this.zc = [];
    this.Ac = [];
    this.DEG_TO_RAD = Math.PI / 180;
    this.RAD_TO_DEG = 180 / Math.PI;
    this.size = 256;
    this.levels = 18;
    for (var d = 0; d < this.levels; d++) {
        this.Bc.push(size / 360);
        this.Cc.push(size / (2 * Math.PI));
        this.zc.push(size / 2);
        this.Ac.push(size);
        size *= 2;
    }
}

/**
 * Get the max of the first two numbers and the min of that and the third
 *
 * @param {Number} a the first number.
 * @param {Number} b the second number.
 * @param {Number} c the third number.
 * @return {Number}
 */
SphericalMercator.prototype.minmax = function(a, b, c) {
    return Math.min(Math.max(a, b), c);
};

/**
 * Convert lat lon to screen pixel value
 *
 * @param {Array} px [lat lon] array of geographic coordinates.
 * @param {Number} zoom number of the zoom level.
 */
SphericalMercator.prototype.ll_to_px = function(ll, zoom) {
    var d = this.zc[zoom];
    var f = this.minmax(Math.sin(this.DEG_TO_RAD * ll[1]), -0.9999, 0.9999);
    var x = Math.round(d + ll[0] * this.Bc[zoom]);
    var y = Math.round(d + 0.5 * Math.log((1 + f) / (1 - f)) * (-this.Cc[zoom]));
    return [x, y];
};

/**
 * Convert screen pixel value to lat lon
 *
 * @param {Array} px [x y] array of geographic coordinates.
 * @param {Number} zoom number of the zoom level.
 */
SphericalMercator.prototype.px_to_ll = function(px, zoom) {
    var zoom_denom = this.zc[zoom];
    var g = (px[1] - zoom_denom) / (-this.Cc[zoom]);
    var lat = (px[0] - zoom_denom) / this.Bc[zoom];
    var lon = this.RAD_TO_DEG * (2 * Math.atan(Math.exp(g)) - 0.5 * Math.PI);
    return [lat, lon];
};

/**
 * Convert tile xyz value to Mapnik envelope
 *
 * @param {Number} x latitude number.
 * @param {Number} y longitude number.
 * @param {Number} zoom zoom.
 * @param {Boolean} tms_style whether to compute a tms tile.
 * @return Object Mapnik envelope.
 */
SphericalMercator.prototype.xyz_to_envelope = function(x, y, zoom, tms_style) {
    if (tms_style) {
        y = (Math.pow(2, zoom) - 1) - y;
    }
    var ll = [x * this.size, (y + 1) * this.size];
    var ur = [(x + 1) * this.size, y * this.size];
    var bbox = this.px_to_ll(ll, zoom).concat(this.px_to_ll(ur, zoom));
    return mercator.forward(bbox);
};

module.exports = new SphericalMercator();
<commit_msg>expose raw proj4 string as property of mercator object<commit_after>var mapnik = require('mapnik');

var srs = '+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m +nadgrids=@null +no_defs +over';
var mercator = new mapnik.Projection(srs);

/**
 * SphericalMercator constructor: precaches calculations
 * for fast tile lookups
 */
function SphericalMercator() {
    var size = 256;
    this.Bc = [];
    this.Cc = [];
    this.zc = [];
    this.Ac = [];
    this.DEG_TO_RAD = Math.PI / 180;
    this.RAD_TO_DEG = 180 / Math.PI;
    this.size = 256;
    this.levels = 18;
    this.srs = srs;
    for (var d = 0; d < this.levels; d++) {
        this.Bc.push(size / 360);
        this.Cc.push(size / (2 * Math.PI));
        this.zc.push(size / 2);
        this.Ac.push(size);
        size *= 2;
    }
}

/**
 * Get the max of the first two numbers and the min of that and the third
 *
 * @param {Number} a the first number.
 * @param {Number} b the second number.
 * @param {Number} c the third number.
 * @return {Number}
 */
SphericalMercator.prototype.minmax = function(a, b, c) {
    return Math.min(Math.max(a, b), c);
};

/**
 * Convert lat lon to screen pixel value
 *
 * @param {Array} px [lat lon] array of geographic coordinates.
 * @param {Number} zoom number of the zoom level.
 */
SphericalMercator.prototype.ll_to_px = function(ll, zoom) {
    var d = this.zc[zoom];
    var f = this.minmax(Math.sin(this.DEG_TO_RAD * ll[1]), -0.9999, 0.9999);
    var x = Math.round(d + ll[0] * this.Bc[zoom]);
    var y = Math.round(d + 0.5 * Math.log((1 + f) / (1 - f)) * (-this.Cc[zoom]));
    return [x, y];
};

/**
 * Convert screen pixel value to lat lon
 *
 * @param {Array} px [x y] array of geographic coordinates.
 * @param {Number} zoom number of the zoom level.
 */
SphericalMercator.prototype.px_to_ll = function(px, zoom) {
    var zoom_denom = this.zc[zoom];
    var g = (px[1] - zoom_denom) / (-this.Cc[zoom]);
    var lat = (px[0] - zoom_denom) / this.Bc[zoom];
    var lon = this.RAD_TO_DEG * (2 * Math.atan(Math.exp(g)) - 0.5 * Math.PI);
    return [lat, lon];
};

/**
 * Convert tile xyz value to Mapnik envelope
 *
 * @param {Number} x latitude number.
 * @param {Number} y longitude number.
 * @param {Number} zoom zoom.
 * @param {Boolean} tms_style whether to compute a tms tile.
 * @return Object Mapnik envelope.
 */
SphericalMercator.prototype.xyz_to_envelope = function(x, y, zoom, tms_style) {
    if (tms_style) {
        y = (Math.pow(2, zoom) - 1) - y;
    }
    var ll = [x * this.size, (y + 1) * this.size];
    var ur = [(x + 1) * this.size, y * this.size];
    var bbox = this.px_to_ll(ll, zoom).concat(this.px_to_ll(ur, zoom));
    return mercator.forward(bbox);
};

module.exports = new SphericalMercator();
<|endoftext|>


<commit_before>var config = require('config');
var FilterView = require('filter-view');
var HelpMeChoose = require('help-me-choose-view');
var LeafletTransitiveLayer = require('Leaflet.TransitiveLayer');
var LocationsView = require('locations-view');
var log = require('./client/log')('planner-page');
var showMapView = require('map-view');
var OptionsView = require('options-view');
var PlannerNav = require('planner-nav');
var querystring = require('querystring');
var scrollbarSize = require('scrollbar-size');
var scrolling = require('scrolling');
var session = require('session');
var textModal = require('text-modal');
var transitive = require('transitive');
var ua = require('user-agent');
var view = require('view');
var showWelcomeWizard = require('welcome-flow');

var FROM = config.geocode().start_address;
var TO = config.geocode().end_address;
var isMobile = window.innerWidth <= 480;

var View = view(require('./template.html'), function(view, model) {
  view.scrollable = view.find('.scrollable');
  view.panelFooter = view.find('.footer');

  if (scrollbarSize > 0) {
    if (ua.os.name === 'Windows' || ua.browser.name !== 'Chrome')
      view.scrollable.style.marginRight = -scrollbarSize + 'px';

    // Scrollbars are fun and implemented the same on every OS/Browser...right
    if (ua.os.name === 'Windows' && ua.browser.name === 'Chrome')
      view.scrollable.style.paddingRight = scrollbarSize + 'px';
  }
});

/**
 * Expose `render`
 */

module.exports = function(ctx, next) {
  log('render');

  var plan = ctx.plan;
  var query = querystring.parse(window.location.search);

  // Set up the views
  var views = {
    'filter-view': new FilterView(plan),
    'locations-view': new LocationsView(plan),
    'options-view': new OptionsView(plan),
    'planner-nav': new PlannerNav(session)
  };

  ctx.view = new View(views);
  ctx.view.on('rendered', function() {
    // Set plan to loading
    plan.loading(true);

    for (var key in views) {
      views[key].emit('rendered', views[key]);
    }

    // Show the map
    var map = showMapView(ctx.view.find('.MapView'));

    // Create the transitive layer
    var transitiveLayer = new LeafletTransitiveLayer(transitive);

    // Set the transitive layer
    map.addLayer(transitiveLayer);

    // Update map on plan change
    updateMapOnPlanChange(plan, map, transitive, transitiveLayer);

    // Clear plan & cookies for now, plan will re-save automatically on save
    plan.clearStore();

    // If it's a shared URL or welcome is complete skip the welcome screen
    if ((query.from && query.to) || session.commuter().profile().welcome_wizard_complete) {
      showQuery(query);
    } else {
      showWelcomeWizard(session);
    }
  });

  plan.on('updating options', function() {
    ctx.view.panelFooter.classList.add('hidden');
  });

  plan.on('updating options complete', function(res) {
    if (res && !res.err) ctx.view.panelFooter.classList.remove('hidden');
  });

  next();
};

/**
 * Reverse Commute
 */

View.prototype.reverseCommute = function(e) {
  e.preventDefault();
  var plan = session.plan();
  plan.set({
    from: plan.to(),
    from_id: plan.to_id(),
    from_ll: plan.to_ll(),
    to: plan.from(),
    to_id: plan.from_id(),
    to_ll: plan.from_ll()
  });

  plan.updateRoutes();
};

/**
 * Scroll
 */

View.prototype.scroll = function(e) {
  e.preventDefault();
  this.scrollable.scrollTop += (this.scrollable.scrollHeight / 5);
};

/**
 * On submit
 */

View.prototype.onsubmit = function(e) {
  e.preventDefault();
};

/**
 * Help Me Choose
 */

View.prototype.helpMeChoose = function(e) {
  HelpMeChoose(session.plan().options()).show();
};

/**
 * Show Journey
 */

function showQuery(query) {
  var plan = session.plan();
  // If no querystring, see if we have them in the plan already
  var from = query.from || plan.from() || FROM;
  var to = query.to || plan.to() || TO;
  var sameAddresses = from === plan.from() && to === plan.to();

  // Set plan from querystring
  if (query.modes) plan.setModes(query.modes);
  if (query.start_time !== undefined) plan.start_time(parseInt(query.start_time, 10));
  if (query.end_time !== undefined) plan.end_time(parseInt(query.end_time, 10));
  if (query.days !== undefined) plan.days(query.days);

  // If has valid coordinates, load
  if (plan.validCoordinates() && sameAddresses) {
    plan.journey({
      places: plan.generatePlaces()
    });
    plan.updateRoutes();
  } else {
    // Set addresses and update the routes
    plan.setAddresses(from, to, function(err) {
      if (err) {
        log.error('%e', err);
      } else {
        plan.journey({
          places: plan.generatePlaces()
        });
        plan.updateRoutes();
      }
    });
  }
}

/**
 * Update Map on plan change
 */

function updateMapOnPlanChange(plan, map, transitive, transitiveLayer) {
  // Register plan update events
  plan.on('change journey', function(journey) {
    if (journey && !isMobile) {
      try {
        log('updating data');
        transitive.updateData(journey);
        map.fitBounds(transitiveLayer.getBounds());
      } catch (e) {
        console.error(e);
      }
    }
  });
}
<commit_msg>Fixed grey map issue when no addresses are typed<commit_after>var config = require('config');
var FilterView = require('filter-view');
var HelpMeChoose = require('help-me-choose-view');
var LeafletTransitiveLayer = require('Leaflet.TransitiveLayer');
var LocationsView = require('locations-view');
var log = require('./client/log')('planner-page');
var showMapView = require('map-view');
var OptionsView = require('options-view');
var PlannerNav = require('planner-nav');
var querystring = require('querystring');
var scrollbarSize = require('scrollbar-size');
var scrolling = require('scrolling');
var session = require('session');
var textModal = require('text-modal');
var transitive = require('transitive');
var ua = require('user-agent');
var view = require('view');
var showWelcomeWizard = require('welcome-flow');

var FROM = config.geocode().start_address;
var TO = config.geocode().end_address;
var isMobile = window.innerWidth <= 480;
var center = config.geocode().center.split(',').map(parseFloat);

var View = view(require('./template.html'), function(view, model) {
  view.scrollable = view.find('.scrollable');
  view.panelFooter = view.find('.footer');

  if (scrollbarSize > 0) {
    if (ua.os.name === 'Windows' || ua.browser.name !== 'Chrome')
      view.scrollable.style.marginRight = -scrollbarSize + 'px';

    // Scrollbars are fun and implemented the same on every OS/Browser...right
    if (ua.os.name === 'Windows' && ua.browser.name === 'Chrome')
      view.scrollable.style.paddingRight = scrollbarSize + 'px';
  }
});

/**
 * Expose `render`
 */

module.exports = function(ctx, next) {
  log('render');

  var plan = ctx.plan;
  var query = querystring.parse(window.location.search);

  // Set up the views
  var views = {
    'filter-view': new FilterView(plan),
    'locations-view': new LocationsView(plan),
    'options-view': new OptionsView(plan),
    'planner-nav': new PlannerNav(session)
  };

  ctx.view = new View(views);
  ctx.view.on('rendered', function() {
    // Set plan to loading
    plan.loading(true);

    for (var key in views) {
      views[key].emit('rendered', views[key]);
    }

    // Show the map
    var map = showMapView(ctx.view.find('.MapView'));

    // Create the transitive layer
    var transitiveLayer = new LeafletTransitiveLayer(transitive);

    // Set the transitive layer
    map.addLayer(transitiveLayer);

    // Update map on plan change
    updateMapOnPlanChange(plan, map, transitive, transitiveLayer);

    // Clear plan & cookies for now, plan will re-save automatically on save
    plan.clearStore();

    // If it's a shared URL or welcome is complete skip the welcome screen
    if ((query.from && query.to) || session.commuter().profile().welcome_wizard_complete) {
      showQuery(query);
    } else {
      showWelcomeWizard(session);
    }
  });

  plan.on('updating options', function() {
    ctx.view.panelFooter.classList.add('hidden');
  });

  plan.on('updating options complete', function(res) {
    if (res && !res.err) ctx.view.panelFooter.classList.remove('hidden');
  });

  next();
};

/**
 * Reverse Commute
 */

View.prototype.reverseCommute = function(e) {
  e.preventDefault();
  var plan = session.plan();
  plan.set({
    from: plan.to(),
    from_id: plan.to_id(),
    from_ll: plan.to_ll(),
    to: plan.from(),
    to_id: plan.from_id(),
    to_ll: plan.from_ll()
  });

  plan.updateRoutes();
};

/**
 * Scroll
 */

View.prototype.scroll = function(e) {
  e.preventDefault();
  this.scrollable.scrollTop += (this.scrollable.scrollHeight / 5);
};

/**
 * On submit
 */

View.prototype.onsubmit = function(e) {
  e.preventDefault();
};

/**
 * Help Me Choose
 */

View.prototype.helpMeChoose = function(e) {
  HelpMeChoose(session.plan().options()).show();
};

/**
 * Show Journey
 */

function showQuery(query) {
  var plan = session.plan();
  // If no querystring, see if we have them in the plan already
  var from = query.from || plan.from() || FROM;
  var to = query.to || plan.to() || TO;
  var sameAddresses = from === plan.from() && to === plan.to();

  // Set plan from querystring
  if (query.modes) plan.setModes(query.modes);
  if (query.start_time !== undefined) plan.start_time(parseInt(query.start_time, 10));
  if (query.end_time !== undefined) plan.end_time(parseInt(query.end_time, 10));
  if (query.days !== undefined) plan.days(query.days);

  // If has valid coordinates, load
  if (plan.validCoordinates() && sameAddresses) {
    plan.journey({
      places: plan.generatePlaces()
    });
    plan.updateRoutes();
  } else {
    // Set addresses and update the routes
    plan.setAddresses(from, to, function(err) {
      if (err) {
        log.error('%e', err);
      } else {
        plan.journey({
          places: plan.generatePlaces()
        });
        plan.updateRoutes();
      }
    });
  }
}

/**
 * Update Map on plan change
 */

function updateMapOnPlanChange(plan, map, transitive, transitiveLayer) {
  // Register plan update events
  plan.on('change journey', function(journey) {
    if (journey && !isMobile) {
      try {
        log('updating data');
        transitive.updateData(journey);
        map.fitBounds(transitiveLayer.getBounds());
      } catch (e) {
	map.setView([center[1], center[0]], config.geocode().zoom);
        console.error(e);
      }
    }
  });
}
<|endoftext|>


<commit_before>if(!jsBackend) { var jsBackend = new Object(); }

/**
 * CK Editor related objects
 *
 * @author	Tijs Verkoyen <tijs@sumocoders.be>
 */
jsBackend.ckeditor =
{
	// initialize the editor
	init: function()
	{
		// load the editor
		if($('textarea.inputEditor, textarea.inputEditorError').length > 0)
		{
			// bind on some global events
			CKEDITOR.on('dialogDefinition', jsBackend.ckeditor.onDialogDefinition);
			CKEDITOR.on('instanceReady', jsBackend.ckeditor.onReady);

			// load the editors
			jsBackend.ckeditor.load();
		}
	},

	load: function()
	{
		// bind on inputEditor and inputEditorError
		$('textarea.inputEditor, textarea.inputEditorError').ckeditor(
			jsBackend.ckeditor.callback,
			{
				customConfig: '',

				// layout configuration
				bodyClass: 'content',
				contentsCss: [
								'/frontend/core/layout/css/screen.css',{option:THEME_HAS_CSS}
								'/frontend/themes/{$THEME}/core/layout/css/screen.css', {/option:THEME_HAS_CSS}
								'/backend/core/layout/css/editor_content.css'{option:THEME_HAS_EDITOR_CSS},
								'/frontend/themes/{$THEME}/core/layout/css/editor_content.css'{/option:THEME_HAS_EDITOR_CSS}
							],
				stylesSet: [],

				// language options
				contentsLanguage: '{$LANGUAGE}',
				language: '{$INTERFACE_LANGUAGE}',

				// paste options
				forcePasteAsPlainText: true,

				toolbar_Simple: [
					{ name: 'basicstyles', items: ['Bold', 'Italic', 'Strike']},
					{ name: 'clipboard', items: ['Undo', 'Redo']},
					{ name: 'paragraph', items: ['NumberedList', 'BulletedList', 'Blockquote']},
					{ name: 'links', items: ['Link', 'Unlink', 'Anchor']},
					{ name: 'document', items: ['Source']},
					'/',
					{ name: 'insert', items : ['Image', 'MediaEmbed', '-', 'SpecialChar']},
					{ name: 'styles', items : ['Format', 'Styles']}
				],

				// buttons
				toolbar_Full: [
					{ name: 'basicstyles', items: ['Bold', 'Italic', 'Strike']},
					{ name: 'clipboard', items: ['Undo', 'Redo']},
					{ name: 'paragraph', items: ['NumberedList', 'BulletedList', 'Blockquote']},
					{ name: 'links', items: ['Link', 'Unlink', 'Anchor']},
					{ name: 'document', items: ['Source', 'ShowBlocks', 'Maximize', 'Templates']},
					'/',
					{ name: 'insert', items : ['Table', '-', 'Image', 'MediaEmbed', '-', 'SpecialChar']},
					{ name: 'styles', items : ['Format', 'Styles']}
				],

				// layout
				skin: 'kama',
				uiColor: '#E7F0F8',
				toolbarStartupExpanded: false,

				// entities
				entities: false,
				entities_greek: false,
				entities_latin: false,

				// load some extra plugins
				// extraPlugins: 'stylesheetparser,MediaEmbed',
				extraPlugins: 'MediaEmbed',

				// remove useless plugins
				removePlugins: 'a11yhelp,about,bidi,colorbutton,colordialog,elementspath,font,find,flash,forms,horizontalrule,indent,newpage,pagebreak,preview,print,scayt,smiley'
			}
		);
	},

	callback: function(element)
	{
		// add the click to edit div
		$(element).before('
{$msgClickToEdit|addslashes}
');

		// add the optionsRTE-class if it isn't present
		if(!$(element).parent('div, p').hasClass('optionsRTE')) $(element).parent('div, p').addClass('optionsRTE');

		// add the CKFinder
		CKFinder.setupCKEditor(null, {
			basePath: '/backend/core/js/ckfinder',
			width: 800
		});
	},

	checkContent: function(evt)
	{
		// get the editor
		var editor = evt.editor;

		// on initalisation we should force the check, which will be passed in the data-container
		var forced = (typeof evt.forced == 'boolean') ? evt.forced : false;

		// was the content changed, or is the check forced?
		if(editor.checkDirty() || forced)
		{
			var content = editor.getData();
			var warnings = [];

			// no alt?
			if(content.match(/ 0)
			{
				// append the warnings after the editor
				$('#cke_' + editor.element.getId()).after(''+ warnings.join(' ') + '');
			}
		}
	},

	onDialogDefinition: function(evt)
	{
		// get the dialog definition
		var dialogDefinition = evt.data.definition;

		// specific stuff for the image-dialog
		if(evt.data.name == 'image')
		{
			// remove the advanced tab because it is confusing fo the end-user
			dialogDefinition.removeContents('advanced');

			// remove the upload tab because we like our users to think about the place of their images
			dialogDefinition.removeContents('Upload');

			// remove the Link tab because there is no point of using two interfaces for the same outcome
			dialogDefinition.removeContents('Link');

			// get the info tab
			var infoTab = dialogDefinition.getContents('info');

			// remove fields we don't want to use, because they will mess up the layout
			infoTab.remove('txtBorder');
			infoTab.remove('txtHSpace');
			infoTab.remove('txtVSpace');
			infoTab.remove('txtBorder');
			infoTab.remove('cmbAlign');
		}

		// specific stuff for the link-dialog
		if(evt.data.name == 'link')
		{
			// remove the advanced tab because it is confusing fo the end-user
			dialogDefinition.removeContents('advanced');

			// remove the upload tab because we like our users to think about the place of their images
			dialogDefinition.removeContents('upload');

			// get the info tab
			var infoTab = dialogDefinition.getContents('info');

			// add a new element
			infoTab.add(
				{
					type: 'vbox',
					id: 'localPageOptions',
					children: [
						{
							type: 'select',
							label: '{$msgEditorSelectInternalPage}',
							id: 'localPage',
							title: '{$msgEditorSelectInternalPage}',
							items: linkList,
							onChange: function(evt)
							{
								CKEDITOR.dialog.getCurrent().getContentElement('info', 'url').setValue(evt.data.value);
							}
						}
					]
				}
			);
		}

		// specific stuff for the table-dialog
		if(evt.data.name == 'table')
		{
			// remove the advanced tab because it is confusing fo the end-user
			dialogDefinition.removeContents('advanced');

			// get the info tab
			var infoTab = dialogDefinition.getContents('info');

			// remove fields we don't want to use, because they will mess up the layout
			infoTab.remove('txtBorder');
			infoTab.remove('cmbAlign');
			infoTab.remove('txtCellSpace');
			infoTab.remove('txtCellPad');

			// set a beter default for the width
			infoTab.get('txtWidth')['default'] = '100%';
		}
	},

	onBlur: function(evt)
	{
		// current element
		var $currentElement = $(document.activeElement);
		var outsideEditor = true;

		// check if the current active elements is an element related to an editor
		if(typeof $currentElement.attr('id') != 'undefined' && $currentElement.attr('id').indexOf('cke_') >= 0) outsideEditor = false;
		else if(typeof $currentElement.attr('class') != 'undefined' && $currentElement.attr('class').indexOf('cke_') >= 0) outsideEditor = false;

		// focus outside the editor?
		if(outsideEditor)
		{
			// show the click to edit
			$('#cke_' + evt.editor.name).siblings('div.clickToEdit').show();

			// hide the toolbar
			if($('#cke_top_' + evt.editor.name + ' .cke_toolbox').is(':visible')) $('#cke_top_' + evt.editor.name + ' .cke_toolbox').hide();
		}

		// check the content
		jsBackend.ckeditor.checkContent(evt);
	},

	onFocus: function(evt)
	{
		// hide the click to edit
		$('#cke_' + evt.editor.name).siblings('div.clickToEdit').hide();

		// show the toolbar, I know the little icon isn't correct.
		if($('#cke_top_' + evt.editor.name + ' .cke_toolbox').is(':hidden')) $('#cke_top_' + evt.editor.name + ' .cke_toolbox').show();
	},

	onReady: function(evt)
	{
		// bind on blur and focus
		evt.editor.on('blur', jsBackend.ckeditor.onBlur);
		evt.editor.on('focus', jsBackend.ckeditor.onFocus);

		// force the content check
		jsBackend.ckeditor.checkContent({ editor: evt.editor, forced: true });
	},
}

$(document).ready(jsBackend.ckeditor.init);<commit_msg>Made it possible to have multiple configs<commit_after>if(!jsBackend) { var jsBackend = new Object(); }

/**
 * CK Editor related objects
 * @todo	merge this into backend.js after the improvements of Thomas are merged.
 * @todo	cleanup, check Thomas' branch
 *
 * @author	Tijs Verkoyen <tijs@sumocoders.be>
 */
jsBackend.ckeditor =
{
	defaultConfig: {
		customConfig: '',

		// layout configuration
		bodyClass: 'content',
		contentsCss: [
						'/frontend/core/layout/css/screen.css',{option:THEME_HAS_CSS}
						'/frontend/themes/{$THEME}/core/layout/css/screen.css', {/option:THEME_HAS_CSS}
						'/backend/core/layout/css/editor_content.css'{option:THEME_HAS_EDITOR_CSS},
						'/frontend/themes/{$THEME}/core/layout/css/editor_content.css'{/option:THEME_HAS_EDITOR_CSS}
					],
		stylesSet: [],

		// language options
		contentsLanguage: '{$LANGUAGE}',
		language: '{$INTERFACE_LANGUAGE}',

		// paste options
		forcePasteAsPlainText: true,

		// buttons
		toolbar_Full: [
			{ name: 'basicstyles', items: ['Bold', 'Italic', 'Strike']},
			{ name: 'clipboard', items: ['Undo', 'Redo']},
			{ name: 'paragraph', items: ['NumberedList', 'BulletedList', 'Blockquote']},
			{ name: 'links', items: ['Link', 'Unlink', 'Anchor']},
			{ name: 'document', items: ['Source', 'ShowBlocks', 'Maximize', 'Templates']},
			'/',
			{ name: 'insert', items : ['Table', '-', 'Image', 'MediaEmbed', '-', 'SpecialChar']},
			{ name: 'styles', items : ['Format', 'Styles']}
		],

		// layout
		skin: 'kama',
		uiColor: '#E7F0F8',
		toolbarStartupExpanded: false,

		// entities
		entities: false,
		entities_greek: false,
		entities_latin: false,

		// load some extra plugins
		// extraPlugins: 'stylesheetparser,MediaEmbed',
		extraPlugins: 'MediaEmbed',

		// remove useless plugins
		removePlugins: 'a11yhelp,about,bidi,colorbutton,colordialog,elementspath,font,find,flash,forms,horizontalrule,indent,newpage,pagebreak,preview,print,scayt,smiley',

		// custom vars
		editorType: 'default',
		showClickToEdit: true
	},

	// initialize the editor
	init: function()
	{
		// load the editor
		if($('textarea.inputEditor, textarea.inputEditorError, textarea.inputEditorNewsletter, textarea.inputEditorNewsletterError').length > 0)
		{
			// bind on some global events
			CKEDITOR.on('dialogDefinition', jsBackend.ckeditor.onDialogDefinition);
			CKEDITOR.on('instanceReady', jsBackend.ckeditor.onReady);

			// load the editors
			jsBackend.ckeditor.load();
		}
	},

	load: function()
	{
		// extend the editor config
		var editorConfig = $.extend(jsBackend.ckeditor.defaultConfig, {});

		// specific config for the newsletter
		var newsletterConfig = $.extend(jsBackend.ckeditor, {
			showClickToEdit: false
		});

		// bind on inputEditor and inputEditorError
		$('textarea.inputEditor, textarea.inputEditorError').ckeditor(jsBackend.ckeditor.callback, editorConfig);
		$('textarea.inputEditorNewsletter, textarea.inputEditorNewsletterError').ckeditor(jsBackend.ckeditor.callback, newsletterConfig);
	},

	callback: function(element)
	{
		if($(element).ckeditorGet().config.showClickToEdit)
		{
			// add the click to edit div
			$(element).before('
{$msgClickToEdit|addslashes}
');
		}

		// add the optionsRTE-class if it isn't present
		if(!$(element).parent('div, p').hasClass('optionsRTE')) $(element).parent('div, p').addClass('optionsRTE');

		// add the CKFinder
		CKFinder.setupCKEditor(null, {
			basePath: '/backend/core/js/ckfinder',
			width: 800
		});
	},

	checkContent: function(evt)
	{
		// get the editor
		var editor = evt.editor;

		// on initalisation we should force the check, which will be passed in the data-container
		var forced = (typeof evt.forced == 'boolean') ? evt.forced : false;

		// was the content changed, or is the check forced?
		if(editor.checkDirty() || forced)
		{
			var content = editor.getData();
			var warnings = [];

			// no alt?
			if(content.match(/ 0)
			{
				// append the warnings after the editor
				$('#cke_' + editor.element.getId()).after(''+ warnings.join(' ') + '');
			}
		}
	},

	onDialogDefinition: function(evt)
	{
		// get the dialog definition
		var dialogDefinition = evt.data.definition;

		// specific stuff for the image-dialog
		if(evt.data.name == 'image')
		{
			// remove the advanced tab because it is confusing fo the end-user
			dialogDefinition.removeContents('advanced');

			// remove the upload tab because we like our users to think about the place of their images
			dialogDefinition.removeContents('Upload');

			// remove the Link tab because there is no point of using two interfaces for the same outcome
			dialogDefinition.removeContents('Link');

			// get the info tab
			var infoTab = dialogDefinition.getContents('info');

			// remove fields we don't want to use, because they will mess up the layout
			infoTab.remove('txtBorder');
			infoTab.remove('txtHSpace');
			infoTab.remove('txtVSpace');
			infoTab.remove('txtBorder');
			infoTab.remove('cmbAlign');
		}

		// specific stuff for the link-dialog
		if(evt.data.name == 'link')
		{
			// remove the advanced tab because it is confusing fo the end-user
			dialogDefinition.removeContents('advanced');

			// remove the upload tab because we like our users to think about the place of their images
			dialogDefinition.removeContents('upload');

			// get the info tab
			var infoTab = dialogDefinition.getContents('info');

			// add a new element
			infoTab.add(
				{
					type: 'vbox',
					id: 'localPageOptions',
					children: [
						{
							type: 'select',
							label: '{$msgEditorSelectInternalPage}',
							id: 'localPage',
							title: '{$msgEditorSelectInternalPage}',
							items: linkList,
							onChange: function(evt)
							{
								CKEDITOR.dialog.getCurrent().getContentElement('info', 'url').setValue(evt.data.value);
							}
						}
					]
				}
			);
		}

		// specific stuff for the table-dialog
		if(evt.data.name == 'table')
		{
			// remove the advanced tab because it is confusing fo the end-user
			dialogDefinition.removeContents('advanced');

			// get the info tab
			var infoTab = dialogDefinition.getContents('info');

			// remove fields we don't want to use, because they will mess up the layout
			infoTab.remove('txtBorder');
			infoTab.remove('cmbAlign');
			infoTab.remove('txtCellSpace');
			infoTab.remove('txtCellPad');

			// set a beter default for the width
			infoTab.get('txtWidth')['default'] = '100%';
		}
	},

	onBlur: function(evt)
	{
		// current element
		var $currentElement = $(document.activeElement);
		var outsideEditor = true;

		// check if the current active elements is an element related to an editor
		if(typeof $currentElement.attr('id') != 'undefined' && $currentElement.attr('id').indexOf('cke_') >= 0) outsideEditor = false;
		else if(typeof $currentElement.attr('class') != 'undefined' && $currentElement.attr('class').indexOf('cke_') >= 0) outsideEditor = false;

		// focus outside the editor?
		if(outsideEditor)
		{
			// show the click to edit
			$('#cke_' + evt.editor.name).siblings('div.clickToEdit').show();

			// hide the toolbar
			if($('#cke_top_' + evt.editor.name + ' .cke_toolbox').is(':visible')) $('#cke_top_' + evt.editor.name + ' .cke_toolbox').hide();
		}

		// check the content
		jsBackend.ckeditor.checkContent(evt);
	},

	onFocus: function(evt)
	{
		// hide the click to edit
		$('#cke_' + evt.editor.name).siblings('div.clickToEdit').hide();

		// show the toolbar, I know the little icon isn't correct.
		if($('#cke_top_' + evt.editor.name + ' .cke_toolbox').is(':hidden')) $('#cke_top_' + evt.editor.name + ' .cke_toolbox').show();
	},

	onReady: function(evt)
	{
		// bind on blur and focus
		evt.editor.on('blur', jsBackend.ckeditor.onBlur);
		evt.editor.on('focus', jsBackend.ckeditor.onFocus);

		// force the content check
		jsBackend.ckeditor.checkContent({ editor: evt.editor, forced: true });
	},
}

$(document).ready(jsBackend.ckeditor.init);<|endoftext|>


<commit_before>define([
	'goo/loaders/handlers/ComponentHandler',
	'goo/entities/components/TransformComponent',
	'goo/math/MathUtils',
	'goo/math/Quaternion',
	'goo/util/PromiseUtil',
	'goo/util/ObjectUtil',
	'goo/util/ArrayUtil',
	'goo/util/rsvp'
],
/** @lends */
function(
	ComponentHandler,
	TransformComponent,
	MathUtils,
	Quaternion,
	PromiseUtil,
	_,
	ArrayUtil,
	RSVP
) {
	"use strict";

	/**
	 * @class For handling loading of transform component
	 * @constructor
	 * @param {World} world The goo world
	 * @param {function} getConfig The config loader function. See {@see DynamicLoader._loadRef}.
	 * @param {function} updateObject The handler function. See {@see DynamicLoader.update}.
	 * @private
	 * @extends ComponentHandler
	 */
	function TransformComponentHandler() {
		ComponentHandler.apply(this, arguments);
		this._type = 'TransformComponent';
	}

	TransformComponentHandler.prototype = Object.create(ComponentHandler.prototype);
	TransformComponentHandler.prototype.constructor = TransformComponentHandler;
	ComponentHandler._registerClass('transform', TransformComponentHandler);

	/**
	 * Prepare component. Set defaults on config here.
	 * @param {object} config
	 * @private
	 */
	TransformComponentHandler.prototype._prepare = function(config) {
		return _.defaults(config, {
			translation: [0, 0, 0],
			rotation: [0, 0, 0],
			scale: [1, 1, 1]
		});
	};

	/**
	 * Create transform component object based on the config.
	 * @param {Entity} entity The entity on which this component should be added.
	 * @returns {TransformComponent} the created component object
	 * @private
	 */
	TransformComponentHandler.prototype._create = function() {
		return new TransformComponent();
	};

	/**
	 * Remove engine component object. TransformComponents can't be removed, so we reset.
	 * @param {Entity} entity The entity from which this component should be removed.
	 * @private
	 */
	TransformComponentHandler.prototype._remove = function(entity) {
		var component = entity.transformComponent;
		// Reset
		component.transform.translation.setd(0, 0, 0);
		component.transform.setRotationXYZ(0, 0, 0);
		component.transform.scale.setd(1, 1, 1);

		// Detach all children
		for (var i = 0; i < component.children.length; i++) {
			var child = component.children[i];
			component.detachChild(child);
		}
		component.setUpdated();
	};

	/**
	 * Update engine transform component object based on the config.
	 * @param {Entity} entity The entity on which this component should be added.
	 * @param {object} config
	 * @param {object} options
	 * @returns {RSVP.Promise} promise that resolves with the component when loading is done.
	 */
	TransformComponentHandler.prototype.update = function(entity, config, options) {
		var that = this;

		function attachChild(component, ref) {
			return that.loadObject(ref, options).then(function(entity) {
				if (entity && entity.transformComponent) {
					component.attachChild(entity.transformComponent);
					if (
						// Not already in world
						!that.world.entityManager.containsEntity(entity) &&
						that.world._addedEntities.indexOf(entity) === -1 &&
						// Parent is in world
						(that.world.entityManager.containsEntity(component.entity) ||
						that.world._addedEntities.indexOf(component.entity) > -1)
					) {
						entity.addToWorld();
					}
				} else {
					console.error('Failed to add child to transform component');
				}
				return component;
			});
		}

		return ComponentHandler.prototype.update.call(this, entity, config, options).then(function(component) {
			if (!component) {
				// Component was removed
				return;
			}

			// Translation
			component.transform.translation.seta(config.translation);
			// Rotation
			component.transform.setRotationXYZ(
				MathUtils.radFromDeg(config.rotation[0]),
				MathUtils.radFromDeg(config.rotation[1]),
				MathUtils.radFromDeg(config.rotation[2])
			);
			// Scale
			component.transform.scale.seta(config.scale);

			var promises = [];
			if (config.children) {
				// Attach children
				// TODO: Watch out for circular dependencies
				// TODO: Use sort values
				var keys = Object.keys(config.children);
				for (var i = 0; i < keys.length; i++) {
					var childRef = config.children[keys[i]].entityRef;
					promises.push(attachChild(component, childRef));
				}
				for (var i = 0; i < component.children.length; i++) {
					var child = component.children[i];
					var id = child.entity.id;
					if (!config.children[id]) {
						component.detachChild(child);
					}
				}
			} else {
				// Detach all children
				for (var i = 0; i < component.children.length; i++) {
					var child = component.children[i];
					component.detachChild(child);
				}
			}

			// When all children are attached, return component
			return RSVP.all(promises).then(function() {
				component.setUpdated();
				return component;
			});
		});
	};

	return TransformComponentHandler;
});
<commit_msg>Review of https://trello.com/c/jvHFvHco/1510-normal-dynamicloader-load-blaha-entity-should-add-blaha-entity-to-world-not-just-its-children<commit_after>define([
	'goo/loaders/handlers/ComponentHandler',
	'goo/entities/components/TransformComponent',
	'goo/math/MathUtils',
	'goo/math/Quaternion',
	'goo/util/PromiseUtil',
	'goo/util/ObjectUtil',
	'goo/util/ArrayUtil',
	'goo/util/rsvp'
],
/** @lends */
function(
	ComponentHandler,
	TransformComponent,
	MathUtils,
	Quaternion,
	PromiseUtil,
	_,
	ArrayUtil,
	RSVP
) {
	"use strict";

	/**
	 * @class For handling loading of transform component
	 * @constructor
	 * @param {World} world The goo world
	 * @param {function} getConfig The config loader function. See {@see DynamicLoader._loadRef}.
	 * @param {function} updateObject The handler function. See {@see DynamicLoader.update}.
	 * @private
	 * @extends ComponentHandler
	 */
	function TransformComponentHandler() {
		ComponentHandler.apply(this, arguments);
		this._type = 'TransformComponent';
	}

	TransformComponentHandler.prototype = Object.create(ComponentHandler.prototype);
	TransformComponentHandler.prototype.constructor = TransformComponentHandler;
	ComponentHandler._registerClass('transform', TransformComponentHandler);

	/**
	 * Prepare component. Set defaults on config here.
	 * @param {object} config
	 * @private
	 */
	TransformComponentHandler.prototype._prepare = function(config) {
		return _.defaults(config, {
			translation: [0, 0, 0],
			rotation: [0, 0, 0],
			scale: [1, 1, 1]
		});
	};

	/**
	 * Create transform component object based on the config.
	 * @param {Entity} entity The entity on which this component should be added.
	 * @returns {TransformComponent} the created component object
	 * @private
	 */
	TransformComponentHandler.prototype._create = function() {
		return new TransformComponent();
	};

	/**
	 * Remove engine component object. TransformComponents can't be removed, so we reset.
	 * @param {Entity} entity The entity from which this component should be removed.
	 * @private
	 */
	TransformComponentHandler.prototype._remove = function(entity) {
		var component = entity.transformComponent;
		// Reset
		component.transform.translation.setd(0, 0, 0);
		component.transform.setRotationXYZ(0, 0, 0);
		component.transform.scale.setd(1, 1, 1);

		// Detach all children
		for (var i = 0; i < component.children.length; i++) {
			var child = component.children[i];
			component.detachChild(child);
		}
		component.setUpdated();
	};

	/**
	 * Update engine transform component object based on the config.
	 * @param {Entity} entity The entity on which this component should be added.
	 * @param {object} config
	 * @param {object} options
	 * @returns {RSVP.Promise} promise that resolves with the component when loading is done.
	 */
	TransformComponentHandler.prototype.update = function(entity, config, options) {
		var that = this;

		function attachChild(component, ref) {
			return that.loadObject(ref, options).then(function(entity) {
				if (entity && entity.transformComponent) {
					component.attachChild(entity.transformComponent);
					/* REVIEW: how about this for clarity
					var entityInWorld = that.world.entityManager.containsEntity(entity) ||
						that.world._addedEntities.indexOf(entity) !== -1;
					var parentInWorld = that.world.entityManager.containsEntity(component.entity) ||
						that.world._addedEntities.indexOf(component.entity) > -1;

					if (!entityInWorld && parentInWorld) {

					*/
					if (
						// Not already in world
						!that.world.entityManager.containsEntity(entity) &&
						that.world._addedEntities.indexOf(entity) === -1 &&
						// Parent is in world
						(that.world.entityManager.containsEntity(component.entity) ||
						that.world._addedEntities.indexOf(component.entity) > -1)
					) {
						entity.addToWorld();
					}
				} else {
					console.error('Failed to add child to transform component');
				}
				return component;
			});
		}

		return ComponentHandler.prototype.update.call(this, entity, config, options).then(function(component) {
			if (!component) {
				// Component was removed
				return;
			}

			// Translation
			component.transform.translation.seta(config.translation);
			// Rotation
			component.transform.setRotationXYZ(
				MathUtils.radFromDeg(config.rotation[0]),
				MathUtils.radFromDeg(config.rotation[1]),
				MathUtils.radFromDeg(config.rotation[2])
			);
			// Scale
			component.transform.scale.seta(config.scale);

			var promises = [];
			if (config.children) {
				// Attach children
				// TODO: Watch out for circular dependencies
				// TODO: Use sort values
				var keys = Object.keys(config.children);
				for (var i = 0; i < keys.length; i++) {
					var childRef = config.children[keys[i]].entityRef;
					promises.push(attachChild(component, childRef));
				}
				for (var i = 0; i < component.children.length; i++) {
					var child = component.children[i];
					var id = child.entity.id;
					if (!config.children[id]) {
						component.detachChild(child);
					}
				}
			} else {
				// Detach all children
				for (var i = 0; i < component.children.length; i++) {
					var child = component.children[i];
					component.detachChild(child);
				}
			}

			// When all children are attached, return component
			return RSVP.all(promises).then(function() {
				component.setUpdated();
				return component;
			});
		});
	};

	return TransformComponentHandler;
});
<|endoftext|>