feat: add confirm dialogs for delete/reset actions
This commit is contained in:
@@ -102,6 +102,9 @@ const rulesetToPermissionConfig = (ruleset: unknown): AgentDraft['permission'] =
|
|||||||
export const AgentsSidebar: React.FC<AgentsSidebarProps> = ({ onItemSelect }) => {
|
export const AgentsSidebar: React.FC<AgentsSidebarProps> = ({ onItemSelect }) => {
|
||||||
const [renameDialogAgent, setRenameDialogAgent] = React.useState<Agent | null>(null);
|
const [renameDialogAgent, setRenameDialogAgent] = React.useState<Agent | null>(null);
|
||||||
const [renameNewName, setRenameNewName] = React.useState('');
|
const [renameNewName, setRenameNewName] = React.useState('');
|
||||||
|
const [confirmActionAgent, setConfirmActionAgent] = React.useState<Agent | null>(null);
|
||||||
|
const [confirmActionType, setConfirmActionType] = React.useState<'delete' | 'reset' | null>(null);
|
||||||
|
const [isConfirmActionPending, setIsConfirmActionPending] = React.useState(false);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
selectedAgentName,
|
selectedAgentName,
|
||||||
@@ -150,14 +153,8 @@ export const AgentsSidebar: React.FC<AgentsSidebarProps> = ({ onItemSelect }) =>
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (window.confirm(`Are you sure you want to delete agent "${agent.name}"?`)) {
|
setConfirmActionAgent(agent);
|
||||||
const success = await deleteAgent(agent.name);
|
setConfirmActionType('delete');
|
||||||
if (success) {
|
|
||||||
toast.success(`Agent "${agent.name}" deleted successfully`);
|
|
||||||
} else {
|
|
||||||
toast.error('Failed to delete agent');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleResetAgent = async (agent: Agent) => {
|
const handleResetAgent = async (agent: Agent) => {
|
||||||
@@ -165,14 +162,37 @@ export const AgentsSidebar: React.FC<AgentsSidebarProps> = ({ onItemSelect }) =>
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (window.confirm(`Are you sure you want to reset agent "${agent.name}" to its default configuration?`)) {
|
setConfirmActionAgent(agent);
|
||||||
const success = await deleteAgent(agent.name);
|
setConfirmActionType('reset');
|
||||||
if (success) {
|
};
|
||||||
toast.success(`Agent "${agent.name}" reset to default`);
|
|
||||||
} else {
|
const closeConfirmActionDialog = () => {
|
||||||
toast.error('Failed to reset agent');
|
setConfirmActionAgent(null);
|
||||||
}
|
setConfirmActionType(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleConfirmAction = async () => {
|
||||||
|
if (!confirmActionAgent || !confirmActionType) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setIsConfirmActionPending(true);
|
||||||
|
const success = await deleteAgent(confirmActionAgent.name);
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
if (confirmActionType === 'delete') {
|
||||||
|
toast.success(`Agent "${confirmActionAgent.name}" deleted successfully`);
|
||||||
|
} else {
|
||||||
|
toast.success(`Agent "${confirmActionAgent.name}" reset to default`);
|
||||||
|
}
|
||||||
|
closeConfirmActionDialog();
|
||||||
|
} else if (confirmActionType === 'delete') {
|
||||||
|
toast.error('Failed to delete agent');
|
||||||
|
} else {
|
||||||
|
toast.error('Failed to reset agent');
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsConfirmActionPending(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDuplicateAgent = (agent: Agent) => {
|
const handleDuplicateAgent = (agent: Agent) => {
|
||||||
@@ -367,6 +387,39 @@ export const AgentsSidebar: React.FC<AgentsSidebarProps> = ({ onItemSelect }) =>
|
|||||||
)}
|
)}
|
||||||
</ScrollableOverlay>
|
</ScrollableOverlay>
|
||||||
|
|
||||||
|
<Dialog
|
||||||
|
open={confirmActionAgent !== null && confirmActionType !== null}
|
||||||
|
onOpenChange={(open) => {
|
||||||
|
if (!open && !isConfirmActionPending) {
|
||||||
|
closeConfirmActionDialog();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<DialogContent className="max-w-md">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>{confirmActionType === 'delete' ? 'Delete Agent' : 'Reset Agent'}</DialogTitle>
|
||||||
|
<DialogDescription>
|
||||||
|
{confirmActionType === 'delete'
|
||||||
|
? `Are you sure you want to delete agent "${confirmActionAgent?.name}"?`
|
||||||
|
: `Are you sure you want to reset agent "${confirmActionAgent?.name}" to its default configuration?`}
|
||||||
|
</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
<DialogFooter>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
onClick={closeConfirmActionDialog}
|
||||||
|
disabled={isConfirmActionPending}
|
||||||
|
className="text-foreground hover:bg-interactive-hover hover:text-foreground"
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<ButtonLarge onClick={handleConfirmAction} disabled={isConfirmActionPending}>
|
||||||
|
{confirmActionType === 'delete' ? 'Delete' : 'Reset'}
|
||||||
|
</ButtonLarge>
|
||||||
|
</DialogFooter>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
|
||||||
{/* Rename Dialog */}
|
{/* Rename Dialog */}
|
||||||
<Dialog open={renameDialogAgent !== null} onOpenChange={(open) => !open && setRenameDialogAgent(null)}>
|
<Dialog open={renameDialogAgent !== null} onOpenChange={(open) => !open && setRenameDialogAgent(null)}>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
|
|||||||
@@ -32,6 +32,9 @@ interface CommandsSidebarProps {
|
|||||||
export const CommandsSidebar: React.FC<CommandsSidebarProps> = ({ onItemSelect }) => {
|
export const CommandsSidebar: React.FC<CommandsSidebarProps> = ({ onItemSelect }) => {
|
||||||
const [renameDialogCommand, setRenameDialogCommand] = React.useState<Command | null>(null);
|
const [renameDialogCommand, setRenameDialogCommand] = React.useState<Command | null>(null);
|
||||||
const [renameNewName, setRenameNewName] = React.useState('');
|
const [renameNewName, setRenameNewName] = React.useState('');
|
||||||
|
const [confirmActionCommand, setConfirmActionCommand] = React.useState<Command | null>(null);
|
||||||
|
const [confirmActionType, setConfirmActionType] = React.useState<'delete' | 'reset' | null>(null);
|
||||||
|
const [isConfirmActionPending, setIsConfirmActionPending] = React.useState(false);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
selectedCommandName,
|
selectedCommandName,
|
||||||
@@ -80,14 +83,8 @@ export const CommandsSidebar: React.FC<CommandsSidebarProps> = ({ onItemSelect }
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (window.confirm(`Are you sure you want to delete command "${command.name}"?`)) {
|
setConfirmActionCommand(command);
|
||||||
const success = await deleteCommand(command.name);
|
setConfirmActionType('delete');
|
||||||
if (success) {
|
|
||||||
toast.success(`Command "${command.name}" deleted successfully`);
|
|
||||||
} else {
|
|
||||||
toast.error('Failed to delete command');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleResetCommand = async (command: Command) => {
|
const handleResetCommand = async (command: Command) => {
|
||||||
@@ -95,14 +92,37 @@ export const CommandsSidebar: React.FC<CommandsSidebarProps> = ({ onItemSelect }
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (window.confirm(`Are you sure you want to reset command "${command.name}" to its default configuration?`)) {
|
setConfirmActionCommand(command);
|
||||||
const success = await deleteCommand(command.name);
|
setConfirmActionType('reset');
|
||||||
if (success) {
|
};
|
||||||
toast.success(`Command "${command.name}" reset to default`);
|
|
||||||
} else {
|
const closeConfirmActionDialog = () => {
|
||||||
toast.error('Failed to reset command');
|
setConfirmActionCommand(null);
|
||||||
}
|
setConfirmActionType(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleConfirmAction = async () => {
|
||||||
|
if (!confirmActionCommand || !confirmActionType) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setIsConfirmActionPending(true);
|
||||||
|
const success = await deleteCommand(confirmActionCommand.name);
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
if (confirmActionType === 'delete') {
|
||||||
|
toast.success(`Command "${confirmActionCommand.name}" deleted successfully`);
|
||||||
|
} else {
|
||||||
|
toast.success(`Command "${confirmActionCommand.name}" reset to default`);
|
||||||
|
}
|
||||||
|
closeConfirmActionDialog();
|
||||||
|
} else if (confirmActionType === 'delete') {
|
||||||
|
toast.error('Failed to delete command');
|
||||||
|
} else {
|
||||||
|
toast.error('Failed to reset command');
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsConfirmActionPending(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDuplicateCommand = (command: Command) => {
|
const handleDuplicateCommand = (command: Command) => {
|
||||||
@@ -264,6 +284,39 @@ export const CommandsSidebar: React.FC<CommandsSidebarProps> = ({ onItemSelect }
|
|||||||
)}
|
)}
|
||||||
</ScrollableOverlay>
|
</ScrollableOverlay>
|
||||||
|
|
||||||
|
<Dialog
|
||||||
|
open={confirmActionCommand !== null && confirmActionType !== null}
|
||||||
|
onOpenChange={(open) => {
|
||||||
|
if (!open && !isConfirmActionPending) {
|
||||||
|
closeConfirmActionDialog();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<DialogContent className="max-w-md">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>{confirmActionType === 'delete' ? 'Delete Command' : 'Reset Command'}</DialogTitle>
|
||||||
|
<DialogDescription>
|
||||||
|
{confirmActionType === 'delete'
|
||||||
|
? `Are you sure you want to delete command "${confirmActionCommand?.name}"?`
|
||||||
|
: `Are you sure you want to reset command "${confirmActionCommand?.name}" to its default configuration?`}
|
||||||
|
</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
<DialogFooter>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
onClick={closeConfirmActionDialog}
|
||||||
|
disabled={isConfirmActionPending}
|
||||||
|
className="text-foreground hover:bg-interactive-hover hover:text-foreground"
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<ButtonLarge onClick={handleConfirmAction} disabled={isConfirmActionPending}>
|
||||||
|
{confirmActionType === 'delete' ? 'Delete' : 'Reset'}
|
||||||
|
</ButtonLarge>
|
||||||
|
</DialogFooter>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
|
||||||
{/* Rename Dialog */}
|
{/* Rename Dialog */}
|
||||||
<Dialog open={renameDialogCommand !== null} onOpenChange={(open) => !open && setRenameDialogCommand(null)}>
|
<Dialog open={renameDialogCommand !== null} onOpenChange={(open) => !open && setRenameDialogCommand(null)}>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
|
|||||||
@@ -19,6 +19,14 @@ import {
|
|||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
|
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
|
||||||
import { ScrollableOverlay } from '@/components/ui/ScrollableOverlay';
|
import { ScrollableOverlay } from '@/components/ui/ScrollableOverlay';
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogDescription,
|
||||||
|
DialogFooter,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
} from '@/components/ui/dialog';
|
||||||
|
|
||||||
const PROFILE_COLORS = [
|
const PROFILE_COLORS = [
|
||||||
{ key: 'keyword', label: 'Green', cssVar: 'var(--syntax-keyword)' },
|
{ key: 'keyword', label: 'Green', cssVar: 'var(--syntax-keyword)' },
|
||||||
@@ -70,6 +78,8 @@ export const GitIdentitiesPage: React.FC = () => {
|
|||||||
const [color, setColor] = React.useState('keyword');
|
const [color, setColor] = React.useState('keyword');
|
||||||
const [icon, setIcon] = React.useState('branch');
|
const [icon, setIcon] = React.useState('branch');
|
||||||
const [isSaving, setIsSaving] = React.useState(false);
|
const [isSaving, setIsSaving] = React.useState(false);
|
||||||
|
const [isDeleteDialogOpen, setIsDeleteDialogOpen] = React.useState(false);
|
||||||
|
const [isDeleting, setIsDeleting] = React.useState(false);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (importData) {
|
if (importData) {
|
||||||
@@ -152,23 +162,31 @@ export const GitIdentitiesPage: React.FC = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDelete = async () => {
|
const handleDelete = () => {
|
||||||
if (!selectedProfileId || isNewProfile) return;
|
if (!selectedProfileId || isNewProfile) return;
|
||||||
|
|
||||||
if (!confirm('Are you sure you want to delete this profile?')) {
|
setIsDeleteDialogOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleConfirmDelete = async () => {
|
||||||
|
if (!selectedProfileId || isNewProfile) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setIsDeleting(true);
|
||||||
try {
|
try {
|
||||||
const success = await deleteProfile(selectedProfileId);
|
const success = await deleteProfile(selectedProfileId);
|
||||||
if (success) {
|
if (success) {
|
||||||
toast.success('Profile deleted successfully');
|
toast.success('Profile deleted successfully');
|
||||||
|
setIsDeleteDialogOpen(false);
|
||||||
} else {
|
} else {
|
||||||
toast.error('Failed to delete profile');
|
toast.error('Failed to delete profile');
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error deleting profile:', error);
|
console.error('Error deleting profile:', error);
|
||||||
toast.error('An error occurred while deleting');
|
toast.error('An error occurred while deleting');
|
||||||
|
} finally {
|
||||||
|
setIsDeleting(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -483,6 +501,32 @@ export const GitIdentitiesPage: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<Dialog
|
||||||
|
open={isDeleteDialogOpen}
|
||||||
|
onOpenChange={(open) => {
|
||||||
|
if (!isDeleting) {
|
||||||
|
setIsDeleteDialogOpen(open);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<DialogContent className="max-w-md">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>Delete Profile</DialogTitle>
|
||||||
|
<DialogDescription>
|
||||||
|
Are you sure you want to delete profile "{selectedProfile?.name || name || 'this profile'}"?
|
||||||
|
</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
<DialogFooter>
|
||||||
|
<Button variant="ghost" onClick={() => setIsDeleteDialogOpen(false)} disabled={isDeleting}>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button variant="destructive" onClick={() => void handleConfirmDelete()} disabled={isDeleting}>
|
||||||
|
Delete
|
||||||
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
</div>
|
</div>
|
||||||
</ScrollableOverlay>
|
</ScrollableOverlay>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,6 +1,14 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { toast } from '@/components/ui';
|
import { toast } from '@/components/ui';
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogDescription,
|
||||||
|
DialogFooter,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
} from '@/components/ui/dialog';
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
@@ -49,6 +57,9 @@ interface GitIdentitiesSidebarProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const GitIdentitiesSidebar: React.FC<GitIdentitiesSidebarProps> = ({ onItemSelect }) => {
|
export const GitIdentitiesSidebar: React.FC<GitIdentitiesSidebarProps> = ({ onItemSelect }) => {
|
||||||
|
const [deleteDialogProfile, setDeleteDialogProfile] = React.useState<GitIdentityProfile | null>(null);
|
||||||
|
const [isDeletePending, setIsDeletePending] = React.useState(false);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
selectedProfileId,
|
selectedProfileId,
|
||||||
defaultGitIdentityId,
|
defaultGitIdentityId,
|
||||||
@@ -99,14 +110,23 @@ export const GitIdentitiesSidebar: React.FC<GitIdentitiesSidebarProps> = ({ onIt
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleDeleteProfile = async (profile: GitIdentityProfile) => {
|
const handleDeleteProfile = async (profile: GitIdentityProfile) => {
|
||||||
if (window.confirm(`Are you sure you want to delete profile "${profile.name}"?`)) {
|
setDeleteDialogProfile(profile);
|
||||||
const success = await deleteProfile(profile.id);
|
};
|
||||||
if (success) {
|
|
||||||
toast.success(`Profile "${profile.name}" deleted successfully`);
|
const handleConfirmDeleteProfile = async () => {
|
||||||
} else {
|
if (!deleteDialogProfile) {
|
||||||
toast.error('Failed to delete profile');
|
return;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setIsDeletePending(true);
|
||||||
|
const success = await deleteProfile(deleteDialogProfile.id);
|
||||||
|
if (success) {
|
||||||
|
toast.success(`Profile "${deleteDialogProfile.name}" deleted successfully`);
|
||||||
|
setDeleteDialogProfile(null);
|
||||||
|
} else {
|
||||||
|
toast.error('Failed to delete profile');
|
||||||
|
}
|
||||||
|
setIsDeletePending(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleToggleDefault = async (profileId: string) => {
|
const handleToggleDefault = async (profileId: string) => {
|
||||||
@@ -216,6 +236,32 @@ export const GitIdentitiesSidebar: React.FC<GitIdentitiesSidebarProps> = ({ onIt
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</ScrollableOverlay>
|
</ScrollableOverlay>
|
||||||
|
|
||||||
|
<Dialog
|
||||||
|
open={deleteDialogProfile !== null}
|
||||||
|
onOpenChange={(open) => {
|
||||||
|
if (!open && !isDeletePending) {
|
||||||
|
setDeleteDialogProfile(null);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<DialogContent className="max-w-md">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>Delete Profile</DialogTitle>
|
||||||
|
<DialogDescription>
|
||||||
|
Are you sure you want to delete profile "{deleteDialogProfile?.name}"?
|
||||||
|
</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
<DialogFooter>
|
||||||
|
<Button variant="ghost" onClick={() => setDeleteDialogProfile(null)} disabled={isDeletePending}>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button variant="destructive" onClick={() => void handleConfirmDeleteProfile()} disabled={isDeletePending}>
|
||||||
|
Delete
|
||||||
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -91,6 +91,8 @@ export const SkillsPage: React.FC = () => {
|
|||||||
const [editingFilePath, setEditingFilePath] = React.useState<string | null>(null); // null = adding, string = editing
|
const [editingFilePath, setEditingFilePath] = React.useState<string | null>(null); // null = adding, string = editing
|
||||||
const [isLoadingFile, setIsLoadingFile] = React.useState(false);
|
const [isLoadingFile, setIsLoadingFile] = React.useState(false);
|
||||||
const [originalFileContent, setOriginalFileContent] = React.useState(''); // Track original for change detection
|
const [originalFileContent, setOriginalFileContent] = React.useState(''); // Track original for change detection
|
||||||
|
const [deleteFilePath, setDeleteFilePath] = React.useState<string | null>(null);
|
||||||
|
const [isDeletingFile, setIsDeletingFile] = React.useState(false);
|
||||||
|
|
||||||
// Detect if skill-level fields have changed
|
// Detect if skill-level fields have changed
|
||||||
const hasSkillChanges = isNewSkill
|
const hasSkillChanges = isNewSkill
|
||||||
@@ -306,7 +308,7 @@ export const SkillsPage: React.FC = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDeleteFile = async (filePath: string) => {
|
const handleDeleteFile = (filePath: string) => {
|
||||||
// For new skills, remove from pending files
|
// For new skills, remove from pending files
|
||||||
if (isNewSkill) {
|
if (isNewSkill) {
|
||||||
setPendingFiles(prev => prev.filter(f => f.path !== filePath));
|
setPendingFiles(prev => prev.filter(f => f.path !== filePath));
|
||||||
@@ -315,23 +317,34 @@ export const SkillsPage: React.FC = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// For existing skills, delete from disk
|
// For existing skills, delete from disk
|
||||||
if (!selectedSkillName) return;
|
if (!selectedSkillName) {
|
||||||
|
return;
|
||||||
if (window.confirm(`Are you sure you want to delete "${filePath}"?`)) {
|
|
||||||
const { deleteSupportingFile } = useSkillsStore.getState();
|
|
||||||
const success = await deleteSupportingFile(selectedSkillName, filePath);
|
|
||||||
|
|
||||||
if (success) {
|
|
||||||
toast.success(`File "${filePath}" deleted`);
|
|
||||||
// Refresh skill details
|
|
||||||
const detail = await getSkillDetail(selectedSkillName);
|
|
||||||
if (detail) {
|
|
||||||
setSupportingFiles(detail.sources.md.supportingFiles || []);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
toast.error('Failed to delete file');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setDeleteFilePath(filePath);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleConfirmDeleteFile = async () => {
|
||||||
|
if (!deleteFilePath || !selectedSkillName) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsDeletingFile(true);
|
||||||
|
const { deleteSupportingFile } = useSkillsStore.getState();
|
||||||
|
const success = await deleteSupportingFile(selectedSkillName, deleteFilePath);
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
toast.success(`File "${deleteFilePath}" deleted`);
|
||||||
|
const detail = await getSkillDetail(selectedSkillName);
|
||||||
|
if (detail) {
|
||||||
|
setSupportingFiles(detail.sources.md.supportingFiles || []);
|
||||||
|
}
|
||||||
|
setDeleteFilePath(null);
|
||||||
|
} else {
|
||||||
|
toast.error('Failed to delete file');
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsDeletingFile(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (isNewSkill && mode === 'external') {
|
if (isNewSkill && mode === 'external') {
|
||||||
@@ -554,6 +567,37 @@ export const SkillsPage: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Add/Edit File Dialog */}
|
{/* Add/Edit File Dialog */}
|
||||||
|
<Dialog
|
||||||
|
open={deleteFilePath !== null}
|
||||||
|
onOpenChange={(open) => {
|
||||||
|
if (!open && !isDeletingFile) {
|
||||||
|
setDeleteFilePath(null);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<DialogContent className="max-w-md">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>Delete Supporting File</DialogTitle>
|
||||||
|
<DialogDescription>
|
||||||
|
Are you sure you want to delete "{deleteFilePath}"?
|
||||||
|
</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
<DialogFooter>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
onClick={() => setDeleteFilePath(null)}
|
||||||
|
disabled={isDeletingFile}
|
||||||
|
className="text-foreground hover:bg-interactive-hover hover:text-foreground"
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<ButtonLarge onClick={handleConfirmDeleteFile} disabled={isDeletingFile}>
|
||||||
|
Delete
|
||||||
|
</ButtonLarge>
|
||||||
|
</DialogFooter>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
|
||||||
<Dialog open={isFileDialogOpen} onOpenChange={(open) => {
|
<Dialog open={isFileDialogOpen} onOpenChange={(open) => {
|
||||||
setIsFileDialogOpen(open);
|
setIsFileDialogOpen(open);
|
||||||
if (!open) setEditingFilePath(null);
|
if (!open) setEditingFilePath(null);
|
||||||
|
|||||||
@@ -32,6 +32,8 @@ interface SkillsSidebarProps {
|
|||||||
export const SkillsSidebar: React.FC<SkillsSidebarProps> = ({ onItemSelect }) => {
|
export const SkillsSidebar: React.FC<SkillsSidebarProps> = ({ onItemSelect }) => {
|
||||||
const [renameDialogSkill, setRenameDialogSkill] = React.useState<DiscoveredSkill | null>(null);
|
const [renameDialogSkill, setRenameDialogSkill] = React.useState<DiscoveredSkill | null>(null);
|
||||||
const [renameNewName, setRenameNewName] = React.useState('');
|
const [renameNewName, setRenameNewName] = React.useState('');
|
||||||
|
const [deleteDialogSkill, setDeleteDialogSkill] = React.useState<DiscoveredSkill | null>(null);
|
||||||
|
const [isDeletePending, setIsDeletePending] = React.useState(false);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
selectedSkillName,
|
selectedSkillName,
|
||||||
@@ -76,14 +78,23 @@ export const SkillsSidebar: React.FC<SkillsSidebarProps> = ({ onItemSelect }) =>
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleDeleteSkill = async (skill: DiscoveredSkill) => {
|
const handleDeleteSkill = async (skill: DiscoveredSkill) => {
|
||||||
if (window.confirm(`Are you sure you want to delete skill "${skill.name}"?`)) {
|
setDeleteDialogSkill(skill);
|
||||||
const success = await deleteSkill(skill.name);
|
};
|
||||||
if (success) {
|
|
||||||
toast.success(`Skill "${skill.name}" deleted successfully`);
|
const handleConfirmDeleteSkill = async () => {
|
||||||
} else {
|
if (!deleteDialogSkill) {
|
||||||
toast.error('Failed to delete skill');
|
return;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setIsDeletePending(true);
|
||||||
|
const success = await deleteSkill(deleteDialogSkill.name);
|
||||||
|
if (success) {
|
||||||
|
toast.success(`Skill "${deleteDialogSkill.name}" deleted successfully`);
|
||||||
|
setDeleteDialogSkill(null);
|
||||||
|
} else {
|
||||||
|
toast.error('Failed to delete skill');
|
||||||
|
}
|
||||||
|
setIsDeletePending(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDuplicateSkill = async (skill: DiscoveredSkill) => {
|
const handleDuplicateSkill = async (skill: DiscoveredSkill) => {
|
||||||
@@ -256,6 +267,37 @@ export const SkillsSidebar: React.FC<SkillsSidebarProps> = ({ onItemSelect }) =>
|
|||||||
)}
|
)}
|
||||||
</ScrollableOverlay>
|
</ScrollableOverlay>
|
||||||
|
|
||||||
|
<Dialog
|
||||||
|
open={deleteDialogSkill !== null}
|
||||||
|
onOpenChange={(open) => {
|
||||||
|
if (!open && !isDeletePending) {
|
||||||
|
setDeleteDialogSkill(null);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<DialogContent className="max-w-md">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>Delete Skill</DialogTitle>
|
||||||
|
<DialogDescription>
|
||||||
|
Are you sure you want to delete skill "{deleteDialogSkill?.name}"?
|
||||||
|
</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
<DialogFooter>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
onClick={() => setDeleteDialogSkill(null)}
|
||||||
|
disabled={isDeletePending}
|
||||||
|
className="text-foreground hover:bg-interactive-hover hover:text-foreground"
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<ButtonLarge onClick={handleConfirmDeleteSkill} disabled={isDeletePending}>
|
||||||
|
Delete
|
||||||
|
</ButtonLarge>
|
||||||
|
</DialogFooter>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
|
||||||
{/* Rename Dialog */}
|
{/* Rename Dialog */}
|
||||||
<Dialog open={renameDialogSkill !== null} onOpenChange={(open) => !open && setRenameDialogSkill(null)}>
|
<Dialog open={renameDialogSkill !== null} onOpenChange={(open) => !open && setRenameDialogSkill(null)}>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
|
|||||||
@@ -4,6 +4,14 @@ import { Button } from '@/components/ui/button';
|
|||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
import { ScrollableOverlay } from '@/components/ui/ScrollableOverlay';
|
import { ScrollableOverlay } from '@/components/ui/ScrollableOverlay';
|
||||||
import { AnimatedTabs } from '@/components/ui/animated-tabs';
|
import { AnimatedTabs } from '@/components/ui/animated-tabs';
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogDescription,
|
||||||
|
DialogFooter,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
} from '@/components/ui/dialog';
|
||||||
import {
|
import {
|
||||||
Select,
|
Select,
|
||||||
SelectContent,
|
SelectContent,
|
||||||
@@ -75,6 +83,7 @@ export const SkillsCatalogPage: React.FC<SkillsCatalogPageProps> = ({ mode, onMo
|
|||||||
const [installDialogOpen, setInstallDialogOpen] = React.useState(false);
|
const [installDialogOpen, setInstallDialogOpen] = React.useState(false);
|
||||||
const [installItem, setInstallItem] = React.useState<SkillsCatalogItem | null>(null);
|
const [installItem, setInstallItem] = React.useState<SkillsCatalogItem | null>(null);
|
||||||
const [isRemovingCatalog, setIsRemovingCatalog] = React.useState(false);
|
const [isRemovingCatalog, setIsRemovingCatalog] = React.useState(false);
|
||||||
|
const [isRemoveCatalogDialogOpen, setIsRemoveCatalogDialogOpen] = React.useState(false);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
void loadCatalog();
|
void loadCatalog();
|
||||||
@@ -118,10 +127,6 @@ export const SkillsCatalogPage: React.FC<SkillsCatalogPageProps> = ({ mode, onMo
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!window.confirm('Remove this catalog?')) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setIsRemovingCatalog(true);
|
setIsRemovingCatalog(true);
|
||||||
try {
|
try {
|
||||||
const settings = await loadSettings();
|
const settings = await loadSettings();
|
||||||
@@ -129,6 +134,7 @@ export const SkillsCatalogPage: React.FC<SkillsCatalogPageProps> = ({ mode, onMo
|
|||||||
const updated = catalogs.filter((c) => c.id !== selectedSourceId);
|
const updated = catalogs.filter((c) => c.id !== selectedSourceId);
|
||||||
await updateDesktopSettings({ skillCatalogs: updated });
|
await updateDesktopSettings({ skillCatalogs: updated });
|
||||||
await loadCatalog({ refresh: true });
|
await loadCatalog({ refresh: true });
|
||||||
|
setIsRemoveCatalogDialogOpen(false);
|
||||||
} finally {
|
} finally {
|
||||||
setIsRemovingCatalog(false);
|
setIsRemovingCatalog(false);
|
||||||
}
|
}
|
||||||
@@ -198,7 +204,7 @@ export const SkillsCatalogPage: React.FC<SkillsCatalogPageProps> = ({ mode, onMo
|
|||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
onClick={() => void removeSelectedCatalog()}
|
onClick={() => setIsRemoveCatalogDialogOpen(true)}
|
||||||
disabled={isRemovingCatalog}
|
disabled={isRemovingCatalog}
|
||||||
className="gap-2"
|
className="gap-2"
|
||||||
>
|
>
|
||||||
@@ -337,6 +343,33 @@ export const SkillsCatalogPage: React.FC<SkillsCatalogPageProps> = ({ mode, onMo
|
|||||||
|
|
||||||
<AddCatalogDialog open={addCatalogOpen} onOpenChange={setAddCatalogOpen} />
|
<AddCatalogDialog open={addCatalogOpen} onOpenChange={setAddCatalogOpen} />
|
||||||
<InstallSkillDialog open={installDialogOpen} onOpenChange={setInstallDialogOpen} item={installItem} />
|
<InstallSkillDialog open={installDialogOpen} onOpenChange={setInstallDialogOpen} item={installItem} />
|
||||||
|
<Dialog
|
||||||
|
open={isRemoveCatalogDialogOpen}
|
||||||
|
onOpenChange={(open) => {
|
||||||
|
if (!isRemovingCatalog) {
|
||||||
|
setIsRemoveCatalogDialogOpen(open);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<DialogContent className="max-w-md">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>Remove Catalog</DialogTitle>
|
||||||
|
<DialogDescription>Are you sure you want to remove this catalog?</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
<DialogFooter>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
onClick={() => setIsRemoveCatalogDialogOpen(false)}
|
||||||
|
disabled={isRemovingCatalog}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button variant="destructive" onClick={() => void removeSelectedCatalog()} disabled={isRemovingCatalog}>
|
||||||
|
Remove
|
||||||
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
</div>
|
</div>
|
||||||
</ScrollableOverlay>
|
</ScrollableOverlay>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user