Skip to content

Commit 5535834

Browse files
committed
Improve shorts floating player
1 parent f71671b commit 5535834

File tree

5 files changed

+373
-10
lines changed

5 files changed

+373
-10
lines changed

ui/component/common/icon-custom.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1539,7 +1539,7 @@ export const icons = {
15391539
),
15401540
[ICONS.FIRE_ACTIVE]: buildIcon(
15411541
<path
1542-
d="M11.3969 23.04C11.3969 23.04 18.4903 21.8396 18.9753 16.2795C19.3997 9.89148 14.2161 7.86333 13.2915 4.56586C13.1861 4.2261 13.1051 3.88045 13.049 3.53109C12.9174 2.68094 12.8516 1.82342 12.852 0.964865C12.852 0.964865 5.607 0.426785 4.87947 10.6227C4.34858 10.1469 3.92655 9.57999 3.63777 8.9548C3.349 8.32962 3.19921 7.65853 3.19706 6.98033C3.19706 6.98033 -4.32074 18.7767 8.45649 23.04C7.94555 22.1623 7.67841 21.1842 7.67841 20.1909C7.67841 19.1976 7.94555 18.2195 8.45649 17.3418C9.54778 15.0653 9.97218 13.8788 9.97218 13.8788C9.97218 13.8788 15.5044 18.6525 11.3969 23.04Z"
1542+
d="M15.45 22.65C17.25 16.65 12.15 12.75 12.15 12.75C12.15 12.75 9.00001 18.15 9.60001 22.65C7.20001 21.45 5.55001 19.8 4.80001 17.7C3.60001 14.55 4.50001 11.1 5.25001 9C5.85001 10.2 7.80001 12.15 7.80001 12.15L7.95001 10.5C8.55001 2.25 12.6 0.9 14.4 0.75C14.4 1.8 14.7 4.8 17.1 7.95C18.75 10.05 20.55 12.45 20.4 16.5C20.1 20.1 17.4 21.9 15.45 22.65Z"
15431543
fill="#d62912"
15441544
strokeWidth="0"
15451545
/>

ui/component/videoRenderFloating/internal/floatingShortsActions/index.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ import { doFetchReactions, doReactionLike, doReactionDislike } from 'redux/actio
44
import { selectClientSetting } from 'redux/selectors/settings';
55
import { toggleAutoplayNextShort } from 'redux/actions/settings';
66
import { doSetShortsSidePanel } from 'redux/actions/shorts';
7+
import { selectIsSubscribedForUri } from 'redux/selectors/subscriptions';
8+
import { doChannelSubscribe, doChannelUnsubscribe } from 'redux/actions/subscriptions';
9+
import { selectPermanentUrlForUri } from 'redux/selectors/claims';
710
import * as SETTINGS from 'constants/settings';
811
import FloatingShortsActions from './view';
912

@@ -12,6 +15,8 @@ const select = (state, props) => ({
1215
likeCount: selectLikeCountForUri(state, props.uri),
1316
dislikeCount: selectDislikeCountForUri(state, props.uri),
1417
autoPlayNextShort: selectClientSetting(state, SETTINGS.AUTOPLAY_NEXT_SHORTS),
18+
isSubscribed: props.channelUrl ? selectIsSubscribedForUri(state, props.channelUrl) : false,
19+
channelPermanentUrl: props.channelUrl ? selectPermanentUrlForUri(state, props.channelUrl) : undefined,
1520
});
1621

1722
const perform = {
@@ -20,6 +25,8 @@ const perform = {
2025
doReactionDislike,
2126
doToggleShortsAutoplay: toggleAutoplayNextShort,
2227
doSetShortsSidePanel,
28+
doChannelSubscribe,
29+
doChannelUnsubscribe,
2330
};
2431

2532
export default connect(select, perform)(FloatingShortsActions);

ui/component/videoRenderFloating/internal/floatingShortsActions/view.jsx

Lines changed: 99 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
import React from 'react';
33
import classnames from 'classnames';
44
import Button from 'component/button';
5+
import ChannelThumbnail from 'component/channelThumbnail';
6+
import Icon from 'component/common/icon';
57
import * as ICONS from 'constants/icons';
68
import * as REACTION_TYPES from 'constants/reactions';
79
import { formatNumberWithCommas } from 'util/number';
@@ -21,6 +23,13 @@ type Props = {
2123
autoPlayNextShort: boolean,
2224
doToggleShortsAutoplay: () => void,
2325
doSetShortsSidePanel: (isOpen: boolean) => void,
26+
channelUrl: ?string,
27+
isSubscribed: boolean,
28+
channelPermanentUrl: ?string,
29+
doChannelSubscribe: (sub: {}) => void,
30+
doChannelUnsubscribe: (sub: {}) => void,
31+
onFireGlow?: () => void,
32+
onSlimeEffect?: () => void,
2433
};
2534

2635
const FloatingShortsActions = ({
@@ -38,11 +47,33 @@ const FloatingShortsActions = ({
3847
autoPlayNextShort,
3948
doToggleShortsAutoplay,
4049
doSetShortsSidePanel,
50+
channelUrl,
51+
isSubscribed,
52+
channelPermanentUrl,
53+
doChannelSubscribe,
54+
doChannelUnsubscribe,
55+
onFireGlow,
56+
onSlimeEffect,
4157
}: Props) => {
58+
const [optimisticReaction, setOptimisticReaction] = React.useState(undefined);
59+
const [fireButtonGlow, setFireButtonGlow] = React.useState(false);
60+
const fireButtonGlowTimeout = React.useRef(null);
61+
const [slimeButtonGlow, setSlimeButtonGlow] = React.useState(false);
62+
const slimeButtonGlowTimeout = React.useRef(null);
63+
const [avatarHover, setAvatarHover] = React.useState(false);
64+
4265
React.useEffect(() => {
4366
if (claimId) doFetchReactions(claimId);
4467
}, [claimId, doFetchReactions]);
4568

69+
React.useEffect(() => {
70+
setOptimisticReaction(undefined);
71+
}, [myReaction]);
72+
73+
const effectiveReaction = optimisticReaction !== undefined ? optimisticReaction : myReaction;
74+
const isFireActive = effectiveReaction === REACTION_TYPES.LIKE;
75+
const isSlimeActive = effectiveReaction === REACTION_TYPES.DISLIKE;
76+
4677
return (
4778
<>
4879
<div className="content__shorts-floating-nav">
@@ -64,16 +95,29 @@ const FloatingShortsActions = ({
6495
<div className="content__shorts-floating-actions">
6596
<div className="shorts-floating-action">
6697
<Button
67-
onClick={() => doReactionLike(uri)}
68-
icon={myReaction === REACTION_TYPES.LIKE ? ICONS.FIRE_ACTIVE : ICONS.FIRE}
98+
onClick={() => {
99+
setOptimisticReaction(isFireActive ? null : REACTION_TYPES.LIKE);
100+
if (!isFireActive) {
101+
if (onFireGlow) onFireGlow();
102+
setFireButtonGlow(false);
103+
clearTimeout(fireButtonGlowTimeout.current);
104+
requestAnimationFrame(() => {
105+
setFireButtonGlow(true);
106+
fireButtonGlowTimeout.current = setTimeout(() => setFireButtonGlow(false), 2000);
107+
});
108+
}
109+
doReactionLike(uri);
110+
}}
111+
icon={isFireActive ? ICONS.FIRE_ACTIVE : ICONS.FIRE}
69112
iconSize={14}
70113
requiresAuth
71114
authSrc="filereaction_like"
72115
className={classnames('button--file-action button-like', {
73-
'button--fire': myReaction === REACTION_TYPES.LIKE,
116+
'button--fire': isFireActive,
117+
'button--fire-glow-pulse': fireButtonGlow,
74118
})}
75119
label={
76-
myReaction === REACTION_TYPES.LIKE ? (
120+
isFireActive ? (
77121
<>
78122
<div className="button__fire-glow" />
79123
<div className="button__fire-particle1" />
@@ -93,16 +137,29 @@ const FloatingShortsActions = ({
93137

94138
<div className="shorts-floating-action">
95139
<Button
96-
onClick={() => doReactionDislike(uri)}
97-
icon={myReaction === REACTION_TYPES.DISLIKE ? ICONS.SLIME_ACTIVE : ICONS.SLIME}
140+
onClick={() => {
141+
setOptimisticReaction(isSlimeActive ? null : REACTION_TYPES.DISLIKE);
142+
if (!isSlimeActive) {
143+
if (onSlimeEffect) onSlimeEffect();
144+
setSlimeButtonGlow(false);
145+
clearTimeout(slimeButtonGlowTimeout.current);
146+
requestAnimationFrame(() => {
147+
setSlimeButtonGlow(true);
148+
slimeButtonGlowTimeout.current = setTimeout(() => setSlimeButtonGlow(false), 3000);
149+
});
150+
}
151+
doReactionDislike(uri);
152+
}}
153+
icon={isSlimeActive ? ICONS.SLIME_ACTIVE : ICONS.SLIME}
98154
iconSize={14}
99155
requiresAuth
100156
authSrc="filereaction_dislike"
101157
className={classnames('button--file-action button-dislike', {
102-
'button--slime': myReaction === REACTION_TYPES.DISLIKE,
158+
'button--slime': isSlimeActive,
159+
'button--slime-glow-pulse': slimeButtonGlow,
103160
})}
104161
label={
105-
myReaction === REACTION_TYPES.DISLIKE ? (
162+
isSlimeActive ? (
106163
<>
107164
<div className="button__slime-stain" />
108165
<div className="button__slime-drop1" />
@@ -116,6 +173,40 @@ const FloatingShortsActions = ({
116173
)}
117174
</div>
118175

176+
{channelUrl && (
177+
<div
178+
className="shorts-floating-action shorts-floating-action--avatar"
179+
onMouseEnter={() => setAvatarHover(true)}
180+
onMouseLeave={() => setAvatarHover(false)}
181+
onClick={() => {
182+
const sub = { channelName: channelUrl.split('/').pop(), uri: channelPermanentUrl };
183+
if (isSubscribed) {
184+
doChannelUnsubscribe(sub);
185+
} else {
186+
doChannelSubscribe(sub);
187+
}
188+
}}
189+
>
190+
<ChannelThumbnail uri={channelUrl} hideStakedIndicator className="shorts-floating-action__avatar" />
191+
<div
192+
className={classnames('shorts-floating-action__subscribe', {
193+
'shorts-floating-action__subscribe--active': isSubscribed,
194+
})}
195+
>
196+
<Icon
197+
icon={
198+
isSubscribed && avatarHover
199+
? ICONS.UNSUBSCRIBE
200+
: isSubscribed || avatarHover
201+
? ICONS.SUBSCRIBED
202+
: ICONS.SUBSCRIBE
203+
}
204+
size={10}
205+
/>
206+
</div>
207+
</div>
208+
)}
209+
119210
<div className="shorts-floating-action">
120211
<Button navigate={navigateUrl} onClick={() => doSetShortsSidePanel(true)} icon={ICONS.INFO} iconSize={14} />
121212
</div>

ui/component/videoRenderFloating/view.jsx

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,10 @@ function VideoRenderFloating(props: Props) {
185185
const shortsFloatingWrapperRef = React.useRef();
186186
const [forceDisable, setForceDisable] = React.useState(false);
187187
const [isShortsFloatingPaused, setIsShortsFloatingPaused] = React.useState(false);
188+
const [fireGlow, setFireGlow] = React.useState(false);
189+
const fireGlowTimeout = React.useRef(null);
190+
const [slimeEffect, setSlimeEffect] = React.useState(false);
191+
const slimeEffectTimeout = React.useRef(null);
188192
const [position, setPosition] = usePersistedState('floating-file-viewer:position', DEFAULT_INITIAL_FLOATING_POS);
189193
const relativePosRef = React.useRef(calculateRelativePos(position.x, position.y));
190194
const noPlayerHeight = fileViewerRect?.height === 0;
@@ -592,6 +596,8 @@ function VideoRenderFloating(props: Props) {
592596
[FLOATING_PLAYER_CLASS]: isFloating,
593597
'content__viewer--shorts-floating': isShortsFloating && !isMobile,
594598
'shorts-floating--paused': isShortsFloatingPaused,
599+
'shorts-floating--fire-glow': fireGlow,
600+
'shorts-floating--slime-effect': slimeEffect,
595601
'content__viewer--inline': !isFloating,
596602
'content__viewer--secondary': isComment,
597603
'content__viewer--theater-mode': theaterMode && mainFilePlaying && !isMobile,
@@ -670,12 +676,44 @@ function VideoRenderFloating(props: Props) {
670676
<FloatingShortsActions
671677
uri={uri}
672678
claimId={claimId}
679+
channelUrl={channelUrl}
673680
navigateUrl={navigateUrl}
674681
onPrevious={hasPreviousShort ? goToPreviousShort : null}
675682
onNext={hasNextShort ? goToNextShort : null}
683+
onFireGlow={() => {
684+
setFireGlow(false);
685+
clearTimeout(fireGlowTimeout.current);
686+
requestAnimationFrame(() => {
687+
setFireGlow(true);
688+
fireGlowTimeout.current = setTimeout(() => setFireGlow(false), 2000);
689+
});
690+
}}
691+
onSlimeEffect={() => {
692+
setSlimeEffect(false);
693+
clearTimeout(slimeEffectTimeout.current);
694+
requestAnimationFrame(() => {
695+
setSlimeEffect(true);
696+
slimeEffectTimeout.current = setTimeout(() => setSlimeEffect(false), 3000);
697+
});
698+
}}
676699
/>
677700
)}
678701

702+
{fireGlow && isShortsFloating && (
703+
<div className="shorts-floating-flames">
704+
{Array.from({ length: 50 }, (_, i) => (
705+
<div
706+
key={i}
707+
className="shorts-floating-flames__particle"
708+
style={{
709+
left: `calc(${(i / 50) * 100}% - 35px)`,
710+
animationDelay: `${Math.random()}s`,
711+
}}
712+
/>
713+
))}
714+
</div>
715+
)}
716+
679717
{isFloating && (
680718
<div
681719
className={classnames('content__info', {

0 commit comments

Comments
 (0)