Skip to content

Commit 4018bee

Browse files
authored
Merge pull request #196 from boostcamp-2020/dev
v0.0.6 배포
2 parents aafc5bd + e04c8ab commit 4018bee

File tree

12 files changed

+236
-124
lines changed

12 files changed

+236
-124
lines changed

β€Žbackend/Dockerfile

+1-3
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
1-
FROM alpine
1+
FROM rockpell/alpine-node:1
22

33
LABEL maintainer="rockpell"
44

5-
RUN apk update && apk upgrade
6-
RUN apk add nodejs-npm && apk add git
75
RUN git clone https://github.com/boostcamp-2020/Project12-C-Slack-Web.git
86
WORKDIR /Project12-C-Slack-Web/backend
97
COPY ./.env.prod ./

β€Žfrontend/src/constant/icon.js

+2
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
faFile,
2020
faPaperclip,
2121
faPaperPlane,
22+
faArrowDown,
2223
} from '@fortawesome/free-solid-svg-icons'
2324
import {
2425
faStar,
@@ -57,3 +58,4 @@ export const SHARE = faShare
5758
export const FILE = faFile
5859
export const CLIP = faPaperclip
5960
export const PAPERPLANE = faPaperPlane
61+
export const ArrowDown = faArrowDown

β€Žfrontend/src/container/ChatMessage/ChatMessage.js

+1
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,7 @@ const StyleLink = styled(NavLink)`
189189
text-decoration: none;
190190
color: ${COLOR.STARBLUE};
191191
background-color: ${COLOR.WHITE};
192+
border: 1px solid transparent;
192193
padding: 5px;
193194
margin-left: 15px;
194195
&:hover {

β€Žfrontend/src/container/ChatRoom/ChatRoom.js

+52-7
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import ChannelHeader from '../ChannelHeader'
1111
import { isEmpty } from '../../util'
1212
import { hasMyReaction, chageReactionState } from '../../util/reactionUpdate'
1313
import useChannelInfo from '../../hooks/useChannelInfo'
14+
import Icon from '../../presenter/Icon'
15+
import { ArrowDown } from '../../constant/icon'
1416

1517
const ChatRoom = ({ width }) => {
1618
const viewport = useRef(null)
@@ -19,13 +21,15 @@ const ChatRoom = ({ width }) => {
1921
const previousReadMessage = useRef(null)
2022
const isLoading = useRef(false)
2123
const isAllMessageFetched = useRef(false)
24+
const isReading = useRef(false)
2225
const workspaceUserInfo = useRecoilValue(workspaceRecoil)
2326
const [channelInfo] = useChannelInfo()
2427
const { workspaceId, channelId } = useParams()
2528
const params = useParams()
2629
const socket = useRecoilValue(socketRecoil)
2730
const [messages, setMessages] = useState([])
2831
const [previousReadMessageIndex, setPreviousReadMessageIndex] = useState(0)
32+
const [hasUnreadMessage, setHasUnreadMessage] = useState(false)
2933

3034
const loadMessage = useCallback(
3135
async (workspaceId, channelId, currentCursor) => {
@@ -80,11 +84,17 @@ const ChatRoom = ({ width }) => {
8084
useEffect(() => {
8185
if (socket) {
8286
socket.on('new message', ({ message }) => {
83-
if (message.channelId === channelId)
87+
if (message.channelId === channelId) {
8488
setMessages(messages => [
8589
...messages,
8690
...hasMyReaction([message], workspaceUserInfo),
8791
])
92+
if (isReading.current && document.hasFocus()) {
93+
setHasUnreadMessage(false)
94+
scrollTo()
95+
} else if (message.userInfo._id !== workspaceUserInfo._id)
96+
setHasUnreadMessage(true)
97+
}
8898

8999
if (document.hidden) {
90100
new Notification('μƒˆλ‘œμš΄ λ©”μ‹œμ§€κ°€ μ™”μŠ΅λ‹ˆλ‹€.', {
@@ -105,12 +115,24 @@ const ChatRoom = ({ width }) => {
105115
}
106116
}
107117
}, [socket, channelId, document.hidden, params])
108-
109118
useEffect(() => {
110119
const handleIntersection = (entries, observer) => {
111120
entries.forEach(entry => {
112-
if (entry.isIntersecting) {
113-
if (!isLoading.current && !isAllMessageFetched.current) {
121+
if (entry.target === messageEndRef.current) {
122+
if (!entry.isIntersecting || !document.hasFocus()) {
123+
isReading.current = false
124+
}
125+
if (entry.isIntersecting) {
126+
setHasUnreadMessage(false)
127+
isReading.current = true
128+
}
129+
}
130+
if (entry.target === observerTargetNode.current) {
131+
if (
132+
entry.isIntersecting &&
133+
!isLoading.current &&
134+
!isAllMessageFetched.current
135+
) {
114136
loadMessage(workspaceId, channelId, observerTargetNode.current.id)
115137
observer.unobserve(entry.target)
116138
observer.observe(observerTargetNode.current)
@@ -123,6 +145,7 @@ const ChatRoom = ({ width }) => {
123145
threshold: 0,
124146
})
125147
if (observerTargetNode.current) IO.observe(observerTargetNode.current)
148+
if (messageEndRef.current) IO.observe(messageEndRef.current)
126149
return () => IO && IO.disconnect()
127150
}, [channelId, workspaceId, loadMessage])
128151

@@ -134,7 +157,9 @@ const ChatRoom = ({ width }) => {
134157
},
135158
[previousReadMessageIndex],
136159
)
137-
160+
const handleUnreadMessageButton = () => {
161+
scrollTo()
162+
}
138163
return (
139164
<ChatArea width={width}>
140165
<ChatHeader>
@@ -152,6 +177,11 @@ const ChatRoom = ({ width }) => {
152177
/>
153178
)
154179
})}
180+
{hasUnreadMessage && (
181+
<UnreadMessage onClick={handleUnreadMessageButton}>
182+
<Icon icon={ArrowDown} color={COLOR.WHITE} /> Unread messages..
183+
</UnreadMessage>
184+
)}
155185
<div ref={messageEndRef}></div>
156186
</ChatContents>
157187
<MessageEditor
@@ -167,7 +197,7 @@ const ChatArea = styled.div`
167197
display: flex;
168198
flex-direction: column;
169199
height: 100%;
170-
width: calc(${props => props.width}% - 2px);
200+
width: ${props => `calc( 100% - ${props.width}px)`};
171201
background: ${COLOR.HOVER_GRAY};
172202
`
173203

@@ -189,5 +219,20 @@ const ChatContents = styled.div`
189219
background: ${COLOR.WHITE};
190220
border: 1px solid rgba(255, 255, 255, 0.1);
191221
`
192-
222+
const UnreadMessage = styled.div`
223+
border-radius: 30px;
224+
border: 1px solid ${COLOR.LIGHT_GRAY};
225+
background-color: ${COLOR.STARBLUE};
226+
color: ${COLOR.WHITE};
227+
width: 170px;
228+
height: 50px;
229+
margin-left: auto;
230+
margin-right: auto;
231+
position: sticky;
232+
bottom: 15px;
233+
text-align: center;
234+
padding: 5px;
235+
font-weight: 700;
236+
cursor: pointer;
237+
`
193238
export default ChatRoom

β€Žfrontend/src/container/FilePreview/FilePreview.js

+31-8
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ import { COLOR } from '../../constant/style'
77
import Button from '../../presenter/Button'
88
import { isImage } from '../../util'
99

10+
const INPUT_MAX_IMG_WIDTH = 80
11+
const MAX_IMG_WIDTH = 300
12+
1013
function FilePreview({ type, setIsRender, file, setFile }) {
1114
const [isHover, setIsHover] = useState(false)
1215

@@ -46,17 +49,37 @@ function FilePreview({ type, setIsRender, file, setFile }) {
4649
)
4750
}
4851

52+
const onImgLoad = e => {
53+
const ratio = MAX_IMG_WIDTH / e.target.naturalWidth
54+
setFile({
55+
...file,
56+
width: e.target.naturalWidth * ratio,
57+
height: e.target.naturalHeight * ratio,
58+
})
59+
}
60+
4961
const renderImgPreview = () => {
5062
return (
5163
<StyledImgDiv
5264
onMouseEnter={enterMouseHandle}
5365
onMouseLeave={leaveMouseHandle}
5466
>
55-
<StyledImg
56-
alt={file?.originalName || '이미지'}
57-
src={file?.url}
58-
type={type}
59-
></StyledImg>
67+
{type === 'input' ? (
68+
<StyledImg
69+
alt={file?.originalName || '이미지'}
70+
src={file?.url}
71+
type={type}
72+
onLoad={onImgLoad}
73+
></StyledImg>
74+
) : (
75+
<StyledImg
76+
alt={file?.originalName || '이미지'}
77+
src={file?.url}
78+
type={type}
79+
width={file?.width}
80+
height={file?.height}
81+
></StyledImg>
82+
)}
6083
{isHover &&
6184
(type === 'input'
6285
? deleteButton()
@@ -95,15 +118,15 @@ function FilePreview({ type, setIsRender, file, setFile }) {
95118
const StyledImgDiv = styled.div`
96119
display: inline-block;
97120
position: relative;
121+
width: fit-content;
98122
`
99123

100124
const StyledFileDiv = styled.div`
101125
display: inline-block;
102126
position: relative;
103127
border: 1px solid ${COLOR.LIGHT_GRAY};
104128
border-radius: 4px;
105-
min-width: 200px;
106-
max-width: 300px;
129+
width: fit-content;
107130
`
108131

109132
const FlexDiv = styled.div`
@@ -114,7 +137,7 @@ const FlexDiv = styled.div`
114137

115138
const StyledImg = styled.img`
116139
max-width: ${({ type }) => {
117-
return type === 'input' ? '80px' : '300px'
140+
return type === 'input' ? `${INPUT_MAX_IMG_WIDTH}px` : `${MAX_IMG_WIDTH}px`
118141
}};
119142
height: auto;
120143
border-radius: 2%;

β€Žfrontend/src/container/FilePreview/FilePreview.stories.js

+3
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ inputImgPreview.args = {
2020
url:
2121
'https://kr.object.ncloudstorage.com/slack-clone-files/file-1608140339770-슬기.jpg',
2222
},
23+
setFile: () => {},
2324
setIsRender: () => {
2425
isRender = false
2526
},
@@ -36,6 +37,8 @@ messageImgPreview.args = {
3637
creator: '5fd81c4d630674160961baf4',
3738
url:
3839
'https://kr.object.ncloudstorage.com/slack-clone-files/file-1608140339770-슬기.jpg',
40+
width: '700px',
41+
height: '500px',
3942
},
4043
}
4144

β€Žfrontend/src/container/FileUploader/FileUploader.js

+7-6
Original file line numberDiff line numberDiff line change
@@ -11,18 +11,19 @@ const fileContentType = 'multipart/form-data'
1111

1212
function FileUploader({ file, setFile }) {
1313
const fileInput = useRef(null)
14-
const handleFileInput = async e => {
15-
if (!e.target.files[0]) return
16-
if (e.target.files[0].size > 8192000) {
14+
15+
const handleFileInput = async () => {
16+
if (!fileInput.current.files[0]) return
17+
if (fileInput.current.files[0].size > 8192000) {
1718
toast.error('8MB μ΄ν•˜μ˜ 파일만 μ—…λ‘œλ“œ ν•  수 μžˆμŠ΅λ‹ˆλ‹€!')
1819
return
1920
}
2021
if (!isEmpty(file)) {
2122
await request.DELETE('/api/file', { name: file.name })
2223
setFile(null)
2324
}
24-
await handlePost(e.target.files[0])
25-
e.target.value = ''
25+
await handlePost(fileInput.current.files[0])
26+
fileInput.current.value = null
2627
}
2728
const handlePost = async selectedFile => {
2829
if (selectedFile) {
@@ -46,7 +47,7 @@ function FileUploader({ file, setFile }) {
4647
type="file"
4748
name="fileData"
4849
ref={fileInput}
49-
onChange={e => handleFileInput(e)}
50+
onChange={handleFileInput}
5051
></StyeldInput>
5152
</>
5253
)

0 commit comments

Comments
Β (0)