diff --git a/src/components/Editor/Context/PipelineDetails.tsx b/src/components/Editor/Context/PipelineDetails.tsx index f24a7b595..bc7e1f506 100644 --- a/src/components/Editor/Context/PipelineDetails.tsx +++ b/src/components/Editor/Context/PipelineDetails.tsx @@ -1,16 +1,19 @@ import { useEffect, useState } from "react"; import { useValidationIssueNavigation } from "@/components/Editor/hooks/useValidationIssueNavigation"; +import TooltipButton from "@/components/shared/Buttons/TooltipButton"; +import { CodeViewer } from "@/components/shared/CodeViewer"; import { ActionBlock } from "@/components/shared/ContextPanel/Blocks/ActionBlock"; import { ContentBlock } from "@/components/shared/ContextPanel/Blocks/ContentBlock"; import { ListBlock } from "@/components/shared/ContextPanel/Blocks/ListBlock"; import { TextBlock } from "@/components/shared/ContextPanel/Blocks/TextBlock"; import { CopyText } from "@/components/shared/CopyText/CopyText"; -import { TaskImplementation } from "@/components/shared/TaskDetails"; +import { Icon } from "@/components/ui/icon"; import { BlockStack } from "@/components/ui/layout"; import { useComponentSpec } from "@/providers/ComponentSpecProvider"; import { getComponentFileFromList } from "@/utils/componentStore"; import { USER_PIPELINES_LIST_NAME } from "@/utils/constants"; +import { componentSpecToText } from "@/utils/yaml"; import PipelineIO from "../../shared/Execution/PipelineIO"; import { PipelineValidationList } from "./PipelineValidationList"; @@ -28,6 +31,8 @@ const PipelineDetails = () => { globalValidationIssues, ); + const [isYamlFullscreen, setIsYamlFullscreen] = useState(false); + // State for file metadata const [fileMeta, setFileMeta] = useState<{ creationTime?: Date; @@ -75,57 +80,70 @@ const PipelineDetails = () => { const actions = [ , - , + setIsYamlFullscreen(true)} + key="view-yaml-action" + > + + , ]; return ( - - - {componentSpec.name ?? "Unnamed Pipeline"} - - - - - - - {componentSpec.description && ( - - )} - - {digest && ( - + + + {componentSpec.name ?? "Unnamed Pipeline"} + + + + + + + {componentSpec.description && ( + + )} + + {digest && ( + + )} + + {annotations.length > 0 && ( + + )} + + + + + + + + {isYamlFullscreen && ( + setIsYamlFullscreen(false)} /> )} - - {annotations.length > 0 && ( - - )} - - - - - - - + ); }; diff --git a/src/components/PipelineRun/RunDetails.tsx b/src/components/PipelineRun/RunDetails.tsx index 6e94efa4b..be0d42ab4 100644 --- a/src/components/PipelineRun/RunDetails.tsx +++ b/src/components/PipelineRun/RunDetails.tsx @@ -1,4 +1,7 @@ +import { useState } from "react"; + import { CopyText } from "@/components/shared/CopyText/CopyText"; +import { Icon } from "@/components/ui/icon"; import { BlockStack, InlineStack } from "@/components/ui/layout"; import { Spinner } from "@/components/ui/spinner"; import { Paragraph, Text } from "@/components/ui/typography"; @@ -13,7 +16,10 @@ import { isStatusComplete, isStatusInProgress, } from "@/services/executionService"; +import { componentSpecToText } from "@/utils/yaml"; +import TooltipButton from "../shared/Buttons/TooltipButton"; +import { CodeViewer } from "../shared/CodeViewer"; import { ActionBlock, type ActionOrReactNode, @@ -24,7 +30,6 @@ import { TextBlock } from "../shared/ContextPanel/Blocks/TextBlock"; import PipelineIO from "../shared/Execution/PipelineIO"; import { InfoBox } from "../shared/InfoBox"; import { StatusBar, StatusText } from "../shared/Status"; -import { TaskImplementation } from "../shared/TaskDetails"; import { CancelPipelineRunButton } from "./components/CancelPipelineRunButton"; import { ClonePipelineButton } from "./components/ClonePipelineButton"; import { InspectPipelineButton } from "./components/InspectPipelineButton"; @@ -43,6 +48,8 @@ export const RunDetails = () => { } = useExecutionData(); const { data: currentUserDetails } = useUserDetails(); + const [isYamlFullscreen, setIsYamlFullscreen] = useState(false); + const editorRoute = componentSpec.name ? `/editor/${encodeURIComponent(componentSpec.name)}` : ""; @@ -95,11 +102,13 @@ export const RunDetails = () => { const actions: ActionOrReactNode[] = []; actions.push( - , + setIsYamlFullscreen(true)} + > + + , ); if (canAccessEditorSpec && componentSpec.name) { @@ -123,57 +132,68 @@ export const RunDetails = () => { } return ( - - - {componentSpec.name ?? "Unnamed Pipeline"} - - - - - {metadata && ( - - )} - - {componentSpec.description && ( - - )} - - - - - {runStatus} - - - - - - - {Object.keys(annotations).length > 0 && ( - ({ - label: key, - value: String(value), - }))} - marker="none" + <> + + + {componentSpec.name ?? "Unnamed Pipeline"} + + + + + {metadata && ( + + )} + + {componentSpec.description && ( + + )} + + + + + {runStatus} + + + + + + + {Object.keys(annotations).length > 0 && ( + ({ + label: key, + value: String(value), + }))} + marker="none" + /> + )} + + + + {isYamlFullscreen && ( + setIsYamlFullscreen(false)} /> )} - - - + ); }; diff --git a/src/components/shared/CodeViewer/CodeViewer.tsx b/src/components/shared/CodeViewer/CodeViewer.tsx index f138e667d..a3a8d1b0f 100644 --- a/src/components/shared/CodeViewer/CodeViewer.tsx +++ b/src/components/shared/CodeViewer/CodeViewer.tsx @@ -1,10 +1,8 @@ -import { FileCode2, Maximize2, X as XIcon } from "lucide-react"; -import { useEffect, useState } from "react"; +import { useEffect } from "react"; import { Button } from "@/components/ui/button"; -import { cn } from "@/lib/utils"; +import { Icon } from "@/components/ui/icon"; -import TooltipButton from "../Buttons/TooltipButton"; import { FullscreenElement } from "../FullscreenElement"; import CodeSyntaxHighlighter from "./CodeSyntaxHighlighter"; @@ -12,7 +10,9 @@ interface CodeViewerProps { code: string; language?: string; filename?: string; - showInlineContent?: boolean; + isFullscreen: boolean; + onClose: () => void; + onExpand?: () => void; } const DEFAULT_CODE_VIEWER_HEIGHT = 128; @@ -21,32 +21,14 @@ const CodeViewer = ({ code, language = "yaml", filename = "", - showInlineContent = true, + isFullscreen, + onClose, + onExpand, }: CodeViewerProps) => { - const [isFullscreen, setIsFullscreen] = useState(false); - const shouldRenderInlineCode = showInlineContent || isFullscreen; - - const handleEnterFullscreen = () => { - setIsFullscreen((prev) => !prev); - }; - - const compactButton = ( - - - - ); - useEffect(() => { const handleEscapeKey = (e: KeyboardEvent) => { if (isFullscreen && e.key === "Escape") { - setIsFullscreen(false); + onClose(); e.preventDefault(); e.stopPropagation(); } @@ -59,56 +41,43 @@ const CodeViewer = ({ }; }, [isFullscreen]); + const showFullscreenButton = (!isFullscreen && onExpand) || isFullscreen; + return ( -
- {shouldRenderInlineCode ? ( -
-
- - {filename} - - (Read Only) -
+
+
+
+ + {filename} + + (Read Only) +
+ {showFullscreenButton && ( + )} +
+
+
+
- ) : ( -
{compactButton}
- )} - {shouldRenderInlineCode && ( -
-
- -
-
- )} +
); diff --git a/src/components/shared/ReactFlow/FlowCanvas/TaskNode/TaskOverview/IOSection/IOCell/IOCodeViewer.tsx b/src/components/shared/ReactFlow/FlowCanvas/TaskNode/TaskOverview/IOSection/IOCell/IOCodeViewer.tsx index c3ec71e2d..39269b3b3 100644 --- a/src/components/shared/ReactFlow/FlowCanvas/TaskNode/TaskOverview/IOSection/IOCell/IOCodeViewer.tsx +++ b/src/components/shared/ReactFlow/FlowCanvas/TaskNode/TaskOverview/IOSection/IOCell/IOCodeViewer.tsx @@ -1,3 +1,5 @@ +import { useState } from "react"; + import { CodeViewer } from "@/components/shared/CodeViewer"; import { safeJsonParse } from "@/utils/string"; @@ -11,6 +13,8 @@ interface IOCodeViewerProps { } const IOCodeViewer = ({ title, value }: IOCodeViewerProps) => { + const [isFullScreen, setIsFullScreen] = useState(false); + const { parsed, isValidJson } = safeJsonParse(value); if (!isValidJson) { @@ -29,7 +33,14 @@ const IOCodeViewer = ({ title, value }: IOCodeViewerProps) => { return (
- + setIsFullScreen(false)} + onExpand={() => setIsFullScreen(true)} + />
); }; diff --git a/src/components/shared/ReactFlow/FlowCanvas/TaskNode/TaskOverview/TaskOverview.tsx b/src/components/shared/ReactFlow/FlowCanvas/TaskNode/TaskOverview/TaskOverview.tsx index 7bdb7d43c..840294d24 100644 --- a/src/components/shared/ReactFlow/FlowCanvas/TaskNode/TaskOverview/TaskOverview.tsx +++ b/src/components/shared/ReactFlow/FlowCanvas/TaskNode/TaskOverview/TaskOverview.tsx @@ -5,16 +5,15 @@ import { LogsIcon, Parentheses, } from "lucide-react"; +import { useState } from "react"; import type { TooltipButtonProps } from "@/components/shared/Buttons/TooltipButton"; import TooltipButton from "@/components/shared/Buttons/TooltipButton"; +import { CodeViewer } from "@/components/shared/CodeViewer"; import { ComponentDetailsDialog } from "@/components/shared/Dialogs"; import { ComponentFavoriteToggle } from "@/components/shared/FavoriteComponentToggle"; import { StatusIcon } from "@/components/shared/Status"; -import { - TaskDetails, - TaskImplementation, -} from "@/components/shared/TaskDetails"; +import { TaskDetails } from "@/components/shared/TaskDetails"; import { Icon } from "@/components/ui/icon"; import { BlockStack, InlineStack } from "@/components/ui/layout"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; @@ -22,6 +21,7 @@ import { Text } from "@/components/ui/typography"; import { useExecutionDataOptional } from "@/providers/ExecutionDataProvider"; import { type TaskNodeContextType } from "@/providers/TaskNodeProvider"; import { isGraphImplementation } from "@/utils/componentSpec"; +import { componentSpecToText } from "@/utils/yaml"; import ArgumentsSection from "../ArgumentsEditor/ArgumentsSection"; import ConfigurationSection from "./ConfigurationSection"; @@ -38,6 +38,8 @@ interface TaskOverviewProps { const TaskOverview = ({ taskNode, actions }: TaskOverviewProps) => { const { name, taskSpec, taskId, state, callbacks } = taskNode; + const [isYamlFullscreen, setIsYamlFullscreen] = useState(false); + const executionData = useExecutionDataOptional(); const details = executionData?.details; @@ -65,114 +67,130 @@ const TaskOverview = ({ taskNode, actions }: TaskOverviewProps) => { ...(actions?.map((action) => ( )) ?? []), - , + setIsYamlFullscreen(true)} + key="view-yaml-action" + > + + , ]; return ( - - - {isSubgraph && } - - {name} - - {canRename && } - - - {readOnly && } - - -
- - - - {readOnly ? ( - - ) : ( - - )} - {readOnly ? "Artifacts" : "Arguments"} - - - - Details - + <> + + + {isSubgraph && } + + {name} + + {canRename && } + + + {readOnly && } + - {readOnly && !isSubgraph && ( - - - Logs +
+ + + + {readOnly ? ( + + ) : ( + + )} + {readOnly ? "Artifacts" : "Arguments"} - )} - {!readOnly && ( - - - Configuration + + + Details - )} - - - - - - {!readOnly && ( - <> - -
- - - )} - {readOnly && ( - + + Logs + + )} + {!readOnly && ( + + + Configuration + + )} + + + - )} - - {readOnly && !isSubgraph && ( - - {!!executionId && ( -
- + + {!readOnly && ( + <> + -
+
+ + + )} + {readOnly && ( + )} - -
- )} - {!readOnly && ( - - - )} -
-
-
+ {readOnly && !isSubgraph && ( + + {!!executionId && ( +
+ +
+ )} + +
+ )} + {!readOnly && ( + + + + )} +
+
+
+ {isYamlFullscreen && ( + setIsYamlFullscreen(false)} + /> + )} + ); }; diff --git a/src/components/shared/ReactFlow/FlowCanvas/TaskNode/TaskOverview/logs.tsx b/src/components/shared/ReactFlow/FlowCanvas/TaskNode/TaskOverview/logs.tsx index fd028b843..d95d910e2 100644 --- a/src/components/shared/ReactFlow/FlowCanvas/TaskNode/TaskOverview/logs.tsx +++ b/src/components/shared/ReactFlow/FlowCanvas/TaskNode/TaskOverview/logs.tsx @@ -18,6 +18,9 @@ const LogDisplay = ({ system_error_exception_full?: string; }; }) => { + const [isLogsFullScreen, setIsLogsFullScreen] = useState(false); + const [isErrorsFullScreen, setIsErrorsFullScreen] = useState(false); + if (!logs.log_text && !logs.system_error_exception_full) { return
No logs available
; } @@ -36,6 +39,9 @@ const LogDisplay = ({ code={logs.log_text || ""} language="text" filename="Execution Logs" + isFullscreen={isLogsFullScreen} + onClose={() => setIsLogsFullScreen(false)} + onExpand={() => setIsLogsFullScreen(true)} />
)} @@ -45,6 +51,9 @@ const LogDisplay = ({ code={logs.system_error_exception_full || ""} language="text" filename="System Error Logs" + isFullscreen={isErrorsFullScreen} + onClose={() => setIsErrorsFullScreen(false)} + onExpand={() => setIsErrorsFullScreen(true)} />
)} diff --git a/src/components/shared/TaskDetails/Implementation.tsx b/src/components/shared/TaskDetails/Implementation.tsx index 1b5ba8199..0e92b7221 100644 --- a/src/components/shared/TaskDetails/Implementation.tsx +++ b/src/components/shared/TaskDetails/Implementation.tsx @@ -1,3 +1,5 @@ +import { useState } from "react"; + import { CodeViewer } from "@/components/shared/CodeViewer"; import type { ComponentSpec } from "@/utils/componentSpec"; import { componentSpecToText } from "@/utils/yaml"; @@ -5,14 +7,14 @@ import { componentSpecToText } from "@/utils/yaml"; interface TaskImplementationProps { displayName: string; componentSpec: ComponentSpec; - showInlineContent?: boolean; } const TaskImplementation = ({ displayName, componentSpec, - showInlineContent = true, }: TaskImplementationProps) => { + const [isFullscreen, setIsFullscreen] = useState(false); + const code = componentSpecToText(componentSpec); if (!componentSpec?.implementation) { @@ -28,7 +30,9 @@ const TaskImplementation = ({ code={code} language="yaml" filename={displayName} - showInlineContent={showInlineContent} + isFullscreen={isFullscreen} + onClose={() => setIsFullscreen(false)} + onExpand={() => setIsFullscreen(true)} /> ); };