|
|
import React, { useState, useEffect } from 'react'; |
|
|
import TopicSelector from './components/TopicSelector'; |
|
|
import PuzzleGrid from './components/PuzzleGrid'; |
|
|
import ClueList from './components/ClueList'; |
|
|
import DebugTab from './components/DebugTab'; |
|
|
import AdvancedSettings from './components/AdvancedSettings'; |
|
|
import LoadingSpinner from './components/LoadingSpinner'; |
|
|
import useCrossword from './hooks/useCrossword'; |
|
|
import './styles/puzzle.css'; |
|
|
|
|
|
function App() { |
|
|
const [selectedTopics, setSelectedTopics] = useState([]); |
|
|
const [customTopics, setCustomTopics] = useState([]); |
|
|
const [difficulty, setDifficulty] = useState('medium'); |
|
|
const [showSolution, setShowSolution] = useState(false); |
|
|
const [customSentence, setCustomSentence] = useState(''); |
|
|
const [multiTheme, setMultiTheme] = useState(true); |
|
|
const [activeTab, setActiveTab] = useState('puzzle'); |
|
|
|
|
|
|
|
|
const [similarityTemperature, setSimilarityTemperature] = useState(0.2); |
|
|
const [difficultyWeight, setDifficultyWeight] = useState(0.5); |
|
|
|
|
|
const { |
|
|
puzzle, |
|
|
loading, |
|
|
error, |
|
|
topics, |
|
|
fetchTopics, |
|
|
generatePuzzle, |
|
|
resetPuzzle |
|
|
} = useCrossword(); |
|
|
|
|
|
useEffect(() => { |
|
|
fetchTopics(); |
|
|
}, [fetchTopics]); |
|
|
|
|
|
const handleGeneratePuzzle = async () => { |
|
|
|
|
|
const allTopics = [...selectedTopics, ...customTopics.filter(t => t.trim())]; |
|
|
|
|
|
if (allTopics.length === 0 && !customSentence.trim()) { |
|
|
alert('Please select at least one topic or provide a custom sentence'); |
|
|
return; |
|
|
} |
|
|
|
|
|
setShowSolution(false); |
|
|
await generatePuzzle(allTopics, difficulty, false, customSentence, multiTheme, { |
|
|
similarityTemperature, |
|
|
difficultyWeight |
|
|
}); |
|
|
}; |
|
|
|
|
|
const handleTopicsChange = (topics) => { |
|
|
setSelectedTopics(topics); |
|
|
}; |
|
|
|
|
|
const handleSentenceChange = (sentence) => { |
|
|
setCustomSentence(sentence); |
|
|
}; |
|
|
|
|
|
const handleMultiThemeChange = (enabled) => { |
|
|
setMultiTheme(enabled); |
|
|
}; |
|
|
|
|
|
const handleCustomTopicsChange = (topics) => { |
|
|
setCustomTopics(topics); |
|
|
}; |
|
|
|
|
|
|
|
|
const handleReset = () => { |
|
|
resetPuzzle(); |
|
|
setSelectedTopics([]); |
|
|
setCustomTopics([]); |
|
|
setShowSolution(false); |
|
|
setDifficulty('medium'); |
|
|
setCustomSentence(''); |
|
|
setMultiTheme(true); |
|
|
setActiveTab('puzzle'); |
|
|
setSimilarityTemperature(0.2); |
|
|
setDifficultyWeight(0.5); |
|
|
}; |
|
|
|
|
|
const handleRevealSolution = () => { |
|
|
setShowSolution(true); |
|
|
}; |
|
|
|
|
|
return ( |
|
|
<div className="crossword-app"> |
|
|
<header className="app-header"> |
|
|
<h1 className="app-title">Crossword Puzzle Generator</h1> |
|
|
<p>Select topics and generate your custom crossword puzzle!</p> |
|
|
</header> |
|
|
|
|
|
<TopicSelector |
|
|
onTopicsChange={handleTopicsChange} |
|
|
availableTopics={topics} |
|
|
selectedTopics={selectedTopics} |
|
|
customTopics={customTopics} |
|
|
onCustomTopicsChange={handleCustomTopicsChange} |
|
|
customSentence={customSentence} |
|
|
onSentenceChange={handleSentenceChange} |
|
|
multiTheme={multiTheme} |
|
|
onMultiThemeChange={handleMultiThemeChange} |
|
|
/> |
|
|
|
|
|
<AdvancedSettings |
|
|
similarityTemperature={similarityTemperature} |
|
|
onTemperatureChange={setSimilarityTemperature} |
|
|
difficultyWeight={difficultyWeight} |
|
|
onWeightChange={setDifficultyWeight} |
|
|
/> |
|
|
|
|
|
<div className="puzzle-controls"> |
|
|
<select |
|
|
value={difficulty} |
|
|
onChange={(e) => setDifficulty(e.target.value)} |
|
|
className="control-btn" |
|
|
> |
|
|
<option value="easy">Easy</option> |
|
|
<option value="medium">Medium</option> |
|
|
<option value="hard">Hard</option> |
|
|
</select> |
|
|
|
|
|
<button |
|
|
onClick={handleGeneratePuzzle} |
|
|
disabled={loading || (selectedTopics.length === 0 && customTopics.filter(t => t.trim()).length === 0 && !customSentence.trim())} |
|
|
className="control-btn generate-btn" |
|
|
> |
|
|
{loading ? 'Generating...' : 'Generate Puzzle'} |
|
|
</button> |
|
|
|
|
|
<button |
|
|
onClick={handleReset} |
|
|
className="control-btn reset-btn" |
|
|
> |
|
|
Reset |
|
|
</button> |
|
|
|
|
|
{puzzle && !showSolution && ( |
|
|
<button |
|
|
onClick={handleRevealSolution} |
|
|
className="control-btn reveal-btn" |
|
|
> |
|
|
Reveal Solution |
|
|
</button> |
|
|
)} |
|
|
</div> |
|
|
|
|
|
{error && ( |
|
|
<div className="error-message"> |
|
|
Error: {error} |
|
|
</div> |
|
|
)} |
|
|
|
|
|
{loading && <LoadingSpinner />} |
|
|
|
|
|
{puzzle && !loading && ( |
|
|
<> |
|
|
{/* Tab Navigation */} |
|
|
<div className="tab-nav"> |
|
|
<button |
|
|
className={`tab-btn ${activeTab === 'puzzle' ? 'active' : ''}`} |
|
|
onClick={() => setActiveTab('puzzle')} |
|
|
> |
|
|
Puzzle |
|
|
</button> |
|
|
{puzzle.debug && ( |
|
|
<button |
|
|
className={`tab-btn ${activeTab === 'debug' ? 'active' : ''}`} |
|
|
onClick={() => setActiveTab('debug')} |
|
|
> |
|
|
Debug |
|
|
</button> |
|
|
)} |
|
|
</div> |
|
|
|
|
|
{/* Tab Content */} |
|
|
{activeTab === 'puzzle' && ( |
|
|
<div className="puzzle-layout"> |
|
|
<PuzzleGrid |
|
|
grid={puzzle.grid} |
|
|
clues={puzzle.clues} |
|
|
showSolution={showSolution} |
|
|
/> |
|
|
<ClueList clues={puzzle.clues} /> |
|
|
</div> |
|
|
)} |
|
|
|
|
|
{activeTab === 'debug' && puzzle.debug && ( |
|
|
<DebugTab debugData={puzzle.debug} /> |
|
|
)} |
|
|
</> |
|
|
)} |
|
|
|
|
|
{!puzzle && !loading && !error && ( |
|
|
<div style={{ textAlign: 'center', padding: '40px', color: '#7f8c8d' }}> |
|
|
Select topics and click "Generate Puzzle" to start! |
|
|
</div> |
|
|
)} |
|
|
</div> |
|
|
); |
|
|
} |
|
|
|
|
|
export default App; |