|
@@ -1,5 +1,5 @@
|
|
|
-import { ArrowLeft, Save, Play, Plus, LayoutGrid, History, Pin, X, Cog, Loader2, ChevronDown } from 'lucide-react'
|
|
|
|
|
-import { useState, useRef, useEffect } from 'react'
|
|
|
|
|
|
|
+import { ArrowLeft, Save, Play, Plus, LayoutGrid, History, Pin, X, Cog, Loader2, ChevronDown, Pencil } from 'lucide-react'
|
|
|
|
|
+import { useState, useRef, useEffect, useCallback } from 'react'
|
|
|
import type { ExecutionDetail } from '../../api/workflows'
|
|
import type { ExecutionDetail } from '../../api/workflows'
|
|
|
|
|
|
|
|
interface TriggerNode {
|
|
interface TriggerNode {
|
|
@@ -29,6 +29,7 @@ interface EditorHeaderProps {
|
|
|
onUnpinExecution: () => void
|
|
onUnpinExecution: () => void
|
|
|
onShowSettings: () => void
|
|
onShowSettings: () => void
|
|
|
onToggleExecutionPanel: () => void
|
|
onToggleExecutionPanel: () => void
|
|
|
|
|
+ onRename?: (newName: string) => void
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
export function EditorHeader({
|
|
export function EditorHeader({
|
|
@@ -52,9 +53,13 @@ export function EditorHeader({
|
|
|
onUnpinExecution,
|
|
onUnpinExecution,
|
|
|
onShowSettings,
|
|
onShowSettings,
|
|
|
onToggleExecutionPanel,
|
|
onToggleExecutionPanel,
|
|
|
|
|
+ onRename,
|
|
|
}: EditorHeaderProps) {
|
|
}: EditorHeaderProps) {
|
|
|
const [showTriggerDropdown, setShowTriggerDropdown] = useState(false)
|
|
const [showTriggerDropdown, setShowTriggerDropdown] = useState(false)
|
|
|
|
|
+ const [isEditingName, setIsEditingName] = useState(false)
|
|
|
|
|
+ const [editingName, setEditingName] = useState(workflowName)
|
|
|
const dropdownRef = useRef<HTMLDivElement>(null)
|
|
const dropdownRef = useRef<HTMLDivElement>(null)
|
|
|
|
|
+ const nameInputRef = useRef<HTMLInputElement>(null)
|
|
|
|
|
|
|
|
// Close dropdown when clicking outside
|
|
// Close dropdown when clicking outside
|
|
|
useEffect(() => {
|
|
useEffect(() => {
|
|
@@ -67,6 +72,46 @@ export function EditorHeader({
|
|
|
return () => document.removeEventListener('mousedown', handleClickOutside)
|
|
return () => document.removeEventListener('mousedown', handleClickOutside)
|
|
|
}, [])
|
|
}, [])
|
|
|
|
|
|
|
|
|
|
+ // Focus input when editing starts
|
|
|
|
|
+ useEffect(() => {
|
|
|
|
|
+ if (isEditingName && nameInputRef.current) {
|
|
|
|
|
+ nameInputRef.current.focus()
|
|
|
|
|
+ nameInputRef.current.select()
|
|
|
|
|
+ }
|
|
|
|
|
+ }, [isEditingName])
|
|
|
|
|
+
|
|
|
|
|
+ // Update editing name when workflow name changes externally
|
|
|
|
|
+ useEffect(() => {
|
|
|
|
|
+ if (!isEditingName) {
|
|
|
|
|
+ setEditingName(workflowName)
|
|
|
|
|
+ }
|
|
|
|
|
+ }, [workflowName, isEditingName])
|
|
|
|
|
+
|
|
|
|
|
+ const handleNameClick = useCallback(() => {
|
|
|
|
|
+ if (onRename && !isViewingExecution) {
|
|
|
|
|
+ setIsEditingName(true)
|
|
|
|
|
+ }
|
|
|
|
|
+ }, [onRename, isViewingExecution])
|
|
|
|
|
+
|
|
|
|
|
+ const handleNameSubmit = useCallback(() => {
|
|
|
|
|
+ const trimmedName = editingName.trim()
|
|
|
|
|
+ if (trimmedName && trimmedName !== workflowName && onRename) {
|
|
|
|
|
+ onRename(trimmedName)
|
|
|
|
|
+ } else {
|
|
|
|
|
+ setEditingName(workflowName)
|
|
|
|
|
+ }
|
|
|
|
|
+ setIsEditingName(false)
|
|
|
|
|
+ }, [editingName, workflowName, onRename])
|
|
|
|
|
+
|
|
|
|
|
+ const handleNameKeyDown = useCallback((e: React.KeyboardEvent) => {
|
|
|
|
|
+ if (e.key === 'Enter') {
|
|
|
|
|
+ handleNameSubmit()
|
|
|
|
|
+ } else if (e.key === 'Escape') {
|
|
|
|
|
+ setEditingName(workflowName)
|
|
|
|
|
+ setIsEditingName(false)
|
|
|
|
|
+ }
|
|
|
|
|
+ }, [handleNameSubmit, workflowName])
|
|
|
|
|
+
|
|
|
const hasMultipleTriggers = triggers.length > 1
|
|
const hasMultipleTriggers = triggers.length > 1
|
|
|
|
|
|
|
|
return (
|
|
return (
|
|
@@ -78,7 +123,28 @@ export function EditorHeader({
|
|
|
>
|
|
>
|
|
|
<ArrowLeft className="w-5 h-5" />
|
|
<ArrowLeft className="w-5 h-5" />
|
|
|
</button>
|
|
</button>
|
|
|
- <h1 className="font-semibold text-gray-900 dark:text-gray-100">{workflowName}</h1>
|
|
|
|
|
|
|
+ {isEditingName ? (
|
|
|
|
|
+ <input
|
|
|
|
|
+ ref={nameInputRef}
|
|
|
|
|
+ type="text"
|
|
|
|
|
+ value={editingName}
|
|
|
|
|
+ onChange={(e) => setEditingName(e.target.value)}
|
|
|
|
|
+ onBlur={handleNameSubmit}
|
|
|
|
|
+ onKeyDown={handleNameKeyDown}
|
|
|
|
|
+ className="font-semibold text-gray-900 dark:text-gray-100 bg-transparent border-b-2 border-primary-500 outline-none px-1 min-w-[200px]"
|
|
|
|
|
+ />
|
|
|
|
|
+ ) : (
|
|
|
|
|
+ <h1
|
|
|
|
|
+ onClick={handleNameClick}
|
|
|
|
|
+ className={`font-semibold text-gray-900 dark:text-gray-100 ${onRename && !isViewingExecution ? 'cursor-pointer hover:text-primary-600 dark:hover:text-primary-400 group flex items-center gap-1' : ''}`}
|
|
|
|
|
+ title={onRename && !isViewingExecution ? 'Click to rename' : undefined}
|
|
|
|
|
+ >
|
|
|
|
|
+ {workflowName}
|
|
|
|
|
+ {onRename && !isViewingExecution && (
|
|
|
|
|
+ <Pencil className="w-3.5 h-3.5 opacity-0 group-hover:opacity-50 transition-opacity" />
|
|
|
|
|
+ )}
|
|
|
|
|
+ </h1>
|
|
|
|
|
+ )}
|
|
|
{hasChanges && (
|
|
{hasChanges && (
|
|
|
<span className="text-xs text-amber-600 dark:text-amber-400 bg-amber-50 dark:bg-amber-900/30 px-2 py-0.5 rounded">
|
|
<span className="text-xs text-amber-600 dark:text-amber-400 bg-amber-50 dark:bg-amber-900/30 px-2 py-0.5 rounded">
|
|
|
Unsaved changes (Ctrl+S)
|
|
Unsaved changes (Ctrl+S)
|