Skip to content

Commit 021a3cb

Browse files
feat: allow infinite drills
resolves #141
1 parent e0fa55c commit 021a3cb

5 files changed

Lines changed: 210 additions & 200 deletions

File tree

src/components/Openings/DrillPerformanceModal.tsx

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1056,16 +1056,20 @@ const DesktopLayout: React.FC<{
10561056
<div className="flex gap-3 border-t border-white/10 p-4">
10571057
<button
10581058
onClick={onContinueAnalyzing}
1059-
className="flex-1 rounded border border-white/10 bg-white/5 py-2 font-medium backdrop-blur-sm transition-colors hover:bg-white/10"
1059+
className={`${
1060+
isLastDrill ? 'w-full' : 'flex-1'
1061+
} rounded border border-white/10 bg-white/5 py-2 font-medium backdrop-blur-sm transition-colors hover:bg-white/10`}
10601062
>
10611063
Analyze
10621064
</button>
1063-
<button
1064-
onClick={onNextDrill}
1065-
className="flex-1 rounded bg-human-4 py-2 font-medium transition-colors hover:bg-human-4/80"
1066-
>
1067-
{isLastDrill ? 'View Summary' : 'Next Drill'}
1068-
</button>
1065+
{!isLastDrill && (
1066+
<button
1067+
onClick={onNextDrill}
1068+
className="flex-1 rounded bg-human-4 py-2 font-medium transition-colors hover:bg-human-4/80"
1069+
>
1070+
Next Drill
1071+
</button>
1072+
)}
10691073
</div>
10701074
</div>
10711075
)

src/components/Openings/OpeningDrillSidebar.tsx

Lines changed: 83 additions & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,8 @@ import { GameNode } from 'src/types'
88
interface Props {
99
currentDrill: OpeningSelection | null
1010
completedDrills: CompletedDrill[]
11-
remainingDrills: OpeningSelection[]
12-
currentDrillIndex: number
13-
totalDrills: number
14-
drillSequence: OpeningSelection[]
15-
onResetCurrentDrill: () => void
16-
onChangeSelections?: () => void
11+
selectionPool: OpeningSelection[]
1712
onLoadCompletedDrill?: (drill: CompletedDrill) => void
18-
onNavigateToDrill?: (drillIndex: number) => void
1913
embedded?: boolean
2014
// New optional props to render moves + controller
2115
openingEndNode?: GameNode | null
@@ -26,14 +20,8 @@ interface Props {
2620
export const OpeningDrillSidebar: React.FC<Props> = ({
2721
currentDrill,
2822
completedDrills,
29-
remainingDrills,
30-
currentDrillIndex,
31-
totalDrills,
32-
drillSequence,
33-
onResetCurrentDrill,
34-
onChangeSelections,
23+
selectionPool,
3524
onLoadCompletedDrill,
36-
onNavigateToDrill,
3725
embedded = false,
3826
openingEndNode,
3927
analysisEnabled,
@@ -113,142 +101,119 @@ export const OpeningDrillSidebar: React.FC<Props> = ({
113101
<span></span>
114102
<span>{currentDrill.targetMoveNumber} moves</span>
115103
</div>
116-
{/* <div className="mt-3 flex gap-2">
117-
<button
118-
onClick={onResetCurrentDrill}
119-
className="w-full rounded bg-background-2 py-1 text-xs transition-colors hover:bg-background-3"
120-
>
121-
Reset Drill
122-
</button>
123-
{onChangeSelections && (
124-
<button
125-
onClick={onChangeSelections}
126-
className="w-full rounded bg-background-2 py-1 text-xs transition-colors hover:bg-background-3"
127-
>
128-
Change
129-
</button>
130-
)}
131-
</div> */}
132104
</div>
133105
) : (
134106
<p className="text-sm text-white/70">No drill selected</p>
135107
)}
136108
</div>
137109

138-
{/* Separator between current drill and list */}
139-
<div className="h-3 w-full border-y border-glassBorder" />
140-
141-
{/* All Drills List */}
142-
<div className="flex h-96 w-full flex-col overflow-hidden">
110+
{/* Completed drills history */}
111+
<div className="flex h-96 w-full flex-col overflow-hidden border-t border-glassBorder">
143112
<div className={listHeaderClass}>
144113
<h3 className="text-sm font-medium text-white/90">
145-
Drill Progress ({currentDrillIndex + 1} of {totalDrills})
114+
Completed Drills ({completedDrills.length})
146115
</h3>
147116
</div>
148117
<div className="red-scrollbar flex h-full flex-col overflow-y-auto">
149-
{drillSequence.length === 0 ? (
150-
<div className="flex h-full items-center justify-center">
151-
<p className="max-w-[12rem] text-center text-xs text-white/70">
152-
No drills selected
118+
{completedDrills.length === 0 ? (
119+
<div className="flex flex-1 items-center justify-center px-4 py-6">
120+
<p className="max-w-[13rem] text-center text-xs text-white/70">
121+
Complete drills to see your history here.
153122
</p>
154123
</div>
155124
) : (
156-
<div className="flex flex-col">
157-
{drillSequence.map((drill, index) => {
158-
// Determine drill status
159-
const isCurrentDrill = index === currentDrillIndex
160-
const isCompleted = completedDrills.some(
161-
(cd) => cd.selection.id === drill.id,
162-
)
163-
const isIncomplete = index < currentDrillIndex && !isCompleted
164-
165-
// Get status info
166-
const getStatusInfo = () => {
167-
if (isCompleted) {
168-
return {
169-
label: 'Completed',
170-
color: 'text-green-400',
171-
bgColor: 'bg-green-900/20 hover:bg-green-900/30',
172-
}
173-
} else if (isCurrentDrill) {
174-
return {
175-
label: 'Current',
176-
color: 'text-blue-400',
177-
bgColor: 'bg-blue-900/20 hover:bg-blue-900/30',
178-
}
179-
} else if (isIncomplete) {
180-
return {
181-
label: 'Incomplete',
182-
color: 'text-yellow-400',
183-
bgColor: 'bg-yellow-900/20 hover:bg-yellow-900/30',
184-
}
185-
} else {
186-
return {
187-
label: 'Pending',
188-
color: 'text-secondary',
189-
bgColor: 'bg-background-1 hover:bg-background-2',
190-
}
191-
}
192-
}
193-
194-
const statusInfo = getStatusInfo()
195-
const drillNumber = index + 1
196-
197-
const shouldHide =
198-
!isCurrentDrill && !isCompleted && index > currentDrillIndex
199-
125+
[...completedDrills]
126+
.map((drill, index) => ({ drill, order: index + 1 }))
127+
.map(({ drill, order }) => {
128+
const selection = drill.selection
200129
return (
201130
<button
202-
key={drill.id}
203-
className={`w-full border-b border-white/5 px-3 py-2 text-left transition-colors ${
204-
isCurrentDrill
205-
? 'border-human-4/30 bg-human-4/20'
206-
: statusInfo.bgColor
207-
} ${
208-
onNavigateToDrill ? 'cursor-pointer' : 'cursor-default'
131+
key={`${selection.id}-history-${order}`}
132+
className={`w-full border-b border-white/5 px-3 py-3 text-left transition-colors ${
133+
onLoadCompletedDrill
134+
? 'cursor-pointer hover:bg-white/5'
135+
: 'cursor-default'
209136
}`}
210-
onClick={() => onNavigateToDrill?.(index)}
211-
disabled={!onNavigateToDrill}
137+
onClick={() => onLoadCompletedDrill?.(drill)}
138+
disabled={!onLoadCompletedDrill}
212139
>
213-
<div className="flex items-start justify-between">
140+
<div className="flex items-start gap-2">
141+
<div className="relative mt-0.5 h-4 w-4 flex-shrink-0">
142+
<Image
143+
src={
144+
selection.playerColor === 'white'
145+
? '/assets/pieces/white king.svg'
146+
: '/assets/pieces/black king.svg'
147+
}
148+
fill={true}
149+
alt={`${selection.playerColor} king`}
150+
/>
151+
</div>
214152
<div className="min-w-0 flex-1">
215-
<div className="flex items-center justify-between">
216-
<p className="text-xs font-medium text-white/90">
217-
Drill #{drillNumber}
153+
<p className="text-xxs text-secondary">
154+
Drill #{order}
155+
</p>
156+
<p className="text-sm font-medium text-white/95">
157+
{selection.opening.name}
158+
</p>
159+
{selection.variation && (
160+
<p className="text-xs text-white/70">
161+
{selection.variation.name}
218162
</p>
219-
<span
220-
className={`text-xs font-medium ${statusInfo.color}`}
221-
>
222-
{statusInfo.label}
223-
</span>
224-
</div>
225-
{!shouldHide && (
226-
<>
227-
<p className="text-xs text-white/90">
228-
{drill.opening.name}
229-
</p>
230-
{drill.variation && (
231-
<p className="text-xs text-white/70">
232-
{drill.variation.name}
233-
</p>
234-
)}
235-
</>
236163
)}
237-
{/* Details removed: icon, vs Maia, and move count */}
238164
</div>
239165
</div>
240166
</button>
241167
)
242-
})}
168+
})
169+
)}
170+
</div>
171+
<div className="flex flex-col gap-1 border-t border-white/10 px-0 py-2">
172+
<h3 className="px-4 text-sm font-medium text-white/90">
173+
Active Opening Pool ({selectionPool.length})
174+
</h3>
175+
{selectionPool.length === 0 ? (
176+
<p className="mt-2 text-xs text-white/70">
177+
Add openings to begin drilling.
178+
</p>
179+
) : (
180+
<div className="flex w-full flex-col">
181+
{selectionPool.map((selection, index) => (
182+
<div
183+
key={`pool-${selection.id}-${index}`}
184+
className="flex w-full items-center gap-2 px-4 py-1"
185+
>
186+
<div className="relative h-4 w-4 flex-shrink-0">
187+
<Image
188+
src={
189+
selection.playerColor === 'white'
190+
? '/assets/pieces/white king.svg'
191+
: '/assets/pieces/black king.svg'
192+
}
193+
fill={true}
194+
alt={`${selection.playerColor} king`}
195+
/>
196+
</div>
197+
<div className="min-w-0 flex-1">
198+
<p className="truncate text-xs font-medium text-white/90">
199+
{selection.opening.name}
200+
</p>
201+
{selection.variation && (
202+
<p className="truncate text-[11px] text-white/60">
203+
{selection.variation.name}
204+
</p>
205+
)}
206+
</div>
207+
</div>
208+
))}
243209
</div>
244210
)}
245211
</div>
246212
</div>
247213

248214
{/* Bottom: Moves + Controller (embedded) */}
249215
{tree?.gameTree && currentDrill && (
250-
<div className="flex w-full flex-1 flex-col overflow-hidden">
251-
<div className="h-3 border-b border-t border-glassBorder" />
216+
<div className="flex w-full flex-1 flex-col overflow-hidden border-t border-glassBorder">
252217
<div className="red-scrollbar flex w-full flex-1 flex-col overflow-y-auto overflow-x-hidden">
253218
<MovesContainer
254219
game={{ id: currentDrill.id, tree: tree.gameTree }}

0 commit comments

Comments
 (0)