This commit is contained in:
2025-12-12 13:30:09 +03:00
commit 2f1e1f35e3
75 changed files with 4603 additions and 0 deletions

View File

@@ -0,0 +1,193 @@
<template>
<div class="room-page" v-if="room">
<div class="room-header">
<h1>{{ room.name }}</h1>
<button class="btn-secondary" @click="leaveAndGoHome">Выйти из комнаты</button>
</div>
<div class="room-layout">
<div class="main-section">
<AudioPlayer
:ws="websocket"
@player-action="handlePlayerAction"
/>
<div class="queue-section card">
<div class="queue-header">
<h3>Очередь</h3>
<button class="btn-secondary" @click="showAddTrack = true">Добавить</button>
</div>
<Queue :queue="roomStore.queue" @play-track="playTrack" />
</div>
</div>
<div class="side-section">
<ParticipantsList :participants="roomStore.participants" />
<ChatWindow :room-id="roomId" :ws="websocket" />
</div>
</div>
<Modal v-if="showAddTrack" title="Добавить в очередь" @close="showAddTrack = false">
<TrackList
:tracks="tracksStore.tracks"
selectable
@select="addTrackToQueue"
/>
</Modal>
</div>
<div v-else class="loading">Загрузка...</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { useRoomStore } from '../stores/room'
import { useTracksStore } from '../stores/tracks'
import { usePlayerStore } from '../stores/player'
import { useWebSocket } from '../composables/useWebSocket'
import { usePlayer } from '../composables/usePlayer'
import AudioPlayer from '../components/player/AudioPlayer.vue'
import Queue from '../components/room/Queue.vue'
import ParticipantsList from '../components/room/ParticipantsList.vue'
import ChatWindow from '../components/chat/ChatWindow.vue'
import TrackList from '../components/tracks/TrackList.vue'
import Modal from '../components/common/Modal.vue'
const route = useRoute()
const router = useRouter()
const roomStore = useRoomStore()
const tracksStore = useTracksStore()
const playerStore = usePlayerStore()
const roomId = route.params.id
const room = ref(null)
const showAddTrack = ref(false)
const { syncToState, setOnTrackEnded } = usePlayer()
function handleTrackEnded() {
sendPlayerAction('next')
}
function handleWsMessage(msg) {
switch (msg.type) {
case 'player_state':
case 'sync_state':
// Call syncToState BEFORE updating store so it can detect URL changes
syncToState(msg)
playerStore.setPlayerState(msg)
break
case 'user_joined':
roomStore.addParticipant(msg.user)
break
case 'user_left':
roomStore.removeParticipant(msg.user_id)
break
case 'queue_updated':
roomStore.fetchQueue(roomId)
break
}
}
const { connect, disconnect, sendPlayerAction, connected } = useWebSocket(roomId, handleWsMessage)
const websocket = { sendPlayerAction, connected }
onMounted(async () => {
await roomStore.fetchRoom(roomId)
room.value = roomStore.currentRoom
await roomStore.joinRoom(roomId)
await roomStore.fetchQueue(roomId)
await tracksStore.fetchTracks()
// Set callback for when track ends
setOnTrackEnded(handleTrackEnded)
connect()
})
onUnmounted(() => {
disconnect()
})
function handlePlayerAction(action, position) {
sendPlayerAction(action, position)
}
function playTrack(track) {
sendPlayerAction('set_track', null, track.id)
}
async function addTrackToQueue(track) {
await roomStore.addToQueue(roomId, track.id)
showAddTrack.value = false
}
async function leaveAndGoHome() {
await roomStore.leaveRoom(roomId)
router.push('/')
}
</script>
<style scoped>
.room-page {
padding-top: 20px;
}
.room-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 24px;
}
.room-header h1 {
margin: 0;
}
.room-layout {
display: grid;
grid-template-columns: 1fr 350px;
gap: 20px;
}
.main-section {
display: flex;
flex-direction: column;
gap: 20px;
}
.side-section {
display: flex;
flex-direction: column;
gap: 20px;
}
.queue-section {
flex: 1;
}
.queue-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
}
.queue-header h3 {
margin: 0;
}
.loading {
text-align: center;
padding: 40px;
color: #aaa;
}
@media (max-width: 900px) {
.room-layout {
grid-template-columns: 1fr;
}
}
</style>