RxJS wait for 2 separate clicks events - javascript

I have a button and an image.
When I click the button, I want it to go to a "wait mode" of sorts.
Waiting for two separate clicks, that both return the x, y value of the mouse click events.
I got the mouse xy part no problem but at loss for what RxJS operator to use next
const elem = document.getElementById("MyImage");
const root = fromEvent(elem, "click");
const xy = root.pipe(map(evt => xyCartoPos(elem, evt)));
xy.subscribe(coords => console.log("xy:", coords));
function xyCartoPos(elem, e) {
const elemRect = elem.getBoundingClientRect();
return {
x: e.clientX - elemRect.x - elemRect.width / 2,
y: flipNum(e.clientY - elemRect.y - elemRect.height / 2)
};
}

You can use bufferCount to emit a fixed number of clicks at once (in one array).
const xy = root.pipe(
map(evt => xyCartoPos(elem, evt)),
bufferCount(2),
//take(1) // use take(1) if you only want to emit one pair of clicks and then complete
);

You could use scan to collect the events as an array, then use filter to verify the length of the array is 2:
const xy = root.pipe(
map(evt => xyCartoPos(elem, evt)),
scan((acc, evt) => {
acc.push(evt);
return acc;
}, []),
filter(events => events.length == 2),
);
This will cause only an array with the two mouse events, after two clicks, to be published to the subscriber.

Related

How to use element ref inside useMemo?

I have an HTML element, nav which has a ref set in it, sideNavRef.
I also want to get a variable to control the animations of that element and for that, I'm doing this:
const sideNavAnimation = useMemo(() => {
if (sideNavRef.current) {
const animations = createSideNavKeyframes({
startingWidth: (7 * document.body.offsetWidth) / 100,
endingWidth: 55,
});
const animation = sideNavRef.current.animate(animations.animation);
animation.pause();
return animation;
}
}, []);
The createSideNavKeyframes returns a keyframes array with calculated easing values for each step (0.1 to 1).
The easing function is this:
function easeInOutQuart(x: number): number {
return x < 0.5 ? 8 * x * x * x * x : 1 - Math.pow(-2 * x + 2, 4) / 2;
}
Since it would need to calculate this on every render I chose to use a memoized value since it wouldn't change.
But since the dependecy array is empty, it means that in the first render the value for the ref is still undefined and so the animations will be undefined as well.
Is there a better way to make this work?
Your function is registered with sideNavRef.current still not defined because useMemo runs before the component renders. You could add sideNavRef.current in the dependency array, to ask useMemo to create a new version of your function, but for that, your component should re-render somehow, cause sideNavRef.current changing won't trigger a re-render and useMemo check its dependency only on renders.
You could use a state called componentRendred for example, add it in the dependency array, and change its value in a useEffect, like so:
const [componentRendred, setComponentRendred] = useState(false);
const sideNavAnimation = useMemo(() => {
if (sideNavRef.current) {
const animations = createSideNavKeyframes({
startingWidth: (7 * document.body.offsetWidth) / 100,
endingWidth: 55,
});
const animation = sideNavRef.current.animate(animations.animation);
animation.pause();
return animation;
}
}, [componentRendred]);
useEffect(()=>{
setComponentRendred(true);
},[])

Detect if Matter.js Body is Circle

I want to test if a particular Matter body is a circle or not, as in:
const compounds = Matter.Composite.allBodies(engine.world)
compounds.forEach(compound => compound.parts.forEach(part => {
const isCircle = ???
if (isCircle) console.log(part.id, 'is a circle')
else console.log(part.id, 'is not a circle')
})
I can't find an official way to test if a Matter body was created as a circle. How can I test if a body was created with new Matter.Body.Circle versus another Body constructor?
You can console.log(a_circle) and check for something to identify a circle by.
I think you can check for a_circle.circleRadius or a_circle.label=='Circle Body'
EDIT: I have looked at the source code before posting this. It's a safe bet (for now as there is no documentation) because you can see that otherwise is just a polygon.
Matter.Bodies.circle = function(x, y, radius, options, maxSides) {
options = options || {};
var circle = {
label: 'Circle Body',
circleRadius: radius
};
// approximate circles with polygons until true circles implemented in SAT
maxSides = maxSides || 25;
var sides = Math.ceil(Math.max(10, Math.min(maxSides, radius)));
// optimisation: always use even number of sides (half the number of unique axes)
if (sides % 2 === 1)
sides += 1;
return Bodies.polygon(x, y, sides, radius, Common.extend({}, circle, options));
}
The other answer suggests looking for circle-specific properties. One problem is, these can change in future Matter.js releases. Also, it doesn't make for readable, intuitive code, and can lead to surprising bugs when additional body types wind up containing a property unexpectedly.
Better is to use the internal label property (also suggested in that answer), which should be stable and defaults to the seemingly-useful "Rectangle Body" and "Circle Body". For simple use cases, this works. Since it's possible to set the label to an object to store arbitrary custom data on the body, it's tempting to go further and use labels for just about everything.
However, I generally ignore labels. The reason is that it pushes too much of the client logic into a physics library that's not really designed for entity management. Furthermore, either of these approaches (labels or body-specific properties) involves iterating all of the bodies to filter out the type you're interested in.
Although no context was provided about the app, having to call allBodies often seems like a potential antipattern. It might be time to consider a redesign so you don't have to. What if you have 5 circles and 500 other bodies? Recursively iterating all 500 on every frame just to find the 5 is a huge waste of resources to achieve something that should be easy and efficient.
My preferred solution for entity management is to simply keep track of each type upon creation, putting them into data structures that are tuned to application-specific needs.
For example, the following script shows a method of efficiently determining body type by presenting the body as a key to a pre-built types map.
const engine = Matter.Engine.create();
engine.gravity.y = 0; // enable top-down
const map = {width: 300, height: 300};
const render = Matter.Render.create({
element: document.querySelector("#container"),
engine,
options: {...map, wireframes: false},
});
const rnd = n => ~~(Math.random() * n);
const rects = [...Array(20)].map(() => Matter.Bodies.rectangle(
rnd(map.width), // x
rnd(map.height), // y
rnd(10) + 15, // w
rnd(10) + 15, // h
{
angle: rnd(Math.PI * 2),
render: {fillStyle: "pink"}
}
));
const circles = [...Array(20)].map(() => Matter.Bodies.circle(
rnd(map.width), // x
rnd(map.height), // y
rnd(5) + 10, // r
{render: {fillStyle: "red"}}
));
const walls = [
Matter.Bodies.rectangle(
0, map.height / 2, 20, map.height, {
isStatic: true, render: {fillStyle: "yellow"}
}
),
Matter.Bodies.rectangle(
map.width / 2, 0, map.width, 20, {
isStatic: true, render: {fillStyle: "yellow"}
}
),
Matter.Bodies.rectangle(
map.width, map.height / 2, 20, map.height, {
isStatic: true, render: {fillStyle: "yellow"}
}
),
Matter.Bodies.rectangle(
map.width / 2, map.height, map.width, 20, {
isStatic: true, render: {fillStyle: "yellow"}
}
),
];
const rectangle = Symbol("rectangle");
const circle = Symbol("circle");
const wall = Symbol("wall");
const types = new Map([
...rects.map(e => [e, rectangle]),
...circles.map(e => [e, circle]),
...walls.map(e => [e, wall]),
]);
const bodies = [...types.keys()];
const mouseConstraint = Matter.MouseConstraint.create(
engine, {element: document.querySelector("#container")}
);
Matter.Composite.add(engine.world, [
...bodies, mouseConstraint
]);
const runner = Matter.Runner.create();
Matter.Events.on(runner, "tick", event => {
const underMouse = Matter.Query.point(
bodies,
mouseConstraint.mouse.position
);
if (underMouse.length) {
const descriptions = underMouse.map(e =>
types.get(e).description
);
document.querySelector("#type-hover").textContent = `
${descriptions.join(", ")} hovered
`;
}
else {
document.querySelector("#type-hover").textContent = `
[hover a body]
`;
}
if (mouseConstraint.body) {
document.querySelector("#type-click").textContent = `
${types.get(mouseConstraint.body).description} selected
`;
}
else {
document.querySelector("#type-click").textContent = `
[click and drag a body]
`;
}
});
Matter.Render.run(render);
Matter.Runner.run(runner, engine);
<script src="https://cdnjs.cloudflare.com/ajax/libs/matter-js/0.18.0/matter.min.js"></script>
<h3 id="type-click">[click and drag a body]</h3>
<h3 id="type-hover">[hover a body]</h3>
<div id="container"></div>
If creating and destroying bodies can happen dynamically, a function would need to be written to handle data structure bookkeeping.
Another approach that might work well for some apps is to have a few type-specific sets or maps. This allows you to quickly access all entities of a particular type. This becomes particularly useful once you begin composing bodies as properties of custom classes.
There's no reason you can't have both structures--a reverse lookup that gives the type or custom class given a MJS body, and a structure that contains references to all entities or MJS bodies of a particlar type.
The reverse lookup could enable logic like "on click, take an action on a specific MJS body depending on its type" or "on click, locate my custom class/model associated with this MJS body", while collections support logic like "destroy all enemies".
Ideally, code shouldn't be doing much type-checking. With proper OOP design, you can implement classes that respond correctly to methods regardless of their type. For example, if you have Enemy and Ally classes that each respond to a click, you might create a method called handleClick in each class. This allows you to use code like clickedEntity.handleClick(); without having to know whether clickedEntity is an Enemy or Ally, thereby avoiding the need for a "get type" operation entirely.
For more design suggestions for Matter.js projects, see:
How do you access a body by its label in MatterJS?
My own model in matter.js

Duplicating SVG's

I have an svg image that I want to duplicate n number of times on the screen. Each time I want to change its position (x and y). For example, I would like to use one svg to create a 3x3 square of the duplicated svg.
How can this be achieved?
I figured it out and what I did was
const duplicateHtml = (element, quantity) => {
const html = new Array(quantity)
.fill('')
.map(() => {
return element.innerHTML;
})
// joing all the elements and returning a string
.join('');
return (element.innerHTML = html);
};
duplicateHtml(document.querySelector('.svg-images'),3);

How does mouse movementX work on drag events?

I am trying to set up dynamic draggable element which will move its anchor point according to the drag delta. However I cannot seem to get the delta using event.movementX or event.movementY.
My simple drag event code:
mainMenuDiv.addEventListener("drag", (event)=>{
console.log(event.movementX, event.movementY);
});
// The console print out is simply:
//>0 0
// This prints out a lot of times as i drag the mouse but doesn't change the properties.
I think i am missing something about the way the movement property works with drag events.
I couldn't get it to work either, so I just did the following:
var start = null, delta = null
el.addEventListener('dragstart', e => {
start = {x: e.clientX, y: e.clientY}
})
el.addEventListener('drag', e => {
delta = {x: e.clientX - start.x, y: e.clientY - start.y}
})
el.addEventListener('dragend', e => start = delta = null)
You can use e.nativeEvent.offsetX and e.nativeEvent.offsetY, too.
I don't know the browser support though.

Bacon.js restart buffering when another EventStream fires

I'm trying to make a simple canvas-drawing app using Bacon.js. It should allow to draw a line by selecting start and end point using mouse clicks like this.
points = paper.asEventStream('click')
.map((e) -> x: e.pageX, y: e.pageY)
.bufferWithCount(2)
So far so good. Now it should allow to cancel starting point selection by clicking Esc.
cancel = doc.asEventStream('keypress')
.map((e) -> e.keyCode)
.filter((key) -> key is 27)
.map(true)
What is the baconian way of restarting buffering?
UPD: The implementation I've got so far looks awful.
points
.merge(cancel)
.buffer(undefined, (buffer) ->
values = buffer.values
if values.some((val) -> val is true) then values.length = 0
else if values.length is 2 then buffer.flush())
You can start the buffering from cancel:
var filteredPairs = cancel.startWith(true).flatMapLatest(function() {
return points.bufferWithCount(2)
})
See jsFiddle

Categories

Resources