diff --git a/vscode/src/panel/handlers/release.ts b/vscode/src/panel/handlers/release.ts index 9f5c900..e4c95fd 100644 --- a/vscode/src/panel/handlers/release.ts +++ b/vscode/src/panel/handlers/release.ts @@ -28,6 +28,8 @@ export async function handleReleaseStatus(panel: KanbanWebviewPanel): Promise 0, } if (!root) { panel.postMessage({ ...base, unreleased: 0, unavailable: '请先打开一个工作区文件夹' }) diff --git a/vscode/src/panel/messages.ts b/vscode/src/panel/messages.ts index 65b690b..c99f48f 100644 --- a/vscode/src/panel/messages.ts +++ b/vscode/src/panel/messages.ts @@ -120,6 +120,7 @@ export type ExtensionToWebview unreleased: number unavailable?: string running: boolean + jenkinsConfigured: boolean } | { type: 'env-lock/status', locked: boolean, fileCount: number, failedCount?: number } | { type: 'issue/remove', issueNumber: number } diff --git a/vscode/webview-ui/src/App.tsx b/vscode/webview-ui/src/App.tsx index 8d80a9f..db1e05e 100644 --- a/vscode/webview-ui/src/App.tsx +++ b/vscode/webview-ui/src/App.tsx @@ -67,11 +67,8 @@ export function App() { commitRunning, runCommit, hasChanges, - branchSyncBehind, - branchSyncRunning, - branchSyncDisabled, - branchSyncTitle, - runBranchSync, + release, + runRelease, envLocked, envFileCount, envLockRunning, @@ -307,16 +304,13 @@ export function App() { commitRunning={commitRunning} hasChanges={hasChanges} onCommit={runCommit} - branchSyncBehind={branchSyncBehind} - branchSyncRunning={branchSyncRunning} - branchSyncDisabled={branchSyncDisabled} - branchSyncTitle={branchSyncTitle} + release={release} + onRelease={runRelease} envLocked={envLocked} envFileCount={envFileCount} envLockRunning={envLockRunning} envLockTitle={envLockTitle} onToggleEnvLock={toggleEnvLock} - onSyncBranch={runBranchSync} youtrackConfigured={youtrackConfigured} onImportYouTrack={importYouTrack} /> @@ -336,16 +330,13 @@ export function App() { commitRunning={commitRunning} hasChanges={hasChanges} onCommit={runCommit} - branchSyncBehind={branchSyncBehind} - branchSyncRunning={branchSyncRunning} - branchSyncDisabled={branchSyncDisabled} - branchSyncTitle={branchSyncTitle} + release={release} + onRelease={runRelease} envLocked={envLocked} envFileCount={envFileCount} envLockRunning={envLockRunning} envLockTitle={envLockTitle} onToggleEnvLock={toggleEnvLock} - onSyncBranch={runBranchSync} youtrackConfigured={youtrackConfigured} onImportYouTrack={importYouTrack} /> @@ -434,16 +425,13 @@ export function App() { commitRunning={commitRunning} hasChanges={hasChanges} onCommit={runCommit} - branchSyncBehind={branchSyncBehind} - branchSyncRunning={branchSyncRunning} - branchSyncDisabled={branchSyncDisabled} - branchSyncTitle={branchSyncTitle} + release={release} + onRelease={runRelease} envLocked={envLocked} envFileCount={envFileCount} envLockRunning={envLockRunning} envLockTitle={envLockTitle} onToggleEnvLock={toggleEnvLock} - onSyncBranch={runBranchSync} youtrackConfigured={youtrackConfigured} onImportYouTrack={importYouTrack} /> diff --git a/vscode/webview-ui/src/components/PanelHeader.tsx b/vscode/webview-ui/src/components/PanelHeader.tsx index 0addfdf..fceec03 100644 --- a/vscode/webview-ui/src/components/PanelHeader.tsx +++ b/vscode/webview-ui/src/components/PanelHeader.tsx @@ -1,4 +1,6 @@ -import { GitCommit, GitMerge, ListPlus, Loader2, Lock, RefreshCw, Settings, Unlock, User, Users } from 'lucide-react' +import type { ReleaseAction, ReleaseStatus } from '../hooks/useIssues' +import { GitCommit, ListPlus, Loader2, Lock, RefreshCw, Rocket, Settings, Unlock, User, Users } from 'lucide-react' +import { useEffect, useRef, useState } from 'react' interface Props { onRefresh: () => void @@ -15,24 +17,9 @@ interface Props { */ hasChanges: boolean onCommit: () => void - /** - * Commits the remote auto-build branch is behind the remote dev branch. - * Combined with `branchSyncDisabled` to compute the button's disabled - * state. - */ - branchSyncBehind: number - branchSyncRunning: boolean - /** - * True when sync is structurally unavailable (same-branch / fetch fail - * / not a repo / …). Disables the button regardless of `branchSyncBehind`. - */ - branchSyncDisabled: boolean - /** - * Hover tooltip, already includes branch names + behind count or the - * unavailable reason. - */ - branchSyncTitle: string - onSyncBranch: () => void + /** 最近一次发布状态;首次消息到达前为 undefined,按钮显示「正在检查」并禁用。 */ + release: ReleaseStatus | undefined + onRelease: (action: ReleaseAction) => void /** * Whether the workspace's `.env*` files are currently chmod-locked (444). * Icon flips between Lock and Unlock based on this. @@ -63,6 +50,19 @@ interface Props { onImportYouTrack: () => void } +const MENU_ITEM_CLASS = 'w-full px-3 py-1 text-left hover:bg-[var(--vscode-menu-selectionBackground)] hover:text-[var(--vscode-menu-selectionForeground)] disabled:cursor-not-allowed disabled:opacity-50 disabled:hover:bg-transparent disabled:hover:text-inherit' + +function releaseTitle(release: ReleaseStatus | undefined): string { + if (!release) + return '正在检查发布状态…' + if (release.unavailable) + return release.unavailable + if (!release.current) + return '尚无发布点 · 点击切第一个发布分支' + const lead = release.unreleased > 0 ? `${release.devBranch} 领先 ${release.unreleased} 个提交` : '已全部发布' + return `正式服发布点 ${release.current} · ${lead}` +} + export function PanelHeader({ onRefresh, scope, @@ -71,11 +71,8 @@ export function PanelHeader({ commitRunning, hasChanges, onCommit, - branchSyncBehind, - branchSyncRunning, - branchSyncDisabled, - branchSyncTitle, - onSyncBranch, + release, + onRelease, envLocked, envFileCount, envLockRunning, @@ -85,27 +82,80 @@ export function PanelHeader({ onImportYouTrack, }: Props) { const showCommit = hasChanges || commitRunning - // Sync button is *always* rendered so the user can see status (vs the - // commit button which auto-hides on a clean tree). Disabled when nothing - // to push, unavailable, or already syncing. - const syncDisabled = branchSyncRunning || branchSyncDisabled || branchSyncBehind <= 0 // Env-lock button is always visible; disabled while a chmod batch is in // flight or when the workspace has no .env files to act on. const envLockDisabled = envLockRunning || envFileCount === 0 + + const [releaseMenuOpen, setReleaseMenuOpen] = useState(false) + const menuRef = useRef(null) + useEffect(() => { + if (!releaseMenuOpen) + return + const onDown = (e: MouseEvent): void => { + if (!menuRef.current?.contains(e.target as Node)) + setReleaseMenuOpen(false) + } + document.addEventListener('mousedown', onDown) + return () => document.removeEventListener('mousedown', onDown) + }, [releaseMenuOpen]) + + const title = releaseTitle(release) + // 发布按钮常驻,让用户随时看到发布点状态;结构性不可用或动作进行中禁用。 + const releaseDisabled = !release || !!release.unavailable || release.running + const unreleased = release?.unreleased ?? 0 + + function pickRelease(action: ReleaseAction): void { + setReleaseMenuOpen(false) + onRelease(action) + } + return (
- +
+ + {releaseMenuOpen && release && ( +
+ + + {release.jenkinsConfigured && ( + + )} +
+ )} +