Skip to content
Snippets Groups Projects
MemberSidebar.tsx 11.7 KiB
Newer Older
insert's avatar
insert committed
/* eslint-disable react-hooks/rules-of-hooks */
insert's avatar
insert committed
import { observer } from "mobx-react-lite";
import { Link, useParams } from "react-router-dom";
insert's avatar
insert committed
import { Presence } from "revolt-api/types/Users";
import { Channel } from "revolt.js/dist/maps/Channels";
import { Message } from "revolt.js/dist/maps/Messages";
insert's avatar
insert committed

import { Text } from "preact-i18n";
import { useContext, useEffect, useState } from "preact/hooks";

import { getState } from "../../../redux";

import { useIntermediate } from "../../../context/intermediate/Intermediate";
insert's avatar
insert committed
import {
    ClientStatus,
    StatusContext,
insert's avatar
insert committed
    useClient,
insert's avatar
insert committed
} from "../../../context/revoltjs/RevoltClient";
import CollapsibleSection from "../../common/CollapsibleSection";
insert's avatar
insert committed
import Button from "../../ui/Button";
insert's avatar
insert committed
import Category from "../../ui/Category";
import InputBox from "../../ui/InputBox";
import Preloader from "../../ui/Preloader";
insert's avatar
insert committed
import placeholderSVG from "../items/placeholder.svg";

import { GenericSidebarBase, GenericSidebarList } from "../SidebarBase";
import { UserButton } from "../items/ButtonItem";
import { ChannelDebugInfo } from "./ChannelDebugInfo";
export default function MemberSidebar({ channel: obj }: { channel?: Channel }) {
    const { channel: channel_id } = useParams<{ channel: string }>();
    const client = useClient();
    const channel = obj ?? client.channels.get(channel_id);

    switch (channel?.channel_type) {
        case "Group":
insert's avatar
insert committed
            return <GroupMemberSidebar channel={channel} />;
        case "TextChannel":
insert's avatar
insert committed
            return <ServerMemberSidebar channel={channel} />;
        default:
            return null;
    }
insert's avatar
insert committed
export const GroupMemberSidebar = observer(
insert's avatar
insert committed
    ({ channel }: { channel: Channel }) => {
insert's avatar
insert committed
        const { openScreen } = useIntermediate();

insert's avatar
insert committed
        const members = channel.recipients?.filter(
            (x) => typeof x !== "undefined",
        );
insert's avatar
insert committed

        /*const voice = useContext(VoiceContext);
    const voiceActive = voice.roomId === channel._id;

    let voiceParticipants: User[] = [];
    if (voiceActive) {
        const idArray = Array.from(voice.participants.keys());
        voiceParticipants = idArray
            .map(x => users.find(y => y?._id === x))
            .filter(x => typeof x !== "undefined") as User[];

        members = members.filter(member => idArray.indexOf(member._id) === -1);

        voiceParticipants.sort((a, b) => a.username.localeCompare(b.username));
    }*/

insert's avatar
insert committed
        members?.sort((a, b) => {
insert's avatar
insert committed
            // ! FIXME: should probably rewrite all this code
            const l =
                +(
insert's avatar
insert committed
                    (a!.online && a!.status?.presence !== Presence.Invisible) ??
insert's avatar
insert committed
                    false
                ) | 0;
            const r =
                +(
insert's avatar
insert committed
                    (b!.online && b!.status?.presence !== Presence.Invisible) ??
insert's avatar
insert committed
                    false
                ) | 0;
insert's avatar
insert committed
            const n = r - l;
            if (n !== 0) {
                return n;
            }
insert's avatar
insert committed
            return a!.username.localeCompare(b!.username);
insert's avatar
insert committed
        });

        return (
            <GenericSidebarBase>
                <GenericSidebarList>
                    <ChannelDebugInfo id={channel._id} />
insert's avatar
insert committed
                    <Search channel={channel} />
insert's avatar
insert committed

                    {/*voiceActive && voiceParticipants.length !== 0 && (
                    <Fragment>
                        <Category
                            type="members"
                            text={
                                <span>
                                    <Text id="app.main.categories.participants" />{" "}
                                    — {voiceParticipants.length}
                                </span>
                            }
                        />
                        {voiceParticipants.map(
                            user =>
                                user && (
                                    <LinkProfile user_id={user._id}>
                                        <UserButton
                                            key={user._id}
                                            user={user}
                                            context={channel}
                                        />
                                    </LinkProfile>
                                )
                        )}
                    </Fragment>
                )*/}
                    <CollapsibleSection
insert's avatar
insert committed
                        sticky
                        id="members"
                        defaultValue
insert's avatar
insert committed
                            <Category
                                variant="uniform"
                                text={
                                    <span>
                                        <Text id="app.main.categories.members" />{" "}
insert's avatar
insert committed
{channel.recipients?.length ?? 0}
insert's avatar
insert committed
                                    </span>
                                }
                            />
insert's avatar
insert committed
                        {members?.length === 0 && (
                            <img src={placeholderSVG} loading="eager" />
                        )}
insert's avatar
insert committed
                        {members?.map(
                            (user) =>
                                user && (
                                    <UserButton
                                        key={user._id}
                                        user={user}
insert's avatar
insert committed
                                        context={channel!}
                                        onClick={() =>
                                            openScreen({
                                                id: "profile",
                                                user_id: user._id,
                                            })
                                        }
                                    />
                                ),
                        )}
                    </CollapsibleSection>
insert's avatar
insert committed
                </GenericSidebarList>
            </GenericSidebarBase>
        );
    },
);

export const ServerMemberSidebar = observer(
insert's avatar
insert committed
    ({ channel }: { channel: Channel }) => {
insert's avatar
insert committed
        const client = useClient();
insert's avatar
insert committed
        const { openScreen } = useIntermediate();
        const status = useContext(StatusContext);

        useEffect(() => {
insert's avatar
insert committed
            if (status === ClientStatus.ONLINE) {
                channel.server!.fetchMembers();
insert's avatar
insert committed
        }, [status, channel.server]);
insert's avatar
insert committed
        const users = [...client.members.keys()]
insert's avatar
insert committed
            .map((x) => JSON.parse(x))
insert's avatar
insert committed
            .filter((x) => x.server === channel.server_id)
            .map((y) => client.users.get(y.user)!)
            .filter((z) => typeof z !== "undefined");
insert's avatar
insert committed

        // copy paste from above
insert's avatar
insert committed
        users.sort((a, b) => {
insert's avatar
insert committed
            // ! FIXME: should probably rewrite all this code
            const l =
                +(
insert's avatar
insert committed
                    (a.online && a.status?.presence !== Presence.Invisible) ??
insert's avatar
insert committed
                    false
                ) | 0;
            const r =
                +(
insert's avatar
insert committed
                    (b.online && b.status?.presence !== Presence.Invisible) ??
insert's avatar
insert committed
                    false
                ) | 0;

            const n = r - l;
            if (n !== 0) {
                return n;
            }

            return a.username.localeCompare(b.username);
        });

        return (
            <GenericSidebarBase>
                <GenericSidebarList>
                    <ChannelDebugInfo id={channel._id} />
insert's avatar
insert committed
                    <Search channel={channel} />
                    <div>{users.length === 0 && <Preloader type="ring" />}</div>
                    {users.length > 0 && (
insert's avatar
insert committed
                        <CollapsibleSection
                            //sticky //will re-add later, need to fix css
                            id="members"
                            defaultValue
                            summary={
                                <span>
                                    <Text id="app.main.categories.members" />{" "}
                                    {users?.length ?? 0}
                                </span>
                            }>
insert's avatar
insert committed
                            {users.map(
insert's avatar
insert committed
                                (user) =>
                                    user && (
                                        <UserButton
                                            key={user._id}
                                            user={user}
                                            context={channel}
                                            onClick={() =>
                                                openScreen({
                                                    id: "profile",
                                                    user_id: user._id,
                                                })
                                            }
                                        />
                                    ),
                            )}
                        </CollapsibleSection>
                    )}
                </GenericSidebarList>
            </GenericSidebarBase>
        );
    },
);
insert's avatar
insert committed
function Search({ channel }: { channel: Channel }) {
    if (!getState().experiments.enabled?.includes("search")) return null;
insert's avatar
insert committed
    type Sort = "Relevance" | "Latest" | "Oldest";
    const [sort, setSort] = useState<Sort>("Relevance");

    const [query, setV] = useState("");
    const [results, setResults] = useState<Message[]>([]);

    async function search() {
insert's avatar
insert committed
        const data = await channel.searchWithUsers({ query, sort });
        setResults(data.messages);
    }

    return (
        <CollapsibleSection
            sticky
            id="search"
            defaultValue={false}
insert's avatar
insert committed
            summary={
                <>
                    <Text id="app.main.channel.search.title" /> (BETA)
                </>
            }>
            <div style={{ display: "flex" }}>
                {["Relevance", "Latest", "Oldest"].map((key) => (
                    <Button
insert's avatar
insert committed
                        key={key}
insert's avatar
insert committed
                        style={{ flex: 1, minWidth: 0 }}
                        compact
                        error={sort === key}
                        onClick={() => setSort(key as Sort)}>
                        <Text
                            id={`app.main.channel.search.sort.${key.toLowerCase()}`}
                        />
                    </Button>
                ))}
            </div>
            <InputBox
                style={{ width: "100%" }}
                onKeyDown={(e) => e.key === "Enter" && search()}
                value={query}
                onChange={(e) => setV(e.currentTarget.value)}
            />
            <div
                style={{
                    display: "flex",
                    flexDirection: "column",
                    gap: "4px",
                    marginTop: "8px",
                }}>
                {results.map((message) => {
                    let href = "";
                    if (channel?.channel_type === "TextChannel") {
insert's avatar
insert committed
                        href += `/server/${channel.server_id}`;
insert's avatar
insert committed
                    href += `/channel/${message.channel_id}/${message._id}`;
insert's avatar
insert committed
                        <Link to={href} key={message._id}>
                            <div
                                style={{
                                    margin: "2px",
                                    padding: "6px",
                                    background: "var(--primary-background)",
                                }}>
insert's avatar
insert committed
                                <b>@{message.author?.username}</b>
                                <br />
                                {message.content}
                            </div>
                        </Link>
                    );
                })}
            </div>
        </CollapsibleSection>