diff --git a/src/app/demo_page/demo_page.ng.html b/src/app/demo_page/demo_page.ng.html index 626727d..38b5d29 100644 --- a/src/app/demo_page/demo_page.ng.html +++ b/src/app/demo_page/demo_page.ng.html @@ -556,6 +556,30 @@ + +
+

+ Node selection +

+ + Node/Group + + + {{selectable.label}} + + + +
diff --git a/src/app/demo_page/demo_page.scss b/src/app/demo_page/demo_page.scss index a1535d8..116e12a 100644 --- a/src/app/demo_page/demo_page.scss +++ b/src/app/demo_page/demo_page.scss @@ -71,6 +71,9 @@ h2 { &.demo-select { width: 40%; } + &.demo-select-full-width { + width: 80%; + } } .demo-control-group { diff --git a/src/app/demo_page/demo_page.spec.ts b/src/app/demo_page/demo_page.spec.ts index 7ec7144..3196ea4 100644 --- a/src/app/demo_page/demo_page.spec.ts +++ b/src/app/demo_page/demo_page.spec.ts @@ -113,6 +113,21 @@ describe('Demo Page', () => { await screenShot.expectMatch('dark-mode'); }); }); + + describe('Node selection', () => { + it('Selects correct nested iteration loop node (screenshot)', async () => { + await harness.getNodeSelectInput().then( + select => select.clickOptions( + {text: 'Trainer (TensorFlow Training, it-1)'})); + await screenShot.expectMatch('select-nested-iteration-loop'); + }); + + it('Selects correct nested group node (screenshot)', async () => { + await harness.getNodeSelectInput().then( + select => select.clickOptions({text: 'Fake Exec 1 (sub1)'})); + await screenShot.expectMatch('select-nested-group'); + }) + }) }); diff --git a/src/app/demo_page/demo_page.ts b/src/app/demo_page/demo_page.ts index e2da9ae..3f0458e 100644 --- a/src/app/demo_page/demo_page.ts +++ b/src/app/demo_page/demo_page.ts @@ -20,6 +20,7 @@ import {HttpClientModule} from '@angular/common/http'; import {Component, ElementRef, NgModule, TemplateRef, ViewChild, ViewEncapsulation} from '@angular/core'; import {FormsModule, ReactiveFormsModule} from '@angular/forms'; import {MatButtonModule} from '@angular/material/button'; +import {MatOptionModule} from '@angular/material/core'; import {MatDialog, MatDialogModule} from '@angular/material/dialog'; import {MatDividerModule} from '@angular/material/divider'; import {MatFormFieldModule} from '@angular/material/form-field'; @@ -170,6 +171,32 @@ function translateColor(col: string, isBg = false) { } } +function getAllSelectableNestedNodesAndGroups( + params: {nodes: DagNode[], groups: DagGroup[]}, + path: string[] = []): Array { + const allNodesAndGroups = []; + const pathLabel = path.length ? ` (${path.join(', ')})` : ''; + for (const subGroup of params.groups) { + allNodesAndGroups.push( + {node: subGroup, path, label: `${subGroup.id}${pathLabel}`}); + if (!subGroup.treatAsLoop) { + allNodesAndGroups.push(...getAllSelectableNestedNodesAndGroups( + subGroup, [...path, subGroup.id])); + } else { + // We need to skip the iteration node itself, as it is not selectable + subGroup.groups.forEach((iterationGroup) => { + allNodesAndGroups.push(...getAllSelectableNestedNodesAndGroups( + iterationGroup, [...path, subGroup.id, iterationGroup.id])); + }); + } + } + for (const subNode of params.nodes) { + allNodesAndGroups.push( + {node: subNode, path, label: `${subNode.id}${pathLabel}`}); + } + return allNodesAndGroups; +} + /** Demo component for directed acyclic graph view. */ @Component({ standalone: false, @@ -237,6 +264,7 @@ export class DagDemoPage { userConfigChange = new Subject(); destroy = new Subject(); + allSelectableNodesAndGroups: Array = []; constructor(public dialog: MatDialog) { this.setCurrDataset(DEFAULT_DATASET, true); this.resetAll(); @@ -339,6 +367,8 @@ export class DagDemoPage { const newGraph = cloneGraph(this.datasets[name]); this.calibrateNodes(newGraph); this.currDataset = newGraph; + this.allSelectableNodesAndGroups = + getAllSelectableNestedNodesAndGroups(newGraph); } onDatasetChanged(event: Event) { @@ -531,6 +561,14 @@ export class DagDemoPage { this.destroy.next(); this.destroy.complete(); } + + triggerSelection(node: SelectedNode) { + this.selectedNode = node; + } + + selectedNodeComparator(a: SelectedNode, b: SelectedNode) { + return a.node.id === b.node.id && a.path.join('') === b.path.join(''); + } } @NgModule({ @@ -546,6 +584,7 @@ export class DagDemoPage { DagScaffoldModule, DagToolbarModule, MatSelectModule, + MatOptionModule, WorkflowGraphIconModule, MatDialogModule, FormsModule, diff --git a/src/app/demo_page/scuba_goldens/demo_page/chrome-linux/select-nested-group.png b/src/app/demo_page/scuba_goldens/demo_page/chrome-linux/select-nested-group.png new file mode 100644 index 0000000..32d26d7 Binary files /dev/null and b/src/app/demo_page/scuba_goldens/demo_page/chrome-linux/select-nested-group.png differ diff --git a/src/app/demo_page/scuba_goldens/demo_page/chrome-linux/select-nested-iteration-loop.png b/src/app/demo_page/scuba_goldens/demo_page/chrome-linux/select-nested-iteration-loop.png new file mode 100644 index 0000000..210c0f9 Binary files /dev/null and b/src/app/demo_page/scuba_goldens/demo_page/chrome-linux/select-nested-iteration-loop.png differ diff --git a/src/app/directed_acyclic_graph.ts b/src/app/directed_acyclic_graph.ts index d9811d6..60b33d5 100644 --- a/src/app/directed_acyclic_graph.ts +++ b/src/app/directed_acyclic_graph.ts @@ -29,7 +29,7 @@ import {ColorThemeLoader} from './color_theme_loader'; import {DagStateService} from './dag-state.service'; import {STATE_SERVICE_PROVIDER} from './dag-state.service.provider'; import {baseColors, BLUE_THEME, clampVal, CLASSIC_THEME, createDAGFeatures, createNewSizeConfig, type DagTheme, DEFAULT_LAYOUT_OPTIONS, DEFAULT_THEME, defaultFeatures, defaultZoomConfig, EdgeStyle, type FeatureToggleOptions, generateTheme, getMargin, isPoint, type LayoutOptions, type Logger, MarkerStyle, type MinimapPosition, nanSafePt, NODE_HEIGHT, NODE_WIDTH, NodeState, OrientationMarginConfig, RankAlignment, RankDirection, RankerAlgorithim, SCROLL_STEP_PER_DELTA, SizeConfig, SVG_ELEMENT_SIZE, type ZoomConfig} from './data_types_internal'; -import {DagRaw, DagRawModule, EnhancedDagGroup, GraphDims} from './directed_acyclic_graph_raw'; +import {DagRaw, DagRawModule, EnhancedDagGroup, GraphDims, setEnhancedGroupSelection} from './directed_acyclic_graph_raw'; import {DagLogger} from './logger/dag_logger'; import {Minimap, MinimapModule} from './minimap/minimap'; import {type DagEdge, DagGroup, DagNode, GraphSpec, GroupIterationRecord, GroupToggleEvent, isDagreInit, NodeMap, type NodeRef, Point, type SelectedNode} from './node_spec'; @@ -577,7 +577,9 @@ export class DirectedAcyclicGraph implements OnInit, OnDestroy { const loopGroup = parent?.$groups?.find(g => g.id === segment); if (loopGroup) { const selectedLoop = path.shift()!; - loopGroup.selectedLoopId = selectedLoop; + const selectedLoopNode = + loopGroup.groups.find(n => n.id === selectedLoop); + setEnhancedGroupSelection(loopGroup, selectedLoopNode!); // Increase pathDepth as we've consumed 2 path segments pathDepth++; return true; @@ -592,6 +594,8 @@ export class DirectedAcyclicGraph implements OnInit, OnDestroy { expandedStateChanges++; // Detect changes so groups can expand properly this.detectChanges(); + // Detect changes in new dagEl so QueryList is updated properly + dagEl.detectChanges(); } pathDepth++; } diff --git a/src/app/directed_acyclic_graph_raw.ng.html b/src/app/directed_acyclic_graph_raw.ng.html index 3e0fd10..2f0b03f 100644 --- a/src/app/directed_acyclic_graph_raw.ng.html +++ b/src/app/directed_acyclic_graph_raw.ng.html @@ -1,5 +1,5 @@