Skip to content
This repository was archived by the owner on Oct 1, 2018. It is now read-only.

Commit a6985eb

Browse files
committed
operator navigation update
1 parent 90577f1 commit a6985eb

11 files changed

+159
-203
lines changed
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import { Directive, OnInit, OnDestroy, Output, EventEmitter, Inject } from '@angular/core';
2+
import { DOCUMENT } from '@angular/platform-browser';
3+
import { fromEvent } from 'rxjs/observable/fromEvent';
4+
import { Subscription } from 'rxjs/Subscription';
5+
import 'rxjs/add/operator/distinctUntilChanged';
6+
import 'rxjs/add/operator/map';
7+
8+
/*
9+
* Modified version of Material Design Docs ToC
10+
*/
11+
interface OperatorHeader {
12+
id: string;
13+
active: boolean;
14+
name: string;
15+
top: number;
16+
}
17+
18+
@Directive({
19+
selector: '[appOperatorScroll]'
20+
})
21+
export class OperatorScrollDirective implements OnInit, OnDestroy {
22+
@Output() activeOperator = new EventEmitter<string>();
23+
24+
private _headers: OperatorHeader[] = [];
25+
private _scrollSubscription: Subscription;
26+
private _scrollContainer: any;
27+
private readonly scrollContainerSelector = '.mat-drawer-content';
28+
private readonly headerSelector = '.operator-header';
29+
30+
constructor(
31+
@Inject(DOCUMENT) private _document: Document
32+
) {}
33+
34+
/** Gets the scroll offset of the scroll container */
35+
private getScrollOffset(): number {
36+
const {top} = this._scrollContainer.getBoundingClientRect();
37+
if (typeof this._scrollContainer.scrollTop !== 'undefined') {
38+
return this._scrollContainer.scrollTop + top;
39+
} else if (typeof this._scrollContainer.pageYOffset !== 'undefined') {
40+
return this._scrollContainer.pageYOffset + top;
41+
}
42+
}
43+
44+
private createHeaderLinks(): OperatorHeader[] {
45+
const links: OperatorHeader[] = [];
46+
const headers =
47+
Array.from(this._document.querySelectorAll(this.headerSelector)) as HTMLElement[];
48+
49+
if (headers.length) {
50+
for (const header of headers) {
51+
const name = header.id;
52+
const { top } = header.getBoundingClientRect();
53+
links.push({
54+
name,
55+
top: top,
56+
id: name,
57+
active: false
58+
});
59+
}
60+
}
61+
62+
return links;
63+
}
64+
65+
private determineActiveOperator(): string {
66+
// Use find to break out as soon as we find active header
67+
const { name } = this._headers
68+
.find((h, i) => this.isHeaderActive(this._headers[i], this._headers[i + 1]));
69+
70+
return name;
71+
}
72+
73+
private isHeaderActive(currentLink: any, nextLink: any): boolean {
74+
// switch slightly early to accomodate scrollIntoView from sidemenu
75+
const scrollOffset = this.getScrollOffset() + 5;
76+
return scrollOffset >= currentLink.top && !(nextLink && nextLink.top < scrollOffset);
77+
}
78+
79+
ngOnInit(): void {
80+
// On init, the sidenav content element doesn't yet exist, so it's not possible
81+
// to subscribe to its scroll event until next tick (when it does exist).
82+
Promise.resolve().then(() => {
83+
this._headers = this.createHeaderLinks();
84+
this._scrollContainer = this.scrollContainerSelector ?
85+
document.querySelectorAll(this.scrollContainerSelector)[0] : window;
86+
87+
this._scrollSubscription = fromEvent(this._scrollContainer, 'scroll')
88+
.map(_ => this.determineActiveOperator())
89+
.distinctUntilChanged()
90+
.subscribe((name: string) => this.activeOperator.emit(name));
91+
});
92+
}
93+
94+
ngOnDestroy(): void {
95+
this._scrollSubscription.unsubscribe();
96+
}
97+
}

src/app/operators/operator-toc/operator-toc.component.html

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

src/app/operators/operator-toc/operator-toc.component.scss

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

src/app/operators/operator-toc/operator-toc.component.ts

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

src/app/operators/operator/operator-header/operator-header.component.scss

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
.operator-name {
2-
font-size:26px;
2+
font-size:30px;
33
}
44

55
mat-toolbar {

src/app/operators/operator/operator.component.html

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
<app-operator-header
22
[operatorName]="operatorName"
3-
[operatorSignature]="signature">
3+
[operatorSignature]="signature"
4+
[id]="operatorName"
5+
class="operator-header">
46
</app-operator-header>
57
<div class="main-operator-container">
68

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
<mat-sidenav-container class="operator-container">
1+
<mat-sidenav-container
2+
class="operator-container"
3+
appOperatorScroll
4+
(activeOperator)="updateUrl($event)">
25
<mat-sidenav
36
mode="side"
47
[opened]="true"
@@ -9,12 +12,15 @@
912
<h3 mat-subheader class="category-subheader">{{ category }}</h3>
1013
<a mat-list-item
1114
*ngFor="let operator of groupedOperators[category]"
12-
[routerLink]="[ '/operators', { name: operator.name } ]">
15+
(click)="scrollToOperator(operator.name)"
16+
[routerLink]="[ '/operators', { name: operator.name } ]"
17+
[class.active-operator]="(activeOperator$ | async) === operator.name">
1318
{{ operator.name }}
1419
</a>
1520
</mat-nav-list>
1621
</mat-sidenav>
17-
<!-- just until I get display worked out, focus on one -->
18-
<app-operator *ngFor="let operator of operators" [operator]="operator"></app-operator>
19-
<app-operator-toc container=".mat-drawer-content"></app-operator-toc>
22+
<app-operator
23+
*ngFor="let operator of operators"
24+
[operator]="operator">
25+
</app-operator>
2026
</mat-sidenav-container>

src/app/operators/operators.component.scss

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
$subheader-color: #3f51b5;
1+
$subheader-color: #333;
2+
$operator-border: #f3f3f3;
23

34
:host {
45
flex: 1 1 auto;
@@ -14,12 +15,16 @@ $subheader-color: #3f51b5;
1415
text-transform: uppercase;
1516
font-weight: 600;
1617
color: white !important;
17-
background: #333;
18+
background: $subheader-color;
1819
}
1920

2021
.operator-list {
2122
a {
22-
border-bottom: 1px solid #f3f3f3;
23+
border-bottom: 1px solid $operator-border;
24+
&.active-operator {
25+
background-color: #f3f3f3;
26+
transition: background-color .3s ease-in-out;
27+
}
2328
&:last-child {
2429
border-bottom: none;
2530
}

0 commit comments

Comments
 (0)