1
0
mirror of https://github.com/spacebarchat/client.git synced 2024-11-22 02:12:38 +01:00

virtualized guild list

This commit is contained in:
Puyodead1 2023-09-25 11:35:23 -04:00
parent 11f93c3ad2
commit 8af482cb89
No known key found for this signature in database
GPG Key ID: A4FA4FEC0DD353FC
10 changed files with 109 additions and 72 deletions

View File

@ -15,12 +15,10 @@ import SidebarPill, { PillType } from "./SidebarPill";
import Tooltip from "./Tooltip";
import CreateInviteModal from "./modals/CreateInviteModal";
export const GuildSidebarListItem = styled.li`
export const GuildSidebarListItem = styled.div`
position: relative;
margin: 0 0 8px;
display: flex;
justify-content: center;
width: 72px;
cursor: pointer;
`;
@ -81,6 +79,13 @@ function GuildItem({ guild, active }: Props) {
},
]);
React.useEffect(() => {
if (app.activeChannelId && app.activeGuildId === guild.id) return setPillType("active");
else if (isHovered) return setPillType("hover");
// TODO: unread
else return setPillType("none");
}, [app.activeGuildId, isHovered]);
const doNavigate = () => {
const channel = guild.channels.find((x) => {
const permission = Permissions.getPermission(app.account!.id, guild, x);
@ -89,13 +94,6 @@ function GuildItem({ guild, active }: Props) {
navigate(`/channels/${guild.id}${channel ? `/${channel.id}` : ""}`);
};
React.useEffect(() => {
if (active) return setPillType("active");
else if (isHovered) return setPillType("hover");
// TODO: unread
else return setPillType("none");
}, [active, isHovered]);
return (
<GuildSidebarListItem
onContextMenu={(e) => {

View File

@ -2,24 +2,31 @@ import { useModals } from "@mattjennings/react-modal-stack";
import { observer } from "mobx-react-lite";
import React from "react";
import { useNavigate } from "react-router-dom";
import { AutoSizer, List, ListRowProps } from "react-virtualized";
import styled from "styled-components";
import { useAppStore } from "../stores/AppStore";
import Guild from "../stores/objects/Guild";
import GuildItem, { GuildSidebarListItem } from "./GuildItem";
import SidebarAction from "./SidebarAction";
import AddServerModal from "./modals/AddServerModal";
const List = styled.ul`
list-style: none;
padding: 0;
const Container = styled.div`
display: flex;
flex-direction: column;
flex: 0 0 48px;
align-items: center;
flex: 0 0 72px;
margin: 4px 0 0 0;
@media (max-width: 560px) {
display: none;
}
.ReactVirtualized__List {
scrollbar-width: none; /* Firefox */
-ms-overflow-style: none; /* Internet Explorer 10+ */
&::-webkit-scrollbar {
width: 0;
height: 0;
}
}
`;
const Divider = styled.div`
@ -31,49 +38,82 @@ const Divider = styled.div`
function GuildSidebar() {
const app = useAppStore();
const { openModal } = useModals();
const navigate = useNavigate();
const { all } = app.guilds;
const itemCount = all.length + 3; // add the home button, divider, and add server button
const renderGuildItem = React.useCallback((guild: Guild, active: boolean) => {
return <GuildItem key={guild.id} guild={guild} active={active} />;
}, []);
const rowRenderer = ({ index, key, style }: ListRowProps) => {
let element: React.ReactNode;
if (index === 0) {
// home button
element = (
<SidebarAction
key="home"
tooltip="Home"
icon={{
icon: "mdiHome",
size: "24px",
}}
action={() => navigate("/channels/@me")}
margin={false}
active={app.activeGuildId === "@me"}
/>
);
} else if (index === 1) {
// divider
element = (
<GuildSidebarListItem>
<Divider key="divider" />
</GuildSidebarListItem>
);
} else if (index === itemCount - 1) {
// add server button/any other end items
element = (
<SidebarAction
key="add-server"
tooltip="Add Server"
icon={{
icon: "mdiPlus",
size: "24px",
color: "var(--success)",
}}
action={() => {
openModal(AddServerModal);
}}
margin={false}
disablePill
useGreenColorScheme
/>
);
} else {
// regular guild item
const guild = all[index - 2];
element = <GuildItem key={key} guild={guild} />;
}
return <div style={style}>{element}</div>;
};
return (
<List>
<SidebarAction
key="home"
tooltip="Home"
icon={{
icon: "mdiHome",
size: "24px",
}}
action={() => navigate("/channels/@me")}
margin={false}
active={app.activeGuildId === "@me"}
/>
<GuildSidebarListItem>
<Divider key="divider" />
</GuildSidebarListItem>
<div aria-label="Servers">
{app.guilds.getAll().map((guild) => renderGuildItem(guild, guild.id === app.activeGuildId))}
</div>
<SidebarAction
key="add-server"
tooltip="Add Server"
icon={{
icon: "mdiPlus",
size: "24px",
color: "var(--success)",
}}
action={() => {
openModal(AddServerModal);
}}
margin={false}
disablePill
useGreenColorScheme
/>
</List>
<Container>
<AutoSizer>
{({ width, height }) => (
<List
height={height}
overscanRowCount={2}
rowCount={itemCount}
rowHeight={({ index }) => {
if (index === 1) return 8; // divider
return 56; // item is 48 + 8 top margin
}}
rowRenderer={rowRenderer}
width={width}
/>
)}
</AutoSizer>
</Container>
);
}

View File

@ -33,10 +33,8 @@ function ChannelPage() {
const { guildId, channelId } = useParams<{ guildId: string; channelId: string }>();
React.useEffect(() => {
if (guildId && channelId) {
app.setActiveGuildId(guildId);
app.setActiveChannelId(channelId);
}
app.setActiveGuildId(guildId);
app.setActiveChannelId(channelId);
}, [guildId, channelId]);
return (

View File

@ -46,9 +46,9 @@ export default class AppStore {
@observable presences = new PresenceStore(this);
@observable queue = new MessageQueue(this);
@observable activeGuild: Guild | null = null;
@observable activeGuildId: Snowflake | null | "@me" = "@me";
@observable activeGuildId: Snowflake | undefined | "@me" = "@me";
@observable activeChannel: Channel | null = null;
@observable activeChannelId: string | null = null;
@observable activeChannelId: string | undefined = undefined;
constructor() {
makeAutoObservable(this);
@ -118,7 +118,7 @@ export default class AppStore {
}
@action
setActiveGuildId(id: Snowflake | null | "@me") {
setActiveGuildId(id: Snowflake | undefined | "@me") {
this.activeGuildId = id;
// try to resolve the guild
@ -126,7 +126,7 @@ export default class AppStore {
}
@action
setActiveChannelId(id: string | null) {
setActiveChannelId(id: string | undefined) {
this.activeChannelId = id;
// try to resolve the channel

View File

@ -25,7 +25,8 @@ export default class ChannelStore {
return this.channels.get(id);
}
getAll() {
@computed
get all() {
return Array.from(this.channels.values());
}

View File

@ -35,7 +35,7 @@ export default class GuildStore {
}
@computed
getAll() {
get all() {
return Array.from(this.guilds.values());
}

View File

@ -26,7 +26,7 @@ export default class PrivateChannelStore {
}
@computed
getAll() {
get all() {
return Array.from(this.channels.values());
}

View File

@ -1,6 +1,6 @@
import type { Snowflake } from "@spacebarchat/spacebar-api-types/globals";
import type { APIRole } from "@spacebarchat/spacebar-api-types/v9";
import { action, makeObservable, observable, ObservableMap } from "mobx";
import { action, computed, makeObservable, observable, ObservableMap } from "mobx";
import AppStore from "./AppStore";
import Role from "./objects/Role";
@ -24,7 +24,8 @@ export default class RoleStore {
roles.forEach((role) => this.add(role));
}
getAll() {
@computed
get all() {
return Array.from(this.roles.values());
}

View File

@ -21,7 +21,7 @@ export default class UserStore {
}
@computed
getAll() {
get all() {
return Array.from(this.users.values());
}

View File

@ -142,15 +142,14 @@ export default class Guild {
@computed
get channels() {
return this.app.channels
.getAll()
return this.app.channels.all
.filter((channel) => this.channels_.has(channel.id))
.sort((a, b) => (a.position ?? 0) - (b.position ?? 0));
}
@computed
get roles() {
return this.app.roles.getAll().filter((role) => this.roles_.has(role.id));
return this.app.roles.all.filter((role) => this.roles_.has(role.id));
}
@action