File
Description
This component contains the graph toggles, the search bar and the button
for creating new components. Additionally it contains the actual graph component and feeds
data to it. This component collects the state of the search bar and graph toggles, combines it and emits it via this.filter$.
Another observable retrieved from the IssueGraphStateService maps these values into the graph
data matching the filters. Whenever new graph data arrives it is feed to the actual graph component. (see ngAfterViewInit)
Implements
Metadata
selector |
app-issue-graph-controls |
styleUrls |
./issue-graph-controls.component.scss |
templateUrl |
./issue-graph-controls.component.html |
Methods
Private
getSelectedCategories
|
getSelectedCategories()
|
|
Gathers booleans indicating whether the toggle switches
coressponding to values in IssueCategory are turned on or off
|
layoutGraph
|
layoutGraph()
|
|
|
setRelationVisibility
|
setRelationVisibility()
|
|
Tell the graph component whether to show issue relations or not.
|
Public
updateSelectedCategories
|
updateSelectedCategories()
|
|
Emit newly selected categories via this.selectedCategories$
|
Private
destroy$
|
Default value : new ReplaySubject<void>(1)
|
|
Public
dialog
|
Type : MatDialog
|
|
featureRequests
|
Default value : true
|
|
Public
selectedCategories$
|
Default value : new BehaviorSubject<SelectedCategories>(this.getSelectedCategories())
|
|
showRelations
|
Default value : true
|
|
unclassified
|
Default value : true
|
|
import {AfterViewInit, Component, OnDestroy, ViewChild} from '@angular/core';
import {MatDialog} from '@angular/material/dialog';
import {ActivatedRoute} from '@angular/router';
import {IssueGraphComponent} from '../issue-graph/issue-graph.component';
import {IssueCategory} from 'src/generated/graphql';
import {BehaviorSubject, combineLatest, ReplaySubject} from 'rxjs';
import {SelectedCategories} from '../shared';
import {IssueGraphStateService} from '../../data/issue-graph/issue-graph-state.service';
import {LabelSearchComponent} from '../label-search/label-search.component';
import {map, takeUntil} from 'rxjs/operators';
import {FilterState} from '@app/graphs/shared';
/**
* This component contains the graph toggles, the search bar and the button
* for creating new components. Additionally it contains the actual graph component and feeds
* data to it. This component collects the state of the search bar and graph toggles, combines it and emits it via this.filter$.
* Another observable retrieved from the IssueGraphStateService maps these values into the graph
* data matching the filters. Whenever new graph data arrives it is feed to the actual graph component. (see ngAfterViewInit)
*/
@Component({
selector: 'app-issue-graph-controls',
templateUrl: './issue-graph-controls.component.html',
styleUrls: ['./issue-graph-controls.component.scss']
})
export class IssueGraphControlsComponent implements AfterViewInit, OnDestroy {
@ViewChild(IssueGraphComponent) issueGraph: IssueGraphComponent;
@ViewChild(LabelSearchComponent) labelSearch: LabelSearchComponent;
projectId: string;
// these 3 booleans are bound to the issue category toggles via ngModel
featureRequests = true;
bug = true;
unclassified = true;
showRelations = true;
// emits state of toggles and search bar combined
filter$: BehaviorSubject<FilterState>;
private destroy$ = new ReplaySubject<void>(1);
constructor(public dialog: MatDialog, private gs: IssueGraphStateService, private route: ActivatedRoute) {
this.projectId = this.route.snapshot.paramMap.get('id');
this.filter$ = new BehaviorSubject({
selectedCategories: this.getSelectedCategories(),
selectedFilter: {
labels: [],
texts: []
}
});
}
public selectedCategories$ = new BehaviorSubject<SelectedCategories>(this.getSelectedCategories());
/**
* Emit newly selected categories via this.selectedCategories$
*/
public updateSelectedCategories(): void {
this.selectedCategories$.next(this.getSelectedCategories());
}
/**
* Gathers booleans indicating whether the toggle switches
* coressponding to values in IssueCategory are turned on or off
*/
private getSelectedCategories(): SelectedCategories {
return {
[IssueCategory.Bug]: this.bug,
[IssueCategory.FeatureRequest]: this.featureRequests,
[IssueCategory.Unclassified]: this.unclassified
};
}
layoutGraph(): void {
this.issueGraph.layoutGraph();
this.issueGraph.drawGraph();
this.issueGraph.fitGraphInView();
}
/**
* Setup this.filter$ and create subscription for observable returned from graphDataForFilter
*/
ngAfterViewInit(): void {
// sets up emission of values representing the state of the graph toggles and the search bar via this.filter$
combineLatest([this.selectedCategories$, this.labelSearch.filterSelection$])
.pipe(
takeUntil(this.destroy$),
map(([selectedCategories, filterSelection]) => ({
selectedCategories,
selectedFilter: filterSelection
}))
)
.subscribe((filterState) => this.filter$.next(filterState));
// gets an obervable from GraphStateService that emits the matching graph state
// after this component emits values on this.filter$ or the IssueGraphComponent
// signals the need for a reload via this.issueGraph.reload$. Whenever new graph state
// arrives we pass it to the graph and issue a redraw on it.
this.gs
.graphDataForFilter(this.filter$, this.issueGraph.reload$, this.destroy$)
.pipe(takeUntil(this.destroy$))
.subscribe((graphData) => {
this.issueGraph.graphData = graphData;
this.issueGraph.drawGraph();
});
}
/**
* Tell the graph component whether to show issue relations or not.
*
*/
setRelationVisibility(): void {
this.issueGraph.setRelationVisibility(this.showRelations);
}
/**
* Cancel subscriptions by emitting a value on this.destroy$
*/
ngOnDestroy() {
this.destroy$.next();
}
}
<!--Graph page controls-->
<div class="container">
<!--Controls-->
<div class="controls">
<!--Feature Requests toggle-->
<mat-slide-toggle
title="Feature Requests"
[(ngModel)]="featureRequests"
matTooltip="Feature Requests"
class="slide-toggle"
[checked]="true"
(change)="updateSelectedCategories()"
>
<mat-icon class="feature-request-icon">emoji_objects</mat-icon>
</mat-slide-toggle>
<!--Bug Reports toggle-->
<mat-slide-toggle
title="Bug Reports"
[(ngModel)]="bug"
matTooltip="Bug Reports"
class="slide-toggle"
[checked]="true"
(change)="updateSelectedCategories()"
>
<mat-icon class="bug-report-icon">bug_report</mat-icon>
</mat-slide-toggle>
<!--Unclassified Issues toggle-->
<mat-slide-toggle
title="Unclassified Issues"
[(ngModel)]="unclassified"
matTooltip="Unclassified Issues"
class="slide-toggle"
[checked]="true"
(change)="updateSelectedCategories()"
>
<mat-icon class="unclassified-icon">help</mat-icon>
</mat-slide-toggle>
<!--Issue Relations toggle-->
<mat-slide-toggle
title="Issue Relations"
[(ngModel)]="showRelations"
matTooltip="Issue Relations"
class="slide-toggle"
[checked]="true"
(change)="setRelationVisibility()"
>
<mat-icon class="relation-edge-icon" svgIcon="relation-edge"></mat-icon>
</mat-slide-toggle>
<!--Center Focus button-->
<button mat-icon-button title="Fit Graph Into View" (click)="this.issueGraph.fitGraphInView()" style="margin-left: 20px">
<mat-icon>center_focus_strong</mat-icon>
</button>
<!--Dashboard button-->
<button mat-icon-button title="Layout Graph" (click)="this.layoutGraph()" style="margin-left: 20px">
<mat-icon>dashboard</mat-icon>
</button>
<!--Search Label field-->
<app-label-search title="Label Search" #labelSearch></app-label-search>
<!--Create New Component button-->
<button
mat-fab
title="Create New Component"
color="primary"
class="create-component-button"
(click)="this.issueGraph.openCreateComponentDialog()"
>
<mat-icon>add</mat-icon>
</button>
</div>
<!--?-->
<app-issue-graph [projectId]="projectId"></app-issue-graph>
</div>
@import "~@angular/material/theming";
@import "variables";
.container {
display: flex;
flex-direction: column;
height: 100%;
width: 100%;
margin: 0;
}
.container app-issue-graph {
flex-grow: 1;
}
.controls {
display: flex;
flex-direction: row;
background-color: $background-controls;
border-bottom: 1px solid $border-color-controls;
}
.controls app-label-search {
display: inline-block;
height: 44px;
flex-grow: 1;
margin-left: 20px;
}
.slide-toggle {
margin-left: 20px;
margin-top: 10px;
margin-bottom: 10px;
}
.bug-report-icon {
color: red;
}
.feature-request-icon {
color: #005eff;
}
.feature-request-icon,
.bug-report-icon,
.notification-icon,
.unclassified-icon {
margin-top: 7px;
}
.relation-edge-icon {
margin-top: 12px;
}
.create-component-button {
margin: 10px;
margin-left: 20px;
right: 10px;
}
.spacer {
flex: 1 1 auto;
}
Legend
Html element with directive