Refactor channels to use reducer (#207)

This commit is contained in:
Szymon Szlachtowicz 2022-01-28 14:01:53 +01:00 committed by GitHub
parent 9cfba1f50c
commit fc2d65f202
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 161 additions and 107 deletions

View File

@ -47,10 +47,11 @@ export function ActivityMessage({
activity, activity,
setShowActivityCenter, setShowActivityCenter,
}: ActivityMessageProps) { }: ActivityMessageProps) {
const { contacts, setActiveChannel } = useMessengerContext(); const { contacts, channelsDispatch } = useMessengerContext();
const { setModal } = useModal(ProfileModalName); const { setModal } = useModal(ProfileModalName);
const showChannel = () => { const showChannel = () => {
activity.channel && setActiveChannel(activity.channel), activity.channel &&
channelsDispatch({ type: "ChangeActive", payload: activity.channel.id }),
setShowActivityCenter(false); setShowActivityCenter(false);
}; };

View File

@ -59,6 +59,7 @@ export function Channel({
setEditGroup, setEditGroup,
}: ChannelProps) { }: ChannelProps) {
const narrow = useNarrow(); const narrow = useNarrow();
const { channelsDispatch } = useMessengerContext();
return ( return (
<ChannelWrapper <ChannelWrapper
@ -79,7 +80,11 @@ export function Channel({
notified={notified} notified={notified}
/> />
{channel?.isMuted && activeView && !narrow && ( {channel?.isMuted && activeView && !narrow && (
<MutedBtn onClick={() => (channel.isMuted = !channel.isMuted)}> <MutedBtn
onClick={() =>
channelsDispatch({ type: "ToggleMuted", payload: channel.id })
}
>
<MutedIcon /> <MutedIcon />
<Tooltip tip="Unmute" className="muted" /> <Tooltip tip="Unmute" className="muted" />
</MutedBtn> </MutedBtn>
@ -90,7 +95,7 @@ export function Channel({
)} )}
</ChannelTextInfo> </ChannelTextInfo>
</ChannelInfo> </ChannelInfo>
{!activeView && !!mention && mention > 0 && !channel?.isMuted && ( {!activeView && !!mention && !channel?.isMuted && (
<NotificationBagde>{mention}</NotificationBagde> <NotificationBagde>{mention}</NotificationBagde>
)} )}
{channel?.isMuted && !activeView && <MutedIcon />} {channel?.isMuted && !activeView && <MutedIcon />}

View File

@ -1,4 +1,4 @@
import React from "react"; import React, { useMemo } from "react";
import styled from "styled-components"; import styled from "styled-components";
import { ChatState, useChatState } from "../../contexts/chatStateProvider"; import { ChatState, useChatState } from "../../contexts/chatStateProvider";
@ -23,12 +23,15 @@ function GenerateChannels({
onCommunityClick, onCommunityClick,
setEditGroup, setEditGroup,
}: GenerateChannelsProps) { }: GenerateChannelsProps) {
const { mentions, notifications, activeChannel, setActiveChannel, channels } = const { mentions, notifications, activeChannel, channelsDispatch, channels } =
useMessengerContext(); useMessengerContext();
const channelList = useMemo(() => Object.values(channels), [channels]);
const setChatState = useChatState()[1]; const setChatState = useChatState()[1];
return ( return (
<> <>
{Object.values(channels) {channelList
.filter((channel) => channel.type === type) .filter((channel) => channel.type === type)
.map((channel) => ( .map((channel) => (
<Channel <Channel
@ -38,7 +41,7 @@ function GenerateChannels({
notified={notifications?.[channel.id] > 0} notified={notifications?.[channel.id] > 0}
mention={mentions?.[channel.id]} mention={mentions?.[channel.id]}
onClick={() => { onClick={() => {
setActiveChannel(channel); channelsDispatch({ type: "ChangeActive", payload: channel.id });
if (onCommunityClick) { if (onCommunityClick) {
onCommunityClick(); onCommunityClick();
} }

View File

@ -34,8 +34,7 @@ export function ChatCreation({
(member) => member?.customName ?? member.trueName (member) => member?.customName ?? member.trueName
) ?? [] ) ?? []
); );
const { contacts, createGroupChat, addMembers, setChannel } = const { contacts, createGroupChat, addMembers } = useMessengerContext();
useMessengerContext();
const setChatState = useChatState()[1]; const setChatState = useChatState()[1];
const addMember = useCallback( const addMember = useCallback(
@ -71,7 +70,7 @@ export function ChatCreation({
setChatState(ChatState.ChatBody); setChatState(ChatState.ChatBody);
} }
}, },
[identity, createGroupChat, setChannel] [identity, createGroupChat]
); );
return ( return (

View File

@ -1,4 +1,4 @@
import React, { useEffect, useMemo, useState } from "react"; import React, { useMemo, useState } from "react";
import styled from "styled-components"; import styled from "styled-components";
import { useMessengerContext } from "../../contexts/messengerProvider"; import { useMessengerContext } from "../../contexts/messengerProvider";
@ -41,20 +41,12 @@ export const ChannelMenu = ({
className, className,
}: ChannelMenuProps) => { }: ChannelMenuProps) => {
const narrow = useNarrow(); const narrow = useNarrow();
const { clearNotifications } = useMessengerContext(); const { clearNotifications, channelsDispatch } = useMessengerContext();
const { setModal } = useModal(EditModalName); const { setModal } = useModal(EditModalName);
const { setModal: setLeavingModal } = useModal(LeavingModalName); const { setModal: setLeavingModal } = useModal(LeavingModalName);
const { setModal: setProfileModal } = useModal(ProfileModalName); const { setModal: setProfileModal } = useModal(ProfileModalName);
const [showSubmenu, setShowSubmenu] = useState(false); const [showSubmenu, setShowSubmenu] = useState(false);
const muting = channel.isMuted;
const [isMuted, setIsMuted] = useState(muting);
useEffect(() => {
if (isMuted) channel.isMuted = true;
if (!isMuted) channel.isMuted = false;
}, [isMuted]);
const { showMenu, setShowMenu: setShowSideMenu } = useContextMenu( const { showMenu, setShowMenu: setShowSideMenu } = useContextMenu(
channel.id + "contextMenu" channel.id + "contextMenu"
); );
@ -113,26 +105,31 @@ export const ChannelMenu = ({
<MenuSection className={`${channel.type === "channel" && "channel"}`}> <MenuSection className={`${channel.type === "channel" && "channel"}`}>
<MenuItem <MenuItem
onClick={() => { onClick={() => {
if (isMuted) { if (channel.isMuted) {
setIsMuted(false); channelsDispatch({ type: "ToggleMuted", payload: channel.id });
setShowMenu(false); setShowMenu(false);
} }
}} }}
onMouseEnter={() => { onMouseEnter={() => {
if (!isMuted) setShowSubmenu(true); if (!channel.isMuted) setShowSubmenu(true);
}} }}
onMouseLeave={() => { onMouseLeave={() => {
if (!isMuted) setShowSubmenu(false); if (!channel.isMuted) setShowSubmenu(false);
}} }}
> >
<MuteIcon width={16} height={16} /> <MuteIcon width={16} height={16} />
{!isMuted && <NextIcon />} {!channel.isMuted && <NextIcon />}
<MenuText> <MenuText>
{(isMuted ? "Unmute" : "Mute") + {(channel.isMuted ? "Unmute" : "Mute") +
(channel.type === "group" ? " Group" : " Chat")} (channel.type === "group" ? " Group" : " Chat")}
</MenuText> </MenuText>
{!isMuted && showSubmenu && ( {!channel.isMuted && showSubmenu && (
<MuteMenu setIsMuted={setIsMuted} className={className} /> <MuteMenu
setIsMuted={() =>
channelsDispatch({ type: "ToggleMuted", payload: channel.id })
}
className={className}
/>
)} )}
</MenuItem> </MenuItem>
<MenuItem onClick={() => clearNotifications(channel.id)}> <MenuItem onClick={() => clearNotifications(channel.id)}>

View File

@ -20,9 +20,8 @@ const MessengerContext = createContext<MessengerType>({
setContacts: () => undefined, setContacts: () => undefined,
activeChannel: undefined, activeChannel: undefined,
channels: {}, channels: {},
setChannel: () => undefined, channelsDispatch: () => undefined,
removeChannel: () => undefined, removeChannel: () => undefined,
setActiveChannel: () => undefined,
createGroupChat: () => undefined, createGroupChat: () => undefined,
changeGroupChatName: () => undefined, changeGroupChatName: () => undefined,
addMembers: () => undefined, addMembers: () => undefined,

View File

@ -7,11 +7,13 @@ import {
} from "@waku/status-communities/dist/cjs"; } from "@waku/status-communities/dist/cjs";
import { useCallback, useMemo } from "react"; import { useCallback, useMemo } from "react";
import { ChannelData, ChannelsData } from "../../models/ChannelData"; import { ChannelData } from "../../models/ChannelData";
import { ChatMessage } from "../../models/ChatMessage"; import { ChatMessage } from "../../models/ChatMessage";
import { Contact } from "../../models/Contact"; import { Contact } from "../../models/Contact";
import { uintToImgUrl } from "../../utils"; import { uintToImgUrl } from "../../utils";
import { ChannelAction } from "./useMessenger";
const contactFromId = (member: string): Contact => { const contactFromId = (member: string): Contact => {
return { return {
blocked: false, blocked: false,
@ -25,10 +27,8 @@ const contactFromId = (member: string): Contact => {
export function useGroupChats( export function useGroupChats(
messenger: Messenger | undefined, messenger: Messenger | undefined,
identity: Identity | undefined, identity: Identity | undefined,
setChannels: React.Dispatch<React.SetStateAction<ChannelsData>>, dispatch: (action: ChannelAction) => void,
setActiveChannel: (channel: ChannelData) => void, addChatMessage: (newMessage: ChatMessage | undefined, id: string) => void
addChatMessage: (newMessage: ChatMessage | undefined, id: string) => void,
channels: ChannelsData
) { ) {
const groupChat = useMemo(() => { const groupChat = useMemo(() => {
if (messenger && identity) { if (messenger && identity) {
@ -51,20 +51,10 @@ export function useGroupChats(
type: "dm", type: "dm",
description: `Chatkey: ${chat.members[0].id}`, description: `Chatkey: ${chat.members[0].id}`,
}; };
setChannels((prev) => { dispatch({ type: "AddChannel", payload: channel });
return { ...prev, [channel.id]: channel };
});
}; };
const removeChat = (chat: GroupChat) => { const removeChat = (chat: GroupChat) => {
setChannels((prev) => { dispatch({ type: "RemoveChannel", payload: chat.chatId });
delete prev[chat.chatId];
return prev;
});
setActiveChannel({
id: "",
name: "",
type: "channel",
} as ChannelData);
}; };
const handleMessage = (msg: StatusChatMessage, sender: string) => { const handleMessage = (msg: StatusChatMessage, sender: string) => {
let image: string | undefined = undefined; let image: string | undefined = undefined;
@ -116,7 +106,7 @@ export function useGroupChats(
groupChat.quitChat(channelId); groupChat.quitChat(channelId);
} }
}, },
[channels, groupChat] [groupChat]
); );
const addMembers = useCallback( const addMembers = useCallback(

View File

@ -6,7 +6,7 @@ import {
Identity, Identity,
Messenger, Messenger,
} from "@waku/status-communities/dist/cjs"; } from "@waku/status-communities/dist/cjs";
import { useCallback, useEffect, useMemo, useState } from "react"; import { useCallback, useEffect, useMemo, useReducer, useState } from "react";
import { useConfig } from "../../contexts/configProvider"; import { useConfig } from "../../contexts/configProvider";
import { ChannelData, ChannelsData } from "../../models/ChannelData"; import { ChannelData, ChannelsData } from "../../models/ChannelData";
@ -41,10 +41,9 @@ export type MessengerType = {
contacts: Contacts; contacts: Contacts;
setContacts: React.Dispatch<React.SetStateAction<Contacts>>; setContacts: React.Dispatch<React.SetStateAction<Contacts>>;
channels: ChannelsData; channels: ChannelsData;
setChannel: (channel: ChannelData) => void; channelsDispatch: (action: ChannelAction) => void;
removeChannel: (channelId: string) => void; removeChannel: (channelId: string) => void;
activeChannel: ChannelData | undefined; activeChannel: ChannelData | undefined;
setActiveChannel: (channel: ChannelData) => void;
createGroupChat: (members: string[]) => void; createGroupChat: (members: string[]) => void;
changeGroupChatName: (name: string, chatId: string) => void; changeGroupChatName: (name: string, chatId: string) => void;
addMembers: (members: string[], chatId: string) => void; addMembers: (members: string[], chatId: string) => void;
@ -101,17 +100,84 @@ function useCreateCommunity(
return { community, communityData }; return { community, communityData };
} }
export type ChannelsState = {
channels: ChannelsData;
activeChannel: ChannelData;
};
export type ChannelAction =
| { type: "AddChannel"; payload: ChannelData }
| { type: "UpdateActive"; payload: ChannelData }
| { type: "ChangeActive"; payload: string }
| { type: "ToggleMuted"; payload: string }
| { type: "RemoveChannel"; payload: string };
function channelReducer(
state: ChannelsState,
action: ChannelAction
): ChannelsState {
switch (action.type) {
case "AddChannel": {
const channels = {
...state.channels,
[action.payload.id]: action.payload,
};
return { channels, activeChannel: action.payload };
}
case "UpdateActive": {
const activeChannel = state.activeChannel;
if (activeChannel) {
return {
channels: { ...state.channels, [activeChannel.id]: action.payload },
activeChannel: action.payload,
};
}
return state;
}
case "ChangeActive": {
const newActive = state.channels[action.payload];
if (newActive) {
return { ...state, activeChannel: newActive };
}
return state;
}
case "ToggleMuted": {
const channel = state.channels[action.payload];
if (channel) {
const updatedChannel: ChannelData = {
...channel,
isMuted: !channel.isMuted,
};
return {
channels: { ...state.channels, [channel.id]: updatedChannel },
activeChannel: updatedChannel,
};
}
return state;
}
case "RemoveChannel": {
const channelsCopy = { ...state.channels };
delete channelsCopy[action.payload];
let newActive = { id: "", name: "", type: "channel" } as ChannelData;
if (Object.values(channelsCopy).length > 0) {
newActive = Object.values(channelsCopy)[0];
}
return { channels: channelsCopy, activeChannel: newActive };
}
default:
throw new Error();
}
}
export function useMessenger( export function useMessenger(
communityKey: string, communityKey: string,
identity: Identity | undefined, identity: Identity | undefined,
newNickname: string | undefined newNickname: string | undefined
) { ) {
const [activeChannel, setActiveChannel] = useState<ChannelData>({ const [channelsState, channelsDispatch] = useReducer(channelReducer, {
id: "", channels: {},
name: "", activeChannel: { id: "", name: "", type: "channel" },
description: "", } as ChannelsState);
type: "channel",
});
const messenger = useCreateMessenger(identity); const messenger = useCreateMessenger(identity);
@ -129,7 +195,7 @@ export function useMessenger(
messages, messages,
mentions, mentions,
clearMentions, clearMentions,
} = useMessages(activeChannel.id, identity, contactsClass); } = useMessages(channelsState?.activeChannel?.id, identity, contactsClass);
const { community, communityData } = useCreateCommunity( const { community, communityData } = useCreateCommunity(
messenger, messenger,
@ -138,37 +204,34 @@ export function useMessenger(
contactsClass contactsClass
); );
const [channels, setChannels] = useState<ChannelsData>({});
const setChannel = useCallback((channel: ChannelData) => {
setChannels((prev) => {
return { ...prev, [channel.id]: channel };
});
setActiveChannel(channel);
}, []);
useEffect(() => { useEffect(() => {
if (community?.chats) { if (community?.chats) {
for (const chat of community.chats.values()) { for (const chat of community.chats.values()) {
setChannel({ channelsDispatch({
type: "AddChannel",
payload: {
id: chat.id, id: chat.id,
name: chat.communityChat?.identity?.displayName ?? "", name: chat.communityChat?.identity?.displayName ?? "",
description: chat.communityChat?.identity?.description ?? "", description: chat.communityChat?.identity?.description ?? "",
type: "channel", type: "channel",
},
}); });
} }
} }
}, [community]); }, [community]);
useEffect(() => { useEffect(() => {
Object.values(channels) Object.values(channelsState.channels)
.filter((channel) => channel.type === "dm") .filter((channel) => channel.type === "dm")
.forEach((channel) => { .forEach((channel) => {
const contact = contacts?.[channel?.members?.[0]?.id ?? ""]; const contact = contacts?.[channel?.members?.[0]?.id ?? ""];
if (contact && channel.name !== (contact?.customName ?? channel.name)) { if (contact && channel.name !== (contact?.customName ?? channel.name)) {
setChannel({ channelsDispatch({
type: "AddChannel",
payload: {
...channel, ...channel,
name: contact?.customName ?? channel.name, name: contact?.customName ?? channel.name,
},
}); });
} }
}); });
@ -180,17 +243,10 @@ export function useMessenger(
createGroupChat, createGroupChat,
changeGroupChatName, changeGroupChatName,
addMembers, addMembers,
} = useGroupChats( } = useGroupChats(messenger, identity, channelsDispatch, addChatMessage);
messenger,
identity,
setChannels,
setActiveChannel,
addChatMessage,
channels
);
const { loadPrevDay, loadingMessages } = useLoadPrevDay( const { loadPrevDay, loadingMessages } = useLoadPrevDay(
activeChannel.id, channelsState.activeChannel.id,
messenger, messenger,
groupChat groupChat
); );
@ -220,28 +276,36 @@ export function useMessenger(
}; };
} }
if (content) { if (content) {
if (activeChannel.type === "group") { if (channelsState.activeChannel.type === "group") {
await groupChat?.sendMessage(activeChannel.id, content, responseTo); await groupChat?.sendMessage(
channelsState.activeChannel.id,
content,
responseTo
);
} else { } else {
await messenger?.sendMessage(activeChannel.id, content, responseTo); await messenger?.sendMessage(
channelsState.activeChannel.id,
content,
responseTo
);
} }
} }
}, },
[messenger, groupChat, activeChannel] [messenger, groupChat, channelsState.activeChannel]
); );
useEffect(() => { useEffect(() => {
if (activeChannel) { if (channelsState.activeChannel) {
if (notifications[activeChannel.id] > 0) { if (notifications[channelsState.activeChannel.id] > 0) {
clearNotifications(activeChannel.id); clearNotifications(channelsState.activeChannel.id);
clearMentions(activeChannel.id); clearMentions(channelsState.activeChannel.id);
} }
} }
}, [notifications, activeChannel]); }, [notifications, channelsState]);
const loadingMessenger = useMemo(() => { const loadingMessenger = useMemo(() => {
return !communityData || !messenger || !activeChannel; return !communityData || !messenger || !channelsState.activeChannel.id;
}, [communityData, messenger, activeChannel]); }, [communityData, messenger, channelsState]);
return { return {
messenger, messenger,
@ -255,11 +319,10 @@ export function useMessenger(
communityData, communityData,
contacts, contacts,
setContacts, setContacts,
channels, channels: channelsState.channels,
setChannel, channelsDispatch,
removeChannel, removeChannel,
activeChannel, activeChannel: channelsState.activeChannel,
setActiveChannel,
mentions, mentions,
clearMentions, clearMentions,
createGroupChat, createGroupChat,

View File

@ -287,12 +287,9 @@ export class GroupChats {
messages.sort((a, b) => messages.sort((a, b) =>
(a?.timestamp?.getTime() ?? 0) < (b?.timestamp?.getTime() ?? 0) ? -1 : 1 (a?.timestamp?.getTime() ?? 0) < (b?.timestamp?.getTime() ?? 0) ? -1 : 1
); );
for (let i = 0; i < messages.length; i++) {
await Promise.all( await this.decodeUpdateMessage(messages[i], false);
messages.map( }
async (message) => await this.decodeUpdateMessage(message, false)
)
);
this.waku.relay.addObserver( this.waku.relay.addObserver(
(message) => this.decodeUpdateMessage(message, true), (message) => this.decodeUpdateMessage(message, true),
[topic] [topic]