<commit_before>function cleanFootnotes(node, elements, marks) {
    if (node.attrs && node.attrs.footnote) {
        // We remove forbidden block nodes
        node.attrs.footnote = node.attrs.footnote.filter(node => !elements.includes(node.type))
        // We remove forbidden marks + inline nodes
        node.attrs.footnote.forEach(subNode => cleanNode(subNode, elements, marks))
    }
    if (node.content) {
        node.content.forEach(subNode => cleanFootnotes(subNode, elements, marks))
    }
}

function cleanNode(node, elements, marks) {
    if (node.marks) {
        // remove forbidden marks
        node.marks = node.marks.filter(mark => !marks.includes(mark.type))
        if (!node.marks.length) {
            delete node.marks
        }
    }
    if (node.content) {
        // remove forbidden elements
        node.content = node.content.filter(node => !elements.includes(node.type))
        node.content.forEach(subNode => cleanNode(subNode, elements, marks))
        if (!node.content.length) {
            delete node.content
        }
    }
}

export function adjustDocToTemplate(doc, template, documentStyles, citationStyles) {
    const removedFootnoteElements = doc.attrs.footnote_elements.filter(element => !template.attrs.footnote_elements.includes(element)),
        removedFootnoteMarks = doc.attrs.footnote_marks.filter(mark => !template.attrs.footnote_marks.includes(mark)),
        attrs = ['footnote_marks', 'footnote_elements', 'languages', 'papersizes', 'template']
    attrs.forEach(attr => doc.attrs[attr] = template.attrs[attr])

    if (!doc.attrs.languages.includes(doc.attrs.language)) {
        if (!doc.attrs.languages.length) {
            throw new Error('Document template allows no languages.')
        }
        doc.attrs.language = doc.attrs.languages[0]
    }

    if (!doc.attrs.papersizes.includes(doc.attrs.papersize)) {
        if (!doc.attrs.papersizes.length) {
            throw new Error('Document template allows no paper sizes.')
        }
        doc.attrs.papersize = doc.attrs.papersizes[0]
    }

    if (!documentStyles.map(style => style.filename).includes(doc.attrs.documentstyle)) {
        if (!documentStyles.length) {
            throw new Error('No document styles have been defined for document template.')
        }
        doc.attrs.documentstyle = documentStyles[0].filename
    }

    if (!citationStyles.map(style => style.short_title).includes(doc.attrs.citationstyle)) {
        if (!citationStyles.length) {
            throw new Error('No citation styles have been defined for document template.')
        }
        doc.attrs.citationstyle = citationStyles[0].short_title
    }

    if (removedFootnoteMarks.length || removedFootnoteElements.length) {
        cleanFootnotes(doc, removedFootnoteElements, removedFootnoteMarks)
    }

    const oldContent = doc.content
    doc.content = [
        oldContent.shift() // The title
    ]

    let movedParts = []

    template.content.slice(1).forEach(part => {
        let oldNode = oldContent.find(
            oldContentNode => oldContentNode.type === part.type && oldContentNode.attrs.id === part.attrs.id
        )
        if (oldNode) {
            while (oldNode !== oldContent[0]) {
                const firstOldContent = oldContent.shift(),
                    inTemplate = !!template.content.find(part => part.type === firstOldContent.type && part.attrs.id === firstOldContent.attrs.id)
                if (inTemplate) {
                    movedParts.push(firstOldContent)
                } else if (
                    firstOldContent.content &&
                    !firstOldContent.attrs.hidden &&
                    firstOldContent.attrs.locking !== 'fixed' &&
                    !(
                        // table with just first row, which is fixed.
                        firstOldContent.attrs.locking === 'header' &&
                        firstOldContent.content.length === 1
                    ) &&
                    !(
                        // heading/richtext with just the default contents
                        firstOldContent.attrs.elements &&
                        firstOldContent.content.length === 1 &&
                        firstOldContent.content[0].type === firstOldContent.attrs.elements[0] &&
                        !firstOldContent.content[0].content
                    )
                ) {
                    firstOldContent.attrs.deleted = true
                    doc.content.push(firstOldContent)
                }
            }
            oldContent.shift()
        } else {
            oldNode = movedParts.find(
                oldContentNode => oldContentNode.type === part.type && oldContentNode.attrs.id === part.attrs.id
            )
            if (oldNode) {
                movedParts = movedParts.filter(oldContentNode => oldContentNode !== oldNode)
            }
        }

        if (oldNode) {
            const newNode = Object.assign(
                    {},
                    oldNode,
                    {
                        attrs: {}
                    }
                )
            Object.entries(part.attrs).forEach(([key, value]) => {
                newNode.attrs[key] = value
            })
            if (newNode.attrs.optional) {
                newNode.attrs.hidden = oldNode.attrs.hidden
            }

            if (oldNode.attrs.elements) { // parts that define elements also define marks.
                const removedElements = oldNode.attrs.elements.filter(element => !newNode.attrs.elements.includes(element))
                const removedMarks = oldNode.attrs.marks.filter(mark => !newNode.attrs.marks.includes(mark))
                if (removedElements.length || removedMarks.length) {
                    cleanNode(newNode, removedElements, removedMarks)
                    if (!newNode.content && ['richtext_part', 'heading_part'].includes(part.type)) {
                        newNode.content = [{type: part.attrs.elements[0]}]
                    } else if (!newNode.content && part.type === 'table_part') {
                        newNode.content = [{type: 'table', content: [{type: 'table_row', content: [{type: 'table_cell', content: [{type: 'paragraph'}]}]}]}]
                    }
                }
            }
            doc.content.push(newNode)

        } else {
            // The node is new and didn't exist in the old document.
            doc.content.push(JSON.parse(JSON.stringify(part)))
        }

    })

    // move remaining oldContent items that were not in template.
    while (oldContent.length) {
        const newNode = oldContent.shift()
        newNode.attrs.deleted = true
        doc.content.push(newNode)
    }

    return doc

}
<commit_msg>remove TOCs and separators that are gone when template changes<commit_after>function cleanFootnotes(node, elements, marks) {
    if (node.attrs && node.attrs.footnote) {
        // We remove forbidden block nodes
        node.attrs.footnote = node.attrs.footnote.filter(node => !elements.includes(node.type))
        // We remove forbidden marks + inline nodes
        node.attrs.footnote.forEach(subNode => cleanNode(subNode, elements, marks))
    }
    if (node.content) {
        node.content.forEach(subNode => cleanFootnotes(subNode, elements, marks))
    }
}

function cleanNode(node, elements, marks) {
    if (node.marks) {
        // remove forbidden marks
        node.marks = node.marks.filter(mark => !marks.includes(mark.type))
        if (!node.marks.length) {
            delete node.marks
        }
    }
    if (node.content) {
        // remove forbidden elements
        node.content = node.content.filter(node => !elements.includes(node.type))
        node.content.forEach(subNode => cleanNode(subNode, elements, marks))
        if (!node.content.length) {
            delete node.content
        }
    }
}

export function adjustDocToTemplate(doc, template, documentStyles, citationStyles) {
    const removedFootnoteElements = doc.attrs.footnote_elements.filter(element => !template.attrs.footnote_elements.includes(element)),
        removedFootnoteMarks = doc.attrs.footnote_marks.filter(mark => !template.attrs.footnote_marks.includes(mark)),
        attrs = ['footnote_marks', 'footnote_elements', 'languages', 'papersizes', 'template']
    attrs.forEach(attr => doc.attrs[attr] = template.attrs[attr])

    if (!doc.attrs.languages.includes(doc.attrs.language)) {
        if (!doc.attrs.languages.length) {
            throw new Error('Document template allows no languages.')
        }
        doc.attrs.language = doc.attrs.languages[0]
    }

    if (!doc.attrs.papersizes.includes(doc.attrs.papersize)) {
        if (!doc.attrs.papersizes.length) {
            throw new Error('Document template allows no paper sizes.')
        }
        doc.attrs.papersize = doc.attrs.papersizes[0]
    }

    if (!documentStyles.map(style => style.filename).includes(doc.attrs.documentstyle)) {
        if (!documentStyles.length) {
            throw new Error('No document styles have been defined for document template.')
        }
        doc.attrs.documentstyle = documentStyles[0].filename
    }

    if (!citationStyles.map(style => style.short_title).includes(doc.attrs.citationstyle)) {
        if (!citationStyles.length) {
            throw new Error('No citation styles have been defined for document template.')
        }
        doc.attrs.citationstyle = citationStyles[0].short_title
    }

    if (removedFootnoteMarks.length || removedFootnoteElements.length) {
        cleanFootnotes(doc, removedFootnoteElements, removedFootnoteMarks)
    }

    const oldContent = doc.content
    doc.content = [
        oldContent.shift() // The title
    ]

    let movedParts = []

    template.content.slice(1).forEach(part => {
        let oldNode = oldContent.find(
            oldContentNode => oldContentNode.type === part.type && oldContentNode.attrs.id === part.attrs.id
        )
        if (oldNode) {
            while (oldNode !== oldContent[0]) {
                const firstOldContent = oldContent.shift(),
                    inTemplate = !!template.content.find(part => part.type === firstOldContent.type && part.attrs.id === firstOldContent.attrs.id)
                if (inTemplate) {
                    movedParts.push(firstOldContent)
                } else if (
                    firstOldContent.content &&
                    !firstOldContent.attrs.hidden &&
                    firstOldContent.attrs.locking !== 'fixed' &&
                    !(
                        // table with just first row, which is fixed.
                        firstOldContent.attrs.locking === 'header' &&
                        firstOldContent.content.length === 1
                    ) &&
                    !(
                        // heading/richtext with just the default contents
                        firstOldContent.attrs.elements &&
                        firstOldContent.content.length === 1 &&
                        firstOldContent.content[0].type === firstOldContent.attrs.elements[0] &&
                        !firstOldContent.content[0].content
                    )
                ) {
                    firstOldContent.attrs.deleted = true
                    doc.content.push(firstOldContent)
                }
            }
            oldContent.shift()
        } else {
            oldNode = movedParts.find(
                oldContentNode => oldContentNode.type === part.type && oldContentNode.attrs.id === part.attrs.id
            )
            if (oldNode) {
                movedParts = movedParts.filter(oldContentNode => oldContentNode !== oldNode)
            }
        }
        if (oldNode) {
            const newNode = Object.assign(
                    {},
                    oldNode,
                    {
                        attrs: {}
                    }
                )
            Object.entries(part.attrs).forEach(([key, value]) => {
                newNode.attrs[key] = value
            })
            if (newNode.attrs.optional) {
                newNode.attrs.hidden = oldNode.attrs.hidden
            }

            if (oldNode.attrs.elements) { // parts that define elements also define marks.
                const removedElements = oldNode.attrs.elements.filter(element => !newNode.attrs.elements.includes(element))
                const removedMarks = oldNode.attrs.marks.filter(mark => !newNode.attrs.marks.includes(mark))
                if (removedElements.length || removedMarks.length) {
                    cleanNode(newNode, removedElements, removedMarks)
                    if (!newNode.content && ['richtext_part', 'heading_part'].includes(part.type)) {
                        newNode.content = [{type: part.attrs.elements[0]}]
                    } else if (!newNode.content && part.type === 'table_part') {
                        newNode.content = [{type: 'table', content: [{type: 'table_row', content: [{type: 'table_cell', content: [{type: 'paragraph'}]}]}]}]
                    }
                }
            }
            doc.content.push(newNode)

        } else {
            // The node is new and didn't exist in the old document.
            doc.content.push(JSON.parse(JSON.stringify(part)))
        }

    })

    // move remaining oldContent items that were not in template.
    while (oldContent.length) {
        const newNode = oldContent.shift()
        if (newNode.attrs.hasOwnProperty('deleted')) {
            newNode.attrs.deleted = true
            doc.content.push(newNode)
        }
    }

    return doc

}
<|endoftext|>


<commit_before>//How big the grid is in pixels
var gridWidthPx = 800, gridHeightPx = 448;
var gridPx = 32;

//Grid size in actual units
var gridWidth = gridWidthPx / gridPx;
var gridHeight = gridHeightPx / gridPx;

//Storage for the current agents and obstacles
var agents = new Array();
var obstacles = new Array();

//The physics world
var world = new B2World(B2Vec2.Zero, true);

//Defines an agent that moves
Agent = function (pos, group) {
	this.group = group;

	this.rotation = 0;

	this.maxForce = 20; //rate of acceleration
	this.maxSpeed = 4; //grid squares / second

	this.radius = 0.23;
	this.minSeparation = 0.8; // We'll move away from anyone nearer than this

	this.maxCohesion = 2; //We'll move closer to anyone within this bound

	this.maxForceSquared = this.maxForce * this.maxForce;
	this.maxSpeedSquared = this.maxSpeed * this.maxSpeed;

	//Create a physics body for the agent
	var fixDef = new B2FixtureDef();
	var bodyDef = new B2BodyDef();

	fixDef.density = 20.0;
	fixDef.friction = 0.0;
	fixDef.restitution = 0.0;
	fixDef.shape = new B2CircleShape(this.radius);

	bodyDef.type = B2Body.b2_dynamicBody;
	//bodyDef.linearDamping = 0.1;
	bodyDef.position.SetV(pos);

	this.body = world.CreateBody(bodyDef);
	this.fixture = this.body.CreateFixture(fixDef);
};
Agent.prototype.position = function () {
	return this.body.GetPosition();
};
Agent.prototype.velocity = function () {
	return this.body.GetLinearVelocity();
};

var destinations = [
	new B2Vec2(gridWidth - 2, gridHeight / 2), //middle right
	new B2Vec2(1, gridHeight / 2) //middle left
];

//Called to start the game
function startGame() {
	//for (var yPos = 1; yPos < gridHeight - 1; yPos++) {
	//	agents.push(new Agent(new B2Vec2(0, yPos), 0));
	//	agents.push(new Agent(new B2Vec2(1, yPos), 0));
	//	agents.push(new Agent(new B2Vec2(2, yPos), 0));
	//}

	//for (yPos = 1; yPos < gridHeight - 1; yPos++) {
	//	agents.push(new Agent(new B2Vec2(gridWidth - 1, yPos), 1));
	//	agents.push(new Agent(new B2Vec2(gridWidth - 2, yPos), 1));
	//	agents.push(new Agent(new B2Vec2(gridWidth - 3, yPos), 1));
	//}

	agents.push(new Agent(new B2Vec2(0, gridHeight / 2), 0));
	agents.push(new Agent(new B2Vec2(gridWidth - 2, gridHeight / 2), 1));

	//for (var i = 0; i < gridHeight; i++) {
	//	if (i == gridHeight / 2 || i == gridHeight / 2 - 1) {
	//		continue;
	//	}
	//	obstacles.push(new B2Vec2(8, i));
	//	obstacles.push(new B2Vec2(7, i));
	//}

	//for (var i = 0; i < 30; i++) {
	//	var x = 1 + Math.floor(Math.random() * (gridWidth - 3));
	//	var y = Math.floor(Math.random() * (gridHeight - 2));
	//	obstacles.push(new B2Vec2(x, y));
	//}

	for (var i = 0; i < obstacles.length; i++) {
		var pos = obstacles[i];

		//Create a physics body for the agent
		var fixDef = new B2FixtureDef();
		var bodyDef = new B2BodyDef();

		fixDef.density = 1.0;
		fixDef.friction = 0.5;
		fixDef.restitution = 0.2;
		fixDef.shape = new B2PolygonShape();
		fixDef.shape.SetAsBox(0.5, 0.5);

		bodyDef.type = B2Body.b2_staticBody;
		bodyDef.position.SetV(pos);

		world.CreateBody(bodyDef).CreateFixture(fixDef);
	}

	stage.addEventListener('stagemouseup', function (ev) {
		destinations[0].x = ev.stageX / gridPx - 0.5;
		destinations[0].y = ev.stageY / gridPx - 0.5;

		destinations[1].x = (gridWidthPx - ev.stageX) / gridPx - 0.5;
		destinations[1].y = (gridHeightPx - ev.stageY) / gridPx - 0.5;
	});
}

function round(val) {
	return val.toFixed(1);
}

//called periodically to update the game
//dt is the change of time since the last update (in seconds)
function gameTick(dt) {
	var i, agent;

	//Calculate steering and flocking forces for all agents
	for (i = agents.length - 1; i >= 0; i--) {
		agent = agents[i];

		//Work out our behaviours
		var ff = steeringBehaviourSeek(agent, destinations[agent.group]);
		var sep = steeringBehaviourSeparation(agent);
		var alg = steeringBehaviourAlignment(agent);
		var coh = steeringBehaviourCohesion(agent);
		var avd = steeringBehaviourAvoid(agent);

		agent.avd = avd.Copy();

		//For visually debugging forces agent.forces = [ff.Copy(), sep.Copy(), alg.Copy(), coh.Copy()];

		agent.forceToApply = ff.Add(sep.Multiply(1.2)).Add(alg.Multiply(0.3)).Add(coh.Multiply(0.05)).Add(avd.Multiply(4));

		var lengthSquared = agent.forceToApply.LengthSquared();
		if (lengthSquared > agent.maxForceSquared) {
			agent.forceToApply.Multiply(agent.maxForce / Math.sqrt(lengthSquared));
		}
	}

	//Move agents based on forces being applied (aka physics)
	for (i = agents.length - 1; i >= 0; i--) {
		agent = agents[i];

		//Apply the force
		//console.log(i + ': ' + agent.forceToApply.x + ', ' + agent.forceToApply.y);
		agent.body.ApplyImpulse(agent.forceToApply.Multiply(dt), agent.position());

		//Calculate our new movement angle TODO: Should probably be done after running step
		agent.rotation = agent.velocity().Angle();
	}

	world.Step(dt, 10, 10);
	world.ClearForces();
}

function steeringBehaviourSeek(agent, dest) {

	if (dest.x == agent.position().x && dest.y == agent.position().y) {
		return new B2Vec2();
	}

	//Desired change of location
	var desired = dest.Copy().Subtract(agent.position());
	//Desired velocity (move there at maximum speed)
	desired.Multiply(agent.maxSpeed / desired.Length());
	//The velocity change we want
	var velocityChange = desired.Subtract(agent.velocity());
	//Convert to a force
	return velocityChange.Multiply(agent.maxForce / agent.maxSpeed);
}

function steeringBehaviourSeparation(agent) {
	var totalForce = new B2Vec2();
	var neighboursCount = 0;

	for (var i = 0; i < agents.length; i++) {
		var a = agents[i];
		if (a != agent) {
			var distance = agent.position().DistanceTo(a.position());
			if (distance < agent.minSeparation && distance > 0) {
				//Vector to other agent
				var pushForce = agent.position().Copy().Subtract(a.position());
				var length = pushForce.Normalize(); //Normalize returns the original length
				var r = (agent.radius + a.radius);

				totalForce.Add(pushForce.Multiply(1 - ((length - r) / (agent.minSeparation - r))));//agent.minSeparation)));
				//totalForce.Add(pushForce.Multiply(1 - (length / agent.minSeparation)));
				//totalForce.Add(pushForce.Divide(agent.radius));

				neighboursCount++;
			}
		}
	}

	if (neighboursCount == 0) {
		return totalForce; //Zero
	}

	return totalForce.Multiply(agent.maxForce / neighboursCount);
}

function steeringBehaviourCohesion(agent) {
	//Start with just our position
	var centerOfMass = new B2Vec2()//agent.position().Copy();
	var neighboursCount = 0;

	for (var i = 0; i < agents.length; i++) {
		var a = agents[i];
		if (a != agent && a.group == agent.group) {
			var distance = agent.position().DistanceTo(a.position());
			if (distance < agent.maxCohesion) {
				//sum up the position of our neighbours
				centerOfMass.Add(a.position());
				neighboursCount++;
			}
		}
	}

	if (neighboursCount == 0) {
		return new B2Vec2();
	}

	//Get the average position of ourself and our neighbours
	centerOfMass.Divide(neighboursCount);

	//seek that position
	return steeringBehaviourSeek(agent, centerOfMass);
}

function steeringBehaviourAlignment(agent) {
	var averageHeading = new B2Vec2();
	var neighboursCount = 0;

	//for each of our neighbours (including ourself)
	for (var i = 0; i < agents.length; i++) {
		var a = agents[i];
		var distance = agent.position().DistanceTo(a.position());
		//That are within the max distance and are moving
		if (distance < agent.maxCohesion && a.velocity().Length() > 0 && a.group == agent.group) {
			//Sum up our headings
			var head = a.velocity().Copy();
			head.Normalize();
			averageHeading.Add(head);
			neighboursCount++;
		}
	}

	if (neighboursCount == 0) {
		return averageHeading; //Zero
	}

	//Divide to get the average heading
	averageHeading.Divide(neighboursCount);

	//Steer towards that heading
	return steerTowards(agent, averageHeading);
}

function steeringBehaviourAvoid(agent) {
	if (agent.velocity().LengthSquared() <= agent.radius) {
		return B2Vec2.Zero;
	}

	var minFraction = 2;
	var closestFixture = null;

	var callback = function (fixture, point, normal, fraction) {
		if (fraction < minFraction) {
			minFraction = fraction;
			closestFixture = fixture;
		}
		return 0;
	};
	world.RayCast(callback, agent.position(), agent.position().Copy().Add(agent.velocity()));
	//TODO: Offset ray casts
	var velCopy = agent.velocity().Copy();
	velCopy.Normalize();
	var temp = velCopy.x;
	velCopy.x = velCopy.y;
	velCopy.y = -temp;
	velCopy.Multiply(agent.radius);

	world.RayCast(callback, agent.position().Copy().Add(velCopy), agent.position().Copy().Add(agent.velocity()).Add(velCopy));
	world.RayCast(callback, agent.position().Copy().Subtract(velCopy), agent.position().Copy().Add(agent.velocity()).Subtract(velCopy));

	//TODO: May be faster to do a single AABB query or a shape query

	if (closestFixture == null) {
		return B2Vec2.Zero;
	}

	var resultVector = null;
	var runningInTo = null;

	var collisionBody = closestFixture.GetBody();
	var otherVelocity = collisionBody.GetLinearVelocity();

	var ourVelocity = agent.velocity().Copy();
	var ourLength = ourVelocity.LengthSquared();
	ourVelocity.Add(otherVelocity);

	var combinedVelocity = ourVelocity.LengthSquared();

	//We are going in the same direction
	if (combinedVelocity > ourLength) {
		return B2Vec2.Zero;
	}

	var otherType = closestFixture.GetShape().GetType();
	if (otherType == B2Shape.e_circleShape) {
		runningInTo = closestFixture.GetBody().GetPosition();

		//Steer to go around it
		var otherPosition = closestFixture.GetBody().GetPosition();
		var otherRadius = closestFixture.GetShape().GetRadius();

		//Vector in its direction
		var vectorInOtherDirection = otherPosition.Copy().Subtract(agent.position());

		//http://stackoverflow.com/questions/13221873/determining-if-one-2d-vector-is-to-the-right-or-left-of-another
		var dot = agent.velocity().x * -vectorInOtherDirection.y + agent.velocity().y * vectorInOtherDirection.x;//B2Math.Dot(agent.velocity(), vectorInOtherDirection);
		var isLeft = dot > 0;

		console.log(agent.group + ' ' + isLeft);
		//http://www.gamedev.net/topic/551175-rotate-vector-90-degrees-to-the-right/#entry4546571
		var rightAngle = isLeft ? new B2Vec2(-vectorInOtherDirection.y, vectorInOtherDirection.x) : new B2Vec2(vectorInOtherDirection.y, -vectorInOtherDirection.x);
		rightAngle.Normalize();

		rightAngle.Multiply(agent.radius + otherRadius);

		//Are we more left or right of them
		//Move it out based on our radius + theirs
		resultVector = rightAngle;//.Add(agent.position());
	} else if (otherType == B2Shape.e_polygonShape) {
		debugger; //TODO
	} else {
		debugger; //WTF!
	}
	//Might need to avoid them.

	return steerTowards(agent, resultVector);
}

function steerTowards(agent, desiredDirection) {
	//Multiply our direction by speed for our desired speed
	var desiredVelocity = desiredDirection.Multiply(agent.maxSpeed);

	//The velocity change we want
	var velocityChange = desiredVelocity.Subtract(agent.velocity());
	//Convert to a force
	return velocityChange.Multiply(agent.maxForce / agent.maxSpeed);
}<commit_msg>Use mind reading avoidance (like starcraft 2), makes this almost sort of work.<commit_after>//How big the grid is in pixels
var gridWidthPx = 800, gridHeightPx = 448;
var gridPx = 32;

//Grid size in actual units
var gridWidth = gridWidthPx / gridPx;
var gridHeight = gridHeightPx / gridPx;

//Storage for the current agents and obstacles
var agents = new Array();
var obstacles = new Array();

//The physics world
var world = new B2World(B2Vec2.Zero, true);

//Defines an agent that moves
Agent = function (pos, group) {
	this.group = group;

	this.rotation = 0;

	this.maxForce = 20; //rate of acceleration
	this.maxSpeed = 4; //grid squares / second

	this.radius = 0.23;
	this.minSeparation = 0.8 * 2; // We'll move away from anyone nearer than this

	this.maxCohesion = 2; //We'll move closer to anyone within this bound

	this.maxForceSquared = this.maxForce * this.maxForce;
	this.maxSpeedSquared = this.maxSpeed * this.maxSpeed;

	//Create a physics body for the agent
	var fixDef = new B2FixtureDef();
	var bodyDef = new B2BodyDef();

	fixDef.density = 20.0;
	fixDef.friction = 0.0;
	fixDef.restitution = 0.0;
	fixDef.shape = new B2CircleShape(this.radius);

	bodyDef.type = B2Body.b2_dynamicBody;
	//bodyDef.linearDamping = 0.1;
	bodyDef.position.SetV(pos);

	this.body = world.CreateBody(bodyDef);
	this.fixture = this.body.CreateFixture(fixDef);

	this.body.SetUserData(this);
	this.fixture.SetUserData(this);
};
Agent.prototype.position = function () {
	return this.body.GetPosition();
};
Agent.prototype.velocity = function () {
	return this.body.GetLinearVelocity();
};

var destinations = [
	new B2Vec2(gridWidth - 2, gridHeight / 2), //middle right
	new B2Vec2(1, gridHeight / 2) //middle left
];

//Called to start the game
function startGame() {
	if (true) {
		for (var yPos = 1; yPos < gridHeight - 1; yPos++) {
			agents.push(new Agent(new B2Vec2(0, yPos), 0));
			agents.push(new Agent(new B2Vec2(1, yPos), 0));
			agents.push(new Agent(new B2Vec2(2, yPos), 0));
		}

		for (yPos = 1; yPos < gridHeight - 1; yPos++) {
			agents.push(new Agent(new B2Vec2(gridWidth - 1, yPos), 1));
			agents.push(new Agent(new B2Vec2(gridWidth - 2, yPos), 1));
			agents.push(new Agent(new B2Vec2(gridWidth - 3, yPos), 1));
		}
	} else if (false) {
		agents.push(new Agent(new B2Vec2(0, 2), 0));
		agents.push(new Agent(new B2Vec2(gridWidth - 2, 2), 1));
	} else if (true) {
		agents.push(new Agent(new B2Vec2(0, gridHeight / 2), 0));
		agents.push(new Agent(new B2Vec2(gridWidth - 2, gridHeight / 2), 1));
	}
	//for (var i = 0; i < gridHeight; i++) {
	//	if (i == gridHeight / 2 || i == gridHeight / 2 - 1) {
	//		continue;
	//	}
	//	obstacles.push(new B2Vec2(8, i));
	//	obstacles.push(new B2Vec2(7, i));
	//}

	//for (var i = 0; i < 30; i++) {
	//	var x = 1 + Math.floor(Math.random() * (gridWidth - 3));
	//	var y = Math.floor(Math.random() * (gridHeight - 2));
	//	obstacles.push(new B2Vec2(x, y));
	//}

	for (var i = 0; i < obstacles.length; i++) {
		var pos = obstacles[i];

		//Create a physics body for the agent
		var fixDef = new B2FixtureDef();
		var bodyDef = new B2BodyDef();

		fixDef.density = 1.0;
		fixDef.friction = 0.5;
		fixDef.restitution = 0.2;
		fixDef.shape = new B2PolygonShape();
		fixDef.shape.SetAsBox(0.5, 0.5);

		bodyDef.type = B2Body.b2_staticBody;
		bodyDef.position.SetV(pos);

		world.CreateBody(bodyDef).CreateFixture(fixDef);
	}

	stage.addEventListener('stagemouseup', function (ev) {
		destinations[0].x = ev.stageX / gridPx - 0.5;
		destinations[0].y = ev.stageY / gridPx - 0.5;

		destinations[1].x = (gridWidthPx - ev.stageX) / gridPx - 0.5;
		destinations[1].y = (gridHeightPx - ev.stageY) / gridPx - 0.5;
	});
}

function round(val) {
	return val.toFixed(1);
}

//called periodically to update the game
//dt is the change of time since the last update (in seconds)
function gameTick(dt) {
	var i, agent;

	for (i = agents.length - 1; i >= 0; i--) {
		agents[i].avoidanceDirection = null;
	}

	//Calculate steering and flocking forces for all agents
	for (i = agents.length - 1; i >= 0; i--) {
		agent = agents[i];

		//Work out our behaviours
		var ff = steeringBehaviourSeek(agent, destinations[agent.group]);
		var sep = steeringBehaviourSeparation(agent);
		var alg = steeringBehaviourAlignment(agent);
		var coh = steeringBehaviourCohesion(agent);
		var avd = steeringBehaviourAvoid(agent);

		agent.avd = avd.Copy();

		//For visually debugging forces agent.forces = [ff.Copy(), sep.Copy(), alg.Copy(), coh.Copy()];

		agent.forceToApply = ff.Add(sep.Multiply(2.2)).Add(alg.Multiply(0.3)).Add(coh.Multiply(0.05)).Add(avd);

		var lengthSquared = agent.forceToApply.LengthSquared();
		if (lengthSquared > agent.maxForceSquared) {
			agent.forceToApply.Multiply(agent.maxForce / Math.sqrt(lengthSquared));
		}
	}

	//Move agents based on forces being applied (aka physics)
	for (i = agents.length - 1; i >= 0; i--) {
		agent = agents[i];

		//Apply the force
		//console.log(i + ': ' + agent.forceToApply.x + ', ' + agent.forceToApply.y);
		agent.body.ApplyImpulse(agent.forceToApply.Multiply(dt), agent.position());

		//Calculate our new movement angle TODO: Should probably be done after running step
		agent.rotation = agent.velocity().Angle();
	}

	world.Step(dt, 10, 10);
	world.ClearForces();
}

function steeringBehaviourSeek(agent, dest) {

	if (dest.x == agent.position().x && dest.y == agent.position().y) {
		return new B2Vec2();
	}

	//Desired change of location
	var desired = dest.Copy().Subtract(agent.position());
	//Desired velocity (move there at maximum speed)
	desired.Multiply(agent.maxSpeed / desired.Length());
	//The velocity change we want
	var velocityChange = desired.Subtract(agent.velocity());
	//Convert to a force
	return velocityChange.Multiply(agent.maxForce / agent.maxSpeed);
}

function steeringBehaviourSeparation(agent) {
	var totalForce = new B2Vec2();
	var neighboursCount = 0;

	for (var i = 0; i < agents.length; i++) {
		var a = agents[i];
		if (a != agent) {
			var distance = agent.position().DistanceTo(a.position());
			if (distance < agent.minSeparation && distance > 0) {
				//Vector to other agent
				var pushForce = agent.position().Copy().Subtract(a.position());
				var length = pushForce.Normalize(); //Normalize returns the original length
				var r = (agent.radius + a.radius);

				totalForce.Add(pushForce.Multiply(1 - ((length - r) / (agent.minSeparation - r))));//agent.minSeparation)));
				//totalForce.Add(pushForce.Multiply(1 - (length / agent.minSeparation)));
				//totalForce.Add(pushForce.Divide(agent.radius));

				neighboursCount++;
			}
		}
	}

	if (neighboursCount == 0) {
		return totalForce; //Zero
	}

	return totalForce.Multiply(agent.maxForce / neighboursCount);
}

function steeringBehaviourCohesion(agent) {
	//Start with just our position
	var centerOfMass = new B2Vec2()//agent.position().Copy();
	var neighboursCount = 0;

	for (var i = 0; i < agents.length; i++) {
		var a = agents[i];
		if (a != agent && a.group == agent.group) {
			var distance = agent.position().DistanceTo(a.position());
			if (distance < agent.maxCohesion) {
				//sum up the position of our neighbours
				centerOfMass.Add(a.position());
				neighboursCount++;
			}
		}
	}

	if (neighboursCount == 0) {
		return new B2Vec2();
	}

	//Get the average position of ourself and our neighbours
	centerOfMass.Divide(neighboursCount);

	//seek that position
	return steeringBehaviourSeek(agent, centerOfMass);
}

function steeringBehaviourAlignment(agent) {
	var averageHeading = new B2Vec2();
	var neighboursCount = 0;

	//for each of our neighbours (including ourself)
	for (var i = 0; i < agents.length; i++) {
		var a = agents[i];
		var distance = agent.position().DistanceTo(a.position());
		//That are within the max distance and are moving
		if (distance < agent.maxCohesion && a.velocity().Length() > 0 && a.group == agent.group) {
			//Sum up our headings
			var head = a.velocity().Copy();
			head.Normalize();
			averageHeading.Add(head);
			neighboursCount++;
		}
	}

	if (neighboursCount == 0) {
		return averageHeading; //Zero
	}

	//Divide to get the average heading
	averageHeading.Divide(neighboursCount);

	//Steer towards that heading
	return steerTowards(agent, averageHeading);
}

function steeringBehaviourAvoid(agent) {
	if (agent.velocity().LengthSquared() <= agent.radius) {
		return B2Vec2.Zero;
	}

	var minFraction = 2;
	var closestFixture = null;

	var callback = function (fixture, point, normal, fraction) {
		if (fixture == agent.fixture) {
			return fraction;
		}
		if (fraction < minFraction) {
			minFraction = fraction;
			closestFixture = fixture;
		}
		return 0;
	};
	world.RayCast(callback, agent.position(), agent.position().Copy().Add(agent.velocity()));
	//TODO: Offset ray casts
	var velCopy = agent.velocity().Copy();
	velCopy.Normalize();
	var temp = velCopy.x;
	velCopy.x = velCopy.y;
	velCopy.y = -temp;
	velCopy.Multiply(agent.radius);

	world.RayCast(callback, agent.position().Copy().Add(velCopy), agent.position().Copy().Add(agent.velocity()).Add(velCopy));
	world.RayCast(callback, agent.position().Copy().Subtract(velCopy), agent.position().Copy().Add(agent.velocity()).Subtract(velCopy));

	//TODO: May be faster to do a single AABB query or a shape query

	if (closestFixture == null) {
		return B2Vec2.Zero;
	}

	var resultVector = null;
	var runningInTo = null;

	var collisionBody = closestFixture.GetBody();
	var otherVelocity = collisionBody.GetLinearVelocity();

	var ourVelocity = agent.velocity().Copy();
	var ourLength = ourVelocity.LengthSquared();
	ourVelocity.Add(otherVelocity);

	var combinedVelocity = ourVelocity.LengthSquared();

	//We are going in the same direction
	if (combinedVelocity > ourLength) {
		return B2Vec2.Zero;
	}

	var otherType = closestFixture.GetShape().GetType();
	if (otherType == B2Shape.e_circleShape) {
		runningInTo = closestFixture.GetBody().GetPosition();

		//Steer to go around it
		var otherPosition = closestFixture.GetBody().GetPosition();
		var otherRadius = closestFixture.GetShape().GetRadius();

		//Vector in its direction
		var vectorInOtherDirection = otherPosition.Copy().Subtract(agent.position());

		//http://stackoverflow.com/questions/13221873/determining-if-one-2d-vector-is-to-the-right-or-left-of-another
		var dot = agent.velocity().x * -vectorInOtherDirection.y + agent.velocity().y * vectorInOtherDirection.x;//B2Math.Dot(agent.velocity(), vectorInOtherDirection);
		var isLeft = dot > 0;

		if (closestFixture.GetUserData().avoidanceDirection !== null) {
			console.log('override');
			//Turn the same angle as them, so we go the opposite way
			isLeft = closestFixture.GetUserData().avoidanceDirection;
		}
		agent.avoidanceDirection = isLeft;

		console.log(agent.group + ' ' + isLeft);
		//http://www.gamedev.net/topic/551175-rotate-vector-90-degrees-to-the-right/#entry4546571
		var rightAngle = isLeft ? new B2Vec2(-vectorInOtherDirection.y, vectorInOtherDirection.x) : new B2Vec2(vectorInOtherDirection.y, -vectorInOtherDirection.x);
		rightAngle.Normalize();

		rightAngle.Multiply(agent.radius + otherRadius);

		//Are we more left or right of them
		//Move it out based on our radius + theirs
		resultVector = rightAngle;//.Add(agent.position());
	} else if (otherType == B2Shape.e_polygonShape) {
		debugger; //TODO
	} else {
		debugger; //WTF!
	}
	//Might need to avoid them.

	return steerTowards(agent, resultVector);
}

function steerTowards(agent, desiredDirection) {
	//Multiply our direction by speed for our desired speed
	var desiredVelocity = desiredDirection.Multiply(agent.maxSpeed);

	//The velocity change we want
	var velocityChange = desiredVelocity.Subtract(agent.velocity());
	//Convert to a force
	return velocityChange.Multiply(agent.maxForce / agent.maxSpeed);
}<|endoftext|>


<commit_before>import React, { Component, PropTypes } from 'react';

import reduxComponentState from 'redux-component-state';

import * as actions from './redux/actions';
import counter from './redux/counterReducer';

class CounterPanel extends Component {

  static propTypes = {
    compActions: PropTypes.object.isRequired,
    counter: PropTypes.shape({
      value: PropTypes.number.isRequired,
      interactionCount: PropTypes.number.isRequired
    }).isRequired
  }

  static defaultProps = {}

  render() {
    const {compActions} = this.props;
    const {increment, decrement} = compActions;

    return (

          Current Count: {this.props.counter.value}

          Number of interactions: {this.props.counter.interactionCount}

          <button onClick={ () => increment() }>increment</button>
          <button onClick={ () => decrement() }>decrement</button>

    );
  }
}

const componentStateConfig = {
  getKey(props) {
    let id = props.id || 'defaultCounterPanel';
    return `counter-${id}`;
  },
  reducers: {counter},
  actions: {
    map: actions,
    aggregate: 'compActions'
  }
};
export default reduxComponentState(componentStateConfig)(CounterPanel);
<commit_msg>Updated CounterPanel in simple example to reflect changes in actions management<commit_after>import React, { Component, PropTypes } from 'react';

import reduxComponentState from 'redux-component-state';

import * as compActions from './redux/actions';
import counter from './redux/counterReducer';

class CounterPanel extends Component {

  static propTypes = {
    compActions: PropTypes.object.isRequired,
    counter: PropTypes.shape({
      value: PropTypes.number.isRequired,
      interactionCount: PropTypes.number.isRequired
    }).isRequired
  }

  static defaultProps = {}

  render() {
    const {increment, decrement} = this.props.compActions;

    return (

          Current Count: {this.props.counter.value}

          Number of interactions: {this.props.counter.interactionCount}

          <button onClick={ () => increment() }>increment</button>
          <button onClick={ () => decrement() }>decrement</button>

    );
  }
}

const componentStateConfig = {
  getKey(props) {
    let id = props.id || 'defaultCounterPanel';
    return `counter-${id}`;
  },
  reducers: {counter},
  actions: { compActions }
};
export default reduxComponentState(componentStateConfig)(CounterPanel);
<|endoftext|>


<commit_before>import {CodeBlockIcon} from '@sanity/icons'

// eslint-disable-next-line import/no-unassigned-import
import 'ace-builds/src-noconflict/mode-rust'
import 'ace-builds/src-noconflict/mode-c_cpp'

export default {
  name: 'codeTest',
  type: 'document',
  title: 'Code test',
  icon: CodeBlockIcon,
  fields: [
    {
      name: 'title',
      title: 'Title',
      type: 'string',
    },
    {
      name: 'code',
      title: 'Default theme',
      description: 'Selectable language',
      type: 'code',
      options: {
        withFilename: true,
      },
    },
    {
      name: 'customMode',
      title: 'Custom mode',
      description: 'Custom, unsupported modes',
      type: 'code',
      options: {
        languageAlternatives: [
          {title: 'Rust', value: 'rust', mode: 'rust'},
          {title: 'JavaScript', value: 'javascript'},
          {title: 'C++', value: 'cpp', mode: 'c_cpp'},
        ],
      },
    },
    {
      name: 'codeArray',
      title: 'Code in arrays',
      description: 'Code in arrays',
      type: 'array',
      of: [
        {
          type: 'block',
        },
        {
          type: 'code',
          options: {
            withFilename: true,
            languageAlternatives: [
              {title: 'Rust', value: 'rust', mode: 'rust'},
              {title: 'JavaScript', value: 'javascript'},
            ],
          },
        },
      ],
    },
    {
      name: 'cssOrJsCode',
      title: 'Github theme',
      type: 'code',
      options: {
        theme: 'github',
        languageAlternatives: [
          {title: 'JavaScript', value: 'javascript'},
          {title: 'CSS', value: 'css'},
        ],
      },
    },
    {
      name: 'jscode',
      title: 'Monokai theme',
      description: 'Only javascript',
      type: 'code',
      options: {
        theme: 'monokai',
        language: 'javascript',
      },
    },
  ],
}
<commit_msg>chore(test-studio): fix lint error<commit_after>import {CodeBlockIcon} from '@sanity/icons'

/* eslint-disable import/no-unassigned-import */
import 'ace-builds/src-noconflict/mode-rust'
import 'ace-builds/src-noconflict/mode-c_cpp'
/* eslint-enable import/no-unassigned-import */

export default {
  name: 'codeTest',
  type: 'document',
  title: 'Code test',
  icon: CodeBlockIcon,
  fields: [
    {
      name: 'title',
      title: 'Title',
      type: 'string',
    },
    {
      name: 'code',
      title: 'Default theme',
      description: 'Selectable language',
      type: 'code',
      options: {
        withFilename: true,
      },
    },
    {
      name: 'customMode',
      title: 'Custom mode',
      description: 'Custom, unsupported modes',
      type: 'code',
      options: {
        languageAlternatives: [
          {title: 'Rust', value: 'rust', mode: 'rust'},
          {title: 'JavaScript', value: 'javascript'},
          {title: 'C++', value: 'cpp', mode: 'c_cpp'},
        ],
      },
    },
    {
      name: 'codeArray',
      title: 'Code in arrays',
      description: 'Code in arrays',
      type: 'array',
      of: [
        {
          type: 'block',
        },
        {
          type: 'code',
          options: {
            withFilename: true,
            languageAlternatives: [
              {title: 'Rust', value: 'rust', mode: 'rust'},
              {title: 'JavaScript', value: 'javascript'},
            ],
          },
        },
      ],
    },
    {
      name: 'cssOrJsCode',
      title: 'Github theme',
      type: 'code',
      options: {
        theme: 'github',
        languageAlternatives: [
          {title: 'JavaScript', value: 'javascript'},
          {title: 'CSS', value: 'css'},
        ],
      },
    },
    {
      name: 'jscode',
      title: 'Monokai theme',
      description: 'Only javascript',
      type: 'code',
      options: {
        theme: 'monokai',
        language: 'javascript',
      },
    },
  ],
}
<|endoftext|>


<commit_before>/*  BLE Configuration Service
    deviceNameCharacteristic - write/write without response - max 10 byte - ascii string
    advertisingParamCharacteristic - write/write without response - 3 bytes - uint16_t adv interval in ms - uint8_t adv timeout in s
    appearanceCharacteristic - write/write without response - 2 bytes - uint16_t appearance
    connectionParamCharacteristic - write/write without response - 8 bytes - uint16_t min conn interval - uint16_t max conn interval - uint16_t slave latency - uint16_t supervision timeout
*/
var configurationService = 'ef680001-9b35-4933-9b10-52ffa9740042';
var deviceNameCharacteristic = 'EF680002-9B35-4933-9B10-52FFA9740042';
var advertisingParamCharacteristic = 'EF680003-9B35-4933-9B10-52FFA9740042';
var appearanceCharacteristic = 'EF680004-9B35-4933-9B10-52FFA9740042';
var connectionParamCharacteristic = 'EF680005-9B35-4933-9B10-52FFA9740042';

/*  Weather Station Service
    temperatureCharacteristic - notify/read - 2 bytes - uint8_t integer - uint8_t decimal
    pressureCharacteristic - notify/read - 3 bytes - uint16_t integer - uint8_t decimal
    humidityCharacteristic - notify/read - 1 byte - uint8_t
    configurationCharacteristic - write/write without response - 7 bytes - uint16_t temp interval in ms - uint16_t pressure interval in ms - uint16_t humidity interval in ms - uint8_t pressure mode (0=barometer, 1=altimeter)
*/
var weatherStationService = '20080001-e36f-4648-91c6-9e86ead38764';
var temperatureCharacteristic = '20080002-e36f-4648-91c6-9e86ead38764';
var pressureCharacteristic = '20080003-e36f-4648-91c6-9e86ead38764';
var humidityCharacteristic = '20080004-e36f-4648-91c6-9e86ead38764';
var configurationCharacteristic = '20080005-e36f-4648-91c6-9e86ead38764';

/*  User Interface Service
    ledCharacteristic - write/read - 4 bytes - uint32_t - LED ID - Red - Green - Blue (LSB)
    buttonCharacteristic - write/read - 2 bytes - uint16_t - Button 2 state - Button 1 state (LSB)
*/
var userInterfaceService = 'C7AE0001-3266-4A5C-859F-0F4799146BB5';
var ledCharacteristic = 'C7AE0002-3266-4A5C-859F-0F4799146BB5';
var buttonCharacteristic = 'C7AE0003-3266-4A5C-859F-0F4799146BB5';

var isConnecting = false;
var isConnected = false;
var sInterval;
var sTimeout;
var sServer;
var humidityCharacteristic;

window.onload = function(){
  document.querySelector('#connect').addEventListener('click', connect);
  document.querySelector('#disconnect').addEventListener('click', disconnectDevice);
  document.querySelector('#disconnect').style.display = "hide";
  document.querySelector('#humidity').addEventListener('click', getHumidity);
};

function log(text) {
    document.querySelector('#log').textContent += text + '\n';
}

function setConnecting(connecting) {
    isConnecting = connecting;
    if (connecting) {
        document.querySelector('#connect').style.display = "none";
    }
    else {
        document.querySelector('#connect').style.display = "block";
    }
}

function setConnected(connected) {
    isConnected = connected;
    if (connected) {
        document.querySelector('#connect').style.display = "none";
        document.querySelector('#disconnect').style.display = "block";
    }
    else {
        document.querySelector('#connect').style.display = "block";
        document.querySelector('#disconnect').style.display = "none";
        sServer.disconnect();
    }
}

function connect() {
    'use strict';

    if (!navigator.bluetooth) {
        log('Web Bluetooth API is not available.\n' +
            'Please make sure the Web Bluetooth flag is enabled.');
        return;
    }

    if (isConnecting) {
        log('Connecting. Please wait.');
        return;
    }

    log('Requesting Bluetooth Device...');

    setConnecting(true);
    navigator.bluetooth.requestDevice({
        filters: [{
            services:[weatherStationService]
        }]
    })
    .then(device => {
        log('Device name: ' + device.name);
        log('Gatt server UUIDs: ' + device.uuids);
        connect2(device, 0);
    })
    .catch(error => {
        setConnecting(false);
        log(error);
    });
}

function connect2(device, retryCount) {
    'use strict';

    device.connectGATT()
    .then(server => {
        log('Got GATT server');
        sServer = server;
        return server.getPrimaryService(weatherStationService);
    })
    .then(service => {
        log('Got service');
        return service.getCharacteristic(temperatureCharacteristic);
     })
    .then(characteristic => {
        log('Got characteristic');
        humidityCharacteristic = characteristic;
        setConnecting(false);
        setConnected(true);
    })
    .catch(error => {
        log(error);
        if (retryCount < 5) {
            log('Retrying...');
            connect2(device, retryCount + 1);
        } else {
            setConnecting(false);
        }
    });
}

function move(event, direction) {
    log("move(" + event + ", " + direction + ")");
    try {
        switch (direction) {
        case "forward":
            sCharacteristic.writeValue(new Uint8Array([1, 0, 0, 0, 0, 0]));
            break;
        case "backward":
            sCharacteristic.writeValue(new Uint8Array([0, 1, 0, 0, 0, 0]));
            break;
        case "left":
            sCharacteristic.writeValue(new Uint8Array([0, 0, 1, 0, 0, 0]));
            break;
        case "right":
            sCharacteristic.writeValue(new Uint8Array([0, 0, 0, 1, 0, 0]));
            break;
        case "up":
            sCharacteristic.writeValue(new Uint8Array([0, 0, 0, 0, 1, 0]));
            break;
        case "down":
            sCharacteristic.writeValue(new Uint8Array([0, 0, 0, 0, 0, 1]));
            break;
        }
        event.preventDefault();
    } catch (error) {
        setConnected(false);
        log(error);
    }
}

function getHumidity(){
  log('Getting humidity...');
  try {
    humidity = humidityCharacteristic.readValue();
    humidity = humidity.buffer ? value : new DataView(value);
    log('Humidity is ' + humidity.getUint8(0) + '%');
  } catch (error) {
        log(error);
    }
}

function stop(event) {
    log("stop(" + event + ")");
    try {
        sCharacteristic.writeValue(new Uint8Array([0, 0, 0, 0, 0, 0]));
        event.preventDefault();
    } catch (error) {
        log(error);
    }
}

function disconnectDevice(){
  document.querySelector('#connect').style.display = "block";
  document.querySelector('#disconnect').style.display = "none";
  sServer.disconnect();
  log("Disconnected.");
}<commit_msg>minor fix<commit_after>/*  BLE Configuration Service
    deviceNameCharacteristic - write/write without response - max 10 byte - ascii string
    advertisingParamCharacteristic - write/write without response - 3 bytes - uint16_t adv interval in ms - uint8_t adv timeout in s
    appearanceCharacteristic - write/write without response - 2 bytes - uint16_t appearance
    connectionParamCharacteristic - write/write without response - 8 bytes - uint16_t min conn interval - uint16_t max conn interval - uint16_t slave latency - uint16_t supervision timeout
*/
var configurationService = 'ef680001-9b35-4933-9b10-52ffa9740042';
var deviceNameCharacteristic = 'EF680002-9B35-4933-9B10-52FFA9740042';
var advertisingParamCharacteristic = 'EF680003-9B35-4933-9B10-52FFA9740042';
var appearanceCharacteristic = 'EF680004-9B35-4933-9B10-52FFA9740042';
var connectionParamCharacteristic = 'EF680005-9B35-4933-9B10-52FFA9740042';

/*  Weather Station Service
    temperatureCharacteristic - notify/read - 2 bytes - uint8_t integer - uint8_t decimal
    pressureCharacteristic - notify/read - 3 bytes - uint16_t integer - uint8_t decimal
    humidityCharacteristic - notify/read - 1 byte - uint8_t
    configurationCharacteristic - write/write without response - 7 bytes - uint16_t temp interval in ms - uint16_t pressure interval in ms - uint16_t humidity interval in ms - uint8_t pressure mode (0=barometer, 1=altimeter)
*/
var weatherStationService = '20080001-e36f-4648-91c6-9e86ead38764';
var temperatureCharacteristic = '20080002-e36f-4648-91c6-9e86ead38764';
var pressureCharacteristic = '20080003-e36f-4648-91c6-9e86ead38764';
var humidityCharacteristic = '20080004-e36f-4648-91c6-9e86ead38764';
var configurationCharacteristic = '20080005-e36f-4648-91c6-9e86ead38764';

/*  User Interface Service
    ledCharacteristic - write/read - 4 bytes - uint32_t - LED ID - Red - Green - Blue (LSB)
    buttonCharacteristic - write/read - 2 bytes - uint16_t - Button 2 state - Button 1 state (LSB)
*/
var userInterfaceService = 'C7AE0001-3266-4A5C-859F-0F4799146BB5';
var ledCharacteristic = 'C7AE0002-3266-4A5C-859F-0F4799146BB5';
var buttonCharacteristic = 'C7AE0003-3266-4A5C-859F-0F4799146BB5';

var isConnecting = false;
var isConnected = false;
var sInterval;
var sTimeout;
var sServer;
var humidityCharacteristic;

window.onload = function(){
  document.querySelector('#connect').addEventListener('click', connect);
  document.querySelector('#disconnect').addEventListener('click', disconnectDevice);
  document.querySelector('#disconnect').style.display = "hide";
  document.querySelector('#humidity').addEventListener('click', getHumidity);
};

function log(text) {
    document.querySelector('#log').textContent += text + '\n';
}

function setConnecting(connecting) {
    isConnecting = connecting;
    if (connecting) {
        document.querySelector('#connect').style.display = "none";
    }
    else {
        document.querySelector('#connect').style.display = "block";
    }
}

function setConnected(connected) {
    isConnected = connected;
    if (connected) {
        document.querySelector('#connect').style.display = "none";
        document.querySelector('#disconnect').style.display = "block";
    }
    else {
        document.querySelector('#connect').style.display = "block";
        document.querySelector('#disconnect').style.display = "none";
        sServer.disconnect();
    }
}

function connect() {
    'use strict';

    if (!navigator.bluetooth) {
        log('Web Bluetooth API is not available.\n' +
            'Please make sure the Web Bluetooth flag is enabled.');
        return;
    }

    if (isConnecting) {
        log('Connecting. Please wait.');
        return;
    }

    log('Requesting Bluetooth Device...');

    setConnecting(true);
    navigator.bluetooth.requestDevice({
        filters: [{
            services:[weatherStationService]
        }]
    })
    .then(device => {
        log('Device name: ' + device.name);
        log('Gatt server UUIDs: ' + device.uuids);
        connect2(device, 0);
    })
    .catch(error => {
        setConnecting(false);
        log(error);
    });
}

function connect2(device, retryCount) {
    'use strict';

    device.connectGATT()
    .then(server => {
        log('Got GATT server');
        sServer = server;
        return server.getPrimaryService(weatherStationService);
    })
    .then(service => {
        log('Got service');
        return service.getCharacteristic(temperatureCharacteristic);
     })
    .then(characteristic => {
        log('Got characteristic');
        humidityCharacteristic = characteristic;
        setConnecting(false);
        setConnected(true);
    })
    .catch(error => {
        log(error);
        if (retryCount < 5) {
            log('Retrying...');
            connect2(device, retryCount + 1);
        } else {
            setConnecting(false);
        }
    });
}

function move(event, direction) {
    log("move(" + event + ", " + direction + ")");
    try {
        switch (direction) {
        case "forward":
            sCharacteristic.writeValue(new Uint8Array([1, 0, 0, 0, 0, 0]));
            break;
        case "backward":
            sCharacteristic.writeValue(new Uint8Array([0, 1, 0, 0, 0, 0]));
            break;
        case "left":
            sCharacteristic.writeValue(new Uint8Array([0, 0, 1, 0, 0, 0]));
            break;
        case "right":
            sCharacteristic.writeValue(new Uint8Array([0, 0, 0, 1, 0, 0]));
            break;
        case "up":
            sCharacteristic.writeValue(new Uint8Array([0, 0, 0, 0, 1, 0]));
            break;
        case "down":
            sCharacteristic.writeValue(new Uint8Array([0, 0, 0, 0, 0, 1]));
            break;
        }
        event.preventDefault();
    } catch (error) {
        setConnected(false);
        log(error);
    }
}

function getHumidity() {
  'use strict';
  log('Getting humidity...');
  humidityCharacteristic.readValue()
  .then(buffer => {
    let data = new DataView(buffer);
    let foo = data.getUint8(0);
    log('Humidity is ' + foo + '%');
  })
  .catch(error => {
    log(error);
  });
}

function stop(event) {
    log("stop(" + event + ")");
    try {
        sCharacteristic.writeValue(new Uint8Array([0, 0, 0, 0, 0, 0]));
        event.preventDefault();
    } catch (error) {
        log(error);
    }
}

function disconnectDevice(){
  document.querySelector('#connect').style.display = "block";
  document.querySelector('#disconnect').style.display = "none";
  sServer.disconnect();
  log("Disconnected.");
}<|endoftext|>


<commit_before>var Checker = require('../../../lib/checker');
var assert = require('assert');

describe('rules/disallow-space-before-object-values', function() {
    var checker;

    beforeEach(function() {
        checker = new Checker();
        checker.registerDefaultRules();
        checker.configure({ disallowSpaceBeforeObjectValues: true });
    });

    it('should report with space after keys colons', function() {
        assert.equal(checker.checkString('var x = { a:1, b: 2 };').getErrorCount(), 1, 'one error is found');
        assert.equal(checker.checkString('var x = { abc : 1, b: 2 };').getErrorCount(), 2, 'two errors are found');
    });

    it('should not report with no space after keys colons', function() {
        assert(checker.checkString('var x = { a:1, bcd :2 };').isEmpty());
    });
});
<commit_msg>Misc: remove extraneous file<commit_after><|endoftext|>


<commit_before>/*
 * FirebugUJS Client-side Library
 *
 *
 * Overview:
 *
 *   To keep track of DOM elements that have events bound to them,
 *   we override the event binding method(s) used by different
 *   javascript libraries and keep track of the DOM elements and
 *   the events that get bound to them.
 *
 *   To do this (and to make this information safely accessible to
 *   the firefox extension), we add a custom attribute to all DOM
 *   elements with events, which holds the event information.
 *
 *
 * To Do:
 *
 *   Instead of overriding the event binding methods of different
 *   javascript libraries, we should try to override the native
 *   javascript functions, if possible, so this is more library agnostic.
 *
 */
if ( FirebugUJS == null ) {

  console.log('FirebugUJS: loading client-side javascript');

  // global

  var FirebugUJS = {

    event_bound_to_element: function( element, event_type, event_function ) {
      if ( element && event_type && event_function ) {
        if (event_function.the_function != null)
          event_function = event_function.the_function;
        //console.log('event_bound_to_element: ' + element + ':' + event_type + ' => ' + event_function );
        console.log('FirebugUJS: event_bound_to_element: ' + element + ':' + event_type );
        if ( element.getAttribute('ujs') == null )
          element.setAttribute('ujs', '');
        element.setAttribute('ujs', element.getAttribute('ujs') + event_type + ':' + event_function + '|' );
      }
    }

  };

  // jQuery

  if ( window.jQuery ) {
    console.log('FirebugUJS: loading jQuery module');
    jQuery.fn.original_bind = jQuery.fn.bind;

    jQuery.fn.extend({
        bind: function( type, data, fn ) {
          FirebugUJS.event_bound_to_element( this[0], type, data );
          this.original_bind( type, data, fn );
        }
      });
  }

  // LowPro

  if ( window.Event && window.Event.addBehavior ) {
    console.log('FirebugUJS: loading LowPro module');

    FirebugUJS.LowPro = {
      original: {
        _wrapObserver: window.Event.addBehavior._wrapObserver
      }
    };

    window.Event.addBehavior._wrapObserver = function(observer) {
      var wrapped_observer = FirebugUJS.LowPro.original._wrapObserver(observer);
      wrapped_observer.the_function = (observer.the_function == null) ? observer : observer.the_function;
      return wrapped_observer;
    }

    Event.observe_old = Event.observe;
    Element.addMethods({
        observe: function(element, eventName, handler) {
          FirebugUJS.event_bound_to_element( element, eventName, handler );
          Event.observe_old( element, eventName, handler );
        }
      });
  }

  // Prototype

  if ( window.Prototype ) {
    console.log('FiregubUJS: loading Prototype module');

    Event.observe_old = Event.observe;
    Element.addMethods({
        observe: function(element, eventName, handler) {
          FirebugUJS.event_bound_to_element( element, eventName, handler );
          Event.observe_old( element, eventName, handler );
        }
      });
  }

}
<commit_msg>Allow for jQuery's chained events<commit_after>/*
 * FirebugUJS Client-side Library
 *
 *
 * Overview:
 *
 *   To keep track of DOM elements that have events bound to them,
 *   we override the event binding method(s) used by different
 *   javascript libraries and keep track of the DOM elements and
 *   the events that get bound to them.
 *
 *   To do this (and to make this information safely accessible to
 *   the firefox extension), we add a custom attribute to all DOM
 *   elements with events, which holds the event information.
 *
 *
 * To Do:
 *
 *   Instead of overriding the event binding methods of different
 *   javascript libraries, we should try to override the native
 *   javascript functions, if possible, so this is more library agnostic.
 *
 */
if ( FirebugUJS == null ) {

  console.log('FirebugUJS: loading client-side javascript');

  // global

  var FirebugUJS = {

    event_bound_to_element: function( element, event_type, event_function ) {
      if ( element && event_type && event_function ) {
        if (event_function.the_function != null)
          event_function = event_function.the_function;
        //console.log('event_bound_to_element: ' + element + ':' + event_type + ' => ' + event_function );
        console.log('FirebugUJS: event_bound_to_element: ' + element + ':' + event_type );
        if ( element.getAttribute('ujs') == null )
          element.setAttribute('ujs', '');
        element.setAttribute('ujs', element.getAttribute('ujs') + event_type + ':' + event_function + '|' );
      }
    }

  };

  // jQuery

  if ( window.jQuery ) {
    console.log('FirebugUJS: loading jQuery module');
    jQuery.fn.original_bind = jQuery.fn.bind;

    jQuery.fn.extend({
        bind: function( type, data, fn ) {
          FirebugUJS.event_bound_to_element( this[0], type, data );
          return this.original_bind( type, data, fn );
        }
      });
  }

  // LowPro

  if ( window.Event && window.Event.addBehavior ) {
    console.log('FirebugUJS: loading LowPro module');

    FirebugUJS.LowPro = {
      original: {
        _wrapObserver: window.Event.addBehavior._wrapObserver
      }
    };

    window.Event.addBehavior._wrapObserver = function(observer) {
      var wrapped_observer = FirebugUJS.LowPro.original._wrapObserver(observer);
      wrapped_observer.the_function = (observer.the_function == null) ? observer : observer.the_function;
      return wrapped_observer;
    }

    Event.observe_old = Event.observe;
    Element.addMethods({
        observe: function(element, eventName, handler) {
          FirebugUJS.event_bound_to_element( element, eventName, handler );
          Event.observe_old( element, eventName, handler );
        }
      });
  }

  // Prototype

  if ( window.Prototype ) {
    console.log('FiregubUJS: loading Prototype module');

    Event.observe_old = Event.observe;
    Element.addMethods({
        observe: function(element, eventName, handler) {
          FirebugUJS.event_bound_to_element( element, eventName, handler );
          Event.observe_old( element, eventName, handler );
        }
      });
  }

}
<|endoftext|>


<commit_before>/*
Copyright 2015, 2016 OpenMarket Ltd
Copyright 2018 New Vector Ltd
Copyright 2019 Michael Telatynski <7t3chguy@gmail.com>
Copyright 2019 The Matrix.org Foundation C.I.C.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import React from 'react';
import PropTypes from 'prop-types';
import {EventStatus} from 'matrix-js-sdk/src/models/event';

import {MatrixClientPeg} from '../../../MatrixClientPeg';
import dis from '../../../dispatcher/dispatcher';
import * as sdk from '../../../index';
import { _t } from '../../../languageHandler';
import Modal from '../../../Modal';
import Resend from '../../../Resend';
import SettingsStore from '../../../settings/SettingsStore';
import { isUrlPermitted } from '../../../HtmlUtils';
import { isContentActionable } from '../../../utils/EventUtils';
import {MenuItem} from "../../structures/ContextMenu";
import {EventType} from "matrix-js-sdk/src/@types/event";
import {replaceableComponent} from "../../../utils/replaceableComponent";

function canCancel(eventStatus) {
    return eventStatus === EventStatus.QUEUED || eventStatus === EventStatus.NOT_SENT;
}

@replaceableComponent("views.context_menus.MessageContextMenu")
export default class MessageContextMenu extends React.Component {
    static propTypes = {
        /* the MatrixEvent associated with the context menu */
        mxEvent: PropTypes.object.isRequired,

        /* an optional EventTileOps implementation that can be used to unhide preview widgets */
        eventTileOps: PropTypes.object,

        /* an optional function to be called when the user clicks collapse thread, if not provided hide button */
        collapseReplyThread: PropTypes.func,

        /* callback called when the menu is dismissed */
        onFinished: PropTypes.func,

        /* if the menu is inside a dialog, we sometimes need to close that dialog after click (forwarding) */
        onCloseDialog: PropTypes.func,
    };

    state = {
        canRedact: false,
        canPin: false,
    };

    componentDidMount() {
        MatrixClientPeg.get().on('RoomMember.powerLevel', this._checkPermissions);
        this._checkPermissions();
    }

    componentWillUnmount() {
        const cli = MatrixClientPeg.get();
        if (cli) {
            cli.removeListener('RoomMember.powerLevel', this._checkPermissions);
        }
    }

    _checkPermissions = () => {
        const cli = MatrixClientPeg.get();
        const room = cli.getRoom(this.props.mxEvent.getRoomId());

        // We explicitly decline to show the redact option on ACL events as it has a potential
        // to obliterate the room - https://github.com/matrix-org/synapse/issues/4042
        const canRedact = room.currentState.maySendRedactionForEvent(this.props.mxEvent, cli.credentials.userId)
            && this.props.mxEvent.getType() !== EventType.RoomServerAcl;
        let canPin = room.currentState.mayClientSendStateEvent('m.room.pinned_events', cli);

        // HACK: Intentionally say we can't pin if the user doesn't want to use the functionality
        if (!SettingsStore.getValue("feature_pinning")) canPin = false;

        this.setState({canRedact, canPin});
    };

    _isPinned() {
        const room = MatrixClientPeg.get().getRoom(this.props.mxEvent.getRoomId());
        const pinnedEvent = room.currentState.getStateEvents('m.room.pinned_events', '');
        if (!pinnedEvent) return false;
        const content = pinnedEvent.getContent();
        return content.pinned && Array.isArray(content.pinned) && content.pinned.includes(this.props.mxEvent.getId());
    }

    onResendClick = () => {
        Resend.resend(this.props.mxEvent);
        this.closeMenu();
    };

    onResendEditClick = () => {
        Resend.resend(this.props.mxEvent.replacingEvent());
        this.closeMenu();
    };

    onResendRedactionClick = () => {
        Resend.resend(this.props.mxEvent.localRedactionEvent());
        this.closeMenu();
    };

    onResendReactionsClick = () => {
        for (const reaction of this._getUnsentReactions()) {
            Resend.resend(reaction);
        }
        this.closeMenu();
    };

    onReportEventClick = () => {
        const ReportEventDialog = sdk.getComponent("dialogs.ReportEventDialog");
        Modal.createTrackedDialog('Report Event', '', ReportEventDialog, {
            mxEvent: this.props.mxEvent,
        }, 'mx_Dialog_reportEvent');
        this.closeMenu();
    };

    onViewSourceClick = () => {
        const ViewSource = sdk.getComponent('structures.ViewSource');
        Modal.createTrackedDialog('View Event Source', '', ViewSource, {
            mxEvent: this.props.mxEvent,
        }, 'mx_Dialog_viewsource');
        this.closeMenu();
    };

    onRedactClick = () => {
        const ConfirmRedactDialog = sdk.getComponent("dialogs.ConfirmRedactDialog");
        Modal.createTrackedDialog('Confirm Redact Dialog', '', ConfirmRedactDialog, {
            onFinished: async (proceed, reason) => {
                if (!proceed) return;

                const cli = MatrixClientPeg.get();
                try {
                    await cli.redactEvent(
                        this.props.mxEvent.getRoomId(),
                        this.props.mxEvent.getId(),
                        undefined,
                        reason ? { reason } : {},
                    );
                } catch (e) {
                    const code = e.errcode || e.statusCode;
                    // only show the dialog if failing for something other than a network error
                    // (e.g. no errcode or statusCode) as in that case the redactions end up in the
                    // detached queue and we show the room status bar to allow retry
                    if (typeof code !== "undefined") {
                        const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
                        // display error message stating you couldn't delete this.
                        Modal.createTrackedDialog('You cannot delete this message', '', ErrorDialog, {
                            title: _t('Error'),
                            description: _t('You cannot delete this message. (%(code)s)', {code}),
                        });
                    }
                }
            },
        }, 'mx_Dialog_confirmredact');
        this.closeMenu();
    };

    onCancelSendClick = () => {
        const mxEvent = this.props.mxEvent;
        const editEvent = mxEvent.replacingEvent();
        const redactEvent = mxEvent.localRedactionEvent();
        const pendingReactions = this._getPendingReactions();

        if (editEvent && canCancel(editEvent.status)) {
            Resend.removeFromQueue(editEvent);
        }
        if (redactEvent && canCancel(redactEvent.status)) {
            Resend.removeFromQueue(redactEvent);
        }
        if (pendingReactions.length) {
            for (const reaction of pendingReactions) {
                Resend.removeFromQueue(reaction);
            }
        }
        if (canCancel(mxEvent.status)) {
            Resend.removeFromQueue(this.props.mxEvent);
        }
        this.closeMenu();
    };

    onForwardClick = () => {
        dis.dispatch({
            action: 'forward_event',
            event: this.props.mxEvent,
        });
        if (this.props.onCloseDialog) this.props.onCloseDialog();
        this.closeMenu();
    };

    onPinClick = () => {
        MatrixClientPeg.get().getStateEvent(this.props.mxEvent.getRoomId(), 'm.room.pinned_events', '')
            .catch((e) => {
                // Intercept the Event Not Found error and fall through the promise chain with no event.
                if (e.errcode === "M_NOT_FOUND") return null;
                throw e;
            })
            .then((event) => {
                const eventIds = (event ? event.pinned : []) || [];
                if (!eventIds.includes(this.props.mxEvent.getId())) {
                    // Not pinned - add
                    eventIds.push(this.props.mxEvent.getId());
                } else {
                    // Pinned - remove
                    eventIds.splice(eventIds.indexOf(this.props.mxEvent.getId()), 1);
                }

                const cli = MatrixClientPeg.get();
                cli.sendStateEvent(this.props.mxEvent.getRoomId(), 'm.room.pinned_events', {pinned: eventIds}, '');
            });
        this.closeMenu();
    };

    closeMenu = () => {
        if (this.props.onFinished) this.props.onFinished();
    };

    onUnhidePreviewClick = () => {
        if (this.props.eventTileOps) {
            this.props.eventTileOps.unhideWidget();
        }
        this.closeMenu();
    };

    onQuoteClick = () => {
        dis.dispatch({
            action: 'quote',
            event: this.props.mxEvent,
        });
        this.closeMenu();
    };

    onPermalinkClick = (e: Event) => {
        e.preventDefault();
        const ShareDialog = sdk.getComponent("dialogs.ShareDialog");
        Modal.createTrackedDialog('share room message dialog', '', ShareDialog, {
            target: this.props.mxEvent,
            permalinkCreator: this.props.permalinkCreator,
        });
        this.closeMenu();
    };

    onCollapseReplyThreadClick = () => {
        this.props.collapseReplyThread();
        this.closeMenu();
    };

    _getReactions(filter) {
        const cli = MatrixClientPeg.get();
        const room = cli.getRoom(this.props.mxEvent.getRoomId());
        const eventId = this.props.mxEvent.getId();
        return room.getPendingEvents().filter(e => {
            const relation = e.getRelation();
            return relation &&
                relation.rel_type === "m.annotation" &&
                relation.event_id === eventId &&
                filter(e);
        });
    }

    _getPendingReactions() {
        return this._getReactions(e => canCancel(e.status));
    }

    _getUnsentReactions() {
        return this._getReactions(e => e.status === EventStatus.NOT_SENT);
    }

    render() {
        const cli = MatrixClientPeg.get();
        const me = cli.getUserId();
        const mxEvent = this.props.mxEvent;
        const eventStatus = mxEvent.status;
        const editStatus = mxEvent.replacingEvent() && mxEvent.replacingEvent().status;
        const redactStatus = mxEvent.localRedactionEvent() && mxEvent.localRedactionEvent().status;
        const unsentReactionsCount = this._getUnsentReactions().length;
        const pendingReactionsCount = this._getPendingReactions().length;
        const allowCancel = canCancel(mxEvent.status) ||
            canCancel(editStatus) ||
            canCancel(redactStatus) ||
            pendingReactionsCount !== 0;
        let resendButton;
        let resendEditButton;
        let resendReactionsButton;
        let resendRedactionButton;
        let redactButton;
        let cancelButton;
        let forwardButton;
        let pinButton;
        let unhidePreviewButton;
        let externalURLButton;
        let quoteButton;
        let collapseReplyThread;

        // status is SENT before remote-echo, null after
        const isSent = !eventStatus || eventStatus === EventStatus.SENT;
        if (!mxEvent.isRedacted()) {
            if (eventStatus === EventStatus.NOT_SENT) {
                resendButton = (
                    <MenuItem className="mx_MessageContextMenu_field" onClick={this.onResendClick}>
                        { _t('Resend') }
                    </MenuItem>
                );
            }

            if (editStatus === EventStatus.NOT_SENT) {
                resendEditButton = (
                    <MenuItem className="mx_MessageContextMenu_field" onClick={this.onResendEditClick}>
                        { _t('Resend edit') }
                    </MenuItem>
                );
            }

            if (unsentReactionsCount !== 0) {
                resendReactionsButton = (
                    <MenuItem className="mx_MessageContextMenu_field" onClick={this.onResendReactionsClick}>
                        { _t('Resend %(unsentCount)s reaction(s)', {unsentCount: unsentReactionsCount}) }
                    </MenuItem>
                );
            }
        }

        if (redactStatus === EventStatus.NOT_SENT) {
            resendRedactionButton = (
                <MenuItem className="mx_MessageContextMenu_field" onClick={this.onResendRedactionClick}>
                    { _t('Resend removal') }
                </MenuItem>
            );
        }

        if (isSent && this.state.canRedact) {
            redactButton = (
                <MenuItem className="mx_MessageContextMenu_field" onClick={this.onRedactClick}>
                    { _t('Remove') }
                </MenuItem>
            );
        }

        if (allowCancel) {
            cancelButton = (
                <MenuItem className="mx_MessageContextMenu_field" onClick={this.onCancelSendClick}>
                    { _t('Cancel Sending') }
                </MenuItem>
            );
        }

        if (isContentActionable(mxEvent)) {
            forwardButton = (
                <MenuItem className="mx_MessageContextMenu_field" onClick={this.onForwardClick}>
                    { _t('Forward Message') }
                </MenuItem>
            );

            if (this.state.canPin) {
                pinButton = (
                    <MenuItem className="mx_MessageContextMenu_field" onClick={this.onPinClick}>
                        { this._isPinned() ? _t('Unpin Message') : _t('Pin Message') }
                    </MenuItem>
                );
            }
        }

        const viewSourceButton = (
            <MenuItem className="mx_MessageContextMenu_field" onClick={this.onViewSourceClick}>
                { _t('View Source') }
            </MenuItem>
        );

        if (this.props.eventTileOps) {
            if (this.props.eventTileOps.isWidgetHidden()) {
                unhidePreviewButton = (
                    <MenuItem className="mx_MessageContextMenu_field" onClick={this.onUnhidePreviewClick}>
                        { _t('Unhide Preview') }
                    </MenuItem>
                );
            }
        }

        let permalink;
        if (this.props.permalinkCreator) {
            permalink = this.props.permalinkCreator.forEvent(this.props.mxEvent.getId());
        }
        // XXX: if we use room ID, we should also include a server where the event can be found (other than in the domain of the event ID)
        const permalinkButton = (
            <MenuItem
                element="a"
                className="mx_MessageContextMenu_field"
                onClick={this.onPermalinkClick}
                href={permalink}
                target="_blank"
                rel="noreferrer noopener"
            >
                { mxEvent.isRedacted() || mxEvent.getType() !== 'm.room.message'
                    ? _t('Share Permalink') : _t('Share Message') }
            </MenuItem>
        );

        if (this.props.eventTileOps) { // this event is rendered using TextualBody
            quoteButton = (
                <MenuItem className="mx_MessageContextMenu_field" onClick={this.onQuoteClick}>
                    { _t('Quote') }
                </MenuItem>
            );
        }

        // Bridges can provide a 'external_url' to link back to the source.
        if (
            typeof(mxEvent.event.content.external_url) === "string" &&
            isUrlPermitted(mxEvent.event.content.external_url)
        ) {
            externalURLButton = (
                <MenuItem
                    element="a"
                    className="mx_MessageContextMenu_field"
                    target="_blank"
                    rel="noreferrer noopener"
                    onClick={this.closeMenu}
                    href={mxEvent.event.content.external_url}
                >
                    { _t('Source URL') }
                </MenuItem>
          );
        }

        if (this.props.collapseReplyThread) {
            collapseReplyThread = (
                <MenuItem className="mx_MessageContextMenu_field" onClick={this.onCollapseReplyThreadClick}>
                    { _t('Collapse Reply Thread') }
                </MenuItem>
            );
        }

        let reportEventButton;
        if (mxEvent.getSender() !== me) {
            reportEventButton = (
                <MenuItem className="mx_MessageContextMenu_field" onClick={this.onReportEventClick}>
                    { _t('Report Content') }
                </MenuItem>
            );
        }

        return (
            <div className="mx_MessageContextMenu">
                { resendButton }
                { resendEditButton }
                { resendReactionsButton }
                { resendRedactionButton }
                { redactButton }
                { cancelButton }
                { forwardButton }
                { pinButton }
                { viewSourceButton }
                { unhidePreviewButton }
                { permalinkButton }
                { quoteButton }
                { externalURLButton }
                { collapseReplyThread }
                { reportEventButton }
            </div>
        );
    }
}
<commit_msg>Close dialog on redact<commit_after>/*
Copyright 2015, 2016 OpenMarket Ltd
Copyright 2018 New Vector Ltd
Copyright 2019 Michael Telatynski <7t3chguy@gmail.com>
Copyright 2019 The Matrix.org Foundation C.I.C.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import React from 'react';
import PropTypes from 'prop-types';
import {EventStatus} from 'matrix-js-sdk/src/models/event';

import {MatrixClientPeg} from '../../../MatrixClientPeg';
import dis from '../../../dispatcher/dispatcher';
import * as sdk from '../../../index';
import { _t } from '../../../languageHandler';
import Modal from '../../../Modal';
import Resend from '../../../Resend';
import SettingsStore from '../../../settings/SettingsStore';
import { isUrlPermitted } from '../../../HtmlUtils';
import { isContentActionable } from '../../../utils/EventUtils';
import {MenuItem} from "../../structures/ContextMenu";
import {EventType} from "matrix-js-sdk/src/@types/event";
import {replaceableComponent} from "../../../utils/replaceableComponent";

function canCancel(eventStatus) {
    return eventStatus === EventStatus.QUEUED || eventStatus === EventStatus.NOT_SENT;
}

@replaceableComponent("views.context_menus.MessageContextMenu")
export default class MessageContextMenu extends React.Component {
    static propTypes = {
        /* the MatrixEvent associated with the context menu */
        mxEvent: PropTypes.object.isRequired,

        /* an optional EventTileOps implementation that can be used to unhide preview widgets */
        eventTileOps: PropTypes.object,

        /* an optional function to be called when the user clicks collapse thread, if not provided hide button */
        collapseReplyThread: PropTypes.func,

        /* callback called when the menu is dismissed */
        onFinished: PropTypes.func,

        /* if the menu is inside a dialog, we sometimes need to close that dialog after click (forwarding) */
        onCloseDialog: PropTypes.func,
    };

    state = {
        canRedact: false,
        canPin: false,
    };

    componentDidMount() {
        MatrixClientPeg.get().on('RoomMember.powerLevel', this._checkPermissions);
        this._checkPermissions();
    }

    componentWillUnmount() {
        const cli = MatrixClientPeg.get();
        if (cli) {
            cli.removeListener('RoomMember.powerLevel', this._checkPermissions);
        }
    }

    _checkPermissions = () => {
        const cli = MatrixClientPeg.get();
        const room = cli.getRoom(this.props.mxEvent.getRoomId());

        // We explicitly decline to show the redact option on ACL events as it has a potential
        // to obliterate the room - https://github.com/matrix-org/synapse/issues/4042
        const canRedact = room.currentState.maySendRedactionForEvent(this.props.mxEvent, cli.credentials.userId)
            && this.props.mxEvent.getType() !== EventType.RoomServerAcl;
        let canPin = room.currentState.mayClientSendStateEvent('m.room.pinned_events', cli);

        // HACK: Intentionally say we can't pin if the user doesn't want to use the functionality
        if (!SettingsStore.getValue("feature_pinning")) canPin = false;

        this.setState({canRedact, canPin});
    };

    _isPinned() {
        const room = MatrixClientPeg.get().getRoom(this.props.mxEvent.getRoomId());
        const pinnedEvent = room.currentState.getStateEvents('m.room.pinned_events', '');
        if (!pinnedEvent) return false;
        const content = pinnedEvent.getContent();
        return content.pinned && Array.isArray(content.pinned) && content.pinned.includes(this.props.mxEvent.getId());
    }

    onResendClick = () => {
        Resend.resend(this.props.mxEvent);
        this.closeMenu();
    };

    onResendEditClick = () => {
        Resend.resend(this.props.mxEvent.replacingEvent());
        this.closeMenu();
    };

    onResendRedactionClick = () => {
        Resend.resend(this.props.mxEvent.localRedactionEvent());
        this.closeMenu();
    };

    onResendReactionsClick = () => {
        for (const reaction of this._getUnsentReactions()) {
            Resend.resend(reaction);
        }
        this.closeMenu();
    };

    onReportEventClick = () => {
        const ReportEventDialog = sdk.getComponent("dialogs.ReportEventDialog");
        Modal.createTrackedDialog('Report Event', '', ReportEventDialog, {
            mxEvent: this.props.mxEvent,
        }, 'mx_Dialog_reportEvent');
        this.closeMenu();
    };

    onViewSourceClick = () => {
        const ViewSource = sdk.getComponent('structures.ViewSource');
        Modal.createTrackedDialog('View Event Source', '', ViewSource, {
            mxEvent: this.props.mxEvent,
        }, 'mx_Dialog_viewsource');
        this.closeMenu();
    };

    onRedactClick = () => {
        const ConfirmRedactDialog = sdk.getComponent("dialogs.ConfirmRedactDialog");
        Modal.createTrackedDialog('Confirm Redact Dialog', '', ConfirmRedactDialog, {
            onFinished: async (proceed, reason) => {
                if (!proceed) return;

                const cli = MatrixClientPeg.get();
                try {
                    await cli.redactEvent(
                        this.props.mxEvent.getRoomId(),
                        this.props.mxEvent.getId(),
                        undefined,
                        reason ? { reason } : {},
                    );
                    if (this.props.onCloseDialog) this.props.onCloseDialog();
                } catch (e) {
                    const code = e.errcode || e.statusCode;
                    // only show the dialog if failing for something other than a network error
                    // (e.g. no errcode or statusCode) as in that case the redactions end up in the
                    // detached queue and we show the room status bar to allow retry
                    if (typeof code !== "undefined") {
                        const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
                        // display error message stating you couldn't delete this.
                        Modal.createTrackedDialog('You cannot delete this message', '', ErrorDialog, {
                            title: _t('Error'),
                            description: _t('You cannot delete this message. (%(code)s)', {code}),
                        });
                    }
                }
            },
        }, 'mx_Dialog_confirmredact');
        this.closeMenu();
    };

    onCancelSendClick = () => {
        const mxEvent = this.props.mxEvent;
        const editEvent = mxEvent.replacingEvent();
        const redactEvent = mxEvent.localRedactionEvent();
        const pendingReactions = this._getPendingReactions();

        if (editEvent && canCancel(editEvent.status)) {
            Resend.removeFromQueue(editEvent);
        }
        if (redactEvent && canCancel(redactEvent.status)) {
            Resend.removeFromQueue(redactEvent);
        }
        if (pendingReactions.length) {
            for (const reaction of pendingReactions) {
                Resend.removeFromQueue(reaction);
            }
        }
        if (canCancel(mxEvent.status)) {
            Resend.removeFromQueue(this.props.mxEvent);
        }
        this.closeMenu();
    };

    onForwardClick = () => {
        dis.dispatch({
            action: 'forward_event',
            event: this.props.mxEvent,
        });
        if (this.props.onCloseDialog) this.props.onCloseDialog();
        this.closeMenu();
    };

    onPinClick = () => {
        MatrixClientPeg.get().getStateEvent(this.props.mxEvent.getRoomId(), 'm.room.pinned_events', '')
            .catch((e) => {
                // Intercept the Event Not Found error and fall through the promise chain with no event.
                if (e.errcode === "M_NOT_FOUND") return null;
                throw e;
            })
            .then((event) => {
                const eventIds = (event ? event.pinned : []) || [];
                if (!eventIds.includes(this.props.mxEvent.getId())) {
                    // Not pinned - add
                    eventIds.push(this.props.mxEvent.getId());
                } else {
                    // Pinned - remove
                    eventIds.splice(eventIds.indexOf(this.props.mxEvent.getId()), 1);
                }

                const cli = MatrixClientPeg.get();
                cli.sendStateEvent(this.props.mxEvent.getRoomId(), 'm.room.pinned_events', {pinned: eventIds}, '');
            });
        this.closeMenu();
    };

    closeMenu = () => {
        if (this.props.onFinished) this.props.onFinished();
    };

    onUnhidePreviewClick = () => {
        if (this.props.eventTileOps) {
            this.props.eventTileOps.unhideWidget();
        }
        this.closeMenu();
    };

    onQuoteClick = () => {
        dis.dispatch({
            action: 'quote',
            event: this.props.mxEvent,
        });
        this.closeMenu();
    };

    onPermalinkClick = (e: Event) => {
        e.preventDefault();
        const ShareDialog = sdk.getComponent("dialogs.ShareDialog");
        Modal.createTrackedDialog('share room message dialog', '', ShareDialog, {
            target: this.props.mxEvent,
            permalinkCreator: this.props.permalinkCreator,
        });
        this.closeMenu();
    };

    onCollapseReplyThreadClick = () => {
        this.props.collapseReplyThread();
        this.closeMenu();
    };

    _getReactions(filter) {
        const cli = MatrixClientPeg.get();
        const room = cli.getRoom(this.props.mxEvent.getRoomId());
        const eventId = this.props.mxEvent.getId();
        return room.getPendingEvents().filter(e => {
            const relation = e.getRelation();
            return relation &&
                relation.rel_type === "m.annotation" &&
                relation.event_id === eventId &&
                filter(e);
        });
    }

    _getPendingReactions() {
        return this._getReactions(e => canCancel(e.status));
    }

    _getUnsentReactions() {
        return this._getReactions(e => e.status === EventStatus.NOT_SENT);
    }

    render() {
        const cli = MatrixClientPeg.get();
        const me = cli.getUserId();
        const mxEvent = this.props.mxEvent;
        const eventStatus = mxEvent.status;
        const editStatus = mxEvent.replacingEvent() && mxEvent.replacingEvent().status;
        const redactStatus = mxEvent.localRedactionEvent() && mxEvent.localRedactionEvent().status;
        const unsentReactionsCount = this._getUnsentReactions().length;
        const pendingReactionsCount = this._getPendingReactions().length;
        const allowCancel = canCancel(mxEvent.status) ||
            canCancel(editStatus) ||
            canCancel(redactStatus) ||
            pendingReactionsCount !== 0;
        let resendButton;
        let resendEditButton;
        let resendReactionsButton;
        let resendRedactionButton;
        let redactButton;
        let cancelButton;
        let forwardButton;
        let pinButton;
        let unhidePreviewButton;
        let externalURLButton;
        let quoteButton;
        let collapseReplyThread;

        // status is SENT before remote-echo, null after
        const isSent = !eventStatus || eventStatus === EventStatus.SENT;
        if (!mxEvent.isRedacted()) {
            if (eventStatus === EventStatus.NOT_SENT) {
                resendButton = (
                    <MenuItem className="mx_MessageContextMenu_field" onClick={this.onResendClick}>
                        { _t('Resend') }
                    </MenuItem>
                );
            }

            if (editStatus === EventStatus.NOT_SENT) {
                resendEditButton = (
                    <MenuItem className="mx_MessageContextMenu_field" onClick={this.onResendEditClick}>
                        { _t('Resend edit') }
                    </MenuItem>
                );
            }

            if (unsentReactionsCount !== 0) {
                resendReactionsButton = (
                    <MenuItem className="mx_MessageContextMenu_field" onClick={this.onResendReactionsClick}>
                        { _t('Resend %(unsentCount)s reaction(s)', {unsentCount: unsentReactionsCount}) }
                    </MenuItem>
                );
            }
        }

        if (redactStatus === EventStatus.NOT_SENT) {
            resendRedactionButton = (
                <MenuItem className="mx_MessageContextMenu_field" onClick={this.onResendRedactionClick}>
                    { _t('Resend removal') }
                </MenuItem>
            );
        }

        if (isSent && this.state.canRedact) {
            redactButton = (
                <MenuItem className="mx_MessageContextMenu_field" onClick={this.onRedactClick}>
                    { _t('Remove') }
                </MenuItem>
            );
        }

        if (allowCancel) {
            cancelButton = (
                <MenuItem className="mx_MessageContextMenu_field" onClick={this.onCancelSendClick}>
                    { _t('Cancel Sending') }
                </MenuItem>
            );
        }

        if (isContentActionable(mxEvent)) {
            forwardButton = (
                <MenuItem className="mx_MessageContextMenu_field" onClick={this.onForwardClick}>
                    { _t('Forward Message') }
                </MenuItem>
            );

            if (this.state.canPin) {
                pinButton = (
                    <MenuItem className="mx_MessageContextMenu_field" onClick={this.onPinClick}>
                        { this._isPinned() ? _t('Unpin Message') : _t('Pin Message') }
                    </MenuItem>
                );
            }
        }

        const viewSourceButton = (
            <MenuItem className="mx_MessageContextMenu_field" onClick={this.onViewSourceClick}>
                { _t('View Source') }
            </MenuItem>
        );

        if (this.props.eventTileOps) {
            if (this.props.eventTileOps.isWidgetHidden()) {
                unhidePreviewButton = (
                    <MenuItem className="mx_MessageContextMenu_field" onClick={this.onUnhidePreviewClick}>
                        { _t('Unhide Preview') }
                    </MenuItem>
                );
            }
        }

        let permalink;
        if (this.props.permalinkCreator) {
            permalink = this.props.permalinkCreator.forEvent(this.props.mxEvent.getId());
        }
        // XXX: if we use room ID, we should also include a server where the event can be found (other than in the domain of the event ID)
        const permalinkButton = (
            <MenuItem
                element="a"
                className="mx_MessageContextMenu_field"
                onClick={this.onPermalinkClick}
                href={permalink}
                target="_blank"
                rel="noreferrer noopener"
            >
                { mxEvent.isRedacted() || mxEvent.getType() !== 'm.room.message'
                    ? _t('Share Permalink') : _t('Share Message') }
            </MenuItem>
        );

        if (this.props.eventTileOps) { // this event is rendered using TextualBody
            quoteButton = (
                <MenuItem className="mx_MessageContextMenu_field" onClick={this.onQuoteClick}>
                    { _t('Quote') }
                </MenuItem>
            );
        }

        // Bridges can provide a 'external_url' to link back to the source.
        if (
            typeof(mxEvent.event.content.external_url) === "string" &&
            isUrlPermitted(mxEvent.event.content.external_url)
        ) {
            externalURLButton = (
                <MenuItem
                    element="a"
                    className="mx_MessageContextMenu_field"
                    target="_blank"
                    rel="noreferrer noopener"
                    onClick={this.closeMenu}
                    href={mxEvent.event.content.external_url}
                >
                    { _t('Source URL') }
                </MenuItem>
          );
        }

        if (this.props.collapseReplyThread) {
            collapseReplyThread = (
                <MenuItem className="mx_MessageContextMenu_field" onClick={this.onCollapseReplyThreadClick}>
                    { _t('Collapse Reply Thread') }
                </MenuItem>
            );
        }

        let reportEventButton;
        if (mxEvent.getSender() !== me) {
            reportEventButton = (
                <MenuItem className="mx_MessageContextMenu_field" onClick={this.onReportEventClick}>
                    { _t('Report Content') }
                </MenuItem>
            );
        }

        return (
            <div className="mx_MessageContextMenu">
                { resendButton }
                { resendEditButton }
                { resendReactionsButton }
                { resendRedactionButton }
                { redactButton }
                { cancelButton }
                { forwardButton }
                { pinButton }
                { viewSourceButton }
                { unhidePreviewButton }
                { permalinkButton }
                { quoteButton }
                { externalURLButton }
                { collapseReplyThread }
                { reportEventButton }
            </div>
        );
    }
}
<|endoftext|>