|
@@ -418,9 +418,11 @@ function WorkflowEditorInner() {
|
|
|
pinnedNodeData,
|
|
pinnedNodeData,
|
|
|
nodeChangeStatus,
|
|
nodeChangeStatus,
|
|
|
isViewingExecution,
|
|
isViewingExecution,
|
|
|
|
|
+ isPinKept,
|
|
|
pinExecution,
|
|
pinExecution,
|
|
|
unpinExecution,
|
|
unpinExecution,
|
|
|
setViewingExecution,
|
|
setViewingExecution,
|
|
|
|
|
+ keepPin,
|
|
|
recomputeChangeStatus,
|
|
recomputeChangeStatus,
|
|
|
} = useExecutionPinStore()
|
|
} = useExecutionPinStore()
|
|
|
|
|
|
|
@@ -550,27 +552,42 @@ function WorkflowEditorInner() {
|
|
|
return { currentNodes, currentConnections }
|
|
return { currentNodes, currentConnections }
|
|
|
}, [nodes, edges])
|
|
}, [nodes, edges])
|
|
|
|
|
|
|
|
- // Handle viewing an execution in read-only mode
|
|
|
|
|
|
|
+ // Handle viewing an execution in read-only mode. Viewing is a temporary,
|
|
|
|
|
+ // read-only look at one past run - it borrows the pin slot to have
|
|
|
|
|
+ // something to show on the canvas, but it is not a "kept" pin: leaving
|
|
|
|
|
+ // Viewing without saying otherwise (see handleReturnToEditor) clears it,
|
|
|
|
|
+ // so a look at old history doesn't quietly leave something pinned behind.
|
|
|
const handleViewExecution = useCallback((execution: ExecutionDetail) => {
|
|
const handleViewExecution = useCallback((execution: ExecutionDetail) => {
|
|
|
const { currentNodes, currentConnections } = getCurrentWorkflowData()
|
|
const { currentNodes, currentConnections } = getCurrentWorkflowData()
|
|
|
- pinExecution(execution, currentNodes, currentConnections)
|
|
|
|
|
|
|
+ pinExecution(execution, currentNodes, currentConnections, false)
|
|
|
setViewingExecution(true) // Must be called AFTER pinExecution (which resets it to false)
|
|
setViewingExecution(true) // Must be called AFTER pinExecution (which resets it to false)
|
|
|
// Keep execution list open for continuous navigation
|
|
// Keep execution list open for continuous navigation
|
|
|
showToast('info', 'Viewing execution results')
|
|
showToast('info', 'Viewing execution results')
|
|
|
}, [getCurrentWorkflowData, pinExecution, setViewingExecution, showToast])
|
|
}, [getCurrentWorkflowData, pinExecution, setViewingExecution, showToast])
|
|
|
|
|
|
|
|
- // Handle pinning execution data
|
|
|
|
|
|
|
+ // Handle pinning execution data. This is the one way to bring a run's
|
|
|
|
|
+ // outputs onto the live canvas while continuing to edit - a deliberate,
|
|
|
|
|
+ // lasting choice, unlike Viewing.
|
|
|
const handlePinExecution = useCallback((execution: ExecutionDetail) => {
|
|
const handlePinExecution = useCallback((execution: ExecutionDetail) => {
|
|
|
const { currentNodes, currentConnections } = getCurrentWorkflowData()
|
|
const { currentNodes, currentConnections } = getCurrentWorkflowData()
|
|
|
- pinExecution(execution, currentNodes, currentConnections)
|
|
|
|
|
|
|
+ pinExecution(execution, currentNodes, currentConnections, true)
|
|
|
|
|
+ // Opens the results panel the same way Execute does, so every action
|
|
|
|
|
+ // that produces something worth looking at behaves the same way.
|
|
|
|
|
+ setShowExecutionPanel(true)
|
|
|
// Keep execution list open for continuous navigation
|
|
// Keep execution list open for continuous navigation
|
|
|
showToast('success', 'Execution data pinned')
|
|
showToast('success', 'Execution data pinned')
|
|
|
}, [getCurrentWorkflowData, pinExecution, showToast])
|
|
}, [getCurrentWorkflowData, pinExecution, showToast])
|
|
|
|
|
|
|
|
- // Return from viewing to editor
|
|
|
|
|
|
|
+ // Return from viewing to editor. A plain view leaves no residue: if the
|
|
|
|
|
+ // user never asked to keep this pin (via "Keep Pinned" in the banner),
|
|
|
|
|
+ // leaving clears it, so the header cannot end up showing "Pinned" for a
|
|
|
|
|
+ // pin nobody deliberately asked to keep.
|
|
|
const handleReturnToEditor = useCallback(() => {
|
|
const handleReturnToEditor = useCallback(() => {
|
|
|
setViewingExecution(false)
|
|
setViewingExecution(false)
|
|
|
- }, [setViewingExecution])
|
|
|
|
|
|
|
+ if (!isPinKept) {
|
|
|
|
|
+ unpinExecution()
|
|
|
|
|
+ }
|
|
|
|
|
+ }, [setViewingExecution, isPinKept, unpinExecution])
|
|
|
|
|
|
|
|
// The pinned/unchanged-vs-changed diff is computed once at pin time. If the
|
|
// The pinned/unchanged-vs-changed diff is computed once at pin time. If the
|
|
|
// user keeps editing after pinning (a node is live-editable in Pinned-not-
|
|
// user keeps editing after pinning (a node is live-editable in Pinned-not-
|
|
@@ -2838,9 +2855,12 @@ function WorkflowEditorInner() {
|
|
|
<ExecutionViewerBanner
|
|
<ExecutionViewerBanner
|
|
|
execution={pinnedExecution}
|
|
execution={pinnedExecution}
|
|
|
onReturnToEditor={handleReturnToEditor}
|
|
onReturnToEditor={handleReturnToEditor}
|
|
|
- onPinData={() => {
|
|
|
|
|
|
|
+ onKeepPinned={() => {
|
|
|
|
|
+ // The one way a view is promoted into a kept pin. Without this,
|
|
|
|
|
+ // returning to the editor would clear it (see handleReturnToEditor).
|
|
|
|
|
+ keepPin()
|
|
|
setViewingExecution(false)
|
|
setViewingExecution(false)
|
|
|
- showToast('success', 'Execution data pinned')
|
|
|
|
|
|
|
+ showToast('success', 'Kept - this run\'s outputs will stay on the canvas until you clear them')
|
|
|
}}
|
|
}}
|
|
|
/>
|
|
/>
|
|
|
)}
|
|
)}
|
|
@@ -2865,7 +2885,8 @@ function WorkflowEditorInner() {
|
|
|
isViewingExecution={isViewingExecution}
|
|
isViewingExecution={isViewingExecution}
|
|
|
executingNodeId={executingNodeId}
|
|
executingNodeId={executingNodeId}
|
|
|
pinnedExecution={pinnedExecution}
|
|
pinnedExecution={pinnedExecution}
|
|
|
- showExecutionPanel={showExecutionPanel || isViewingExecution || !!pinnedExecution}
|
|
|
|
|
|
|
+ isPinKept={isPinKept}
|
|
|
|
|
+ showExecutionPanel={showExecutionPanel || isViewingExecution}
|
|
|
hasExecutionData={!!(effectiveExecutionState.executionId || Object.keys(effectiveExecutionState.nodeStates).length > 0)}
|
|
hasExecutionData={!!(effectiveExecutionState.executionId || Object.keys(effectiveExecutionState.nodeStates).length > 0)}
|
|
|
isActive={!!workflow?.active}
|
|
isActive={!!workflow?.active}
|
|
|
isTogglingActive={toggleActiveMutation.isPending}
|
|
isTogglingActive={toggleActiveMutation.isPending}
|
|
@@ -2938,7 +2959,7 @@ function WorkflowEditorInner() {
|
|
|
// of the nodes it was copied from.
|
|
// of the nodes it was copied from.
|
|
|
pointerRef.current = reactFlowInstance.screenToFlowPosition({ x: e.clientX, y: e.clientY })
|
|
pointerRef.current = reactFlowInstance.screenToFlowPosition({ x: e.clientX, y: e.clientY })
|
|
|
}}
|
|
}}
|
|
|
- className={`flex-1 overflow-hidden ${(showExecutionPanel || isViewingExecution || pinnedExecution) ? 'w-2/3' : 'w-full'}`}>
|
|
|
|
|
|
|
+ className={`flex-1 overflow-hidden ${(showExecutionPanel || isViewingExecution) ? 'w-2/3' : 'w-full'}`}>
|
|
|
<ReactFlow
|
|
<ReactFlow
|
|
|
nodes={isViewingExecution && viewedNodes ? viewedNodes : nodes}
|
|
nodes={isViewingExecution && viewedNodes ? viewedNodes : nodes}
|
|
|
edges={isViewingExecution && viewedEdges ? viewedEdges : edges}
|
|
edges={isViewingExecution && viewedEdges ? viewedEdges : edges}
|
|
@@ -3035,7 +3056,7 @@ function WorkflowEditorInner() {
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
{/* Execution results panel */}
|
|
{/* Execution results panel */}
|
|
|
- {(showExecutionPanel || isViewingExecution || pinnedExecution) && (
|
|
|
|
|
|
|
+ {(showExecutionPanel || isViewingExecution) && (
|
|
|
<ExecutionResultsPanel
|
|
<ExecutionResultsPanel
|
|
|
executionState={effectiveExecutionState}
|
|
executionState={effectiveExecutionState}
|
|
|
nodes={isViewingExecution && viewedNodes ? viewedNodes : nodes}
|
|
nodes={isViewingExecution && viewedNodes ? viewedNodes : nodes}
|
|
@@ -3044,14 +3065,14 @@ function WorkflowEditorInner() {
|
|
|
selectedIterations={selectedIterations}
|
|
selectedIterations={selectedIterations}
|
|
|
isViewingExecution={isViewingExecution}
|
|
isViewingExecution={isViewingExecution}
|
|
|
onClose={() => {
|
|
onClose={() => {
|
|
|
- // Closing here means the same thing as the banner's "Return to
|
|
|
|
|
- // Editor": stop looking at the read-only snapshot, but leave the
|
|
|
|
|
- // pin alone. Clearing pinned data is a separate, deliberate
|
|
|
|
|
- // action (the "Clear" control), reachable in every mode where
|
|
|
|
|
- // something is pinned - not a side effect of closing this panel.
|
|
|
|
|
|
|
+ // Closing here means exactly the same thing as the banner's
|
|
|
|
|
+ // "Return to Editor" - both go through handleReturnToEditor, so
|
|
|
|
|
+ // the two closes cannot drift apart again. An ephemeral view
|
|
|
|
|
+ // pin is cleared; a kept one is left alone, same as leaving via
|
|
|
|
|
+ // the banner.
|
|
|
setShowExecutionPanel(false)
|
|
setShowExecutionPanel(false)
|
|
|
if (isViewingExecution) {
|
|
if (isViewingExecution) {
|
|
|
- setViewingExecution(false)
|
|
|
|
|
|
|
+ handleReturnToEditor()
|
|
|
}
|
|
}
|
|
|
}}
|
|
}}
|
|
|
onToggleNodeExpansion={toggleNodeExpansion}
|
|
onToggleNodeExpansion={toggleNodeExpansion}
|