diff --git a/aio/src/app/app.component.html b/aio/src/app/app.component.html index fcad3b428c..8622e550ca 100644 --- a/aio/src/app/app.component.html +++ b/aio/src/app/app.component.html @@ -1,7 +1,7 @@ Home diff --git a/aio/src/app/app.module.ts b/aio/src/app/app.module.ts index 2e57d85970..707712daea 100644 --- a/aio/src/app/app.module.ts +++ b/aio/src/app/app.module.ts @@ -5,7 +5,8 @@ import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { Location, LocationStrategy, PathLocationStrategy } from '@angular/common'; -import { MdToolbarModule, MdButtonModule, MdIconModule, MdInputModule, MdSidenavModule, MdTabsModule, Platform} from '@angular/material'; +import { MdToolbarModule, MdButtonModule, MdIconModule, MdInputModule, MdSidenavModule, MdTabsModule, Platform, + MdIconRegistry } from '@angular/material'; // Temporary fix for MdSidenavModule issue: // crashes with "missing first" operator when SideNav.mode is "over" @@ -31,6 +32,29 @@ import { NavItemComponent } from 'app/layout/nav-item/nav-item.component'; import { SearchResultsComponent } from './search/search-results/search-results.component'; import { SearchBoxComponent } from './search/search-box/search-box.component'; import { AutoScrollService } from 'app/shared/auto-scroll.service'; +import { CustomMdIconRegistry, SVG_ICONS } from 'app/shared/custom-md-icon-registry'; + +// These are the hardcoded inline svg sources to be used by the `` component +export const svgIconProviders = [ + { + provide: SVG_ICONS, + useValue: { + name: 'keyboard_arrow_right', + svgSource: '' + }, + multi: true + }, + { + provide: SVG_ICONS, + useValue: { + name: 'menu', + svgSource: '' + }, + multi: true + } +]; @NgModule({ imports: [ @@ -69,6 +93,8 @@ import { AutoScrollService } from 'app/shared/auto-scroll.service'; SearchService, Platform, AutoScrollService, + { provide: MdIconRegistry, useClass: CustomMdIconRegistry }, + svgIconProviders ], bootstrap: [AppComponent] }) diff --git a/aio/src/app/layout/nav-item/nav-item.component.html b/aio/src/app/layout/nav-item/nav-item.component.html index 0fde70cbbd..3894100686 100644 --- a/aio/src/app/layout/nav-item/nav-item.component.html +++ b/aio/src/app/layout/nav-item/nav-item.component.html @@ -9,13 +9,13 @@ {{node.title}} - keyboard_arrow_right + {{node.title}} - keyboard_arrow_right +
diff --git a/aio/src/app/shared/custom-md-icon-registry.spec.ts b/aio/src/app/shared/custom-md-icon-registry.spec.ts new file mode 100644 index 0000000000..d7a1db03b0 --- /dev/null +++ b/aio/src/app/shared/custom-md-icon-registry.spec.ts @@ -0,0 +1,39 @@ +import { MdIconRegistry } from '@angular/material'; +import { CustomMdIconRegistry, SVG_ICONS, SvgIconInfo } from './custom-md-icon-registry'; + +describe('CustomMdIconRegistry', () => { + it('should get the SVG element for a preloaded icon from the cache', () => { + const mockHttp: any = {}; + const mockSanitizer: any = {}; + const svgSrc = ''; + const svgIcons: SvgIconInfo[] = [ + { name: 'test_icon', svgSource: svgSrc } + ]; + const registry = new CustomMdIconRegistry(mockHttp, mockSanitizer, svgIcons); + let svgElement: SVGElement; + registry.getNamedSvgIcon('test_icon', null).subscribe(el => svgElement = el as SVGElement); + expect(svgElement).toEqual(createSvg(svgSrc)); + }); + + it('should call through to the MdIconRegistry if the icon name is not in the preloaded cache', () => { + const mockHttp: any = {}; + const mockSanitizer: any = {}; + const svgSrc = ''; + const svgIcons: SvgIconInfo[] = [ + { name: 'test_icon', svgSource: svgSrc } + ]; + spyOn(MdIconRegistry.prototype, 'getNamedSvgIcon'); + + const registry = new CustomMdIconRegistry(mockHttp, mockSanitizer, svgIcons); + registry.getNamedSvgIcon('other_icon', null); + expect(MdIconRegistry.prototype.getNamedSvgIcon).toHaveBeenCalledWith('other_icon', null); + }); +}); + +function createSvg(svgSrc) { + const div = document.createElement('div'); + div.innerHTML = svgSrc; + return div.querySelector('svg'); +} diff --git a/aio/src/app/shared/custom-md-icon-registry.ts b/aio/src/app/shared/custom-md-icon-registry.ts new file mode 100644 index 0000000000..77b05d9169 --- /dev/null +++ b/aio/src/app/shared/custom-md-icon-registry.ts @@ -0,0 +1,58 @@ +import { InjectionToken, Inject, Injectable } from '@angular/core'; +import { of } from 'rxjs/observable/of'; +import { MdIconRegistry } from '@angular/material'; +import { Http } from '@angular/http'; +import { DomSanitizer } from '@angular/platform-browser'; + +/** + * Use SVG_ICONS (and SvgIconInfo) as "multi" providers to provide the SVG source + * code for the icons that you wish to have preloaded in the `CustomMdIconRegistry` + * For compatibility with the MdIconComponent, please ensure that the SVG source has + * the following attributes: + * + * * `xmlns="http://www.w3.org/2000/svg"` + * * `focusable="false"` (disable IE11 default behavior to make SVGs focusable) + * * `height="100%"` (the default) + * * `width="100%"` (the default) + * * `preserveAspectRatio="xMidYMid meet"` (the default) + * + */ +export const SVG_ICONS = new InjectionToken>('SvgIcons'); +export interface SvgIconInfo { + name: string; + svgSource: string; +} + +interface SvgIconMap { + [iconName: string]: SVGElement; +} + +/** + * A custom replacement for Angular Material's `MdIconRegistry`, which allows + * us to provide preloaded icon SVG sources. + */ +@Injectable() +export class CustomMdIconRegistry extends MdIconRegistry { + private preloadedSvgElements: SvgIconMap = {}; + + constructor(http: Http, sanitizer: DomSanitizer, @Inject(SVG_ICONS) svgIcons: SvgIconInfo[]) { + super(http, sanitizer); + this.loadSvgElements(svgIcons); + } + + getNamedSvgIcon(iconName, namespace) { + if (this.preloadedSvgElements[iconName]) { + return of(this.preloadedSvgElements[iconName].cloneNode(true)); + } + return super.getNamedSvgIcon(iconName, namespace); + } + + private loadSvgElements(svgIcons: SvgIconInfo[]) { + const div = document.createElement('DIV'); + svgIcons.forEach(icon => { + // SECURITY: the source for the SVG icons is provided in code by trusted developers + div.innerHTML = icon.svgSource; + this.preloadedSvgElements[icon.name] = div.querySelector('svg'); + }); + } +} diff --git a/aio/src/styles/1-layouts/_sidenav.scss b/aio/src/styles/1-layouts/_sidenav.scss index 2749bba3d0..a6ccfa1362 100644 --- a/aio/src/styles/1-layouts/_sidenav.scss +++ b/aio/src/styles/1-layouts/_sidenav.scss @@ -58,7 +58,7 @@ md-sidenav-container div.mat-sidenav-content { } //icons _within_ nav - .material-icons { + .mat-icon { position: absolute; top: 6px; margin-left: 10px; @@ -83,11 +83,12 @@ md-sidenav-container div.mat-sidenav-content { width: 100%; } -.material-icons { +.mat-icon { display: inline-block; position: absolute; top: 6px; right: 8px; + color: $mediumgray; } .heading-children.expanded { @@ -138,11 +139,11 @@ a.selected.level-1, padding-left: 30px; } -.level-1.expanded .material-icons, .level-2.expanded .material-icons { +.level-1.expanded .mat-icon, .level-2.expanded .mat-icon { @include rotate(90deg); } -.level-1:not(.expanded) .material-icons, .level-2:not(.expanded) .material-icons { +.level-1:not(.expanded) .mat-icon, .level-2:not(.expanded) .mat-icon { @include rotate(0deg); } @@ -161,5 +162,5 @@ aio-nav-menu.top-menu { aio-nav-item:last-child div { border-bottom: 2px solid rgba($mediumgray, 0.5); } - + } \ No newline at end of file diff --git a/aio/src/styles/2-modules/_hamburger.scss b/aio/src/styles/2-modules/_hamburger.scss index 1380f1637b..a628062f7b 100644 --- a/aio/src/styles/2-modules/_hamburger.scss +++ b/aio/src/styles/2-modules/_hamburger.scss @@ -18,6 +18,7 @@ color: $offwhite; } -.hamburger md-icon { +.hamburger .mat-icon { position: inherit; -} \ No newline at end of file + color: white; +}