/* * Vencord, a Discord client mod * Copyright (c) 2025 Vendicated and contributors * SPDX-License-Identifier: GPL-3.0-or-later */ import { ErrorBoundary } from "@components/index"; import { findComponentByCodeLazy } from "@webpack"; import { SearchableSelect, useState } from "@webpack/common"; import { JSX } from "react"; import { formatLowerBadge } from "./misc"; // GuildlessServerListItem's built-in pill does not support unread state. export const GuildlessServerListItemComponent = findComponentByCodeLazy("tooltip:", "lowerBadgeSize:"); export const GuildedServerListItemPillComponent = findComponentByCodeLazy('"pill":"empty"'); export const ServerListItemLowerBadgeComponent = findComponentByCodeLazy("STATUS_DANGER,disableColor:"); export const ServerListItemUpperBadgeComponent = findComponentByCodeLazy("STATUS_DANGER,shape:", "iconBadge,"); export const RadioGroup = findComponentByCodeLazy("\"label\",\"description\"]);"); export const QuestTile = findComponentByCodeLazy(".rowIndex,trackGuildAndChannelMetadata"); export enum QuestRewardType { UNKNOWN = 0, REWARD_CODE = 1, IN_GAME = 2, COLLECTIBLE = 3, VIRTUAL_CURRENCY = 4, FRACTIONAL_PREMIUM = 5 } export enum QuestStatus { Claimed = "CLAIMED", Unclaimed = "UNCLAIMED", Ignored = "IGNORED", Expired = "EXPIRED", Unknown = "UNKNOWN" } export interface FetchingQuestsSettingProps { interval: number | null; alert: string | null; } export interface RadioOption { name: string; value: string; } export interface SelectOption { label: string; value: string | number; } export interface DynamicDropdownSettingOption { label: string; value: string; selected: boolean; type?: string; } export interface RGB { r: number; g: number; b: number; } export type ExcludedQuestMap = Map; export type QuestMap = Map; export interface ExcludedQuest { id: string; } export interface Quest { questifyNumber: number; id: string; config: { application: { id: string; name: string; }, startsAt: string; expiresAt: string; messages: { questName: string; }; rewardsConfig: { rewards: { type: QuestRewardType; }[]; }, taskConfigV2: { tasks: { WATCH_VIDEO?: { type: "WATCH_VIDEO"; target: number; }, WATCH_VIDEO_ON_MOBILE?: { type: "WATCH_VIDEO_ON_MOBILE"; target: number; }, PLAY_ON_DESKTOP?: { type: "PLAY_ON_DESKTOP"; target: number; }, PLAY_ON_XBOX?: { type: "PLAY_ON_XBOX"; target: number; }, PLAY_ON_PLAYSTATION?: { type: "PLAY_ON_PLAYSTATION"; target: number; }, PLAY_ACTIVITY?: { type: "PLAY_ACTIVITY"; target: number; }; }; }; }, userStatus: null | { userId: string; claimedAt: string | null; completedAt: string | null; enrolledAt: string | null; progress: null | { WATCH_VIDEO?: { eventName: "WATCH_VIDEO"; value: number; }, WATCH_VIDEO_ON_MOBILE?: { eventName: "WATCH_VIDEO_ON_MOBILE"; value: number; }, PLAY_ON_DESKTOP?: { eventName: "PLAY_ON_DESKTOP"; value: number; }, PLAY_ON_XBOX?: { eventName: "PLAY_ON_XBOX"; value: number; }, PLAY_ON_PLAYSTATION?: { eventName: "PLAY_ON_PLAYSTATION"; value: number; }, PLAY_ACTIVITY?: { eventName: "PLAY_ACTIVITY"; value: number; }, }; }, dummyColor?: number | null; } export function SoundIcon(height: number, width: number, className?: string): JSX.Element { return ( ); } export function QuestIcon(height: number, width: number, className?: string): JSX.Element { return ( ); } export interface ServerListItemUpperBadgeProps { icon?: (() => JSX.Element), color?: string; className?: string; } export function ServerListItemUpperBadge(props: ServerListItemUpperBadgeProps): JSX.Element { return ( ); } export interface ServerListItemLowerBadgeProps { count: number; className?: string; color?: string; style?: React.CSSProperties; maxDigits?: number; } export function ServerListItemLowerBadge(props: ServerListItemLowerBadgeProps): JSX.Element { return ( ); } export interface GuildlessServerListItemProps { id?: string; className?: string; icon?: JSX.Element; tooltip?: string; showPill?: boolean; isVisible?: boolean; isSelected?: boolean; hasUnread?: boolean; lowerBadgeProps?: ServerListItemLowerBadgeProps; upperBadgeProps?: ServerListItemUpperBadgeProps; onClick?: ((e: React.MouseEvent) => void); onContextMenu?: ((e: React.MouseEvent) => void); onMouseDown?: ((e: React.MouseEvent) => void); } export function GuildlessServerListItem({ id, className = "vc-server-list-item", icon, tooltip, showPill = true, isVisible, isSelected, hasUnread, lowerBadgeProps, upperBadgeProps, onClick, onContextMenu, onMouseDown }: GuildlessServerListItemProps): JSX.Element { const baseClasses = className.split(" ") || ["vc-server-list-item"]; const mainContainerClass = `${baseClasses.join(" ")}-container`; const iconContainerClass = `${baseClasses.join(" ")}-icon-container`; const buttonContainerClass = `${baseClasses.join(" ")}-server-list-button-container`; const pillContainerClass = `${baseClasses.join(" ")}-pill-container`; const buttonClass = `${baseClasses.join(" ")}-button`; const pillClass = `${baseClasses.join(" ")}-pill`; const pillClassSelected = `${pillClass} selected`; const pillClassHovered = `${pillClass} hovered`; const lowerBadgeClass = `${baseClasses.join(" ")}-lower-badge`; const upperBadgeClass = `${baseClasses.join(" ")}-upper-badge`; const [hovered, setHovered] = useState(false); const visible = isVisible ?? true; const selected = isSelected ?? false; const unread = hasUnread ?? false; const lowerBadgeData = lowerBadgeProps ? { ...lowerBadgeProps, className: lowerBadgeProps.className ?? lowerBadgeClass } : null; const lowerBadgeSize = lowerBadgeData ? { width: formatLowerBadge(lowerBadgeData.count, lowerBadgeData.maxDigits)[1] } : null; const lowerBadge = !lowerBadgeData || lowerBadgeData.count === 0 ? null : ServerListItemLowerBadge({ ...lowerBadgeData, style: { ...(lowerBadgeData.style || {}), ...(lowerBadgeSize || {}) } }); const upperBadgeData = upperBadgeProps ? { ...upperBadgeProps, className: upperBadgeProps.className ?? upperBadgeClass } : null; const upperBadge = upperBadgeData ? ServerListItemUpperBadge(upperBadgeData) : null; const wrappedIcon = icon ? (
{icon}
) : undefined; const componentProps: Record = { ...(wrappedIcon && { icon: () => wrappedIcon }), ...(tooltip !== undefined && { tooltip }), ...(onClick !== undefined && { onClick }), ...(onMouseDown !== undefined && { onMouseDown }), ...(onContextMenu !== undefined && { onContextMenu }), // This one styles the backdrop of the badge while the one passed as a prop to the badge styles the badge itself. ...(lowerBadge && lowerBadgeSize ? { lowerBadgeSize: lowerBadgeSize } : {}), ...(lowerBadge ? { lowerBadge: lowerBadge } : {}), ...(upperBadge ? { upperBadge: upperBadge } : {}), }; return ( {visible && (
setHovered(true)} onMouseLeave={() => setHovered(false)} {...componentProps} />
)}
); } export interface DynamicDropdownProps extends React.ComponentProps { feedback?: string; } export const DynamicDropdown: React.FC = props => { return ; };