diff --git a/.vscode/settings.json b/.vscode/settings.json index 98059497..c02ad941 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -21,5 +21,6 @@ }, "[javascriptreact]": { "editor.defaultFormatter": "svipas.prettier-plus" - } + }, + "emmet.triggerExpansionOnTab": true } diff --git a/package.json b/package.json index 32c3f9d1..d83d3878 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,7 @@ "react-refresh": "^0.11.0", "react-router-dom": "6", "react-textarea-autosize": "^8.3.3", + "react-window": "^1.8.6", "redux-persist": "^6.0.0", "resolve": "^1.22.0", "rooks": "^5.10.0", diff --git a/src/app/services/channel.js b/src/app/services/channel.js index c3b933f7..a3260e26 100644 --- a/src/app/services/channel.js +++ b/src/app/services/channel.js @@ -1,13 +1,9 @@ import { createApi } from "@reduxjs/toolkit/query/react"; -import toast from "react-hot-toast"; +// import toast from "react-hot-toast"; import baseQuery from "./base.query"; import { ContentTypes } from "../config"; -import { addChannelMsg } from "../slices/message.channel"; import { updateChannel } from "../slices/channels"; -import { - addPendingMessage, - removePendingMessage, -} from "../slices/message.pending"; +import { onMessageSendStarted } from "./handlers"; export const channelApi = createApi({ reducerPath: "channel", baseQuery, @@ -61,36 +57,8 @@ export const channelApi = createApi({ method: "POST", body: content, }), - async onQueryStarted( - { id, content, type, from_uid }, - { dispatch, queryFulfilled } - ) { - // id: who send to ,from_uid: who sent - const mid = new Date().getTime(); - const tmpMsg = { - id, - content, - content_type: ContentTypes[type], - created_at: new Date().getTime(), - mid, - from_uid, - unread: false, - }; - dispatch(addPendingMessage({ type: "channel", msg: tmpMsg })); - try { - const { data: server_mid } = await queryFulfilled; - console.log("channel server mid", server_mid); - // 此处的id,是指给谁发的 - dispatch( - addChannelMsg({ ...tmpMsg, mid: server_mid, unread: false }) - ); - dispatch(removePendingMessage({ id, mid, type: "channel" })); - } catch { - console.log("channel message send failed"); - toast.error("Send Message Failed"); - dispatch(removePendingMessage({ id, mid, type: "channel" })); - // patchResult.undo(); - } + async onQueryStarted(param1, param2) { + await onMessageSendStarted.call(this, param1, param2, "channel"); }, }), }), diff --git a/src/app/services/contact.js b/src/app/services/contact.js index 40fc8e21..8001617d 100644 --- a/src/app/services/contact.js +++ b/src/app/services/contact.js @@ -1,15 +1,9 @@ import { createApi } from "@reduxjs/toolkit/query/react"; -import toast from "react-hot-toast"; - +// import toast from "react-hot-toast"; import baseQuery from "./base.query"; import BASE_URL, { ContentTypes } from "../config"; -import { addUserMsg } from "../slices/message.user"; import { removeContact } from "../slices/contacts"; -import { - addPendingMessage, - removePendingMessage, -} from "../slices/message.pending"; - +import { onMessageSendStarted } from "./handlers"; export const contactApi = createApi({ reducerPath: "contact", baseQuery, @@ -73,36 +67,8 @@ export const contactApi = createApi({ method: "POST", body: content, }), - async onQueryStarted( - { id, content, type, from_uid }, - { dispatch, queryFulfilled } - ) { - // id: who send to ,from_uid: who sent - const mid = new Date().getTime(); - const tmpMsg = { - id, - content, - content_type: ContentTypes[type], - created_at: new Date().getTime(), - mid, - from_uid, - unread: false, - }; - dispatch(addPendingMessage({ type: "user", msg: tmpMsg })); - try { - // 走sse推送 - const { data: server_mid } = await queryFulfilled; - // console.log("wtf", wtf); - // 此处的id,是指给谁发的 - dispatch( - addUserMsg({ id, ...tmpMsg, mid: server_mid, unread: false }) - ); - dispatch(removePendingMessage({ id, mid, type: "user" })); - } catch { - toast.error("Send Message Failed"); - dispatch(removePendingMessage({ id, mid, type: "user" })); - // patchResult.undo(); - } + async onQueryStarted(param1, param2) { + await onMessageSendStarted.call(this, param1, param2, "user"); }, }), }), diff --git a/src/app/services/handlers.js b/src/app/services/handlers.js new file mode 100644 index 00000000..6c99e676 --- /dev/null +++ b/src/app/services/handlers.js @@ -0,0 +1,56 @@ +import toast from "react-hot-toast"; +import { ContentTypes } from "../config"; +import { + // addChannelMsg, + addChannelPendingMsg, + removeChannelPendingMsg, + replaceChannelPendingMsg, +} from "../slices/message.channel"; +import { + addUserPendingMsg, + removeUserPendingMsg, + replaceUserPendingMsg, +} from "../slices/message.user"; + +// import { +// addPendingMessage, +// removePendingMessage, +// } from "../slices/message.pending"; +export const onMessageSendStarted = async ( + { id, content, type, from_uid }, + { dispatch, queryFulfilled }, + from = "channel" +) => { + // id: who send to ,from_uid: who sent + const ts = new Date().getTime(); + const tmpMsg = { + id, + content: type == "image" ? URL.createObjectURL(content) : content, + content_type: ContentTypes[type], + created_at: ts, + local_mid: ts, + from_uid, + // unread: false, + }; + const addPendingMessage = + from == "channel" ? addChannelPendingMsg : addUserPendingMsg; + const replacePendingMessage = + from == "channel" ? replaceChannelPendingMsg : replaceUserPendingMsg; + const removePendingMessage = + from == "channel" ? removeChannelPendingMsg : removeUserPendingMsg; + // dispatch(addPendingMessage({ type: from, msg: tmpMsg })); + dispatch(addPendingMessage({ ...tmpMsg })); + try { + const { data: server_mid } = await queryFulfilled; + console.log("message server mid", server_mid); + // 此处的id,是指给谁发的 + // const addMessage = from == "channel" ? addChannelMsg : addUserMsg; + dispatch(replacePendingMessage({ id, local_mid: ts, server_mid })); + // dispatch(removePendingMessage({ id, mid:ts, type: from })); + } catch { + console.log("message send failed"); + toast.error("Send Message Failed"); + dispatch(removePendingMessage({ id, mid: ts, type: from })); + // patchResult.undo(); + } +}; diff --git a/src/app/slices/message.channel.js b/src/app/slices/message.channel.js index 21fa5487..3ed05c01 100644 --- a/src/app/slices/message.channel.js +++ b/src/app/slices/message.channel.js @@ -6,6 +6,9 @@ import { msgClearUnread, msgUpdate, msgDelete, + msgAddPending, + msgRemovePending, + msgReplacePending, } from "./message.handler"; const initialState = {}; @@ -37,6 +40,15 @@ const channelMsgSlice = createSlice({ clearChannelMsgUnread(state, action) { msgClearUnread(state, action.payload); }, + addChannelPendingMsg(state, action) { + msgAddPending(state, action.payload); + }, + replaceChannelPendingMsg(state, action) { + msgReplacePending(state, action.payload); + }, + removeChannelPendingMsg(state, action) { + msgRemovePending(state, action.payload); + }, }, }); export const { @@ -48,5 +60,8 @@ export const { clearChannelMsgUnread, setChannelMsgRead, addChannelMsg, + addChannelPendingMsg, + replaceChannelPendingMsg, + removeChannelPendingMsg, } = channelMsgSlice.actions; export default channelMsgSlice.reducer; diff --git a/src/app/slices/message.handler.js b/src/app/slices/message.handler.js index 93924a62..f45c4cf1 100644 --- a/src/app/slices/message.handler.js +++ b/src/app/slices/message.handler.js @@ -58,10 +58,53 @@ export const msgAdd = (state, payload) => { state[id] = { [mid]: newMsg }; } }; +export const msgAddPending = (state, payload) => { + const { + id, + content, + created_at, + local_mid, + reply_mid = null, + from_uid, + content_type, + unread = false, + } = payload; + const newMsg = { content, content_type, created_at, from_uid, unread }; + + if (reply_mid && state[id][reply_mid]) { + newMsg.reply = { mid: reply_mid, ...state[id][reply_mid] }; + } + if (state[id]) { + state[id][local_mid] = newMsg; + } else { + state[id] = { [local_mid]: newMsg }; + } +}; +export const msgReplacePending = (state, payload) => { + const { id, local_mid, server_mid } = payload; + if (state[id] && state[id][local_mid]) { + // 先赋值,再去delete + state[id] = Object.fromEntries( + Object.entries(state[id]).map(([key, obj]) => { + return key == local_mid ? [server_mid, obj] : [key, obj]; + }) + ); + // state[id][server_mid] = { ...state[id][local_mid] }; + // delete state[id][local_mid]; + } +}; +export const msgRemovePending = (state, payload) => { + const { id, local_mid } = payload; + if (state[id] && state[id][local_mid]) { + delete state[id][local_mid]; + } +}; export const msgSetRead = (state, payload) => { const { id, mid } = payload; console.log("set read", id, mid); - state[id][mid].unread = false; + if (state[id] && state[id][mid]) { + state[id][mid].unread = false; + } }; export const msgDelete = (state, payload) => { const { id, mid } = payload; diff --git a/src/app/slices/message.user.js b/src/app/slices/message.user.js index 4c62db96..0de3fee3 100644 --- a/src/app/slices/message.user.js +++ b/src/app/slices/message.user.js @@ -5,6 +5,9 @@ import { msgSetRead, msgUpdate, msgDelete, + msgAddPending, + msgRemovePending, + msgReplacePending, } from "./message.handler"; const initialState = {}; const userMsgSlice = createSlice({ @@ -32,6 +35,15 @@ const userMsgSlice = createSlice({ setUserMsgRead(state, action) { msgSetRead(state, action.payload); }, + addUserPendingMsg(state, action) { + msgAddPending(state, action.payload); + }, + replaceUserPendingMsg(state, action) { + msgReplacePending(state, action.payload); + }, + removeUserPendingMsg(state, action) { + msgRemovePending(state, action.payload); + }, }, }); export const { @@ -42,5 +54,8 @@ export const { initUserMsg, addUserMsg, setUserMsgRead, + addUserPendingMsg, + replaceUserPendingMsg, + removeUserPendingMsg, } = userMsgSlice.actions; export default userMsgSlice.reducer; diff --git a/src/common/component/ImagePreviewModal.js b/src/common/component/ImagePreviewModal.js new file mode 100644 index 00000000..a4018057 --- /dev/null +++ b/src/common/component/ImagePreviewModal.js @@ -0,0 +1,64 @@ +import { useRef } from "react"; +import styled, { keyframes } from "styled-components"; +import { useOutsideClick } from "rooks"; +import Modal from "./Modal"; +const AniFadeIn = keyframes` +from{ +background: transparent; +} +to{ + background: rgba(1, 1, 1, 0.9); +} +`; +const StyledWrapper = styled.div` + /* todo */ + transition: all 0.5s ease; + width: 100vw; + height: 100vh; + /* background-color: rgba(1, 1, 1, 0.9); */ + animation: ${AniFadeIn} 0.3s ease-in-out forwards; + display: flex; + align-items: center; + justify-content: center; + .box { + display: flex; + flex-direction: column; + justify-content: flex-start; + gap: 10px; + img { + max-width: 70vw; + max-height: 70vw; + } + .origin { + font-weight: bold; + font-size: 14px; + color: #aaa; + &:hover { + text-decoration: underline; + color: #fff; + } + } + } +`; +export default function ImagePreviewModal({ image = null, closeModal }) { + const wrapperRef = useRef(); + useOutsideClick(wrapperRef, closeModal); + + if (!image) return null; + return ( + + +
+ preview image + + Download original + +
+
+
+ ); +} diff --git a/src/common/component/Message/index.js b/src/common/component/Message/index.js index d4ccf1a0..72b01486 100644 --- a/src/common/component/Message/index.js +++ b/src/common/component/Message/index.js @@ -1,8 +1,9 @@ -import { useEffect, useRef, useState } from "react"; +import React, { useEffect, useRef, useState } from "react"; import dayjs from "dayjs"; import { useDispatch, useSelector } from "react-redux"; import { useInViewRef } from "rooks"; import Tippy from "@tippyjs/react"; + import Profile from "../Profile"; import Avatar from "../Avatar"; import { setChannelMsgRead } from "../../../app/slices/message.channel"; @@ -12,7 +13,7 @@ import Commands from "./Commands"; import { emojis } from "./EmojiPicker"; import EditMessage from "./EditMessage"; import renderContent from "./renderContent"; -export default function Message({ +function Message({ reply = null, gid = "", mid = "", @@ -68,7 +69,10 @@ export default function Message({ return removed ? ( "removed" ) : ( - + ); } +export default React.memo(Message, (prevs, nexts) => { + return prevs.mid == nexts.mid; +}); diff --git a/src/common/component/Message/renderContent.js b/src/common/component/Message/renderContent.js index eec55c84..c3994ba6 100644 --- a/src/common/component/Message/renderContent.js +++ b/src/common/component/Message/renderContent.js @@ -27,11 +27,16 @@ const renderContent = (type, content, edited = false) => { ); break; + case "image/png": case "image/jpeg": ctn = ( ); break; diff --git a/src/common/component/Message/styled.js b/src/common/component/Message/styled.js index 9cfbf279..19d733b3 100644 --- a/src/common/component/Message/styled.js +++ b/src/common/component/Message/styled.js @@ -7,6 +7,11 @@ const StyledMsg = styled.div` padding: 4px; margin: 8px 0; border-radius: 8px; + content-visibility: auto; + contain-intrinsic-size: auto 150px; + &.in_view { + content-visibility: visible; + } &:hover, &.preview { background: #f5f6f7; @@ -95,6 +100,7 @@ const StyledMsg = styled.div` } .img { max-width: 400px; + cursor: pointer; } a { border-radius: 2px; diff --git a/src/common/component/Send/UploadModal/index.js b/src/common/component/Send/UploadModal/index.js index 7437ef0d..31588967 100644 --- a/src/common/component/Send/UploadModal/index.js +++ b/src/common/component/Send/UploadModal/index.js @@ -1,12 +1,12 @@ import { useState, useEffect } from "react"; -import { useDispatch } from "react-redux"; +import { useSelector } from "react-redux"; // import toast from "react-hot-toast"; // import { useNavigate } from "react-router-dom"; // import { useSelector, useDispatch } from "react-redux"; import { useSendChannelMsgMutation } from "../../../../app/services/channel"; import { useSendMsgMutation } from "../../../../app/services/contact"; -import { addChannelMsg } from "../../../../app/slices/message.channel"; -import { addUserMsg } from "../../../../app/slices/message.user"; +// import { addChannelMsg } from "../../../../app/slices/message.channel"; +// import { addUserMsg } from "../../../../app/slices/message.user"; import Modal from "../../Modal"; import Button from "../../styled/Button"; @@ -18,25 +18,13 @@ export default function UploadModal({ files = [], closeModal, }) { - const dispatch = useDispatch(); + // const dispatch = useDispatch(); + const from_uid = useSelector((store) => store.authData.user.uid); const [ sendChannelMsg, - { - error: sendChannelError, - isLoading: channelSending, - isSuccess: sendChannelSuccess, - data: sendChannelData, - }, + { isLoading: channelSending }, ] = useSendChannelMsgMutation(); - const [ - sendUserMsg, - { - error: sendUserError, - isLoading: userSending, - isSuccess: sendUserSuccess, - data: sendUserData, - }, - ] = useSendMsgMutation(); + const [sendUserMsg, { isLoading: userSending }] = useSendMsgMutation(); const [blobs, setBlobs] = useState([]); useEffect(() => { files.forEach((file) => { @@ -57,22 +45,9 @@ export default function UploadModal({ }, [files]); const handleUpload = () => { const uploadFn = type == "user" ? sendUserMsg : sendChannelMsg; - uploadFn({ id: sendTo, content: files[0], type: "image" }); + uploadFn({ id: sendTo, content: files[0], type: "image", from_uid }); + closeModal(); }; - useEffect(() => { - if (sendUserSuccess) { - dispatch(addUserMsg({ id: sendTo, ...sendUserData, unread: false })); - closeModal(); - } - }, [sendUserSuccess, sendUserData]); - - useEffect(() => { - if (sendChannelSuccess) { - const { gid, ...rest } = sendChannelData; - dispatch(addChannelMsg({ id: gid, ...rest, unread: false })); - closeModal(); - } - }, [sendChannelSuccess, sendChannelData]); if (!sendTo) return null; return ( @@ -108,9 +83,7 @@ export default function UploadModal({ />
- - {b.name} - + {b.name} (click title to change name)
{`${Math.floor(b.size / 1000)}KB`} diff --git a/src/common/component/Setting/LogoutConfirmModal.js b/src/common/component/Setting/LogoutConfirmModal.js index dcaa3543..ba0e69b1 100644 --- a/src/common/component/Setting/LogoutConfirmModal.js +++ b/src/common/component/Setting/LogoutConfirmModal.js @@ -2,20 +2,13 @@ import { useEffect, useState } from "react"; import styled from "styled-components"; import { useDispatch } from "react-redux"; -import { useNavigate } from "react-router-dom"; + import { toggleSetting } from "../../../app/slices/ui"; -import { clearAuthData } from "../../../app/slices/auth.data"; -import { clearMark } from "../../../app/slices/visit.mark"; -import { clearChannels } from "../../../app/slices/channels"; -import { clearContacts } from "../../../app/slices/contacts"; -import { clearChannelMsg } from "../../../app/slices/message.channel"; -import { clearUserMsg } from "../../../app/slices/message.user"; -import { clearPendingMsg } from "../../../app/slices/message.pending"; // import BASE_URL from "../../app/config"; -import { useLazyLogoutQuery } from "../../../app/services/auth"; import StyledModal from "../styled/Modal"; import Button from "../styled/Button"; import Checkbox from "../styled/Checkbox"; +import useLogout from "../../hook/useLogout"; const StyledConfirm = styled(StyledModal)` .clear { font-weight: normal; @@ -37,10 +30,9 @@ const StyledConfirm = styled(StyledModal)` `; import Modal from "../Modal"; export default function LogoutConfirmModal({ closeModal }) { - const [clearLocal, setClearLocal] = useState(false); const dispatch = useDispatch(); - const navigate = useNavigate(); - const [logout, { isLoading, isSuccess }] = useLazyLogoutQuery(); + const [clearLocal, setClearLocal] = useState(false); + const { logout, exited, exiting, clearLocalData } = useLogout(); const handleLogout = () => { logout(); }; @@ -48,22 +40,15 @@ export default function LogoutConfirmModal({ closeModal }) { setClearLocal(evt.target.checked); }; useEffect(() => { - if (isSuccess) { + if (exited) { if (clearLocal) { console.log("clear all store"); - dispatch(clearMark()); - dispatch(clearChannelMsg()); - dispatch(clearUserMsg()); - dispatch(clearChannels()); - dispatch(clearContacts()); - dispatch(clearPendingMsg()); + clearLocalData(); } - dispatch(clearAuthData()); // closeModal(); dispatch(toggleSetting()); - navigate("/login"); } - }, [isSuccess, clearLocal]); + }, [exited, clearLocal]); return ( Cancel } diff --git a/src/common/component/Setting/config/Firebase.js b/src/common/component/Setting/config/Firebase.js index 0747fa6f..9f91eb75 100644 --- a/src/common/component/Setting/config/Firebase.js +++ b/src/common/component/Setting/config/Firebase.js @@ -2,6 +2,7 @@ import StyledContainer from "./StyledContainer"; // import { useSelector } from "react-redux"; import Input from "../../styled/Input"; +import Textarea from "../../styled/Textarea"; import Toggle from "../../styled/Toggle"; import Label from "../../styled/Label"; import SaveTip from "../../SaveTip"; @@ -62,7 +63,8 @@ export default function ConfigFirebase() {
- { + dispatch(clearMark()); + dispatch(clearChannelMsg()); + dispatch(clearUserMsg()); + dispatch(clearChannels()); + dispatch(clearContacts()); + dispatch(clearPendingMsg()); + }; + + useEffect(() => { + if (isSuccess) { + dispatch(clearAuthData()); + navigate("/login"); + } + }, [isSuccess]); + + return { clearLocalData, logout, exited: isSuccess, exiting: isLoading }; +} diff --git a/src/routes/chat/ChannelChat/index.js b/src/routes/chat/ChannelChat/index.js index 2d3e966c..91e84c72 100644 --- a/src/routes/chat/ChannelChat/index.js +++ b/src/routes/chat/ChannelChat/index.js @@ -25,11 +25,10 @@ export default function ChannelChat({ // const containerRef = useRef(null); const [dragFiles, setDragFiles] = useState([]); const dispatch = useDispatch(); - const { msgs, users, pendingMsgs } = useSelector((store) => { + const { msgs, users } = useSelector((store) => { return { msgs: store.channelMessage[cid] || {}, users: store.contacts, - pendingMsgs: store.pendingMessage.channel[cid] || {}, }; }); const handleClearUnreads = () => { @@ -97,11 +96,11 @@ export default function ChannelChat({ {/* */}
- {[...Object.entries(msgs), ...Object.entries(pendingMsgs)] + {Object.entries(msgs) .sort(([, msg1], [, msg2]) => { return msg1.created_at - msg2.created_at; }) - .map(([mid, msg]) => { + .map(([mid, msg], idx) => { if (!msg) return null; const { likes = {}, @@ -126,7 +125,7 @@ export default function ChannelChat({ unread={unread} gid={cid} mid={mid} - key={mid} + key={idx} time={created_at} fromUid={from_uid} content={content} diff --git a/src/routes/chat/DMChat/index.js b/src/routes/chat/DMChat/index.js index 3d990276..57f90574 100644 --- a/src/routes/chat/DMChat/index.js +++ b/src/routes/chat/DMChat/index.js @@ -11,10 +11,9 @@ export default function DMChat({ uid = "", dropFiles = [] }) { console.log("dm files", dropFiles); const [dragFiles, setDragFiles] = useState([]); const contacts = useSelector((store) => store.contacts); - const { msgs, pendingMsgs } = useSelector((store) => { + const { msgs } = useSelector((store) => { return { msgs: store.userMessage[uid] || {}, - pendingMsgs: store.pendingMessage.user[uid] || {}, }; }); const [currUser, setCurrUser] = useState(null); @@ -69,11 +68,11 @@ export default function DMChat({ uid = "", dropFiles = [] }) { >
- {[...Object.entries(msgs), ...Object.entries(pendingMsgs)] + {Object.entries(msgs) .sort(([, msg1], [, msg2]) => { return msg1.created_at - msg2.created_at; }) - .map(([mid, msg]) => { + .map(([mid, msg], idx) => { if (!msg) return null; // console.log("user msg", msg); const { @@ -99,7 +98,7 @@ export default function DMChat({ uid = "", dropFiles = [] }) { unread={unread} fromUid={from_uid} mid={mid} - key={mid} + key={idx} time={created_at} uid={uid} content={content} diff --git a/src/routes/chat/Layout.js b/src/routes/chat/Layout.js index a4246515..0fac645c 100644 --- a/src/routes/chat/Layout.js +++ b/src/routes/chat/Layout.js @@ -1,7 +1,9 @@ -// import { useState } from "react"; +import { useState, useRef, useEffect } from "react"; import { useDrop } from "react-dnd"; import { NativeTypes } from "react-dnd-html5-backend"; import styled from "styled-components"; +import ImagePreviewModal from "../../common/component/ImagePreviewModal"; + const StyledWrapper = styled.article` position: relative; width: 100%; @@ -75,6 +77,8 @@ export default function Layout({ contacts = null, setDragFiles, }) { + const messagesContainer = useRef(null); + const [previewImage, setPreviewImage] = useState(null); const [{ isActive }, drop] = useDrop(() => ({ accept: [NativeTypes.FILE], drop({ files }) { @@ -86,31 +90,59 @@ export default function Layout({ isActive: monitor.canDrop() && monitor.isOver(), }), })); + const closePreviewModal = () => { + setPreviewImage(null); + }; + useEffect(() => { + if (messagesContainer) { + const container = messagesContainer.current; + container.addEventListener( + "click", + (evt) => { + console.log(evt); + const { target } = evt; + if (target.nodeType == 1 && target.classList.contains("preview")) { + setPreviewImage(target.src); + } + }, + true + ); + } + }, []); + return ( - -
{header}
-
- {children} - {contacts &&
{contacts}
} -
-
+ <> + {previewImage && ( + + )} + +
{header}
+
+ {children} + {contacts &&
{contacts}
} +
-
-

Upload to #Channel

- - Photos accept jpg, png, max size limit to 10M. - +
+
+

Upload to #Channel

+ + Photos accept jpg, png, max size limit to 10M. + +
-
-
+ + ); } diff --git a/src/routes/chat/MessageList.js b/src/routes/chat/MessageList.js new file mode 100644 index 00000000..87636cf7 --- /dev/null +++ b/src/routes/chat/MessageList.js @@ -0,0 +1,24 @@ +// import React from "react"; +import { VariableSizeList as List } from "react-window"; +import Message from "../../common/component/Message"; + +export default function MessageList({ messages = [] }) { + const Row = ({ index, style }) => ( +
+ +
+ ); + const getItemSize = (index) => + messages[index].content_type.startsWith("image") ? 150 : 56; + return ( + + {Row} + + ); +} diff --git a/src/routes/home/Loading.js b/src/routes/home/Loading.js index f35cf9d9..d164b9ce 100644 --- a/src/routes/home/Loading.js +++ b/src/routes/home/Loading.js @@ -1,9 +1,13 @@ -// import React from 'react' +import { useState, useEffect } from "react"; import styled from "styled-components"; +import Button from "../../common/component/styled/Button"; +import useLogout from "../../common/hook/useLogout"; const StyledWrapper = styled.div` width: 100vw; height: 100vh; display: flex; + flex-direction: column; + gap: 15px; align-items: center; justify-content: center; .loading { @@ -19,11 +23,41 @@ const StyledWrapper = styled.div` border: 4px solid #999; border-radius: 50%; } + .reload { + visibility: hidden; + &.visible { + visibility: visible; + } + } `; export default function Loading() { + const [reloadVisible, setReloadVisible] = useState(false); + const { clearLocalData } = useLogout(); + const handleReload = () => { + clearLocalData(); + location.reload(true); + }; + useEffect(() => { + const inter = setTimeout(() => { + setReloadVisible(true); + }, 1500); + + return () => { + clearTimeout(inter); + }; + }, []); + return (
Loading...
+ {reloadVisible && ( + + )}
); } diff --git a/yarn.lock b/yarn.lock index 017fd30f..fe8929a8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5662,6 +5662,11 @@ memfs@^3.1.2, memfs@^3.4.1: dependencies: fs-monkey "1.0.3" +"memoize-one@>=3.1.1 <6": + version "5.2.1" + resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.2.1.tgz#8337aa3c4335581839ec01c3d594090cebe8f00e" + integrity sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q== + meow@^8.0.0: version "8.1.2" resolved "https://registry.yarnpkg.com/meow/-/meow-8.1.2.tgz#bcbe45bda0ee1729d350c03cffc8395a36c4e897" @@ -7105,6 +7110,14 @@ react-textarea-autosize@^8.3.3: use-composed-ref "^1.0.0" use-latest "^1.0.0" +react-window@^1.8.6: + version "1.8.6" + resolved "https://registry.yarnpkg.com/react-window/-/react-window-1.8.6.tgz#d011950ac643a994118632665aad0c6382e2a112" + integrity sha512-8VwEEYyjz6DCnGBsd+MgkD0KJ2/OXFULyDtorIiTz+QzwoP94tBoA7CnbtyXMm+cCeAUER5KJcPtWl9cpKbOBg== + dependencies: + "@babel/runtime" "^7.0.0" + memoize-one ">=3.1.1 <6" + react@^17.0.2: version "17.0.2" resolved "https://registry.yarnpkg.com/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037"