|
1 |
| -import { Component, OnInit } from '@angular/core'; |
| 1 | +import {Component, Input, OnInit} from '@angular/core'; |
| 2 | +import {Http} from '@angular/http'; |
| 3 | +import {Router, NavigationExtras} from '@angular/router'; |
| 4 | +import {DocInfoService} from '../../../../shared'; |
| 5 | +import 'rxjs/add/operator/toPromise'; |
| 6 | + |
| 7 | +type Lang = 'dart' | 'ts' | 'js'; |
| 8 | +export type ApiStyleClass = '' | 'stable' | 'directive' | 'decorator' | 'class' | 'interface' | 'function' | 'enum' | 'const' | 'pipe'; |
| 9 | +export type ApiMatch = '' | 'stable' | 'directive' | 'decorator' | 'class' | 'interface' | 'function' | 'enum' | 'var' | 'let' | 'const' | 'pipe'; |
| 10 | + |
| 11 | +export interface ApiType { |
| 12 | + cssClass: ApiStyleClass, |
| 13 | + title: string, |
| 14 | + matches: Array<ApiMatch> |
| 15 | +} |
2 | 16 |
|
3 | 17 | @Component({
|
4 |
| - selector: 'ngio-api-list', |
5 |
| - templateUrl: './api-list.component.html', |
6 |
| - styleUrls: ['./api-list.component.css'] |
| 18 | + selector: 'api-list', |
| 19 | + templateUrl: 'api-list.component.html' |
7 | 20 | })
|
8 | 21 | export class ApiListComponent implements OnInit {
|
| 22 | + @Input() src: string = 'api-list.json'; |
| 23 | + |
| 24 | + isApiLoaded: boolean = false; |
| 25 | + showApiMenu: boolean = false; |
| 26 | + showStatusMenu: boolean = false; |
| 27 | + searchText: string = ''; |
| 28 | + emptyApiType: ApiType = { cssClass: '', title: 'Type: All', matches: [''] }; |
| 29 | + emptyStatusType = { cssClass: '', title: 'Status: All', matches: [''] }; |
| 30 | + selectedApiType: ApiType = this.emptyApiType; // default to all items |
| 31 | + selectedApiStatus: any = this.emptyStatusType; |
| 32 | + groupedSections = []; |
| 33 | + sections: any[]; |
| 34 | + |
| 35 | + _apiTypes: Array<ApiType> = [ |
| 36 | + { cssClass: '', title: 'All', matches: [''] }, |
| 37 | + { cssClass: 'directive', title: 'Directive', matches: ['directive'] }, |
| 38 | + { cssClass: 'pipe', title: 'Pipe', matches: ['pipe'] }, |
| 39 | + { cssClass: 'decorator', title: 'Decorator', matches: ['decorator'] }, |
| 40 | + { cssClass: 'class', title: 'Class', matches: ['class'] }, |
| 41 | + { cssClass: 'interface', title: 'Interface', matches: ['interface'] }, |
| 42 | + { cssClass: 'function', title: 'Function', matches: ['function'] }, |
| 43 | + { cssClass: 'enum', title: 'Enum', matches: ['enum'] }, |
| 44 | + { cssClass: 'const', title: 'Const', matches: ['var', 'let', 'const'] } |
| 45 | + ]; |
| 46 | + |
| 47 | + apiStatuses = [ |
| 48 | + { cssClass: '', title: 'All', matches: [''] }, |
| 49 | + { cssClass: 'stable', title: 'Stable', matches: ['stable']}, |
| 50 | + { cssClass: 'deprecated', title: 'Deprecated', matches: ['deprecated']}, |
| 51 | + { cssClass: 'experimental', title: 'Experimental', matches: ['experimental']}, |
| 52 | + { cssClass: 'security', title: 'Security Risk', matches: ['security']} |
| 53 | + ]; |
| 54 | + |
| 55 | + // Dart API entries are not tagged Stable vs Experimental. |
| 56 | + _apiTypesForDart: Array<ApiType> = this._apiTypes.filter(t => |
| 57 | + t.title.match(/All|Class|Const|Function/)); |
| 58 | + |
| 59 | + constructor( |
| 60 | + private http: Http, |
| 61 | + private router: Router, |
| 62 | + private docInfoSvc: DocInfoService |
| 63 | + ) { |
| 64 | + router.events.subscribe((event) => { |
| 65 | + this.applyFilterOnSections(); |
| 66 | + }); |
| 67 | + } |
| 68 | + |
| 69 | + get ngLang() { return this.docInfoSvc.ngLang; } |
| 70 | + |
| 71 | + ngOnInit(): void { |
| 72 | + const urlPrefix = this.router.url.split('?')[0].replace(/^\/docs\//, '/assets/'); |
| 73 | + const url = `${urlPrefix}/${this.src}`; |
| 74 | + this.fetchUrl(url); |
| 75 | + |
| 76 | + // extract type and status from url |
| 77 | + var split = this.router.url.split('?'); |
| 78 | + if (split.length > 1) { |
| 79 | + const paramsRaw = split[1].split('&'); |
| 80 | + const type = paramsRaw[0] ? paramsRaw[0].split('=')[1] : ''; |
| 81 | + const status = paramsRaw[1] ? paramsRaw[1].split('=')[1] : ''; |
| 82 | + |
| 83 | + // update our internal model if a match for type is found |
| 84 | + const apiTypes = this._apiTypes.filter(t => t.matches[0] === type); |
| 85 | + if (apiTypes.length > 0) this.selectedApiType = apiTypes[0]; |
| 86 | + |
| 87 | + // update our internal model if a match for status is found |
| 88 | + const apiStatuses = this.apiStatuses.filter(t => t.matches[0] === status); |
| 89 | + if (apiStatuses.length > 0) this.selectedApiStatus = apiStatuses[0]; |
| 90 | + } |
| 91 | + } |
| 92 | + |
| 93 | + fetchUrl(url): Promise<any> { |
| 94 | + return this.http.get(url) |
| 95 | + .toPromise() |
| 96 | + .then(resp => { |
| 97 | + this.sections = resp.json(); |
| 98 | + this.groupedSections = Object.keys(this.sections).map((title) => { |
| 99 | + return { title: title, items: this.sections[title] }; |
| 100 | + }); |
| 101 | + this.isApiLoaded = true; |
| 102 | + this.applyFilterOnSections(); |
| 103 | + }) |
| 104 | + .catch(this.handleError); |
| 105 | + } |
9 | 106 |
|
10 |
| - constructor() { } |
| 107 | + private handleError(error: any): Promise<any> { |
| 108 | + console.error('An error occurred', error); // TODO: report via page. |
| 109 | + return Promise.reject(error.message || error); |
| 110 | + } |
| 111 | + |
| 112 | + get apiTypes(): Array<ApiType> { |
| 113 | + return this.ngLang === 'dart' ? this._apiTypesForDart : this._apiTypes; |
| 114 | + } |
| 115 | + |
| 116 | + |
| 117 | + filterSectionEntries(section) { |
| 118 | + for (let item of section.items) { |
| 119 | + const isPassingApiFilters = (this.isSearchingAllBucketsFiltered() || this.isMatchingBucket(item)); |
| 120 | + const isPassingStatusFilters = this.isSearchingAllStatusesFiltered() || this.isMatchingStatus(item) || this.isSecurityRisk(item); |
| 121 | + |
| 122 | + item.show = (isPassingApiFilters && isPassingStatusFilters) && this.isTextMatching(item) |
| 123 | + } |
| 124 | + }; |
| 125 | + |
| 126 | + applyFilterOnSections() { |
| 127 | + // Cannot guarantee that <param> in queryParam=<param> is an ApiMatch so we union type check it with a string. |
| 128 | + const selectedApiMatchOnUrl: string | ApiMatch = this.router.url.split('=')[1]; |
| 129 | + this.changeSelectedApiTypeForApiMatch(selectedApiMatchOnUrl); |
| 130 | + if (this.isApiLoaded) { |
| 131 | + this.groupedSections.forEach((s) => { |
| 132 | + this.filterSectionEntries(s) |
| 133 | + }); |
| 134 | + } |
| 135 | + } |
| 136 | + |
| 137 | + changeSelectedApiTypeForApiMatch(match: string | ApiMatch) { |
| 138 | + const apiTypesThatMatch = this.apiTypes.filter((apiType) => { |
| 139 | + const matchName = apiType.matches[0]; |
| 140 | + return matchName === match; // Should this be: apiType.matches.indexOf(match as ApiMatch) >= 0; ? |
| 141 | + }); |
| 142 | + |
| 143 | + const shouldUpdateSelectedApiType = apiTypesThatMatch.length > 0; |
| 144 | + |
| 145 | + if (shouldUpdateSelectedApiType) { |
| 146 | + this.selectedApiType = apiTypesThatMatch[0]; |
| 147 | + } |
| 148 | + } |
| 149 | + |
| 150 | + isSearchingAllBucketsFiltered(): boolean { |
| 151 | + return this.selectedApiType.matches[0] === ''; |
| 152 | + } |
| 153 | + |
| 154 | + isSearchingAllStatusesFiltered(): boolean { |
| 155 | + return this.selectedApiStatus.matches[0] === ''; |
| 156 | + } |
11 | 157 |
|
12 |
| - ngOnInit() { |
| 158 | + isTextMatching(item): boolean { |
| 159 | + return (!this.searchText || item.title.toLowerCase().indexOf(this.searchText.toLowerCase()) !== -1); |
13 | 160 | }
|
14 | 161 |
|
| 162 | + isMatchingBucket(item): boolean { |
| 163 | + // Hmm, should be able to use [].includes(). |
| 164 | + return this.selectedApiType.matches.indexOf(item.docType) >= 0; |
| 165 | + } |
| 166 | + |
| 167 | + isMatchingStatus(item): boolean { |
| 168 | + return this.selectedApiStatus.matches.indexOf(item.stability) >= 0; |
| 169 | + } |
| 170 | + |
| 171 | + isSecurityRisk(item): boolean { |
| 172 | + // if true "is a security risk", if false "is not a security risk" |
| 173 | + // I know, it's confusing. |
| 174 | + const _isSecurityRisk = !item.secure; |
| 175 | + return this.selectedApiStatus.matches[0] === 'security' && _isSecurityRisk; |
| 176 | + } |
| 177 | + |
| 178 | + isEntryStable(item): boolean { |
| 179 | + return item.stability === this.selectedApiType.matches[0]; |
| 180 | + } |
| 181 | + |
| 182 | + isApiTypeSelected(apiType) { |
| 183 | + return apiType === this.selectedApiType; |
| 184 | + } |
| 185 | + |
| 186 | + hasVisibleEntries(section): boolean { |
| 187 | + return section.items.filter((i) => { return i.show }).length > 0; |
| 188 | + } |
| 189 | + |
| 190 | + triggerSearch(value) { |
| 191 | + this.searchText = value; |
| 192 | + this.applyFilterOnSections(); |
| 193 | + } |
| 194 | + |
| 195 | + setApiType(t) { |
| 196 | + this.selectedApiType = t; |
| 197 | + |
| 198 | + const type = t.matches[0]; |
| 199 | + const status = this.selectedApiStatus.matches[0]; |
| 200 | + |
| 201 | + let navigationExtras: NavigationExtras = { |
| 202 | + queryParams: { |
| 203 | + type: type, |
| 204 | + status: status |
| 205 | + } |
| 206 | + }; |
| 207 | + |
| 208 | + this.router.navigate([`/docs/${this.ngLang}/latest/api`], navigationExtras); |
| 209 | + |
| 210 | + this.toggleApiMenu(); |
| 211 | + } |
| 212 | + |
| 213 | + setStatus(s) { |
| 214 | + this.selectedApiStatus = s; |
| 215 | + |
| 216 | + const type = this.selectedApiType.matches[0]; |
| 217 | + const status = s.matches[0]; |
| 218 | + |
| 219 | + let navigationExtras: NavigationExtras = { |
| 220 | + queryParams: { |
| 221 | + type: type, |
| 222 | + status: status |
| 223 | + } |
| 224 | + }; |
| 225 | + |
| 226 | + this.router.navigate([`/docs/${this.ngLang}/latest/api`], navigationExtras); |
| 227 | + |
| 228 | + this.toggleStatusMenu(); |
| 229 | + } |
| 230 | + |
| 231 | + toggleApiMenu() { |
| 232 | + this.showApiMenu = !this.showApiMenu; |
| 233 | + } |
| 234 | + |
| 235 | + toggleStatusMenu() { |
| 236 | + this.showStatusMenu = !this.showStatusMenu; |
| 237 | + } |
| 238 | + |
| 239 | + clearType() { |
| 240 | + |
| 241 | + } |
15 | 242 | }
|
0 commit comments