vimalk78 commited on
Commit
cf76e1a
·
1 Parent(s): 474a1cc

feat: Add custom topics input and expand predefined topics list

Browse files
crossword-app/backend-py/src/routes/api.py CHANGED
@@ -81,7 +81,19 @@ async def get_topics():
81
  {"id": "nature", "name": "Nature"},
82
  {"id": "transportation", "name": "Transportation"},
83
  {"id": "art", "name": "Art"},
84
- {"id": "medicine", "name": "Medicine"}
 
 
 
 
 
 
 
 
 
 
 
 
85
  ]
86
  return topics
87
 
 
81
  {"id": "nature", "name": "Nature"},
82
  {"id": "transportation", "name": "Transportation"},
83
  {"id": "art", "name": "Art"},
84
+ {"id": "medicine", "name": "Medicine"},
85
+ {"id": "philosophy", "name": "Philosophy"},
86
+ {"id": "music", "name": "Music"},
87
+ {"id": "books", "name": "Books"},
88
+ {"id": "movies", "name": "Movies"},
89
+ {"id": "appliances", "name": "Appliances"},
90
+ {"id": "culture", "name": "Culture"},
91
+ {"id": "cuisine", "name": "Cuisine"},
92
+ {"id": "languages", "name": "Languages"},
93
+ {"id": "relations", "name": "Relations"},
94
+ {"id": "space", "name": "Space"},
95
+ {"id": "universe", "name": "Universe"},
96
+ {"id": "meditation", "name": "Meditation"}
97
  ]
98
  return topics
99
 
crossword-app/frontend/src/App.jsx CHANGED
@@ -9,6 +9,7 @@ import './styles/puzzle.css';
9
 
10
  function App() {
11
  const [selectedTopics, setSelectedTopics] = useState([]);
 
12
  const [difficulty, setDifficulty] = useState('medium');
13
  const [showSolution, setShowSolution] = useState(false);
14
  const [customSentence, setCustomSentence] = useState('');
@@ -30,13 +31,16 @@ function App() {
30
  }, [fetchTopics]);
31
 
32
  const handleGeneratePuzzle = async () => {
33
- if (selectedTopics.length === 0 && !customSentence.trim()) {
 
 
 
34
  alert('Please select at least one topic or provide a custom sentence');
35
  return;
36
  }
37
 
38
  setShowSolution(false);
39
- await generatePuzzle(selectedTopics, difficulty, false, customSentence, multiTheme);
40
  };
41
 
42
  const handleTopicsChange = (topics) => {
@@ -51,10 +55,15 @@ function App() {
51
  setMultiTheme(enabled);
52
  };
53
 
 
 
 
 
54
 
55
  const handleReset = () => {
56
  resetPuzzle();
57
  setSelectedTopics([]);
 
58
  setShowSolution(false);
59
  setDifficulty('medium');
60
  setCustomSentence('');
@@ -77,6 +86,8 @@ function App() {
77
  onTopicsChange={handleTopicsChange}
78
  availableTopics={topics}
79
  selectedTopics={selectedTopics}
 
 
80
  customSentence={customSentence}
81
  onSentenceChange={handleSentenceChange}
82
  multiTheme={multiTheme}
@@ -96,7 +107,7 @@ function App() {
96
 
97
  <button
98
  onClick={handleGeneratePuzzle}
99
- disabled={loading || (selectedTopics.length === 0 && !customSentence.trim())}
100
  className="control-btn generate-btn"
101
  >
102
  {loading ? 'Generating...' : 'Generate Puzzle'}
 
9
 
10
  function App() {
11
  const [selectedTopics, setSelectedTopics] = useState([]);
12
+ const [customTopics, setCustomTopics] = useState([]);
13
  const [difficulty, setDifficulty] = useState('medium');
14
  const [showSolution, setShowSolution] = useState(false);
15
  const [customSentence, setCustomSentence] = useState('');
 
31
  }, [fetchTopics]);
32
 
33
  const handleGeneratePuzzle = async () => {
34
+ // Combine predefined and custom topics
35
+ const allTopics = [...selectedTopics, ...customTopics.filter(t => t.trim())];
36
+
37
+ if (allTopics.length === 0 && !customSentence.trim()) {
38
  alert('Please select at least one topic or provide a custom sentence');
39
  return;
40
  }
41
 
42
  setShowSolution(false);
43
+ await generatePuzzle(allTopics, difficulty, false, customSentence, multiTheme);
44
  };
45
 
46
  const handleTopicsChange = (topics) => {
 
55
  setMultiTheme(enabled);
56
  };
57
 
58
+ const handleCustomTopicsChange = (topics) => {
59
+ setCustomTopics(topics);
60
+ };
61
+
62
 
63
  const handleReset = () => {
64
  resetPuzzle();
65
  setSelectedTopics([]);
66
+ setCustomTopics([]);
67
  setShowSolution(false);
68
  setDifficulty('medium');
69
  setCustomSentence('');
 
86
  onTopicsChange={handleTopicsChange}
87
  availableTopics={topics}
88
  selectedTopics={selectedTopics}
89
+ customTopics={customTopics}
90
+ onCustomTopicsChange={handleCustomTopicsChange}
91
  customSentence={customSentence}
92
  onSentenceChange={handleSentenceChange}
93
  multiTheme={multiTheme}
 
107
 
108
  <button
109
  onClick={handleGeneratePuzzle}
110
+ disabled={loading || (selectedTopics.length === 0 && customTopics.filter(t => t.trim()).length === 0 && !customSentence.trim())}
111
  className="control-btn generate-btn"
112
  >
113
  {loading ? 'Generating...' : 'Generate Puzzle'}
crossword-app/frontend/src/components/TopicSelector.jsx CHANGED
@@ -4,6 +4,8 @@ const TopicSelector = ({
4
  onTopicsChange,
5
  availableTopics = [],
6
  selectedTopics = [],
 
 
7
  customSentence = '',
8
  onSentenceChange,
9
  multiTheme = true,
@@ -17,6 +19,28 @@ const TopicSelector = ({
17
  onTopicsChange(newSelectedTopics);
18
  };
19
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
  return (
21
  <div className="topic-selector">
22
  <h3>Select Topics</h3>
@@ -32,6 +56,41 @@ const TopicSelector = ({
32
  ))}
33
  </div>
34
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
35
  <div className="sentence-input-container">
36
  <label htmlFor="custom-sentence" className="sentence-label">
37
  Custom Sentence (optional)
@@ -41,7 +100,7 @@ const TopicSelector = ({
41
  className="sentence-input"
42
  value={customSentence}
43
  onChange={(e) => onSentenceChange && onSentenceChange(e.target.value)}
44
- placeholder="Enter a sentence to influence word selection..."
45
  rows="3"
46
  maxLength="200"
47
  />
@@ -81,10 +140,10 @@ const TopicSelector = ({
81
  </div>
82
 
83
  <p className="selected-count">
84
- {selectedTopics.length} topic{selectedTopics.length !== 1 ? 's' : ''} selected
85
  </p>
86
  </div>
87
  );
88
  };
89
 
90
- export default TopicSelector;
 
4
  onTopicsChange,
5
  availableTopics = [],
6
  selectedTopics = [],
7
+ customTopics = [],
8
+ onCustomTopicsChange,
9
  customSentence = '',
10
  onSentenceChange,
11
  multiTheme = true,
 
19
  onTopicsChange(newSelectedTopics);
20
  };
21
 
22
+ const handleAddCustomTopic = () => {
23
+ const newCustomTopics = [...customTopics, ''];
24
+ onCustomTopicsChange && onCustomTopicsChange(newCustomTopics);
25
+ };
26
+
27
+ const handleCustomTopicChange = (index, value) => {
28
+ // Limit to 3 words (max 2 spaces)
29
+ const spaceCount = (value.match(/ /g) || []).length;
30
+ if (spaceCount > 2) {
31
+ return; // Don't update if more than 2 spaces
32
+ }
33
+
34
+ const newCustomTopics = [...customTopics];
35
+ newCustomTopics[index] = value;
36
+ onCustomTopicsChange && onCustomTopicsChange(newCustomTopics);
37
+ };
38
+
39
+ const handleRemoveCustomTopic = (index) => {
40
+ const newCustomTopics = customTopics.filter((_, i) => i !== index);
41
+ onCustomTopicsChange && onCustomTopicsChange(newCustomTopics);
42
+ };
43
+
44
  return (
45
  <div className="topic-selector">
46
  <h3>Select Topics</h3>
 
56
  ))}
57
  </div>
58
 
59
+ {/* Custom Topics Section */}
60
+ <div className="custom-topics-container">
61
+ <h3>Custom Topics</h3>
62
+ <p className="custom-topics-description">Add your own topics (up to 3 words per box)</p>
63
+
64
+ {customTopics.map((topic, index) => (
65
+ <div key={index} className="custom-topic-input-container">
66
+ <input
67
+ type="text"
68
+ value={topic}
69
+ onChange={(e) => handleCustomTopicChange(index, e.target.value)}
70
+ placeholder='Enter a custom topic... "climate change", "video game", "ice cream"'
71
+ className="custom-topic-input"
72
+ maxLength="30"
73
+ />
74
+ <button
75
+ type="button"
76
+ onClick={() => handleRemoveCustomTopic(index)}
77
+ className="remove-topic-btn"
78
+ title="Remove topic"
79
+ >
80
+
81
+ </button>
82
+ </div>
83
+ ))}
84
+
85
+ <button
86
+ type="button"
87
+ onClick={handleAddCustomTopic}
88
+ className="add-topic-btn"
89
+ >
90
+ + Add Custom Topic
91
+ </button>
92
+ </div>
93
+
94
  <div className="sentence-input-container">
95
  <label htmlFor="custom-sentence" className="sentence-label">
96
  Custom Sentence (optional)
 
100
  className="sentence-input"
101
  value={customSentence}
102
  onChange={(e) => onSentenceChange && onSentenceChange(e.target.value)}
103
+ placeholder='Enter a sentence to influence word selection... e.g., "india won the test series against england" or "Fresh ingredients make the perfect morning meal"'
104
  rows="3"
105
  maxLength="200"
106
  />
 
140
  </div>
141
 
142
  <p className="selected-count">
143
+ {selectedTopics.length + customTopics.filter(t => t.trim()).length} total topic{(selectedTopics.length + customTopics.filter(t => t.trim()).length) !== 1 ? 's' : ''} selected
144
  </p>
145
  </div>
146
  );
147
  };
148
 
149
+ export default TopicSelector;
crossword-app/frontend/src/styles/puzzle.css CHANGED
@@ -65,6 +65,81 @@
65
  margin: 0;
66
  }
67
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
68
  /* Custom Sentence Input Styles */
69
  .sentence-input-container {
70
  margin-top: 20px;
 
65
  margin: 0;
66
  }
67
 
68
+ /* Custom Topics Styles */
69
+ .custom-topics-container {
70
+ margin-top: 20px;
71
+ padding-top: 15px;
72
+ border-top: 1px solid #e9ecef;
73
+ }
74
+
75
+ .custom-topics-container h3 {
76
+ color: #2c3e50;
77
+ margin-bottom: 8px;
78
+ }
79
+
80
+ .custom-topics-description {
81
+ color: #7f8c8d;
82
+ font-size: 0.9rem;
83
+ margin-bottom: 15px;
84
+ margin-top: 0;
85
+ }
86
+
87
+ .custom-topic-input-container {
88
+ display: flex;
89
+ align-items: center;
90
+ gap: 8px;
91
+ margin-bottom: 10px;
92
+ }
93
+
94
+ .custom-topic-input {
95
+ flex: 1;
96
+ padding: 8px 12px;
97
+ border: 2px solid #ddd;
98
+ border-radius: 4px;
99
+ font-size: 0.9rem;
100
+ transition: border-color 0.3s ease;
101
+ }
102
+
103
+ .custom-topic-input:focus {
104
+ outline: none;
105
+ border-color: #3498db;
106
+ }
107
+
108
+ .remove-topic-btn {
109
+ background: #e74c3c;
110
+ color: white;
111
+ border: none;
112
+ border-radius: 4px;
113
+ width: 30px;
114
+ height: 30px;
115
+ cursor: pointer;
116
+ transition: background-color 0.3s ease;
117
+ display: flex;
118
+ align-items: center;
119
+ justify-content: center;
120
+ font-size: 14px;
121
+ }
122
+
123
+ .remove-topic-btn:hover {
124
+ background: #c0392b;
125
+ }
126
+
127
+ .add-topic-btn {
128
+ background: #27ae60;
129
+ color: white;
130
+ border: none;
131
+ padding: 8px 16px;
132
+ border-radius: 4px;
133
+ cursor: pointer;
134
+ transition: background-color 0.3s ease;
135
+ font-size: 0.9rem;
136
+ margin-top: 5px;
137
+ }
138
+
139
+ .add-topic-btn:hover {
140
+ background: #229954;
141
+ }
142
+
143
  /* Custom Sentence Input Styles */
144
  .sentence-input-container {
145
  margin-top: 20px;