Files
Netcatty/components/host/HostTreeGroupDeleteDialog.tsx
T
陈大猫 36e5779d94 perf(terminal): reduce terminal tab-switch and layout jank (#1321)
* perf(terminal): smooth layout drags and faster tab switching

Defer xterm refit during split, sidebar, and host-tree drags while keeping pane containers in sync with live layout measurements. Refactor TerminalLayer into focused sections with TabBridge/memo optimizations and add the terminal host tree sidebar.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(terminal): keep side panels alive and guard session attach races

Prevent terminal boot unmount from leaking backend sessions, keep SFTP/scripts/theme/AI state when switching side tabs, and defer heavy SFTP UI mount so first entry stays responsive.

Co-authored-by: Cursor <cursoragent@cursor.com>

* perf(terminal): reduce tab switch jank

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-09 03:35:03 +08:00

97 lines
3.0 KiB
TypeScript

import React, { useEffect, useState } from 'react';
import { useI18n } from '../../application/i18n/I18nProvider';
import {
hostTreeInlineGroupDeleteStore,
useHostTreeInlineGroupDeleteTarget,
} from '../../application/state/hostTreeInlineGroupDeleteStore';
import { Button } from '../ui/button';
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from '../ui/dialog';
type HostTreeGroupDeleteDialogProps = {
managedGroupPaths?: Set<string>;
onConfirmDelete: (groupPath: string, deleteHosts: boolean) => void | Promise<void>;
};
export const HostTreeGroupDeleteDialog: React.FC<HostTreeGroupDeleteDialogProps> = ({
managedGroupPaths,
onConfirmDelete,
}) => {
const { t } = useI18n();
const targetPath = useHostTreeInlineGroupDeleteTarget();
const [deleteHosts, setDeleteHosts] = useState(false);
const isOpen = Boolean(targetPath);
const isManaged = Boolean(targetPath && managedGroupPaths?.has(targetPath));
useEffect(() => {
if (!isOpen) {
setDeleteHosts(false);
}
}, [isOpen]);
return (
<Dialog
open={isOpen}
onOpenChange={(open) => {
if (!open) hostTreeInlineGroupDeleteStore.close();
}}
>
<DialogContent>
<DialogHeader>
<DialogTitle>{t('vault.groups.deleteDialogTitle')}</DialogTitle>
<DialogDescription>
{isManaged
? t('vault.groups.deleteDialog.managedDesc')
: t('vault.groups.deleteDialog.desc')}
</DialogDescription>
</DialogHeader>
<div className="py-4 space-y-4">
{targetPath && (
<>
<p className="text-sm text-muted-foreground">
{t('vault.groups.pathLabel')}:{' '}
<span className="font-mono">{targetPath}</span>
</p>
{!isManaged && (
<label className="flex items-center gap-2 text-sm cursor-pointer">
<input
type="checkbox"
checked={deleteHosts}
onChange={(event) => setDeleteHosts(event.target.checked)}
className="rounded border-border"
/>
<span>{t('vault.groups.deleteDialog.deleteHosts')}</span>
</label>
)}
</>
)}
</div>
<DialogFooter>
<Button variant="ghost" onClick={() => hostTreeInlineGroupDeleteStore.close()}>
{t('common.cancel')}
</Button>
<Button
variant="destructive"
onClick={() => {
if (!targetPath) return;
void Promise.resolve(onConfirmDelete(targetPath, isManaged || deleteHosts)).finally(() => {
hostTreeInlineGroupDeleteStore.close();
setDeleteHosts(false);
});
}}
>
{t('common.delete')}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
};