stuff
This commit is contained in:
parent
13ecd4ff57
commit
625c5837ee
4 changed files with 161 additions and 110 deletions
|
|
@ -739,50 +739,58 @@ export class GameGateway implements OnGatewayConnection, OnGatewayDisconnect, On
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Обновляем вопросы через service (который добавит UUID если нужно)
|
try {
|
||||||
await this.roomsService.updateRoomPack(payload.roomId, payload.questions);
|
// Убеждаемся, что questions - это массив
|
||||||
|
const questionsArray = Array.isArray(payload.questions) ? payload.questions : [];
|
||||||
|
|
||||||
// После обновления вопросов проверяем и обновляем currentQuestionId
|
// Обновляем вопросы через service (который добавит UUID если нужно)
|
||||||
const room = (await this.prisma.room.findUnique({
|
await this.roomsService.updateRoomPack(payload.roomId, questionsArray);
|
||||||
where: { id: payload.roomId },
|
|
||||||
include: { roomPack: true } as unknown as Prisma.RoomInclude,
|
|
||||||
})) as unknown as RoomWithPack | null;
|
|
||||||
|
|
||||||
if (room) {
|
// После обновления вопросов проверяем и обновляем currentQuestionId
|
||||||
const questions = ((room.roomPack as unknown as { questions?: Question[] } | null)?.questions || []) as Question[];
|
const room = (await this.prisma.room.findUnique({
|
||||||
const currentQuestionId = (room.currentQuestionId as string | null) || null;
|
where: { id: payload.roomId },
|
||||||
|
include: { roomPack: true } as unknown as Prisma.RoomInclude,
|
||||||
|
})) as unknown as RoomWithPack | null;
|
||||||
|
|
||||||
// Проверяем, что currentQuestionId все еще валиден
|
if (room) {
|
||||||
let validQuestionId = currentQuestionId;
|
const questions = ((room.roomPack as unknown as { questions?: Question[] } | null)?.questions || []) as Question[];
|
||||||
if (currentQuestionId) {
|
const currentQuestionId = (room.currentQuestionId as string | null) || null;
|
||||||
const questionExists = questions.some((q: any) => q.id === currentQuestionId);
|
|
||||||
if (!questionExists) {
|
// Проверяем, что currentQuestionId все еще валиден
|
||||||
// Текущий вопрос был удален, устанавливаем первый
|
let validQuestionId = currentQuestionId;
|
||||||
validQuestionId = questions[0]?.id && typeof questions[0].id === 'string'
|
if (currentQuestionId) {
|
||||||
|
const questionExists = questions.some((q: any) => q.id === currentQuestionId);
|
||||||
|
if (!questionExists) {
|
||||||
|
// Текущий вопрос был удален, устанавливаем первый
|
||||||
|
validQuestionId = questions[0]?.id && typeof questions[0].id === 'string'
|
||||||
|
? questions[0].id
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
} else if (questions.length > 0) {
|
||||||
|
// Если нет currentQuestionId, устанавливаем первый
|
||||||
|
validQuestionId = questions[0].id && typeof questions[0].id === 'string'
|
||||||
? questions[0].id
|
? questions[0].id
|
||||||
: null;
|
: null;
|
||||||
}
|
}
|
||||||
} else if (questions.length > 0) {
|
|
||||||
// Если нет currentQuestionId, устанавливаем первый
|
// Обновляем currentQuestionId если изменился
|
||||||
validQuestionId = questions[0].id && typeof questions[0].id === 'string'
|
if (validQuestionId !== currentQuestionId) {
|
||||||
? questions[0].id
|
const questionIndex = questions.findIndex((q: any) => q.id === validQuestionId);
|
||||||
: null;
|
await this.prisma.room.update({
|
||||||
|
where: { id: payload.roomId },
|
||||||
|
data: {
|
||||||
|
currentQuestionId: validQuestionId,
|
||||||
|
currentQuestionIndex: questionIndex >= 0 ? questionIndex : 0
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Обновляем currentQuestionId если изменился
|
await this.broadcastFullState(payload.roomCode);
|
||||||
if (validQuestionId !== currentQuestionId) {
|
} catch (error: any) {
|
||||||
const questionIndex = questions.findIndex((q: any) => q.id === validQuestionId);
|
console.error('Error updating room pack:', error);
|
||||||
await this.prisma.room.update({
|
client.emit('error', { message: error.message || 'Failed to update questions' });
|
||||||
where: { id: payload.roomId },
|
|
||||||
data: {
|
|
||||||
currentQuestionId: validQuestionId,
|
|
||||||
currentQuestionIndex: questionIndex >= 0 ? questionIndex : 0
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.broadcastFullState(payload.roomCode);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SubscribeMessage('importQuestions')
|
@SubscribeMessage('importQuestions')
|
||||||
|
|
@ -810,8 +818,13 @@ export class GameGateway implements OnGatewayConnection, OnGatewayDisconnect, On
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.roomPackService.importQuestions(payload.roomId, payload.sourcePackId, payload.questionIndices);
|
try {
|
||||||
await this.broadcastFullState(payload.roomCode);
|
await this.roomPackService.importQuestions(payload.roomId, payload.sourcePackId, payload.questionIndices);
|
||||||
|
await this.broadcastFullState(payload.roomCode);
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('Error importing questions:', error);
|
||||||
|
client.emit('error', { message: error.message || 'Failed to import questions' });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@SubscribeMessage('kickPlayer')
|
@SubscribeMessage('kickPlayer')
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
import { useState, useEffect } from 'react'
|
import { useState, useEffect } from 'react'
|
||||||
import { questionsApi } from '../services/api'
|
import { questionsApi } from '../services/api'
|
||||||
import { useTheme } from '../context/ThemeContext'
|
import { useTheme } from '../context/ThemeContext'
|
||||||
|
import { useAuth } from '../context/AuthContext'
|
||||||
|
import socketService from '../services/socket'
|
||||||
import './GameManagementModal.css'
|
import './GameManagementModal.css'
|
||||||
import './QuestionsModal.css'
|
import './QuestionsModal.css'
|
||||||
|
|
||||||
|
|
@ -37,6 +39,7 @@ const GameManagementModal = ({
|
||||||
onAddPlayer,
|
onAddPlayer,
|
||||||
}) => {
|
}) => {
|
||||||
const { currentThemeData } = useTheme()
|
const { currentThemeData } = useTheme()
|
||||||
|
const { user } = useAuth()
|
||||||
const [activeTab, setActiveTab] = useState(initialTab) // players | game | scoring | questions
|
const [activeTab, setActiveTab] = useState(initialTab) // players | game | scoring | questions
|
||||||
const [selectedPlayer, setSelectedPlayer] = useState(null)
|
const [selectedPlayer, setSelectedPlayer] = useState(null)
|
||||||
const [customPoints, setCustomPoints] = useState(10)
|
const [customPoints, setCustomPoints] = useState(10)
|
||||||
|
|
@ -96,6 +99,24 @@ const GameManagementModal = ({
|
||||||
}
|
}
|
||||||
}, [isOpen, initialTab])
|
}, [isOpen, initialTab])
|
||||||
|
|
||||||
|
// Обработка WebSocket ошибок для операций с вопросами
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isOpen) return
|
||||||
|
|
||||||
|
const handleError = (error) => {
|
||||||
|
console.error('WebSocket error in GameManagementModal:', error)
|
||||||
|
if (error.message) {
|
||||||
|
setJsonError(error.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
socketService.on('error', handleError)
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
socketService.off('error', handleError)
|
||||||
|
}
|
||||||
|
}, [isOpen])
|
||||||
|
|
||||||
if (!isOpen) return null
|
if (!isOpen) return null
|
||||||
|
|
||||||
const gameStatus = room?.status || 'WAITING'
|
const gameStatus = room?.status || 'WAITING'
|
||||||
|
|
@ -256,13 +277,16 @@ const GameManagementModal = ({
|
||||||
})),
|
})),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Убеждаемся, что questions - это массив
|
||||||
|
const currentQuestions = Array.isArray(questions) ? questions : []
|
||||||
|
|
||||||
let updatedQuestions
|
let updatedQuestions
|
||||||
if (editingQuestion) {
|
if (editingQuestion) {
|
||||||
updatedQuestions = questions.map(q =>
|
updatedQuestions = currentQuestions.map(q =>
|
||||||
q.id === editingQuestion.id ? questionData : q
|
q.id === editingQuestion.id ? questionData : q
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
updatedQuestions = [...questions, questionData]
|
updatedQuestions = [...currentQuestions, questionData]
|
||||||
}
|
}
|
||||||
|
|
||||||
onUpdateQuestions(updatedQuestions)
|
onUpdateQuestions(updatedQuestions)
|
||||||
|
|
@ -577,27 +601,35 @@ const GameManagementModal = ({
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleImportSelected = () => {
|
const handleImportSelected = () => {
|
||||||
|
if (!room?.id || !room?.code || !user?.id || !selectedPack) {
|
||||||
|
setJsonError('Ошибка: недостаточно данных для импорта')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const indices = Array.from(selectedQuestionIndices)
|
const indices = Array.from(selectedQuestionIndices)
|
||||||
const questionsToImport = indices.map(idx => packQuestions[idx]).filter(Boolean)
|
if (indices.length === 0) {
|
||||||
|
setJsonError('Выберите хотя бы один вопрос для импорта')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const copiedQuestions = questionsToImport.map((q) => ({
|
try {
|
||||||
id: crypto.randomUUID(),
|
socketService.importQuestions(
|
||||||
text: q.text || '',
|
room.id,
|
||||||
answers: (q.answers || []).map(a => ({
|
room.code,
|
||||||
id: a.id || crypto.randomUUID(),
|
user.id,
|
||||||
text: a.text,
|
selectedPack,
|
||||||
points: a.points
|
indices
|
||||||
})),
|
)
|
||||||
}))
|
|
||||||
|
|
||||||
const updatedQuestions = [...questions, ...copiedQuestions]
|
setSelectedQuestionIndices(new Set())
|
||||||
onUpdateQuestions(updatedQuestions)
|
setSearchQuery('')
|
||||||
|
setShowPackImportModal(false)
|
||||||
setSelectedQuestionIndices(new Set())
|
setJsonError('')
|
||||||
setSearchQuery('')
|
// Уведомление будет показано через WebSocket событие gameStateUpdated
|
||||||
setShowPackImportModal(false)
|
} catch (error) {
|
||||||
setJsonError('')
|
console.error('Error importing questions:', error)
|
||||||
alert(`Импортировано ${copiedQuestions.length} вопросов`)
|
setJsonError('Ошибка при импорте вопросов: ' + (error.message || 'Неизвестная ошибка'))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,9 @@ const GamePage = () => {
|
||||||
// Храним participantId текущего пользователя для проверки удаления
|
// Храним participantId текущего пользователя для проверки удаления
|
||||||
const currentUserParticipantIdRef = useRef(null);
|
const currentUserParticipantIdRef = useRef(null);
|
||||||
// Храним предыдущий themeId комнаты для отслеживания изменений
|
// Храним предыдущий themeId комнаты для отслеживания изменений
|
||||||
const previousThemeIdRef = useRef(null);
|
// Используем undefined для обозначения первой загрузки (не установлено),
|
||||||
|
// null означает "тема не установлена в комнате"
|
||||||
|
const previousThemeIdRef = useRef(undefined);
|
||||||
|
|
||||||
// ЕДИНСТВЕННЫЙ обработчик состояния игры
|
// ЕДИНСТВЕННЫЙ обработчик состояния игры
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -65,13 +67,14 @@ const GamePage = () => {
|
||||||
currentUserParticipantIdRef.current = currentUserParticipant?.id || null;
|
currentUserParticipantIdRef.current = currentUserParticipant?.id || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Применяем тему комнаты, если она изменилась
|
// Применяем тему комнаты при первой загрузке или если она изменилась
|
||||||
const currentThemeId = state.themeId || null;
|
const currentThemeId = state.themeId || null;
|
||||||
if (currentThemeId !== previousThemeIdRef.current) {
|
const isFirstLoad = previousThemeIdRef.current === undefined;
|
||||||
|
|
||||||
|
if (isFirstLoad || currentThemeId !== previousThemeIdRef.current) {
|
||||||
previousThemeIdRef.current = currentThemeId;
|
previousThemeIdRef.current = currentThemeId;
|
||||||
if (currentThemeId) {
|
// Применяем тему (даже если null - вернемся к дефолтной теме)
|
||||||
changeTheme(currentThemeId);
|
changeTheme(currentThemeId);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -268,14 +271,17 @@ const GamePage = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleUpdateRoomQuestions = async (newQuestions) => {
|
const handleUpdateRoomQuestions = async (newQuestions) => {
|
||||||
if (!gameState.roomId) return;
|
if (!gameState.roomId || !user?.id) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// Убеждаемся, что newQuestions - это массив
|
||||||
|
const questionsArray = Array.isArray(newQuestions) ? newQuestions : [];
|
||||||
|
|
||||||
socketService.emit('updateRoomPack', {
|
socketService.emit('updateRoomPack', {
|
||||||
roomId: gameState.roomId,
|
roomId: gameState.roomId,
|
||||||
roomCode: gameState.roomCode,
|
roomCode: gameState.roomCode,
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
questions: newQuestions
|
questions: questionsArray
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error updating room pack:', error);
|
console.error('Error updating room pack:', error);
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,8 @@ const RoomPage = () => {
|
||||||
const { changeTheme } = useTheme();
|
const { changeTheme } = useTheme();
|
||||||
|
|
||||||
// Храним предыдущий themeId комнаты для отслеживания изменений
|
// Храним предыдущий themeId комнаты для отслеживания изменений
|
||||||
const previousThemeIdRef = useRef(null);
|
// Используем undefined для обозначения первой загрузки (не установлено)
|
||||||
|
const previousThemeIdRef = useRef(undefined);
|
||||||
|
|
||||||
// Callback для автоматической навигации при старте игры
|
// Callback для автоматической навигации при старте игры
|
||||||
const handleGameStartedEvent = useCallback(() => {
|
const handleGameStartedEvent = useCallback(() => {
|
||||||
|
|
@ -120,44 +121,39 @@ const RoomPage = () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Показываем модальное окно выбора роли, если allowSpectators === true и пользователь авторизован
|
// Единая логика присоединения к комнате
|
||||||
useEffect(() => {
|
|
||||||
if (
|
|
||||||
room &&
|
|
||||||
user &&
|
|
||||||
!joined &&
|
|
||||||
!isRoleSelectionModalOpen &&
|
|
||||||
room.allowSpectators &&
|
|
||||||
!participants.some((p) => p.userId === user.id)
|
|
||||||
) {
|
|
||||||
setIsRoleSelectionModalOpen(true);
|
|
||||||
}
|
|
||||||
}, [room, user, joined, participants, isRoleSelectionModalOpen]);
|
|
||||||
|
|
||||||
// Автоматическое присоединение как PLAYER, если зрители не разрешены
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleJoin = async () => {
|
const handleJoin = async () => {
|
||||||
if (room && user && !joined && !isRoleSelectionModalOpen) {
|
// Пропускаем если нет комнаты, пользователя или уже присоединились
|
||||||
const isParticipant = participants.some((p) => p.userId === user.id);
|
if (!room || !user || joined) return;
|
||||||
if (!isParticipant) {
|
|
||||||
// Если зрители не разрешены, присоединяемся как PLAYER автоматически
|
// Проверяем, не является ли пользователь уже участником
|
||||||
// Присоединение разрешено независимо от статуса игры (WAITING, PLAYING, FINISHED)
|
const isParticipant = participants.some((p) => p.userId === user.id);
|
||||||
if (!room.allowSpectators) {
|
if (isParticipant) {
|
||||||
try {
|
setJoined(true);
|
||||||
setJoinError(null);
|
return;
|
||||||
await joinRoom(room.id, user.id, user.name || 'Гость', 'PLAYER');
|
}
|
||||||
setJoined(true);
|
|
||||||
// Если игра уже началась, перенаправление произойдет после обновления participants
|
// Если зрители разрешены, показываем модальное окно выбора роли
|
||||||
} catch (error) {
|
if (room.allowSpectators) {
|
||||||
console.error('Join error:', error);
|
if (!isRoleSelectionModalOpen) {
|
||||||
const errorMessage = error.response?.data?.message || error.message || 'Ошибка при присоединении к комнате';
|
setIsRoleSelectionModalOpen(true);
|
||||||
setJoinError(errorMessage);
|
|
||||||
alert(errorMessage);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
setJoined(true);
|
|
||||||
}
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Если зрители не разрешены, автоматически присоединяемся как PLAYER
|
||||||
|
// Присоединение разрешено независимо от статуса игры (WAITING, PLAYING, FINISHED)
|
||||||
|
try {
|
||||||
|
setJoinError(null);
|
||||||
|
await joinRoom(room.id, user.id, user.name || 'Гость', 'PLAYER');
|
||||||
|
setJoined(true);
|
||||||
|
// Если игра уже началась, перенаправление произойдет после обновления participants
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Join error:', error);
|
||||||
|
const errorMessage = error.response?.data?.message || error.message || 'Ошибка при присоединении к комнате';
|
||||||
|
setJoinError(errorMessage);
|
||||||
|
alert(errorMessage);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -228,12 +224,13 @@ const RoomPage = () => {
|
||||||
|
|
||||||
const handleGameStateUpdated = (state) => {
|
const handleGameStateUpdated = (state) => {
|
||||||
const currentThemeId = state.themeId || null;
|
const currentThemeId = state.themeId || null;
|
||||||
// Применяем тему если она изменилась или если это первое присоединение (previousThemeIdRef.current === null)
|
const isFirstLoad = previousThemeIdRef.current === undefined;
|
||||||
if (currentThemeId !== previousThemeIdRef.current) {
|
|
||||||
|
// Применяем тему при первой загрузке или если она изменилась
|
||||||
|
if (isFirstLoad || currentThemeId !== previousThemeIdRef.current) {
|
||||||
previousThemeIdRef.current = currentThemeId;
|
previousThemeIdRef.current = currentThemeId;
|
||||||
if (currentThemeId) {
|
// Применяем тему (даже если null - вернемся к дефолтной теме)
|
||||||
changeTheme(currentThemeId);
|
changeTheme(currentThemeId);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -248,9 +245,12 @@ const RoomPage = () => {
|
||||||
if (!roomCode || !room) return;
|
if (!roomCode || !room) return;
|
||||||
|
|
||||||
const currentThemeId = room.themeId || null;
|
const currentThemeId = room.themeId || null;
|
||||||
// Применяем тему если она существует и еще не применена (первое присоединение) или изменилась
|
const isFirstLoad = previousThemeIdRef.current === undefined;
|
||||||
if (currentThemeId && currentThemeId !== previousThemeIdRef.current) {
|
|
||||||
|
// Применяем тему при первой загрузке или если она изменилась
|
||||||
|
if (isFirstLoad || currentThemeId !== previousThemeIdRef.current) {
|
||||||
previousThemeIdRef.current = currentThemeId;
|
previousThemeIdRef.current = currentThemeId;
|
||||||
|
// Применяем тему (даже если null - вернемся к дефолтной теме)
|
||||||
changeTheme(currentThemeId);
|
changeTheme(currentThemeId);
|
||||||
}
|
}
|
||||||
}, [roomCode, room?.themeId, changeTheme]);
|
}, [roomCode, room?.themeId, changeTheme]);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue