diff --git a/src/components/Learn/tours/navigatingEditor.tour.json b/src/components/Learn/tours/navigatingEditor.tour.json new file mode 100644 index 000000000..dfe02f4de --- /dev/null +++ b/src/components/Learn/tours/navigatingEditor.tour.json @@ -0,0 +1,110 @@ +{ + "id": "navigating-the-editor", + "displayName": "Guided Tour: Navigating the Editor", + "requiresEditor": true, + "starterPipelineUrl": "example-pipelines/Intro-Hello World.pipeline.component.yaml", + "steps": [ + { + "selector": "[data-tour-anchor=\"no-spotlight\"]", + "content": "Welcome to the pipeline editor! This quick tour will show you around so you know where to find everything: the menu bar, canvas, dockable panels, and floating windows.", + "position": "center" + }, + { + "selector": "[data-tour=\"editor-top-bar-left\"]", + "highlightedSelectors": [ + "[data-tour=\"editor-top-bar-left\"]", + "[data-tour=\"editor-menu-items\"]" + ], + "content": "The top bar holds your pipeline name and editor menus.\n\nEach menu groups one type of command: **File** for pipeline operations, **View** for layout presets, **Runs** for submissions, **Components** for your libraries, and **Windows** for panels. A **Node** menu also appears here when you have a task selected.", + "position": "bottom" + }, + { + "selector": "[data-tour=\"editor-top-bar-actions\"]", + "content": "Over on the right are your quick actions. Submit a run, check autosave status, jump to Settings, or open the documentation.", + "position": "bottom" + }, + { + "selector": "[data-tour=\"editor-canvas\"]", + "content": "This is your workspace. Drag components onto it, connect tasks by linking their input and output handles, and pan or zoom around larger graphs.\n\nUseful controls sit along the bottom: a **minimap** in the bottom-left, and **viewport controls** with **undo/redo** in the bottom-right.", + "position": "center", + "resizeObservables": ["[data-tour=\"editor-canvas\"]"] + }, + { + "selector": "[data-dock-area=\"left\"]", + "highlightedSelectors": [ + "[data-dock-window=\"component-library\"]", + "[data-dock-window-content=\"component-library\"]", + "[data-dock-window=\"runs-and-submission\"]", + "[data-dock-window-content=\"runs-and-submission\"]", + "[data-dock-window=\"recent-runs\"]", + "[data-dock-window-content=\"recent-runs\"]" + ], + "content": "The left sidebar holds your docked panels.\n\n**Components** lets you browse and drag tasks onto the canvas. **Runs and submission** lets you submit your pipeline, and **Recent runs** shows the latest runs of this pipeline.", + "position": "right", + "resizeObservables": ["[data-dock-area=\"left\"]"] + }, + { + "selector": "[data-dock-area=\"right\"]", + "content": "The right sidebar holds Pipeline Details and its properties. Set the pipeline description, tags, and notes here, and review any validation warnings.", + "resizeObservables": ["[data-dock-area=\"right\"]"] + }, + { + "selector": "[data-tour-node=\"task\"][data-task-name=\"Greet\"]", + "content": "Try clicking the **Greet** task on the canvas to select it.", + "position": "top", + "stepInteraction": true, + "interaction": "select-task" + }, + { + "selector": "[data-window-id=\"context-panel\"]", + "position": "left", + "highlightedSelectors": [ + "[data-window-id=\"context-panel\"]", + "[data-dock-window-content=\"context-panel\"]" + ], + "mutationObservables": [ + "[data-window-id=\"context-panel\"]", + "[data-dock-window-content=\"context-panel\"]" + ], + "resizeObservables": [ + "[data-window-id=\"context-panel\"]", + "[data-dock-window-content=\"context-panel\"]" + ], + "targetWindowId": "context-panel", + "content": "Selecting a task opens its Task Properties panel here in the right sidebar. From this panel you can edit input arguments, configure node settings, define annotations, and inspect the component spec." + }, + { + "selector": "[data-window-id=\"context-panel\"]", + "content": "Windows are flexible. Try grabbing the Task Properties header and dragging it out of the dock to float it as its own window.", + "position": "left", + "stepInteraction": true, + "interaction": "undock-window", + "targetWindowId": "context-panel", + "fallbackContent": "Windows are flexible. Try grabbing the Task Properties header and dragging it around the canvas." + }, + { + "selector": "[data-window-id=\"context-panel\"]", + "content": "Nice! Now drag that floating window back onto the left or right sidebar to re-dock it.", + "position": "left", + "stepInteraction": true, + "interaction": "redock-window", + "targetWindowId": "context-panel", + "fallbackContent": "Windows can be docked in either sidebar. Create the perfect layout that suits you!" + }, + { + "selector": "[data-tracking-id=\"v2.pipeline_editor.windows_menu\"]", + "highlightedSelectors": [ + "[data-tracking-id=\"v2.pipeline_editor.windows_menu\"]", + "[data-tour=\"windows-menu-content\"]", + "[data-tour=\"windows-menu-submenu-content\"]" + ], + "mutationObservables": [ + "[data-tour=\"windows-menu-content\"]", + "[data-tour=\"windows-menu-submenu-content\"]" + ], + "content": "Last one. If you ever need to reconfigure your layout, the Windows menu lets you toggle panels on or off and apply a layout preset.", + "position": "right", + "stepInteraction": true + } + ] +} diff --git a/src/routes/v2/pages/Editor/EditorV2.tsx b/src/routes/v2/pages/Editor/EditorV2.tsx index 573840d88..832f40bd0 100644 --- a/src/routes/v2/pages/Editor/EditorV2.tsx +++ b/src/routes/v2/pages/Editor/EditorV2.tsx @@ -105,7 +105,10 @@ const PipelineEditor = withSuspenseWrapper( data-testid="editor-v2" > -
+
)} - + @@ -172,6 +178,7 @@ export const EditorMenuBar = observer(function EditorMenuBar() { wrap="nowrap" blockAlign="center" className="shrink-0" + data-tour="editor-top-bar-actions" > {displayMenu && ( <> diff --git a/src/routes/v2/pages/Editor/components/EditorMenuBar/components/WindowsMenu.tsx b/src/routes/v2/pages/Editor/components/EditorMenuBar/components/WindowsMenu.tsx index c41556fcb..f56bdfd54 100644 --- a/src/routes/v2/pages/Editor/components/EditorMenuBar/components/WindowsMenu.tsx +++ b/src/routes/v2/pages/Editor/components/EditorMenuBar/components/WindowsMenu.tsx @@ -35,14 +35,34 @@ export const WindowsMenu = observer(function WindowsMenu() { windows.applyViewPreset(preset); }; + // Notify any listener (e.g. the Learning Hub tour) that the menu has + // opened or closed so they can re-measure highlight regions for portal-rendered + // dropdown content. On close we also wait past Radix's exit animation + // (~150ms zoom/fade out) before re-measuring, since the content is still + // mounted with non-zero bounds while animating away. + const notifyOpenStateChange = (open: boolean) => { + requestAnimationFrame(() => { + window.dispatchEvent(new Event("resize")); + }); + if (!open) { + setTimeout(() => { + window.dispatchEvent(new Event("resize")); + }, 250); + } + }; + return ( - + Windows - + {sortedWindows.map((win) => ( ))} - + Views - + {VIEW_PRESETS.map((preset) => ( { const target = event.target as Element | null; - if (target?.closest(".react-flow__node")) { + if (target?.closest('[data-tour-node="task"]')) { advance(); } }; diff --git a/src/routes/v2/pages/Editor/components/FlowCanvas/components/CanvasUndoRedo.tsx b/src/routes/v2/pages/Editor/components/FlowCanvas/components/CanvasUndoRedo.tsx index d62871fde..d3864ddba 100644 --- a/src/routes/v2/pages/Editor/components/FlowCanvas/components/CanvasUndoRedo.tsx +++ b/src/routes/v2/pages/Editor/components/FlowCanvas/components/CanvasUndoRedo.tsx @@ -27,7 +27,10 @@ export const CanvasUndoRedo = observer(function CanvasUndoRedo() { }; return ( -
+
diff --git a/src/routes/v2/shared/nodes/IONode/inputManifestBase.ts b/src/routes/v2/shared/nodes/IONode/inputManifestBase.ts index fb69a9f9d..af5fb77df 100644 --- a/src/routes/v2/shared/nodes/IONode/inputManifestBase.ts +++ b/src/routes/v2/shared/nodes/IONode/inputManifestBase.ts @@ -64,11 +64,17 @@ export const inputManifestBase: ManifestPartial = { buildNodes(spec) { return [...spec.inputs].map((input, index) => - createEntityNode(input, "input", ioDefaultPosition(index, -200), { - entityId: input.$id, - ioType: "input", - name: input.name, - } satisfies IONodeData), + createEntityNode( + input, + "input", + ioDefaultPosition(index, -200), + { + entityId: input.$id, + ioType: "input", + name: input.name, + } satisfies IONodeData, + { "data-task-name": input.name }, + ), ); }, diff --git a/src/routes/v2/shared/nodes/IONode/outputManifestBase.ts b/src/routes/v2/shared/nodes/IONode/outputManifestBase.ts index c3655dd9a..a544d8740 100644 --- a/src/routes/v2/shared/nodes/IONode/outputManifestBase.ts +++ b/src/routes/v2/shared/nodes/IONode/outputManifestBase.ts @@ -59,11 +59,17 @@ export const outputManifestBase: ManifestPartial = { buildNodes(spec) { return [...spec.outputs].map((output, index) => - createEntityNode(output, "output", ioDefaultPosition(index, 800), { - entityId: output.$id, - ioType: "output", - name: output.name, - } satisfies IONodeData), + createEntityNode( + output, + "output", + ioDefaultPosition(index, 800), + { + entityId: output.$id, + ioType: "output", + name: output.name, + } satisfies IONodeData, + { "data-task-name": output.name }, + ), ); }, diff --git a/src/routes/v2/shared/nodes/TaskNode/taskManifestBase.ts b/src/routes/v2/shared/nodes/TaskNode/taskManifestBase.ts index 423ff32ec..0e4b6ff13 100644 --- a/src/routes/v2/shared/nodes/TaskNode/taskManifestBase.ts +++ b/src/routes/v2/shared/nodes/TaskNode/taskManifestBase.ts @@ -72,10 +72,16 @@ export const taskManifestBase: ManifestPartial = { buildNodes(spec) { return [...spec.tasks].map((task, index) => - createEntityNode(task, "task", taskDefaultPosition(index), { - entityId: task.$id, - name: task.name, - } satisfies TaskNodeData), + createEntityNode( + task, + "task", + taskDefaultPosition(index), + { + entityId: task.$id, + name: task.name, + } satisfies TaskNodeData, + { "data-task-name": task.name, "data-tour-node": "task" }, + ), ); }, diff --git a/src/routes/v2/shared/nodes/buildUtils.ts b/src/routes/v2/shared/nodes/buildUtils.ts index abb3c83e3..bbc5849c3 100644 --- a/src/routes/v2/shared/nodes/buildUtils.ts +++ b/src/routes/v2/shared/nodes/buildUtils.ts @@ -47,6 +47,7 @@ export function createEntityNode( nodeType: string, fallback: { x: number; y: number }, data: Record, + domAttributes?: Record, ): Node { const position = entity.annotations.get(EDITOR_POSITION_ANNOTATION) as { x: number; @@ -59,6 +60,7 @@ export function createEntityNode( position: resolvePosition(position, fallback), zIndex, data, + domAttributes, }; } diff --git a/src/routes/v2/shared/windows/components/DockedWindow.tsx b/src/routes/v2/shared/windows/components/DockedWindow.tsx index 19811290a..7c4294488 100644 --- a/src/routes/v2/shared/windows/components/DockedWindow.tsx +++ b/src/routes/v2/shared/windows/components/DockedWindow.tsx @@ -134,6 +134,7 @@ export const DockedWindow = observer(function DockedWindow() {
- +