Skip to content

Commit bf26909

Browse files
authored
Merge pull request #116 from DMU-DebugVisual/inseong_websocket
Inseong websocket
2 parents bd20135 + 34727a9 commit bf26909

File tree

18 files changed

+1678
-194
lines changed

18 files changed

+1678
-194
lines changed

src/components/codecast/codecastlive/CodecastLive.jsx

Lines changed: 98 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,62 @@ async function joinRoomApi(roomId, token) {
5959
throw new Error(text || `HTTP ${res.status}`);
6060
}
6161

62+
const pickFirstNonEmptyString = (...values) => {
63+
for (const value of values) {
64+
if (typeof value === 'string') {
65+
const trimmed = value.trim();
66+
if (trimmed) return trimmed;
67+
}
68+
}
69+
return '';
70+
};
71+
72+
const resolveParticipantName = (primary, fallbackEntry, fallbackId) => {
73+
const name = pickFirstNonEmptyString(
74+
primary?.displayName,
75+
primary?.nickName,
76+
primary?.nickname,
77+
primary?.profile?.nickname,
78+
primary?.profile?.name,
79+
primary?.userName,
80+
primary?.username,
81+
primary?.email,
82+
primary?.userEmail,
83+
primary?.ownerName,
84+
primary?.name,
85+
typeof primary?.userId === 'string' ? primary.userId : null,
86+
fallbackEntry?.displayName,
87+
fallbackEntry?.nickName,
88+
fallbackEntry?.nickname,
89+
fallbackEntry?.profile?.nickname,
90+
fallbackEntry?.profile?.name,
91+
fallbackEntry?.userName,
92+
fallbackEntry?.username,
93+
fallbackEntry?.email,
94+
fallbackEntry?.userEmail,
95+
fallbackEntry?.ownerName,
96+
fallbackEntry?.name,
97+
typeof fallbackEntry?.userId === 'string' ? fallbackEntry.userId : null,
98+
fallbackId != null ? String(fallbackId) : null
99+
);
100+
101+
return name || (fallbackId != null ? String(fallbackId) : '익명 사용자');
102+
};
103+
104+
const resolveParticipantEmail = (primary, fallbackEntry) => {
105+
return pickFirstNonEmptyString(
106+
primary?.email,
107+
primary?.userEmail,
108+
primary?.contactEmail,
109+
primary?.ownerEmail,
110+
fallbackEntry?.email,
111+
fallbackEntry?.userEmail,
112+
fallbackEntry?.contactEmail,
113+
fallbackEntry?.ownerEmail
114+
);
115+
};
116+
117+
62118
export default function CodecastLive({ isDark }) {
63119
const navigate = useNavigate();
64120
const location = useLocation();
@@ -69,6 +125,7 @@ export default function CodecastLive({ isDark }) {
69125
// 로그인 유저
70126
const storedUserId = localStorage.getItem('userId') || '';
71127
const username = localStorage.getItem('username') || storedUserId || 'anonymous';
128+
const storedEmail = localStorage.getItem('email') || localStorage.getItem('userEmail') || '';
72129
const token = localStorage.getItem('token') || '';
73130
useEffect(() => {
74131
if (!token) {
@@ -114,12 +171,14 @@ export default function CodecastLive({ isDark }) {
114171
() => ({
115172
id: userId,
116173
name: username,
174+
displayName: username,
175+
email: storedEmail,
117176
role: defaultRole,
118177
code: defaultFile.content,
119178
file: defaultFile,
120179
stage: 'ready',
121180
}),
122-
[userId, username, defaultRole, defaultFile]
181+
[userId, username, defaultRole, defaultFile, storedEmail]
123182
);
124183

125184
const [participants, setParticipants] = useState([initialMe]);
@@ -267,7 +326,10 @@ export default function CodecastLive({ isDark }) {
267326

268327
if (ownerIdFromSession) {
269328
sessionOwnerPatch = {
270-
name: sessionLike.ownerName || sessionLike.owner?.userName || sessionLike.owner?.name,
329+
ownerName: sessionLike.ownerName,
330+
name: resolveParticipantName(sessionLike.owner, null, ownerIdFromSession),
331+
displayName: resolveParticipantName(sessionLike.owner, null, ownerIdFromSession),
332+
email: resolveParticipantEmail(sessionLike.owner, null),
271333
stage: normalizedStage,
272334
sessionId: sId, // ✅ 세션 ID 저장
273335
file: sessionFile
@@ -353,9 +415,14 @@ export default function CodecastLive({ isDark }) {
353415
const prevEntry = prevMap.get(id);
354416
const normalizedId = String(id);
355417
const isHost = draft.role === 'host' || prevEntry?.role === 'host';
418+
const resolvedName = resolveParticipantName(draft, prevEntry, id);
419+
const resolvedEmail = resolveParticipantEmail(draft, prevEntry) || prevEntry?.email || '';
420+
356421
nextMap.set(id, {
357422
id,
358-
name: draft.name || prevEntry?.name || id,
423+
name: resolvedName,
424+
displayName: resolvedName,
425+
email: resolvedEmail,
359426
role: isHost
360427
? 'host'
361428
: editIds.has(normalizedId)
@@ -376,7 +443,10 @@ export default function CodecastLive({ isDark }) {
376443
const ownerParticipantId = msg.owner.userId || msg.owner.id;
377444
if (ownerParticipantId) {
378445
upsert(ownerParticipantId, {
379-
name: msg.owner.userName || msg.owner.userId || msg.owner.name || ownerParticipantId,
446+
ownerName: msg.owner.displayName || msg.owner.userName,
447+
name: resolveParticipantName(msg.owner, null, ownerParticipantId),
448+
displayName: resolveParticipantName(msg.owner, null, ownerParticipantId),
449+
email: resolveParticipantEmail(msg.owner, null),
380450
role: 'host',
381451
});
382452
}
@@ -386,7 +456,10 @@ export default function CodecastLive({ isDark }) {
386456
const participantId = participant?.userId || participant?.id;
387457
if (!participantId) return;
388458
upsert(participantId, {
389-
name: participant.userName || participant.userId || participant.name || participantId,
459+
displayName: participant.displayName || participant.ownerName,
460+
ownerName: participant.ownerName,
461+
name: resolveParticipantName(participant, null, participantId),
462+
email: resolveParticipantEmail(participant, null),
390463
stage: participant.stage,
391464
code: typeof participant.code === 'string' ? participant.code : undefined,
392465
file: participant.file,
@@ -410,7 +483,7 @@ export default function CodecastLive({ isDark }) {
410483
return ordered;
411484
});
412485
},
413-
[initialMe, sessionId, setSessionOwnerId, updateParticipants, userId]
486+
[initialMe, setSessionOwnerId, updateParticipants, userId]
414487
);
415488

416489
const handleSystemEvent = useCallback(
@@ -430,12 +503,19 @@ export default function CodecastLive({ isDark }) {
430503
}
431504

432505
if (ownerId) {
506+
const nameDraft = {
507+
ownerName,
508+
displayName: ownerName,
509+
name: ownerName,
510+
};
511+
433512
updateParticipants((prev) =>
434513
prev.map((p) =>
435514
p.id === ownerId
436515
? {
437516
...p,
438-
name: ownerName || p.name,
517+
name: resolveParticipantName(nameDraft, p, ownerId),
518+
displayName: resolveParticipantName(nameDraft, p, ownerId),
439519
sessionId: nextSessionId || p.sessionId, // ✅
440520
file: file
441521
? { ...p.file, ...file, ...(typeof code === 'string' ? { content: code } : {}) }
@@ -460,11 +540,19 @@ export default function CodecastLive({ isDark }) {
460540
}
461541

462542
if (ownerId && stage) {
543+
const nameDraft = {
544+
ownerName: payload.ownerName,
545+
displayName: payload.ownerName,
546+
name: payload.ownerName,
547+
};
548+
463549
updateParticipants((prev) =>
464550
prev.map((p) =>
465551
p.id === ownerId
466552
? {
467553
...p,
554+
name: resolveParticipantName(nameDraft, p, ownerId),
555+
displayName: resolveParticipantName(nameDraft, p, ownerId),
468556
sessionId: nextSessionId || p.sessionId, // ✅
469557
stage,
470558
file: file
@@ -809,11 +897,14 @@ export default function CodecastLive({ isDark }) {
809897
const isSessionOwner = !!ownerId && p.id === ownerId;
810898
const hasEditPermission = perms[p.id] === 'edit' || perms[idKey] === 'edit';
811899
const sessionRole = isSessionOwner ? 'owner' : hasEditPermission ? 'edit' : 'view';
900+
const displayName = resolveParticipantName(p, null, p.id);
812901

813902
return {
814903
...p,
815904
sessionRole,
816905
isRoomOwner: p.id === roomOwnerId,
906+
displayName,
907+
name: displayName,
817908
};
818909
});
819910
}, [participants, activeSessionPermissions, activeSessionMeta?.ownerId, roomOwnerId]);

src/components/codecast/codecastlive/CodecastSidebar.jsx

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,19 @@ export default function CodecastSidebar({
7474
<ul className="participant-list">
7575
{participants.map((participant) => {
7676
const role = participant.sessionRole || 'view';
77+
const displayName = (() => {
78+
const candidates = [
79+
participant.displayName,
80+
participant.name,
81+
participant.userName,
82+
participant.username,
83+
participant.email,
84+
participant.userEmail,
85+
participant.ownerName,
86+
participant.id != null ? String(participant.id) : ''
87+
];
88+
return candidates.find((text) => typeof text === 'string' && text.trim())?.trim() || '익명 사용자';
89+
})();
7790
const isSessionOwnerRole = role === 'owner';
7891
const isFocused = participant.id === focusedParticipantId;
7992
const isMenuOpen = menuFor === participant.id;
@@ -102,7 +115,7 @@ export default function CodecastSidebar({
102115
}}
103116
>
104117
{participant.avatar ? (
105-
<img src={participant.avatar} alt={participant.name} className="avatar" />
118+
<img src={participant.avatar} alt={displayName} className="avatar" />
106119
) : (
107120
<div className="avatar placeholder">
108121
<FaUser className="default-user-icon" />
@@ -111,7 +124,7 @@ export default function CodecastSidebar({
111124

112125
<div className="participant-main">
113126
<span className="name">
114-
{participant.name}
127+
{displayName}
115128
{isSelf && <span className="self-badge"></span>}
116129
</span>
117130
<span className={`stage ${stage}`}>{stageLabel}</span>
@@ -124,7 +137,7 @@ export default function CodecastSidebar({
124137
<button
125138
className="more-btn"
126139
type="button"
127-
aria-label={`${participant.name} 더보기`}
140+
aria-label={`${displayName} 더보기`}
128141
onClick={(event) => {
129142
event.stopPropagation();
130143
setMenuFor(isMenuOpen ? null : participant.id);

src/components/ide/AnimationFactory.js

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import GraphAnimation from './animations/GraphAnimation';
77
import HeapAnimation from './animations/HeapAnimation';
88
import LinkedListAnimation from './animations/LinkedListAnimation';
99
import RecursionAnimation from './animations/RecursionAnimation';
10+
import StackAnimation from './animations/StackAnimation';
11+
import QueueAnimation from './animations/QueueAnimation';
1012
import PlaceholderAnimation from './animations/PlaceholderAnimation';
1113

1214
/**
@@ -46,6 +48,16 @@ export class AnimationFactory {
4648
'fibonacci': RecursionAnimation,
4749
'factorial': RecursionAnimation,
4850

51+
// ✅ 스택
52+
'stack': StackAnimation,
53+
'stack-demo': StackAnimation,
54+
'stack-visualization': StackAnimation,
55+
56+
// ✅ 큐
57+
'queue': QueueAnimation,
58+
'queue-demo': QueueAnimation,
59+
'fifo-queue': QueueAnimation,
60+
4961
// 🚧 기타
5062
'variables': PlaceholderAnimation,
5163
'default': PlaceholderAnimation
@@ -102,9 +114,32 @@ export class AnimationFactory {
102114
if (vizType === 'graph') return 'graph';
103115
if (vizType === 'list' || vizType === 'linkedlist') return 'linked-list';
104116
if (vizType === 'recursion' || vizType === 'recursive') return 'recursion';
117+
if (vizType === 'stack') return 'stack';
118+
if (vizType === 'queue' || vizType === 'fifo') return 'queue';
105119
}
106120
}
107121

122+
const stackOp = events.find(e =>
123+
e.kind === 'ds_op' &&
124+
e.target &&
125+
e.target.toLowerCase().includes('stack')
126+
);
127+
if (stackOp) {
128+
console.log('✅ ds_op target으로 stack 감지');
129+
return 'stack';
130+
}
131+
132+
const queueOp = events.find(e => {
133+
if (e.kind !== 'ds_op' || !e.target) return false;
134+
const target = e.target.toLowerCase().trim();
135+
if (target.includes('queue')) return true;
136+
return target === 'q' || target === 'queue';
137+
});
138+
if (queueOp) {
139+
console.log('✅ ds_op target으로 queue 감지');
140+
return 'queue';
141+
}
142+
108143
// 2️⃣ ds_op의 target으로 자료구조 감지
109144
const heapOp = events.find(e =>
110145
e.kind === 'ds_op' &&

0 commit comments

Comments
 (0)