@@ -106,6 +106,7 @@ export default function CodecastLive({ isDark }) {
106106
107107 const [ participants , setParticipants ] = useState ( [ initialMe ] ) ;
108108 const [ currentUser , setCurrentUser ] = useState ( initialMe ) ;
109+ const [ activeParticipantId , setActiveParticipantId ] = useState ( initialMe . id ) ;
109110
110111 // 채팅
111112 const [ isChatOpen , setIsChatOpen ] = useState ( false ) ;
@@ -144,6 +145,21 @@ export default function CodecastLive({ isDark }) {
144145
145146 const { connect, disconnect, subscribeSystem, subscribeCode, sendCodeUpdate, publish } = useCollabSocket ( ) ;
146147
148+ useEffect ( ( ) => {
149+ const exists = participants . some ( ( p ) => p . id === activeParticipantId ) ;
150+ if ( ! exists ) {
151+ const hostParticipant = participants . find ( ( p ) => p . role === 'host' ) ;
152+ setActiveParticipantId ( hostParticipant ?. id || userId ) ;
153+ }
154+ } , [ participants , activeParticipantId , userId ] ) ;
155+
156+ const activeParticipant = useMemo ( ( ) => {
157+ const target = participants . find ( ( p ) => p . id === activeParticipantId ) ;
158+ if ( target ) return target ;
159+ const selfParticipant = participants . find ( ( p ) => p . id === userId ) ;
160+ return selfParticipant || participants [ 0 ] || null ;
161+ } , [ participants , activeParticipantId , userId ] ) ;
162+
147163 const applyRoomStateUpdate = useCallback (
148164 ( msg ) => {
149165 if ( ! msg ) return ;
@@ -440,6 +456,7 @@ export default function CodecastLive({ isDark }) {
440456
441457 // 에디터 변경 → 서버 publish
442458 const handleEditorChange = ( nextText ) => {
459+ if ( activeParticipantId !== currentUser . id ) return ;
443460 // 서버에 보내는 것은 동일
444461 // 로컬 상태 업데이트
445462 setCurrentUser ( ( prev ) => ( {
@@ -533,6 +550,8 @@ export default function CodecastLive({ isDark }) {
533550 stage : 'ready' ,
534551 } ) ) ;
535552
553+ setActiveParticipantId ( userId ) ;
554+
536555 broadcastSystemEvent ( 'SESSION_READY' , {
537556 sessionId : newSessionId ,
538557 ownerId : currentUser . id ,
@@ -586,6 +605,8 @@ export default function CodecastLive({ isDark }) {
586605 return ;
587606 }
588607
608+ setActiveParticipantId ( userId ) ;
609+
589610 const nextStage = currentUser . stage === 'editing' ? 'ready' : 'editing' ;
590611 const prevStage = currentUser . stage ;
591612 const nextStatus = nextStage === 'editing' ? 'ACTIVE' : 'INACTIVE' ;
@@ -772,9 +793,38 @@ export default function CodecastLive({ isDark }) {
772793 const findByName = ( name ) => participants . find ( ( p ) => p . name === name ) ;
773794 const findById = ( id ) => participants . find ( ( p ) => p . id === id ) ;
774795
796+ const isViewingSelf = activeParticipant ?. id === currentUser . id ;
797+ const editorReadOnly = ! ( isViewingSelf && currentUser . stage === 'editing' && canEdit ) ;
798+
799+ const previewParticipants = useMemo ( ( ) => {
800+ const editingList = participants . filter ( ( p ) => p . stage === 'editing' && p . file !== null ) ;
801+ const selfParticipant = participants . find ( ( p ) => p . id === userId ) ;
802+ if ( selfParticipant && ! editingList . some ( ( p ) => p . id === selfParticipant . id ) ) {
803+ editingList . push ( selfParticipant ) ;
804+ }
805+ return editingList ;
806+ } , [ participants , userId ] ) ;
807+
775808 // 참가자가 'editing' 상태일 때만 프리뷰에 표시
776809 // 이제 기본 파일도 공유 가능하므로 defaultFile.id 조건은 제거합니다.
777- const isAnyParticipantSharing = participants . some ( p => p . stage === 'editing' && p . file !== null ) ;
810+ const isAnyParticipantSharing = previewParticipants . some ( p => p . stage === 'editing' && p . file !== null ) ;
811+
812+ useEffect ( ( ) => {
813+ if ( ! isAnyParticipantSharing && activeParticipantId !== userId ) {
814+ setActiveParticipantId ( userId ) ;
815+ }
816+ } , [ isAnyParticipantSharing , activeParticipantId , userId ] ) ;
817+
818+ useEffect ( ( ) => {
819+ if ( ! isViewingSelf ) return ;
820+ if ( currentUser . stage === 'editing' ) return ;
821+ const editingParticipant = participants . find (
822+ ( p ) => p . stage === 'editing' && p . file !== null && p . id !== userId
823+ ) ;
824+ if ( editingParticipant && editingParticipant . id !== activeParticipantId ) {
825+ setActiveParticipantId ( editingParticipant . id ) ;
826+ }
827+ } , [ isViewingSelf , currentUser . stage , participants , activeParticipantId , userId ] ) ;
778828
779829
780830 return (
@@ -876,11 +926,12 @@ export default function CodecastLive({ isDark }) {
876926
877927 < div className = "editor-area" >
878928 < CodeEditor
879- file = { currentUser . file }
929+ file = { activeParticipant ? .file }
880930 onChange = { handleEditorChange }
881- currentUser = { currentUser }
931+ currentUser = { activeParticipant || currentUser }
932+ readOnly = { editorReadOnly }
882933 isDark = { isDark }
883- selectFileAction = { FileSelectButton }
934+ selectFileAction = { isViewingSelf ? FileSelectButton : null }
884935 />
885936 </ div >
886937
@@ -923,11 +974,11 @@ export default function CodecastLive({ isDark }) {
923974 < div className = "preview-strip nochrome" aria-label = "participants preview strip" >
924975 < div className = "preview-strip-scroller" >
925976 < CodePreviewList
926- participants = { participants . filter ( p => p . stage === 'editing' ) }
927- activeName = { currentUser . name }
977+ participants = { previewParticipants }
978+ activeName = { activeParticipant ?. name || currentUser . name }
928979 onSelect = { ( userName ) => {
929980 const pickedUser = participants . find ( ( p ) => p . name === userName ) ;
930- if ( pickedUser ) setCurrentUser ( pickedUser ) ;
981+ if ( pickedUser ) setActiveParticipantId ( pickedUser . id ) ;
931982 } }
932983 />
933984 </ div >
0 commit comments