bigbluebutton-Github/bigbluebutton-html5/imports/ui/components/emoji-rain/component.jsx
prlanzarin 66788e9697 fix(reactions): crash when interactionsButton coords are absent
The client may crash whenever a emoji rain animation is triggered, but
the interactions button element cannot be located. This happens because
the button coordinates are fetched without checking whether the element
exists.

Get the coordinate fetching method to return null if the
interactionsButton element cannot be located, and ignore the emoji rain
action if that is the case. Whenever no valid coordinates are found, log
an warning so we can track this and figure out what's happening with the
button.

Fix a few typos in the getInteractionsButtonCoordinates method.
2024-04-12 14:27:20 -03:00

122 lines
3.9 KiB
JavaScript

import React, { useRef, useState, useEffect } from 'react';
import Settings from '/imports/ui/services/settings';
import Service from './service';
import logger from '/imports/startup/client/logger';
const EmojiRain = ({ reactions }) => {
const containerRef = useRef(null);
const [isAnimating, setIsAnimating] = useState(false);
const EMOJI_SIZE = Meteor.settings.public.app.emojiRain.emojiSize;
const NUMBER_OF_EMOJIS = Meteor.settings.public.app.emojiRain.numberOfEmojis;
const EMOJI_RAIN_ENABLED = Meteor.settings.public.app.emojiRain.enabled;
const { animations } = Settings.application;
function createEmojiRain(emoji) {
const coord = Service.getInteractionsButtonCoordinates();
const flyingEmojis = [];
if (coord == null) {
logger.warn({
logCode: 'interactions_emoji_rain_no_coord',
}, 'No coordinates for interactions button, skipping emoji rain');
return;
}
for (i = 0; i < NUMBER_OF_EMOJIS; i++) {
const initialPosition = {
x: coord.x + coord.width / 8,
y: coord.y + coord.height / 5,
};
const endPosition = {
x: Math.floor(Math.random() * 100) + coord.x - 100 / 2,
y: Math.floor(Math.random() * 300) + coord.y / 2,
};
const scale = Math.floor(Math.random() * (8 - 4 + 1)) - 40;
const sec = Math.floor(Math.random() * 1700) + 2000;
const shapeElement = document.createElement('svg');
const emojiElement = document.createElement('text');
emojiElement.setAttribute('x', '50%');
emojiElement.setAttribute('y', '50%');
emojiElement.innerHTML = emoji;
shapeElement.style.position = 'absolute';
shapeElement.style.left = `${initialPosition.x}px`;
shapeElement.style.top = `${initialPosition.y}px`;
shapeElement.style.transform = `scaleX(0.${scale}) scaleY(0.${scale})`;
shapeElement.style.transition = `${sec}ms`;
shapeElement.style.fontSize = `${EMOJI_SIZE}em`;
shapeElement.style.pointerEvents = 'none';
shapeElement.appendChild(emojiElement);
containerRef.current.appendChild(shapeElement);
flyingEmojis.push({ shapeElement, endPosition });
}
requestAnimationFrame(() => setTimeout(() => flyingEmojis.forEach((emoji) => {
const { shapeElement, endPosition } = emoji;
shapeElement.style.left = `${endPosition.x}px`;
shapeElement.style.top = `${endPosition.y}px`;
shapeElement.style.transform = 'scaleX(0) scaleY(0)';
}), 0));
setTimeout(() => {
flyingEmojis.forEach((emoji) => emoji.shapeElement.remove());
flyingEmojis.length = 0;
}, 2000);
}
const handleVisibilityChange = () => {
if (document.hidden) {
setIsAnimating(false);
} else if (EMOJI_RAIN_ENABLED && animations) {
setIsAnimating(true);
}
};
useEffect(() => {
document.addEventListener('visibilitychange', handleVisibilityChange);
return () => {
document.removeEventListener('visibilitychange', handleVisibilityChange);
};
}, []);
useEffect(() => {
if (EMOJI_RAIN_ENABLED && animations && !isAnimating && !document.hidden) {
setIsAnimating(true);
} else if (!animations) {
setIsAnimating(false);
}
}, [EMOJI_RAIN_ENABLED, animations, isAnimating]);
useEffect(() => {
if (isAnimating) {
reactions.forEach((reaction) => {
const currentTime = new Date().getTime();
const secondsSinceCreated = (currentTime - reaction.creationDate.getTime()) / 1000;
if (secondsSinceCreated <= 1 && (reaction.reaction !== 'none')) {
createEmojiRain(reaction.reaction);
}
});
}
}, [isAnimating, reactions]);
const containerStyle = {
width: '100vw',
height: '100vh',
position: 'fixed',
top: 0,
left: 0,
overflow: 'hidden',
pointerEvents: 'none',
zIndex: 2,
};
return <div ref={containerRef} style={containerStyle} />;
};
export default EmojiRain;