Fix reaction burst skin tone variations
This commit is contained in:
parent
ae4fa04e95
commit
1834c09b28
4 changed files with 89 additions and 45 deletions
|
@ -18,7 +18,7 @@ import { CallReactionBurstEmoji } from './CallReactionBurstEmoji';
|
||||||
const LIFETIME = 3000;
|
const LIFETIME = 3000;
|
||||||
|
|
||||||
export type CallReactionBurstType = {
|
export type CallReactionBurstType = {
|
||||||
value: string;
|
values: Array<string>;
|
||||||
};
|
};
|
||||||
|
|
||||||
type CallReactionBurstStateType = CallReactionBurstType & {
|
type CallReactionBurstStateType = CallReactionBurstType & {
|
||||||
|
@ -124,10 +124,10 @@ export function CallReactionBurstProvider({
|
||||||
<CallReactionBurstContext.Provider value={contextValue}>
|
<CallReactionBurstContext.Provider value={contextValue}>
|
||||||
{createPortal(
|
{createPortal(
|
||||||
<div className="CallReactionBursts">
|
<div className="CallReactionBursts">
|
||||||
{bursts.map(({ value, key }) => (
|
{bursts.map(({ values, key }) => (
|
||||||
<CallReactionBurstEmoji
|
<CallReactionBurstEmoji
|
||||||
key={key}
|
key={key}
|
||||||
value={value}
|
values={values}
|
||||||
onAnimationEnd={() => hideBurst(key)}
|
onAnimationEnd={() => hideBurst(key)}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
|
|
@ -8,7 +8,7 @@ import { v4 as uuid } from 'uuid';
|
||||||
import { Emojify } from './conversation/Emojify';
|
import { Emojify } from './conversation/Emojify';
|
||||||
|
|
||||||
export type PropsType = {
|
export type PropsType = {
|
||||||
value: string;
|
values: Array<string>;
|
||||||
onAnimationEnd?: () => unknown;
|
onAnimationEnd?: () => unknown;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -25,14 +25,17 @@ type AnimationConfig = {
|
||||||
velocity: number;
|
velocity: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function CallReactionBurstEmoji({ value }: PropsType): JSX.Element {
|
// values is an array of emojis, which is useful when bursting multi skin tone set of
|
||||||
|
// emojis to get the correct representation
|
||||||
|
export function CallReactionBurstEmoji({ values }: PropsType): JSX.Element {
|
||||||
const [toY, setToY] = React.useState<number>(0);
|
const [toY, setToY] = React.useState<number>(0);
|
||||||
const fromY = -50;
|
const fromY = -50;
|
||||||
|
|
||||||
const generateEmojiProps = React.useCallback(() => {
|
const generateEmojiProps = React.useCallback(
|
||||||
|
(index: number) => {
|
||||||
return {
|
return {
|
||||||
key: uuid(),
|
key: uuid(),
|
||||||
value,
|
value: values[index % values.length],
|
||||||
springConfig: {
|
springConfig: {
|
||||||
mass: random(10, 20),
|
mass: random(10, 20),
|
||||||
tension: random(45, 60),
|
tension: random(45, 60),
|
||||||
|
@ -49,7 +52,9 @@ export function CallReactionBurstEmoji({ value }: PropsType): JSX.Element {
|
||||||
fromRotate: random(-45, 45),
|
fromRotate: random(-45, 45),
|
||||||
toRotate: random(-45, 45),
|
toRotate: random(-45, 45),
|
||||||
};
|
};
|
||||||
}, [fromY, toY, value]);
|
},
|
||||||
|
[fromY, toY, values]
|
||||||
|
);
|
||||||
|
|
||||||
// Calculate target Y position before first render. Emojis need to animate Y upwards
|
// Calculate target Y position before first render. Emojis need to animate Y upwards
|
||||||
// by the value of the container's top, plus the emoji's maximum height.
|
// by the value of the container's top, plus the emoji's maximum height.
|
||||||
|
@ -59,12 +64,12 @@ export function CallReactionBurstEmoji({ value }: PropsType): JSX.Element {
|
||||||
const { top } = containerRef.current.getBoundingClientRect();
|
const { top } = containerRef.current.getBoundingClientRect();
|
||||||
const calculatedToY = -top;
|
const calculatedToY = -top;
|
||||||
setToY(calculatedToY);
|
setToY(calculatedToY);
|
||||||
setEmojis([{ ...generateEmojiProps(), toY: calculatedToY }]);
|
setEmojis([{ ...generateEmojiProps(0), toY: calculatedToY }]);
|
||||||
}
|
}
|
||||||
}, [generateEmojiProps]);
|
}, [generateEmojiProps]);
|
||||||
|
|
||||||
const [emojis, setEmojis] = React.useState<Array<AnimatedEmojiProps>>([
|
const [emojis, setEmojis] = React.useState<Array<AnimatedEmojiProps>>([
|
||||||
generateEmojiProps(),
|
generateEmojiProps(0),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
|
@ -74,14 +79,14 @@ export function CallReactionBurstEmoji({ value }: PropsType): JSX.Element {
|
||||||
if (emojiCount + 1 >= NUM_EMOJIS) {
|
if (emojiCount + 1 >= NUM_EMOJIS) {
|
||||||
clearInterval(timer);
|
clearInterval(timer);
|
||||||
}
|
}
|
||||||
return [...curEmojis, generateEmojiProps()];
|
return [...curEmojis, generateEmojiProps(emojiCount)];
|
||||||
});
|
});
|
||||||
}, DELAY_BETWEEN_EMOJIS);
|
}, DELAY_BETWEEN_EMOJIS);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
clearInterval(timer);
|
clearInterval(timer);
|
||||||
};
|
};
|
||||||
}, [fromY, toY, value, generateEmojiProps]);
|
}, [fromY, toY, values, generateEmojiProps]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="CallReactionBurstEmoji" ref={containerRef}>
|
<div className="CallReactionBurstEmoji" ref={containerRef}>
|
||||||
|
|
|
@ -653,9 +653,9 @@ export function GroupCallReactions(): JSX.Element {
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
const activeCall = useReactionsEmitter(
|
const activeCall = useReactionsEmitter({
|
||||||
props.activeCall as ActiveGroupCallType
|
activeCall: props.activeCall as ActiveGroupCallType,
|
||||||
);
|
});
|
||||||
|
|
||||||
return <CallScreen {...props} activeCall={activeCall} />;
|
return <CallScreen {...props} activeCall={activeCall} />;
|
||||||
}
|
}
|
||||||
|
@ -670,11 +670,30 @@ export function GroupCallReactionsSpam(): JSX.Element {
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
const activeCall = useReactionsEmitter(
|
const activeCall = useReactionsEmitter({
|
||||||
props.activeCall as ActiveGroupCallType,
|
activeCall: props.activeCall as ActiveGroupCallType,
|
||||||
250
|
frequency: 250,
|
||||||
|
});
|
||||||
|
|
||||||
|
return <CallScreen {...props} activeCall={activeCall} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function GroupCallReactionsSkinTones(): JSX.Element {
|
||||||
|
const remoteParticipants = allRemoteParticipants.slice(0, 3);
|
||||||
|
const [props] = React.useState(
|
||||||
|
createProps({
|
||||||
|
callMode: CallMode.Group,
|
||||||
|
remoteParticipants,
|
||||||
|
viewMode: CallViewMode.Overflow,
|
||||||
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const activeCall = useReactionsEmitter({
|
||||||
|
activeCall: props.activeCall as ActiveGroupCallType,
|
||||||
|
frequency: 500,
|
||||||
|
emojis: ['👍', '👍🏻', '👍🏼', '👍🏽', '👍🏾', '👍🏿', '❤️', '😂', '😮', '😢'],
|
||||||
|
});
|
||||||
|
|
||||||
return <CallScreen {...props} activeCall={activeCall} />;
|
return <CallScreen {...props} activeCall={activeCall} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -701,11 +720,17 @@ export function GroupCallReactionsManyInOrder(): JSX.Element {
|
||||||
return <CallScreen {...props} />;
|
return <CallScreen {...props} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
function useReactionsEmitter(
|
function useReactionsEmitter({
|
||||||
activeCall: ActiveGroupCallType,
|
activeCall,
|
||||||
frequency = 2000,
|
frequency = 2000,
|
||||||
removeAfter = 5000
|
removeAfter = 5000,
|
||||||
) {
|
emojis = DEFAULT_PREFERRED_REACTION_EMOJI,
|
||||||
|
}: {
|
||||||
|
activeCall: ActiveGroupCallType;
|
||||||
|
frequency?: number;
|
||||||
|
removeAfter?: number;
|
||||||
|
emojis?: Array<string>;
|
||||||
|
}) {
|
||||||
const [call, setCall] = React.useState(activeCall);
|
const [call, setCall] = React.useState(activeCall);
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
const interval = setInterval(() => {
|
const interval = setInterval(() => {
|
||||||
|
@ -725,7 +750,7 @@ function useReactionsEmitter(
|
||||||
{
|
{
|
||||||
timestamp: timeNow,
|
timestamp: timeNow,
|
||||||
demuxId,
|
demuxId,
|
||||||
value: sample(DEFAULT_PREFERRED_REACTION_EMOJI) as string,
|
value: sample(emojis) as string,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -736,7 +761,7 @@ function useReactionsEmitter(
|
||||||
});
|
});
|
||||||
}, frequency);
|
}, frequency);
|
||||||
return () => clearInterval(interval);
|
return () => clearInterval(interval);
|
||||||
}, [frequency, removeAfter, call]);
|
}, [emojis, frequency, removeAfter, call]);
|
||||||
return call;
|
return call;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -87,6 +87,7 @@ import {
|
||||||
} from './CallReactionBurst';
|
} from './CallReactionBurst';
|
||||||
import { isGroupOrAdhocActiveCall } from '../util/isGroupOrAdhocCall';
|
import { isGroupOrAdhocActiveCall } from '../util/isGroupOrAdhocCall';
|
||||||
import { assertDev } from '../util/assert';
|
import { assertDev } from '../util/assert';
|
||||||
|
import { emojiToData } from './emoji/lib';
|
||||||
|
|
||||||
export type PropsType = {
|
export type PropsType = {
|
||||||
activeCall: ActiveCallType;
|
activeCall: ActiveCallType;
|
||||||
|
@ -1048,7 +1049,13 @@ function useReactionsToast(props: UseReactionsToastType): void {
|
||||||
const reactionsShown = useRef<
|
const reactionsShown = useRef<
|
||||||
Map<
|
Map<
|
||||||
string,
|
string,
|
||||||
{ value: string; isBursted: boolean; expireAt: number; demuxId: number }
|
{
|
||||||
|
value: string;
|
||||||
|
originalValue: string;
|
||||||
|
isBursted: boolean;
|
||||||
|
expireAt: number;
|
||||||
|
demuxId: number;
|
||||||
|
}
|
||||||
>
|
>
|
||||||
>(new Map());
|
>(new Map());
|
||||||
const burstsShown = useRef<Map<string, number>>(new Map());
|
const burstsShown = useRef<Map<string, number>>(new Map());
|
||||||
|
@ -1094,8 +1101,13 @@ function useReactionsToast(props: UseReactionsToastType): void {
|
||||||
recentBurstTime &&
|
recentBurstTime &&
|
||||||
recentBurstTime + REACTIONS_BURST_TRAILING_WINDOW > time
|
recentBurstTime + REACTIONS_BURST_TRAILING_WINDOW > time
|
||||||
);
|
);
|
||||||
|
// Normalize skin tone emoji to calculate burst threshold, but save original
|
||||||
|
// value to show in the burst animation
|
||||||
|
const emojiData = emojiToData(value);
|
||||||
|
const normalizedValue = emojiData?.unified ?? value;
|
||||||
reactionsShown.current.set(key, {
|
reactionsShown.current.set(key, {
|
||||||
value,
|
value: normalizedValue,
|
||||||
|
originalValue: value,
|
||||||
isBursted,
|
isBursted,
|
||||||
expireAt: timestamp + REACTIONS_BURST_WINDOW,
|
expireAt: timestamp + REACTIONS_BURST_WINDOW,
|
||||||
demuxId,
|
demuxId,
|
||||||
|
@ -1158,6 +1170,7 @@ function useReactionsToast(props: UseReactionsToastType): void {
|
||||||
}
|
}
|
||||||
|
|
||||||
burstsShown.current.set(value, time);
|
burstsShown.current.set(value, time);
|
||||||
|
const values: Array<string> = [];
|
||||||
reactionKeys.forEach(key => {
|
reactionKeys.forEach(key => {
|
||||||
const reactionShown = reactionsShown.current.get(key);
|
const reactionShown = reactionsShown.current.get(key);
|
||||||
if (!reactionShown) {
|
if (!reactionShown) {
|
||||||
|
@ -1165,8 +1178,9 @@ function useReactionsToast(props: UseReactionsToastType): void {
|
||||||
}
|
}
|
||||||
|
|
||||||
reactionShown.isBursted = true;
|
reactionShown.isBursted = true;
|
||||||
|
values.push(reactionShown.originalValue);
|
||||||
});
|
});
|
||||||
showBurst({ value });
|
showBurst({ values });
|
||||||
|
|
||||||
if (burstsShown.current.size >= REACTIONS_BURST_MAX_IN_SHORT_WINDOW) {
|
if (burstsShown.current.size >= REACTIONS_BURST_MAX_IN_SHORT_WINDOW) {
|
||||||
break;
|
break;
|
||||||
|
|
Loading…
Reference in a new issue