Merge pull request #18959 from lfzawacki/bbb-2.7
feat(reactions): port new reactions and fix emojiRain
This commit is contained in:
commit
01c3f6fb45
@ -4,11 +4,14 @@ import PropTypes from 'prop-types';
|
|||||||
import BBBMenu from '/imports/ui/components/common/menu/component';
|
import BBBMenu from '/imports/ui/components/common/menu/component';
|
||||||
import UserReactionService from '/imports/ui/components/user-reaction/service';
|
import UserReactionService from '/imports/ui/components/user-reaction/service';
|
||||||
import UserListService from '/imports/ui/components/user-list/service';
|
import UserListService from '/imports/ui/components/user-list/service';
|
||||||
import { Emoji } from 'emoji-mart';
|
|
||||||
import { convertRemToPixels } from '/imports/utils/dom-utils';
|
import { convertRemToPixels } from '/imports/utils/dom-utils';
|
||||||
|
import data from '@emoji-mart/data';
|
||||||
|
import { init } from 'emoji-mart';
|
||||||
|
|
||||||
import Styled from './styles';
|
import Styled from './styles';
|
||||||
|
|
||||||
|
const REACTIONS = Meteor.settings.public.userReaction.reactions;
|
||||||
|
|
||||||
const ReactionsButton = (props) => {
|
const ReactionsButton = (props) => {
|
||||||
const {
|
const {
|
||||||
intl,
|
intl,
|
||||||
@ -20,6 +23,9 @@ const ReactionsButton = (props) => {
|
|||||||
autoCloseReactionsBar,
|
autoCloseReactionsBar,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
|
// initialize emoji-mart data, need for the new version
|
||||||
|
init({ data });
|
||||||
|
|
||||||
const [showEmojiPicker, setShowEmojiPicker] = useState(false);
|
const [showEmojiPicker, setShowEmojiPicker] = useState(false);
|
||||||
|
|
||||||
const intlMessages = defineMessages({
|
const intlMessages = defineMessages({
|
||||||
@ -74,43 +80,20 @@ const ReactionsButton = (props) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const emojiProps = {
|
const emojiProps = {
|
||||||
native: true,
|
|
||||||
size: convertRemToPixels(1.5),
|
size: convertRemToPixels(1.5),
|
||||||
padding: '4px',
|
padding: '4px',
|
||||||
};
|
};
|
||||||
|
|
||||||
const reactions = [
|
const handReaction = {
|
||||||
{
|
id: 'hand',
|
||||||
id: 'smiley',
|
native: '✋',
|
||||||
native: '😃',
|
};
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'neutral_face',
|
|
||||||
native: '😐',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'slightly_frowning_face',
|
|
||||||
native: '🙁',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: '+1',
|
|
||||||
native: '👍',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: '-1',
|
|
||||||
native: '👎',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'clap',
|
|
||||||
native: '👏',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
let actions = [];
|
let actions = [];
|
||||||
|
|
||||||
reactions.forEach(({ id, native }) => {
|
REACTIONS.forEach(({ id, native }) => {
|
||||||
actions.push({
|
actions.push({
|
||||||
label: <Styled.ButtonWrapper active={currentUserReaction === native}><Emoji key={id} emoji={{ id }} {...emojiProps} /></Styled.ButtonWrapper>,
|
label: <Styled.ButtonWrapper active={currentUserReaction === native}><em-emoji key={native} native={native} {...emojiProps} /></Styled.ButtonWrapper>,
|
||||||
key: id,
|
key: id,
|
||||||
onClick: () => handleReactionSelect(native),
|
onClick: () => handleReactionSelect(native),
|
||||||
customStyles: actionCustomStyles,
|
customStyles: actionCustomStyles,
|
||||||
@ -118,29 +101,29 @@ const ReactionsButton = (props) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
actions.push({
|
actions.push({
|
||||||
label: <Styled.RaiseHandButtonWrapper isMobile={isMobile} data-test={raiseHand ? 'lowerHandBtn' : 'raiseHandBtn'} active={raiseHand}><Emoji key="hand" emoji={{ id: 'hand' }} {...emojiProps} />{RaiseHandButtonLabel()}</Styled.RaiseHandButtonWrapper>,
|
label: <Styled.RaiseHandButtonWrapper isMobile={isMobile} data-test={raiseHand ? 'lowerHandBtn' : 'raiseHandBtn'} active={raiseHand}><em-emoji key={handReaction.id} native={handReaction.native} emoji={{ id: handReaction.id }} {...emojiProps} />{RaiseHandButtonLabel()}</Styled.RaiseHandButtonWrapper>,
|
||||||
key: 'hand',
|
key: 'hand',
|
||||||
onClick: () => handleRaiseHandButtonClick(),
|
onClick: () => handleRaiseHandButtonClick(),
|
||||||
customStyles: {...actionCustomStyles, width: 'auto'},
|
customStyles: {...actionCustomStyles, width: 'auto'},
|
||||||
});
|
});
|
||||||
|
|
||||||
const icon = !raiseHand && currentUserReaction === 'none' ? 'hand' : null;
|
const icon = !raiseHand && currentUserReaction === 'none' ? 'hand' : null;
|
||||||
const currentUserReactionEmoji = reactions.find(({ native }) => native === currentUserReaction);
|
const currentUserReactionEmoji = REACTIONS.find(({ native }) => native === currentUserReaction);
|
||||||
|
|
||||||
let customIcon = null;
|
let customIcon = null;
|
||||||
|
|
||||||
if (raiseHand) {
|
if (raiseHand) {
|
||||||
customIcon = <Emoji key="hand" emoji={{ id: 'hand' }} {...emojiProps} />;
|
customIcon = <em-emoji key={handReaction.id} native={handReaction.native} emoji={handReaction} {...emojiProps} />;
|
||||||
} else {
|
} else {
|
||||||
if (!icon) {
|
if (!icon) {
|
||||||
customIcon = <Emoji key={currentUserReactionEmoji?.id} emoji={{ id: currentUserReactionEmoji?.id }} {...emojiProps} />;
|
customIcon = <em-emoji key={currentUserReactionEmoji?.id} native={currentUserReactionEmoji?.native} emoji={{ id: currentUserReactionEmoji?.id }} {...emojiProps} />;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BBBMenu
|
<BBBMenu
|
||||||
trigger={(
|
trigger={(
|
||||||
<Styled.ReactionsDropdown>
|
<Styled.ReactionsDropdown id="interactionsButton">
|
||||||
<Styled.RaiseHandButton
|
<Styled.RaiseHandButton
|
||||||
data-test="reactionsButton"
|
data-test="reactionsButton"
|
||||||
icon={icon}
|
icon={icon}
|
||||||
|
@ -101,28 +101,13 @@ const ReactionsDropdown = styled.div`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
const Wrapper = styled.div`
|
const Wrapper = styled.div`
|
||||||
.emoji-mart-bar {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
.emoji-mart-search {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
.emoji-mart-category[aria-label="Frequently Used"] {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
.emoji-mart-category-label{
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
.emoji-mart{
|
|
||||||
border: none;
|
|
||||||
}
|
|
||||||
@media(min-width: 600px) {
|
|
||||||
.emoji-mart-scroll{
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
padding: 0;
|
margin: 0.2em 0.2em 0.2em 0.2em;
|
||||||
height: 270px;
|
text-align: center;
|
||||||
width: 280px;
|
max-height: 270px;
|
||||||
}
|
width: 270px;
|
||||||
|
em-emoji {
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
@ -29,6 +29,7 @@ import WebcamContainer from '../webcam/container';
|
|||||||
import PresentationAreaContainer from '../presentation/presentation-area/container';
|
import PresentationAreaContainer from '../presentation/presentation-area/container';
|
||||||
import ScreenshareContainer from '../screenshare/container';
|
import ScreenshareContainer from '../screenshare/container';
|
||||||
import ExternalVideoContainer from '../external-video-player/container';
|
import ExternalVideoContainer from '../external-video-player/container';
|
||||||
|
import EmojiRainContainer from '../emoji-rain/container';
|
||||||
import Styled from './styles';
|
import Styled from './styles';
|
||||||
import { DEVICE_TYPE, ACTIONS, SMALL_VIEWPORT_BREAKPOINT, PANELS } from '../layout/enums';
|
import { DEVICE_TYPE, ACTIONS, SMALL_VIEWPORT_BREAKPOINT, PANELS } from '../layout/enums';
|
||||||
import {
|
import {
|
||||||
@ -647,6 +648,7 @@ class App extends Component {
|
|||||||
<PadsSessionsContainer />
|
<PadsSessionsContainer />
|
||||||
<WakeLockContainer />
|
<WakeLockContainer />
|
||||||
{this.renderActionsBar()}
|
{this.renderActionsBar()}
|
||||||
|
<EmojiRainContainer />
|
||||||
{customStyleUrl ? <link rel="stylesheet" type="text/css" href={customStyleUrl} /> : null}
|
{customStyleUrl ? <link rel="stylesheet" type="text/css" href={customStyleUrl} /> : null}
|
||||||
{customStyle ? <link rel="stylesheet" type="text/css" href={`data:text/css;charset=UTF-8,${encodeURIComponent(customStyle)}`} /> : null}
|
{customStyle ? <link rel="stylesheet" type="text/css" href={`data:text/css;charset=UTF-8,${encodeURIComponent(customStyle)}`} /> : null}
|
||||||
{isRandomUserSelectModalOpen ? <RandomUserSelectContainer
|
{isRandomUserSelectModalOpen ? <RandomUserSelectContainer
|
||||||
|
@ -249,7 +249,7 @@ class MessageForm extends PureComponent {
|
|||||||
stopUserTyping,
|
stopUserTyping,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const { message } = this.state;
|
const { message } = this.state;
|
||||||
let msg = message.trim();
|
const msg = message.trim();
|
||||||
|
|
||||||
if (msg.length < minMessageLength) return;
|
if (msg.length < minMessageLength) return;
|
||||||
|
|
||||||
@ -289,8 +289,6 @@ class MessageForm extends PureComponent {
|
|||||||
<Styled.EmojiPickerWrapper>
|
<Styled.EmojiPickerWrapper>
|
||||||
<Styled.EmojiPicker
|
<Styled.EmojiPicker
|
||||||
onEmojiSelect={(emojiObject) => this.handleEmojiSelect(emojiObject)}
|
onEmojiSelect={(emojiObject) => this.handleEmojiSelect(emojiObject)}
|
||||||
showPreview={false}
|
|
||||||
showSkinTones={false}
|
|
||||||
/>
|
/>
|
||||||
</Styled.EmojiPickerWrapper>
|
</Styled.EmojiPickerWrapper>
|
||||||
);
|
);
|
||||||
|
@ -109,21 +109,11 @@ const EmojiButton = styled(Button)`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
const EmojiPickerWrapper = styled.div`
|
const EmojiPickerWrapper = styled.div`
|
||||||
.emoji-mart {
|
em-emoji-picker {
|
||||||
max-width: 100% !important;
|
width: 100%;
|
||||||
}
|
max-height: 300px;
|
||||||
.emoji-mart-anchor {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
.emoji-mart-emoji {
|
|
||||||
cursor: pointer !important;
|
|
||||||
}
|
|
||||||
.emoji-mart-category-list {
|
|
||||||
span {
|
|
||||||
cursor: pointer !important;
|
|
||||||
display: inline-block !important;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
padding-bottom: 5px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const EmojiPicker = styled(EmojiPickerComponent)``;
|
const EmojiPicker = styled(EmojiPickerComponent)``;
|
||||||
|
@ -1,26 +1,16 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { injectIntl } from 'react-intl';
|
import { injectIntl } from 'react-intl';
|
||||||
import { Picker } from 'emoji-mart';
|
import data from '@emoji-mart/data';
|
||||||
import 'emoji-mart/css/emoji-mart.css';
|
import Picker from '@emoji-mart/react';
|
||||||
|
|
||||||
const DISABLE_EMOJIS = Meteor.settings.public.chat.disableEmojis;
|
const DISABLE_EMOJIS = Meteor.settings.public.chat.disableEmojis;
|
||||||
const FREQUENT_SORT_ON_CLICK = Meteor.settings.public.chat.emojiPicker.frequentEmojiSortOnClick;
|
|
||||||
|
|
||||||
const propTypes = {
|
const propTypes = {
|
||||||
intl: PropTypes.shape({
|
intl: PropTypes.shape({
|
||||||
formatMessage: PropTypes.func.isRequired,
|
formatMessage: PropTypes.func.isRequired,
|
||||||
}).isRequired,
|
}).isRequired,
|
||||||
onEmojiSelect: PropTypes.func.isRequired,
|
onEmojiSelect: PropTypes.func.isRequired,
|
||||||
style: PropTypes.shape({}),
|
|
||||||
showPreview: PropTypes.bool,
|
|
||||||
showSkinTones: PropTypes.bool,
|
|
||||||
};
|
|
||||||
|
|
||||||
const defaultProps = {
|
|
||||||
style: null,
|
|
||||||
showPreview: true,
|
|
||||||
showSkinTones: true,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const emojisToExclude = [
|
const emojisToExclude = [
|
||||||
@ -31,8 +21,6 @@ const EmojiPicker = (props) => {
|
|||||||
const {
|
const {
|
||||||
intl,
|
intl,
|
||||||
onEmojiSelect,
|
onEmojiSelect,
|
||||||
showPreview,
|
|
||||||
showSkinTones,
|
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const i18n = {
|
const i18n = {
|
||||||
@ -65,23 +53,19 @@ const EmojiPicker = (props) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Picker
|
<Picker
|
||||||
emoji=""
|
data={data}
|
||||||
onSelect={(emojiObject, event) => onEmojiSelect(emojiObject, event)}
|
onEmojiSelect={(emojiObject, event) => onEmojiSelect(emojiObject, event)}
|
||||||
enableFrequentEmojiSort={FREQUENT_SORT_ON_CLICK}
|
|
||||||
native
|
|
||||||
title=""
|
|
||||||
emojiSize={24}
|
emojiSize={24}
|
||||||
emojiTooltip
|
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
showPreview={showPreview}
|
previewPosition="none"
|
||||||
showSkinTones={showSkinTones}
|
skinTonePosition="none"
|
||||||
useButton
|
theme="light"
|
||||||
emojisToShowFilter={(emoji) => !emojisToExclude.includes(emoji.unified)}
|
dynamicWidth
|
||||||
|
exceptEmojis={emojisToExclude}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
EmojiPicker.propTypes = propTypes;
|
EmojiPicker.propTypes = propTypes;
|
||||||
EmojiPicker.defaultProps = defaultProps;
|
|
||||||
|
|
||||||
export default injectIntl(EmojiPicker);
|
export default injectIntl(EmojiPicker);
|
||||||
|
@ -1,71 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { injectIntl } from 'react-intl';
|
|
||||||
import { Picker } from 'emoji-mart';
|
|
||||||
import 'emoji-mart/css/emoji-mart.css';
|
|
||||||
|
|
||||||
const propTypes = {
|
|
||||||
intl: PropTypes.shape({
|
|
||||||
formatMessage: PropTypes.func.isRequired,
|
|
||||||
}).isRequired,
|
|
||||||
onEmojiSelect: PropTypes.func.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
const defaultProps = {
|
|
||||||
};
|
|
||||||
|
|
||||||
const emojisToExclude = [
|
|
||||||
// reactions
|
|
||||||
'1F605', '1F61C', '1F604', '1F609', '1F602', '1F92A',
|
|
||||||
'1F634', '1F62C', '1F633', '1F60D', '02665', '1F499',
|
|
||||||
'1F615', '1F61F', '1F928', '1F644', '1F612', '1F621',
|
|
||||||
'1F614', '1F625', '1F62D', '1F62F', '1F631', '1F630',
|
|
||||||
'1F44F', '1F44C', '1F44D', '1F44E', '1F4AA', '1F44A',
|
|
||||||
'1F64C', '1F64F', '1F440', '1F4A9', '1F921', '1F480',
|
|
||||||
];
|
|
||||||
|
|
||||||
const inlineStyle = {
|
|
||||||
margin: '.5rem 0',
|
|
||||||
alignSelf: 'center',
|
|
||||||
width: '100%',
|
|
||||||
};
|
|
||||||
|
|
||||||
const ReactionsPicker = (props) => {
|
|
||||||
const {
|
|
||||||
intl,
|
|
||||||
onEmojiSelect,
|
|
||||||
style,
|
|
||||||
} = props;
|
|
||||||
|
|
||||||
const i18n = {
|
|
||||||
notfound: intl.formatMessage({ id: 'app.emojiPicker.notFound' }),
|
|
||||||
clear: intl.formatMessage({ id: 'app.emojiPicker.clear' }),
|
|
||||||
skintext: intl.formatMessage({ id: 'app.emojiPicker.skintext' }),
|
|
||||||
categories: {
|
|
||||||
reactions: intl.formatMessage({ id: 'app.emojiPicker.categories.reactions' }),
|
|
||||||
},
|
|
||||||
categorieslabel: intl.formatMessage({ id: 'app.emojiPicker.categories.label' }),
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Picker
|
|
||||||
onSelect={(emojiObject, event) => onEmojiSelect(emojiObject, event)}
|
|
||||||
native
|
|
||||||
emoji=""
|
|
||||||
title=""
|
|
||||||
emojiSize={30}
|
|
||||||
emojiTooltip
|
|
||||||
style={Object.assign(inlineStyle, style)}
|
|
||||||
i18n={i18n}
|
|
||||||
showPreview={false}
|
|
||||||
showSkinTones={false}
|
|
||||||
emojisToShowFilter={(emoji) => emojisToExclude.includes(emoji.unified)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
ReactionsPicker.propTypes = propTypes;
|
|
||||||
ReactionsPicker.defaultProps = defaultProps;
|
|
||||||
|
|
||||||
export default injectIntl(ReactionsPicker);
|
|
@ -0,0 +1,35 @@
|
|||||||
|
import styled, { css } from 'styled-components';
|
||||||
|
|
||||||
|
const EmojiWrapper = styled.span`
|
||||||
|
padding-top: 0.9em;
|
||||||
|
padding-bottom: 0.1em;
|
||||||
|
${({ selected }) => !selected && css`
|
||||||
|
:hover {
|
||||||
|
border-radius:100%;
|
||||||
|
outline-color: transparent;
|
||||||
|
outline-style:solid;
|
||||||
|
box-shadow: 0 0 0 0.25em #eee;
|
||||||
|
background-color: #eee;
|
||||||
|
opacity: 0.75;
|
||||||
|
}
|
||||||
|
`}
|
||||||
|
${({ selected }) => selected && css`
|
||||||
|
em-emoji {
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
`}
|
||||||
|
${({ selected, emoji }) => selected && selected !== emoji && css`
|
||||||
|
opacity: 0.75;
|
||||||
|
`}
|
||||||
|
${({ selected, emoji }) => selected && selected === emoji && css`
|
||||||
|
border-radius:100%;
|
||||||
|
outline-color: transparent;
|
||||||
|
outline-style:solid;
|
||||||
|
box-shadow: 0 0 0 0.25em #eee;
|
||||||
|
background-color: #eee;
|
||||||
|
`}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export default {
|
||||||
|
EmojiWrapper,
|
||||||
|
};
|
@ -0,0 +1,113 @@
|
|||||||
|
import React, { useRef, useState, useEffect } from 'react';
|
||||||
|
import Settings from '/imports/ui/services/settings';
|
||||||
|
import Service from './service';
|
||||||
|
|
||||||
|
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.getInteractionsButtonCoordenates();
|
||||||
|
const flyingEmojis = [];
|
||||||
|
|
||||||
|
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;
|
@ -0,0 +1,11 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { withTracker } from 'meteor/react-meteor-data';
|
||||||
|
import EmojiRain from './component';
|
||||||
|
import UserReaction from '/imports/api/user-reaction';
|
||||||
|
import Auth from '/imports/ui/services/auth';
|
||||||
|
|
||||||
|
const EmojiRainContainer = (props) => <EmojiRain {...props} />;
|
||||||
|
|
||||||
|
export default withTracker(() => ({
|
||||||
|
reactions: UserReaction.find({ meetingId: Auth.meetingID }).fetch(),
|
||||||
|
}))(EmojiRainContainer);
|
@ -0,0 +1,9 @@
|
|||||||
|
const getInteractionsButtonCoordenates = () => {
|
||||||
|
const el = document.getElementById('interactionsButton');
|
||||||
|
const coordenada = el.getBoundingClientRect();
|
||||||
|
return coordenada;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default {
|
||||||
|
getInteractionsButtonCoordenates,
|
||||||
|
};
|
@ -4,7 +4,6 @@ import { injectIntl, defineMessages } from 'react-intl';
|
|||||||
import TooltipContainer from '/imports/ui/components/common/tooltip/container';
|
import TooltipContainer from '/imports/ui/components/common/tooltip/container';
|
||||||
import { Session } from 'meteor/session';
|
import { Session } from 'meteor/session';
|
||||||
import { findDOMNode } from 'react-dom';
|
import { findDOMNode } from 'react-dom';
|
||||||
import { Emoji } from 'emoji-mart';
|
|
||||||
import UserAvatar from '/imports/ui/components/user-avatar/component';
|
import UserAvatar from '/imports/ui/components/user-avatar/component';
|
||||||
import Icon from '/imports/ui/components/common/icon/component';
|
import Icon from '/imports/ui/components/common/icon/component';
|
||||||
import lockContextContainer from '/imports/ui/components/lock-viewers/context/container';
|
import lockContextContainer from '/imports/ui/components/lock-viewers/context/container';
|
||||||
@ -634,20 +633,30 @@ class UserListItem extends PureComponent {
|
|||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const emojiProps = {
|
const emojiProps = {
|
||||||
native: true,
|
|
||||||
size: '1.3rem',
|
size: '1.3rem',
|
||||||
};
|
};
|
||||||
|
|
||||||
let userAvatarFiltered = user.avatar;
|
let userAvatarFiltered = user.avatar;
|
||||||
|
|
||||||
|
const emojiIcons = [
|
||||||
|
{
|
||||||
|
id: 'hand',
|
||||||
|
native: '✋',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'clock7',
|
||||||
|
native: '⏰',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
const getIconUser = () => {
|
const getIconUser = () => {
|
||||||
if (user.raiseHand === true) {
|
if (user.raiseHand === true) {
|
||||||
return isReactionsEnabled
|
return isReactionsEnabled
|
||||||
? <Emoji key="hand" emoji={{ id: 'hand' }} {...emojiProps} />
|
? <em-emoji key={emojiIcons[0].id} native={emojiIcons[0].native} emoji={emojiIcons[0]} {...emojiProps} />
|
||||||
: <Icon iconName={normalizeEmojiName('raiseHand')} />;
|
: <Icon iconName={normalizeEmojiName('raiseHand')} />;
|
||||||
} if (user.away === true) {
|
} if (user.away === true) {
|
||||||
return isReactionsEnabled
|
return isReactionsEnabled
|
||||||
? <Emoji key="away" emoji={{ id: 'clock7' }} {...emojiProps} />
|
? <em-emoji key="away" native={emojiIcons[1].native} emoji={emojiIcons[1]} {...emojiProps} />
|
||||||
: <Icon iconName={normalizeEmojiName('away')} />;
|
: <Icon iconName={normalizeEmojiName('away')} />;
|
||||||
} if (user.emoji !== 'none' && user.emoji !== 'notAway') {
|
} if (user.emoji !== 'none' && user.emoji !== 'notAway') {
|
||||||
return <Icon iconName={normalizeEmojiName(user.emoji)} />;
|
return <Icon iconName={normalizeEmojiName(user.emoji)} />;
|
||||||
|
20
bigbluebutton-html5/package-lock.json
generated
20
bigbluebutton-html5/package-lock.json
generated
@ -337,6 +337,16 @@
|
|||||||
"kuler": "^2.0.0"
|
"kuler": "^2.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@emoji-mart/data": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@emoji-mart/data/-/data-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-1HP8BxD2azjqWJvxIaWAMyTySeZY0Osr83ukYjltPVkNXeJvTz7yDrPLBtnrD5uqJ3tg4CcLuuBW09wahqL/fg=="
|
||||||
|
},
|
||||||
|
"@emoji-mart/react": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@emoji-mart/react/-/react-1.1.1.tgz",
|
||||||
|
"integrity": "sha512-NMlFNeWgv1//uPsvLxvGQoIerPuVdXwK/EUek8OOkJ6wVOWPUizRBJU0hDqWZCOROVpfBgCemaC3m6jDOXi03g=="
|
||||||
|
},
|
||||||
"@emotion/babel-plugin": {
|
"@emotion/babel-plugin": {
|
||||||
"version": "11.11.0",
|
"version": "11.11.0",
|
||||||
"resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.11.0.tgz",
|
"resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.11.0.tgz",
|
||||||
@ -2808,13 +2818,9 @@
|
|||||||
"integrity": "sha512-ypZHxY+Sf/PXu7LVN+xoeanyisnJeSOy8Ki439L/oLueZb4c72FI45zXcK3gPpmTwyufh9m6NnbMLXnJh/0Fxg=="
|
"integrity": "sha512-ypZHxY+Sf/PXu7LVN+xoeanyisnJeSOy8Ki439L/oLueZb4c72FI45zXcK3gPpmTwyufh9m6NnbMLXnJh/0Fxg=="
|
||||||
},
|
},
|
||||||
"emoji-mart": {
|
"emoji-mart": {
|
||||||
"version": "3.0.1",
|
"version": "5.5.2",
|
||||||
"resolved": "https://registry.npmjs.org/emoji-mart/-/emoji-mart-3.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/emoji-mart/-/emoji-mart-5.5.2.tgz",
|
||||||
"integrity": "sha512-sxpmMKxqLvcscu6mFn9ITHeZNkGzIvD0BSNFE/LJESPbCA8s1jM6bCDPjWbV31xHq7JXaxgpHxLB54RCbBZSlg==",
|
"integrity": "sha512-Sqc/nso4cjxhOwWJsp9xkVm8OF5c+mJLZJFoFfzRuKO+yWiN7K8c96xmtughYb0d/fZ8UC6cLIQ/p4BR6Pv3/A=="
|
||||||
"requires": {
|
|
||||||
"@babel/runtime": "^7.0.0",
|
|
||||||
"prop-types": "^15.6.0"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"emoji-regex": {
|
"emoji-regex": {
|
||||||
"version": "8.0.0",
|
"version": "8.0.0",
|
||||||
|
@ -31,6 +31,8 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.17.9",
|
"@babel/runtime": "^7.17.9",
|
||||||
"@browser-bunyan/server-stream": "^1.8.0",
|
"@browser-bunyan/server-stream": "^1.8.0",
|
||||||
|
"@emoji-mart/data": "^1.1.2",
|
||||||
|
"@emoji-mart/react": "^1.1.1",
|
||||||
"@emotion/react": "^11.10.8",
|
"@emotion/react": "^11.10.8",
|
||||||
"@emotion/styled": "^11.10.8",
|
"@emotion/styled": "^11.10.8",
|
||||||
"@jitsi/sdp-interop": "0.1.14",
|
"@jitsi/sdp-interop": "0.1.14",
|
||||||
@ -45,7 +47,7 @@
|
|||||||
"browser-bunyan": "^1.8.0",
|
"browser-bunyan": "^1.8.0",
|
||||||
"classnames": "^2.2.6",
|
"classnames": "^2.2.6",
|
||||||
"darkreader": "^4.9.46",
|
"darkreader": "^4.9.46",
|
||||||
"emoji-mart": "^3.0.1",
|
"emoji-mart": "^5.5.2",
|
||||||
"eventemitter2": "~6.4.6",
|
"eventemitter2": "~6.4.6",
|
||||||
"fastdom": "^1.0.10",
|
"fastdom": "^1.0.10",
|
||||||
"fibers": "^4.0.2",
|
"fibers": "^4.0.2",
|
||||||
|
@ -152,6 +152,14 @@ public:
|
|||||||
# Enables the new raiseHand icon inside of the reaction menu (introduced in BBB 2.7)
|
# Enables the new raiseHand icon inside of the reaction menu (introduced in BBB 2.7)
|
||||||
# If both reactionsButton and raiseHandActionButton are enabled, reactionsButton takes precedence.
|
# If both reactionsButton and raiseHandActionButton are enabled, reactionsButton takes precedence.
|
||||||
enabled: true
|
enabled: true
|
||||||
|
emojiRain:
|
||||||
|
# If true, new reactions will be activated
|
||||||
|
enabled: false
|
||||||
|
# Can set the throttle, the number of emojis that will be displayed and their size
|
||||||
|
intervalEmojis: 2000
|
||||||
|
numberOfEmojis: 5
|
||||||
|
# emojiSize: size of the emoji in 'em' units
|
||||||
|
emojiSize: 2
|
||||||
# If enabled, before joining microphone the client will perform a trickle
|
# If enabled, before joining microphone the client will perform a trickle
|
||||||
# ICE against Kurento and use the information about successfull
|
# ICE against Kurento and use the information about successfull
|
||||||
# candidate-pairs to filter out local candidates in SIP.js's SDP.
|
# candidate-pairs to filter out local candidates in SIP.js's SDP.
|
||||||
@ -589,12 +597,24 @@ public:
|
|||||||
autoConvertEmoji: true
|
autoConvertEmoji: true
|
||||||
emojiPicker:
|
emojiPicker:
|
||||||
enable: false
|
enable: false
|
||||||
frequentEmojiSortOnClick: false
|
# e.g.: disableEmojis: ['grin','laughing']
|
||||||
# e.g.: disableEmojis: ['1F595','1F922']
|
|
||||||
disableEmojis: []
|
disableEmojis: []
|
||||||
userReaction:
|
userReaction:
|
||||||
enabled: true
|
enabled: true
|
||||||
expire: 60
|
expire: 60
|
||||||
|
reactions:
|
||||||
|
- id: 'smiley'
|
||||||
|
native: '😃'
|
||||||
|
- id: 'neutral_face'
|
||||||
|
native: '😐'
|
||||||
|
- id: 'slightly_frowning_face'
|
||||||
|
native: '🙁'
|
||||||
|
- id: '+1'
|
||||||
|
native: '👍'
|
||||||
|
- id: '-1'
|
||||||
|
native: '👎'
|
||||||
|
- id: 'clap'
|
||||||
|
native: '👏'
|
||||||
userStatus:
|
userStatus:
|
||||||
enabled: false
|
enabled: false
|
||||||
notes:
|
notes:
|
||||||
|
Loading…
Reference in New Issue
Block a user