Add image message support (#50)
This commit is contained in:
parent
377f4e5409
commit
d4353cad84
|
@ -25,7 +25,7 @@ interface ChatBodyProps {
|
|||
community: CommunityData;
|
||||
messenger: any;
|
||||
messages: ChatMessage[];
|
||||
sendMessage: (text: string) => void;
|
||||
sendMessage: (text: string, image?: Uint8Array) => void;
|
||||
onClick: () => void;
|
||||
showMembers: boolean;
|
||||
showCommunity: boolean;
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import { Picker } from "emoji-mart";
|
||||
import React, { useState } from "react";
|
||||
import React, { useMemo, useState } from "react";
|
||||
import styled from "styled-components";
|
||||
|
||||
import { uintToImgUrl } from "../../helpers/uintToImgUrl";
|
||||
import { lightTheme, Theme } from "../../styles/themes";
|
||||
import { EmojiIcon } from "../Icons/EmojiIcon";
|
||||
import { GifIcon } from "../Icons/GifIcon";
|
||||
|
@ -11,13 +12,21 @@ import "emoji-mart/css/emoji-mart.css";
|
|||
|
||||
type ChatInputProps = {
|
||||
theme: Theme;
|
||||
addMessage: (message: string) => void;
|
||||
addMessage: (message: string, image?: Uint8Array) => void;
|
||||
};
|
||||
|
||||
export function ChatInput({ theme, addMessage }: ChatInputProps) {
|
||||
const [content, setContent] = useState("");
|
||||
const [showEmoji, setShowEmoji] = useState(false);
|
||||
|
||||
const [inputHeight, setInputHeight] = useState(40);
|
||||
const [imageUint, setImageUint] = useState<undefined | Uint8Array>(undefined);
|
||||
const image = useMemo(() => {
|
||||
if (imageUint) {
|
||||
return uintToImgUrl(imageUint);
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}, [imageUint]);
|
||||
const addEmoji = (e: any) => {
|
||||
const sym = e.unified.split("-");
|
||||
const codesArray: any[] = [];
|
||||
|
@ -55,27 +64,48 @@ export function ChatInput({ theme, addMessage }: ChatInputProps) {
|
|||
type="file"
|
||||
multiple={true}
|
||||
accept="image/png, image/jpeg"
|
||||
onChange={(e) => {
|
||||
const fileReader = new FileReader();
|
||||
fileReader.onloadend = (s) => {
|
||||
const arr = new Uint8Array(s.target?.result as ArrayBuffer);
|
||||
setImageUint(arr);
|
||||
};
|
||||
if (e?.target?.files?.[0]) {
|
||||
fileReader.readAsArrayBuffer(e.target.files[0]);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</AddPictureBtn>
|
||||
<Input
|
||||
<InputImageWrapper
|
||||
theme={theme}
|
||||
placeholder={"Message"}
|
||||
value={content}
|
||||
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||
const target = e.target;
|
||||
target.style.height = "40px";
|
||||
target.style.height = `${Math.min(target.scrollHeight, 160)}px`;
|
||||
setContent(target.value);
|
||||
}}
|
||||
onKeyPress={(e: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
||||
if (e.key == "Enter" && !e.getModifierState("Shift")) {
|
||||
e.preventDefault();
|
||||
(e.target as HTMLTextAreaElement).style.height = "40px";
|
||||
addMessage(content);
|
||||
setContent("");
|
||||
}
|
||||
}}
|
||||
/>
|
||||
style={{ height: `${inputHeight + (image ? 73 : 0)}px` }}
|
||||
>
|
||||
{image && (
|
||||
<ImagePreview src={image} onClick={() => setImageUint(undefined)} />
|
||||
)}
|
||||
<Input
|
||||
theme={theme}
|
||||
placeholder={"Message"}
|
||||
value={content}
|
||||
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||
const target = e.target;
|
||||
target.style.height = "40px";
|
||||
target.style.height = `${Math.min(target.scrollHeight, 160)}px`;
|
||||
setInputHeight(target.scrollHeight);
|
||||
setContent(target.value);
|
||||
}}
|
||||
onKeyPress={(e: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
||||
if (e.key == "Enter" && !e.getModifierState("Shift")) {
|
||||
e.preventDefault();
|
||||
(e.target as HTMLTextAreaElement).style.height = "40px";
|
||||
setInputHeight(40);
|
||||
addMessage(content, imageUint);
|
||||
setImageUint(undefined);
|
||||
setContent("");
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</InputImageWrapper>
|
||||
<AddEmojiBtn onClick={() => setShowEmoji(!showEmoji)}>
|
||||
<EmojiIcon theme={theme} isActive={showEmoji} />
|
||||
</AddEmojiBtn>
|
||||
|
@ -100,20 +130,39 @@ const InputWrapper = styled.div`
|
|||
position: relative;
|
||||
`;
|
||||
|
||||
const ImagePreview = styled.img`
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
border-radius: 16px 16px 4px 16px;
|
||||
margin-left: 8px;
|
||||
margin-top: 9px;
|
||||
`;
|
||||
|
||||
const InputImageWrapper = styled.div<ThemeProps>`
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: ${({ theme }) => theme.inputColor};
|
||||
border-radius: 36px 16px 4px 36px;
|
||||
height: 40px;
|
||||
margin-right: 8px;
|
||||
margin-left: 10px;
|
||||
`;
|
||||
|
||||
const Input = styled.textarea<ThemeProps>`
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
background: ${({ theme }) => theme.inputColor};
|
||||
border-radius: 36px 16px 4px 36px;
|
||||
border: 1px solid ${({ theme }) => theme.inputColor};
|
||||
color: ${({ theme }) => theme.primary};
|
||||
margin-left: 10px;
|
||||
border-radius: 36px 16px 4px 36px;
|
||||
outline: none;
|
||||
resize: none;
|
||||
|
||||
padding-top: 9px;
|
||||
padding-bottom: 9px;
|
||||
padding-left: 12px;
|
||||
padding-right: 112px;
|
||||
outline: none;
|
||||
resize: none;
|
||||
|
||||
font-family: Inter;
|
||||
font-style: normal;
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import { decode } from "html-entities";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import React, { useEffect, useMemo, useState } from "react";
|
||||
import styled from "styled-components";
|
||||
|
||||
import { ChatMessage } from "../../models/ChatMessage";
|
||||
import { Metadata } from "../../models/Metadata";
|
||||
import { Theme } from "../../styles/themes";
|
||||
|
||||
|
@ -11,16 +12,17 @@ const regEx =
|
|||
/* eslint-enable no-useless-escape */
|
||||
|
||||
type ChatMessageContentProps = {
|
||||
content: string;
|
||||
message: ChatMessage;
|
||||
theme: Theme;
|
||||
fetchMetadata?: (url: string) => Promise<Metadata | undefined>;
|
||||
};
|
||||
|
||||
export function ChatMessageContent({
|
||||
content,
|
||||
message,
|
||||
theme,
|
||||
fetchMetadata,
|
||||
}: ChatMessageContentProps) {
|
||||
const { content, image } = useMemo(() => message, [message]);
|
||||
const [elements, setElements] = useState<(string | React.ReactElement)[]>([
|
||||
content,
|
||||
]);
|
||||
|
@ -75,10 +77,11 @@ export function ChatMessageContent({
|
|||
};
|
||||
updatePreview();
|
||||
}, [link]);
|
||||
if (openGraph) {
|
||||
return (
|
||||
<ContentWrapper>
|
||||
<div>{elements.map((el) => el)}</div>
|
||||
return (
|
||||
<ContentWrapper>
|
||||
<div>{elements.map((el) => el)}</div>
|
||||
{image && <MessageImage src={image} />}
|
||||
{openGraph && (
|
||||
<PreviewWrapper
|
||||
onClick={() => window?.open(link, "_blank", "noopener")?.focus()}
|
||||
>
|
||||
|
@ -88,13 +91,18 @@ export function ChatMessageContent({
|
|||
{openGraph["og:site_name"]}
|
||||
</PreviewSiteNameWrapper>
|
||||
</PreviewWrapper>
|
||||
</ContentWrapper>
|
||||
);
|
||||
} else {
|
||||
return <>{elements.map((el) => el)}</>;
|
||||
}
|
||||
)}
|
||||
</ContentWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
const MessageImage = styled.img`
|
||||
width: 147px;
|
||||
height: 196px;
|
||||
border-radius: 16px;
|
||||
margin-top: 8px;
|
||||
`;
|
||||
|
||||
const PreviewSiteNameWrapper = styled.div`
|
||||
font-family: Inter;
|
||||
font-style: normal;
|
||||
|
|
|
@ -78,7 +78,7 @@ export function ChatMessages({
|
|||
</MessageHeaderWrapper>
|
||||
<MessageText theme={theme}>
|
||||
<ChatMessageContent
|
||||
content={message.content}
|
||||
message={message}
|
||||
theme={theme}
|
||||
fetchMetadata={fetchMetadata}
|
||||
/>
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
export function uintToImgUrl(img: Uint8Array) {
|
||||
const blob = new Blob([img], { type: "image/png" });
|
||||
return URL.createObjectURL(blob);
|
||||
}
|
|
@ -4,6 +4,7 @@ import { useCallback, useEffect, useState } from "react";
|
|||
import { Identity, Messenger } from "status-communities/dist/cjs";
|
||||
import { ApplicationMetadataMessage } from "status-communities/dist/cjs/application_metadata_message";
|
||||
|
||||
import { uintToImgUrl } from "../helpers/uintToImgUrl";
|
||||
import { ChatMessage } from "../models/ChatMessage";
|
||||
|
||||
function binarySetInsert<T>(
|
||||
|
@ -51,9 +52,15 @@ export function useMessenger(chatId: string, chatIdList: string[]) {
|
|||
}, []);
|
||||
|
||||
const addNewMessageRaw = useCallback(
|
||||
(signer: Uint8Array, content: string, date: Date, id: string) => {
|
||||
(
|
||||
signer: Uint8Array,
|
||||
content: string,
|
||||
date: Date,
|
||||
id: string,
|
||||
image?: string
|
||||
) => {
|
||||
const sender = signer.reduce((p, c) => p + c.toString(16), "0x");
|
||||
const newMessage = { sender, content, date };
|
||||
const newMessage = { sender, content, date, image };
|
||||
setMessages((prev) => {
|
||||
return {
|
||||
...prev,
|
||||
|
@ -77,10 +84,18 @@ export function useMessenger(chatId: string, chatIdList: string[]) {
|
|||
|
||||
const addNewMessage = useCallback(
|
||||
(msg: ApplicationMetadataMessage, id: string) => {
|
||||
if (msg.signer && msg.chatMessage?.text && msg.chatMessage.clock) {
|
||||
const content = msg.chatMessage.text;
|
||||
if (
|
||||
msg.signer &&
|
||||
(msg.chatMessage?.text || msg.chatMessage?.image) &&
|
||||
msg.chatMessage.clock
|
||||
) {
|
||||
const content = msg.chatMessage.text ?? "";
|
||||
let img: string | undefined = undefined;
|
||||
if (msg.chatMessage?.image) {
|
||||
img = uintToImgUrl(msg.chatMessage?.image.payload);
|
||||
}
|
||||
const date = new Date(msg.chatMessage.clock);
|
||||
addNewMessageRaw(msg.signer, content, date, id);
|
||||
addNewMessageRaw(msg.signer, content, date, id, img);
|
||||
}
|
||||
},
|
||||
[addNewMessageRaw]
|
||||
|
@ -141,7 +156,6 @@ export function useMessenger(chatId: string, chatIdList: string[]) {
|
|||
|
||||
const loadNextDay = useCallback(
|
||||
(id: string) => {
|
||||
console.log(id);
|
||||
if (messenger) {
|
||||
const endTime = lastLoadTime[id];
|
||||
const startTime = new Date();
|
||||
|
@ -163,13 +177,23 @@ export function useMessenger(chatId: string, chatIdList: string[]) {
|
|||
);
|
||||
|
||||
const sendMessage = useCallback(
|
||||
async (messageText: string) => {
|
||||
await messenger?.sendMessage(messageText, chatId);
|
||||
async (messageText: string, image?: Uint8Array) => {
|
||||
let mediaContent = undefined;
|
||||
if (image) {
|
||||
mediaContent = {
|
||||
image,
|
||||
imageType: 1,
|
||||
contentType: 1,
|
||||
};
|
||||
}
|
||||
await messenger?.sendMessage(messageText, chatId, mediaContent);
|
||||
|
||||
addNewMessageRaw(
|
||||
messenger?.identity.publicKey ?? new Uint8Array(),
|
||||
messageText,
|
||||
new Date(),
|
||||
chatId
|
||||
chatId,
|
||||
image ? uintToImgUrl(image) : undefined
|
||||
);
|
||||
},
|
||||
[chatId, messenger]
|
||||
|
|
|
@ -2,4 +2,5 @@ export type ChatMessage = {
|
|||
content: string;
|
||||
date: Date;
|
||||
sender: string;
|
||||
image?: string;
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue