From 484b6f118b42ad10e37817dba21178978ed4b43d Mon Sep 17 00:00:00 2001 From: Asraye Date: Wed, 17 Sep 2025 00:44:49 +1000 Subject: [PATCH] feat: add invite hover action changes "invite people" into "create invite" for consistency --- src/components/guilds/GuildSidebar.tsx | 486 ++++++++++++++++--------- 1 file changed, 319 insertions(+), 167 deletions(-) diff --git a/src/components/guilds/GuildSidebar.tsx b/src/components/guilds/GuildSidebar.tsx index aecf0f6..92b9177 100644 --- a/src/components/guilds/GuildSidebar.tsx +++ b/src/components/guilds/GuildSidebar.tsx @@ -1,83 +1,104 @@ -import {A, useNavigate, useParams} from "@solidjs/router"; -import {createMemo, createSignal, For, Match, Show, Switch, onMount, onCleanup} from "solid-js"; -import {getApi} from "../../api/Api"; -import {GuildChannel} from "../../types/channel"; -import {ModalId, useModal} from "../ui/Modal"; -import Icon, {IconElement} from "../icons/Icon"; +import { A, useNavigate, useParams } from "@solidjs/router"; +import { + createMemo, + createSignal, + For, + Match, + Show, + Switch, + onMount, + onCleanup, +} from "solid-js"; +import { getApi } from "../../api/Api"; +import { GuildChannel } from "../../types/channel"; +import { ModalId, useModal } from "../ui/Modal"; +import Icon, { IconElement } from "../icons/Icon"; import ChevronDown from "../icons/svg/ChevronDown"; import UserPlus from "../icons/svg/UserPlus"; import Trash from "../icons/svg/Trash"; import RightFromBracket from "../icons/svg/RightFromBracket"; import HomeIcon from "../icons/svg/Home"; import useContextMenu from "../../hooks/useContextMenu"; -import ContextMenu, {ContextMenuButton, DangerContextMenuButton} from "../ui/ContextMenu"; +import ContextMenu, { + ContextMenuButton, + DangerContextMenuButton, +} from "../ui/ContextMenu"; import Code from "../icons/svg/Code"; import Plus from "../icons/svg/Plus"; -import {getIcon} from "../channels/CreateChannelModal"; +import { getIcon } from "../channels/CreateChannelModal"; import Gear from "../icons/svg/Gear"; import FolderPlus from "../icons/svg/FolderPlus"; import BookmarkEmpty from "../icons/svg/BookmarkEmpty"; -import {ReactiveSet} from "@solid-primitives/set"; +import { ReactiveSet } from "@solid-primitives/set"; import ChevronRight from "../icons/svg/ChevronRight"; import tooltip from "../../directives/tooltip"; -import {setShowSidebar} from "../../App"; +import { setShowSidebar } from "../../App"; -void tooltip +void tooltip; interface GuildDropdownButtonProps { - icon: IconElement, - label: string, - groupHoverColor?: string, - svgClass?: string, - labelClass?: string, - onClick?: () => any, - py?: string, + icon: IconElement; + label: string; + groupHoverColor?: string; + svgClass?: string; + labelClass?: string; + onClick?: () => any; + py?: string; } function GuildDropdownButton(props: GuildDropdownButtonProps) { - const svgClasses = "w-4 h-4 " + (props.svgClass ?? "") - const labelClasses = "ml-2 font-medium " + (props.labelClass ?? "") + const svgClasses = "w-4 h-4 " + (props.svgClass ?? ""); + const labelClasses = "ml-2 font-medium " + (props.labelClass ?? ""); const groupHoverClass = props.groupHoverColor ? `hover:bg-${props.groupHoverColor}` - : "hover:bg-accent" + : "hover:bg-accent"; return ( -
  • +
  • {props.label}
  • - ) + ); } interface ChannelProps { - channel: GuildChannel + channel: GuildChannel; } function Channel(props: ChannelProps) { - const api = getApi()! - const cache = api.cache! + const api = getApi()!; + const cache = api.cache!; - const params = useParams() - const navigate = useNavigate() - const guildId = createMemo(() => BigInt(params.guildId)) - const contextMenu = useContextMenu()! + const params = useParams(); + const navigate = useNavigate(); + const guildId = createMemo(() => BigInt(params.guildId)); + const contextMenu = useContextMenu()!; - const isUnread = createMemo(() => cache.isChannelUnread(props.channel.id)) - const mentionCount = createMemo(() => cache.countGuildMentionsIn(guildId(), props.channel.id)) - const permissions = createMemo(() => cache.getClientPermissions(guildId(), props.channel.id)) + const isUnread = createMemo(() => cache.isChannelUnread(props.channel.id)); + const mentionCount = createMemo(() => + cache.countGuildMentionsIn(guildId(), props.channel.id), + ); + const permissions = createMemo(() => + cache.getClientPermissions(guildId(), props.channel.id), + ); const markRead = async () => { - const lastMessageId = cache.lastMessages.get(props.channel.id)?.id + const lastMessageId = cache.lastMessages.get(props.channel.id)?.id; if (lastMessageId) { - await api.request('PUT', `/channels/${props.channel.id}/ack/${lastMessageId}`) + await api.request( + "PUT", + `/channels/${props.channel.id}/ack/${lastMessageId}`, + ); } - } - const active = () => params.channelId === props.channel.id.toString() - const settings = () => `/guilds/${guildId()}/${props.channel.id}/settings` + }; + const active = () => params.channelId === props.channel.id.toString(); + const settings = () => `/guilds/${guildId()}/${props.channel.id}/settings`; - const [hovered, setHovered] = createSignal(false) - const {showModal} = useModal() + const [hovered, setHovered] = createSignal(false); + const { showModal } = useModal(); return ( - + window.navigator.clipboard.writeText(props.channel.id.toString())} + onClick={() => + window.navigator.clipboard.writeText(props.channel.id.toString()) + } /> - - navigate(settings())} + + navigate(settings())} /> - + showModal(ModalId.DeleteChannel, props.channel)} /> - + , )} onMouseEnter={() => setHovered(true)} onMouseLeave={() => setHovered(false)} onClick={() => { - if (window.innerWidth < 768) setShowSidebar(false) + if (window.innerWidth < 768) setShowSidebar(false); }} > {props.channel.name} - - + + + + + + - - -
    + + +
    {mentionCount()?.toLocaleString()}
    - - +
    + - - + + - ) + ); } -function GuildMajorLink(props: { icon: IconElement, label: string, href: string, active: boolean }) { +function GuildMajorLink(props: { + icon: IconElement; + label: string; + href: string; + active: boolean; +}) { return ( - +
    - + {props.label}
    - ) + ); } export default function GuildSidebar() { - const params = useParams() - const navigate = useNavigate() - const guildId = createMemo(() => BigInt(params.guildId)) - const channelId = createMemo(() => params.channelId && BigInt(params.channelId)) - const contextMenu = useContextMenu()! - const {showModal} = useModal() + const params = useParams(); + const navigate = useNavigate(); + const guildId = createMemo(() => BigInt(params.guildId)); + const channelId = createMemo( + () => params.channelId && BigInt(params.channelId), + ); + const contextMenu = useContextMenu()!; + const { showModal } = useModal(); - const api = getApi()! - const guild = createMemo(() => api.cache!.guilds.get(guildId())!) - if (!guild()) return + const api = getApi()!; + const guild = createMemo(() => api.cache!.guilds.get(guildId())!); + if (!guild()) return; - const [dropdownExpanded, setDropdownExpanded] = createSignal(false) - const isOwner = createMemo(() => guild().owner_id === api.cache?.clientUser?.id) + const [dropdownExpanded, setDropdownExpanded] = createSignal(false); + const isOwner = createMemo( + () => guild().owner_id === api.cache?.clientUser?.id, + ); - let dropdownRef: HTMLUListElement | undefined - let toggleRef: HTMLDivElement | undefined + let dropdownRef: HTMLUListElement | undefined; + let toggleRef: HTMLDivElement | undefined; onMount(() => { const handleClick = (e: MouseEvent) => { - if (!dropdownExpanded()) return + if (!dropdownExpanded()) return; if ( !dropdownRef?.contains(e.target as Node) && !toggleRef?.contains(e.target as Node) ) { - setDropdownExpanded(false) + setDropdownExpanded(false); } - } + }; - document.addEventListener("click", handleClick) - onCleanup(() => document.removeEventListener("click", handleClick)) - }) + document.addEventListener("click", handleClick); + onCleanup(() => document.removeEventListener("click", handleClick)); + }); const BaseContextMenu = () => ( - + showModal(ModalId.CreateInvite, guild())} /> - ) - const guildPermissions = createMemo(() => api.cache?.getClientPermissions(guildId())) + ); + const guildPermissions = createMemo(() => + api.cache?.getClientPermissions(guildId()), + ); const channels = createMemo(() => { const channels = api.cache?.guildChannelReactor ?.get(guildId()) - ?.map(id => api.cache!.channels.get(id) as GuildChannel) - ?.filter(c => c && api.cache?.getClientPermissions(guildId(), c.id).has('VIEW_CHANNEL')) - if (!channels) return + ?.map((id) => api.cache!.channels.get(id) as GuildChannel) + ?.filter( + (c) => + c && + api.cache?.getClientPermissions(guildId(), c.id).has("VIEW_CHANNEL"), + ); + if (!channels) return; - const groups = new Map([ + const groups = new Map< + bigint | null, + { category: GuildChannel; children: GuildChannel[] } + >([ [null, { category: null, children: [] }] as any, ...channels - ?.filter(c => c.type === 'category') - ?.map(c => [c.id, { category: c, children: [] }]) - ]) - channels.forEach(c => groups.get(c.parent_id ? BigInt(c.parent_id) : null)?.children.push(c)) - groups.forEach(group => group.children.sort((a, b) => { - const aIsCategory = a.type === 'category' - const bIsCategory = b.type === 'category' - if (aIsCategory && !bIsCategory) return 1 - if (!aIsCategory && bIsCategory) return -1 - return a.position - b.position - })) - return groups - }) - const collapsed = new ReactiveSet() + ?.filter((c) => c.type === "category") + ?.map((c) => [c.id, { category: c, children: [] }]), + ]); + channels.forEach((c) => + groups.get(c.parent_id ? BigInt(c.parent_id) : null)?.children.push(c), + ); + groups.forEach((group) => + group.children.sort((a, b) => { + const aIsCategory = a.type === "category"; + const bIsCategory = b.type === "category"; + if (aIsCategory && !bIsCategory) return 1; + if (!aIsCategory && bIsCategory) return -1; + return a.position - b.position; + }), + ); + return groups; + }); + const collapsed = new ReactiveSet(); - const RenderChannel = (props: { channel: GuildChannel, collapsed?: boolean }) => ( - - - - }> - + const RenderChannel = (props: { + channel: GuildChannel; + collapsed?: boolean; + }) => ( + + + + } + > + - ) - + ); + const RenderCategory = (props: { - id: bigint, group: { category: GuildChannel, children: GuildChannel[] } + id: bigint; + group: { category: GuildChannel; children: GuildChannel[] }; }) => { return ( - 0 - || (props.id ? api.cache!.getClientPermissions(guildId(), props.id) : guildPermissions())?.has('MANAGE_CHANNELS') - }> + 0 || + (props.id + ? api.cache!.getClientPermissions(guildId(), props.id) + : guildPermissions() + )?.has("MANAGE_CHANNELS") + } + >
    window.navigator.clipboard.writeText(props.id.toString())} + onClick={() => + window.navigator.clipboard.writeText(props.id.toString()) + } /> - + showModal(ModalId.DeleteChannel, props.group.category)} + onClick={() => + showModal(ModalId.DeleteChannel, props.group.category) + } /> - + , )} > - -
    - - Empty Category -
    - }> - {(channel) => } + + Empty Category + + } + > + {(channel) => ( + + )} - ) - } + ); + }; return (
    - + showModal(ModalId.CreateChannel, { guildId: guildId() })} + onClick={() => + showModal(ModalId.CreateChannel, { guildId: guildId() }) + } /> showModal(ModalId.CreateCategory, { guildId: guildId() })} + onClick={() => + showModal(ModalId.CreateCategory, { guildId: guildId() }) + } /> - + , )} >
    setDropdownExpanded(prev => !prev)} + classList={{ "min-h-[150px]": !!guild().banner }} + onClick={() => setDropdownExpanded((prev) => !prev)} >
    -
    - +
    + {guild().name} + + + Official + + -
    + {guild().description && (
    -

    {guild().description}

    +

    + {guild().description} +

    )} @@ -375,29 +518,33 @@ export default function GuildSidebar() { tabIndex={0} class="flex flex-col py-1 absolute rounded-b-xl bg-bg-3/60 backdrop-blur inset-x-0 top-full z-[200]" > - + showModal(ModalId.CreateInvite, guild())} /> - + showModal(ModalId.CreateChannel, { guildId: guildId() })} + onClick={() => + showModal(ModalId.CreateChannel, { guildId: guildId() }) + } /> showModal(ModalId.CreateCategory, { guildId: guildId() })} + onClick={() => + showModal(ModalId.CreateCategory, { guildId: guildId() }) + } /> - +
    - - + + {(channel) => }
    - ) + ); }