Skip to content

Commit 45ff1b7

Browse files
authored
Merge pull request #80 from DMU-DebugVisual/sojeong
코드방송 다크모드, 포커스, 파일선택 추가
2 parents 249df4d + c7cb4e5 commit 45ff1b7

14 files changed

+758
-211
lines changed

src/App.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ function AppContent() {
7979
<Route path="/community/post/:id" element={<PostDetail />} /> {/* ✅ 상세 페이지 */}
8080
<Route path="/broadcast" element={<Codecast />} />
8181
<Route path="/startbroadcast" element={<StartCodecast />} />
82-
<Route path="/broadcast/live" element={<CodecastLive />} />
82+
<Route path="/broadcast/live" element={<CodecastLive isDark={isDark} />} />
8383
<Route path="/mypage" element={<MyPageLayout nickname={nickname} />}>
8484
<Route index element={<Mypage nickname={nickname} />} />
8585
<Route path="project" element={<MyProject />} />

src/components/codecast/Codecast.jsx

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,24 @@
11
import React from "react";
22
import { FaPlus, FaArrowRight, FaDesktop } from "react-icons/fa";
33
import "./Codecast.css";
4-
import {Link} from "react-router-dom";
4+
import { Link, useNavigate } from "react-router-dom";
55

66
const Codecast = () => {
7+
const navigate = useNavigate();
8+
9+
const handleJoin = () => {
10+
// 나중에 코드 검증 로직 추가 가능
11+
navigate("/broadcast/live");
12+
};
13+
714
return (
815
<div>
9-
1016
<section className="broadcast-container">
1117
<div className="broadcast-header">
1218
<FaDesktop className="broadcast-icon" />
1319
<h2>코드 방송</h2>
1420
<p>
15-
실시간으로 코드를 공유하고 함께 학습하세요.<br/>
21+
실시간으로 코드를 공유하고 함께 학습하세요.<br />
1622
방송 코드를 입력하거나 직접 방송을 시작해보세요.
1723
</p>
1824
</div>
@@ -22,9 +28,9 @@ const Codecast = () => {
2228
<h3>방송 참여하기</h3>
2329
<p>방송 코드를 입력하여 진행 중인 방송에 참여하세요.</p>
2430
<div className="input-group">
25-
<input type="text" placeholder="방송 코드 입력"/>
26-
<button>
27-
<FaArrowRight/>
31+
<input type="text" placeholder="방송 코드 입력" />
32+
<button onClick={handleJoin}>
33+
<FaArrowRight />
2834
참여하기
2935
</button>
3036
</div>
@@ -36,7 +42,7 @@ const Codecast = () => {
3642
<h3>새 방송 시작하기</h3>
3743
<p>새로운 코드 방송을 시작하여 다른 사용자들과 실시간으로 코드를 공유하세요.</p>
3844
<Link to="/startbroadcast" className="start-btn">
39-
<FaPlus/>
45+
<FaPlus />
4046
새 방송 시작
4147
</Link>
4248
</div>
Lines changed: 34 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
1+
/* ========== 기본: Light ========== */
2+
13
.code-editor {
24
flex: 1;
5+
min-width: 0;
6+
min-height: 0;
37
display: flex;
48
flex-direction: column;
5-
background-color: #0f0f1a;
6-
padding: 20px;
7-
overflow-y: auto;
8-
color: white;
9+
background-color: #ffffff;
10+
padding: 16px;
11+
color: #222;
912
font-family: 'Courier New', monospace;
1013
font-size: 15px;
1114
}
@@ -14,20 +17,25 @@
1417
display: flex;
1518
justify-content: space-between;
1619
align-items: center;
17-
margin-bottom: 10px;
20+
margin-bottom: 8px;
21+
gap: 12px;
1822
}
1923

24+
/* ✅ 사용자 뱃지 (복원) */
2025
.current-user-badge {
21-
background-color: #222;
22-
color: #fff;
26+
background-color: #f2f2f4;
27+
color: #333;
2328
padding: 6px 12px;
2429
border-radius: 12px;
2530
font-weight: 600;
2631
font-size: 14px;
27-
box-shadow: 0 0 4px rgba(0, 0, 0, 0.3);
32+
border: 1px solid #e4e4ea;
33+
display: inline-flex;
34+
align-items: center;
35+
gap: 6px;
2836
}
2937

30-
38+
/* 실행 버튼 */
3139
.run-button {
3240
background-color: #6a0dad;
3341
color: white;
@@ -41,14 +49,25 @@
4149
gap: 6px;
4250
transition: background-color 0.2s ease;
4351
}
52+
.run-button:hover { background-color: #8844dd; }
4453

45-
.run-button:hover {
46-
background-color: #8844dd;
54+
/* Monaco 에디터가 부모 높이를 꽉 채우도록 */
55+
.code-editor > div, /* @monaco-editor/react 래퍼 */
56+
.code-editor .monaco-editor,
57+
.code-editor .monaco-editor .overflow-guard {
58+
flex: 1;
59+
min-height: 0;
4760
}
4861

49-
.code-block {
62+
/* ========== Dark Mode Overrides ========== */
63+
64+
.dark-mode .code-editor {
5065
background-color: #0f0f1a;
51-
padding: 10px;
52-
white-space: pre-wrap;
53-
line-height: 1.5;
66+
color: #fff;
67+
}
68+
69+
.dark-mode .current-user-badge {
70+
background-color: #222;
71+
color: #fff;
72+
border-color: #3a3a48;
5473
}
Lines changed: 42 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,69 @@
1-
import React, {useEffect, useState} from 'react';
1+
import React, { useEffect, useMemo, useState } from 'react';
22
import Editor from '@monaco-editor/react';
33
import { FaPlay } from 'react-icons/fa';
44
import './CodeEditor.css';
55

6-
const CodeEditor = ({ currentUser }) => {
7-
const [code, setCode] = useState(currentUser.code);
6+
/**
7+
* Props
8+
* - file?: { name, language, content }
9+
* - readOnly?: boolean
10+
* - onChange?: (next: string) => void
11+
* - currentUser?: { name: string, role: 'host'|'edit'|'view', code?: string } // ✅ 뱃지 표시용
12+
*/
13+
export default function CodeEditor({ file, readOnly = false, onChange, currentUser, isDark }) {
14+
const initialValue = useMemo(() => file?.content ?? '', [file?.content]);
15+
const [code, setCode] = useState(initialValue);
816

9-
// ✅ currentUser가 바뀔 때마다 코드 상태를 새로 설정
10-
useEffect(() => {
11-
setCode(currentUser.code);
12-
}, [currentUser]);
17+
useEffect(() => setCode(initialValue), [initialValue]);
1318

14-
const handleRun = () => {
15-
alert("코드 실행 기능은 아직 미구현입니다.");
16-
};
19+
const language = useMemo(() => file?.language || 'python', [file?.language]);
20+
21+
const handleRun = () => alert('코드 실행 기능은 아직 미구현입니다.');
1722

18-
const getIcon = (role) => {
19-
switch (role) {
20-
case 'host':
21-
return '👑';
22-
case 'editing':
23-
return '✍️';
24-
default:
25-
return '';
26-
}
23+
const roleIcon = (role) => {
24+
if (role === 'host') return '👑';
25+
if (role === 'edit') return '✏️';
26+
if (role === 'view') return '👁️';
27+
return '';
2728
};
2829

30+
// const theme = document.body.classList.contains('dark-mode') ? 'vs-light' : 'vs-dark';
31+
const theme = isDark ? 'vs-dark' : 'vs-light'; // ✅ props 우선
2932
return (
3033
<section className="code-editor">
3134
<div className="editor-header">
32-
<div className="current-user-badge">
33-
{getIcon(currentUser.role)} {currentUser.name}
34-
</div>
35+
{/* ✅ 사용자 뱃지 복원, 권한 뱃지 제거 */}
36+
{currentUser?.name && (
37+
<div className="current-user-badge">
38+
{roleIcon(currentUser.role)} {currentUser.name}
39+
</div>
40+
)}
41+
3542
<button className="run-button" onClick={handleRun}>
3643
<FaPlay /> 실행
3744
</button>
3845
</div>
3946

47+
{/* 필요 시 height=100%로 바꿔도 됩니다 (부모 컨테이너가 flex:1이면) */}
4048
<Editor
41-
height="calc(100vh - 160px)" // 헤더와 버튼 고려
42-
defaultLanguage="python"
43-
theme="vs-dark"
49+
height="90%"
50+
language={language}
51+
theme={theme}
4452
value={code}
45-
onChange={(newValue) => setCode(newValue)}
53+
onChange={(v) => {
54+
const next = v ?? '';
55+
setCode(next);
56+
onChange && onChange(next);
57+
}}
4658
options={{
59+
readOnly,
4760
fontSize: 14,
4861
minimap: { enabled: false },
4962
padding: { top: 10 },
63+
scrollBeyondLastLine: false,
64+
smoothScrolling: true,
5065
}}
5166
/>
5267
</section>
5368
);
54-
};
55-
56-
export default CodeEditor;
69+
}
Lines changed: 71 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,88 @@
1-
.code-preview-list {
2-
background-color: #191919;
3-
padding: 12px 16px;
1+
/* ========== 기본: Light ========== */
2+
3+
.preview-bar {
4+
border-top: 1px solid #e6e6ea;
5+
background: #fafafb;
6+
padding: 10px 12px;
7+
}
8+
9+
.preview-track {
410
display: flex;
5-
gap: 16px;
11+
gap: 12px;
612
overflow-x: auto;
7-
border-top: 1px solid #333;
13+
scrollbar-width: thin;
814
}
915

10-
.code-preview-card {
11-
background-color: #1f1f2e;
16+
.preview-card {
17+
flex: 0 0 280px;
18+
text-align: left;
19+
background: #ffffff;
20+
border: 1px solid #e6e6ef;
1221
border-radius: 10px;
13-
min-width: 240px;
14-
max-width: 240px;
15-
flex-shrink: 0;
16-
color: white;
17-
font-size: 13px;
18-
font-family: 'Courier New', monospace;
19-
display: flex;
20-
flex-direction: column;
21-
border: 1px solid #333;
22-
overflow: hidden;
22+
padding: 10px;
23+
cursor: pointer;
24+
transition: transform .12s ease, box-shadow .12s ease, border-color .12s ease;
25+
}
26+
.preview-card:hover {
27+
transform: translateY(-1px);
28+
box-shadow: 0 6px 14px rgba(0,0,0,0.08);
29+
border-color: #d7d7ec;
30+
}
31+
.preview-card.active {
32+
border-color: #6f42c1;
33+
box-shadow: 0 0 0 2px rgba(111,66,193,0.15) inset;
2334
}
2435

25-
.card-header {
26-
background-color: #2c2c2c;
27-
padding: 8px 12px;
36+
.preview-header {
2837
display: flex;
29-
justify-content: space-between;
3038
align-items: center;
31-
font-weight: bold;
39+
gap: 8px;
40+
margin-bottom: 8px;
41+
}
42+
.r-icon { font-size: 14px; }
43+
.r-icon.host { color: #ffbf00; }
44+
.r-icon.edit { color: #00a152; }
45+
.r-icon.view { color: #9aa0a6; }
46+
47+
.p-name {
48+
font-weight: 700;
49+
color: #222;
3250
font-size: 14px;
33-
border-bottom: 1px solid #444;
34-
height: 30px;
3551
}
3652

37-
.card-name {
38-
color: #fff;
53+
.snippet {
54+
margin: 0;
55+
color: #555;
56+
font-size: 12px;
57+
white-space: pre-wrap;
58+
line-height: 1.4;
59+
max-height: 80px;
60+
overflow: hidden;
3961
}
4062

41-
.card-role {
42-
font-size: 16px;
63+
/* ========== Dark Mode Overrides ========== */
64+
65+
.dark-mode .preview-bar {
66+
border-top: 1px solid #333;
67+
background: #141419;
4368
}
4469

45-
.preview-code {
46-
padding: 12px;
47-
white-space: pre-wrap;
48-
line-height: 1.5;
49-
max-height: calc(1.4em * 5); /* 4줄 기준 */
50-
overflow: hidden;
70+
.dark-mode .preview-card {
71+
background: #1a1a26;
72+
border: 1px solid rgba(255,255,255,0.08);
73+
}
74+
.dark-mode .preview-card:hover {
75+
border-color: rgba(128,90,213,0.5);
76+
box-shadow: 0 8px 20px rgba(0,0,0,0.25);
77+
}
78+
.dark-mode .preview-card.active {
79+
border-color: #6a0dad;
80+
box-shadow: 0 0 0 2px rgba(106,13,173,0.25) inset;
5181
}
82+
83+
.dark-mode .p-name { color: #eee; }
84+
.dark-mode .snippet { color: #c8c1d9; }
85+
86+
.dark-mode .r-icon.host { color: gold; }
87+
.dark-mode .r-icon.edit { color: #00c853; }
88+
.dark-mode .r-icon.view { color: #888; }

0 commit comments

Comments
 (0)