From d38b915999b28f814c201c5db54a87ec15e9d5c3 Mon Sep 17 00:00:00 2001 From: Loukious <Loukious@users.noreply.github.com> Date: Tue, 26 Mar 2024 00:56:27 +0100 Subject: [PATCH] Merge remote-tracking branch 'fres621/whos-watching' --- src/plugins/WhosWatching/index.tsx | 98 ++++++++++++++++++++++++++++-- 1 file changed, 94 insertions(+), 4 deletions(-) diff --git a/src/plugins/WhosWatching/index.tsx b/src/plugins/WhosWatching/index.tsx index 9fbee42c100..2234e8c6d93 100644 --- a/src/plugins/WhosWatching/index.tsx +++ b/src/plugins/WhosWatching/index.tsx @@ -4,12 +4,17 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ +import { definePluginSettings } from "@api/Settings"; import { classNameFactory } from "@api/Styles"; import { Flex } from "@components/Flex"; import { Devs } from "@utils/constants"; -import definePlugin from "@utils/types"; -import { findByPropsLazy, findStoreLazy } from "@webpack"; -import { Forms, i18n, RelationshipStore, Tooltip, UserStore, useStateFromStores } from "@webpack/common"; +import { openUserProfile } from "@utils/discord"; +import { Margins } from "@utils/margins"; +import { classes } from "@utils/misc"; +import definePlugin, { OptionType } from "@utils/types"; +import { findByPropsLazy, findComponentByCodeLazy, findStoreLazy } from "@webpack"; +import { Clickable, Forms, i18n, RelationshipStore, Tooltip, UserStore, useStateFromStores } from "@webpack/common"; +import { User } from "discord-types/general"; interface WatchingProps { userIds: string[]; @@ -22,6 +27,15 @@ function getUsername(user: any): string { return RelationshipStore.getNickname(user.id) || user.globalName || user.username; } +const settings = definePluginSettings({ + showPanel: { + description: "Show spectators under screenshare panel", + type: OptionType.BOOLEAN, + default: true, + restartNeeded: true + }, +}); + function Watching({ userIds, guildId }: WatchingProps): JSX.Element { // Missing Users happen when UserStore.getUser(id) returns null -- The client should automatically cache spectators, so this might not be possible but it's better to be sure just in case let missingUsers = 0; @@ -51,12 +65,16 @@ const { encodeStreamKey }: { encodeStreamKey: (any) => string; } = findByPropsLazy("encodeStreamKey"); +const UserSummaryItem = findComponentByCodeLazy("defaultRenderUser", "showDefaultAvatarsForNullUsers"); +const AvatarStyles = findByPropsLazy("moreUsers", "emptyUser", "avatarContainer", "clickableAvatar"); + export default definePlugin({ name: "WhosWatching", - description: "Lets you view what users are watching your stream by hovering over the screenshare icon", + description: "Hover over the screenshare icon to view what users are watching your stream", authors: [ Devs.Fres ], + settings: settings, patches: [ { find: ".Masks.STATUS_SCREENSHARE,width:32", @@ -64,8 +82,80 @@ export default definePlugin({ match: /default:function\(\)\{return ([a-zA-Z0-9_]{0,5})\}/, replace: "default:function(){return $self.component({OriginalComponent:$1})}" } + }, + { + predicate: () => settings.store.showPanel, + find: "this.isJoinableActivity()||", + replacement: { + match: /(this\.isJoinableActivity\(\).{0,200}children:.{0,50})"div"/, + replace: "$1$self.WrapperComponent" + } } ], + WrapperComponent: function ({ ...props }) { + const stream = useStateFromStores([ApplicationStreamingStore], () => ApplicationStreamingStore.getCurrentUserActiveStream()); + + if (!stream) return <div {...props}>{props.children}</div>; + + const userIds = ApplicationStreamingStore.getViewerIds(encodeStreamKey(stream)); + + let missingUsers = 0; + const users = userIds.map(id => UserStore.getUser(id)).filter(user => Boolean(user) ? true : (missingUsers += 1, false)); + + function renderMoreUsers(_label: string, count: number) { + const sliced = users.slice(count - 1); + return ( + <Tooltip text={<Watching userIds={userIds} guildId={stream.guildId} />}> + {({ onMouseEnter, onMouseLeave }) => ( + <div + className={AvatarStyles.moreUsers} + onMouseEnter={onMouseEnter} + onMouseLeave={onMouseLeave} + > + +{sliced.length + missingUsers} + </div> + )} + </Tooltip> + ); + } + + return ( + <> + <div {...props}>{props.children}</div> + <div className={classes(cl("spectators_panel"), Margins.top8)}> + {users.length ? + <> + <Forms.FormTitle tag="h3" style={{ marginTop: 8, marginBottom: 0, textTransform: "uppercase" }}>{i18n.Messages.SPECTATORS.format({ numViewers: userIds.length })}</Forms.FormTitle> + <UserSummaryItem + users={users} + count={userIds.length} + renderIcon={false} + max={12} + showDefaultAvatarsForNullUsers + showUserPopout + guildId={stream.guildId} + renderMoreUsers={renderMoreUsers} + renderUser={(user: User) => ( + <Clickable + className={AvatarStyles.clickableAvatar} + onClick={() => openUserProfile(user.id)} + > + <img + className={AvatarStyles.avatar} + src={user.getAvatarURL(void 0, 80, true)} + alt={user.username} + title={user.username} + /> + </Clickable> + )} + /> + </> + : <Forms.FormText>No spectators</Forms.FormText> + } + </div> + </> + ); + }, component: function ({ OriginalComponent }) { return (props: any) => { const stream = useStateFromStores([ApplicationStreamingStore], () => ApplicationStreamingStore.getCurrentUserActiveStream());