mirror of
https://github.com/randyjc/Rahoot.git
synced 2026-03-13 20:15:35 +01:00
adding multiple choice (not fully tested yet)
This commit is contained in:
@@ -4,17 +4,20 @@ import { ButtonHTMLAttributes, ElementType, PropsWithChildren } from "react"
|
||||
type Props = PropsWithChildren &
|
||||
ButtonHTMLAttributes<HTMLButtonElement> & {
|
||||
icon: ElementType
|
||||
selected?: boolean
|
||||
}
|
||||
|
||||
const AnswerButton = ({
|
||||
className,
|
||||
icon: Icon,
|
||||
children,
|
||||
selected = false,
|
||||
...otherProps
|
||||
}: Props) => (
|
||||
<button
|
||||
className={clsx(
|
||||
"shadow-inset flex items-center gap-3 rounded px-4 py-6 text-left",
|
||||
"shadow-inset flex items-center gap-3 rounded px-4 py-6 text-left transition-all",
|
||||
selected && "ring-4 ring-white/80 shadow-lg",
|
||||
className,
|
||||
)}
|
||||
{...otherProps}
|
||||
|
||||
@@ -33,7 +33,7 @@ type MediaLibraryItem = {
|
||||
const blankQuestion = (): EditableQuestion => ({
|
||||
question: "",
|
||||
answers: ["", ""],
|
||||
solution: 0,
|
||||
solution: [0],
|
||||
cooldown: 5,
|
||||
time: 20,
|
||||
})
|
||||
@@ -186,10 +186,14 @@ const QuizEditor = ({ quizzList, onBack, onListUpdate }: Props) => {
|
||||
return
|
||||
}
|
||||
currentAnswers.splice(aIndex, 1)
|
||||
let nextSolution = nextQuestions[qIndex].solution
|
||||
if (nextSolution >= currentAnswers.length) {
|
||||
nextSolution = currentAnswers.length - 1
|
||||
}
|
||||
const currentSolution = Array.isArray(nextQuestions[qIndex].solution)
|
||||
? nextQuestions[qIndex].solution
|
||||
: [nextQuestions[qIndex].solution]
|
||||
const adjusted = currentSolution
|
||||
.filter((idx) => idx !== aIndex)
|
||||
.map((idx) => (idx > aIndex ? idx - 1 : idx))
|
||||
const nextSolution =
|
||||
adjusted.length > 0 ? adjusted : [Math.max(0, currentAnswers.length - 1)]
|
||||
nextQuestions[qIndex] = {
|
||||
...nextQuestions[qIndex],
|
||||
answers: currentAnswers,
|
||||
@@ -735,30 +739,47 @@ const QuizEditor = ({ quizzList, onBack, onListUpdate }: Props) => {
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-2 md:grid-cols-2">
|
||||
{question.answers.map((answer, aIndex) => (
|
||||
<div
|
||||
key={aIndex}
|
||||
className={clsx(
|
||||
"flex items-center gap-2 rounded-md border p-2",
|
||||
question.solution === aIndex
|
||||
? "border-green-500"
|
||||
: "border-gray-200",
|
||||
)}
|
||||
>
|
||||
<input
|
||||
type="radio"
|
||||
name={`solution-${qIndex}`}
|
||||
checked={question.solution === aIndex}
|
||||
onChange={() =>
|
||||
updateQuestion(qIndex, { solution: aIndex })
|
||||
<div className="grid gap-2 md:grid-cols-2">
|
||||
{question.answers.map((answer, aIndex) => (
|
||||
<div
|
||||
key={aIndex}
|
||||
className={clsx(
|
||||
"flex items-center gap-2 rounded-md border p-2",
|
||||
(Array.isArray(question.solution)
|
||||
? question.solution.includes(aIndex)
|
||||
: question.solution === aIndex)
|
||||
? "border-green-500"
|
||||
: "border-gray-200",
|
||||
)}
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
name={`solution-${qIndex}-${aIndex}`}
|
||||
checked={
|
||||
Array.isArray(question.solution)
|
||||
? question.solution.includes(aIndex)
|
||||
: question.solution === aIndex
|
||||
}
|
||||
onChange={(e) => {
|
||||
const current = Array.isArray(question.solution)
|
||||
? question.solution
|
||||
: [question.solution]
|
||||
let next = current
|
||||
if (e.target.checked) {
|
||||
next = Array.from(new Set([...current, aIndex])).sort(
|
||||
(a, b) => a - b,
|
||||
)
|
||||
} else {
|
||||
next = current.filter((idx) => idx !== aIndex)
|
||||
}
|
||||
/>
|
||||
<Input
|
||||
className="flex-1"
|
||||
value={answer}
|
||||
onChange={(e) =>
|
||||
updateAnswer(qIndex, aIndex, e.target.value)
|
||||
updateQuestion(qIndex, { solution: next })
|
||||
}}
|
||||
/>
|
||||
<Input
|
||||
className="flex-1"
|
||||
value={answer}
|
||||
onChange={(e) =>
|
||||
updateAnswer(qIndex, aIndex, e.target.value)
|
||||
}
|
||||
placeholder={`Answer ${aIndex + 1}`}
|
||||
/>
|
||||
|
||||
@@ -21,7 +21,15 @@ type Props = {
|
||||
}
|
||||
|
||||
const Answers = ({
|
||||
data: { question, answers, image, media, time, totalPlayer },
|
||||
data: {
|
||||
question,
|
||||
answers,
|
||||
image,
|
||||
media,
|
||||
time,
|
||||
totalPlayer,
|
||||
allowsMultiple,
|
||||
},
|
||||
}: Props) => {
|
||||
const { gameId }: { gameId?: string } = useParams()
|
||||
const { socket } = useSocket()
|
||||
@@ -31,6 +39,8 @@ const Answers = ({
|
||||
const [paused, setPaused] = useState(false)
|
||||
const [totalAnswer, setTotalAnswer] = useState(0)
|
||||
const [isMediaPlaying, setIsMediaPlaying] = useState(false)
|
||||
const [selectedAnswers, setSelectedAnswers] = useState<number[]>([])
|
||||
const [submitted, setSubmitted] = useState(false)
|
||||
|
||||
const [sfxPop] = useSound(SFX_ANSWERS_SOUND, {
|
||||
volume: 0.1,
|
||||
@@ -45,20 +55,39 @@ const Answers = ({
|
||||
},
|
||||
)
|
||||
|
||||
const handleAnswer = (answerKey: number) => () => {
|
||||
if (!player) {
|
||||
const submitAnswers = (keys: number[]) => {
|
||||
if (!player || submitted || keys.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
socket?.emit("player:selectedAnswer", {
|
||||
gameId,
|
||||
data: {
|
||||
answerKey,
|
||||
answerKeys: keys,
|
||||
},
|
||||
})
|
||||
setSubmitted(true)
|
||||
sfxPop()
|
||||
}
|
||||
|
||||
const handleAnswer = (answerKey: number) => () => {
|
||||
if (!player) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!allowsMultiple) {
|
||||
setSelectedAnswers([answerKey])
|
||||
submitAnswers([answerKey])
|
||||
return
|
||||
}
|
||||
|
||||
setSelectedAnswers((prev) =>
|
||||
prev.includes(answerKey)
|
||||
? prev.filter((key) => key !== answerKey)
|
||||
: [...prev, answerKey],
|
||||
)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
playMusic()
|
||||
|
||||
@@ -88,12 +117,25 @@ const Answers = ({
|
||||
sfxPop()
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
setCooldown(time)
|
||||
setPaused(false)
|
||||
setSelectedAnswers([])
|
||||
setSubmitted(false)
|
||||
setTotalAnswer(0)
|
||||
}, [question, time])
|
||||
|
||||
return (
|
||||
<div className="flex h-full flex-1 flex-col justify-between">
|
||||
<div className="mx-auto inline-flex h-full w-full max-w-7xl flex-1 flex-col items-center justify-center gap-5">
|
||||
<h2 className="text-center text-2xl font-bold text-white drop-shadow-lg md:text-4xl lg:text-5xl">
|
||||
{question}
|
||||
</h2>
|
||||
{allowsMultiple && (
|
||||
<p className="rounded-full bg-black/40 px-4 py-2 text-sm font-semibold text-white">
|
||||
Select all correct answers, then submit.
|
||||
</p>
|
||||
)}
|
||||
|
||||
<QuestionMedia
|
||||
media={media || (image ? { type: "image", url: image } : undefined)}
|
||||
@@ -128,11 +170,29 @@ const Answers = ({
|
||||
className={clsx(ANSWERS_COLORS[key])}
|
||||
icon={ANSWERS_ICONS[key]}
|
||||
onClick={handleAnswer(key)}
|
||||
disabled={submitted}
|
||||
selected={selectedAnswers.includes(key)}
|
||||
>
|
||||
{answer}
|
||||
</AnswerButton>
|
||||
))}
|
||||
</div>
|
||||
{allowsMultiple && (
|
||||
<div className="mx-auto flex w-full max-w-7xl justify-end px-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => submitAnswers(selectedAnswers)}
|
||||
disabled={submitted || selectedAnswers.length === 0}
|
||||
className={clsx(
|
||||
"rounded bg-white/80 px-4 py-2 text-sm font-semibold text-slate-900 shadow-md transition hover:bg-white",
|
||||
(submitted || selectedAnswers.length === 0) &&
|
||||
"cursor-not-allowed opacity-60",
|
||||
)}
|
||||
>
|
||||
{submitted ? "Submitted" : "Submit answers"}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -67,6 +67,10 @@ const Responses = ({
|
||||
stopMusic()
|
||||
}, [playMusic, stopMusic])
|
||||
|
||||
const correctSet = new Set(
|
||||
Array.isArray(correct) ? correct : typeof correct === "number" ? [correct] : [],
|
||||
)
|
||||
|
||||
return (
|
||||
<div className="flex h-full flex-1 flex-col justify-between">
|
||||
<div className="mx-auto inline-flex h-full w-full max-w-7xl flex-1 flex-col items-center justify-center gap-5">
|
||||
@@ -107,7 +111,7 @@ const Responses = ({
|
||||
<AnswerButton
|
||||
key={key}
|
||||
className={clsx(ANSWERS_COLORS[key], {
|
||||
"opacity-65": responses && correct !== key,
|
||||
"opacity-65": responses && !correctSet.has(key),
|
||||
})}
|
||||
icon={ANSWERS_ICONS[key]}
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user