Files
desktop/app/components/InputChainHidden/InputChainHidden.tsx
rosetta 83f38dc63f 'init'
2026-01-30 05:01:05 +02:00

177 lines
7.0 KiB
TypeScript

import { Button, Group, Stack, Text, Box, Transition } from "@mantine/core";
import classes from './InputChain.module.css'
import { useEffect, useState } from "react";
interface InputChainProps {
text: string;
hidden: number;
onPassed: () => void;
onNotPassed?: () => void;
size?: string;
w?: number;
}
export function InputChainHidden(props : InputChainProps) {
const text = props.text;
if(text.trim() == ""){
return (<></>);
}
const words = text.split(" ");
const [hiddenIndices, setHiddenIndices] = useState<number[]>([]);
const [selectedWords, setSelectedWords] = useState<(string | null)[]>([]);
const [availableWords, setAvailableWords] = useState<string[]>([]);
const [wrongIndices, setWrongIndices] = useState<number[]>([]);
const [correctIndices, setCorrectIndices] = useState<number[]>([]);
const [mounted, setMounted] = useState(false);
useEffect(() => {
let hidden : number[] = [];
while (hidden.length < props.hidden) {
let num = Math.floor(Math.random() * words.length);
if(hidden.indexOf(num) == -1){
hidden.push(num);
}
}
hidden.sort((a, b) => a - b);
setHiddenIndices(hidden);
const hiddenWords = hidden.map(idx => words[idx]);
const shuffled = [...hiddenWords].sort(() => Math.random() - 0.5);
setAvailableWords(shuffled);
setSelectedWords(new Array(hidden.length).fill(null));
setTimeout(() => setMounted(true), 100);
}, []);
const handleWordClick = (word: string) => {
const firstEmptyIndex = selectedWords.findIndex(w => w === null);
if (firstEmptyIndex !== -1) {
const newSelected = [...selectedWords];
newSelected[firstEmptyIndex] = word;
setSelectedWords(newSelected);
setAvailableWords(availableWords.filter(w => w !== word));
checkIfPassed(newSelected);
validateWords(newSelected);
}
};
const handleRemoveWord = (index: number) => {
const word = selectedWords[index];
if (word) {
const newSelected = [...selectedWords];
newSelected[index] = null;
setSelectedWords(newSelected);
setAvailableWords([...availableWords, word]);
if(props.onNotPassed){
props.onNotPassed();
}
validateWords(newSelected);
}
};
const validateWords = (selected: (string | null)[]) => {
const wrong: number[] = [];
const correct: number[] = [];
selected.forEach((word, idx) => {
if (word !== null) {
if (word === words[hiddenIndices[idx]]) {
correct.push(idx);
} else {
wrong.push(idx);
}
}
});
setWrongIndices(wrong);
setCorrectIndices(correct);
};
const checkIfPassed = (selected: (string | null)[]) => {
const allFilled = selected.every(w => w !== null);
if (allFilled) {
const isCorrect = selected.every((word, idx) =>
word === words[hiddenIndices[idx]]
);
if (isCorrect) {
props.onPassed();
} else if(props.onNotPassed) {
props.onNotPassed();
}
} else {
if(props.onNotPassed){
props.onNotPassed();
}
}
};
return (
<Stack gap="sm">
{/* Target area - where words should be placed */}
<Transition mounted={mounted} transition="slide-up" duration={400} timingFunction="ease">
{(styles) => (
<Box className={classes.targetArea} style={styles}>
<Text size="sm" mb="xs" c="dimmed">
Click the words in the correct order:
</Text>
<Group gap="xs">
{words.map((word, i) => {
const hiddenIdx = hiddenIndices.indexOf(i);
const isHidden = hiddenIdx !== -1;
if (!isHidden) {
return (
<Box key={i} className={classes.staticWord}>
<Text size="sm" c="dimmed">{i + 1}.</Text>
<Text size="sm">{word}</Text>
</Box>
);
}
const isWrong = wrongIndices.includes(hiddenIdx);
const isCorrect = correctIndices.includes(hiddenIdx);
return (
<Button
key={i}
variant={selectedWords[hiddenIdx] ? "filled" : "default"}
size="sm"
className={classes.targetSlot}
color={isWrong ? "red" : isCorrect ? "green" : undefined}
onClick={() => selectedWords[hiddenIdx] && handleRemoveWord(hiddenIdx)}
>
<Text size="sm" c={selectedWords[hiddenIdx] ? 'white' : 'dimmed'} mr={4}>{i + 1}.</Text>
{selectedWords[hiddenIdx] || "_____"}
</Button>
);
})}
</Group>
</Box>
)}
</Transition>
{/* Available words area */}
<Transition mounted={mounted} transition="slide-up" duration={500} timingFunction="ease">
{(transitionStyles) => (
<Box mih={100} className={classes.availableArea} style={transitionStyles}>
<Text size="sm" mb="xs" c="dimmed">
Available words:
</Text>
<Group gap="xs">
{availableWords.map((word, idx) => (
<Button
key={`${word}-${idx}`}
variant="light"
size="sm"
onClick={() => handleWordClick(word)}
className={classes.availableWord}
>
{word}
</Button>
))}
</Group>
</Box>
)}
</Transition>
</Stack>
);
}