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

move channel and role storage to app root

This commit is contained in:
Puyodead1 2023-09-23 16:35:26 -04:00
parent 711e79cdfd
commit 81b57c4ebb
No known key found for this signature in database
GPG Key ID: A4FA4FEC0DD353FC
13 changed files with 151 additions and 109 deletions

View File

@ -21,11 +21,10 @@ export function EmptyChannelList() {
}
interface Props {
channelId?: string;
guild: Guild;
}
function ChannelList({ channelId, guild }: Props) {
function ChannelList({ guild }: Props) {
const app = useAppStore();
const renderChannelListItem = React.useCallback(
@ -33,7 +32,7 @@ function ChannelList({ channelId, guild }: Props) {
const permission = Permissions.getPermission(app.account!.id, guild, channel);
if (!permission.has("VIEW_CHANNEL")) return null;
const active = channelId === channel.id;
const active = app.activeChannelId === channel.id;
const isCategory = channel.type === ChannelType.GuildCategory;
return (
<ChannelListItem
@ -45,10 +44,10 @@ function ChannelList({ channelId, guild }: Props) {
/>
);
},
[app.account, channelId, guild],
[app.account, app.activeChannelId, guild],
);
return <List>{guild.channels.mapped.map((channel) => renderChannelListItem(channel))}</List>;
return <List>{guild.channelsMapped.map((channel) => renderChannelListItem(channel))}</List>;
}
export default observer(ChannelList);

View File

@ -1,7 +1,6 @@
import { observer } from "mobx-react-lite";
import styled from "styled-components";
import Channel from "../stores/objects/Channel";
import Guild from "../stores/objects/Guild";
import { useAppStore } from "../stores/AppStore";
import ChannelHeader from "./ChannelHeader";
import ChannelList, { EmptyChannelList } from "./ChannelList";
import Container from "./Container";
@ -18,19 +17,14 @@ const Wrapper = styled(Container)`
}
`;
interface Props {
channel?: Channel;
guild?: Guild;
guildId?: string;
channelId?: string;
}
function ChannelSidebar() {
const app = useAppStore();
function ChannelSidebar({ guild, channelId, guildId }: Props) {
return (
<Wrapper>
{/* TODO: replace with dm search if no guild */}
<ChannelHeader guild={guild} guildId={guildId} />
{guild ? <ChannelList channelId={channelId} guild={guild} /> : <EmptyChannelList />}
<ChannelHeader />
{app.activeGuild ? <ChannelList guild={app.activeGuild} /> : <EmptyChannelList />}
<UserPanel />
</Wrapper>
);

View File

@ -82,7 +82,7 @@ function GuildItem({ guild, active }: Props) {
]);
const doNavigate = () => {
const channel = guild.channels.mapped.find((x) => {
const channel = guild.channelsMapped.find((x) => {
const permission = Permissions.getPermission(app.account!.id, guild, x);
return permission.has("VIEW_CHANNEL") && x.type !== ChannelType.GuildCategory;
});

View File

@ -29,11 +29,7 @@ const Divider = styled.div`
background-color: var(--text-disabled);
`;
interface Props {
guildId: string;
}
function GuildSidebar({ guildId }: Props) {
function GuildSidebar() {
const app = useAppStore();
const { openModal } = useModals();
const navigate = useNavigate();
@ -53,13 +49,13 @@ function GuildSidebar({ guildId }: Props) {
}}
action={() => navigate("/channels/@me")}
margin={false}
active={guildId === "@me"}
active={app.activeGuildId === "@me"}
/>
<GuildSidebarListItem>
<Divider key="divider" />
</GuildSidebarListItem>
<div aria-label="Servers">
{app.guilds.getAll().map((guild) => renderGuildItem(guild, guild.id === guildId))}
{app.guilds.getAll().map((guild) => renderGuildItem(guild, guild.id === app.activeGuildId))}
</div>
<SidebarAction

View File

@ -75,19 +75,19 @@ function Content(props: Props2) {
/**
* Main component for rendering channel messages
*/
function Chat({ channel, guild, guildId }: Props) {
function Chat() {
const app = useAppStore();
const logger = useLogger("Messages");
React.useEffect(() => {
if (!channel || !guild) return;
if (!app.activeChannel || !app.activeGuild || app.activeChannelId === "@me") return;
runInAction(() => {
app.gateway.onChannelOpen(guild.id, channel.id);
app.gateway.onChannelOpen(app.activeGuildId!, app.activeChannelId!);
});
}, [channel, guild]);
}, [app.activeChannel, app.activeGuild]);
if (guildId && guildId === "@me") {
if (app.activeGuildId && app.activeGuildId === "@me") {
return (
<WrapperTwo>
<span>Home Section Placeholder</span>
@ -95,7 +95,7 @@ function Chat({ channel, guild, guildId }: Props) {
);
}
if (!guild || !channel) {
if (!app.activeGuild || !app.activeChannel) {
return (
<WrapperTwo>
<span
@ -113,8 +113,8 @@ function Chat({ channel, guild, guildId }: Props) {
return (
<WrapperTwo>
<ChatHeader channel={channel} />
<Content channel={channel} guild={guild} />
<ChatHeader channel={app.activeChannel} />
<Content channel={app.activeChannel} guild={app.activeGuild} />
</WrapperTwo>
);
}

View File

@ -30,22 +30,25 @@ function ChannelPage() {
const contextMenu = React.useContext(ContextMenuContext);
const bannerContext = React.useContext(BannerContext);
const { guildId, channelId } = useParams<{
guildId: string;
channelId: string;
}>();
const guild = app.guilds.get(guildId!);
const channel = guild?.channels.get(channelId!);
const { guildId, channelId } = useParams<{ guildId: string; channelId: string }>();
React.useEffect(() => {
console.log(guildId, channelId);
if (guildId && channelId) {
app.setActiveGuildId(guildId);
app.setActiveChannelId(channelId);
}
}, [guildId, channelId]);
return (
<Container>
<Banner />
<Wrapper>
{contextMenu.visible && <ContextMenu {...contextMenu} />}
<GuildSidebar guildId={guildId!} />
<ChannelSidebar channel={channel} guild={guild} channelId={channelId} guildId={guildId} />
<GuildSidebar />
<ChannelSidebar />
<ErrorBoundary section="component">
<Chat channel={channel} guild={guild} channelId={channelId} guildId={guildId} />
<Chat />
</ErrorBoundary>
</Wrapper>
</Container>

View File

@ -1,17 +1,21 @@
import type { APIUser } from "@spacebarchat/spacebar-api-types/v9";
import type { APIUser, Snowflake } from "@spacebarchat/spacebar-api-types/v9";
import { action, computed, makeAutoObservable, observable } from "mobx";
import secureLocalStorage from "react-secure-storage";
import Logger from "../utils/Logger";
import REST from "../utils/REST";
import AccountStore from "./AccountStore";
import ChannelStore from "./ChannelStore";
import ExperimentsStore from "./ExperimentsStore";
import GatewayConnectionStore from "./GatewayConnectionStore";
import GuildStore from "./GuildStore";
import MessageQueue from "./MessageQueue";
import PresenceStore from "./PresenceStore";
import PrivateChannelStore from "./PrivateChannelStore";
import RoleStore from "./RoleStore";
import ThemeStore from "./ThemeStore";
import UserStore from "./UserStore";
import Channel from "./objects/Channel";
import Guild from "./objects/Guild";
// dev thing to force toggle branding on auth pages for testing.
export const AUTH_NO_BRANDING = false;
@ -33,13 +37,18 @@ export default class AppStore {
@observable account: AccountStore | null = null;
@observable gateway = new GatewayConnectionStore(this);
@observable guilds = new GuildStore(this);
// @observable channels = new ChannelStore(this);
@observable roles = new RoleStore(this);
@observable channels = new ChannelStore(this);
@observable users = new UserStore();
@observable privateChannels = new PrivateChannelStore(this);
@observable rest = new REST(this);
@observable experiments = new ExperimentsStore();
@observable presences = new PresenceStore(this);
@observable queue = new MessageQueue(this);
@observable activeGuild: Guild | null = null;
@observable activeGuildId: Snowflake | null | "@me" = "@me";
@observable activeChannel: Channel | null = null;
@observable activeChannelId: string | null = null;
constructor() {
makeAutoObservable(this);
@ -107,6 +116,22 @@ export default class AppStore {
get isReady() {
return !this.isAppLoading && this.isGatewayReady && this.isNetworkConnected;
}
@action
setActiveGuildId(id: Snowflake | null | "@me") {
this.activeGuildId = id;
// try to resolve the guild
this.activeGuild = (id ? this.guilds.get(id) : null) ?? null;
}
@action
setActiveChannelId(id: string | null) {
this.activeChannelId = id;
// try to resolve the channel
this.activeChannel = (id ? this.channels.get(id) : null) ?? null;
}
}
export const appStore = new AppStore();

View File

@ -1,5 +1,4 @@
import type { APIChannel } from "@spacebarchat/spacebar-api-types/v9";
import { ChannelType } from "@spacebarchat/spacebar-api-types/v9";
import { action, computed, observable, ObservableMap } from "mobx";
import AppStore from "./AppStore";
import Channel from "./objects/Channel";
@ -26,7 +25,6 @@ export default class ChannelStore {
return this.channels.get(id);
}
@computed
getAll() {
return Array.from(this.channels.values());
}
@ -41,46 +39,7 @@ export default class ChannelStore {
return this.channels.size;
}
private sortPosition(channels: Channel[]) {
sortPosition(channels: Channel[]) {
return channels.sort((a, b) => (a.position ?? 0) - (b.position ?? 0));
}
@computed
get mapped(): Channel[] {
const channels = this.getAll();
const result: {
id: string;
children: Channel[];
category: Channel | null;
}[] = [];
const categories = this.sortPosition(channels.filter((x) => x.type === ChannelType.GuildCategory));
const categorizedChannels = channels.filter((x) => x.type !== ChannelType.GuildCategory && x.parentId !== null);
const uncategorizedChannels = this.sortPosition(
channels.filter((x) => x.type !== ChannelType.GuildCategory && x.parentId === null),
);
// for each category, add an object containing the category and its children
categories.forEach((category) => {
result.push({
id: category.id,
children: this.sortPosition(categorizedChannels.filter((x) => x.parentId === category.id)),
category: category,
});
});
// add an object containing the remaining uncategorized channels
result.push({
id: "root",
children: uncategorizedChannels,
category: null,
});
// flatten down to a single array where the category is the first element followed by its children
return result
.map((x) => [x.category, ...x.children])
.flat()
.filter((x) => x !== null) as Channel[];
}
}

View File

@ -559,7 +559,7 @@ export default class GatewayConnectionStore {
this.logger.warn(`[ChannelCreate] Guild ${data.guild_id} not found for channel ${data.id}`);
return;
}
guild.channels.add(data);
guild.addChannel(data);
};
private onChannelDelete = (data: GatewayChannelDeleteDispatchData) => {
@ -573,7 +573,7 @@ export default class GatewayConnectionStore {
this.logger.warn(`[ChannelDelete] Guild ${data.guild_id} not found for channel ${data.id}`);
return;
}
guild.channels.remove(data.id);
guild.removeChannel(data.id);
};
private onMessageCreate = (data: GatewayMessageCreateDispatchData) => {
@ -582,7 +582,7 @@ export default class GatewayConnectionStore {
this.logger.warn(`[MessageCreate] Guild ${data.guild_id} not found for channel ${data.id}`);
return;
}
const channel = guild.channels.get(data.channel_id);
const channel = this.app.channels.get(data.channel_id);
if (!channel) {
this.logger.warn(`[MessageCreate] Channel ${data.channel_id} not found for message ${data.id}`);
return;
@ -598,7 +598,7 @@ export default class GatewayConnectionStore {
this.logger.warn(`[MessageUpdate] Guild ${data.guild_id} not found for channel ${data.id}`);
return;
}
const channel = guild.channels.get(data.channel_id);
const channel = this.app.channels.get(data.channel_id);
if (!channel) {
this.logger.warn(`[MessageUpdate] Channel ${data.channel_id} not found for message ${data.id}`);
return;
@ -613,7 +613,7 @@ export default class GatewayConnectionStore {
this.logger.warn(`[MessageDelete] Guild ${data.guild_id} not found for channel ${data.id}`);
return;
}
const channel = guild.channels.get(data.channel_id);
const channel = this.app.channels.get(data.channel_id);
if (!channel) {
this.logger.warn(`[MessageDelete] Channel ${data.channel_id} not found for message ${data.id}`);
return;
@ -632,7 +632,7 @@ export default class GatewayConnectionStore {
this.logger.warn(`[TypingStart] Guild ${data.guild_id} not found for channel ${data.channel_id}`);
return;
}
const channel = guild.channels.get(data.channel_id);
const channel = this.app.channels.get(data.channel_id);
if (!channel) {
this.logger.warn(`[TypingStart] Channel ${data.channel_id} not found`);
return;

View File

@ -56,7 +56,7 @@ export default class GuildMemberListStore {
for (const item of items) {
if ("group" in item) {
const role = this.guild.roles.get(item.group.id);
const role = this.app.roles.get(item.group.id);
listData.push({
title: `${(role?.name ?? item.group.id).toUpperCase()}`,

View File

@ -24,6 +24,10 @@ export default class RoleStore {
roles.forEach((role) => this.add(role));
}
getAll() {
return Array.from(this.roles.values());
}
@action
remove(id: Snowflake) {
this.roles.delete(id);

View File

@ -1,15 +1,16 @@
import type { Snowflake } from "@spacebarchat/spacebar-api-types/globals";
import type {
APIGuild,
GatewayGuild,
GatewayGuildMemberListUpdateDispatchData,
import {
ChannelType,
type APIChannel,
type APIGuild,
type GatewayGuild,
type GatewayGuildMemberListUpdateDispatchData,
} from "@spacebarchat/spacebar-api-types/v9";
import { action, computed, makeObservable, observable } from "mobx";
import { ObservableSet, action, computed, makeObservable, observable } from "mobx";
import AppStore from "../AppStore";
import ChannelStore from "../ChannelStore";
import GuildMemberListStore from "../GuildMemberListStore";
import GuildMemberStore from "../GuildMemberStore";
import RoleStore from "../RoleStore";
import Channel from "./Channel";
export default class Guild {
private readonly app: AppStore;
@ -19,13 +20,13 @@ export default class Guild {
@observable threads: unknown[];
@observable stickers: unknown[]; // TODO:
@observable stageInstances: unknown[]; // TODO:
@observable roles: RoleStore;
@observable roles_: ObservableSet<Snowflake>;
@observable memberCount: number;
@observable lazy: boolean;
@observable large: boolean;
@observable guildScheduledEvents: unknown[]; // TODO:
@observable emojis: unknown[]; // TODO:
@observable channels: ChannelStore;
@observable channels_: ObservableSet<Snowflake>;
@observable name: string;
@observable description: string | null = null;
@observable icon: string | null = null;
@ -58,8 +59,8 @@ export default class Guild {
constructor(app: AppStore, data: GatewayGuild) {
this.app = app;
this.roles = new RoleStore(app);
this.channels = new ChannelStore(app);
this.roles_ = new ObservableSet();
this.channels_ = new ObservableSet();
this.members = new GuildMemberStore(app, this);
this.id = data.id;
@ -100,11 +101,11 @@ export default class Guild {
this.nsfwLevel = data.properties.nsfw_level;
this.hubType = data.properties.hub_type;
this.roles.addAll(data.roles);
// FIXME: hack to prevent errors after guild creation where channels is undefined
if (data.channels) {
this.channels.addAll(data.channels);
}
app.roles.addAll(data.roles);
app.channels.addAll(data.channels);
data.roles.forEach((role) => this.roles_.add(role.id));
data.channels?.forEach((channel) => this.channels_.add(channel.id));
makeObservable(this);
}
@ -140,4 +141,65 @@ export default class Guild {
.map((word) => word.substring(0, 1))
.join("");
}
@computed
get channels() {
return this.app.channels.getAll().filter((channel) => this.channels_.has(channel.id));
}
@computed
get roles() {
return this.app.roles.getAll().filter((role) => this.roles_.has(role.id));
}
@action
addChannel(data: APIChannel) {
this.channels_.add(data.id);
this.app.channels.add(data);
}
@action
removeChannel(id: Snowflake) {
this.channels_.delete(id);
this.app.channels.remove(id);
}
@computed
get channelsMapped(): Channel[] {
const channels = this.channels;
const result: {
id: string;
children: Channel[];
category: Channel | null;
}[] = [];
const categories = this.app.channels.sortPosition(channels.filter((x) => x.type === ChannelType.GuildCategory));
const categorizedChannels = channels.filter((x) => x.type !== ChannelType.GuildCategory && x.parentId !== null);
const uncategorizedChannels = this.app.channels.sortPosition(
channels.filter((x) => x.type !== ChannelType.GuildCategory && x.parentId === null),
);
// for each category, add an object containing the category and its children
categories.forEach((category) => {
result.push({
id: category.id,
children: this.app.channels.sortPosition(categorizedChannels.filter((x) => x.parentId === category.id)),
category: category,
});
});
// add an object containing the remaining uncategorized channels
result.push({
id: "root",
children: uncategorizedChannels,
category: null,
});
// flatten down to a single array where the category is the first element followed by its children
return result
.map((x) => [x.category, ...x.children])
.flat()
.filter((x) => x !== null) as Channel[];
}
}

View File

@ -32,7 +32,7 @@ export default class GuildMember {
this.user = data.user;
this.nick = data.nick;
this.avatar = data.avatar;
this.roles = data.roles.map((role) => guild.roles.get(role)).filter(Boolean) as Role[];
this.roles = data.roles.map((role) => app.roles.get(role)).filter(Boolean) as Role[];
this.joined_at = data.joined_at;
this.premium_since = data.premium_since;
this.deaf = data.deaf;