mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-28 09:23:08 +08:00
feat(commands): add create button and file browser to commands page
- Add "New Command" button to CommandsManagerPage header - Integrate CommandCreateDialog for creating/importing commands - Add file browser button to source path input in CommandCreateDialog - Uses FloatingFileBrowser component for file selection - Supports browsing project directory for .md command files - Add i18n keys for browse file button (en/zh) Users can now create commands via: 1. Import existing .md file (with visual file picker) 2. AI-generate new command from description
This commit is contained in:
@@ -16,6 +16,7 @@ import {
|
|||||||
XCircle,
|
XCircle,
|
||||||
Loader2,
|
Loader2,
|
||||||
Info,
|
Info,
|
||||||
|
FolderOpen,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
@@ -31,6 +32,7 @@ import { Textarea } from '@/components/ui/Textarea';
|
|||||||
import { Label } from '@/components/ui/Label';
|
import { Label } from '@/components/ui/Label';
|
||||||
import { validateCommandImport, createCommand } from '@/lib/api';
|
import { validateCommandImport, createCommand } from '@/lib/api';
|
||||||
import { useWorkflowStore, selectProjectPath } from '@/stores/workflowStore';
|
import { useWorkflowStore, selectProjectPath } from '@/stores/workflowStore';
|
||||||
|
import { FloatingFileBrowser } from '@/components/terminal-dashboard/FloatingFileBrowser';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
|
|
||||||
export interface CommandCreateDialogProps {
|
export interface CommandCreateDialogProps {
|
||||||
@@ -68,6 +70,9 @@ export function CommandCreateDialog({ open, onOpenChange, onCreated, cliType = '
|
|||||||
|
|
||||||
const [isCreating, setIsCreating] = useState(false);
|
const [isCreating, setIsCreating] = useState(false);
|
||||||
|
|
||||||
|
// File browser state
|
||||||
|
const [isFileBrowserOpen, setIsFileBrowserOpen] = useState(false);
|
||||||
|
|
||||||
const resetState = useCallback(() => {
|
const resetState = useCallback(() => {
|
||||||
setMode('import');
|
setMode('import');
|
||||||
setLocation('project');
|
setLocation('project');
|
||||||
@@ -78,6 +83,7 @@ export function CommandCreateDialog({ open, onOpenChange, onCreated, cliType = '
|
|||||||
setCommandName('');
|
setCommandName('');
|
||||||
setDescription('');
|
setDescription('');
|
||||||
setIsCreating(false);
|
setIsCreating(false);
|
||||||
|
setIsFileBrowserOpen(false);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleOpenChange = useCallback((open: boolean) => {
|
const handleOpenChange = useCallback((open: boolean) => {
|
||||||
@@ -250,6 +256,7 @@ export function CommandCreateDialog({ open, onOpenChange, onCreated, cliType = '
|
|||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="sourcePath">{formatMessage({ id: 'commands.create.sourcePath' })}</Label>
|
<Label htmlFor="sourcePath">{formatMessage({ id: 'commands.create.sourcePath' })}</Label>
|
||||||
|
<div className="flex gap-2">
|
||||||
<Input
|
<Input
|
||||||
id="sourcePath"
|
id="sourcePath"
|
||||||
value={sourcePath}
|
value={sourcePath}
|
||||||
@@ -258,8 +265,18 @@ export function CommandCreateDialog({ open, onOpenChange, onCreated, cliType = '
|
|||||||
setValidationResult(null);
|
setValidationResult(null);
|
||||||
}}
|
}}
|
||||||
placeholder={formatMessage({ id: 'commands.create.sourcePathPlaceholder' })}
|
placeholder={formatMessage({ id: 'commands.create.sourcePathPlaceholder' })}
|
||||||
className="font-mono text-sm"
|
className="font-mono text-sm flex-1"
|
||||||
/>
|
/>
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="outline"
|
||||||
|
size="icon"
|
||||||
|
onClick={() => setIsFileBrowserOpen(true)}
|
||||||
|
title={formatMessage({ id: 'commands.create.browseFile' })}
|
||||||
|
>
|
||||||
|
<FolderOpen className="w-4 h-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
<p className="text-xs text-muted-foreground">{formatMessage({ id: 'commands.create.sourcePathHint' })}</p>
|
<p className="text-xs text-muted-foreground">{formatMessage({ id: 'commands.create.sourcePathHint' })}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -401,6 +418,19 @@ export function CommandCreateDialog({ open, onOpenChange, onCreated, cliType = '
|
|||||||
</Button>
|
</Button>
|
||||||
</DialogFooter>
|
</DialogFooter>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
|
|
||||||
|
{/* File Browser for selecting source file */}
|
||||||
|
<FloatingFileBrowser
|
||||||
|
isOpen={isFileBrowserOpen}
|
||||||
|
onClose={() => setIsFileBrowserOpen(false)}
|
||||||
|
rootPath={projectPath}
|
||||||
|
onInsertPath={(path) => {
|
||||||
|
setSourcePath(path);
|
||||||
|
setValidationResult(null);
|
||||||
|
setIsFileBrowserOpen(false);
|
||||||
|
}}
|
||||||
|
initialSelectedPath={sourcePath || null}
|
||||||
|
/>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -74,6 +74,7 @@
|
|||||||
"sourcePath": "Source File Path",
|
"sourcePath": "Source File Path",
|
||||||
"sourcePathPlaceholder": "Enter absolute path to command file",
|
"sourcePathPlaceholder": "Enter absolute path to command file",
|
||||||
"sourcePathHint": "File must be a valid command markdown file",
|
"sourcePathHint": "File must be a valid command markdown file",
|
||||||
|
"browseFile": "Browse files",
|
||||||
"customName": "Custom Name",
|
"customName": "Custom Name",
|
||||||
"customNamePlaceholder": "Leave empty to use original name",
|
"customNamePlaceholder": "Leave empty to use original name",
|
||||||
"customNameHint": "Optional, overrides default command name",
|
"customNameHint": "Optional, overrides default command name",
|
||||||
|
|||||||
@@ -74,6 +74,7 @@
|
|||||||
"sourcePath": "源文件路径",
|
"sourcePath": "源文件路径",
|
||||||
"sourcePathPlaceholder": "输入命令文件的绝对路径",
|
"sourcePathPlaceholder": "输入命令文件的绝对路径",
|
||||||
"sourcePathHint": "文件必须是有效的命令 Markdown 文件",
|
"sourcePathHint": "文件必须是有效的命令 Markdown 文件",
|
||||||
|
"browseFile": "浏览文件",
|
||||||
"customName": "自定义名称",
|
"customName": "自定义名称",
|
||||||
"customNamePlaceholder": "留空则使用原始名称",
|
"customNamePlaceholder": "留空则使用原始名称",
|
||||||
"customNameHint": "可选,覆盖默认命令名称",
|
"customNameHint": "可选,覆盖默认命令名称",
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import {
|
|||||||
AlertCircle,
|
AlertCircle,
|
||||||
Maximize2,
|
Maximize2,
|
||||||
Minimize2,
|
Minimize2,
|
||||||
|
Plus,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { useAppStore, selectIsImmersiveMode } from '@/stores/appStore';
|
import { useAppStore, selectIsImmersiveMode } from '@/stores/appStore';
|
||||||
import { Card } from '@/components/ui/Card';
|
import { Card } from '@/components/ui/Card';
|
||||||
@@ -27,6 +28,7 @@ import { Badge } from '@/components/ui/Badge';
|
|||||||
import { TabsNavigation } from '@/components/ui/TabsNavigation';
|
import { TabsNavigation } from '@/components/ui/TabsNavigation';
|
||||||
import { useCommands, useCommandMutations } from '@/hooks';
|
import { useCommands, useCommandMutations } from '@/hooks';
|
||||||
import { CommandGroupAccordion } from '@/components/commands/CommandGroupAccordion';
|
import { CommandGroupAccordion } from '@/components/commands/CommandGroupAccordion';
|
||||||
|
import { CommandCreateDialog } from '@/components/shared/CommandCreateDialog';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
|
|
||||||
// ========== Main Page Component ==========
|
// ========== Main Page Component ==========
|
||||||
@@ -42,6 +44,8 @@ export function CommandsManagerPage() {
|
|||||||
const [expandedGroups, setExpandedGroups] = useState<Set<string>>(new Set(['cli', 'workflow']));
|
const [expandedGroups, setExpandedGroups] = useState<Set<string>>(new Set(['cli', 'workflow']));
|
||||||
// Search state
|
// Search state
|
||||||
const [searchQuery, setSearchQuery] = useState('');
|
const [searchQuery, setSearchQuery] = useState('');
|
||||||
|
// Create dialog state
|
||||||
|
const [isCreateDialogOpen, setIsCreateDialogOpen] = useState(false);
|
||||||
|
|
||||||
// Immersive mode state
|
// Immersive mode state
|
||||||
const isImmersiveMode = useAppStore(selectIsImmersiveMode);
|
const isImmersiveMode = useAppStore(selectIsImmersiveMode);
|
||||||
@@ -117,6 +121,10 @@ export function CommandsManagerPage() {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
|
<Button onClick={() => setIsCreateDialogOpen(true)} disabled={isToggling}>
|
||||||
|
<Plus className="w-4 h-4 mr-2" />
|
||||||
|
{formatMessage({ id: 'commands.actions.create' })}
|
||||||
|
</Button>
|
||||||
<button
|
<button
|
||||||
onClick={toggleImmersiveMode}
|
onClick={toggleImmersiveMode}
|
||||||
className={cn(
|
className={cn(
|
||||||
@@ -279,6 +287,16 @@ export function CommandsManagerPage() {
|
|||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Command Create Dialog */}
|
||||||
|
<CommandCreateDialog
|
||||||
|
open={isCreateDialogOpen}
|
||||||
|
onOpenChange={setIsCreateDialogOpen}
|
||||||
|
onCreated={() => {
|
||||||
|
setIsCreateDialogOpen(false);
|
||||||
|
refetch();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user