Skip to content

Commit 90b16ad

Browse files
authored
Merge pull request #90 from DMU-DebugVisual/sojeong
Codecast 헤더, 사이드바, 파일 선택, 프리뷰, 채팅창 업데이트
2 parents c6410e0 + b34e7ba commit 90b16ad

File tree

12 files changed

+913
-97
lines changed

12 files changed

+913
-97
lines changed
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
/* 백드롭 */
2+
.chat-backdrop {
3+
position: fixed;
4+
inset: 0;
5+
background: rgba(0,0,0,0);
6+
opacity: 0;
7+
pointer-events: none;
8+
transition: opacity .2s ease;
9+
z-index: 1500;
10+
}
11+
.chat-backdrop.show {
12+
background: rgba(0,0,0,0.35);
13+
opacity: 1;
14+
pointer-events: auto;
15+
}
16+
17+
/* 패널 */
18+
.chat-panel {
19+
position: fixed;
20+
top: 0;
21+
right: 0;
22+
width: 360px;
23+
max-width: calc(100% - 48px);
24+
height: 100vh;
25+
background: #fff;
26+
border-left: 1px solid #e6e6ef;
27+
box-shadow: -8px 0 24px rgba(0,0,0,0.08);
28+
transform: translateX(100%);
29+
transition: transform .22s ease;
30+
z-index: 1600;
31+
32+
display: flex;
33+
flex-direction: column;
34+
}
35+
.chat-panel.open {
36+
transform: translateX(0);
37+
}
38+
39+
.chat-header {
40+
display: flex;
41+
align-items: center;
42+
justify-content: space-between;
43+
padding: 14px 16px;
44+
border-bottom: 1px solid #e6e6ef;
45+
}
46+
.chat-header h3 {
47+
margin: 0;
48+
font-size: 16px;
49+
}
50+
51+
.chat-close {
52+
background: transparent;
53+
border: none;
54+
cursor: pointer;
55+
font-size: 16px;
56+
color: #666;
57+
}
58+
59+
.chat-list {
60+
flex: 1;
61+
overflow: auto;
62+
padding: 12px 14px;
63+
}
64+
65+
.chat-item + .chat-item { margin-top: 10px; }
66+
.chat-user {
67+
font-weight: 700;
68+
font-size: 13px;
69+
color: #333;
70+
}
71+
.chat-text {
72+
font-size: 14px;
73+
color: #222;
74+
}
75+
76+
.chat-input {
77+
display: flex;
78+
gap: 8px;
79+
padding: 10px 12px;
80+
border-top: 1px solid #e6e6ef;
81+
}
82+
.chat-input input {
83+
flex: 1;
84+
border: 1px solid #e0e0ea;
85+
border-radius: 8px;
86+
padding: 10px 12px;
87+
font-size: 14px;
88+
}
89+
.chat-input .send-btn {
90+
border: none;
91+
border-radius: 8px;
92+
padding: 0 14px;
93+
background: #6f42c1;
94+
color: #fff;
95+
cursor: pointer;
96+
}
97+
98+
/* 다크 모드 */
99+
.dark-mode .chat-panel {
100+
background: #1a1a26;
101+
border-left: 1px solid rgba(255,255,255,0.08);
102+
box-shadow: -10px 0 28px rgba(0,0,0,0.35);
103+
}
104+
105+
.dark-mode .chat-header {
106+
border-bottom-color: rgba(255,255,255,0.08);
107+
}
108+
109+
.dark-mode .chat-close {
110+
color: #bbb;
111+
}
112+
113+
.dark-mode .chat-user {
114+
color: #eaeaf2;
115+
}
116+
117+
.dark-mode .chat-text {
118+
color: #ddd;
119+
}
120+
121+
.dark-mode .chat-input {
122+
border-top-color: rgba(255,255,255,0.08);
123+
}
124+
.dark-mode .chat-input input {
125+
background: #232333;
126+
color: #eee;
127+
border-color: #2f2f40;
128+
}
129+
.dark-mode .chat-input .send-btn {
130+
background: #6a0dad;
131+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import React, { useEffect, useRef, useState } from 'react';
2+
import './ChatPanel.css';
3+
import { FaTimes, FaPaperPlane } from 'react-icons/fa';
4+
5+
export default function ChatPanel({ open, messages, onClose, onSend }) {
6+
const [text, setText] = useState('');
7+
const listRef = useRef(null);
8+
9+
useEffect(() => {
10+
// 새 메시지 도착/열릴 때 하단으로 스크롤
11+
listRef.current?.scrollTo({ top: listRef.current.scrollHeight, behavior: 'smooth' });
12+
}, [messages, open]);
13+
14+
const handleSubmit = (e) => {
15+
e.preventDefault();
16+
onSend?.(text);
17+
setText('');
18+
};
19+
20+
return (
21+
<>
22+
{/* 반투명 배경 (클릭 시 닫힘) */}
23+
<div className={`chat-backdrop ${open ? 'show' : ''}`} onClick={onClose} />
24+
25+
{/* 패널 */}
26+
<aside className={`chat-panel ${open ? 'open' : ''}`} aria-hidden={!open}>
27+
<div className="chat-header">
28+
<h3>방 채팅</h3>
29+
<button className="chat-close" onClick={onClose} aria-label="채팅 닫기"><FaTimes /></button>
30+
</div>
31+
32+
<div className="chat-list" ref={listRef}>
33+
{messages.map((m) => (
34+
<div key={m.id} className="chat-item">
35+
<div className="chat-user">{m.user}</div>
36+
<div className="chat-text">{m.text}</div>
37+
</div>
38+
))}
39+
</div>
40+
41+
<form className="chat-input" onSubmit={handleSubmit}>
42+
<input
43+
type="text"
44+
placeholder="메시지를 입력하세요…"
45+
value={text}
46+
onChange={(e) => setText(e.target.value)}
47+
/>
48+
<button type="submit" className="send-btn" aria-label="전송">
49+
<FaPaperPlane />
50+
</button>
51+
</form>
52+
</aside>
53+
</>
54+
);
55+
}

src/components/codecast/codecastlive/CodeEditor.css

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
gap: 12px;
2222
}
2323

24-
/* ✅ 사용자 뱃지 (복원) */
24+
/* ✅ 사용자 뱃지 */
2525
.current-user-badge {
2626
background-color: #f2f2f4;
2727
color: #333;
@@ -35,6 +35,13 @@
3535
gap: 6px;
3636
}
3737

38+
/* 오른쪽 버튼 묶음 */
39+
.toolbar-right {
40+
display: inline-flex;
41+
align-items: center;
42+
gap: 8px;
43+
}
44+
3845
/* 실행 버튼 */
3946
.run-button {
4047
background-color: #6a0dad;
@@ -51,6 +58,26 @@
5158
}
5259
.run-button:hover { background-color: #8844dd; }
5360

61+
/* 시각화 버튼 */
62+
.viz-button {
63+
background-color: #f2f2f4;
64+
color: #333;
65+
border: 1px solid #e4e4ea;
66+
padding: 6px 12px;
67+
border-radius: 6px;
68+
cursor: pointer;
69+
font-size: 14px;
70+
display: flex;
71+
align-items: center;
72+
gap: 6px;
73+
transition: background-color 0.2s ease, border-color 0.2s ease, color .2s ease;
74+
}
75+
.viz-button:hover {
76+
background-color: #ece9f7;
77+
border-color: #d7c7ff;
78+
color: #4f2aa3;
79+
}
80+
5481
/* Monaco 에디터가 부모 높이를 꽉 채우도록 */
5582
.code-editor > div, /* @monaco-editor/react 래퍼 */
5683
.code-editor .monaco-editor,
@@ -71,3 +98,14 @@
7198
color: #fff;
7299
border-color: #3a3a48;
73100
}
101+
102+
.dark-mode .viz-button {
103+
background-color: #2b2b34;
104+
color: #eaeaf2;
105+
border: 1px solid #3a3a48;
106+
}
107+
.dark-mode .viz-button:hover {
108+
background-color: #34343f;
109+
border-color: #6a0dad;
110+
color: #c9a7ff;
111+
}

src/components/codecast/codecastlive/CodeEditor.jsx

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import React, { useEffect, useMemo, useState } from 'react';
22
import Editor from '@monaco-editor/react';
3-
import { FaPlay } from 'react-icons/fa';
3+
import { FaPlay, FaCrown, FaPenFancy, FaEye, FaChartBar } from 'react-icons/fa';
44
import './CodeEditor.css';
55

66
/**
@@ -19,16 +19,17 @@ export default function CodeEditor({ file, readOnly = false, onChange, currentUs
1919
const language = useMemo(() => file?.language || 'python', [file?.language]);
2020

2121
const handleRun = () => alert('코드 실행 기능은 아직 미구현입니다.');
22+
const handleVisualize = () => alert('코드 시각화 기능은 아직 미구현입니다.');
2223

2324
const roleIcon = (role) => {
24-
if (role === 'host') return '👑';
25-
if (role === 'edit') return '✏️';
26-
if (role === 'view') return '👁️';
25+
if (role === 'host') return <FaCrown className="role-icon host" />;
26+
if (role === 'edit') return <FaPenFancy className="role-icon edit" />;
27+
if (role === 'view') return <FaEye className="role-icon view" />;
2728
return '';
2829
};
2930

30-
// const theme = document.body.classList.contains('dark-mode') ? 'vs-light' : 'vs-dark';
3131
const theme = isDark ? 'vs-dark' : 'vs-light'; // ✅ props 우선
32+
3233
return (
3334
<section className="code-editor">
3435
<div className="editor-header">
@@ -39,9 +40,18 @@ export default function CodeEditor({ file, readOnly = false, onChange, currentUs
3940
</div>
4041
)}
4142

42-
<button className="run-button" onClick={handleRun}>
43+
{/*<button className="run-button" onClick={handleRun}>
4344
<FaPlay /> 실행
44-
</button>
45+
</button>*/}
46+
{/* ✅ 오른쪽 툴바: 실행 + 시각화 */}
47+
<div className="toolbar-right">
48+
<button className="run-button" onClick={handleRun}>
49+
<FaPlay/> 실행
50+
</button>
51+
<button className="viz-button" onClick={handleVisualize}>
52+
<FaChartBar/> 시각화
53+
</button>
54+
</div>
4555
</div>
4656

4757
{/* 필요 시 height=100%로 바꿔도 됩니다 (부모 컨테이너가 flex:1이면) */}

src/components/codecast/codecastlive/CodePreviewList.jsx

Lines changed: 25 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -12,22 +12,31 @@ export default function CodePreviewList({ participants, activeName, onSelect })
1212
return (
1313
<div className="preview-bar">
1414
<div className="preview-track">
15-
{participants.map((p) => (
16-
<button
17-
key={p.name}
18-
className={`preview-card ${activeName === p.name ? 'active' : ''}`}
19-
onClick={() => onSelect(p.name)}
20-
title={`${p.name} 열기`}
21-
>
22-
<div className="preview-header">
23-
<RoleIcon role={p.role} />
24-
<span className="p-name">{p.name}</span>
25-
</div>
26-
<pre className="snippet">
27-
{(p.code && p.code.trim()) ? p.code.slice(0, 120) : '파일 미선택 / 코드 없음'}
28-
</pre>
29-
</button>
30-
))}
15+
{participants.map((p) => {
16+
const hasFile = !!p.file; // 파일 선택 여부
17+
const content = p.file?.content ?? '';
18+
const hasContent = content.trim().length > 0;
19+
20+
return (
21+
<button
22+
key={p.name}
23+
className={`preview-card ${activeName === p.name ? 'active' : ''}`}
24+
onClick={() => onSelect(p.name)}
25+
title={`${p.name} 열기`}
26+
>
27+
<div className="preview-header">
28+
<RoleIcon role={p.role} />
29+
<span className="p-name">{p.name}</span>
30+
</div>
31+
32+
<pre className="snippet">
33+
{hasFile
34+
? (hasContent ? content.slice(0, 120) : '') // ✅ 새 파일(빈 내용) → 비워두기
35+
: '파일 미선택 / 코드 없음'} {/* ✅ 파일 없을 때만 플레이스홀더 */}
36+
</pre>
37+
</button>
38+
);
39+
})}
3140
</div>
3241
</div>
3342
);

src/components/codecast/codecastlive/CodecastHeader.css

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,36 @@
2424
color: #222;
2525
}
2626

27+
/* 제목 수정 input */
28+
.title-input {
29+
font-size: 20px;
30+
font-weight: 700;
31+
padding: 2px 6px;
32+
border: 1px solid #ccc;
33+
border-radius: 6px;
34+
outline: none;
35+
}
36+
37+
.title-input:focus {
38+
border-color: #6f42c1;
39+
box-shadow: 0 0 0 2px rgba(111, 66, 193, 0.2);
40+
}
41+
42+
/* 수정 버튼 */
43+
.edit-title-btn {
44+
background: transparent;
45+
border: none;
46+
cursor: pointer;
47+
color: #666;
48+
font-size: 14px;
49+
margin-left: 6px;
50+
transition: color 0.2s ease;
51+
}
52+
53+
.edit-title-btn:hover {
54+
color: #6f42c1;
55+
}
56+
2757
.header-right {
2858
display: flex;
2959
align-items: center;

0 commit comments

Comments
 (0)