|
|
@@ -1,4 +1,4 @@
|
|
|
-import { ArrowLeft, Save, Play, Pause, Plus, LayoutGrid, History, Pin, X, Cog, Loader2, ChevronDown, Pencil, Copy, Undo2, Redo2, UploadCloud } from 'lucide-react'
|
|
|
+import { ArrowLeft, Play, Pause, Plus, LayoutGrid, History, Pin, X, Cog, Loader2, ChevronDown, Pencil, Copy, Undo2, Redo2, UploadCloud, Check, AlertTriangle } from 'lucide-react'
|
|
|
import { useState, useRef, useEffect, useCallback } from 'react'
|
|
|
import type { ExecutionDetail } from '../../api/workflows'
|
|
|
|
|
|
@@ -8,10 +8,12 @@ interface TriggerNode {
|
|
|
type: string
|
|
|
}
|
|
|
|
|
|
+export type SaveStatus = 'saved' | 'saving' | 'unsaved' | 'error'
|
|
|
+
|
|
|
interface EditorHeaderProps {
|
|
|
workflowName: string
|
|
|
hasChanges: boolean
|
|
|
- isSaving: boolean
|
|
|
+ saveStatus: SaveStatus
|
|
|
isRunning: boolean
|
|
|
isViewingExecution: boolean
|
|
|
executingNodeId: string | null
|
|
|
@@ -22,7 +24,6 @@ interface EditorHeaderProps {
|
|
|
isTogglingActive: boolean
|
|
|
triggers: TriggerNode[]
|
|
|
onNavigateBack: () => void
|
|
|
- onSave: () => void
|
|
|
onToggleActive: () => void
|
|
|
onExecute: () => void
|
|
|
onExecuteTrigger: (triggerNodeId: string) => void
|
|
|
@@ -48,10 +49,54 @@ interface EditorHeaderProps {
|
|
|
onRename?: (newName: string) => void
|
|
|
}
|
|
|
|
|
|
+// Small, quiet, and always accurate: this is the only signal left that work is
|
|
|
+// or isn't on disk once the Save button is gone. "Saved" stays low-key since
|
|
|
+// it's the common case; "Unsaved" and "Saving" get a touch more color since
|
|
|
+// they're the states worth noticing; "Save failed" is red on purpose so it is
|
|
|
+// never mistaken for "saved".
|
|
|
+function SaveIndicator({ status }: { status: SaveStatus }) {
|
|
|
+ switch (status) {
|
|
|
+ case 'saving':
|
|
|
+ return (
|
|
|
+ <span className="flex items-center gap-1 text-xs text-blue-600 dark:text-blue-400 bg-blue-50 dark:bg-blue-900/30 px-2 py-0.5 rounded">
|
|
|
+ <Loader2 className="w-3 h-3 animate-spin" />
|
|
|
+ Saving...
|
|
|
+ </span>
|
|
|
+ )
|
|
|
+ case 'unsaved':
|
|
|
+ return (
|
|
|
+ <span
|
|
|
+ className="flex items-center gap-1 text-xs text-amber-600 dark:text-amber-400 bg-amber-50 dark:bg-amber-900/30 px-2 py-0.5 rounded"
|
|
|
+ title="Autosave will save this shortly, or press Ctrl+S"
|
|
|
+ >
|
|
|
+ Unsaved changes
|
|
|
+ </span>
|
|
|
+ )
|
|
|
+ case 'error':
|
|
|
+ return (
|
|
|
+ <span
|
|
|
+ className="flex items-center gap-1 text-xs text-red-600 dark:text-red-400 bg-red-50 dark:bg-red-900/30 px-2 py-0.5 rounded"
|
|
|
+ title="The last save attempt failed - your edits are still on the canvas, but not yet on disk"
|
|
|
+ >
|
|
|
+ <AlertTriangle className="w-3 h-3" />
|
|
|
+ Save failed
|
|
|
+ </span>
|
|
|
+ )
|
|
|
+ case 'saved':
|
|
|
+ default:
|
|
|
+ return (
|
|
|
+ <span className="flex items-center gap-1 text-xs text-gray-400 dark:text-gray-500" title="Everything on the canvas is saved">
|
|
|
+ <Check className="w-3 h-3" />
|
|
|
+ Saved
|
|
|
+ </span>
|
|
|
+ )
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
export function EditorHeader({
|
|
|
workflowName,
|
|
|
hasChanges,
|
|
|
- isSaving,
|
|
|
+ saveStatus,
|
|
|
isRunning,
|
|
|
isViewingExecution,
|
|
|
executingNodeId,
|
|
|
@@ -62,7 +107,6 @@ export function EditorHeader({
|
|
|
isTogglingActive,
|
|
|
triggers,
|
|
|
onNavigateBack,
|
|
|
- onSave,
|
|
|
onToggleActive,
|
|
|
onExecute,
|
|
|
onExecuteTrigger,
|
|
|
@@ -146,14 +190,20 @@ export function EditorHeader({
|
|
|
|
|
|
const hasMultipleTriggers = triggers.length > 1
|
|
|
|
|
|
+ // Every control reachable while looking at a read-only execution snapshot
|
|
|
+ // gets the same treatment: disabled, with a tooltip explaining why, rather
|
|
|
+ // than appearing and vanishing as the mode changes.
|
|
|
+ const viewingReason = 'Not available while viewing a read-only execution snapshot'
|
|
|
+
|
|
|
return (
|
|
|
- <div className="h-14 px-4 flex items-center justify-between bg-white dark:bg-slate-800 border-b border-gray-200 dark:border-slate-700">
|
|
|
- <div className="flex items-center gap-4">
|
|
|
+ <div className="h-12 px-3 flex items-center justify-between bg-white dark:bg-slate-800 border-b border-gray-200 dark:border-slate-700 text-sm">
|
|
|
+ <div className="flex items-center gap-3 min-w-0">
|
|
|
<button
|
|
|
onClick={onNavigateBack}
|
|
|
- className="p-2 hover:bg-gray-100 dark:hover:bg-slate-700 rounded-lg text-gray-600 dark:text-gray-400"
|
|
|
+ className="p-1.5 hover:bg-gray-100 dark:hover:bg-slate-700 rounded-lg text-gray-600 dark:text-gray-400"
|
|
|
+ title="Back to workflows"
|
|
|
>
|
|
|
- <ArrowLeft className="w-5 h-5" />
|
|
|
+ <ArrowLeft className="w-4 h-4" />
|
|
|
</button>
|
|
|
{isEditingName ? (
|
|
|
<input
|
|
|
@@ -163,12 +213,12 @@ export function EditorHeader({
|
|
|
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]"
|
|
|
+ className="font-semibold text-gray-900 dark:text-gray-100 bg-transparent border-b-2 border-primary-500 outline-none px-1 min-w-[160px]"
|
|
|
/>
|
|
|
) : (
|
|
|
<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' : ''}`}
|
|
|
+ className={`font-semibold text-gray-900 dark:text-gray-100 truncate ${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}
|
|
|
@@ -177,11 +227,7 @@ export function EditorHeader({
|
|
|
)}
|
|
|
</h1>
|
|
|
)}
|
|
|
- {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">
|
|
|
- Unsaved changes (Ctrl+S)
|
|
|
- </span>
|
|
|
- )}
|
|
|
+ {!isViewingExecution && <SaveIndicator status={saveStatus} />}
|
|
|
{isRunning && (
|
|
|
<span className="flex items-center gap-1 text-xs text-blue-600 dark:text-blue-400 bg-blue-50 dark:bg-blue-900/30 px-2 py-0.5 rounded">
|
|
|
<Loader2 className="w-3 h-3 animate-spin" />
|
|
|
@@ -189,11 +235,12 @@ export function EditorHeader({
|
|
|
</span>
|
|
|
)}
|
|
|
</div>
|
|
|
- <div className="flex items-center gap-2">
|
|
|
+ <div className="flex items-center gap-1">
|
|
|
<button
|
|
|
onClick={onAddNode}
|
|
|
- className="flex items-center gap-2 px-3 py-1.5 text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-slate-700 rounded-lg"
|
|
|
+ className="flex items-center gap-1.5 px-2 py-1 text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-slate-700 rounded-lg disabled:opacity-40 disabled:cursor-not-allowed"
|
|
|
disabled={isViewingExecution}
|
|
|
+ title={isViewingExecution ? viewingReason : 'Add a node'}
|
|
|
>
|
|
|
<Plus className="w-4 h-4" />
|
|
|
Add Node
|
|
|
@@ -202,7 +249,7 @@ export function EditorHeader({
|
|
|
single-user install never sees it. */}
|
|
|
{myOtherTabs > 0 && (
|
|
|
<span
|
|
|
- className="px-2 py-0.5 text-xs rounded-full bg-gray-100 dark:bg-slate-700 text-gray-600 dark:text-gray-300 mr-1"
|
|
|
+ className="px-1.5 py-0.5 text-xs rounded-full bg-gray-100 dark:bg-slate-700 text-gray-600 dark:text-gray-300 mr-1"
|
|
|
title={`You have this workflow open in ${myOtherTabs + 1} places. Each one holds its own copy, so saving in one makes the others out of date.`}
|
|
|
>
|
|
|
{myOtherTabs + 1} tabs
|
|
|
@@ -216,60 +263,90 @@ export function EditorHeader({
|
|
|
{otherViewers.slice(0, 3).map((v) => (
|
|
|
<span
|
|
|
key={v.userId}
|
|
|
- className="w-6 h-6 rounded-full bg-indigo-500 text-white text-xs flex items-center justify-center ring-2 ring-white dark:ring-slate-800 uppercase"
|
|
|
+ className="w-5 h-5 rounded-full bg-indigo-500 text-white text-xs flex items-center justify-center ring-2 ring-white dark:ring-slate-800 uppercase"
|
|
|
>
|
|
|
{v.username.slice(0, 1)}
|
|
|
</span>
|
|
|
))}
|
|
|
{otherViewers.length > 3 && (
|
|
|
- <span className="w-6 h-6 rounded-full bg-gray-400 text-white text-xs flex items-center justify-center ring-2 ring-white dark:ring-slate-800">
|
|
|
+ <span className="w-5 h-5 rounded-full bg-gray-400 text-white text-xs flex items-center justify-center ring-2 ring-white dark:ring-slate-800">
|
|
|
+{otherViewers.length - 3}
|
|
|
</span>
|
|
|
)}
|
|
|
</div>
|
|
|
)}
|
|
|
|
|
|
- {/* Shown rather than left to the shortcut: an editor that can undo and
|
|
|
- does not say so is one nobody tries it in. */}
|
|
|
+ {/* Secondary controls: icon-only with a tooltip, to keep the bar from
|
|
|
+ crowding, since these are reached for far less often than Execute
|
|
|
+ or Publish. */}
|
|
|
<button
|
|
|
onClick={onUndo}
|
|
|
disabled={isViewingExecution || !canUndo}
|
|
|
- title="Undo the last change (Ctrl+Z)"
|
|
|
- className="flex items-center gap-2 px-2 py-1.5 text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-slate-700 rounded-lg disabled:opacity-40 disabled:cursor-not-allowed"
|
|
|
+ title={isViewingExecution ? viewingReason : 'Undo the last change (Ctrl+Z)'}
|
|
|
+ className="p-1.5 text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-slate-700 rounded-lg disabled:opacity-40 disabled:cursor-not-allowed"
|
|
|
>
|
|
|
<Undo2 className="w-4 h-4" />
|
|
|
</button>
|
|
|
<button
|
|
|
onClick={onRedo}
|
|
|
disabled={isViewingExecution || !canRedo}
|
|
|
- title="Redo (Ctrl+Shift+Z)"
|
|
|
- className="flex items-center gap-2 px-2 py-1.5 text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-slate-700 rounded-lg disabled:opacity-40 disabled:cursor-not-allowed"
|
|
|
+ title={isViewingExecution ? viewingReason : 'Redo (Ctrl+Shift+Z)'}
|
|
|
+ className="p-1.5 text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-slate-700 rounded-lg disabled:opacity-40 disabled:cursor-not-allowed"
|
|
|
>
|
|
|
<Redo2 className="w-4 h-4" />
|
|
|
</button>
|
|
|
- {/* Publishing is what decides which version triggers run, so it says
|
|
|
- plainly whether the stored workflow has moved past it. */}
|
|
|
<button
|
|
|
onClick={onShowVersions}
|
|
|
- className="flex items-center gap-2 px-3 py-1.5 text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-slate-700 rounded-lg"
|
|
|
- title="Version history"
|
|
|
+ className="p-1.5 text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-slate-700 rounded-lg disabled:opacity-40 disabled:cursor-not-allowed"
|
|
|
+ title={isViewingExecution ? viewingReason : 'Version history'}
|
|
|
disabled={isViewingExecution}
|
|
|
>
|
|
|
<History className="w-4 h-4" />
|
|
|
</button>
|
|
|
+ <button
|
|
|
+ onClick={onAutoLayout}
|
|
|
+ className="p-1.5 text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-slate-700 rounded-lg disabled:opacity-40 disabled:cursor-not-allowed"
|
|
|
+ title={isViewingExecution ? viewingReason : 'Auto-arrange nodes vertically'}
|
|
|
+ disabled={isViewingExecution}
|
|
|
+ >
|
|
|
+ <LayoutGrid className="w-4 h-4" />
|
|
|
+ </button>
|
|
|
+ <button
|
|
|
+ onClick={onDuplicate}
|
|
|
+ disabled={isDuplicating || isViewingExecution}
|
|
|
+ className="p-1.5 text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-slate-700 rounded-lg disabled:opacity-40 disabled:cursor-not-allowed"
|
|
|
+ title={isViewingExecution ? viewingReason : 'Create an inactive copy of this workflow and open it'}
|
|
|
+ >
|
|
|
+ {isDuplicating ? <Loader2 className="w-4 h-4 animate-spin" /> : <Copy className="w-4 h-4" />}
|
|
|
+ </button>
|
|
|
+ <button
|
|
|
+ onClick={onShowSettings}
|
|
|
+ disabled={isViewingExecution}
|
|
|
+ className="p-1.5 text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-slate-700 rounded-lg disabled:opacity-40 disabled:cursor-not-allowed"
|
|
|
+ title={isViewingExecution ? viewingReason : 'Workflow settings'}
|
|
|
+ >
|
|
|
+ <Cog className="w-4 h-4" />
|
|
|
+ </button>
|
|
|
+
|
|
|
+ <div className="w-px self-stretch my-2 bg-gray-200 dark:bg-slate-700 mx-1" />
|
|
|
+
|
|
|
+ {/* Publishing is what decides which version triggers run, so it says
|
|
|
+ plainly whether the stored workflow has moved past it. */}
|
|
|
<button
|
|
|
onClick={onPublish}
|
|
|
disabled={isViewingExecution || isPublishing || !hasUnpublishedChanges || hasChanges}
|
|
|
title={
|
|
|
- hasChanges
|
|
|
- ? 'Waiting for the current change to save - publishing sends what is stored, not what is on screen'
|
|
|
- : !hasUnpublishedChanges
|
|
|
- ? `Version ${publishedVersion} is published, and is exactly what triggers run`
|
|
|
- : publishedVersion === null
|
|
|
- ? 'Nothing published yet - publish to decide what triggers run'
|
|
|
- : `Publish what is stored now. Triggers currently run version ${publishedVersion}`
|
|
|
+ isViewingExecution
|
|
|
+ ? viewingReason
|
|
|
+ : hasChanges
|
|
|
+ ? 'Waiting for the current change to save - publishing sends what is stored, not what is on screen'
|
|
|
+ : !hasUnpublishedChanges
|
|
|
+ ? `Version ${publishedVersion} is published, and is exactly what triggers run`
|
|
|
+ : publishedVersion === null
|
|
|
+ ? 'Nothing published yet - publish to decide what triggers run'
|
|
|
+ : `Publish what is stored now. Triggers currently run version ${publishedVersion}`
|
|
|
}
|
|
|
- className={`flex items-center gap-2 px-3 py-1.5 rounded-lg ${
|
|
|
+ className={`flex items-center gap-1.5 px-2 py-1 rounded-lg ${
|
|
|
hasUnpublishedChanges && !hasChanges
|
|
|
? 'bg-green-600 text-white hover:bg-green-700'
|
|
|
: 'text-gray-500 dark:text-gray-400'
|
|
|
@@ -278,18 +355,9 @@ export function EditorHeader({
|
|
|
<UploadCloud className="w-4 h-4" />
|
|
|
{isPublishing ? 'Publishing...' : hasUnpublishedChanges ? 'Publish' : 'Published'}
|
|
|
</button>
|
|
|
- <button
|
|
|
- onClick={onAutoLayout}
|
|
|
- className="flex items-center gap-2 px-3 py-1.5 text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-slate-700 rounded-lg"
|
|
|
- title="Auto-arrange nodes vertically"
|
|
|
- disabled={isViewingExecution}
|
|
|
- >
|
|
|
- <LayoutGrid className="w-4 h-4" />
|
|
|
- Auto Layout
|
|
|
- </button>
|
|
|
<button
|
|
|
onClick={onShowExecutionList}
|
|
|
- className={`flex items-center gap-2 px-3 py-1.5 rounded-lg ${
|
|
|
+ className={`flex items-center gap-1.5 px-2 py-1 rounded-lg ${
|
|
|
pinnedExecution
|
|
|
? 'bg-green-100 dark:bg-green-900/30 text-green-700 dark:text-green-400'
|
|
|
: 'text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-slate-700'
|
|
|
@@ -298,47 +366,27 @@ export function EditorHeader({
|
|
|
>
|
|
|
<History className="w-4 h-4" />
|
|
|
Executions
|
|
|
- {pinnedExecution && (
|
|
|
- <span className="flex items-center gap-1 text-xs bg-green-200 dark:bg-green-800 px-1.5 py-0.5 rounded">
|
|
|
- <Pin className="w-3 h-3" />
|
|
|
- Pinned
|
|
|
- </span>
|
|
|
- )}
|
|
|
</button>
|
|
|
- {pinnedExecution && !isViewingExecution && (
|
|
|
+ {/* Clear pinned data: reachable in every mode where there is a pin to
|
|
|
+ clear, viewing included - it only clears the pin store, so there is
|
|
|
+ nothing about Viewing mode that makes it unsafe here. */}
|
|
|
+ {pinnedExecution && (
|
|
|
<button
|
|
|
onClick={onUnpinExecution}
|
|
|
- className="flex items-center gap-1 px-2 py-1.5 text-xs text-gray-500 dark:text-gray-400 hover:text-red-600 dark:hover:text-red-400 hover:bg-red-50 dark:hover:bg-red-900/20 rounded-lg"
|
|
|
+ className="flex items-center gap-1 px-1.5 py-1 text-xs text-green-700 dark:text-green-400 hover:text-red-600 dark:hover:text-red-400 hover:bg-red-50 dark:hover:bg-red-900/20 rounded-lg"
|
|
|
title="Clear pinned data"
|
|
|
>
|
|
|
+ <Pin className="w-3 h-3" />
|
|
|
<X className="w-3 h-3" />
|
|
|
- Clear
|
|
|
</button>
|
|
|
)}
|
|
|
- <button
|
|
|
- onClick={onDuplicate}
|
|
|
- disabled={isDuplicating}
|
|
|
- className="flex items-center gap-2 px-3 py-1.5 text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-slate-700 rounded-lg disabled:opacity-50"
|
|
|
- title="Create an inactive copy of this workflow and open it"
|
|
|
- >
|
|
|
- {isDuplicating ? <Loader2 className="w-4 h-4 animate-spin" /> : <Copy className="w-4 h-4" />}
|
|
|
- Duplicate
|
|
|
- </button>
|
|
|
- <button
|
|
|
- onClick={onShowSettings}
|
|
|
- className="flex items-center gap-2 px-3 py-1.5 text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-slate-700 rounded-lg"
|
|
|
- title="Workflow Settings"
|
|
|
- >
|
|
|
- <Cog className="w-4 h-4" />
|
|
|
- Settings
|
|
|
- </button>
|
|
|
<button
|
|
|
onClick={onToggleActive}
|
|
|
- disabled={isTogglingActive}
|
|
|
- title={isActive
|
|
|
+ disabled={isTogglingActive || isViewingExecution}
|
|
|
+ title={isViewingExecution ? viewingReason : isActive
|
|
|
? 'Stop running this workflow on its schedule'
|
|
|
: 'Run this workflow on its schedule'}
|
|
|
- className={`flex items-center gap-2 px-3 py-1.5 rounded-lg disabled:opacity-50 ${
|
|
|
+ className={`flex items-center gap-1.5 px-2 py-1 rounded-lg disabled:opacity-50 ${
|
|
|
isActive
|
|
|
? 'bg-green-100 dark:bg-green-900/30 text-green-700 dark:text-green-400 hover:bg-green-200 dark:hover:bg-green-900/50'
|
|
|
: 'bg-gray-100 dark:bg-slate-700 text-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-slate-600'
|
|
|
@@ -353,22 +401,15 @@ export function EditorHeader({
|
|
|
)}
|
|
|
{isActive ? 'Deactivate' : 'Activate'}
|
|
|
</button>
|
|
|
- <button
|
|
|
- onClick={onSave}
|
|
|
- disabled={!hasChanges || isSaving}
|
|
|
- className="flex items-center gap-2 px-3 py-1.5 bg-gray-100 dark:bg-slate-700 text-gray-700 dark:text-gray-300 rounded-lg hover:bg-gray-200 dark:hover:bg-slate-600 disabled:opacity-50"
|
|
|
- >
|
|
|
- <Save className="w-4 h-4" />
|
|
|
- {isSaving ? 'Saving...' : 'Save'}
|
|
|
- </button>
|
|
|
{/* Execute button - shows dropdown if multiple triggers */}
|
|
|
<div className="relative" ref={dropdownRef}>
|
|
|
{hasMultipleTriggers ? (
|
|
|
<>
|
|
|
<button
|
|
|
onClick={() => setShowTriggerDropdown(!showTriggerDropdown)}
|
|
|
- disabled={isRunning}
|
|
|
- className="flex items-center gap-2 px-3 py-1.5 bg-primary-600 text-white rounded-lg hover:bg-primary-700 disabled:opacity-50"
|
|
|
+ disabled={isRunning || isViewingExecution}
|
|
|
+ title={isViewingExecution ? `${viewingReason} - Execute would start a new run of the current graph, not the one shown here` : undefined}
|
|
|
+ className="flex items-center gap-1.5 px-2 py-1 bg-primary-600 text-white rounded-lg hover:bg-primary-700 disabled:opacity-50"
|
|
|
>
|
|
|
<Play className="w-4 h-4" />
|
|
|
Execute
|
|
|
@@ -401,8 +442,9 @@ export function EditorHeader({
|
|
|
) : (
|
|
|
<button
|
|
|
onClick={onExecute}
|
|
|
- disabled={isRunning}
|
|
|
- className="flex items-center gap-2 px-3 py-1.5 bg-primary-600 text-white rounded-lg hover:bg-primary-700 disabled:opacity-50"
|
|
|
+ disabled={isRunning || isViewingExecution}
|
|
|
+ title={isViewingExecution ? `${viewingReason} - Execute would start a new run of the current graph, not the one shown here` : undefined}
|
|
|
+ className="flex items-center gap-1.5 px-2 py-1 bg-primary-600 text-white rounded-lg hover:bg-primary-700 disabled:opacity-50"
|
|
|
>
|
|
|
<Play className="w-4 h-4" />
|
|
|
Execute
|
|
|
@@ -412,7 +454,7 @@ export function EditorHeader({
|
|
|
{hasExecutionData && (
|
|
|
<button
|
|
|
onClick={onToggleExecutionPanel}
|
|
|
- className={`flex items-center gap-2 px-3 py-1.5 rounded-lg ${
|
|
|
+ className={`flex items-center gap-1.5 px-2 py-1 rounded-lg ${
|
|
|
showExecutionPanel
|
|
|
? 'bg-blue-100 dark:bg-blue-900/30 text-blue-700 dark:text-blue-400'
|
|
|
: 'bg-gray-100 dark:bg-slate-700 text-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-slate-600'
|