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