diff --git a/src/plugins/memberCount/MemberCount.tsx b/src/plugins/memberCount/MemberCount.tsx
new file mode 100644
index 000000000..50665353e
--- /dev/null
+++ b/src/plugins/memberCount/MemberCount.tsx
@@ -0,0 +1,66 @@
+/*
+ * Vencord, a Discord client mod
+ * Copyright (c) 2024 Vendicated and contributors
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+import { getCurrentChannel } from "@utils/discord";
+import { SelectedChannelStore, Tooltip, useEffect, useStateFromStores } from "@webpack/common";
+
+import { ChannelMemberStore, cl, GuildMemberCountStore, numberFormat } from ".";
+import { OnlineMemberCountStore } from "./OnlineMemberCountStore";
+
+export function MemberCount({ isTooltip, tooltipGuildId }: { isTooltip?: true; tooltipGuildId?: string; }) {
+ const currentChannel = useStateFromStores([SelectedChannelStore], () => getCurrentChannel());
+
+ const guildId = isTooltip ? tooltipGuildId! : currentChannel.guild_id;
+
+ const totalCount = useStateFromStores(
+ [GuildMemberCountStore],
+ () => GuildMemberCountStore.getMemberCount(guildId)
+ );
+
+ let onlineCount = useStateFromStores(
+ [OnlineMemberCountStore],
+ () => OnlineMemberCountStore.getCount(guildId)
+ );
+
+ const { groups } = useStateFromStores(
+ [ChannelMemberStore],
+ () => ChannelMemberStore.getProps(guildId, currentChannel.id)
+ );
+
+ if (!isTooltip && (groups.length >= 1 || groups[0].id !== "unknown")) {
+ onlineCount = groups.reduce((total, curr) => total + (curr.id === "offline" ? 0 : curr.count), 0);
+ }
+
+ useEffect(() => {
+ OnlineMemberCountStore.ensureCount(guildId);
+ }, [guildId]);
+
+ if (totalCount == null)
+ return null;
+
+ const formattedOnlineCount = onlineCount != null ? numberFormat(onlineCount) : "?";
+
+ return (
+
+
+ {props => (
+
+
+ {formattedOnlineCount}
+
+ )}
+
+
+ {props => (
+
+
+ {numberFormat(totalCount)}
+
+ )}
+
+
+ );
+}
diff --git a/src/plugins/memberCount/OnlineMemberCountStore.ts b/src/plugins/memberCount/OnlineMemberCountStore.ts
new file mode 100644
index 000000000..8790f5e29
--- /dev/null
+++ b/src/plugins/memberCount/OnlineMemberCountStore.ts
@@ -0,0 +1,52 @@
+/*
+ * Vencord, a Discord client mod
+ * Copyright (c) 2024 Vendicated and contributors
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+import { proxyLazy } from "@utils/lazy";
+import { sleep } from "@utils/misc";
+import { Queue } from "@utils/Queue";
+import { Flux, FluxDispatcher, GuildChannelStore, PrivateChannelsStore } from "@webpack/common";
+
+export const OnlineMemberCountStore = proxyLazy(() => {
+ const preloadQueue = new Queue();
+
+ const onlineMemberMap = new Map();
+
+ class OnlineMemberCountStore extends Flux.Store {
+ getCount(guildId: string) {
+ return onlineMemberMap.get(guildId);
+ }
+
+ async _ensureCount(guildId: string) {
+ if (onlineMemberMap.has(guildId)) return;
+
+ await PrivateChannelsStore.preload(guildId, GuildChannelStore.getDefaultChannel(guildId).id);
+ }
+
+ ensureCount(guildId: string) {
+ if (onlineMemberMap.has(guildId)) return;
+
+ preloadQueue.push(() =>
+ this._ensureCount(guildId)
+ .then(
+ () => sleep(200),
+ () => sleep(200)
+ )
+ );
+ }
+ }
+
+ return new OnlineMemberCountStore(FluxDispatcher, {
+ GUILD_MEMBER_LIST_UPDATE({ guildId, groups }: { guildId: string, groups: { count: number; id: string; }[]; }) {
+ onlineMemberMap.set(
+ guildId,
+ groups.reduce((total, curr) => total + (curr.id === "offline" ? 0 : curr.count), 0)
+ );
+ },
+ ONLINE_GUILD_MEMBER_COUNT_UPDATE({ guildId, count }) {
+ onlineMemberMap.set(guildId, count);
+ }
+ });
+});
diff --git a/src/plugins/memberCount/index.tsx b/src/plugins/memberCount/index.tsx
index d9cd548e9..eb4ce372c 100644
--- a/src/plugins/memberCount/index.tsx
+++ b/src/plugins/memberCount/index.tsx
@@ -16,101 +16,48 @@
* along with this program. If not, see .
*/
+import "./style.css";
+
+import { classNameFactory } from "@api/Styles";
import ErrorBoundary from "@components/ErrorBoundary";
-import { Flex } from "@components/Flex";
import { Devs } from "@utils/constants";
-import { getCurrentChannel } from "@utils/discord";
import definePlugin from "@utils/types";
import { findStoreLazy } from "@webpack";
-import { SelectedChannelStore, Tooltip, useStateFromStores } from "@webpack/common";
import { FluxStore } from "@webpack/types";
-const GuildMemberCountStore = findStoreLazy("GuildMemberCountStore") as FluxStore & { getMemberCount(guildId: string): number | null; };
-const ChannelMemberStore = findStoreLazy("ChannelMemberStore") as FluxStore & {
+import { MemberCount } from "./MemberCount";
+
+export const GuildMemberCountStore = findStoreLazy("GuildMemberCountStore") as FluxStore & { getMemberCount(guildId: string): number | null; };
+export const ChannelMemberStore = findStoreLazy("ChannelMemberStore") as FluxStore & {
getProps(guildId: string, channelId: string): { groups: { count: number; id: string; }[]; };
};
const sharedIntlNumberFormat = new Intl.NumberFormat();
-const numberFormat = (value: number) => sharedIntlNumberFormat.format(value);
-
-function MemberCount() {
- const { id: channelId, guild_id: guildId } = useStateFromStores([SelectedChannelStore], () => getCurrentChannel());
- const { groups } = useStateFromStores(
- [ChannelMemberStore],
- () => ChannelMemberStore.getProps(guildId, channelId)
- );
- const total = useStateFromStores(
- [GuildMemberCountStore],
- () => GuildMemberCountStore.getMemberCount(guildId)
- );
-
- if (total == null)
- return null;
-
- const online =
- (groups.length === 1 && groups[0].id === "unknown")
- ? 0
- : groups.reduce((count, curr) => count + (curr.id === "offline" ? 0 : curr.count), 0);
-
- return (
-
-
- {props => (
-
-
- {numberFormat(online)}
-
- )}
-
-
- {props => (
-
-
- {numberFormat(total)}
-
- )}
-
-
- );
-}
+export const numberFormat = (value: number) => sharedIntlNumberFormat.format(value);
+export const cl = classNameFactory("vc-membercount-");
export default definePlugin({
name: "MemberCount",
- description: "Shows the amount of online & total members in the server member list",
+ description: "Shows the amount of online & total members in the server member list and tooltip",
authors: [Devs.Ven, Devs.Commandtechno],
- patches: [{
- find: "{isSidebarVisible:",
- replacement: {
- match: /(?<=let\{className:(\i),.+?children):\[(\i\.useMemo[^}]+"aria-multiselectable")/,
- replace: ":[$1?.startsWith('members')?$self.render():null,$2"
+ patches: [
+ {
+ find: "{isSidebarVisible:",
+ replacement: {
+ match: /(?<=let\{className:(\i),.+?children):\[(\i\.useMemo[^}]+"aria-multiselectable")/,
+ replace: ":[$1?.startsWith('members')?$self.render():null,$2"
+ }
+ },
+ {
+ find: ".invitesDisabledTooltip",
+ replacement: {
+ match: /(?<=\.VIEW_AS_ROLES_MENTIONS_WARNING.{0,100})]/,
+ replace: ",$self.renderTooltip(arguments[0].guild)]"
+ }
}
- }],
+ ],
- render: ErrorBoundary.wrap(MemberCount, { noop: true })
+ render: ErrorBoundary.wrap(MemberCount, { noop: true }),
+ renderTooltip: ErrorBoundary.wrap(guild => , { noop: true })
});
diff --git a/src/plugins/memberCount/style.css b/src/plugins/memberCount/style.css
new file mode 100644
index 000000000..f43bff830
--- /dev/null
+++ b/src/plugins/memberCount/style.css
@@ -0,0 +1,44 @@
+.vc-membercount-widget {
+ display: flex;
+ align-content: center;
+
+ --color-online: var(--green-360);
+ --color-total: var(--primary-400);
+}
+
+.vc-membercount-tooltip {
+ margin-top: 0.25em;
+ margin-left: 2px;
+}
+
+.vc-membercount-member-list {
+ justify-content: center;
+ margin-top: 1em;
+ padding-inline: 1em;
+}
+
+.vc-membercount-online {
+ color: var(--color-online);
+}
+
+.vc-membercount-total {
+ color: var(--color-total);
+}
+
+.vc-membercount-online-dot {
+ background-color: var(--color-online);
+ display: inline-block;
+ width: 12px;
+ height: 12px;
+ border-radius: 50%;
+ margin-right: 0.5em;
+}
+
+.vc-membercount-total-dot {
+ display: inline-block;
+ width: 6px;
+ height: 6px;
+ border-radius: 50%;
+ border: 3px solid var(--color-total);
+ margin: 0 0.5em 0 1em;
+}