From 979db26e6aeebbd27d4260109a45502cde00b5dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=BE=B7=E5=AE=87?= Date: Wed, 15 Jul 2026 20:21:53 +0800 Subject: [PATCH] extract sidebar --- apps/ai-game-creator-shell/src/App.tsx | 280 +++-------------- apps/ai-game-creator-shell/src/styles.css | 181 +---------- .../ai-game-creator-shell/src/view/layout.tsx | 281 ++++++++++++++++++ 3 files changed, 328 insertions(+), 414 deletions(-) create mode 100644 apps/ai-game-creator-shell/src/view/layout.tsx diff --git a/apps/ai-game-creator-shell/src/App.tsx b/apps/ai-game-creator-shell/src/App.tsx index a22e5bd2d..29008f5fa 100644 --- a/apps/ai-game-creator-shell/src/App.tsx +++ b/apps/ai-game-creator-shell/src/App.tsx @@ -1,7 +1,15 @@ import { - Fragment, + FileText, + FolderKanban, + Gamepad2, + Image, + MessageSquare, + Zap, +} from 'lucide-react'; +import { type ChangeEvent, type FormEvent, + Fragment, type KeyboardEvent as ReactKeyboardEvent, type MouseEvent as ReactMouseEvent, type ReactNode, @@ -11,33 +19,6 @@ import { useRef, useState, } from 'react'; -import { - Bell, - BookOpen, - CircleHelp, - Contact, - FileText, - FolderKanban, - Gamepad2, - Home, - Image, - LogOut, - MessageSquare, - Settings, - User, - Zap, -} from 'lucide-react'; -import HomeView, { - type HomeDraft, - type HomeAgentMode, - type HomeAgentModeItem, - type HomeAttachmentDraft, -} from './view/home'; -import { useLauncherHomeDraftStore } from './view/home/useHomeDraftStore'; -import { - getClientProfileDashboard, - getClientProfileWalletLedger, -} from './services/clientApi'; import type { AuthEntryResponse, @@ -48,15 +29,6 @@ import type { AuthUser, LogoutResponse, } from '../../../packages/shared/src/contracts/auth'; -import { - API_RESPONSE_ENVELOPE_HEADER, - API_RESPONSE_ENVELOPE_VERSION, - unwrapApiResponse, -} from '../../../packages/shared/src/http'; -import type { - ProfileDashboardSummary, - ProfileWalletLedgerEntry, -} from '../../../packages/shared/src/contracts/runtime'; import { createGameCreationAppManifest, createGameCreationAppSeedTasks, @@ -67,21 +39,42 @@ import { GAME_CREATION_APP_COMMANDS, GAME_CREATION_APP_LIMITED_RUN_COMMANDS, type GameCreationAgentCapabilityDescriptor, - type GameCreationAppAgentGroup, - type GameCreationAppCommandDescriptor, - type GameCreationAppLimitedRunCommandDescriptor, type GameCreationAgentRepairRouteTrace, type GameCreationAgentRunStep, - type GameCreationAgentToolCallTrace, type GameCreationAgentRunTrace, + type GameCreationAgentToolCallTrace, + type GameCreationAppAgentGroup, type GameCreationAppAssetSourceKind, + type GameCreationAppCommandDescriptor, + type GameCreationAppLimitedRunCommandDescriptor, type GameCreationAppManifest, type GameCreationAppPermission, type GameCreationAppPreviewStatus, - type GameCreationAppTaskStatus, type GameCreationAppTaskState, + type GameCreationAppTaskStatus, selectGameCreationAppReadyTasks, } from '../../../packages/shared/src/contracts/gameCreationApp'; +import type { + ProfileDashboardSummary, + ProfileWalletLedgerEntry, +} from '../../../packages/shared/src/contracts/runtime'; +import { + API_RESPONSE_ENVELOPE_HEADER, + API_RESPONSE_ENVELOPE_VERSION, + unwrapApiResponse, +} from '../../../packages/shared/src/http'; +import { + getClientProfileDashboard, + getClientProfileWalletLedger, +} from './services/clientApi'; +import HomeView, { + type HomeAgentMode, + type HomeAgentModeItem, + type HomeAttachmentDraft, + type HomeDraft, +} from './view/home'; +import { useLauncherHomeDraftStore } from './view/home/useHomeDraftStore'; +import { Sidebar, type LauncherView } from './view/layout'; const seedManifest = createGameCreationAppManifest( 'local-project-draft', @@ -103,14 +96,6 @@ const launcherNotifications: Array<{ detail: string; actionLabel: string; }> = []; -type LauncherView = - | 'home' - | 'projects' - | 'agent-chat' - | 'guide' - | 'contact' - | 'news' - | 'project-development'; type LauncherImportedAttachment = { fileName: string; mediaType: string; @@ -2129,14 +2114,10 @@ export function WorkspaceLauncher({ ); const [walletLedgerStatus, setWalletLedgerStatus] = useState('尚未读取明细'); - const [accountMenuOpen, setAccountMenuOpen] = useState(false); - const [helpMenuOpen, setHelpMenuOpen] = useState(false); const [launcherNotice, setLauncherNotice] = useState<{ title: string; message: string; } | null>(null); - const accountMenuRef = useRef(null); - const helpMenuRef = useRef(null); const resetLauncherHomeDraft = useLauncherHomeDraftStore( (state) => state.reset, ); @@ -2228,44 +2209,6 @@ export function WorkspaceLauncher({ void loadAgentChatLlmStatus(); }, [launcherView, agentChatSelectedAgentId]); - useEffect(() => { - function closeSidebarMenusIfOutside(target: EventTarget | null) { - if (!(target instanceof Node)) { - return; - } - if ( - accountMenuRef.current?.contains(target) || - helpMenuRef.current?.contains(target) - ) { - return; - } - setAccountMenuOpen(false); - setHelpMenuOpen(false); - } - - function handlePointerDown(event: PointerEvent) { - closeSidebarMenusIfOutside(event.target); - } - - function handleFocusIn(event: FocusEvent) { - closeSidebarMenusIfOutside(event.target); - } - - function handleWindowBlur() { - setAccountMenuOpen(false); - setHelpMenuOpen(false); - } - - document.addEventListener('pointerdown', handlePointerDown); - document.addEventListener('focusin', handleFocusIn); - window.addEventListener('blur', handleWindowBlur); - return () => { - document.removeEventListener('pointerdown', handlePointerDown); - document.removeEventListener('focusin', handleFocusIn); - window.removeEventListener('blur', handleWindowBlur); - }; - }, []); - function validateProjectPath(nextProjectPath: string) { const trimmedProjectPath = nextProjectPath.trim(); if (!trimmedProjectPath || !isAbsoluteProjectPath(trimmedProjectPath)) { @@ -2620,8 +2563,6 @@ export function WorkspaceLauncher({ } function showLauncherNotice(title: string) { - setAccountMenuOpen(false); - setHelpMenuOpen(false); setWalletPanelOpen(false); setLauncherNotice({ title, @@ -3096,144 +3037,17 @@ export function WorkspaceLauncher({ className="launcher-shell platform-theme platform-theme--light" aria-label="GameAgent 客户端" > - + { + resetLauncherHomeDraft(); + onLogout(); + }} + onNoticeRequest={showLauncherNotice} + onRuntimeConfigOpen={() => setRuntimeConfigOpen(true)} + onViewChange={setLauncherView} + />
0 @@ -3260,8 +3074,6 @@ export function WorkspaceLauncher({ type="button" aria-label="泥点余额" onClick={() => { - setAccountMenuOpen(false); - setHelpMenuOpen(false); setWalletPanelOpen((open) => !open); }} > diff --git a/apps/ai-game-creator-shell/src/styles.css b/apps/ai-game-creator-shell/src/styles.css index 02d4423bb..9864cc468 100644 --- a/apps/ai-game-creator-shell/src/styles.css +++ b/apps/ai-game-creator-shell/src/styles.css @@ -157,38 +157,6 @@ textarea { color: var(--platform-text-strong); } -.launcher-shell.platform-theme .launcher-sidebar { - border-color: var(--platform-surface-border); - background: var(--platform-nav-fill); -} - -.launcher-shell.platform-theme .launcher-logo { - background: var(--platform-button-primary-fill); - color: var(--platform-button-primary-text); - box-shadow: var(--platform-profile-action-shadow); -} - -.launcher-shell.platform-theme .launcher-sidebar nav button, -.launcher-shell.platform-theme .launcher-config-button { - color: var(--platform-nav-item-text); -} - -.launcher-shell.platform-theme .launcher-sidebar .launcher-nav-active { - border: 1px solid var(--platform-nav-active-border); - background: var(--platform-nav-active-fill); - color: var(--platform-nav-item-text-active); - box-shadow: var(--platform-nav-active-shadow); -} - -.launcher-shell.platform-theme .launcher-sidebar nav button:hover, -.launcher-shell.platform-theme .launcher-config-button:hover, -.launcher-shell.platform-theme .launcher-sidebar .launcher-help-menu button:hover, -.launcher-shell.platform-theme .launcher-sidebar .launcher-help-menu button:focus-visible, -.launcher-shell.platform-theme .launcher-sidebar .launcher-account-popover button:hover, -.launcher-shell.platform-theme .launcher-sidebar .launcher-account-popover button:focus-visible { - background: var(--platform-nav-item-hover-fill); -} - .launcher-shell.platform-theme .launcher-main { background: var(--platform-body-fill); } @@ -205,16 +173,13 @@ textarea { .launcher-shell.platform-theme .launcher-countdown, .launcher-shell.platform-theme .launcher-account-bar button, .launcher-shell.platform-theme .launcher-wallet-group, -.launcher-shell.platform-theme .launcher-account-popover, -.launcher-shell.platform-theme .launcher-wallet-popover, -.launcher-shell.platform-theme .launcher-help-menu { +.launcher-shell.platform-theme .launcher-wallet-popover { border-color: var(--platform-subpanel-border); background: var(--platform-subpanel-fill); color: var(--platform-text-strong); } .launcher-shell.platform-theme .launcher-account-bar button, -.launcher-shell.platform-theme .launcher-account-popover button, .launcher-shell.platform-theme .launcher-wallet-popover button, .launcher-shell.platform-theme .launcher-wallet-ledger div span { color: var(--platform-text-strong); @@ -224,146 +189,17 @@ textarea { border-left-color: var(--platform-subpanel-border); } -.launcher-shell.platform-theme .launcher-account-popover span, .launcher-shell.platform-theme .launcher-wallet-popover span, -.launcher-shell.platform-theme .launcher-account-popover small, .launcher-shell.platform-theme .launcher-wallet-popover small { color: var(--platform-text-soft); } -.launcher-sidebar { - position: sticky; - top: 0; - z-index: 40; - display: flex; - flex-direction: column; - align-items: center; - gap: 18px; - height: 100vh; - padding: 16px 0 18px; - border-right: 1px solid #eceef2; - background: #fff; - overflow: visible; -} - -.launcher-brand { - display: grid; - place-items: center; -} - -.launcher-logo { - display: grid; - width: 24px; - height: 24px; - border-radius: 50%; - background: #101010; - color: #fff; - font-size: 10px; - font-weight: 700; - letter-spacing: 0; - place-items: center; -} - -.launcher-sidebar nav { - display: grid; - gap: 13px; - justify-items: center; -} - -.launcher-sidebar-bottom { - position: relative; - z-index: 60; - display: grid; - justify-items: center; - gap: 13px; - margin-top: auto; -} - -.launcher-sidebar button, .launcher-promo button, .launcher-account-bar button { border: 0; cursor: pointer; } -.launcher-sidebar nav button, -.launcher-config-button { - display: grid; - width: 28px; - height: 28px; - padding: 0; - border-radius: 8px; - background: transparent; - color: #161616; - font-size: 17px; - place-items: center; -} - -.launcher-sidebar .launcher-nav-active { - background: #f3f4f6; -} - -.launcher-account-nav, -.launcher-help-nav { - position: relative; -} - -.launcher-help-menu { - position: absolute; - top: auto; - bottom: -4px; - left: 36px; - display: grid; - min-width: 150px; - gap: 5px; - padding: 8px; - border: 1px solid #e5e7eb; - border-radius: 8px; - background: #fff; - box-shadow: 0 14px 34px rgb(15 23 42 / 14%); - z-index: 90; -} - -.launcher-sidebar .launcher-help-menu button, -.launcher-sidebar .launcher-account-popover button { - display: flex; - align-items: center; - justify-content: flex-start; - gap: 8px; - width: 100%; - height: 30px; - padding: 0 9px; - border-radius: 7px; - background: transparent; - color: #1f2937; - font-size: 12px; - white-space: nowrap; -} - -.launcher-sidebar .launcher-help-menu button:hover, -.launcher-sidebar .launcher-help-menu button:focus-visible, -.launcher-sidebar .launcher-account-popover button:hover, -.launcher-sidebar .launcher-account-popover button:focus-visible { - background: #f3f4f6; -} - -.launcher-config-button { - font-size: 15px; -} - -.launcher-config-button:hover { - background: #f3f4f6; -} - -.launcher-help-nav .launcher-config-button, -.launcher-help-nav .launcher-config-button:hover, -.launcher-help-nav .launcher-config-button:focus, -.launcher-help-nav .launcher-config-button:focus-visible, -.launcher-help-nav .launcher-config-button:active, -.launcher-help-nav .launcher-config-button[aria-expanded='true'] { - background: transparent !important; -} - .launcher-main { position: relative; display: grid; @@ -469,7 +305,6 @@ textarea { display: flex; } -.launcher-account-popover, .launcher-wallet-popover { position: absolute; top: 34px; @@ -486,30 +321,16 @@ textarea { z-index: 90; } -.launcher-account-nav .launcher-account-popover { - top: auto; - right: auto; - bottom: -4px; - left: 36px; - width: 218px; - gap: 6px; - padding: 10px; -} - -.launcher-account-popover strong, .launcher-wallet-popover strong { font-size: 13px; } -.launcher-account-popover span, .launcher-wallet-popover span, -.launcher-account-popover small, .launcher-wallet-popover small { color: #6b7280; font-size: 11px; } -.launcher-account-popover button, .launcher-wallet-popover button { justify-content: flex-start; width: 100%; diff --git a/apps/ai-game-creator-shell/src/view/layout.tsx b/apps/ai-game-creator-shell/src/view/layout.tsx new file mode 100644 index 000000000..7f45c1fe9 --- /dev/null +++ b/apps/ai-game-creator-shell/src/view/layout.tsx @@ -0,0 +1,281 @@ +import { + Bell, + BookOpen, + CircleHelp, + Contact, + FolderKanban, + Home, + LogOut, + Settings, + User, +} from 'lucide-react'; +import { useEffect, useRef, useState } from 'react'; + +function cx(...classNames: Array) { + return classNames.filter(Boolean).join(' '); +} + +const sidebarIconButtonClass = + 'grid h-12 w-12 cursor-pointer place-items-center rounded-full border-0 bg-transparent p-0 text-[15px] text-(--platform-nav-item-text) hover:bg-(--platform-nav-item-hover-fill)'; +const sidebarHelpButtonClass = + 'grid h-12 w-12 cursor-pointer place-items-center rounded-full border-0 bg-transparent p-0 text-[15px] text-(--platform-nav-item-text)'; +const sidebarMenuItemClass = + 'flex h-[30px] w-full cursor-pointer items-center justify-start gap-2 whitespace-nowrap rounded-[7px] border-0 bg-transparent px-[9px] py-0 text-left text-xs text-(--platform-text-strong) hover:bg-(--platform-nav-item-hover-fill) focus-visible:bg-(--platform-nav-item-hover-fill)'; + +export type LauncherView = + | 'home' + | 'projects' + | 'agent-chat' + | 'guide' + | 'contact' + | 'news' + | 'project-development'; + +type SidebarUserInfo = { + displayName?: string | null; + phoneNumberMasked?: string | null; + publicUserCode?: string | null; +}; + +type LauncherSidebarProps = { + activeView: LauncherView; + currentUser: SidebarUserInfo; + onViewChange: (view: LauncherView) => void; + onNoticeRequest: (title: string) => void; + onRuntimeConfigOpen: () => void; + onLogout: () => void; +}; + +export function Sidebar({ + activeView, + currentUser, + onViewChange, + onNoticeRequest, + onRuntimeConfigOpen, + onLogout, +}: LauncherSidebarProps) { + const [accountMenuOpen, setAccountMenuOpen] = useState(false); + const [helpMenuOpen, setHelpMenuOpen] = useState(false); + const accountMenuRef = useRef(null); + const helpMenuRef = useRef(null); + + useEffect(() => { + function closeSidebarMenusIfOutside(target: EventTarget | null) { + if (!(target instanceof Node)) { + return; + } + if ( + accountMenuRef.current?.contains(target) || + helpMenuRef.current?.contains(target) + ) { + return; + } + setAccountMenuOpen(false); + setHelpMenuOpen(false); + } + + function handlePointerDown(event: PointerEvent) { + closeSidebarMenusIfOutside(event.target); + } + + function handleFocusIn(event: FocusEvent) { + closeSidebarMenusIfOutside(event.target); + } + + function handleWindowBlur() { + setAccountMenuOpen(false); + setHelpMenuOpen(false); + } + + document.addEventListener('pointerdown', handlePointerDown); + document.addEventListener('focusin', handleFocusIn); + window.addEventListener('blur', handleWindowBlur); + return () => { + document.removeEventListener('pointerdown', handlePointerDown); + document.removeEventListener('focusin', handleFocusIn); + window.removeEventListener('blur', handleWindowBlur); + }; + }, []); + + function closeMenus() { + setAccountMenuOpen(false); + setHelpMenuOpen(false); + } + + function showNotice(title: string) { + closeMenus(); + onNoticeRequest(title); + } + + function changeView(view: LauncherView) { + closeMenus(); + onViewChange(view); + } + + return ( + + ); +}