refactor: replace image message with file message
This commit is contained in:
+2
-3
@@ -1,12 +1,10 @@
|
||||
// const BASE_URL = `${location.origin}/api`;
|
||||
const BASE_URL = `https://dev.rustchat.com/api`;
|
||||
export const CACHE_VERSION = `0.3.2`;
|
||||
export const CACHE_VERSION = `0.3.3`;
|
||||
// const BASE_URL = `https://rustchat.net/api`;
|
||||
export const ContentTypes = {
|
||||
text: "text/plain",
|
||||
markdown: "text/markdown",
|
||||
image: "image/png",
|
||||
imageJPG: "image/jpeg",
|
||||
file: "rustchat/file",
|
||||
formData: "multipart/form-data",
|
||||
json: "application/json",
|
||||
@@ -26,6 +24,7 @@ export const googleClientID =
|
||||
// "840319286941-6ds7lbvk55eq8mjortf68cb2ll65lprt.apps.googleusercontent.com";
|
||||
export const tokenHeader = "X-API-Key";
|
||||
export const FILE_SLICE_SIZE = 1000 * 200 * 8; //200kb
|
||||
export const FILE_IMAGE_SIZE = 1000 * 10000 * 8; //10mb
|
||||
export const KEY_TOKEN = "RUSTCHAT_TOKEN";
|
||||
export const KEY_EXPIRE = "RUSTCHAT_TOKEN_EXPIRE";
|
||||
export const KEY_REFRESH_TOKEN = "RUSTCHAT_REFRESH_TOKEN";
|
||||
|
||||
@@ -46,6 +46,33 @@ export const channelApi = createApi({
|
||||
}
|
||||
},
|
||||
}),
|
||||
getHistoryMessages: builder.query({
|
||||
query: ({ gid, mid = 0, limit = 50 }) => ({
|
||||
url: `/group/${gid}/history?before=${mid}&limit=${limit}`,
|
||||
}),
|
||||
// async onQueryStarted(id, { dispatch, getState, queryFulfilled }) {
|
||||
// const {
|
||||
// ui: { channelSetting },
|
||||
// channelMessage,
|
||||
// } = getState();
|
||||
// try {
|
||||
// await queryFulfilled;
|
||||
// dispatch(removeChannel(id));
|
||||
// if (id == channelSetting) {
|
||||
// dispatch(toggleChannelSetting());
|
||||
// }
|
||||
// // 删掉该channel下的所有消息&reaction
|
||||
// const mids = channelMessage[id];
|
||||
// if (mids) {
|
||||
// dispatch(removeChannelSession(id));
|
||||
// dispatch(removeMessage(mids));
|
||||
// dispatch(removeReactionMessage(mids));
|
||||
// }
|
||||
// } catch {
|
||||
// console.log("remove channel error");
|
||||
// }
|
||||
// },
|
||||
}),
|
||||
removeChannel: builder.query({
|
||||
query: (id) => ({
|
||||
url: `group/${id}`,
|
||||
@@ -82,7 +109,7 @@ export const channelApi = createApi({
|
||||
},
|
||||
url: `group/${id}/send`,
|
||||
method: "POST",
|
||||
body: content,
|
||||
body: type == "file" ? JSON.stringify(content) : content,
|
||||
}),
|
||||
async onQueryStarted(param1, param2) {
|
||||
await onMessageSendStarted.call(this, param1, param2, "channel");
|
||||
@@ -106,6 +133,7 @@ export const channelApi = createApi({
|
||||
});
|
||||
|
||||
export const {
|
||||
useLazyGetHistoryMessagesQuery,
|
||||
useGetChannelQuery,
|
||||
useUpdateChannelMutation,
|
||||
useLazyRemoveChannelQuery,
|
||||
|
||||
@@ -95,7 +95,7 @@ export const contactApi = createApi({
|
||||
},
|
||||
url: `user/${id}/send`,
|
||||
method: "POST",
|
||||
body: content,
|
||||
body: type == "file" ? JSON.stringify(content) : content,
|
||||
}),
|
||||
async onQueryStarted(param1, param2) {
|
||||
await onMessageSendStarted.call(this, param1, param2, "user");
|
||||
|
||||
@@ -17,6 +17,8 @@ export const onMessageSendStarted = async (
|
||||
from = "channel"
|
||||
) => {
|
||||
// id: who send to ,from_uid: who sent
|
||||
console.log("handlers data", content, type, properties);
|
||||
const isImage = properties.file_type?.startsWith("image");
|
||||
const ts = properties.local_id || new Date().getTime();
|
||||
// let imageData = null;
|
||||
// if (type == "image") {
|
||||
@@ -31,7 +33,7 @@ export const onMessageSendStarted = async (
|
||||
// }
|
||||
// }
|
||||
const tmpMsg = {
|
||||
content: type == "image" ? URL.createObjectURL(content) : content,
|
||||
content: isImage ? content.path : content,
|
||||
content_type: ContentTypes[type],
|
||||
created_at: ts,
|
||||
properties,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { createApi } from "@reduxjs/toolkit/query/react";
|
||||
// import { batch } from "react-redux";
|
||||
|
||||
import BASE_URL, { ContentTypes } from "../config";
|
||||
import { ContentTypes } from "../config";
|
||||
import { updateReadChannels, updateReadUsers } from "../slices/footprint";
|
||||
import { onMessageSendStarted } from "./handlers";
|
||||
|
||||
@@ -59,19 +59,6 @@ export const messageApi = createApi({
|
||||
return data ? data : {};
|
||||
},
|
||||
}),
|
||||
uploadImage: builder.mutation({
|
||||
query: (image) => ({
|
||||
headers: {
|
||||
"content-type": ContentTypes.image,
|
||||
},
|
||||
url: `/resource/image`,
|
||||
method: "POST",
|
||||
body: image,
|
||||
}),
|
||||
transformResponse: (image_id) => {
|
||||
return `${BASE_URL}/resource/image?id=${encodeURIComponent(image_id)}`;
|
||||
},
|
||||
}),
|
||||
replyMessage: builder.mutation({
|
||||
query: ({ reply_mid, content, type = "text" }) => ({
|
||||
headers: {
|
||||
@@ -93,29 +80,16 @@ export const messageApi = createApi({
|
||||
}),
|
||||
async onQueryStarted(data, { dispatch, queryFulfilled }) {
|
||||
const { users = null, groups = null } = data;
|
||||
// const { readUsers, readChannels } = getState().footprint;
|
||||
// const prevUsers = users.map(({ uid }) => {
|
||||
// return { uid, mid: readUsers[uid] };
|
||||
// });
|
||||
// const prevChannels = users.map(({ gid }) => {
|
||||
// return { gid, mid: readChannels[gid] };
|
||||
// });
|
||||
// batch(() => {
|
||||
if (users) {
|
||||
dispatch(updateReadUsers(users));
|
||||
}
|
||||
if (groups) {
|
||||
dispatch(updateReadChannels(groups));
|
||||
}
|
||||
// });
|
||||
try {
|
||||
await queryFulfilled;
|
||||
} catch {
|
||||
// todo
|
||||
// batch(() => {
|
||||
// dispatch(updateReadChannels(prevChannels));
|
||||
// dispatch(updateReadUsers(prevUsers));
|
||||
// });
|
||||
}
|
||||
},
|
||||
}),
|
||||
@@ -125,7 +99,6 @@ export const messageApi = createApi({
|
||||
export const {
|
||||
usePrepareUploadFileMutation,
|
||||
useUploadFileMutation,
|
||||
useUploadImageMutation,
|
||||
useEditMessageMutation,
|
||||
useReactMessageMutation,
|
||||
useReplyMessageMutation,
|
||||
|
||||
+12
-13
@@ -1,5 +1,6 @@
|
||||
import { createSlice } from "@reduxjs/toolkit";
|
||||
import BASE_URL, { ContentTypes } from "../config";
|
||||
import { isImage } from "../../common/utils";
|
||||
const initialState = {
|
||||
replying: {},
|
||||
};
|
||||
@@ -19,27 +20,25 @@ const messageSlice = createSlice({
|
||||
},
|
||||
addMessage(state, action) {
|
||||
const data = action.payload;
|
||||
const { mid, sending, content_type, content } = data;
|
||||
const { mid, sending, content_type, content, properties = {} } = data;
|
||||
// 如果是正发送,并且已存在,则不覆盖
|
||||
if (sending && state[mid]) return;
|
||||
const isImage = content_type.startsWith("image");
|
||||
const isFile = content_type == ContentTypes.file;
|
||||
// file
|
||||
if (!sending && isFile) {
|
||||
data.file_path = content;
|
||||
data.content = `${BASE_URL}/resource/file?file_path=${encodeURIComponent(
|
||||
data.file_path
|
||||
)}`;
|
||||
}
|
||||
// image
|
||||
if (!sending && isImage) {
|
||||
data.image_id = content;
|
||||
data.content = `${BASE_URL}/resource/image?id=${encodeURIComponent(
|
||||
data.image_id
|
||||
)}`;
|
||||
data.thumbnail = `${BASE_URL}/resource/thumbnail?id=${encodeURIComponent(
|
||||
data.image_id
|
||||
)}`;
|
||||
data.download = `${BASE_URL}/resource/file?file_path=${encodeURIComponent(
|
||||
data.file_path
|
||||
)}&download=true`;
|
||||
// image
|
||||
const isPic = isImage(properties.file_type, properties.size);
|
||||
if (isPic) {
|
||||
data.thumbnail = `${BASE_URL}/resource/file?file_path=${encodeURIComponent(
|
||||
data.file_path
|
||||
)}&thumbnail=true`;
|
||||
}
|
||||
}
|
||||
state[mid] = data;
|
||||
},
|
||||
|
||||
@@ -6,22 +6,22 @@ import "@toast-ui/editor-plugin-code-syntax-highlight/dist/toastui-editor-plugin
|
||||
import codeSyntaxHighlight from "@toast-ui/editor-plugin-code-syntax-highlight/dist/toastui-editor-plugin-code-syntax-highlight-all.js";
|
||||
|
||||
import StyledWrapper from "./styled";
|
||||
import { useUploadImageMutation } from "../../../app/services/message";
|
||||
import useUploadFile from "../../hook/useUploadFile";
|
||||
|
||||
import Button from "../../component/styled/Button";
|
||||
|
||||
function MarkdownEditor({ placeholder, sendMarkdown, setEditorInstance }) {
|
||||
const editorRef = useRef(undefined);
|
||||
const { uploadFile } = useUploadFile();
|
||||
// const [pHolder, setPHolder] = useState(placeholder);
|
||||
const [uploadImage] = useUploadImageMutation();
|
||||
useEffect(() => {
|
||||
const editor = editorRef?.current;
|
||||
if (editor) {
|
||||
const editorInstance = editor.getInstance();
|
||||
editorInstance.removeHook("addImageBlobHook");
|
||||
editorInstance.addHook("addImageBlobHook", async (blob, callback) => {
|
||||
const { data: url } = await uploadImage(blob);
|
||||
callback(url);
|
||||
const { thumbnail = "" } = await uploadFile(blob);
|
||||
callback(thumbnail);
|
||||
});
|
||||
setEditorInstance(editorInstance);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useSelector } from "react-redux";
|
||||
import MrakdownRender from "../MrakdownRender";
|
||||
|
||||
import { ContentTypes } from "../../../app/config";
|
||||
import { getFileIcon } from "../../utils";
|
||||
import { getFileIcon, isImage } from "../../utils";
|
||||
import Avatar from "../Avatar";
|
||||
const Styled = styled.div`
|
||||
cursor: pointer;
|
||||
@@ -98,20 +98,20 @@ const renderContent = (data) => {
|
||||
</div>
|
||||
);
|
||||
break;
|
||||
case ContentTypes.image:
|
||||
case ContentTypes.imageJPG:
|
||||
res = <img className="pic" src={thumbnail} />;
|
||||
break;
|
||||
case ContentTypes.file:
|
||||
{
|
||||
const { file_type, name } = properties;
|
||||
const { file_type, name, size } = properties;
|
||||
const icon = getFileIcon(file_type, name);
|
||||
res = (
|
||||
<>
|
||||
{icon}
|
||||
<span className="file_name">{name}</span>
|
||||
</>
|
||||
);
|
||||
if (isImage(file_type, size)) {
|
||||
res = <img className="pic" src={thumbnail} />;
|
||||
} else {
|
||||
res = (
|
||||
<>
|
||||
{icon}
|
||||
<span className="file_name">{name}</span>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import Linkit from "react-linkify";
|
||||
import dayjs from "dayjs";
|
||||
import Mention from "./Mention";
|
||||
import MrakdownRender from "../MrakdownRender";
|
||||
import { getDefaultSize } from "../../utils";
|
||||
import { getDefaultSize, isImage } from "../../utils";
|
||||
import FileBox from "../FileBox";
|
||||
import URLPreview from "./URLPreview";
|
||||
import reactStringReplace from "react-string-replace";
|
||||
@@ -68,35 +68,38 @@ const renderContent = ({
|
||||
ctn = <MrakdownRender content={content} />;
|
||||
}
|
||||
break;
|
||||
case "image/png":
|
||||
case "image/jpeg":
|
||||
{
|
||||
const { name, size, type } = properties;
|
||||
const { width, height } = getDefaultSize(properties);
|
||||
ctn = (
|
||||
<img
|
||||
className="img preview"
|
||||
style={{ width: `${width}px`, height: `${height}px` }}
|
||||
data-meta={JSON.stringify({ width, height, name, type, size })}
|
||||
data-origin={content}
|
||||
src={thumbnail || content}
|
||||
/>
|
||||
);
|
||||
}
|
||||
break;
|
||||
case "rustchat/file":
|
||||
{
|
||||
const { size, name, file_type } = properties;
|
||||
ctn = (
|
||||
<FileBox
|
||||
from_uid={from_uid}
|
||||
created_at={created_at}
|
||||
content={content}
|
||||
size={size}
|
||||
name={name}
|
||||
file_type={file_type}
|
||||
/>
|
||||
);
|
||||
if (isImage(file_type, size)) {
|
||||
const { width, height } = getDefaultSize(properties);
|
||||
ctn = (
|
||||
<img
|
||||
className="img preview"
|
||||
style={{ width: `${width}px`, height: `${height}px` }}
|
||||
data-meta={JSON.stringify({
|
||||
width,
|
||||
height,
|
||||
name,
|
||||
file_type,
|
||||
size,
|
||||
})}
|
||||
data-origin={content}
|
||||
src={thumbnail || content}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
ctn = (
|
||||
<FileBox
|
||||
from_uid={from_uid}
|
||||
created_at={created_at}
|
||||
content={content}
|
||||
size={size}
|
||||
name={name}
|
||||
file_type={file_type}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ import Styled from "./styled";
|
||||
import ImageElement from "./ImageElement";
|
||||
import { CONFIG } from "./config";
|
||||
import Contact from "../Contact";
|
||||
import useUploadFile from "../../hook/useUploadFile";
|
||||
// import Mention from "./Mention";
|
||||
export const TEXT_EDITOR_PREFIX = "rustchat_text_editor";
|
||||
export const setEditorFocus = (edtr) => {
|
||||
@@ -55,6 +56,9 @@ const Plugins = ({
|
||||
sendMessages,
|
||||
members = [],
|
||||
}) => {
|
||||
const enableMentions = members.length > 0;
|
||||
const { uploadFile } = useUploadFile();
|
||||
const filesRef = useRef([]);
|
||||
// const plateEditor = usePlateEditorRef(`${TEXT_EDITOR_PREFIX}_${id}`);
|
||||
const contactData = useSelector((store) => store.contacts.byId);
|
||||
const [msgs, setMsgs] = useState([]);
|
||||
@@ -66,6 +70,7 @@ const Plugins = ({
|
||||
className: "box",
|
||||
placeholder,
|
||||
};
|
||||
|
||||
useKey(
|
||||
"Enter",
|
||||
(evt) => {
|
||||
@@ -96,36 +101,44 @@ const Plugins = ({
|
||||
target: editableRef,
|
||||
}
|
||||
);
|
||||
const plugins = createPlugins(
|
||||
[
|
||||
createParagraphPlugin(),
|
||||
createImagePlugin(),
|
||||
createNodeIdPlugin(),
|
||||
createSoftBreakPlugin(CONFIG.softBreak),
|
||||
createTrailingBlockPlugin(CONFIG.trailingBlock),
|
||||
createExitBreakPlugin(CONFIG.exitBreak),
|
||||
createComboboxPlugin(),
|
||||
createMentionPlugin({
|
||||
// component: Mention,
|
||||
// handlers: {
|
||||
// onKeyDown: ({ query }) => {
|
||||
// console.log("mention", query);
|
||||
// return true;
|
||||
// },
|
||||
// },
|
||||
options: {
|
||||
createMentionNode: (item) => {
|
||||
console.log("mention", item);
|
||||
const {
|
||||
text,
|
||||
data: { uid },
|
||||
} = item;
|
||||
return { value: `@${text}`, uid };
|
||||
},
|
||||
insertSpaceAfterMention: true,
|
||||
const pluginArr = [
|
||||
createParagraphPlugin(),
|
||||
createImagePlugin({
|
||||
options: {
|
||||
uploadImage: async (dataUrl) => {
|
||||
const resp = await fetch(dataUrl);
|
||||
const blob = await resp.blob();
|
||||
const { thumbnail, ...rest } = await uploadFile(blob);
|
||||
const { name, file_type, size, path, hash } = rest;
|
||||
filesRef.current.push({ name, file_type, size, path, hash });
|
||||
return thumbnail;
|
||||
},
|
||||
}),
|
||||
],
|
||||
},
|
||||
}),
|
||||
createNodeIdPlugin(),
|
||||
createSoftBreakPlugin(CONFIG.softBreak),
|
||||
createTrailingBlockPlugin(CONFIG.trailingBlock),
|
||||
createExitBreakPlugin(CONFIG.exitBreak),
|
||||
];
|
||||
const plugins = createPlugins(
|
||||
enableMentions
|
||||
? pluginArr.concat([
|
||||
createComboboxPlugin(),
|
||||
createMentionPlugin({
|
||||
options: {
|
||||
createMentionNode: (item) => {
|
||||
console.log("mention", item);
|
||||
const {
|
||||
text,
|
||||
data: { uid },
|
||||
} = item;
|
||||
return { value: `@${text}`, uid };
|
||||
},
|
||||
insertSpaceAfterMention: true,
|
||||
},
|
||||
}),
|
||||
])
|
||||
: pluginArr,
|
||||
{
|
||||
components,
|
||||
}
|
||||
@@ -146,27 +159,40 @@ const Plugins = ({
|
||||
});
|
||||
return { value: arr.join(""), mentions };
|
||||
};
|
||||
for await (const v of val) {
|
||||
for (const v of val) {
|
||||
if (v.type == "img") {
|
||||
// img
|
||||
const resp = await fetch(v.url);
|
||||
const value = await resp.blob();
|
||||
tmps.push({ type: "image", value });
|
||||
const url = v.url;
|
||||
const file_path = decodeURIComponent(
|
||||
new URL(url).searchParams.get("file_path")
|
||||
);
|
||||
console.log("files", filesRef.current, file_path);
|
||||
const json = filesRef.current.find((f) => f.path == file_path) || {};
|
||||
const { name, size, hash, path, ...rest } = json;
|
||||
tmps.push({
|
||||
type: "file",
|
||||
content: { name, size, hash, path },
|
||||
properties: rest,
|
||||
});
|
||||
} else {
|
||||
// p
|
||||
const { value, mentions } = getMixedText(v.children);
|
||||
const prev = tmps[tmps.length - 1];
|
||||
if (!prev) {
|
||||
tmps.push([{ type: "text", value, mentions }]);
|
||||
tmps.push([
|
||||
{ type: "text", content: value, properties: { mentions } },
|
||||
]);
|
||||
} else {
|
||||
if (Array.isArray(prev)) {
|
||||
tmps[tmps.length - 1].push({
|
||||
type: "text",
|
||||
value,
|
||||
mentions,
|
||||
content: value,
|
||||
properties: { mentions },
|
||||
});
|
||||
} else {
|
||||
tmps.push([{ type: "text", value, mentions }]);
|
||||
tmps.push([
|
||||
{ type: "text", content: value, properties: { mentions } },
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -175,14 +201,14 @@ const Plugins = ({
|
||||
return Array.isArray(tmp)
|
||||
? {
|
||||
type: "text",
|
||||
value: tmp.map((t) => t.value).join("\n"),
|
||||
mentions: tmp.map((t) => t.mentions).flat(),
|
||||
content: tmp.map((t) => t.content).join("\n"),
|
||||
properties: { mentions: tmp.map((t) => t.mentions).flat() },
|
||||
}
|
||||
: tmp;
|
||||
});
|
||||
const msgs = arr.filter(({ value }) => !!value);
|
||||
const msgs = arr.filter(({ content }) => !!content);
|
||||
setMsgs(msgs);
|
||||
console.log("tmps", val, tmps, msgs);
|
||||
console.log("tmps", tmps, arr, msgs);
|
||||
},
|
||||
[msgs]
|
||||
);
|
||||
@@ -197,26 +223,28 @@ const Plugins = ({
|
||||
initialValue={initialValue}
|
||||
plugins={plugins}
|
||||
>
|
||||
<MentionCombobox
|
||||
// component={StyledCombobox}
|
||||
onRenderItem={({ item }) => {
|
||||
console.log("wtf", item);
|
||||
return <Contact uid={item.data.uid} interactive={false} />;
|
||||
}}
|
||||
items={members.map((id) => {
|
||||
const data = contactData[id];
|
||||
if (!data) return null;
|
||||
const { uid, name, ...rest } = data;
|
||||
return {
|
||||
key: uid,
|
||||
text: name,
|
||||
data: {
|
||||
uid,
|
||||
...rest,
|
||||
},
|
||||
};
|
||||
})}
|
||||
/>
|
||||
{enableMentions ? (
|
||||
<MentionCombobox
|
||||
// component={StyledCombobox}
|
||||
onRenderItem={({ item }) => {
|
||||
console.log("wtf", item);
|
||||
return <Contact uid={item.data.uid} interactive={false} />;
|
||||
}}
|
||||
items={members.map((id) => {
|
||||
const data = contactData[id];
|
||||
if (!data) return null;
|
||||
const { uid, name, ...rest } = data;
|
||||
return {
|
||||
key: uid,
|
||||
text: name,
|
||||
data: {
|
||||
uid,
|
||||
...rest,
|
||||
},
|
||||
};
|
||||
})}
|
||||
/>
|
||||
) : null}
|
||||
</Plate>
|
||||
</Styled>
|
||||
);
|
||||
|
||||
@@ -34,10 +34,15 @@ export default function MrakdownRender({ content }) {
|
||||
"click",
|
||||
(evt) => {
|
||||
console.log(evt);
|
||||
evt.stopPropagation();
|
||||
const { target } = evt;
|
||||
// 图片
|
||||
if (target.nodeName == "IMG") {
|
||||
const data = { originUrl: target.dataset.origin || target.src };
|
||||
const urlObj = new URL(target.src);
|
||||
const originUrl = `${urlObj.origin}${
|
||||
urlObj.pathname
|
||||
}?file_path=${urlObj.searchParams.get("file_path")}`;
|
||||
const data = { originUrl };
|
||||
setPreviewImage(data);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -4,7 +4,7 @@ import { ContentTypes } from "../../../app/config";
|
||||
import MrakdownRender from "../MrakdownRender";
|
||||
import closeIcon from "../../../assets/icons/close.circle.svg?url";
|
||||
import pictureIcon from "../../../assets/icons/picture.svg?url";
|
||||
import { getFileIcon } from "../../utils";
|
||||
import { getFileIcon, isImage } from "../../utils";
|
||||
import { removeReplyingMessage } from "../../../app/slices/message";
|
||||
import styled from "styled-components";
|
||||
const Styled = styled.div`
|
||||
@@ -97,20 +97,20 @@ const renderContent = (data) => {
|
||||
</div>
|
||||
);
|
||||
break;
|
||||
case ContentTypes.image:
|
||||
case ContentTypes.imageJPG:
|
||||
res = <img className="pic" src={pictureIcon} />;
|
||||
break;
|
||||
case ContentTypes.file:
|
||||
{
|
||||
const { file_type, name } = properties;
|
||||
const icon = getFileIcon(file_type, name);
|
||||
res = (
|
||||
<>
|
||||
{icon}
|
||||
<span className="name">{name}</span>
|
||||
</>
|
||||
);
|
||||
const { file_type, name, size } = properties;
|
||||
if (isImage(file_type, size)) {
|
||||
res = <img className="pic" src={pictureIcon} />;
|
||||
} else {
|
||||
const icon = getFileIcon(file_type, name);
|
||||
res = (
|
||||
<>
|
||||
{icon}
|
||||
<span className="name">{name}</span>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
|
||||
@@ -75,7 +75,8 @@ function Send({
|
||||
const handleSendMessage = async (msgs = []) => {
|
||||
if (!msgs || msgs.length == 0 || !id) return;
|
||||
for await (const msg of msgs) {
|
||||
const { type: content_type, value: content, mentions = [] } = msg;
|
||||
console.log("send msg", msg);
|
||||
const { type: content_type, content, properties = {} } = msg;
|
||||
if (replying_mid) {
|
||||
console.log("replying", replying_mid);
|
||||
await replyMessage({
|
||||
@@ -93,7 +94,7 @@ function Send({
|
||||
type: content_type,
|
||||
content,
|
||||
from_uid,
|
||||
properties: { local_id: new Date().getTime(), mentions },
|
||||
properties,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { useEffect } from "react";
|
||||
import { useSelector } from "react-redux";
|
||||
import FileItem from "./FileItem";
|
||||
import useSendImageMessage from "../../hook/useSendImageMessage";
|
||||
import useSendFileMessage from "../../hook/useSendFileMessage";
|
||||
import useUploadFile from "../../hook/useUploadFile";
|
||||
import Modal from "../Modal";
|
||||
import Button from "../styled/Button";
|
||||
import { isTreatAsImage } from "../../utils";
|
||||
// import { isTreatAsImage } from "../../utils";
|
||||
import StyledWrapper from "./styled";
|
||||
import useSendMessage from "../../hook/useSendMessage";
|
||||
|
||||
export default function UploadModal({
|
||||
context = "user",
|
||||
@@ -16,42 +16,45 @@ export default function UploadModal({
|
||||
}) {
|
||||
const from_uid = useSelector((store) => store.authData.uid);
|
||||
const {
|
||||
sendImageMessage,
|
||||
isSending: isSendingImage,
|
||||
isSuccess: sendImageSuccess,
|
||||
} = useSendImageMessage({
|
||||
sendMessage,
|
||||
isSuccess: sendMessageSuccess,
|
||||
isSending,
|
||||
} = useSendMessage({
|
||||
context,
|
||||
from: from_uid,
|
||||
to: sendTo,
|
||||
});
|
||||
const {
|
||||
sendFileMessage,
|
||||
data,
|
||||
uploadFile,
|
||||
progress,
|
||||
isSending: isSendingFile,
|
||||
isSuccess: sendFileSuccess,
|
||||
} = useSendFileMessage({
|
||||
context,
|
||||
from: from_uid,
|
||||
to: sendTo,
|
||||
});
|
||||
isUploading,
|
||||
isSuccess: uploadSuccess,
|
||||
} = useUploadFile();
|
||||
const handleUpload = () => {
|
||||
const file = files[0];
|
||||
// const { type } = file;
|
||||
if (isTreatAsImage(file)) {
|
||||
sendImageMessage(file);
|
||||
} else {
|
||||
sendFileMessage(file);
|
||||
}
|
||||
uploadFile(file);
|
||||
};
|
||||
useEffect(() => {
|
||||
if (sendFileSuccess || sendImageSuccess) {
|
||||
if (uploadSuccess) {
|
||||
// 把已经上传的东西当做消息发出去
|
||||
const { size, path, name, hash, ...rest } = data;
|
||||
sendMessage({
|
||||
type: "file",
|
||||
content: { size, name, path, hash },
|
||||
properties: rest,
|
||||
});
|
||||
}
|
||||
}, [uploadSuccess, data]);
|
||||
useEffect(() => {
|
||||
if (sendMessageSuccess) {
|
||||
closeModal();
|
||||
}
|
||||
}, [sendImageSuccess, sendFileSuccess]);
|
||||
}, [sendMessageSuccess]);
|
||||
|
||||
if (!sendTo) return null;
|
||||
console.log("upload file modal", files, sendTo);
|
||||
const isSending = isSendingFile || isSendingImage;
|
||||
const sending = isUploading || isSending;
|
||||
return (
|
||||
<Modal>
|
||||
<StyledWrapper
|
||||
@@ -64,12 +67,10 @@ export default function UploadModal({
|
||||
</Button>
|
||||
<Button
|
||||
className="upload"
|
||||
disabled={isSending}
|
||||
disabled={sending}
|
||||
onClick={handleUpload}
|
||||
>
|
||||
{isSending
|
||||
? `Uploading (${Math.floor(progress * 100)}%)`
|
||||
: `Upload`}
|
||||
{sending ? `Uploading (${progress}%)` : `Upload`}
|
||||
</Button>
|
||||
</>
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// import second from 'first'
|
||||
import { useSendChannelMsgMutation } from "../../app/services/channel";
|
||||
import { useSendMsgMutation } from "../../app/services/contact";
|
||||
export default function useSendImageMessage({
|
||||
export default function useSendMessage({
|
||||
context = "user",
|
||||
from = null,
|
||||
to = null,
|
||||
@@ -18,20 +18,18 @@ export default function useSendImageMessage({
|
||||
sendUserMsg,
|
||||
{ isLoading: userSending, isSuccess: userSuccess, isError: userError },
|
||||
] = useSendMsgMutation();
|
||||
const uploadFn = context == "user" ? sendUserMsg : sendChannelMsg;
|
||||
const sendImageMessage = (file) => {
|
||||
if (!file) return;
|
||||
const { name, size, type } = file;
|
||||
uploadFn({
|
||||
const sendFn = context == "user" ? sendUserMsg : sendChannelMsg;
|
||||
const sendMessage = ({ type = "text", content, properties = {} }) => {
|
||||
sendFn({
|
||||
id: to,
|
||||
content: file,
|
||||
properties: { name, size, type, local_id: new Date().getTime() },
|
||||
type: "image",
|
||||
content,
|
||||
properties: { ...properties, local_id: new Date().getTime() },
|
||||
type,
|
||||
from_uid: from,
|
||||
});
|
||||
};
|
||||
return {
|
||||
sendImageMessage,
|
||||
sendMessage,
|
||||
isError: channelError || userError,
|
||||
isSending: userSending || channelSending,
|
||||
isSuccess: channelSuccess || userSuccess,
|
||||
@@ -1,57 +1,41 @@
|
||||
import { useState, useRef } from "react";
|
||||
// import { ContentTypes } from "../../app/config";
|
||||
// import { sliceFile } from "../utils";
|
||||
import { FILE_SLICE_SIZE } from "../../app/config";
|
||||
import BASE_URL, { FILE_SLICE_SIZE } from "../../app/config";
|
||||
import {
|
||||
usePrepareUploadFileMutation,
|
||||
useUploadFileMutation,
|
||||
} from "../../app/services/message";
|
||||
import { useSendMsgMutation } from "../../app/services/contact";
|
||||
import { useSendChannelMsgMutation } from "../../app/services/channel";
|
||||
|
||||
export default function useUploadImageMessage({
|
||||
context = "user",
|
||||
from = null,
|
||||
to = null,
|
||||
}) {
|
||||
// const slicedRef = useRef(false);
|
||||
export default function useUploadFile() {
|
||||
const [data, setData] = useState(null);
|
||||
const [uploadingFile, setUploadingFile] = useState(false);
|
||||
const sliceUploadedCountRef = useRef(0);
|
||||
const totalSliceCountRef = useRef(1);
|
||||
// const [uploadedSliceCount, setUploadedSliceCount] = useState(0)
|
||||
const [
|
||||
prepareUploadFile,
|
||||
{ isLoading: isPreparing, isSuccess: isPrepared },
|
||||
] = usePrepareUploadFileMutation();
|
||||
const [
|
||||
uploadFile,
|
||||
uploadFileFn,
|
||||
{ isLoading: isUploading, isSuccess: isUploaded, isError: uploadFileError },
|
||||
] = useUploadFileMutation();
|
||||
const [
|
||||
sendChannelMsg,
|
||||
{
|
||||
isLoading: channelSending,
|
||||
isSuccess: channelSuccess,
|
||||
isError: channelError,
|
||||
},
|
||||
] = useSendChannelMsgMutation();
|
||||
|
||||
const [
|
||||
sendUserMsg,
|
||||
{ isLoading: userSending, isSuccess: userSuccess, isError: userError },
|
||||
] = useSendMsgMutation();
|
||||
const sendFn = context == "user" ? sendUserMsg : sendChannelMsg;
|
||||
const uploadChunk = async (data) => {
|
||||
const { file_id, chunk, is_last } = data;
|
||||
const formData = new FormData();
|
||||
formData.append("file_id", file_id);
|
||||
formData.append("chunk_data", chunk);
|
||||
formData.append("chunk_is_last", is_last);
|
||||
return uploadFile(formData);
|
||||
return uploadFileFn(formData);
|
||||
};
|
||||
const sendFileMessage = async (file) => {
|
||||
const uploadFile = async (file) => {
|
||||
if (!file) return;
|
||||
const { name, type: file_type, size: file_size } = file;
|
||||
setData(null);
|
||||
const {
|
||||
name = `rustchat-${new Date().getTime()}.${file.type.split("/")[1]}`,
|
||||
type: file_type,
|
||||
size: file_size,
|
||||
} = file;
|
||||
// 拿file id
|
||||
const { data: file_id } = await prepareUploadFile({
|
||||
content_type: file_type,
|
||||
@@ -101,30 +85,30 @@ export default function useUploadImageMessage({
|
||||
const {
|
||||
data: { path, size, hash },
|
||||
} = uploadResult;
|
||||
const content = JSON.stringify({
|
||||
const encodedPath = encodeURIComponent(path);
|
||||
const res = {
|
||||
name,
|
||||
file_type,
|
||||
path,
|
||||
size,
|
||||
hash,
|
||||
path,
|
||||
});
|
||||
console.log("upload content", content);
|
||||
await sendFn({
|
||||
id: to,
|
||||
content,
|
||||
type: "file",
|
||||
properties: { file_type },
|
||||
from_uid: from,
|
||||
});
|
||||
url: `${BASE_URL}/resource/file?file_path=${encodedPath}`,
|
||||
thumbnail: file_type.startsWith("image")
|
||||
? `${BASE_URL}/resource/file?file_path=${encodedPath}&thumbnail=true`
|
||||
: "",
|
||||
download: `${BASE_URL}/resource/file?file_path=${encodedPath}&download=true`,
|
||||
};
|
||||
setData(res);
|
||||
return res;
|
||||
};
|
||||
const isSending =
|
||||
userSending || channelSending || isPreparing || uploadingFile;
|
||||
return {
|
||||
progress: isSending
|
||||
? sliceUploadedCountRef.current / totalSliceCountRef.current
|
||||
: 1,
|
||||
sendFileMessage,
|
||||
isError: channelError || userError || uploadFileError,
|
||||
isSending,
|
||||
isSuccess: (channelSuccess || userSuccess) && isPrepared,
|
||||
data,
|
||||
isUploading: uploadingFile,
|
||||
progress: Number(
|
||||
(sliceUploadedCountRef.current / totalSliceCountRef.current) * 100
|
||||
).toFixed(2),
|
||||
uploadFile,
|
||||
isError: uploadFileError,
|
||||
isSuccess: !!data,
|
||||
};
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import { FILE_IMAGE_SIZE } from "../app/config";
|
||||
import IconPdf from "../assets/icons/file.pdf.svg";
|
||||
import IconAudio from "../assets/icons/file.audio.svg";
|
||||
import IconVideo from "../assets/icons/file.video.svg";
|
||||
@@ -5,6 +6,9 @@ import IconUnkown from "../assets/icons/file.unkown.svg";
|
||||
import IconDoc from "../assets/icons/file.doc.svg";
|
||||
import IconCode from "../assets/icons/file.code.svg";
|
||||
import IconImage from "../assets/icons/file.image.svg";
|
||||
export const isImage = (file_type = "", size = 0) => {
|
||||
return file_type.startsWith("image") && size <= FILE_IMAGE_SIZE;
|
||||
};
|
||||
export const isObjectEqual = (obj1, obj2) => {
|
||||
let o1 = Object.entries(obj1 ?? {})
|
||||
.sort()
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React from "react";
|
||||
import dayjs from "dayjs";
|
||||
import { isImage } from "../../common/utils";
|
||||
import { ContentTypes } from "../../app/config";
|
||||
import Divider from "../../common/component/Divider";
|
||||
import Message from "../../common/component/Message";
|
||||
@@ -37,20 +38,13 @@ export function getUnreadCount({
|
||||
}
|
||||
export const renderPreviewMessage = (message = null) => {
|
||||
if (!message) return null;
|
||||
const { content_type, content } = message;
|
||||
const { content_type, content, properties = {} } = message;
|
||||
let res = null;
|
||||
switch (content_type) {
|
||||
case ContentTypes.text:
|
||||
{
|
||||
res = content;
|
||||
}
|
||||
break;
|
||||
case ContentTypes.imageJPG:
|
||||
case ContentTypes.image:
|
||||
{
|
||||
res = `[image]`;
|
||||
}
|
||||
|
||||
break;
|
||||
case ContentTypes.markdown:
|
||||
{
|
||||
@@ -60,7 +54,11 @@ export const renderPreviewMessage = (message = null) => {
|
||||
break;
|
||||
case ContentTypes.file:
|
||||
{
|
||||
res = `[file]`;
|
||||
if (isImage(properties.file_type, properties.size)) {
|
||||
res = `[image]`;
|
||||
} else {
|
||||
res = `[file]`;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
Reference in New Issue
Block a user