diff --git a/src/components/Analysis/AnalysisGameList.tsx b/src/components/Analysis/AnalysisGameList.tsx index 179b0210..7923d8b7 100644 --- a/src/components/Analysis/AnalysisGameList.tsx +++ b/src/components/Analysis/AnalysisGameList.tsx @@ -23,6 +23,7 @@ import { import { MaiaGameListEntry } from 'src/types' import { formatMaiaName, formatSavedName } from 'src/lib/maiaNames' import { useRouter } from 'next/router' +import { AnalysisGameListName } from 'src/constants/analysis' interface GameData { game_id: string @@ -59,6 +60,9 @@ interface AnalysisGameListProps { onGameSelected?: () => void // Called when a game is selected (for mobile popup closing) refreshTrigger?: number // Used to trigger refresh when custom analysis is added embedded?: boolean // Render without outer card container + // Switches to this list; a new nonce switches again (the site's Analysis + // menu asks for a list each time it's used). + requestedList?: { list: AnalysisGameListName; nonce: number } | null } export const AnalysisGameList: React.FC = ({ @@ -68,6 +72,7 @@ export const AnalysisGameList: React.FC = ({ refreshTrigger, loadNewWorldChampionshipGame, embedded = false, + requestedList, }) => { const router = useRouter() const { analysisLichessList, analysisTournamentList } = @@ -165,9 +170,7 @@ export const AnalysisGameList: React.FC = ({ } }, [analysisTournamentList, currentId, listKeys]) - const [selected, setSelected] = useState< - 'tournament' | 'lichess' | 'play' | 'hb' | 'custom' | 'favorites' - >(() => { + const [selected, setSelected] = useState(() => { if (currentId?.[1] === 'custom') { return 'custom' } else if (currentId?.[1] === 'lichess') { @@ -181,6 +184,9 @@ export const AnalysisGameList: React.FC = ({ } return 'tournament' }) + useEffect(() => { + if (requestedList) setSelected(requestedList.list) + }, [requestedList]) const [loadingIndex, setLoadingIndex] = useState(null) const [openIndex, setOpenIndex] = useState(initialOpenIndex) @@ -802,7 +808,7 @@ export const AnalysisGameList: React.FC = ({ selected={selected} setSelected={setSelected} /> -
+
setSelected(name)} - className={`relative flex items-center justify-center py-1 ${selected === name ? 'bg-white/10' : 'bg-white/5 hover:bg-white/10'} ${name === 'favorites' ? 'px-3' : ''}`} + className={`relative flex items-center justify-center whitespace-nowrap py-1 ${name === 'favorites' ? 'flex-none px-2' : 'flex-auto px-1'} ${selected === name ? 'bg-white/10' : 'bg-white/5 hover:bg-white/10'}`} >

{ const [showMenu, setShowMenu] = useState(false) const [showPlayDropdown, setShowPlayDropdown] = useState(false) const [showMoreDropdown, setShowMoreDropdown] = useState(false) + const [showAnalysisDropdown, setShowAnalysisDropdown] = useState(false) + + const openAnalysisList = (request: AnalysisMenuRequest) => { + setShowAnalysisDropdown(false) + if (router.pathname === '/analysis/[...id]') { + window.dispatchEvent( + new CustomEvent(OPEN_ANALYSIS_LIST_EVENT, { detail: request }), + ) + return + } + try { + sessionStorage.setItem(OPEN_ANALYSIS_LIST_KEY, request) + } catch { + // Without storage the analysis page still opens, on its usual tab. + } + router.push('/analysis') + } const [showProfileDropdown, setShowProfileDropdown] = useState(false) const { isMobile, width } = useContext(WindowSizeContext) @@ -243,12 +266,47 @@ export const Header: React.FC = () => { )}

- - ANALYSIS - +
+ setShowAnalysisDropdown(false)} + className={`-gap-1 flex items-center px-2 py-1 transition-all duration-200 hover:!text-primary ${router.pathname.startsWith('/analysis') ? '!text-primary' : '!text-primary/80'}`} + > +

ANALYSIS

+ + arrow_drop_down + + + + {showAnalysisDropdown && ( + +
+ {ANALYSIS_MENU_ITEMS.map((item) => ( + + ))} +
+
+ )} +
+
+ ANALYSIS_MENU_ITEMS.some((item) => item.request === value) + +// On the analysis page the menu sends this event; from any other page it +// leaves the request here and opens the analysis page, which reads it. +export const OPEN_ANALYSIS_LIST_EVENT = 'maia:open-analysis-list' as const +export const OPEN_ANALYSIS_LIST_KEY = 'maia.openAnalysisList' as const diff --git a/src/pages/analysis/[...id].tsx b/src/pages/analysis/[...id].tsx index 6f897201..69a74c0a 100644 --- a/src/pages/analysis/[...id].tsx +++ b/src/pages/analysis/[...id].tsx @@ -76,7 +76,14 @@ import { useTablebase, } from 'src/hooks' import { tourConfigs } from 'src/constants/tours' -import { ANALYSIS_LEFT_PANEL_TABS } from 'src/constants/analysis' +import { + ANALYSIS_LEFT_PANEL_TABS, + AnalysisGameListName, + AnalysisMenuRequest, + OPEN_ANALYSIS_LIST_EVENT, + OPEN_ANALYSIS_LIST_KEY, + isAnalysisMenuRequest, +} from 'src/constants/analysis' import type { DrawBrushes, DrawShape } from 'chessground/draw' import { MAIA_MODELS } from 'src/constants/common' import { applyEngineAnalysisData } from 'src/lib/analysis' @@ -1408,6 +1415,45 @@ const Analysis: React.FC = ({ const [desktopLeftPanelTab, setDesktopLeftPanelTab] = useState< (typeof ANALYSIS_LEFT_PANEL_TABS)[number]['id'] >(ANALYSIS_LEFT_PANEL_TABS[0].id) + const [showGameListMobile, setShowGameListMobile] = useState(false) + + // The site's Analysis menu opens one of the game lists (or the box for + // pasting a PGN or FEN) with an event here, or from another page through + // session storage. + const [gameListRequest, setGameListRequest] = useState<{ + list: AnalysisGameListName + nonce: number + } | null>(null) + useEffect(() => { + const open = (request: AnalysisMenuRequest) => { + if (request === 'import') { + setShowCustomModal(true) + return + } + setGameListRequest({ list: request, nonce: Date.now() }) + if (useMobileStyleAnalysisLayout) { + setShowGameListMobile(true) + } else { + setDesktopLeftPanelTab('select-game') + } + } + const onRequest = (event: Event) => { + const request = (event as CustomEvent).detail + if (isAnalysisMenuRequest(request)) open(request) + } + window.addEventListener(OPEN_ANALYSIS_LIST_EVENT, onRequest) + + let pending: string | null = null + try { + pending = sessionStorage.getItem(OPEN_ANALYSIS_LIST_KEY) + if (pending) sessionStorage.removeItem(OPEN_ANALYSIS_LIST_KEY) + } catch { + // Storage can be blocked; the menu still works on this page. + } + if (isAnalysisMenuRequest(pending)) open(pending) + + return () => window.removeEventListener(OPEN_ANALYSIS_LIST_EVENT, onRequest) + }, [useMobileStyleAnalysisLayout]) const showDesktopMovesAfterNavigation = useCallback(() => { if (desktopLeftPanelTab !== 'select-game') return @@ -1733,6 +1779,7 @@ const Analysis: React.FC = ({ } onCustomAnalysis={() => setShowCustomModal(true)} refreshTrigger={refreshTrigger} + requestedList={gameListRequest} embedded />
@@ -1988,8 +2035,6 @@ const Analysis: React.FC = ({ ) - const [showGameListMobile, setShowGameListMobile] = useState(false) - const mobileLayout = ( = ({ }} onGameSelected={() => setShowGameListMobile(false)} refreshTrigger={refreshTrigger} + requestedList={gameListRequest} />