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,
setShowActivityCenter,
}: ActivityMessageProps) {
const { contacts, setActiveChannel } = useMessengerContext();
const { contacts, channelsDispatch } = useMessengerContext();
const { setModal } = useModal(ProfileModalName);
const showChannel = () => {
activity.channel && setActiveChannel(activity.channel),
activity.channel &&
channelsDispatch({ type: "ChangeActive", payload: activity.channel.id }),
setShowActivityCenter(false);
};

View File

@ -59,6 +59,7 @@ export function Channel({
setEditGroup,
}: ChannelProps) {
const narrow = useNarrow();
const { channelsDispatch } = useMessengerContext();
return (
<ChannelWrapper
@ -79,7 +80,11 @@ export function Channel({
notified={notified}
/>
{channel?.isMuted && activeView && !narrow && (
<MutedBtn onClick={() => (channel.isMuted = !channel.isMuted)}>
<MutedBtn
onClick={() =>
channelsDispatch({ type: "ToggleMuted", payload: channel.id })
}
>
<MutedIcon />
<Tooltip tip="Unmute" className="muted" />
</MutedBtn>
@ -90,7 +95,7 @@ export function Channel({
)}
</ChannelTextInfo>
</ChannelInfo>
{!activeView && !!mention && mention > 0 && !channel?.isMuted && (
{!activeView && !!mention && !channel?.isMuted && (
<NotificationBagde>{mention}</NotificationBagde>
)}
{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 { ChatState, useChatState } from "../../contexts/chatStateProvider";
@ -23,12 +23,15 @@ function GenerateChannels({
onCommunityClick,
setEditGroup,
}: GenerateChannelsProps) {
const { mentions, notifications, activeChannel, setActiveChannel, channels } =
const { mentions, notifications, activeChannel, channelsDispatch, channels } =
useMessengerContext();
const channelList = useMemo(() => Object.values(channels), [channels]);
const setChatState = useChatState()[1];
return (
<>
{Object.values(channels)
{channelList
.filter((channel) => channel.type === type)
.map((channel) => (
<Channel
@ -38,7 +41,7 @@ function GenerateChannels({
notified={notifications?.[channel.id] > 0}
mention={mentions?.[channel.id]}
onClick={() => {
setActiveChannel(channel);
channelsDispatch({ type: "ChangeActive", payload: channel.id });
if (onCommunityClick) {
onCommunityClick();
}

View File

@ -34,8 +34,7 @@ export function ChatCreation({
(member) => member?.customName ?? member.trueName
) ?? []
);
const { contacts, createGroupChat, addMembers, setChannel } =
useMessengerContext();
const { contacts, createGroupChat, addMembers } = useMessengerContext();
const setChatState = useChatState()[1];
const addMember = useCallback(
@ -71,7 +70,7 @@ export function ChatCreation({
setChatState(ChatState.ChatBody);
}
},
[identity, createGroupChat, setChannel]
[identity, createGroupChat]
);
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 { useMessengerContext } from "../../contexts/messengerProvider";
@ -41,20 +41,12 @@ export const ChannelMenu = ({
className,
}: ChannelMenuProps) => {
const narrow = useNarrow();
const { clearNotifications } = useMessengerContext();
const { clearNotifications, channelsDispatch } = useMessengerContext();
const { setModal } = useModal(EditModalName);
const { setModal: setLeavingModal } = useModal(LeavingModalName);
const { setModal: setProfileModal } = useModal(ProfileModalName);
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(
channel.id + "contextMenu"
);
@ -113,26 +105,31 @@ export const ChannelMenu = ({
<MenuSection className={`${channel.type === "channel" && "channel"}`}>
<MenuItem
onClick={() => {
if (isMuted) {
setIsMuted(false);
if (channel.isMuted) {
channelsDispatch({ type: "ToggleMuted", payload: channel.id });
setShowMenu(false);
}
}}
onMouseEnter={() => {
if (!isMuted) setShowSubmenu(true);
if (!channel.isMuted) setShowSubmenu(true);
}}
onMouseLeave={() => {
if (!isMuted) setShowSubmenu(false);
if (!channel.isMuted) setShowSubmenu(false);
}}
>
<MuteIcon width={16} height={16} />
{!isMuted && <NextIcon />}
{!channel.isMuted && <NextIcon />}
<MenuText>
{(isMuted ? "Unmute" : "Mute") +
{(channel.isMuted ? "Unmute" : "Mute") +
(channel.type === "group" ? " Group" : " Chat")}
</MenuText>
{!isMuted && showSubmenu && (
<MuteMenu setIsMuted={setIsMuted} className={className} />
{!channel.isMuted && showSubmenu && (
<MuteMenu
setIsMuted={() =>
channelsDispatch({ type: "ToggleMuted", payload: channel.id })
}
className={className}
/>
)}
</MenuItem>
<MenuItem onClick={() => clearNotifications(channel.id)}>

View File

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

View File

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

View File

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

View File

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