Skip to content

Commit bce1c39

Browse files
style: fixed loading components + redesigned opening selections
1 parent 9062a34 commit bce1c39

16 files changed

Lines changed: 192 additions & 125 deletions

File tree

src/components/Board/MovesContainer.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -371,7 +371,7 @@ export const MovesContainer: React.FC<
371371
return (
372372
<div
373373
ref={containerRef}
374-
className={`red-scrollbar grid ${heightClass} auto-rows-min grid-cols-[2.5rem_1fr_1fr_1fr_1fr] overflow-y-auto overflow-x-hidden whitespace-nowrap text-white/90 md:h-full md:w-full ${
374+
className={`red-scrollbar grid ${heightClass} w-full max-w-full auto-rows-min grid-cols-[2.5rem_1fr_1fr_1fr_1fr] overflow-y-auto overflow-x-hidden whitespace-nowrap text-white/90 md:h-full ${
375375
embedded
376376
? 'border-b border-glassBorder bg-transparent'
377377
: 'rounded-md border border-glassBorder bg-glass backdrop-blur-md'
@@ -391,14 +391,15 @@ export const MovesContainer: React.FC<
391391
}
392392
}}
393393
data-index={index * 2 + 1}
394-
className={`col-span-2 flex h-7 flex-1 cursor-pointer flex-row items-center justify-between px-2 text-sm hover:bg-glass-hover ${controller.currentNode === whiteNode && 'bg-glass-strong'} ${highlightSet.has(index * 2 + 1) && 'bg-glass-strong'}`}
394+
className={`col-span-2 flex h-7 min-w-0 flex-1 cursor-pointer flex-row items-center justify-between px-2 text-sm hover:bg-glass-hover ${controller.currentNode === whiteNode && 'bg-glass-strong'} ${highlightSet.has(index * 2 + 1) && 'bg-glass-strong'}`}
395395
>
396396
<span
397397
style={{
398398
color: showAnnotations
399399
? whiteNode?.color || 'inherit'
400400
: 'inherit',
401401
}}
402+
className="truncate"
402403
>
403404
{whiteNode?.san ?? whiteNode?.move}
404405
</span>
@@ -432,14 +433,15 @@ export const MovesContainer: React.FC<
432433
}
433434
}}
434435
data-index={index * 2 + 2}
435-
className={`col-span-2 flex h-7 flex-1 cursor-pointer flex-row items-center justify-between px-2 text-sm hover:bg-glass-hover ${controller.currentNode === blackNode && 'bg-glass-strong'} ${highlightSet.has(index * 2 + 2) && 'bg-glass-strong'}`}
436+
className={`col-span-2 flex h-7 min-w-0 flex-1 cursor-pointer flex-row items-center justify-between px-2 text-sm hover:bg-glass-hover ${controller.currentNode === blackNode && 'bg-glass-strong'} ${highlightSet.has(index * 2 + 2) && 'bg-glass-strong'}`}
436437
>
437438
<span
438439
style={{
439440
color: showAnnotations
440441
? blackNode?.color || 'inherit'
441442
: 'inherit',
442443
}}
444+
className="truncate"
443445
>
444446
{blackNode?.san ?? blackNode?.move}
445447
</span>

src/components/Common/Loading.tsx

Lines changed: 85 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import Chessground from '@react-chess/chessground'
22
import { useEffect, useMemo, useState } from 'react'
3+
import { motion, AnimatePresence } from 'framer-motion'
34

45
const states = [
56
'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1',
@@ -13,14 +14,38 @@ const states = [
1314
]
1415

1516
interface LoadingProps {
17+
// When true, shows loading UI (optionally after a delay); when false, shows children
18+
isLoading?: boolean
19+
// Milliseconds to wait before showing the loading UI
20+
delay?: number
21+
// Visual options for the loading UI
1622
transparent?: boolean
1723
message?: React.ReactNode
24+
// Optional content to render when not loading
25+
children?: React.ReactNode
1826
}
1927

2028
export const Loading: React.FC<LoadingProps> = ({
29+
isLoading = true,
30+
delay = 1000,
2131
transparent = false,
2232
message,
33+
children,
2334
}) => {
35+
// Delay handling for showing the loading UI
36+
const [showLoading, setShowLoading] = useState(false)
37+
useEffect(() => {
38+
let timer: NodeJS.Timeout
39+
if (isLoading) {
40+
timer = setTimeout(() => setShowLoading(true), delay)
41+
} else {
42+
setShowLoading(false)
43+
}
44+
return () => {
45+
if (timer) clearTimeout(timer)
46+
}
47+
}, [isLoading, delay])
48+
2449
const [currentIndex, setCurrentIndex] = useState(0)
2550
const [renderKey, setRenderKey] = useState(0)
2651

@@ -32,49 +57,71 @@ export const Loading: React.FC<LoadingProps> = ({
3257
useEffect(() => {
3358
const increment = async () => {
3459
await new Promise((resolve) => setTimeout(resolve, 500))
35-
setCurrentIndex(currentIndex + 1)
36-
60+
setCurrentIndex((idx) => idx + 1)
3761
setRenderKey((prev) => prev + 1)
3862
}
39-
40-
increment()
41-
}, [currentIndex])
63+
if (isLoading && showLoading) {
64+
increment()
65+
}
66+
}, [isLoading, showLoading, currentIndex])
4267

4368
return (
44-
<div
45-
className={`my-40 flex w-screen items-center justify-center ${
46-
transparent
47-
? 'absolute left-0 top-0 h-screen bg-backdrop/90'
48-
: 'bg-backdrop'
49-
} md:my-auto`}
50-
>
51-
<div className="flex flex-col items-center gap-4">
52-
<div
53-
className={`h-[50vw] w-[50vw] md:h-[30vh] md:w-[30vh] ${
54-
!transparent ? 'opacity-50' : 'opacity-100'
55-
}`}
69+
<AnimatePresence mode="wait">
70+
{isLoading && showLoading ? (
71+
<motion.div
72+
key="loading"
73+
initial={{ opacity: 0 }}
74+
animate={{ opacity: 1 }}
75+
exit={{ opacity: 0 }}
76+
transition={{ duration: 0.3 }}
77+
className="my-auto"
5678
>
57-
<div className="h-full w-full">
58-
<Chessground
59-
key={renderKey}
60-
contained
61-
config={{
62-
fen: currentState,
63-
animation: {
64-
duration: 0,
65-
},
66-
viewOnly: true,
67-
}}
68-
/>
79+
<div
80+
className={`my-40 flex w-screen items-center justify-center ${
81+
transparent
82+
? 'absolute left-0 top-0 h-screen bg-backdrop/90'
83+
: 'bg-backdrop'
84+
} md:my-auto`}
85+
>
86+
<div className="flex flex-col items-center gap-4">
87+
<div
88+
className={`h-[50vw] w-[50vw] md:h-[30vh] md:w-[30vh] ${
89+
!transparent ? 'opacity-50' : 'opacity-100'
90+
}`}
91+
>
92+
<div className="h-full w-full">
93+
<Chessground
94+
key={renderKey}
95+
contained
96+
config={{
97+
fen: currentState,
98+
animation: {
99+
duration: 0,
100+
},
101+
viewOnly: true,
102+
}}
103+
/>
104+
</div>
105+
</div>
106+
<h2 className="text-2xl font-semibold">Loading...</h2>
107+
{message ? (
108+
<p className="max-w-prose px-4 text-center text-secondary">
109+
{message}
110+
</p>
111+
) : null}
112+
</div>
69113
</div>
70-
</div>
71-
<h2 className="text-2xl font-semibold">Loading...</h2>
72-
{message ? (
73-
<p className="max-w-prose px-4 text-center text-secondary">
74-
{message}
75-
</p>
76-
) : null}
77-
</div>
78-
</div>
114+
</motion.div>
115+
) : !isLoading ? (
116+
<motion.div
117+
key="content"
118+
initial={{ opacity: 0, y: 10 }}
119+
animate={{ opacity: 1, y: 0 }}
120+
transition={{ duration: 0.4, ease: 'easeOut' }}
121+
>
122+
{children}
123+
</motion.div>
124+
) : null}
125+
</AnimatePresence>
79126
)
80127
}

src/components/Common/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ export * from './FeedbackButton'
66
export * from './Footer'
77
export * from './Header'
88
export * from './Loading'
9-
export * from './DelayedLoading'
109
export * from './Markdown'
1110
export * from './PlayerInfo'
1211
export * from './GameInfo'

src/components/Openings/OpeningDrillSidebar.tsx

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1-
import React from 'react'
1+
import React, { useContext } from 'react'
22
import Image from 'next/image'
33
import { CompletedDrill, OpeningSelection } from 'src/types/openings'
4+
import { BoardController, MovesContainer } from 'src/components'
5+
import { TreeControllerContext } from 'src/contexts'
6+
import { GameNode } from 'src/types'
47

58
interface Props {
69
currentDrill: OpeningSelection | null
@@ -14,6 +17,10 @@ interface Props {
1417
onLoadCompletedDrill?: (drill: CompletedDrill) => void
1518
onNavigateToDrill?: (drillIndex: number) => void
1619
embedded?: boolean
20+
// New optional props to render moves + controller
21+
openingEndNode?: GameNode | null
22+
analysisEnabled?: boolean
23+
continueAnalyzingMode?: boolean
1724
}
1825

1926
export const OpeningDrillSidebar: React.FC<Props> = ({
@@ -28,6 +35,9 @@ export const OpeningDrillSidebar: React.FC<Props> = ({
2835
onLoadCompletedDrill,
2936
onNavigateToDrill,
3037
embedded = false,
38+
openingEndNode,
39+
analysisEnabled,
40+
continueAnalyzingMode,
3141
}) => {
3242
const containerClass = embedded
3343
? 'flex h-full w-full flex-col 2xl:min-w-72'
@@ -41,6 +51,23 @@ export const OpeningDrillSidebar: React.FC<Props> = ({
4151
? 'px-4 pb-2'
4252
: 'border-b border-white/10 px-3 py-2'
4353

54+
const tree = useContext(TreeControllerContext)
55+
56+
// Navigation guards to respect opening start
57+
const customGoToPreviousNode = () => {
58+
if (!openingEndNode || !tree?.currentNode) return tree?.goToPreviousNode()
59+
if (tree.currentNode !== openingEndNode) {
60+
tree.goToPreviousNode()
61+
}
62+
}
63+
64+
const customGoToRootNode = () => {
65+
if (!openingEndNode) return tree?.goToRootNode()
66+
if (tree) {
67+
tree.goToNode(openingEndNode)
68+
}
69+
}
70+
4471
return (
4572
<div className={containerClass}>
4673
{/* Current Drill Info */}
@@ -227,6 +254,40 @@ export const OpeningDrillSidebar: React.FC<Props> = ({
227254
)}
228255
</div>
229256
</div>
257+
258+
{/* Bottom: Moves + Controller (embedded) */}
259+
{tree?.gameTree && currentDrill && (
260+
<div className="flex flex-1 flex-col overflow-hidden">
261+
<div className="h-3 border-t border-glassBorder" />
262+
<div className="red-scrollbar flex flex-1 flex-col overflow-y-auto overflow-x-hidden">
263+
<MovesContainer
264+
game={{ id: currentDrill.id, tree: tree.gameTree }}
265+
startFromNode={openingEndNode || undefined}
266+
restrictNavigationBefore={openingEndNode || undefined}
267+
showAnnotations={!!(analysisEnabled || continueAnalyzingMode)}
268+
showVariations={!!continueAnalyzingMode}
269+
embedded
270+
heightClass="h-40"
271+
/>
272+
<BoardController
273+
gameTree={tree.gameTree}
274+
orientation={tree.orientation}
275+
setOrientation={() => {}}
276+
currentNode={tree.currentNode}
277+
plyCount={tree.plyCount}
278+
goToNode={tree.goToNode}
279+
goToNextNode={tree.goToNextNode}
280+
goToPreviousNode={customGoToPreviousNode}
281+
goToRootNode={customGoToRootNode}
282+
disableFlip={true}
283+
disablePrevious={
284+
openingEndNode ? tree.currentNode === openingEndNode : false
285+
}
286+
embedded
287+
/>
288+
</div>
289+
</div>
290+
)}
230291
</div>
231292
)
232293
}

src/pages/analysis/[...id].tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import {
2222
GameNode,
2323
} from 'src/types'
2424
import { WindowSizeContext, TreeControllerContext, useTour } from 'src/contexts'
25-
import { DelayedLoading } from 'src/components/Common/DelayedLoading'
25+
import { Loading } from 'src/components'
2626
import { AuthenticatedWrapper } from 'src/components/Common/AuthenticatedWrapper'
2727
import { PlayerInfo } from 'src/components/Common/PlayerInfo'
2828
import { MoveMap } from 'src/components/Analysis/MoveMap'
@@ -215,9 +215,9 @@ const AnalysisPage: NextPage = () => {
215215
router={router}
216216
/>
217217
) : (
218-
<DelayedLoading isLoading={true}>
218+
<Loading isLoading={true}>
219219
<div></div>
220-
</DelayedLoading>
220+
</Loading>
221221
)}
222222
</>
223223
)

src/pages/analysis/index.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { NextPage } from 'next'
22
import { useRouter } from 'next/router'
3-
import { DelayedLoading } from 'src/components'
3+
import { Loading } from 'src/components'
44
import { fetchMaiaGameList } from 'src/api'
55
import { AnalysisListContext } from 'src/contexts'
66
import { useContext, useEffect, useState } from 'react'
@@ -51,9 +51,9 @@ const AnalysisPage: NextPage = () => {
5151
}, [analysisTournamentList, analysisPlayList, push])
5252

5353
return (
54-
<DelayedLoading isLoading={loading}>
54+
<Loading isLoading={loading}>
5555
<div></div>
56-
</DelayedLoading>
56+
</Loading>
5757
)
5858
}
5959

src/pages/analysis/stream/[gameId].tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { useRouter } from 'next/router'
44
import Head from 'next/head'
55
import { AnimatePresence } from 'framer-motion'
66

7-
import { DelayedLoading } from 'src/components'
7+
import { Loading } from 'src/components'
88
import { AuthenticatedWrapper } from 'src/components/Common/AuthenticatedWrapper'
99
import { DownloadModelModal } from 'src/components/Common/DownloadModelModal'
1010
import { useLichessStreamController } from 'src/hooks/useLichessStreamController'
@@ -197,7 +197,7 @@ const StreamAnalysisPage: NextPage = () => {
197197
streamController.game &&
198198
!streamController.streamState.gameEnded ? (
199199
<div className="absolute left-0 top-0 z-50">
200-
<DelayedLoading
200+
<Loading
201201
transparent
202202
isLoading={true}
203203
message={
@@ -210,7 +210,7 @@ const StreamAnalysisPage: NextPage = () => {
210210
}
211211
>
212212
<></>
213-
</DelayedLoading>
213+
</Loading>
214214
</div>
215215
) : null}
216216
{analysisController && (

0 commit comments

Comments
 (0)