Refactor hooks and add reducers (#208)
This commit is contained in:
parent
f2aa41309a
commit
21e49cb7bf
|
@ -3,8 +3,8 @@ import React, { useMemo } from "react";
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
|
|
||||||
import { useIdentity } from "../../contexts/identityProvider";
|
import { useIdentity } from "../../contexts/identityProvider";
|
||||||
|
import { useMessengerContext } from "../../contexts/messengerProvider";
|
||||||
import { useModal } from "../../contexts/modalProvider";
|
import { useModal } from "../../contexts/modalProvider";
|
||||||
import { useManageContact } from "../../hooks/useManageContact";
|
|
||||||
import { AddContactIcon } from "../Icons/AddContactIcon";
|
import { AddContactIcon } from "../Icons/AddContactIcon";
|
||||||
import { BlockSvg } from "../Icons/BlockIcon";
|
import { BlockSvg } from "../Icons/BlockIcon";
|
||||||
import { ChatSvg } from "../Icons/ChatIcon";
|
import { ChatSvg } from "../Icons/ChatIcon";
|
||||||
|
@ -26,6 +26,8 @@ type ContactMenuProps = {
|
||||||
|
|
||||||
export function ContactMenu({ id, setShowMenu }: ContactMenuProps) {
|
export function ContactMenu({ id, setShowMenu }: ContactMenuProps) {
|
||||||
const identity = useIdentity();
|
const identity = useIdentity();
|
||||||
|
const { contacts, contactsDispatch } = useMessengerContext();
|
||||||
|
const contact = useMemo(() => contacts[id], [id, contacts]);
|
||||||
const isUser = useMemo(() => {
|
const isUser = useMemo(() => {
|
||||||
if (identity) {
|
if (identity) {
|
||||||
return id === bufToHex(identity.publicKey);
|
return id === bufToHex(identity.publicKey);
|
||||||
|
@ -35,7 +37,6 @@ export function ContactMenu({ id, setShowMenu }: ContactMenuProps) {
|
||||||
}, [id, identity]);
|
}, [id, identity]);
|
||||||
|
|
||||||
const { setModal } = useModal(ProfileModalName);
|
const { setModal } = useModal(ProfileModalName);
|
||||||
const { contact, setBlocked, setIsUntrustworthy } = useManageContact(id);
|
|
||||||
|
|
||||||
if (!contact) return null;
|
if (!contact) return null;
|
||||||
return (
|
return (
|
||||||
|
@ -88,7 +89,11 @@ export function ContactMenu({ id, setShowMenu }: ContactMenuProps) {
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
</MenuSection>
|
</MenuSection>
|
||||||
<MenuSection>
|
<MenuSection>
|
||||||
<MenuItem onClick={() => setIsUntrustworthy(!contact.isUntrustworthy)}>
|
<MenuItem
|
||||||
|
onClick={() =>
|
||||||
|
contactsDispatch({ type: "toggleTrustworthy", payload: { id } })
|
||||||
|
}
|
||||||
|
>
|
||||||
<WarningSvg
|
<WarningSvg
|
||||||
width={16}
|
width={16}
|
||||||
height={16}
|
height={16}
|
||||||
|
@ -104,7 +109,7 @@ export function ContactMenu({ id, setShowMenu }: ContactMenuProps) {
|
||||||
{!contact.isFriend && !isUser && (
|
{!contact.isFriend && !isUser && (
|
||||||
<MenuItem
|
<MenuItem
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setBlocked(!contact.blocked);
|
contactsDispatch({ type: "toggleBlocked", payload: { id } });
|
||||||
setShowMenu(false);
|
setShowMenu(false);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
|
@ -4,9 +4,9 @@ import styled from "styled-components";
|
||||||
|
|
||||||
import { useActivities } from "../../contexts/activityProvider";
|
import { useActivities } from "../../contexts/activityProvider";
|
||||||
import { useIdentity } from "../../contexts/identityProvider";
|
import { useIdentity } from "../../contexts/identityProvider";
|
||||||
|
import { useMessengerContext } from "../../contexts/messengerProvider";
|
||||||
import { useModal } from "../../contexts/modalProvider";
|
import { useModal } from "../../contexts/modalProvider";
|
||||||
import { useToasts } from "../../contexts/toastProvider";
|
import { useToasts } from "../../contexts/toastProvider";
|
||||||
import { useManageContact } from "../../hooks/useManageContact";
|
|
||||||
import { copy } from "../../utils";
|
import { copy } from "../../utils";
|
||||||
import { buttonStyles } from "../Buttons/buttonStyle";
|
import { buttonStyles } from "../Buttons/buttonStyle";
|
||||||
import {
|
import {
|
||||||
|
@ -75,13 +75,8 @@ export const ProfileModal = () => {
|
||||||
setRequestCreation(requestState ?? false);
|
setRequestCreation(requestState ?? false);
|
||||||
}, [requestState]);
|
}, [requestState]);
|
||||||
|
|
||||||
const {
|
const { contacts, contactsDispatch } = useMessengerContext();
|
||||||
contact,
|
const contact = useMemo(() => contacts[id], [id, contacts]);
|
||||||
setBlocked,
|
|
||||||
setCustomName,
|
|
||||||
setIsUntrustworthy,
|
|
||||||
setIsUserFriend,
|
|
||||||
} = useManageContact(id);
|
|
||||||
const [customNameInput, setCustomNameInput] = useState("");
|
const [customNameInput, setCustomNameInput] = useState("");
|
||||||
|
|
||||||
if (!contact) return null;
|
if (!contact) return null;
|
||||||
|
@ -129,7 +124,10 @@ export const ProfileModal = () => {
|
||||||
{customNameInput && (
|
{customNameInput && (
|
||||||
<ClearBtn
|
<ClearBtn
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setCustomName(undefined);
|
contactsDispatch({
|
||||||
|
type: "setCustomName",
|
||||||
|
payload: { id, customName: undefined },
|
||||||
|
});
|
||||||
setCustomNameInput("");
|
setCustomNameInput("");
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
@ -184,7 +182,10 @@ export const ProfileModal = () => {
|
||||||
<Btn
|
<Btn
|
||||||
disabled={!customNameInput}
|
disabled={!customNameInput}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setCustomName(customNameInput);
|
contactsDispatch({
|
||||||
|
type: "setCustomName",
|
||||||
|
payload: { id, customName: customNameInput },
|
||||||
|
});
|
||||||
setRenaming(false);
|
setRenaming(false);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
@ -234,7 +235,7 @@ export const ProfileModal = () => {
|
||||||
<ProfileBtn
|
<ProfileBtn
|
||||||
className={contact.blocked ? "" : "red"}
|
className={contact.blocked ? "" : "red"}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setBlocked(!contact.blocked);
|
contactsDispatch({ type: "toggleBlocked", payload: { id } });
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{contact.blocked ? "Unblock" : "Block"}
|
{contact.blocked ? "Unblock" : "Block"}
|
||||||
|
@ -243,14 +244,21 @@ export const ProfileModal = () => {
|
||||||
{contact.isFriend && (
|
{contact.isFriend && (
|
||||||
<ProfileBtn
|
<ProfileBtn
|
||||||
className="red"
|
className="red"
|
||||||
onClick={() => setIsUserFriend(false)}
|
onClick={() =>
|
||||||
|
contactsDispatch({
|
||||||
|
type: "setIsFriend",
|
||||||
|
payload: { id, isFriend: false },
|
||||||
|
})
|
||||||
|
}
|
||||||
>
|
>
|
||||||
Remove Contact
|
Remove Contact
|
||||||
</ProfileBtn>
|
</ProfileBtn>
|
||||||
)}
|
)}
|
||||||
<ProfileBtn
|
<ProfileBtn
|
||||||
className={contact.isUntrustworthy ? "" : "red"}
|
className={contact.isUntrustworthy ? "" : "red"}
|
||||||
onClick={() => setIsUntrustworthy(!contact.isUntrustworthy)}
|
onClick={() =>
|
||||||
|
contactsDispatch({ type: "toggleTrustworthy", payload: { id } })
|
||||||
|
}
|
||||||
>
|
>
|
||||||
{contact.isUntrustworthy
|
{contact.isUntrustworthy
|
||||||
? "Remove Untrustworthy Mark"
|
? "Remove Untrustworthy Mark"
|
||||||
|
|
|
@ -17,7 +17,7 @@ const MessengerContext = createContext<MessengerType>({
|
||||||
loadingMessenger: true,
|
loadingMessenger: true,
|
||||||
communityData: undefined,
|
communityData: undefined,
|
||||||
contacts: {},
|
contacts: {},
|
||||||
setContacts: () => undefined,
|
contactsDispatch: () => undefined,
|
||||||
activeChannel: undefined,
|
activeChannel: undefined,
|
||||||
channels: {},
|
channels: {},
|
||||||
channelsDispatch: () => undefined,
|
channelsDispatch: () => undefined,
|
||||||
|
|
|
@ -0,0 +1,79 @@
|
||||||
|
import { useReducer } from "react";
|
||||||
|
|
||||||
|
import { ChannelData, ChannelsData } from "../../models/ChannelData";
|
||||||
|
|
||||||
|
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 useChannelsReducer() {
|
||||||
|
return useReducer(channelReducer, {
|
||||||
|
channels: {},
|
||||||
|
activeChannel: { id: "", name: "", type: "channel" },
|
||||||
|
} as ChannelsState);
|
||||||
|
}
|
|
@ -4,36 +4,116 @@ import {
|
||||||
Messenger,
|
Messenger,
|
||||||
} from "@waku/status-communities/dist/cjs";
|
} from "@waku/status-communities/dist/cjs";
|
||||||
import { bufToHex } from "@waku/status-communities/dist/cjs/utils";
|
import { bufToHex } from "@waku/status-communities/dist/cjs/utils";
|
||||||
import { useEffect, useMemo, useState } from "react";
|
import { useMemo, useReducer, useState } from "react";
|
||||||
|
|
||||||
import { Contacts } from "../../models/Contact";
|
import { Contacts } from "../../models/Contact";
|
||||||
|
|
||||||
|
export type ContactsAction =
|
||||||
|
| { type: "updateOnline"; payload: { id: string; clock: number } }
|
||||||
|
| { type: "setTrueName"; payload: { id: string; trueName: string } }
|
||||||
|
| {
|
||||||
|
type: "setCustomName";
|
||||||
|
payload: { id: string; customName: string | undefined };
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: "setIsUntrustworthy";
|
||||||
|
payload: { id: string; isUntrustworthy: boolean };
|
||||||
|
}
|
||||||
|
| { type: "setIsFriend"; payload: { id: string; isFriend: boolean } }
|
||||||
|
| { type: "setBlocked"; payload: { id: string; blocked: boolean } }
|
||||||
|
| { type: "toggleBlocked"; payload: { id: string } }
|
||||||
|
| { type: "toggleTrustworthy"; payload: { id: string } };
|
||||||
|
|
||||||
|
function contactsReducer(state: Contacts, action: ContactsAction): Contacts {
|
||||||
|
const id = action.payload.id;
|
||||||
|
const prev = state[id];
|
||||||
|
|
||||||
|
switch (action.type) {
|
||||||
|
case "updateOnline": {
|
||||||
|
const now = Date.now();
|
||||||
|
const clock = action.payload.clock;
|
||||||
|
if (prev) {
|
||||||
|
return { ...state, [id]: { ...prev, online: clock > now - 301000 } };
|
||||||
|
}
|
||||||
|
return { ...state, [id]: { id, trueName: id.slice(0, 10) } };
|
||||||
|
}
|
||||||
|
case "setTrueName": {
|
||||||
|
const trueName = action.payload.trueName;
|
||||||
|
if (prev) {
|
||||||
|
return { ...state, [id]: { ...prev, trueName } };
|
||||||
|
}
|
||||||
|
return { ...state, [id]: { id, trueName } };
|
||||||
|
}
|
||||||
|
case "setCustomName": {
|
||||||
|
const customName = action.payload.customName;
|
||||||
|
if (prev) {
|
||||||
|
return { ...state, [id]: { ...prev, customName } };
|
||||||
|
}
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
case "setIsUntrustworthy": {
|
||||||
|
const isUntrustworthy = action.payload.isUntrustworthy;
|
||||||
|
if (prev) {
|
||||||
|
return { ...state, [id]: { ...prev, isUntrustworthy } };
|
||||||
|
}
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
case "setIsFriend": {
|
||||||
|
const isFriend = action.payload.isFriend;
|
||||||
|
if (prev) {
|
||||||
|
return { ...state, [id]: { ...prev, isFriend } };
|
||||||
|
}
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
case "setBlocked": {
|
||||||
|
const blocked = action.payload.blocked;
|
||||||
|
if (prev) {
|
||||||
|
return { ...state, [id]: { ...prev, blocked } };
|
||||||
|
}
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
case "toggleBlocked": {
|
||||||
|
if (prev) {
|
||||||
|
return { ...state, [id]: { ...prev, blocked: !prev.blocked } };
|
||||||
|
}
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
case "toggleTrustworthy": {
|
||||||
|
if (prev) {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
[id]: { ...prev, isUntrustworthy: !prev.isUntrustworthy },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
throw new Error();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function useContacts(
|
export function useContacts(
|
||||||
messenger: Messenger | undefined,
|
messenger: Messenger | undefined,
|
||||||
identity: Identity | undefined,
|
identity: Identity | undefined,
|
||||||
newNickname: string | undefined
|
newNickname: string | undefined
|
||||||
) {
|
) {
|
||||||
const [nickname, setNickname] = useState<string | undefined>(undefined);
|
const [nickname, setNickname] = useState<string | undefined>(undefined);
|
||||||
const [internalContacts, setInternalContacts] = useState<{
|
const [contacts, contactsDispatch] = useReducer(contactsReducer, {});
|
||||||
[id: string]: { clock: number; nickname?: string };
|
|
||||||
}>({});
|
|
||||||
|
|
||||||
const contactsClass = useMemo(() => {
|
const contactsClass = useMemo(() => {
|
||||||
if (messenger) {
|
if (messenger) {
|
||||||
const newContacts = new ContactsClass(
|
const newContacts = new ContactsClass(
|
||||||
identity,
|
identity,
|
||||||
messenger.waku,
|
messenger.waku,
|
||||||
(id, clock) => {
|
(id, clock) =>
|
||||||
setInternalContacts((prev) => {
|
contactsDispatch({ type: "updateOnline", payload: { id, clock } }),
|
||||||
return { ...prev, [id]: { ...prev[id], clock } };
|
|
||||||
});
|
|
||||||
},
|
|
||||||
(id, nickname) => {
|
(id, nickname) => {
|
||||||
setInternalContacts((prev) => {
|
if (identity?.publicKey && id === bufToHex(identity.publicKey)) {
|
||||||
if (identity?.publicKey && id === bufToHex(identity.publicKey)) {
|
setNickname(nickname);
|
||||||
setNickname(nickname);
|
}
|
||||||
}
|
contactsDispatch({
|
||||||
return { ...prev, [id]: { ...prev[id], nickname } };
|
type: "setTrueName",
|
||||||
|
payload: { id, trueName: nickname },
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
newNickname
|
newNickname
|
||||||
|
@ -42,27 +122,5 @@ export function useContacts(
|
||||||
}
|
}
|
||||||
}, [messenger, identity]);
|
}, [messenger, identity]);
|
||||||
|
|
||||||
const [contacts, setContacts] = useState<Contacts>({});
|
return { contacts, contactsDispatch, contactsClass, nickname };
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const now = Date.now();
|
|
||||||
setContacts((prev) => {
|
|
||||||
const newContacts: Contacts = {};
|
|
||||||
Object.entries(internalContacts).forEach(([id, { clock, nickname }]) => {
|
|
||||||
newContacts[id] = {
|
|
||||||
id,
|
|
||||||
online: clock > now - 301000,
|
|
||||||
trueName: nickname ?? id.slice(0, 10),
|
|
||||||
isUntrustworthy: false,
|
|
||||||
blocked: false,
|
|
||||||
};
|
|
||||||
if (prev[id]) {
|
|
||||||
newContacts[id] = { ...prev[id], ...newContacts[id] };
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return newContacts;
|
|
||||||
});
|
|
||||||
}, [internalContacts]);
|
|
||||||
|
|
||||||
return { contacts, setContacts, contactsClass, nickname };
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,7 @@ 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";
|
import { ChannelAction } from "./useChannelsReducer";
|
||||||
|
|
||||||
const contactFromId = (member: string): Contact => {
|
const contactFromId = (member: string): Contact => {
|
||||||
return {
|
return {
|
||||||
|
@ -43,13 +43,14 @@ export function useGroupChats(
|
||||||
name: chat.name ?? chat.chatId.slice(0, 10),
|
name: chat.name ?? chat.chatId.slice(0, 10),
|
||||||
type: "group",
|
type: "group",
|
||||||
description: `${chat.members.length} members`,
|
description: `${chat.members.length} members`,
|
||||||
members: members,
|
members,
|
||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
id: chat.chatId,
|
id: chat.chatId,
|
||||||
name: chat.members[0].id,
|
name: chat.members[0].id,
|
||||||
type: "dm",
|
type: "dm",
|
||||||
description: `Chatkey: ${chat.members[0].id}`,
|
description: `Chatkey: ${chat.members[0].id}`,
|
||||||
|
members,
|
||||||
};
|
};
|
||||||
dispatch({ type: "AddChannel", payload: channel });
|
dispatch({ type: "AddChannel", payload: channel });
|
||||||
};
|
};
|
||||||
|
|
|
@ -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, useReducer, useState } from "react";
|
import { useCallback, useEffect, useMemo, 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";
|
||||||
|
@ -17,7 +17,8 @@ import { createCommunity } from "../../utils/createCommunity";
|
||||||
import { createMessenger } from "../../utils/createMessenger";
|
import { createMessenger } from "../../utils/createMessenger";
|
||||||
import { uintToImgUrl } from "../../utils/uintToImgUrl";
|
import { uintToImgUrl } from "../../utils/uintToImgUrl";
|
||||||
|
|
||||||
import { useContacts } from "./useContacts";
|
import { ChannelAction, useChannelsReducer } from "./useChannelsReducer";
|
||||||
|
import { ContactsAction, useContacts } from "./useContacts";
|
||||||
import { useGroupChats } from "./useGroupChats";
|
import { useGroupChats } from "./useGroupChats";
|
||||||
import { useLoadPrevDay } from "./useLoadPrevDay";
|
import { useLoadPrevDay } from "./useLoadPrevDay";
|
||||||
import { useMessages } from "./useMessages";
|
import { useMessages } from "./useMessages";
|
||||||
|
@ -39,7 +40,7 @@ export type MessengerType = {
|
||||||
loadingMessenger: boolean;
|
loadingMessenger: boolean;
|
||||||
communityData: CommunityData | undefined;
|
communityData: CommunityData | undefined;
|
||||||
contacts: Contacts;
|
contacts: Contacts;
|
||||||
setContacts: React.Dispatch<React.SetStateAction<Contacts>>;
|
contactsDispatch: (action: ContactsAction) => void;
|
||||||
channels: ChannelsData;
|
channels: ChannelsData;
|
||||||
channelsDispatch: (action: ChannelAction) => void;
|
channelsDispatch: (action: ChannelAction) => void;
|
||||||
removeChannel: (channelId: string) => void;
|
removeChannel: (channelId: string) => void;
|
||||||
|
@ -78,9 +79,12 @@ function useCreateCommunity(
|
||||||
|
|
||||||
const communityData = useMemo(() => {
|
const communityData = useMemo(() => {
|
||||||
if (community?.description) {
|
if (community?.description) {
|
||||||
Object.keys(community.description.proto.members).forEach((contact) =>
|
const membersList = Object.keys(community.description.proto.members);
|
||||||
contactsClass?.addContact(contact)
|
|
||||||
);
|
if (contactsClass) {
|
||||||
|
membersList.forEach(contactsClass.addContact, contactsClass);
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: community.publicKeyStr,
|
id: community.publicKeyStr,
|
||||||
name: community.description.identity?.displayName ?? "",
|
name: community.description.identity?.displayName ?? "",
|
||||||
|
@ -88,8 +92,8 @@ function useCreateCommunity(
|
||||||
community.description?.identity?.images?.thumbnail?.payload ??
|
community.description?.identity?.images?.thumbnail?.payload ??
|
||||||
new Uint8Array()
|
new Uint8Array()
|
||||||
),
|
),
|
||||||
members: 0,
|
members: membersList.length,
|
||||||
membersList: Object.keys(community.description.proto.members),
|
membersList,
|
||||||
description: community.description.identity?.description ?? "",
|
description: community.description.identity?.description ?? "",
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
|
@ -100,88 +104,14 @@ 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 [channelsState, channelsDispatch] = useReducer(channelReducer, {
|
const [channelsState, channelsDispatch] = useChannelsReducer();
|
||||||
channels: {},
|
|
||||||
activeChannel: { id: "", name: "", type: "channel" },
|
|
||||||
} as ChannelsState);
|
|
||||||
|
|
||||||
const messenger = useCreateMessenger(identity);
|
const messenger = useCreateMessenger(identity);
|
||||||
|
const { contacts, contactsDispatch, contactsClass, nickname } = useContacts(
|
||||||
const { contacts, setContacts, contactsClass, nickname } = useContacts(
|
|
||||||
messenger,
|
messenger,
|
||||||
identity,
|
identity,
|
||||||
newNickname
|
newNickname
|
||||||
|
@ -224,18 +154,21 @@ export function useMessenger(
|
||||||
Object.values(channelsState.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?.[1]?.id ?? ""];
|
||||||
if (contact && channel.name !== (contact?.customName ?? channel.name)) {
|
if (
|
||||||
|
contact &&
|
||||||
|
channel.name !== (contact?.customName ?? contact.trueName)
|
||||||
|
) {
|
||||||
channelsDispatch({
|
channelsDispatch({
|
||||||
type: "AddChannel",
|
type: "AddChannel",
|
||||||
payload: {
|
payload: {
|
||||||
...channel,
|
...channel,
|
||||||
name: contact?.customName ?? channel.name,
|
name: contact?.customName ?? contact.trueName,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}, [contacts]);
|
}, [contacts, channelsState.channels]);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
groupChat,
|
groupChat,
|
||||||
|
@ -276,7 +209,7 @@ export function useMessenger(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (content) {
|
if (content) {
|
||||||
if (channelsState.activeChannel.type === "group") {
|
if (channelsState.activeChannel.type !== "channel") {
|
||||||
await groupChat?.sendMessage(
|
await groupChat?.sendMessage(
|
||||||
channelsState.activeChannel.id,
|
channelsState.activeChannel.id,
|
||||||
content,
|
content,
|
||||||
|
@ -318,7 +251,7 @@ export function useMessenger(
|
||||||
loadingMessenger,
|
loadingMessenger,
|
||||||
communityData,
|
communityData,
|
||||||
contacts,
|
contacts,
|
||||||
setContacts,
|
contactsDispatch,
|
||||||
channels: channelsState.channels,
|
channels: channelsState.channels,
|
||||||
channelsDispatch,
|
channelsDispatch,
|
||||||
removeChannel,
|
removeChannel,
|
||||||
|
|
|
@ -1,60 +0,0 @@
|
||||||
import { useCallback, useMemo } from "react";
|
|
||||||
|
|
||||||
import { useMessengerContext } from "../contexts/messengerProvider";
|
|
||||||
|
|
||||||
export function useManageContact(id: string) {
|
|
||||||
const { contacts, setContacts } = useMessengerContext();
|
|
||||||
const contact = useMemo(() => contacts[id], [id, contacts]);
|
|
||||||
|
|
||||||
const setCustomName = useCallback(
|
|
||||||
(customName: string | undefined) => {
|
|
||||||
setContacts((prev) => {
|
|
||||||
const prevUser = prev[id];
|
|
||||||
if (!prevUser) return prev;
|
|
||||||
return { ...prev, [id]: { ...prevUser, customName } };
|
|
||||||
});
|
|
||||||
},
|
|
||||||
[id]
|
|
||||||
);
|
|
||||||
|
|
||||||
const setBlocked = useCallback(
|
|
||||||
(blocked: boolean) => {
|
|
||||||
setContacts((prev) => {
|
|
||||||
const prevUser = prev[id];
|
|
||||||
if (!prevUser) return prev;
|
|
||||||
return { ...prev, [id]: { ...prevUser, blocked } };
|
|
||||||
});
|
|
||||||
},
|
|
||||||
[id]
|
|
||||||
);
|
|
||||||
|
|
||||||
const setIsUntrustworthy = useCallback(
|
|
||||||
(isUntrustworthy: boolean) => {
|
|
||||||
setContacts((prev) => {
|
|
||||||
const prevUser = prev[id];
|
|
||||||
if (!prevUser) return prev;
|
|
||||||
return { ...prev, [id]: { ...prevUser, isUntrustworthy } };
|
|
||||||
});
|
|
||||||
},
|
|
||||||
[id]
|
|
||||||
);
|
|
||||||
|
|
||||||
const setIsUserFriend = useCallback(
|
|
||||||
(isFriend: boolean) => {
|
|
||||||
setContacts((prev) => {
|
|
||||||
const prevUser = prev[id];
|
|
||||||
if (!prevUser) return prev;
|
|
||||||
return { ...prev, [id]: { ...prevUser, isFriend } };
|
|
||||||
});
|
|
||||||
},
|
|
||||||
[id]
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
contact,
|
|
||||||
setCustomName,
|
|
||||||
setBlocked,
|
|
||||||
setIsUntrustworthy,
|
|
||||||
setIsUserFriend,
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -32,12 +32,34 @@ export type GroupChatsType = {
|
||||||
};
|
};
|
||||||
/* TODO: add chat messages encryption */
|
/* TODO: add chat messages encryption */
|
||||||
|
|
||||||
|
class GroupChatUsers {
|
||||||
|
private users: { [id: string]: GroupMember } = {};
|
||||||
|
private identity: Identity;
|
||||||
|
|
||||||
|
public constructor(_identity: Identity) {
|
||||||
|
this.identity = _identity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getUser(id: string): Promise<GroupMember> {
|
||||||
|
if (this.users[id]) {
|
||||||
|
return this.users[id];
|
||||||
|
}
|
||||||
|
const topic = await getNegotiatedTopic(this.identity, id);
|
||||||
|
const symKey = await createSymKeyFromPassword(topic);
|
||||||
|
const partitionedTopic = getPartitionedTopic(id);
|
||||||
|
const groupUser: GroupMember = { topic, symKey, id, partitionedTopic };
|
||||||
|
this.users[id] = groupUser;
|
||||||
|
return groupUser;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class GroupChats {
|
export class GroupChats {
|
||||||
waku: Waku;
|
waku: Waku;
|
||||||
identity: Identity;
|
identity: Identity;
|
||||||
private callback: (chats: GroupChat) => void;
|
private callback: (chats: GroupChat) => void;
|
||||||
private removeCallback: (chats: GroupChat) => void;
|
private removeCallback: (chats: GroupChat) => void;
|
||||||
private addMessage: (message: ChatMessage, sender: string) => void;
|
private addMessage: (message: ChatMessage, sender: string) => void;
|
||||||
|
private groupChatUsers;
|
||||||
|
|
||||||
public chats: GroupChatsType = {};
|
public chats: GroupChatsType = {};
|
||||||
/**
|
/**
|
||||||
|
@ -62,6 +84,7 @@ export class GroupChats {
|
||||||
) {
|
) {
|
||||||
this.waku = waku;
|
this.waku = waku;
|
||||||
this.identity = identity;
|
this.identity = identity;
|
||||||
|
this.groupChatUsers = new GroupChatUsers(identity);
|
||||||
this.callback = callback;
|
this.callback = callback;
|
||||||
this.removeCallback = removeCallback;
|
this.removeCallback = removeCallback;
|
||||||
this.addMessage = addMessage;
|
this.addMessage = addMessage;
|
||||||
|
@ -117,10 +140,7 @@ export class GroupChats {
|
||||||
const members: GroupMember[] = [];
|
const members: GroupMember[] = [];
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
event.event.members.map(async (member) => {
|
event.event.members.map(async (member) => {
|
||||||
const topic = await getNegotiatedTopic(this.identity, member);
|
members.push(await this.groupChatUsers.getUser(member));
|
||||||
const symKey = await createSymKeyFromPassword(topic);
|
|
||||||
const partitionedTopic = getPartitionedTopic(member);
|
|
||||||
members.push({ topic, symKey, id: member, partitionedTopic });
|
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
await this.addChat(
|
await this.addChat(
|
||||||
|
@ -160,10 +180,7 @@ export class GroupChats {
|
||||||
const members: GroupMember[] = [];
|
const members: GroupMember[] = [];
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
event.event.members.map(async (member) => {
|
event.event.members.map(async (member) => {
|
||||||
const topic = await getNegotiatedTopic(this.identity, member);
|
members.push(await this.groupChatUsers.getUser(member));
|
||||||
const symKey = await createSymKeyFromPassword(topic);
|
|
||||||
const partitionedTopic = getPartitionedTopic(member);
|
|
||||||
members.push({ topic, symKey, id: member, partitionedTopic });
|
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
chat.members.push(...members);
|
chat.members.push(...members);
|
||||||
|
@ -353,10 +370,7 @@ export class GroupChats {
|
||||||
!chat.members.map((chatMember) => chatMember.id).includes(member)
|
!chat.members.map((chatMember) => chatMember.id).includes(member)
|
||||||
)
|
)
|
||||||
.map(async (member) => {
|
.map(async (member) => {
|
||||||
const topic = await getNegotiatedTopic(this.identity, member);
|
newMembers.push(await this.groupChatUsers.getUser(member));
|
||||||
const symKey = await createSymKeyFromPassword(topic);
|
|
||||||
const partitionedTopic = getPartitionedTopic(member);
|
|
||||||
newMembers.push({ topic, symKey, id: member, partitionedTopic });
|
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -383,10 +397,7 @@ export class GroupChats {
|
||||||
|
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
members.map(async (member) => {
|
members.map(async (member) => {
|
||||||
const topic = await getNegotiatedTopic(this.identity, member);
|
newMembers.push(await this.groupChatUsers.getUser(member));
|
||||||
const symKey = await createSymKeyFromPassword(topic);
|
|
||||||
const partitionedTopic = getPartitionedTopic(member);
|
|
||||||
newMembers.push({ topic, symKey, id: member, partitionedTopic });
|
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue