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