Skip to content

Commit 2e6c590

Browse files
feat: add initial iteration of eco tree for openings
1 parent c062cf6 commit 2e6c590

7 files changed

Lines changed: 3002 additions & 25 deletions

File tree

src/components/Openings/EcoTreeView.tsx

Lines changed: 446 additions & 0 deletions
Large diffs are not rendered by default.

src/components/Openings/OpeningSelectionModal.tsx

Lines changed: 191 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,21 @@ import {
88
OpeningVariation,
99
OpeningSelection,
1010
DrillConfiguration,
11+
EcoOpening,
12+
EcoOpeningVariation,
13+
EcoDatabase,
14+
EcoSection,
15+
PopularOpening,
1116
} from 'src/types'
1217
import { ModalContainer } from '../Common/ModalContainer'
1318
import { useTour } from 'src/contexts'
1419
import { tourConfigs } from 'src/constants/tours'
1520
import { WindowSizeContext } from 'src/contexts/WindowSizeContext'
21+
import EcoTreeView from './EcoTreeView'
22+
import {
23+
processEcoDatabase,
24+
ecoToLegacyOpening,
25+
} from 'src/lib/openings/ecoProcessor'
1626
import {
1727
trackOpeningSelectionModalOpened,
1828
trackOpeningSearchUsed,
@@ -26,9 +36,11 @@ import { MAIA_MODELS_WITH_NAMES } from 'src/constants/common'
2636
import { selectOpeningDrills } from 'src/api/opening'
2737

2838
type MobileTab = 'browse' | 'selected'
39+
type ViewMode = 'simple' | 'eco'
2940

3041
interface Props {
3142
openings: Opening[]
43+
ecoDatabase?: EcoDatabase
3244
initialSelections?: OpeningSelection[]
3345
onComplete: (configuration: DrillConfiguration) => void
3446
onClose: () => void
@@ -791,6 +803,7 @@ const SelectedPanel: React.FC<{
791803

792804
export const OpeningSelectionModal: React.FC<Props> = ({
793805
openings,
806+
ecoDatabase,
794807
initialSelections = [],
795808
onComplete,
796809
onClose,
@@ -810,8 +823,24 @@ export const OpeningSelectionModal: React.FC<Props> = ({
810823
const [drillCount, setDrillCount] = useState(5)
811824
const [searchTerm, setSearchTerm] = useState('')
812825
const [activeTab, setActiveTab] = useState<MobileTab>('browse')
826+
const [viewMode, setViewMode] = useState<ViewMode>(
827+
ecoDatabase ? 'eco' : 'simple',
828+
)
813829
const [initialTourCheck, setInitialTourCheck] = useState(false)
814830
const [hasTrackedModalOpen, setHasTrackedModalOpen] = useState(false)
831+
832+
// ECO database processing
833+
const ecoData = useMemo(() => {
834+
if (!ecoDatabase) return null
835+
return processEcoDatabase(ecoDatabase)
836+
}, [ecoDatabase])
837+
838+
// ECO preview state - initialize with first ECO opening if available
839+
const [previewEcoOpening, setPreviewEcoOpening] = useState<EcoOpening | null>(
840+
ecoData?.sections?.[0]?.openings?.[0] || null,
841+
)
842+
const [previewEcoVariation, setPreviewEcoVariation] =
843+
useState<EcoOpeningVariation | null>(null)
815844
const [mobilePopupOpening, setMobilePopupOpening] = useState<Opening | null>(
816845
null,
817846
)
@@ -863,8 +892,60 @@ export const OpeningSelectionModal: React.FC<Props> = ({
863892
}
864893

865894
const previewFen = useMemo(() => {
895+
if (viewMode === 'eco' && previewEcoOpening) {
896+
return previewEcoVariation
897+
? previewEcoVariation.fen
898+
: previewEcoOpening.fen
899+
}
866900
return previewVariation ? previewVariation.fen : previewOpening.fen
867-
}, [previewOpening, previewVariation])
901+
}, [
902+
previewOpening,
903+
previewVariation,
904+
previewEcoOpening,
905+
previewEcoVariation,
906+
viewMode,
907+
])
908+
909+
// ECO selection handlers
910+
const handleEcoOpeningClick = (
911+
opening: EcoOpening,
912+
variation?: EcoOpeningVariation,
913+
) => {
914+
setPreviewEcoOpening(opening)
915+
setPreviewEcoVariation(variation || null)
916+
trackOpeningPreviewSelected(
917+
opening.name,
918+
opening.id,
919+
!!variation,
920+
variation?.name,
921+
)
922+
}
923+
924+
const handleEcoQuickAdd = (
925+
opening: EcoOpening,
926+
variation?: EcoOpeningVariation,
927+
) => {
928+
const legacyOpening = ecoToLegacyOpening(opening)
929+
const legacyVariation = variation
930+
? {
931+
id: variation.id,
932+
name: variation.name,
933+
fen: variation.fen,
934+
pgn: variation.pgn,
935+
}
936+
: null
937+
938+
addQuickSelection(legacyOpening, legacyVariation)
939+
}
940+
941+
const isEcoSelected = (
942+
opening: EcoOpening,
943+
variation?: EcoOpeningVariation,
944+
) => {
945+
return selections.some(
946+
(s) => s.opening.id === opening.id && s.variation?.id === variation?.id,
947+
)
948+
}
868949

869950
const filteredOpenings = useMemo(() => {
870951
if (!searchTerm) return openings
@@ -899,24 +980,40 @@ export const OpeningSelectionModal: React.FC<Props> = ({
899980
}
900981

901982
const addSelection = () => {
902-
if (isDuplicateSelection(previewOpening, previewVariation)) return
983+
// Handle ECO vs traditional opening selection
984+
const currentOpening =
985+
viewMode === 'eco' && previewEcoOpening
986+
? ecoToLegacyOpening(previewEcoOpening)
987+
: previewOpening
988+
989+
const currentVariation =
990+
viewMode === 'eco' && previewEcoVariation
991+
? {
992+
id: previewEcoVariation.id,
993+
name: previewEcoVariation.name,
994+
fen: previewEcoVariation.fen,
995+
pgn: previewEcoVariation.pgn,
996+
}
997+
: previewVariation
998+
999+
if (isDuplicateSelection(currentOpening, currentVariation)) return
9031000

9041001
const newSelection: OpeningSelection = {
905-
id: `${previewOpening.id}-${previewVariation?.id || 'main'}-${selectedColor}-${selectedMaiaVersion.id}-${targetMoveNumber}`,
906-
opening: previewOpening,
907-
variation: previewVariation,
1002+
id: `${currentOpening.id}-${currentVariation?.id || 'main'}-${selectedColor}-${selectedMaiaVersion.id}-${targetMoveNumber}`,
1003+
opening: currentOpening,
1004+
variation: currentVariation,
9081005
playerColor: selectedColor,
9091006
maiaVersion: selectedMaiaVersion.id,
9101007
targetMoveNumber,
9111008
}
9121009

9131010
// Track opening configuration and addition
9141011
trackOpeningConfiguredAndAdded(
915-
previewOpening.name,
1012+
currentOpening.name,
9161013
selectedColor,
9171014
selectedMaiaVersion.id,
9181015
targetMoveNumber,
919-
previewVariation?.name,
1016+
currentVariation?.name,
9201017
)
9211018

9221019
setSelections([...selections, newSelection])
@@ -1178,6 +1275,32 @@ export const OpeningSelectionModal: React.FC<Props> = ({
11781275
your color and opponent strength.
11791276
</p>
11801277
</div>
1278+
1279+
{/* View Toggle */}
1280+
{ecoDatabase && (
1281+
<div className="flex items-center gap-2 rounded bg-background-2 p-1">
1282+
<button
1283+
onClick={() => setViewMode('simple')}
1284+
className={`rounded px-3 py-1 text-sm font-medium transition-colors ${
1285+
viewMode === 'simple'
1286+
? 'bg-human-4 text-white'
1287+
: 'text-secondary hover:text-primary'
1288+
}`}
1289+
>
1290+
Simple
1291+
</button>
1292+
<button
1293+
onClick={() => setViewMode('eco')}
1294+
className={`rounded px-3 py-1 text-sm font-medium transition-colors ${
1295+
viewMode === 'eco'
1296+
? 'bg-human-4 text-white'
1297+
: 'text-secondary hover:text-primary'
1298+
}`}
1299+
>
1300+
ECO Tree
1301+
</button>
1302+
</div>
1303+
)}
11811304
</div>
11821305
</div>
11831306

@@ -1190,26 +1313,69 @@ export const OpeningSelectionModal: React.FC<Props> = ({
11901313

11911314
{/* Main Content - Responsive Layout */}
11921315
<div className="grid w-full flex-1 grid-cols-1 overflow-hidden md:grid-cols-3">
1193-
<BrowsePanel
1194-
activeTab={activeTab}
1195-
filteredOpenings={filteredOpenings}
1196-
previewOpening={previewOpening}
1197-
previewVariation={previewVariation}
1198-
setPreviewOpening={setPreviewOpening}
1199-
setPreviewVariation={setPreviewVariation}
1200-
setActiveTab={setActiveTab}
1201-
addQuickSelection={addQuickSelection}
1202-
isDuplicateSelection={isDuplicateSelection}
1203-
searchTerm={searchTerm}
1204-
setSearchTerm={setSearchTerm}
1205-
selections={selections}
1206-
onOpeningClick={handleMobileOpeningClick}
1207-
removeSelection={removeSelection}
1208-
/>
1316+
{viewMode === 'eco' && ecoData ? (
1317+
<div
1318+
className={`flex w-full flex-col overflow-y-scroll ${activeTab !== 'browse' ? 'hidden md:flex' : 'flex'} md:border-r md:border-white/10`}
1319+
>
1320+
<div className="hidden h-20 flex-col justify-center gap-1 border-b border-white/10 p-4 md:flex">
1321+
<h2 className="text-xl font-bold">ECO Opening Tree</h2>
1322+
<p className="text-xs text-secondary">
1323+
Browse openings organized by ECO classification
1324+
</p>
1325+
</div>
1326+
1327+
<div className="flex h-16 flex-col justify-center gap-1 border-b border-white/10 p-4 md:hidden">
1328+
<h2 className="text-lg font-bold">ECO Openings</h2>
1329+
<p className="text-xs text-secondary">
1330+
Choose from ECO database
1331+
</p>
1332+
</div>
1333+
1334+
<EcoTreeView
1335+
sections={ecoData.sections}
1336+
popularOpenings={ecoData.popularOpenings}
1337+
onOpeningClick={handleEcoOpeningClick}
1338+
onQuickAdd={handleEcoQuickAdd}
1339+
isSelected={isEcoSelected}
1340+
searchTerm={searchTerm}
1341+
setSearchTerm={setSearchTerm}
1342+
/>
1343+
</div>
1344+
) : (
1345+
<BrowsePanel
1346+
activeTab={activeTab}
1347+
filteredOpenings={filteredOpenings}
1348+
previewOpening={previewOpening}
1349+
previewVariation={previewVariation}
1350+
setPreviewOpening={setPreviewOpening}
1351+
setPreviewVariation={setPreviewVariation}
1352+
setActiveTab={setActiveTab}
1353+
addQuickSelection={addQuickSelection}
1354+
isDuplicateSelection={isDuplicateSelection}
1355+
searchTerm={searchTerm}
1356+
setSearchTerm={setSearchTerm}
1357+
selections={selections}
1358+
onOpeningClick={handleMobileOpeningClick}
1359+
removeSelection={removeSelection}
1360+
/>
1361+
)}
12091362
<PreviewPanel
12101363
selections={selections}
1211-
previewOpening={previewOpening}
1212-
previewVariation={previewVariation}
1364+
previewOpening={
1365+
viewMode === 'eco' && previewEcoOpening
1366+
? ecoToLegacyOpening(previewEcoOpening)
1367+
: previewOpening
1368+
}
1369+
previewVariation={
1370+
viewMode === 'eco' && previewEcoVariation
1371+
? {
1372+
id: previewEcoVariation.id,
1373+
name: previewEcoVariation.name,
1374+
fen: previewEcoVariation.fen,
1375+
pgn: previewEcoVariation.pgn,
1376+
}
1377+
: previewVariation
1378+
}
12131379
previewFen={previewFen}
12141380
selectedColor={selectedColor}
12151381
setSelectedColor={setSelectedColor}

0 commit comments

Comments
 (0)