Skip to content

Commit 3c159b2

Browse files
committed
UI changes: added ability to collapse the preview along with adding responsiveness to some elements. Added other minor components for more robust UI.
Added a .yml preview which can be used as the job file to the UI and workflow zip. Tech debt: combined the toolMap.js and toolData.js files into a single API call to the .cwl file itself and then put the metadata into toolAnnotation.js.
1 parent cce57d0 commit 3c159b2

18 files changed

+4579
-5913
lines changed

public/cwl/toolMap.js

Lines changed: 0 additions & 4787 deletions
This file was deleted.

src/components/CWLPreviewPanel.jsx

Lines changed: 86 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useState, useEffect, useRef, useCallback } from 'react';
1+
import React, { useState, useEffect, useRef, useCallback, useMemo } from 'react';
22
import { Modal } from 'react-bootstrap';
33
import { buildCWLWorkflowObject, buildJobTemplate } from '../hooks/buildWorkflow.js';
44
import YAML from 'js-yaml';
@@ -34,6 +34,23 @@ function CWLPreviewPanel({ getWorkflowData }) {
3434
const [copied, setCopied] = useState(false);
3535
const debounceRef = useRef(null);
3636

37+
const [isCollapsed, setIsCollapsed] = useState(() => {
38+
try {
39+
const saved = localStorage.getItem('cwlPanelCollapsed');
40+
return saved === null ? true : JSON.parse(saved) === true;
41+
} catch {
42+
return true;
43+
}
44+
});
45+
46+
const toggleCollapse = useCallback(() => {
47+
setIsCollapsed(prev => {
48+
const next = !prev;
49+
localStorage.setItem('cwlPanelCollapsed', JSON.stringify(next));
50+
return next;
51+
});
52+
}, []);
53+
3754
useEffect(() => {
3855
if (debounceRef.current) clearTimeout(debounceRef.current);
3956

@@ -77,65 +94,80 @@ function CWLPreviewPanel({ getWorkflowData }) {
7794
}).catch(() => {});
7895
}, [activeContent]);
7996

80-
const highlightedHtml = activeContent ? highlightYaml(activeContent) : '';
97+
const highlightedHtml = useMemo(() => activeContent ? highlightYaml(activeContent) : '', [activeContent]);
8198

8299
return (
83100
<>
84-
<div className="cwl-preview-panel">
85-
<div className="cwl-preview-header">
86-
<div className="cwl-tab-bar">
87-
<button
88-
className={`cwl-tab${activeTab === 'workflow' ? ' active' : ''}`}
89-
onClick={() => setActiveTab('workflow')}
90-
>
91-
.cwl
92-
</button>
93-
<button
94-
className={`cwl-tab${activeTab === 'job' ? ' active' : ''}`}
95-
onClick={() => setActiveTab('job')}
96-
>
97-
.yml
98-
</button>
101+
<div className={`cwl-preview-panel${isCollapsed ? ' cwl-collapsed' : ''}`}>
102+
{isCollapsed ? (
103+
<div className="cwl-collapsed-strip" onClick={toggleCollapse} title="Expand CWL Preview">
104+
<span className="cwl-collapsed-label">CWL Preview</span>
99105
</div>
100-
<div className="cwl-preview-actions">
101-
<button
102-
className="cwl-action-btn"
103-
onClick={handleCopy}
104-
disabled={!activeContent}
105-
title="Copy to clipboard"
106-
>
107-
{copied ? 'Copied!' : 'Copy'}
108-
</button>
109-
<button
110-
className="cwl-action-btn"
111-
onClick={() => setShowFullscreen(true)}
112-
disabled={!activeContent}
113-
title="Expand to fullscreen"
114-
>
115-
Expand
116-
</button>
117-
</div>
118-
</div>
119-
120-
<div className="cwl-preview-body">
121-
{error && (
122-
<div className="cwl-error-banner">
123-
<span className="cwl-error-icon">!</span>
124-
<span>{error}</span>
106+
) : (
107+
<>
108+
<div className="cwl-preview-header">
109+
<div className="cwl-tab-bar">
110+
<button
111+
className={`cwl-tab${activeTab === 'workflow' ? ' active' : ''}`}
112+
onClick={() => setActiveTab('workflow')}
113+
>
114+
.cwl
115+
</button>
116+
<button
117+
className={`cwl-tab${activeTab === 'job' ? ' active' : ''}`}
118+
onClick={() => setActiveTab('job')}
119+
>
120+
.yml
121+
</button>
122+
</div>
123+
<div className="cwl-preview-actions">
124+
<button
125+
className="cwl-action-btn"
126+
onClick={handleCopy}
127+
disabled={!activeContent}
128+
title="Copy to clipboard"
129+
>
130+
{copied ? 'Copied!' : 'Copy'}
131+
</button>
132+
<button
133+
className="cwl-action-btn"
134+
onClick={() => setShowFullscreen(true)}
135+
disabled={!activeContent}
136+
title="Expand to fullscreen"
137+
>
138+
Expand
139+
</button>
140+
<button
141+
className="cwl-action-btn"
142+
onClick={toggleCollapse}
143+
title="Collapse panel"
144+
>
145+
&raquo;
146+
</button>
147+
</div>
125148
</div>
126-
)}
127-
{showPlaceholder && !error && (
128-
<div className="cwl-empty-message">
129-
Connect at least two nodes to preview the generated CWL workflow.
149+
150+
<div className="cwl-preview-body">
151+
{error && (
152+
<div className="cwl-error-banner">
153+
<span className="cwl-error-icon">!</span>
154+
<span>{error}</span>
155+
</div>
156+
)}
157+
{showPlaceholder && !error && (
158+
<div className="cwl-empty-message">
159+
Connect at least two nodes to preview the generated CWL workflow.
160+
</div>
161+
)}
162+
{activeContent && (
163+
<pre
164+
className="cwl-code"
165+
dangerouslySetInnerHTML={{ __html: highlightedHtml }}
166+
/>
167+
)}
130168
</div>
131-
)}
132-
{activeContent && (
133-
<pre
134-
className="cwl-code"
135-
dangerouslySetInnerHTML={{ __html: highlightedHtml }}
136-
/>
137-
)}
138-
</div>
169+
</>
170+
)}
139171
</div>
140172

141173
<Modal

src/components/EdgeMappingModal.jsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import React, { useState, useRef, useEffect } from 'react';
22
import { Modal, Button } from 'react-bootstrap';
3-
import { TOOL_MAP } from '../../public/cwl/toolMap.js';
3+
import { getToolConfigSync } from '../utils/toolRegistry.js';
44
import { parseExtensionsFromGlob, checkExtensionCompatibility } from '../utils/extensionValidation.js';
55
import { useToast } from '../context/ToastContext.jsx';
66
import '../styles/edgeMappingModal.css';
@@ -77,7 +77,7 @@ const getToolIO = (toolLabel, isDummy = false) => {
7777
isDummy: true
7878
};
7979
}
80-
const tool = TOOL_MAP[toolLabel];
80+
const tool = getToolConfigSync(toolLabel);
8181
if (tool) {
8282
return {
8383
outputs: Object.entries(tool.outputs).map(([name, def]) => ({
@@ -135,7 +135,7 @@ const EdgeMappingModal = ({
135135
const defaultMapping = [];
136136
if (sourceIO.outputs.length > 0 && targetIO.inputs.length > 0) {
137137
// For defined tools, use primaryOutputs if available
138-
const tool = TOOL_MAP[sourceNode?.label];
138+
const tool = getToolConfigSync(sourceNode?.label);
139139
const primaryOutput = tool?.primaryOutputs?.[0] || sourceIO.outputs[0].name;
140140
defaultMapping.push({
141141
sourceOutput: primaryOutput,

src/components/NodeComponent.jsx

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ import React, { useState, useMemo, useRef, useContext } from 'react';
22
import { createPortal } from 'react-dom';
33
import { Handle, Position } from 'reactflow';
44
import { Modal, Form } from 'react-bootstrap';
5-
import { TOOL_MAP, DOCKER_IMAGES } from '../../public/cwl/toolMap.js';
6-
import { DOCKER_TAGS, toolByName } from '../data/toolData.js';
5+
import { getToolConfigSync } from '../utils/toolRegistry.js';
6+
import { DOCKER_IMAGES, DOCKER_TAGS, annotationByName } from '../utils/toolAnnotations.js';
77
import { useToast } from '../context/ToastContext.jsx';
88
import TagDropdown from './TagDropdown.jsx';
99
import { ScatterPropagationContext } from '../context/ScatterPropagationContext.jsx';
@@ -23,8 +23,9 @@ const LIBRARY_MAP = {
2323
};
2424

2525
const getLibraryFromDockerImage = (dockerImage) => {
26+
const baseImage = dockerImage.split(':')[0];
2627
for (const [key, image] of Object.entries(DOCKER_IMAGES)) {
27-
if (image === dockerImage) {
28+
if (image === baseImage) {
2829
return LIBRARY_MAP[key] || null;
2930
}
3031
}
@@ -55,7 +56,7 @@ const NodeComponent = ({ data, id }) => {
5556
const infoIconRef = useRef(null);
5657

5758
// Get tool definition and optional inputs
58-
const tool = TOOL_MAP[data.label];
59+
const tool = getToolConfigSync(data.label);
5960
const optionalInputs = tool?.optionalInputs || {};
6061
const hasDefinedTool = !!tool;
6162
const dockerImage = tool?.dockerImage || null;
@@ -88,7 +89,7 @@ const NodeComponent = ({ data, id }) => {
8889
// Find tool info using pre-computed Map for O(1) lookup
8990
// (Previously O(L×C×T) triple-nested loop)
9091
const toolInfo = useMemo(() => {
91-
return toolByName.get(data.label) || null;
92+
return annotationByName.get(data.label) || null;
9293
}, [data.label]);
9394

9495
// Generate a helpful default JSON showing available optional parameters

src/components/modalityTooltip.jsx

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import React, { useState, useRef } from 'react';
2+
import { createPortal } from 'react-dom';
3+
import '../styles/workflowMenuItem.css';
4+
5+
function ModalityTooltip({ children, name, description }) {
6+
const [isHovered, setIsHovered] = useState(false);
7+
const [tooltipPos, setTooltipPos] = useState({ top: 0, left: 0 });
8+
const elementRef = useRef(null);
9+
10+
const handleMouseEnter = () => {
11+
if (elementRef.current) {
12+
const rect = elementRef.current.getBoundingClientRect();
13+
setTooltipPos({
14+
top: rect.top + rect.height / 2,
15+
left: rect.right + 10
16+
});
17+
}
18+
setIsHovered(true);
19+
};
20+
21+
const handleMouseLeave = () => {
22+
setIsHovered(false);
23+
};
24+
25+
return (
26+
<>
27+
<div
28+
ref={elementRef}
29+
onMouseEnter={handleMouseEnter}
30+
onMouseLeave={handleMouseLeave}
31+
>
32+
{children}
33+
</div>
34+
{isHovered && description && createPortal(
35+
<div
36+
className="workflow-tooltip"
37+
style={{
38+
top: tooltipPos.top,
39+
left: tooltipPos.left,
40+
transform: 'translateY(-50%)'
41+
}}
42+
>
43+
<div className="tooltip-section tooltip-fullname">
44+
<span className="tooltip-text">{name}</span>
45+
</div>
46+
<div className="tooltip-section">
47+
<span className="tooltip-label">Description</span>
48+
<span className="tooltip-text">{description}</span>
49+
</div>
50+
</div>,
51+
document.body
52+
)}
53+
</>
54+
);
55+
}
56+
57+
export default ModalityTooltip;

0 commit comments

Comments
 (0)