ServerInfo Sorting

This commit is contained in:
thororen1234 2024-11-04 02:53:22 -05:00
parent 4d712b5020
commit f167613e5a
4 changed files with 258 additions and 11 deletions

View file

@ -15,6 +15,8 @@ import { findByPropsLazy, findComponentByCodeLazy } from "@webpack";
import { FluxDispatcher, Forms, GuildChannelStore, GuildMemberStore, GuildStore, IconUtils, Parser, PresenceStore, RelationshipStore, ScrollerThin, SnowflakeUtils, TabBar, Timestamp, useEffect, UserStore, UserUtils, useState, useStateFromStores } from "@webpack/common";
import { Guild, User } from "discord-types/general";
import { settings } from ".";
const IconClasses = findByPropsLazy("icon", "acronym", "childWrapper");
const FriendRow = findComponentByCodeLazy(".listName,discriminatorClass");
@ -31,7 +33,8 @@ export function openGuildInfoModal(guild: Guild) {
const enum Tabs {
ServerInfo,
Friends,
BlockedUsers
BlockedUsers,
MutualMembers
}
interface GuildProps {
@ -56,6 +59,7 @@ function renderTimestamp(timestamp: number) {
function GuildInfoModal({ guild }: GuildProps) {
const [friendCount, setFriendCount] = useState<number>();
const [blockedCount, setBlockedCount] = useState<number>();
const [mutualMembersCount, setMutualMembersCount] = useState<number>();
useEffect(() => {
fetched.friends = false;
@ -126,6 +130,12 @@ function GuildInfoModal({ guild }: GuildProps) {
>
Friends{friendCount !== undefined ? ` (${friendCount})` : ""}
</TabBar.Item>
<TabBar.Item
className={cl("tab", { selected: currentTab === Tabs.MutualMembers })}
id={Tabs.MutualMembers}
>
Mutual Server Members{mutualMembersCount !== undefined ? ` (${mutualMembersCount})` : ""}
</TabBar.Item>
<TabBar.Item
className={cl("tab", { selected: currentTab === Tabs.BlockedUsers })}
id={Tabs.BlockedUsers}
@ -137,6 +147,7 @@ function GuildInfoModal({ guild }: GuildProps) {
<div className={cl("tab-content")}>
{currentTab === Tabs.ServerInfo && <ServerInfoTab guild={guild} />}
{currentTab === Tabs.Friends && <FriendsTab guild={guild} setCount={setFriendCount} />}
{currentTab === Tabs.MutualMembers && <MutualMembersTab guild={guild} setCount={setMutualMembersCount} />}
{currentTab === Tabs.BlockedUsers && <BlockedUsersTab guild={guild} setCount={setBlockedCount} />}
</div>
</div>
@ -243,16 +254,152 @@ function UserList(type: "friends" | "blocked", guild: Guild, ids: string[], setC
useEffect(() => setCount(members.length), [members.length]);
const sortedMembers = members
.map(id => UserStore.getUser(id))
.sort(
(a, b) => {
switch (settings.store.sorting) {
case "username":
return a.username.localeCompare(b.username);
case "displayname":
return a?.globalName?.localeCompare(b?.globalName || b.username)
|| a.username.localeCompare(b?.globalName || b.username);
default:
return 0;
}
}
);
return (
<ScrollerThin fade className={cl("scroller")}>
{members.map(id =>
{sortedMembers.map(user => (
<FriendRow
user={UserStore.getUser(id)}
status={PresenceStore.getStatus(id) || "offline"}
onSelect={() => openUserProfile(id)}
user={user}
status={PresenceStore.getStatus(user.id) || "offline"}
onSelect={() => openUserProfile(user.id)}
onContextMenu={() => { }}
/>
)}
))}
</ScrollerThin>
);
}
interface MemberWithMutuals {
id: string;
mutualCount: number;
mutualGuilds: Array<{
guild: Guild;
iconUrl: string | null;
}>;
}
function getMutualGuilds(id: string): MemberWithMutuals {
const mutualGuilds: Array<{ guild: Guild; iconUrl: string | null; }> = [];
for (const guild of Object.values(GuildStore.getGuilds())) {
if (GuildMemberStore.isMember(guild.id, id)) {
const iconUrl = guild.icon
? IconUtils.getGuildIconURL({
id: guild.id,
icon: guild.icon,
canAnimate: true,
size: 20
}) ?? null
: null;
mutualGuilds.push({ guild, iconUrl });
}
}
return {
id,
mutualCount: mutualGuilds.length,
mutualGuilds
};
}
function MutualServerIcons({ member }: { member: MemberWithMutuals; }) {
const MAX_ICONS = 3;
const { mutualGuilds, mutualCount } = member;
return (
<div className={cl("mutual-guilds")}>
{mutualGuilds.slice(0, MAX_ICONS).map(({ guild, iconUrl }) => (
<div key={guild.id} className={cl("guild-icon")} role="img" aria-label={guild.name}>
{iconUrl ? (
<img src={iconUrl} alt="" />
) : (
<div className={cl("guild-acronym")}>{guild.acronym}</div>
)}
</div>
))}
{mutualCount > MAX_ICONS && (
<div className={cl("guild-count")}>
+{mutualCount - MAX_ICONS}
</div>
)}
</div>
);
}
function MutualMembersTab({ guild, setCount }: RelationshipProps) {
const [members, setMembers] = useState<MemberWithMutuals[]>([]);
const currentUserId = UserStore.getCurrentUser().id;
useEffect(() => {
const guildMembers = GuildMemberStore.getMemberIds(guild.id);
const membersWithMutuals = guildMembers
.map(id => getMutualGuilds(id))
// dont show yourself and members that are only in this server
.filter(member => member.mutualCount > 1 && member.id !== currentUserId);
// sort by mutual server count (descending)
membersWithMutuals.sort((a, b) => b.mutualCount - a.mutualCount);
setMembers(membersWithMutuals);
setCount(membersWithMutuals.length);
}, [guild.id]);
return (
<ScrollerThin fade className={cl("scroller")}>
{members
.map(member => {
const user = UserStore.getUser(member.id);
return { ...member, user };
})
.filter(Boolean)
.sort((a, b) => {
switch (settings.store.sorting) {
case "username":
return a.user.username.localeCompare(b.user.username);
case "displayname":
return a.user?.globalName?.localeCompare(b.user?.globalName || b.user.username)
|| a.user.username.localeCompare(b.user?.globalName || b.user.username);
default:
return 0;
}
})
.map(member => (
<div
className={cl("member-row")}
key={member.id}
onClick={() => openUserProfile(member.id)}
>
<div className={cl("member-content")}>
<FriendRow
user={member.user}
status={PresenceStore.getStatus(member.id) || "offline"}
onSelect={() => { }}
onContextMenu={() => { }}
mutualGuilds={member.mutualCount}
/>
</div>
<div className={cl("member-icons")} onClick={e => e.stopPropagation()}>
<MutualServerIcons member={member} />
</div>
</div>
))}
</ScrollerThin>
);
}

View file

@ -5,9 +5,9 @@
*/
import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu";
import { migratePluginSettings } from "@api/Settings";
import { Devs } from "@utils/constants";
import definePlugin from "@utils/types";
import { definePluginSettings, migratePluginSettings } from "@api/Settings";
import { Devs, EquicordDevs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types";
import { Menu } from "@webpack/common";
import { Guild } from "discord-types/general";
@ -25,15 +25,38 @@ const Patch: NavContextMenuPatchCallback = (children, { guild }: { guild: Guild;
);
};
export const settings = definePluginSettings({
sorting: {
type: OptionType.SELECT,
description: "Username or if applicable Display Name",
options: [
{
label: "Username",
value: "username"
},
{
label: "Display Name",
value: "displayname",
default: true
},
{
label: "Dont Sort",
value: "none",
}
]
}
});
migratePluginSettings("ServerInfo", "ServerProfile"); // what was I thinking with this name lmao
export default definePlugin({
name: "ServerInfo",
description: "Allows you to view info about a server",
authors: [Devs.Ven, Devs.Nuckyz],
authors: [Devs.Ven, Devs.Nuckyz, EquicordDevs.Z1xus],
dependencies: ["DynamicImageModalAPI"],
tags: ["guild", "info", "ServerProfile"],
contextMenus: {
"guild-context": Patch,
"guild-header-popout": Patch
}
},
settings
});

View file

@ -51,6 +51,7 @@
color: var(--interactive-normal);
cursor: pointer;
height: 39px;
margin-right: -10px;
line-height: 14px;
}
@ -93,6 +94,74 @@
max-height: 500px;
}
.vc-gp-member-row {
display: flex;
align-items: center;
padding-right: 16px;
cursor: pointer;
position: relative;
margin: 1px 0;
width: 100%;
}
.vc-gp-member-row:hover {
background-color: var(--background-modifier-hover);
}
.vc-gp-member-content {
flex: 1;
min-width: 0;
width: 100%;
position: relative;
}
.vc-gp-member-icons {
user-select: none;
position: absolute;
right: 20px;
z-index: 2;
}
.vc-gp-mutual-guilds {
display: flex;
align-items: center;
gap: 4px;
margin-left: auto;
padding-left: 8px;
}
.vc-gp-guild-icon {
width: 20px;
height: 20px;
border-radius: 50%;
background: var(--background-tertiary);
overflow: hidden;
}
.vc-gp-guild-icon img {
width: 100%;
height: 100%;
object-fit: cover;
}
.vc-gp-guild-acronym {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
font-size: 8px;
font-weight: 600;
color: var(--text-normal);
text-transform: uppercase;
}
.vc-gp-guild-count {
font-size: 12px;
color: var(--text-muted);
font-weight: 600;
}
.vc-gp-scroller [class^="listRow"] {
margin: 1px 0;
}

View file

@ -580,6 +580,10 @@ export const Devs = /* #__PURE__*/ Object.freeze({
name: "RamziAH",
id: 1279957227612147747n
},
SomeAspy: {
name: "SomeAspy",
id: 516750892372852754n
},
} satisfies Record<string, Dev>);
export const EquicordDevs = Object.freeze({
@ -936,6 +940,10 @@ export const EquicordDevs = Object.freeze({
name: "nvhhr",
id: 165098921071345666n
},
Z1xus: {
name: "Z1xus",
id: 377450600797044746n,
}
} satisfies Record<string, Dev>);
// iife so #__PURE__ works correctly