-
카테고리
-
세부 분야
프론트엔드
-
해결 여부
미해결
안읽은메세지 개수 표시하기
23.05.14 15:49 작성 조회수 655
0
안녕하세요 제로초님
디엠부분에서는 안읽은 메시지 표시가 떠서 읽으면 없어지는데
채널부분에서는 없어지지않고 남아있습니다!
답변을 작성해보세요.
0
장산
질문자2023.05.15
음 채널부분은 추가하라고 하신데로
useEffect(() => {
localStorage.setItem(`${workspace}-${channel}}`, new Date().getTime().toString());
}, [workspace, channel]);
이거 추가하고 EachChannel은 복붙해서 가져왔는데 보니까 채널부분은 채팅을 쳐야지만 안읽은 표시가 사라지네요
import ChatBox from '@components/ChatBox';
import ChatList from '@components/ChatList';
import InviteChannelModal from '@components/InviteChannelModal';
import useInput from '@hooks/useInput';
import useSocket from '@hooks/useSocket';
import { DragOver } from '@pages/Channel/styles';
import { Header, Container } from '@pages/DM/styles';
import { IChannel, IChat, IDM, IUser } from '@typings/db';
import fetcher from '@utils/fetcher';
import makeSection from '@utils/makeSection';
import axios from 'axios';
import { channel } from 'diagnostics_channel';
import gravatar from 'gravatar';
import React, { useCallback, useEffect, useRef, useState } from 'react';
import { Scrollbars } from 'react-custom-scrollbars';
import { Redirect, useParams } from 'react-router';
import { toast } from 'react-toastify';
import useSWR from 'swr';
import useSWRInfinite from 'swr/infinite';
const PAGE_SIZE = 20;
const Channel = () => {
const { workspace, channel } = useParams<{ workspace: string; channel: string }>();
const [showInviteChannelModal, setShowInviteChannelModal] = useState(false);
const [socket] = useSocket(workspace);
const { data: myData } = useSWR('/api/users', fetcher);
const { data: userData } = useSWR<IUser>('/api/users', fetcher);
const {
data: chatData,
mutate: mutateChat,
setSize,
} = useSWRInfinite<IChat[]>(
(index) => `/api/workspaces/${workspace}/channels/${channel}/chats?perPage=${PAGE_SIZE}&page=${index + 1}`,
fetcher,
{
onSuccess(data) {
if (data?.length === 1) {
setTimeout(() => {
scrollbarRef.current?.scrollToBottom();
}, 100);
}
},
},
);
const { data: channelMembersData } = useSWR<IUser[]>(
userData ? `/api/workspaces/${workspace}/channels/${channel}/members` : null,
fetcher,
);
const { data: channelsData } = useSWR<IChannel[]>(`/api/workspaces/${workspace}/channels`, fetcher);
const channelData = channelsData?.find((v) => v.name === channel);
const [chat, onChangeChat, setChat] = useInput('');
const scrollbarRef = useRef<Scrollbars>(null);
const [dragOver, setDragOver] = useState(false);
const isEmpty = chatData?.[0]?.length === 0;
const isReachingEnd = isEmpty || (chatData && chatData[chatData.length - 1]?.length < PAGE_SIZE);
useEffect(() => {
if (chatData?.length === 1) {
console.log('toBottomWhenLoaded', scrollbarRef.current);
setTimeout(() => {
console.log('scrollbar', scrollbarRef.current);
scrollbarRef.current?.scrollToBottom();
}, 500);
}
}, [chatData]);
const onSubmitForm = useCallback(
(e) => {
e.preventDefault();
if (chat?.trim() && chatData && channelData && userData) {
const savedChat = chat;
mutateChat((prevChatData) => {
prevChatData?.[0].unshift({
id: (chatData[0][0]?.id || 0) + 1,
content: savedChat,
UserId: myData.id,
User: myData,
ChannelId: channelData.id,
Channel: channelData,
createdAt: new Date(),
});
return prevChatData;
}, false).then(() => {
localStorage.setItem(`${workspace}-${channel}`, new Date().getTime().toString());
setChat('');
if (scrollbarRef.current) {
console.log('scrollToBottom!', scrollbarRef.current?.getValues());
scrollbarRef.current.scrollToBottom();
}
});
axios
.post(`/api/workspaces/${workspace}/channels/${channel}/chats`, {
content: chat,
})
.catch(console.error);
}
},
[chat, workspace, channel, channelData, userData, chatData, mutateChat, setChat],
);
const onClickInviteChannel = useCallback(() => {
setShowInviteChannelModal(true);
}, []);
const onCloseModal = useCallback(() => {
setShowInviteChannelModal(false);
}, []);
const onMessage = useCallback(
(data: IChat) => {
if (
data.Channel.name === channel &&
(data.content.startsWith('uploads\\') || data.content.startsWith('uploads/') || data.UserId !== userData?.id)
) {
mutateChat((chatData) => {
chatData?.[0].unshift(data);
return chatData;
}, false).then(() => {
if (scrollbarRef.current) {
if (
scrollbarRef.current.getScrollHeight() <
scrollbarRef.current.getClientHeight() + scrollbarRef.current.getScrollTop() + 150
) {
console.log('scrollToBottom!', scrollbarRef.current?.getValues());
setTimeout(() => {
scrollbarRef.current?.scrollToBottom();
}, 100);
} else {
toast.success('새 메시지가 도착했습니다.', {
onClick() {
scrollbarRef.current?.scrollToBottom();
},
closeOnClick: true,
});
}
}
});
}
},
[channel, mutateChat, userData],
);
useEffect(() => {
socket?.on('message', onMessage);
return () => {
socket?.off('message', onMessage);
};
}, [socket, onMessage]);
useEffect(() => {
localStorage.setItem(`${workspace}-${channel}}`, new Date().getTime().toString());
}, [workspace, channel]);
//화면에 직접 이미지 드래그해서 옮기기
const onDrop = useCallback(
(e) => {
e.preventDefault();
console.log(e);
const formData = new FormData();
if (e.dataTransfer.items) {
// Use DataTransferItemList interface to access the file(s)
for (let i = 0; i < e.dataTransfer.items.length; i++) {
// If dropped items aren't files, reject them
if (e.dataTransfer.items[i].kind === 'file') {
const file = e.dataTransfer.items[i].getAsFile();
console.log('... file[' + i + '].name = ' + file.name);
formData.append('image', file);
}
}
} else {
// Use DataTransfer interface to access the file(s)
for (let i = 0; i < e.dataTransfer.files.length; i++) {
console.log('... file[' + i + '].name = ' + e.dataTransfer.files[i].name);
formData.append('image', e.dataTransfer.files[i]);
}
}
axios.post(`/api/workspaces/${workspace}/dms/${channel}/images`, formData).then(() => {
setDragOver(false);
localStorage.setItem(`${workspace}-${channel}`, new Date().getTime().toString());
mutateChat();
});
},
[workspace, channel],
);
const onDragOver = useCallback((e) => {
e.preventDefault();
console.log(e);
setDragOver(true);
}, []);
if (channelsData && !channelData) {
return <Redirect to={`/workspace/${workspace}/channel/일반`} />;
}
const chatSections = makeSection(chatData ? chatData.flat().reverse() : []);
return (
<Container onDrop={onDrop} onDragOver={onDragOver}>
<Header>
<span>#{channel}</span>
<div className="header-right" style={{ display: 'flex' }}>
<span style={{ display: 'flex', alignItems: 'center' }}>{channelMembersData?.length}</span>
<button
onClick={onClickInviteChannel}
className="c-button-unstyled p-ia__view_header__button"
aria-label="Add people to #react-native"
data-sk="tooltip_parent"
type="button"
>
<i className="c-icon p-ia__view_header__button_icon c-icon--add-user" aria-hidden="true" />
</button>
</div>
</Header>
<ChatList
scrollbarRef={scrollbarRef}
isReachingEnd={isReachingEnd}
isEmpty={isEmpty}
chatSections={chatSections}
setSize={setSize}
/>
<ChatBox
onSubmitForm={onSubmitForm}
chat={chat}
onChangeChat={onChangeChat}
placeholder={`Message ${myData.nickname}`}
data={[]}
/>
<InviteChannelModal
show={showInviteChannelModal}
onCloseModal={onCloseModal}
setShowInviteChannelModal={setShowInviteChannelModal}
/>
{dragOver && <DragOver>업로드!</DragOver>}
</Container>
);
};
export default Channel;
조현영
지식공유자2023.05.15
https://github.com/ZeroCho/sleact/blob/master/front/components/EachChannel/index.tsx#L17-L18
이 부분도 추가하셨나요? 여기서 숫자가 있을지 없을지 판단되는 겁니다.
장산
질문자2023.05.15
허허 혹시 EachChannel 이코드 아닌가요?
import { IChannel, IUser } from '@typings/db';
import fetcher from '@utils/fetcher';
import React, { useEffect, VFC } from 'react';
import { useParams } from 'react-router';
import { NavLink, useLocation } from 'react-router-dom';
import useSWR from 'swr';
interface Props {
channel: IChannel;
}
const EachChannel: VFC<Props> = ({ channel }) => {
const { workspace } = useParams<{ workspace?: string }>();
const location = useLocation();
const { data: userData } = useSWR<IUser>('/api/users', fetcher, {
dedupingInterval: 2000, // 2초
});
const date = localStorage.getItem(`${workspace}-${channel.name}`) || 0;
const { data: count, mutate } = useSWR<number>(
userData ? `/api/workspaces/${workspace}/channels/${channel.name}/unreads?after=${date}` : null,
fetcher,
);
useEffect(() => {
if (location.pathname === `/workspace/${workspace}/channel/${channel.name}`) {
mutate(0);
}
}, [mutate, location.pathname, workspace, channel]);
return (
<NavLink key={channel.name} activeClassName="selected" to={`/workspace/${workspace}/channel/${channel.name}`}>
<span className={count !== undefined && count > 0 ? 'bold' : undefined}># {channel.name}</span>
{count !== undefined && count > 0 && <span className="count">{count}</span>}
</NavLink>
);
};
export default EachChannel;
제가 뭐 잘못 건드렸나요... 혹시 Channel말고도 연관 되어있는 파일이 있을까요?
조현영
지식공유자2023.05.15
https://github.com/ZeroCho/sleact/blob/master/front/components/ChannelList/index.tsx
EachChannel은 ChannelList에서 쓰이므로 여기도 봐보세요. 저 부분 리렌더링이 안 되는 것 같기도 하고요.
장산
질문자2023.05.15
import { CollapseButton } from '@components/DMList/styles';
import EachChannel from '@components/EachChannel';
import { IChannel, IUser } from '@typings/db';
import fetcher from '@utils/fetcher';
import React, { FC, useCallback, useState } from 'react';
import { useParams } from 'react-router';
import useSWR from 'swr';
interface Props {
channelData?: IChannel[];
userData?: IUser;
}
const ChannelList: FC<Props> = () => {
const { workspace } = useParams<{ workspace?: string }>();
const [channelCollapse, setChannelCollapse] = useState(false);
const { data: userData } = useSWR<IUser>('/api/users', fetcher, {
dedupingInterval: 2000, // 2초
});
const { data: channelData } = useSWR<IChannel[]>(userData ? `/api/workspaces/${workspace}/channels` : null, fetcher);
const toggleChannelCollapse = useCallback(() => {
setChannelCollapse((prev) => !prev);
}, []);
return (
<>
<h2>
<CollapseButton collapse={channelCollapse} onClick={toggleChannelCollapse}>
<i
className="c-icon p-channel_sidebar__section_heading_expand p-channel_sidebar__section_heading_expand--show_more_feature c-icon--caret-right c-icon--inherit c-icon--inline"
data-qa="channel-section-collapse"
aria-hidden="true"
/>
</CollapseButton>
<span>Channels</span>
</h2>
<div>
{!channelCollapse &&
channelData?.map((channel) => {
return <EachChannel key={channel.id} channel={channel} />;
})}
</div>
</>
);
};
export default ChannelList;
sleact /back 폴더에서 npm run dev하고 실행하는건 맞죠?
장산
질문자2023.05.16
EachChannel이랑 channelList 제로초님 코드 그대로 가져다 썼는데 안읽은메세지 갯수가 사라지지가 않네요 한번 계속 타고 올라가 보겠습니다
답변 1