Files
ysj7705 69b647145f
CI / Type-check & Build (push) Failing after 5m50s
CI / Build & Push image (push) Skipped
first commit
2026-08-12 15:01:43 +09:00

109 lines
3.6 KiB
Vue

<script setup lang="ts">
import type { ActiveStream, Participant, WatchMap } from '~/types/room'
const props = defineProps<{
participants: Participant[]
activeStreams: ActiveStream[]
watchMap: WatchMap
myClientId: string
watchingStreamIds: string[]
}>()
const emit = defineEmits<{
watch: [stream: ActiveStream]
unwatch: [stream: ActiveStream]
}>()
function nicknameOf(clientId: string) {
return props.participants.find((p) => p.clientId === clientId)?.nickname || clientId.slice(0, 6)
}
function viewersFor(streamId: string) {
const ids: string[] = []
for (const [viewerId, streams] of Object.entries(props.watchMap)) {
if (streams.includes(streamId)) ids.push(viewerId)
}
return ids.map(nicknameOf)
}
function isWatching(streamId: string) {
return props.watchingStreamIds.includes(streamId)
}
function watchingSummary(clientId: string) {
const streams = props.watchMap[clientId] || []
if (!streams.length) return '시청 없음'
return streams
.map((sid) => {
const s = props.activeStreams.find((a) => a.streamId === sid)
return s?.broadcasterNickname || 'stream'
})
.join(', ')
}
</script>
<template>
<aside class="flex h-full flex-col gap-4 border-r border-[var(--border)] bg-[var(--bg-elevated)]/50 p-3">
<section>
<h3 class="mb-2 text-xs font-semibold uppercase tracking-wider text-[var(--text-muted)]">
참여자 ({{ participants.length }})
</h3>
<ul class="space-y-2">
<li
v-for="p in participants"
:key="p.clientId"
class="rounded-md border border-[var(--border)] bg-[var(--bg-panel)] px-2.5 py-2 text-sm"
>
<div class="flex items-center justify-between gap-2">
<span class="font-medium">
{{ p.nickname }}
<span v-if="p.clientId === myClientId" class="text-[var(--text-muted)]">(나)</span>
</span>
<span
v-if="p.isStreaming"
class="rounded bg-[var(--accent-soft)] px-1.5 py-0.5 text-[10px] text-[var(--accent)]"
>
LIVE
</span>
</div>
<p class="mt-1 text-[11px] text-[var(--text-muted)]">
시청: {{ watchingSummary(p.clientId) }}
</p>
</li>
</ul>
</section>
<section class="flex-1 overflow-auto">
<h3 class="mb-2 text-xs font-semibold uppercase tracking-wider text-[var(--text-muted)]">
방송 중 ({{ activeStreams.length }})
</h3>
<ul class="space-y-2">
<li
v-for="s in activeStreams"
:key="s.streamId"
class="rounded-md border border-[var(--border)] bg-[var(--bg-panel)] px-2.5 py-2 text-sm"
>
<p class="font-medium">{{ s.broadcasterNickname }}</p>
<p class="mt-0.5 text-[11px] text-[var(--text-muted)]">
시청자: {{ viewersFor(s.streamId).join(', ') || '없음' }}
</p>
<div class="mt-2">
<button
v-if="s.broadcasterId !== myClientId"
type="button"
class="btn-ghost w-full !py-1 text-xs"
@click="isWatching(s.streamId) ? emit('unwatch', s) : emit('watch', s)"
>
{{ isWatching(s.streamId) ? '시청 종료' : '시청' }}
</button>
<p v-else class="text-[11px] text-[var(--text-muted)]">내 화면</p>
</div>
</li>
<li v-if="!activeStreams.length" class="text-xs text-[var(--text-muted)]">
아직 공유 중인 화면이 없습니다.
</li>
</ul>
</section>
</aside>
</template>