refactor: extract skill install dialog footer and content

Extract footer and content sections from SkillInstallDialog and
SkillInstallManagementDialog into separate components for improved
maintainability and clarity of component responsibilities.
This commit is contained in:
Jinjing
2026-08-28 00:09:26 -07:00
parent c2ce89189e
commit 4efc60293a
4 changed files with 283 additions and 151 deletions
@@ -1,13 +1,6 @@
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { Download, Loader2, ShieldCheck } from 'lucide-react'
import { Button } from '@/components/ui/button'
import {
Dialog,
DialogContent,
DialogFooter,
DialogHeader,
DialogTitle
} from '@/components/ui/dialog'
import { Loader2 } from 'lucide-react'
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'
import { useAppStore } from '@/store'
import type {
SkillInstallDestination,
@@ -32,6 +25,7 @@ import { useSkillInstallProgress } from './skill-install-progress-state'
import { translate } from '@/i18n/i18n'
import { resolveSkillShareForInstall } from './skill-warning-preview-gate'
import { useSkillInstallRisk } from './use-skill-install-risk'
import { SkillInstallDialogFooter } from './SkillInstallDialogFooter'
export function SkillInstallDialog({
open,
@@ -374,65 +368,21 @@ export function SkillInstallDialog({
{installProgress.phaseLabel}
</p>
) : null}
{!bundleVersion ? (
<DialogFooter>
<Button type="button" variant="ghost" onClick={close} disabled={busy}>
{translate('auto.components.skills.SkillInstallDialog.d198ec91e5', 'Close')}
</Button>
{!preview && !resolvingInitialLink ? (
<Button type="button" disabled={busy || !link.trim()} onClick={() => void inspect()}>
{busy ? (
<Loader2 className="size-4 animate-spin" />
) : (
<ShieldCheck className="size-4" />
)}
{busy
? translate(
'auto.components.skills.SkillInstallReviewContent.69236de8d6',
'Checking…'
)
: translate(
'auto.components.skills.SkillInstallReviewContent.157de228b4',
'Inspect skill'
)}
</Button>
) : null}
{busy && installProgress.activeOperationId ? (
<Button type="button" variant="secondary" onClick={() => void cancelInstall()}>
{translate(
'auto.components.skills.SkillInstallDialog.05588076a9',
'Cancel installation'
)}
</Button>
) : null}
{preview &&
(!result || ['conflict', 'partial', 'failed', 'cancelled'].includes(result.status)) ? (
<Button
type="button"
disabled={busy || (scope === 'workspace' && !workspace)}
onClick={() => void install()}
className="w-32"
>
{busy ? (
<Loader2 className="size-4 animate-spin" />
) : (
<Download className="size-4" />
)}
{busy
? translate('auto.components.skills.SkillInstallDialog.241e72f9d6', 'Installing…')
: result
? translate(
'auto.components.skills.SkillInstallDialog.59c3b76cdd',
'Retry install'
)
: translate(
'auto.components.skills.SkillInstallDialog.39acb9e8f4',
'Install skill'
)}
</Button>
) : null}
</DialogFooter>
) : null}
<SkillInstallDialogFooter
activeOperationId={installProgress.activeOperationId}
busy={busy}
hasBundleVersion={Boolean(bundleVersion)}
hasPreview={Boolean(preview)}
link={link}
resolvingInitialLink={resolvingInitialLink}
result={result}
scope={scope}
workspace={workspace}
onCancelInstall={() => void cancelInstall()}
onClose={close}
onInspect={() => void inspect()}
onInstall={(discardLocal) => void install(discardLocal)}
/>
</DialogContent>
</Dialog>
)
@@ -0,0 +1,81 @@
import { Download, Loader2, ShieldCheck } from 'lucide-react'
import { Button } from '@/components/ui/button'
import { DialogFooter } from '@/components/ui/dialog'
import type { SkillInstallResult } from '../../../../shared/skill-install-contract'
import { translate } from '@/i18n/i18n'
type SkillInstallDialogFooterProps = {
activeOperationId: string | null
busy: boolean
hasBundleVersion: boolean
hasPreview: boolean
link: string
resolvingInitialLink: boolean
result: SkillInstallResult | null
scope: 'global' | 'workspace'
workspace: string
onCancelInstall: () => void
onClose: () => void
onInspect: () => void
onInstall: (discardLocal?: boolean) => void
}
export function SkillInstallDialogFooter({
activeOperationId,
busy,
hasBundleVersion,
hasPreview,
link,
resolvingInitialLink,
result,
scope,
workspace,
onCancelInstall,
onClose,
onInspect,
onInstall
}: SkillInstallDialogFooterProps): React.JSX.Element | null {
if (hasBundleVersion) {
return null
}
return (
<DialogFooter>
<Button type="button" variant="ghost" onClick={onClose} disabled={busy}>
{translate('auto.components.skills.SkillInstallDialog.d198ec91e5', 'Close')}
</Button>
{!hasPreview && !resolvingInitialLink ? (
<Button type="button" disabled={busy || !link.trim()} onClick={onInspect}>
{busy ? <Loader2 className="size-4 animate-spin" /> : <ShieldCheck className="size-4" />}
{busy
? translate('auto.components.skills.SkillInstallReviewContent.69236de8d6', 'Checking…')
: translate(
'auto.components.skills.SkillInstallReviewContent.157de228b4',
'Inspect skill'
)}
</Button>
) : null}
{busy && activeOperationId ? (
<Button type="button" variant="secondary" onClick={onCancelInstall}>
{translate('auto.components.skills.SkillInstallDialog.05588076a9', 'Cancel installation')}
</Button>
) : null}
{hasPreview &&
(!result || ['conflict', 'partial', 'failed', 'cancelled'].includes(result.status)) ? (
<Button
type="button"
disabled={busy || (scope === 'workspace' && !workspace)}
onClick={() => onInstall()}
className="w-32"
>
{busy ? <Loader2 className="size-4 animate-spin" /> : <Download className="size-4" />}
{busy
? translate('auto.components.skills.SkillInstallDialog.241e72f9d6', 'Installing…')
: result
? translate('auto.components.skills.SkillInstallDialog.59c3b76cdd', 'Retry install')
: translate('auto.components.skills.SkillInstallDialog.39acb9e8f4', 'Install skill')}
</Button>
) : null}
</DialogFooter>
)
}
@@ -1,14 +1,5 @@
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { Loader2 } from 'lucide-react'
import { Button } from '@/components/ui/button'
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle
} from '@/components/ui/dialog'
import { Dialog } from '@/components/ui/dialog'
import { useAppStore } from '@/store'
import type {
ManagedSkillInstall,
@@ -17,18 +8,15 @@ import type {
import type { SkillBundleInstallResult } from '../../../../shared/skill-bundle-install-contract'
import type { SkillCloudPackageDetails } from '../../../../shared/skill-cloud-contract'
import { notifyInstalledAgentSkillsChanged } from '@/hooks/useInstalledAgentSkills'
import { skillInstallResultLabel } from './skill-install-result-label'
import { skillInstallManagementCopy } from './skill-install-management-copy'
import { useSkillInstallProgress } from './skill-install-progress-state'
import { SkillManagedInstallRow } from './SkillManagedInstallRow'
import { summarizeManagedSkillRemoval } from './skill-managed-removal-summary'
import {
groupManagedSkillInstalls,
type SkillManagedInstallGroup
} from './skill-managed-install-groups'
import { SkillInstallMachineSelect } from './SkillInstallMachineSelect'
import { SkillInstallManagementStatus } from './SkillInstallManagementStatus'
import { translate } from '@/i18n/i18n'
import { SkillInstallManagementDialogContent } from './SkillInstallManagementDialogContent'
export function SkillInstallManagementDialog({
open,
@@ -351,77 +339,39 @@ export function SkillInstallManagementDialog({
onOpenChange(false)
}
const destructiveConflict =
result?.status === 'conflict' ||
Boolean(selected?.installs.some((install) => install.state === 'modified'))
return (
<Dialog open={open} onOpenChange={(next) => !next && !busy && close()}>
<DialogContent className="max-h-[calc(100vh-3rem)] overflow-x-hidden overflow-y-auto scrollbar-sleek sm:max-w-2xl [&>*]:min-w-0">
<DialogHeader>
<DialogTitle>{copy.title}</DialogTitle>
<DialogDescription>{copy.description}</DialogDescription>
</DialogHeader>
<SkillInstallMachineSelect
value={environmentId}
onChange={setEnvironmentId}
localLabel={copy.localMachine}
sshLabel={copy.ssh}
disconnectedLabel={copy.disconnected}
environments={runtimeEnvironments}
sshTargets={[...sshTargetLabels.entries()].map(([id, label]) => ({
id: `ssh:${id}`,
label,
connected: sshConnectionStates.get(id)?.status === 'connected'
}))}
/>
{busy && installs.length === 0 ? <Loader2 className="mx-auto size-5 animate-spin" /> : null}
{!busy && installs.length === 0 ? (
<p className="rounded-md border border-border p-4 text-sm text-muted-foreground">
{copy.noInstalls}
</p>
) : null}
{groups.length > 0 ? (
<ul className="divide-y divide-border rounded-md border border-border">
{groups.map((group) => (
<SkillManagedInstallRow
key={group.key}
group={group}
open={selectedKey === group.key}
details={selectedKey === group.key ? details : null}
versionId={selectedKey === group.key ? versionId : ''}
busy={busy}
confirmRemove={selectedKey === group.key && confirmRemove}
installActive={Boolean(installProgress.activeOperationId)}
editedWarning={selectedKey === group.key && destructiveConflict}
bundleResult={selectedKey === group.key ? bundleResult : null}
result={selectedKey === group.key ? result : null}
onOpenChange={(next) => (next ? void selectInstall(group) : collapse())}
onVersionChange={setVersionId}
onInstall={(discardLocal) => void installVersion(discardLocal)}
onCancelInstall={() => void cancelInstall()}
onSendToMachine={(shareId) => {
close()
useAppStore.getState().openSkillShare(shareId)
}}
onRemove={(discardLocal) => void remove(discardLocal)}
/>
))}
</ul>
) : null}
<SkillInstallManagementStatus
resultLabel={result ? skillInstallResultLabel(result) : null}
progressLabel={installProgress.phaseLabel}
error={error}
notice={notice}
/>
<DialogFooter>
<Button type="button" variant="ghost" onClick={close} disabled={busy}>
{copy.close}
</Button>
</DialogFooter>
</DialogContent>
<SkillInstallManagementDialogContent
bundleResult={bundleResult}
busy={busy}
confirmRemove={confirmRemove}
copy={copy}
details={details}
environmentId={environmentId}
error={error}
groups={groups}
installs={installs}
installActive={Boolean(installProgress.activeOperationId)}
notice={notice}
progressLabel={installProgress.phaseLabel}
result={result}
runtimeEnvironments={runtimeEnvironments}
selectedKey={selectedKey}
sshConnectionStates={sshConnectionStates}
sshTargetLabels={sshTargetLabels}
versionId={versionId}
onCancelInstall={() => void cancelInstall()}
onClose={close}
onEnvironmentChange={setEnvironmentId}
onInstall={(discardLocal) => void installVersion(discardLocal)}
onOpenChange={(group, next) => (next ? void selectInstall(group) : collapse())}
onRemove={(discardLocal) => void remove(discardLocal)}
onSendToMachine={(shareId) => {
close()
useAppStore.getState().openSkillShare(shareId)
}}
onVersionChange={setVersionId}
/>
</Dialog>
)
}
@@ -0,0 +1,151 @@
import { Loader2 } from 'lucide-react'
import { Button } from '@/components/ui/button'
import {
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle
} from '@/components/ui/dialog'
import type {
ManagedSkillInstall,
SkillInstallResult
} from '../../../../shared/skill-install-contract'
import type { SkillBundleInstallResult } from '../../../../shared/skill-bundle-install-contract'
import type { SkillCloudPackageDetails } from '../../../../shared/skill-cloud-contract'
import type { skillInstallManagementCopy } from './skill-install-management-copy'
import { skillInstallResultLabel } from './skill-install-result-label'
import { SkillInstallMachineSelect } from './SkillInstallMachineSelect'
import { SkillInstallManagementStatus } from './SkillInstallManagementStatus'
import { SkillManagedInstallRow } from './SkillManagedInstallRow'
import type { SkillManagedInstallGroup } from './skill-managed-install-groups'
type SkillInstallManagementDialogContentProps = {
bundleResult: SkillBundleInstallResult | null
busy: boolean
confirmRemove: boolean
copy: ReturnType<typeof skillInstallManagementCopy>
details: SkillCloudPackageDetails | null
environmentId: string
error: string | null
groups: SkillManagedInstallGroup[]
installs: ManagedSkillInstall[]
installActive: boolean
notice: string | null
result: SkillInstallResult | null
progressLabel: string | null
runtimeEnvironments: readonly { id: string; name: string }[]
selectedKey: string
sshConnectionStates: ReadonlyMap<string, { status: string }>
sshTargetLabels: ReadonlyMap<string, string>
versionId: string
onCancelInstall: () => void
onClose: () => void
onEnvironmentChange: (value: string) => void
onInstall: (discardLocal?: boolean) => void
onOpenChange: (group: SkillManagedInstallGroup, open: boolean) => void
onRemove: (discardLocal?: boolean) => void
onSendToMachine: (shareId: string) => void
onVersionChange: (versionId: string) => void
}
export function SkillInstallManagementDialogContent({
bundleResult,
busy,
confirmRemove,
copy,
details,
environmentId,
error,
groups,
installs,
installActive,
notice,
progressLabel,
result,
runtimeEnvironments,
selectedKey,
sshConnectionStates,
sshTargetLabels,
versionId,
onCancelInstall,
onClose,
onEnvironmentChange,
onInstall,
onOpenChange,
onRemove,
onSendToMachine,
onVersionChange
}: SkillInstallManagementDialogContentProps): React.JSX.Element {
const destructiveConflict =
result?.status === 'conflict' ||
Boolean(
groups
.find((group) => group.key === selectedKey)
?.installs.some((install) => install.state === 'modified')
)
return (
<DialogContent className="max-h-[calc(100vh-3rem)] overflow-x-hidden overflow-y-auto scrollbar-sleek sm:max-w-2xl [&>*]:min-w-0">
<DialogHeader>
<DialogTitle>{copy.title}</DialogTitle>
<DialogDescription>{copy.description}</DialogDescription>
</DialogHeader>
<SkillInstallMachineSelect
value={environmentId}
onChange={onEnvironmentChange}
localLabel={copy.localMachine}
sshLabel={copy.ssh}
disconnectedLabel={copy.disconnected}
environments={runtimeEnvironments}
sshTargets={[...sshTargetLabels.entries()].map(([id, label]) => ({
id: `ssh:${id}`,
label,
connected: sshConnectionStates.get(id)?.status === 'connected'
}))}
/>
{busy && installs.length === 0 ? <Loader2 className="mx-auto size-5 animate-spin" /> : null}
{!busy && installs.length === 0 ? (
<p className="rounded-md border border-border p-4 text-sm text-muted-foreground">
{copy.noInstalls}
</p>
) : null}
{groups.length > 0 ? (
<ul className="divide-y divide-border rounded-md border border-border">
{groups.map((group) => (
<SkillManagedInstallRow
key={group.key}
group={group}
open={selectedKey === group.key}
details={selectedKey === group.key ? details : null}
versionId={selectedKey === group.key ? versionId : ''}
busy={busy}
confirmRemove={selectedKey === group.key && confirmRemove}
installActive={installActive}
editedWarning={selectedKey === group.key && destructiveConflict}
bundleResult={selectedKey === group.key ? bundleResult : null}
result={selectedKey === group.key ? result : null}
onOpenChange={(next) => onOpenChange(group, next)}
onVersionChange={onVersionChange}
onInstall={onInstall}
onCancelInstall={onCancelInstall}
onSendToMachine={onSendToMachine}
onRemove={onRemove}
/>
))}
</ul>
) : null}
<SkillInstallManagementStatus
resultLabel={result ? skillInstallResultLabel(result) : null}
progressLabel={progressLabel}
error={error}
notice={notice}
/>
<DialogFooter>
<Button type="button" variant="ghost" onClick={onClose} disabled={busy}>
{copy.close}
</Button>
</DialogFooter>
</DialogContent>
)
}