Skip to content
Snippets Groups Projects
ServerListSidebar.tsx 6.86 KiB
Newer Older
insert's avatar
insert committed
import { Channel, Servers } from "revolt.js/dist/api/objects";
import { Link, useLocation, useParams } from "react-router-dom";
import { useChannels, useForceUpdate, useServers } from "../../../context/revoltjs/hooks";
import { mapChannelWithUnread } from "./common";
import { Unreads } from "../../../redux/reducers/unreads";
import { connectState } from "../../../redux/connector";
import styled, { css } from "styled-components";
import { Children } from "../../../types/Preact";
import LineDivider from "../../ui/LineDivider";
import ServerIcon from "../../common/ServerIcon";
import PaintCounter from "../../../lib/PaintCounter";
insert's avatar
insert committed
import { attachContextMenu } from 'preact-context-menu';
insert's avatar
insert committed

function Icon({ children, unread, size }: { children: Children, unread?: 'mention' | 'unread', size: number }) {
    return (
        <svg
            width={size}
            height={size}
            aria-hidden="true"
            viewBox="0 0 32 32"
        >
            <foreignObject x="0" y="0" width="32" height="32">
                { children }
            </foreignObject>
            {unread === 'unread' && (
                <circle
                    cx="27"
                    cy="27"
                    r="5"
                    fill={"white"}
                />
            )}
            {unread === 'mention' && (
                <circle
                    cx="27"
                    cy="27"
                    r="5"
                    fill={"red"}
                />
            )}
        </svg>
    )
}

const ServersBase = styled.div`
    width: 52px;
    height: 100%;
    display: flex;
    flex-direction: column;
`;

const ServerList = styled.div`
    flex-grow: 1;
    display: flex;
    overflow-y: scroll;
    padding-bottom: 48px;
    flex-direction: column;
    border-inline-end: 2px solid var(--sidebar-active);

    scrollbar-width: none;

    > :first-child > svg {
        margin: 6px 0 6px 4px;
    }

    &::-webkit-scrollbar {
        width: 0px;
    }
`;

const ServerEntry = styled.div<{ active: boolean, invert?: boolean }>`
    height: 44px;
    padding: 4px;
    margin: 2px 0 2px 4px;

    border-top-left-radius: 4px;
    border-bottom-left-radius: 4px;

    img {
        width: 32px;
        height: 32px;
    }

    ${ props => props.active && css`
        background: var(--sidebar-active);
    ` }

    ${ props => props.active && props.invert && css`
        img {
            filter: saturate(0) brightness(10);
        }
    ` }
`;

interface Props {
    unreads: Unreads;
}

export function ServerListSidebar({ unreads }: Props) {
    const ctx = useForceUpdate();
    const activeServers = useServers(undefined, ctx) as Servers.Server[];
    const channels = (useChannels(undefined, ctx) as Channel[])
        .map(x => mapChannelWithUnread(x, unreads));
    
    const unreadChannels = channels.filter(x => x.unread)
        .map(x => x._id);

    const servers = activeServers.map(server => {
        let alertCount = 0;
        for (let id of server.channels) {
            let channel = channels.find(x => x._id === id);
            if (channel?.alertCount) {
                alertCount += channel.alertCount;
            }
        }

        return {
            ...server,
            unread: (typeof server.channels.find(x => unreadChannels.includes(x)) !== 'undefined' ?
                ( alertCount > 0 ? 'mention' : 'unread' ) : undefined) as 'mention' | 'unread' | undefined,
            alertCount
        }
    });

    const path = useLocation().pathname;
    const { server: server_id } = useParams<{ server?: string }>();
    const server = servers.find(x => x!._id == server_id);

    // const { openScreen } = useContext(IntermediateContext);

    let homeUnread: 'mention' | 'unread' | undefined;
    let alertCount = 0;
    for (let x of channels) {
        if (((x.channel_type === 'DirectMessage' && x.active) || x.channel_type === 'Group') && x.unread) {
            homeUnread = 'unread';
            alertCount += x.alertCount ?? 0;
        }
    }

    if (alertCount > 0) homeUnread = 'mention';

    return (
        <ServersBase>
            <ServerList>
                <Link to={`/`}>
                    <ServerEntry invert
                        active={typeof server === 'undefined' && !path.startsWith('/invite')}>
                        <Icon size={36} unread={homeUnread}>
                            <img src="/assets/app_icon.png"  />
                        </Icon>
                    </ServerEntry>
                </Link>
                <LineDivider />
                {
                    servers.map(entry =>
                        <Link to={`/server/${entry!._id}`}>
                            <ServerEntry
                                active={entry!._id === server?._id}
insert's avatar
insert committed
                                onContextMenu={attachContextMenu('Menu', { server: entry!._id })}>
insert's avatar
insert committed
                                <Icon size={36} unread={entry.unread}>
                                    <ServerIcon size={32} target={entry} />
                                </Icon>
                            </ServerEntry>
                        </Link>
                    )
                }
                <PaintCounter small />
            </ServerList>
        </ServersBase>
insert's avatar
insert committed
        // ! FIXME: add overlay back
insert's avatar
insert committed
        /*<div className={styles.servers}>
            <div className={styles.list}>
                <Link to={`/`}>
                    <div className={styles.entry}
                        data-active={typeof server === 'undefined' && !path.startsWith('/invite')}>
                        <Icon size={36} unread={homeUnread} alertCount={alertCount}>
                            <div className={styles.logo} />
                        </Icon>
                    </div>
                </Link>
                <LineDivider className={styles.divider} />
                {
                    servers.map(entry =>
                        <Link to={`/server/${entry!._id}`}>
                            <div className={styles.entry}
                                data-active={entry!._id === server?._id}
                                onContextMenu={attachContextMenu('Menu', { server: entry!._id })}>
                                <Icon size={36} unread={entry.unread}>
                                    <ServerIcon id={entry!._id} size={32} />
                                </Icon>
                            </div>
                        </Link>
                    )
                }
            </div>
            <div className={styles.overlay}>
                <div className={styles.actions}>
                    <IconButton onClick={() => openScreen({ id: 'special_input', type: 'create_server' })}>
                        <PlusCircle size={36} />
                    </IconButton>
                </div>
            </div>
            </div> */
    )
}

export default connectState(
    ServerListSidebar,
    state => {
        return {
            unreads: state.unreads
        };
    }
);