From bdc4ab2db1e0ca1c511c04d08a6298930d537627 Mon Sep 17 00:00:00 2001 From: zerosoul Date: Wed, 30 Mar 2022 10:17:48 +0800 Subject: [PATCH] feat: upload files and management --- src/app/cache/index.js | 4 + src/app/cache/useRehydrate.js | 7 ++ .../listener.middleware/handler.file.msg.js | 19 ++++ .../listener.middleware/handler.message.js | 20 ++-- src/app/listener.middleware/index.js | 13 ++- src/app/slices/message.file.js | 42 ++++++++ src/app/slices/message.js | 19 ---- src/app/store.js | 2 + src/assets/icons/chat.svg | 5 +- src/assets/icons/contact.svg | 5 +- src/assets/icons/file.grid.svg | 3 + src/assets/icons/file.list.svg | 3 + src/assets/icons/folder.svg | 3 + src/common/component/FileBox/index.js | 98 +++++++++++++++++++ src/common/component/FileBox/preview/Audio.js | 6 ++ src/common/component/FileBox/preview/Code.js | 26 +++++ src/common/component/FileBox/preview/Image.js | 19 ++++ src/common/component/FileBox/preview/Pdf.js | 28 ++++++ src/common/component/FileBox/preview/Video.js | 18 ++++ src/common/component/FileBox/preview/index.js | 7 ++ src/common/component/FileBox/styled.js | 55 +++++++++++ src/common/component/Message/FileBox.js | 81 --------------- .../component/Message/PreviewMessage.js | 17 +++- src/common/component/Message/renderContent.js | 2 +- src/common/hook/useSendFileMessage.js | 1 + src/common/hook/useStreaming/chat.handler.js | 13 +++ src/routes/home/index.js | 9 +- src/routes/resources/Filter.js | 18 ++++ src/routes/resources/Search.js | 8 ++ src/routes/resources/View.js | 58 +++++++++++ src/routes/resources/index.js | 64 ++++++++++++ src/routes/resources/styled.js | 33 +++++++ 32 files changed, 585 insertions(+), 121 deletions(-) create mode 100644 src/app/listener.middleware/handler.file.msg.js create mode 100644 src/app/slices/message.file.js create mode 100644 src/assets/icons/file.grid.svg create mode 100644 src/assets/icons/file.list.svg create mode 100644 src/assets/icons/folder.svg create mode 100644 src/common/component/FileBox/index.js create mode 100644 src/common/component/FileBox/preview/Audio.js create mode 100644 src/common/component/FileBox/preview/Code.js create mode 100644 src/common/component/FileBox/preview/Image.js create mode 100644 src/common/component/FileBox/preview/Pdf.js create mode 100644 src/common/component/FileBox/preview/Video.js create mode 100644 src/common/component/FileBox/preview/index.js create mode 100644 src/common/component/FileBox/styled.js delete mode 100644 src/common/component/Message/FileBox.js create mode 100644 src/routes/resources/Filter.js create mode 100644 src/routes/resources/Search.js create mode 100644 src/routes/resources/View.js create mode 100644 src/routes/resources/index.js create mode 100644 src/routes/resources/styled.js diff --git a/src/app/cache/index.js b/src/app/cache/index.js index ded8c2be..26f2f5e7 100644 --- a/src/app/cache/index.js +++ b/src/app/cache/index.js @@ -22,6 +22,10 @@ const tables = [ storeName: "message", description: "store message with key-val full data", }, + { + storeName: "messageFile", + description: "store file message list refs", + }, { storeName: "messageReaction", description: "store message reaction with key-val full data", diff --git a/src/app/cache/useRehydrate.js b/src/app/cache/useRehydrate.js index 3bb9bd43..da8c1a7a 100644 --- a/src/app/cache/useRehydrate.js +++ b/src/app/cache/useRehydrate.js @@ -9,6 +9,7 @@ import { fullfillUserMsg } from "../slices/message.user"; import { fullfillChannels } from "../slices/channels"; import { fullfillContacts } from "../slices/contacts"; import { fullfillFootprint } from "../slices/footprint"; +import { fullfillFileMessage } from "../slices/message.file"; import { fullfillUI } from "../slices/ui"; const useRehydrate = () => { const [iterated, setIterated] = useState(false); @@ -17,6 +18,7 @@ const useRehydrate = () => { const rehydrateData = { channels: [], contacts: [], + fileMessage: {}, channelMessage: {}, userMessage: {}, reactionMessage: {}, @@ -50,6 +52,9 @@ const useRehydrate = () => { case "messageChannel": rehydrateData.channelMessage[key] = data; break; + case "messageFile": + rehydrateData.fileMessage[key] = data || []; + break; case "messageDM": rehydrateData.userMessage[key] = data; break; @@ -74,6 +79,8 @@ const useRehydrate = () => { dispatch(fullfillServer(rehydrateData.server)); console.log("fullfill channels from indexedDB"); dispatch(fullfillChannels(rehydrateData.channels)); + // file message + dispatch(fullfillFileMessage(rehydrateData.fileMessage.list)); dispatch(fullfillChannelMsg(rehydrateData.channelMessage)); dispatch(fullfillUserMsg(rehydrateData.userMessage)); dispatch(fullfillMessage(rehydrateData.message)); diff --git a/src/app/listener.middleware/handler.file.msg.js b/src/app/listener.middleware/handler.file.msg.js new file mode 100644 index 00000000..60614e2e --- /dev/null +++ b/src/app/listener.middleware/handler.file.msg.js @@ -0,0 +1,19 @@ +import clearTable from "./clear.handler"; +export default async function handler({ operation, data = {} }) { + const table = window.CACHE["messageFile"]; + if (operation.startsWith("reset")) { + clearTable("messageFile"); + return; + } + switch (operation) { + case "addFileMessage": + case "removeFileMessage": + { + console.log("file message opt", data, operation); + await table.setItem("list", data); + } + break; + default: + break; + } +} diff --git a/src/app/listener.middleware/handler.message.js b/src/app/listener.middleware/handler.message.js index 0c973c09..cc7f70df 100644 --- a/src/app/listener.middleware/handler.message.js +++ b/src/app/listener.middleware/handler.message.js @@ -23,16 +23,16 @@ export default async function handler({ operation, data = {}, payload }) { ); } break; - case "readMessage": - { - const mids = Array.isArray(payload) ? payload : [payload]; - await Promise.all( - mids.map(async (mid) => { - await table.setItem(mid + "", data[mid]); - }) - ); - } - break; + // case "readMessage": + // { + // const mids = Array.isArray(payload) ? payload : [payload]; + // await Promise.all( + // mids.map(async (mid) => { + // await table.setItem(mid + "", data[mid]); + // }) + // ); + // } + // break; default: break; } diff --git a/src/app/listener.middleware/index.js b/src/app/listener.middleware/index.js index 2c4717f3..8d609bbe 100644 --- a/src/app/listener.middleware/index.js +++ b/src/app/listener.middleware/index.js @@ -6,6 +6,7 @@ import channelMsgHandler from "./handler.channel.msg"; import dmMsgHandler from "./handler.dm.msg"; import serverHandler from "./handler.server"; import messageHandler from "./handler.message"; +import fileMessageHandler from "./handler.file.msg"; import reactionHandler from "./handler.reaction"; import UIHandler from "./handler.ui"; import footprintHandler from "./handler.footprint"; @@ -16,6 +17,7 @@ const operations = [ "contacts", "userMessage", "reactionMessage", + "fileMessage", "message", "ui", "footprint", @@ -28,7 +30,7 @@ const listenerMiddleware = createListenerMiddleware(); // Add one or more listener entries that look for specific actions. // They may contain any sync or async logic, similar to thunks. listenerMiddleware.startListening({ - predicate: (action, currentState, previousState) => { + predicate: (action) => { const { type = "" } = action; const [prefix] = type.split("/"); console.log("operation", type, operations.includes(prefix)); @@ -89,6 +91,15 @@ listenerMiddleware.startListening({ }); } break; + case "fileMessage": + { + await fileMessageHandler({ + operation, + // payload, + data: state, + }); + } + break; case "message": { await messageHandler({ diff --git a/src/app/slices/message.file.js b/src/app/slices/message.file.js new file mode 100644 index 00000000..5b7a66cd --- /dev/null +++ b/src/app/slices/message.file.js @@ -0,0 +1,42 @@ +import { createSlice } from "@reduxjs/toolkit"; + +const initialState = []; +const fileMessageSlice = createSlice({ + name: "fileMessage", + initialState, + reducers: { + resetFileMessage() { + return initialState; + }, + fullfillFileMessage(state, action) { + return action.payload || []; + }, + addFileMessage(state, action) { + const mid = action.payload; + // 加入file message 列表 + const fidIdx = state.findIndex((fid) => fid == mid); + if (fidIdx == -1) { + state.unshift(+mid); + } + }, + removeFileMessage(state, action) { + const mids = Array.isArray(action.payload) + ? action.payload + : [action.payload]; + mids.forEach((id) => { + // 从file message 列表删掉 + const fidIdx = state.fileMessages.findIndex((fid) => fid == id); + if (fidIdx > -1) { + state.splice(fidIdx, 1); + } + }); + }, + }, +}); +export const { + removeFileMessage, + resetFileMessage, + fullfillFileMessage, + addFileMessage, +} = fileMessageSlice.actions; +export default fileMessageSlice.reducer; diff --git a/src/app/slices/message.js b/src/app/slices/message.js index a08a6923..d5f32250 100644 --- a/src/app/slices/message.js +++ b/src/app/slices/message.js @@ -2,7 +2,6 @@ import { createSlice } from "@reduxjs/toolkit"; import BASE_URL, { ContentTypes } from "../config"; const initialState = { replying: {}, - fileMessages: [], }; const messageSlice = createSlice({ name: "message", @@ -18,16 +17,6 @@ const messageSlice = createSlice({ const { mid, ...rest } = action.payload; state[mid] = { ...state[mid], ...rest }; }, - readMessage(state, action) { - const mids = Array.isArray(action.payload) - ? action.payload - : [action.payload]; - mids.forEach((id) => { - if (state[id]) { - state[id].read = true; - } - }); - }, addMessage(state, action) { const data = action.payload; const { mid, sending, content_type, content } = data; @@ -41,8 +30,6 @@ const messageSlice = createSlice({ data.content = `${BASE_URL}/resource/file?file_path=${encodeURIComponent( data.file_path )}`; - // 加入file message 列表 - state.fileMessages.unshift(mid); } // image if (!sending && isImage) { @@ -62,11 +49,6 @@ const messageSlice = createSlice({ : [action.payload]; mids.forEach((id) => { delete state[id]; - // 从file message 列表删掉 - const fidIdx = state.fileMessages.findIndex((fid) => fid == id); - if (fidIdx > -1) { - state.fileMessages.splice(fidIdx, 1); - } }); }, addReplyingMessage(state, action) { @@ -87,7 +69,6 @@ export const { fullfillMessage, setMessage, updateMessage, - readMessage, addMessage, removeMessage, addReplyingMessage, diff --git a/src/app/store.js b/src/app/store.js index 123b200e..7f126a63 100644 --- a/src/app/store.js +++ b/src/app/store.js @@ -10,6 +10,7 @@ import contactsReducer from "./slices/contacts"; import reactionMsgReducer from "./slices/message.reaction"; import channelMsgReducer from "./slices/message.channel"; import userMsgReducer from "./slices/message.user"; +import fileMsgReducer from "./slices/message.file"; import messageReducer from "./slices/message"; import { authApi } from "./services/auth"; import { contactApi } from "./services/contact"; @@ -27,6 +28,7 @@ const reducer = combineReducers({ reactionMessage: reactionMsgReducer, userMessage: userMsgReducer, channelMessage: channelMsgReducer, + fileMessage: fileMsgReducer, message: messageReducer, [authApi.reducerPath]: authApi.reducer, [messageApi.reducerPath]: messageApi.reducer, diff --git a/src/assets/icons/chat.svg b/src/assets/icons/chat.svg index 16b22dcd..0e6e35cd 100644 --- a/src/assets/icons/chat.svg +++ b/src/assets/icons/chat.svg @@ -1,4 +1,3 @@ - - - + + diff --git a/src/assets/icons/contact.svg b/src/assets/icons/contact.svg index 263dcf64..90a1ddec 100644 --- a/src/assets/icons/contact.svg +++ b/src/assets/icons/contact.svg @@ -1,4 +1,3 @@ - - - + + diff --git a/src/assets/icons/file.grid.svg b/src/assets/icons/file.grid.svg new file mode 100644 index 00000000..1bc2c06e --- /dev/null +++ b/src/assets/icons/file.grid.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/icons/file.list.svg b/src/assets/icons/file.list.svg new file mode 100644 index 00000000..b415ec13 --- /dev/null +++ b/src/assets/icons/file.list.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/icons/folder.svg b/src/assets/icons/folder.svg new file mode 100644 index 00000000..650e8778 --- /dev/null +++ b/src/assets/icons/folder.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/common/component/FileBox/index.js b/src/common/component/FileBox/index.js new file mode 100644 index 00000000..97379a6e --- /dev/null +++ b/src/common/component/FileBox/index.js @@ -0,0 +1,98 @@ +// import React from 'react' +import dayjs from "dayjs"; +import relativeTime from "dayjs/plugin/relativeTime"; +import { useSelector } from "react-redux"; +import Styled from "./styled"; +import { + VideoPreview, + AudioPreview, + ImagePreview, + PdfPreview, + CodePreview, +} from "./preview"; +import { getFileIcon, formatBytes } from "../../utils"; +import IconDownload from "../../../assets/icons/download.svg"; +dayjs.extend(relativeTime); +const renderPreview = (data) => { + const { file_type, name, content } = data; + let preview = null; + + const checks = { + image: /^image/gi, + audio: /^audio/gi, + video: /^video/gi, + code: /(json|javascript|java|rb|c|php|xml|css|html)$/gi, + doc: /^text/gi, + pdf: /\/pdf$/gi, + }; + const _arr = name.split("."); + const _type = file_type || _arr[_arr.length - 1]; + switch (true) { + case checks.image.test(_type): + { + console.log("image"); + preview = ; + } + break; + case checks.pdf.test(_type): + preview = ; + break; + case checks.code.test(_type): + preview = ; + break; + // case checks.doc.test(_type): + // icon = ; + // break; + case checks.audio.test(_type): + preview = ; + break; + case checks.video.test(_type): + preview = ; + break; + + // default: + // icon = ; + // break; + } + return preview; +}; +export default function FileBox({ + preview = false, + flex, + file_type, + name, + size, + created_at, + from_uid, + content, +}) { + const fromUser = useSelector((store) => store.contacts.byId[from_uid]); + const icon = getFileIcon(file_type, name); + if (!content || !fromUser || !name) return null; + console.log("file content", content, name, flex); + return ( + +
+ {icon} +
+ {name} + + {formatBytes(size)} + {dayjs(created_at).fromNow()} + + by {fromUser.name} + + +
+ + + +
+ {preview && ( +
+ {renderPreview({ file_type, content, name })} +
+ )} + + ); +} diff --git a/src/common/component/FileBox/preview/Audio.js b/src/common/component/FileBox/preview/Audio.js new file mode 100644 index 00000000..5888baa3 --- /dev/null +++ b/src/common/component/FileBox/preview/Audio.js @@ -0,0 +1,6 @@ +import React from "react"; + +export default function Audio({ url = "" }) { + if (!url) return null; + return