Skip to content

Commit b34e7ba

Browse files
committed
코드방송 파일 선택에 새 파일 작성 추가
1 parent 8cbe101 commit b34e7ba

4 files changed

Lines changed: 251 additions & 28 deletions

File tree

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/CodecastLive.jsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,8 @@ const CodecastLive = ({ isDark }) => {
8181
// 🔸 채팅창 열림 상태, 채팅 내용
8282
const [isChatOpen, setIsChatOpen] = useState(false);
8383
const [messages, setMessages] = useState([
84-
{ id: 1, user: '김코딩', text: '안녕하세요! 오늘은 버블 정렬부터 갈게요.' },
85-
{ id: 2, user: '이알고', text: '넵 준비됐습니다 🙌' },
84+
{ id: 1, user: '김코딩', text: '안녕하세요!' },
85+
{ id: 2, user: '이알고', text: '🙌🙌' },
8686
]);
8787

8888
const handleStartShare = () => setShowPicker(true);

src/components/codecast/codecastlive/FilePickerModal.css

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,15 @@
5151
background: #eef0fb;
5252
}
5353

54+
.fpm-item.new-file {
55+
background: #efeaff;
56+
border: 1px dashed #c9b6ff;
57+
}
58+
.fpm-item.new-file:hover {
59+
background: #e7e0ff;
60+
border-color: #bfa9ff;
61+
}
62+
5463
.fpm-name {
5564
font-weight: 600;
5665
color: #333;
@@ -72,6 +81,84 @@
7281
}
7382
.fpm-close:hover { filter: brightness(1.03); }
7483

84+
/* 새 파일 작성 UI */
85+
.fpm-newfile {
86+
margin-top: 8px;
87+
background: #f8f8ff;
88+
border: 1px solid #ece9ff;
89+
border-radius: 12px;
90+
padding: 12px;
91+
}
92+
93+
.fpm-row {
94+
display: flex;
95+
gap: 8px;
96+
}
97+
98+
.fpm-input, .fpm-select {
99+
padding: 10px 12px;
100+
border: 1px solid #ccc;
101+
border-radius: 8px;
102+
font-size: 14px;
103+
color: #222;
104+
background: #fff;
105+
}
106+
107+
.fpm-input {
108+
flex: 1;
109+
}
110+
111+
.fpm-select {
112+
width: 180px;
113+
}
114+
115+
.fpm-hint {
116+
margin-top: 8px;
117+
font-size: 13px;
118+
color: #666;
119+
}
120+
121+
.fpm-preview {
122+
color: #4f2aa3;
123+
}
124+
125+
.fpm-actions {
126+
display: flex;
127+
justify-content: space-between;
128+
gap: 8px;
129+
margin-top: 12px;
130+
}
131+
132+
.fpm-primary,
133+
.fpm-secondary {
134+
border: none;
135+
border-radius: 10px;
136+
padding: 10px 14px;
137+
font-size: 14px;
138+
cursor: pointer;
139+
}
140+
141+
.fpm-primary {
142+
background: #6a0dad;
143+
color: #fff;
144+
}
145+
.fpm-primary:disabled {
146+
opacity: 0.6;
147+
cursor: default;
148+
}
149+
.fpm-primary:hover:not(:disabled) {
150+
background: #7d2ae8;
151+
}
152+
153+
.fpm-secondary {
154+
background: #f2f2f7;
155+
color: #333;
156+
border: 1px solid #e4e4ea;
157+
}
158+
.fpm-secondary:hover {
159+
background: #e9e9ef;
160+
}
161+
75162
/* ========== Dark Mode Overrides ========== */
76163

77164
.dark-mode .fpm-modal {
@@ -92,6 +179,15 @@
92179
background: #1f1f2c;
93180
}
94181

182+
.dark-mode .fpm-item.new-file {
183+
background: #201a33;
184+
border-color: rgba(128,90,213,0.35);
185+
}
186+
.dark-mode .fpm-item.new-file:hover {
187+
background: #251e3d;
188+
border-color: rgba(128,90,213,0.6);
189+
}
190+
95191
.dark-mode .fpm-name { color: #eee; }
96192
.dark-mode .fpm-lang { color: #bdbdbd; }
97193

@@ -101,3 +197,23 @@
101197
border: none;
102198
}
103199
.dark-mode .fpm-close:hover { filter: brightness(1.05); }
200+
201+
.dark-mode .fpm-newfile {
202+
background: #1a1a26;
203+
border-color: rgba(255,255,255,0.08);
204+
}
205+
.dark-mode .fpm-input,
206+
.dark-mode .fpm-select {
207+
background: #232333;
208+
color: #eee;
209+
border-color: #2f2f40;
210+
}
211+
.dark-mode .fpm-hint { color: #c8c1d9; }
212+
.dark-mode .fpm-preview { color: #c9a7ff; }
213+
.dark-mode .fpm-secondary {
214+
background: #2b2b34;
215+
color: #eaeaf2;
216+
border: 1px solid #3a3a48;
217+
}
218+
.dark-mode .fpm-secondary:hover { background: #34343f; }
219+
.dark-mode .fpm-primary { background: #6a0dad; }

src/components/codecast/codecastlive/FilePickerModal.jsx

Lines changed: 108 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,118 @@
1-
import React from 'react';
1+
import React, { useState } from 'react';
22
import './FilePickerModal.css';
33

44
export default function FilePickerModal({ files, onSelect, onClose }) {
5+
const [isNewFile, setIsNewFile] = useState(false);
6+
const [title, setTitle] = useState('');
7+
const [language, setLanguage] = useState('python');
8+
9+
// 언어 → 확장자 매핑
10+
const extensions = {
11+
python: 'py',
12+
java: 'java',
13+
c: 'c',
14+
'c++': 'cpp',
15+
javascript: 'js',
16+
};
17+
18+
const ensureExtension = (baseName, lang) => {
19+
const ext = extensions[lang] || '';
20+
const dotExt = `.${ext}`;
21+
// 사용자가 이미 같은 확장자를 쓴 경우 중복 방지
22+
if (baseName.toLowerCase().endsWith(dotExt)) return baseName;
23+
// 사용자가 다른 확장자를 쓴 경우는 그대로 두고 뒤에 선택한 확장자를 붙임
24+
return `${baseName}${dotExt}`;
25+
};
26+
27+
const handleCreate = () => {
28+
const raw = title.trim();
29+
if (!raw) return;
30+
const filename = ensureExtension(raw, language);
31+
onSelect({
32+
id: `new-${Date.now()}`,
33+
name: filename,
34+
language,
35+
content: '',
36+
});
37+
};
38+
539
return (
640
<div className="fpm-backdrop" onClick={onClose}>
741
<div className="fpm-modal" onClick={(e) => e.stopPropagation()}>
842
<h3>공유할 파일 선택</h3>
9-
<ul className="fpm-list">
10-
{files.map(f => (
11-
<li key={f.id} className="fpm-item" onClick={() => onSelect(f)}>
12-
<span className="fpm-name">{f.name}</span>
13-
<span className="fpm-lang">{f.language}</span>
14-
</li>
15-
))}
16-
</ul>
17-
<button className="fpm-close" onClick={onClose}>닫기</button>
43+
44+
{!isNewFile ? (
45+
<>
46+
<ul className="fpm-list">
47+
<li
48+
className="fpm-item new-file"
49+
onClick={() => setIsNewFile(true)}
50+
title="새 파일 작성하기"
51+
>
52+
<span className="fpm-name">+ 새 파일 작성하기</span>
53+
<span className="fpm-lang">제목 & 언어 선택</span>
54+
</li>
55+
56+
{files.map((f) => (
57+
<li
58+
key={f.id}
59+
className="fpm-item"
60+
onClick={() => onSelect(f)}
61+
>
62+
<span className="fpm-name">{f.name}</span>
63+
<span className="fpm-lang">{f.language}</span>
64+
</li>
65+
))}
66+
</ul>
67+
68+
<button className="fpm-close" onClick={onClose}>닫기</button>
69+
</>
70+
) : (
71+
<>
72+
<div className="fpm-newfile">
73+
<div className="fpm-row">
74+
<input
75+
className="fpm-input"
76+
type="text"
77+
placeholder="파일 제목을 입력하세요 (예: BubbleSort)"
78+
value={title}
79+
onChange={(e) => setTitle(e.target.value)}
80+
onKeyDown={(e) => { if (e.key === 'Enter') handleCreate(); }}
81+
autoFocus
82+
/>
83+
<select
84+
className="fpm-select"
85+
value={language}
86+
onChange={(e) => setLanguage(e.target.value)}
87+
>
88+
<option value="python">Python (.py)</option>
89+
<option value="java">Java (.java)</option>
90+
<option value="c">C (.c)</option>
91+
<option value="c++">C++ (.cpp)</option>
92+
<option value="javascript">JavaScript (.js)</option>
93+
</select>
94+
</div>
95+
96+
<div className="fpm-hint">
97+
최종 파일명 미리보기:&nbsp;
98+
<strong className="fpm-preview">
99+
{title ? ensureExtension(title, language) : '(제목을 입력하세요)'}
100+
</strong>
101+
</div>
102+
103+
<div className="fpm-actions">
104+
<button className="fpm-secondary" onClick={() => setIsNewFile(false)}>
105+
← 목록으로
106+
</button>
107+
<button className="fpm-primary" onClick={handleCreate} disabled={!title.trim()}>
108+
생성
109+
</button>
110+
</div>
111+
</div>
112+
113+
<button className="fpm-close" onClick={onClose}>닫기</button>
114+
</>
115+
)}
18116
</div>
19117
</div>
20118
);

0 commit comments

Comments
 (0)