Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

md-icon #281

Merged
merged 45 commits into from
Apr 23, 2016
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
62e82fe
Start of md-icon directive and service.
Mar 23, 2016
8a188e4
Return SVG element instead of string, set default styles.
Mar 23, 2016
8b6f5ff
API and caching cleanup.
Mar 24, 2016
9518ca2
var->const
Mar 24, 2016
ec761da
Make MdIconProvider available at top level so it's shared by all comp…
Mar 24, 2016
776b755
Icon set support.
Mar 24, 2016
d88144e
Font support, various refactorings.
dozingcat Mar 27, 2016
159d53d
Merge remote-tracking branch 'origin/master' into mdicon
Mar 28, 2016
e423582
Rename directive bindings.
Mar 29, 2016
3ce1334
Merge branch 'master' into mdicon
dozingcat Apr 3, 2016
22a92e1
test
dozingcat Apr 3, 2016
0e6f396
Injecting test HTTP classes.
dozingcat Apr 4, 2016
014be77
start of tests
dozingcat Apr 4, 2016
ea3beea
HTTP tests.
dozingcat Apr 5, 2016
72d167f
Improved change detection and added more tests.
dozingcat Apr 7, 2016
53aeed2
Icon namespaces, addIcon and addIconSet are now additive.
dozingcat Apr 8, 2016
7390015
cleanup
Apr 8, 2016
86d86aa
Merge branch 'master' into mdicon
Apr 8, 2016
17f40c5
fix tests
Apr 8, 2016
87dd4ac
Remove local changes.
Apr 8, 2016
82bd25e
remove unused entry
Apr 8, 2016
e4d568f
more tests
dozingcat Apr 10, 2016
af2f94b
MdIconProvider->MdIconRegistry
dozingcat Apr 10, 2016
154b5e0
Fix bug copying icons out of icon sets, and use Observable.share() co…
dozingcat Apr 13, 2016
cef1277
Remove unused imports.
dozingcat Apr 13, 2016
30f8cce
Remove unnecessary cloneNode.
dozingcat Apr 14, 2016
73a9a3f
Remove custom viewBox size.
Apr 15, 2016
03bcbed
Merge remote-tracking branch 'upstream/master' into mdicon
Apr 15, 2016
e87af2e
Updated for PR comments.
dozingcat Apr 18, 2016
062307e
formatting fixes
dozingcat Apr 18, 2016
d3682b7
Fix tslint errors.
dozingcat Apr 18, 2016
8e2ff62
Factor out common test assertions, and add tests for <svg> elements i…
dozingcat Apr 18, 2016
4b02d27
Fix tests for Edge and IE11 (element.children bad, element.childNodes…
dozingcat Apr 19, 2016
195acd3
Merge remote-tracking branch 'upstream/master' into mdicon
dozingcat Apr 19, 2016
6391cdb
Merge remote-tracking branch 'upstream/master' into mdicon
Apr 19, 2016
437ab0c
In-progress updates for PR comments.
Apr 19, 2016
364391a
More PR comments.
Apr 19, 2016
6dc1af2
Remove Renderer.detachView call since it doesn't work in IE11.
dozingcat Apr 20, 2016
796d37f
comments
dozingcat Apr 20, 2016
a4ea23e
Move test SVGs to separate file.
dozingcat Apr 20, 2016
df6415b
Use <md-icon> for menu icon.
Apr 20, 2016
04b23fa
Use watched:false in karma.config.ts.
Apr 20, 2016
37d8362
Merge remote-tracking branch 'upstream/master' into mdicon
Apr 22, 2016
6927d52
Merge remote-tracking branch 'upstream/master' into mdicon
Apr 22, 2016
08e8cbd
PR comments for test component bindings and demo styles.
dozingcat Apr 23, 2016
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
In-progress updates for PR comments.
  • Loading branch information
Brian Nenninger committed Apr 19, 2016
commit 437ab0ce18cdbfa63be2747d10a2048f73297f5a
50 changes: 20 additions & 30 deletions src/components/icon/icon-registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ class SvgIconConfig {
}
}

/** Returns the cache key to use for an icon namespace and name. */
const iconKey = (namespace: string, name: string) => namespace + ':' + name;

/**
* Service to register and display icons used by the <md-icon> component.
* - Registers icon URLs by namespace and name.
Expand All @@ -51,19 +54,13 @@ export class MdIconRegistry {
*/
private _iconSetConfigs = new Map<string, SvgIconConfig[]>();

/**
* Cache for icons loaded by direct URLs.
*/
/** Cache for icons loaded by direct URLs. */
private _cachedIconsByUrl = new Map<string, SVGElement>();

/**
* In-progress icon fetches. Used to coalesce multiple requests to the same URL.
*/
/** In-progress icon fetches. Used to coalesce multiple requests to the same URL. */
private _inProgressUrlFetches = new Map<string, Observable<string>>();

/**
* Map from font identifiers to their CSS class names. Used for icon fonts.
*/
/** Map from font identifiers to their CSS class names. Used for icon fonts. */
private _fontCssClassesByAlias = new Map<string, string>();

/**
Expand All @@ -75,32 +72,24 @@ export class MdIconRegistry {

constructor(private _http: Http) {}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add method descriptions

/**
* Registers an icon by URL in the default namespace.
*/
/** Registers an icon by URL in the default namespace. */
addSvgIcon(iconName: string, url: string): this {
return this.addSvgIconInNamespace('', iconName, url);
}

/**
* Registers an icon by URL in the specified namespace.
*/
/** Registers an icon by URL in the specified namespace. */
addSvgIconInNamespace(namespace: string, iconName: string, url: string): this {
const iconKey = namespace + ':' + iconName;
this._svgIconConfigs.set(iconKey, new SvgIconConfig(url));
const key = iconKey(namespace, iconName);
this._svgIconConfigs.set(key, new SvgIconConfig(url));
return this;
}

/**
* Registers an icon set by URL in the default namespace.
*/
/** Registers an icon set by URL in the default namespace. */
addSvgIconSet(url: string): this {
return this.addSvgIconSetInNamespace('', url);
}

/**
* Registers an icon set by URL in the specified namespace.
*/
/** Registers an icon set by URL in the specified namespace. */
addSvgIconSetInNamespace(namespace: string, url: string): this {
const config = new SvgIconConfig(url);
if (this._iconSetConfigs.has(namespace)) {
Expand Down Expand Up @@ -168,16 +157,16 @@ export class MdIconRegistry {
*/
getNamedSvgIcon(name: string, namespace = ''): Observable<SVGElement> {
// Return (copy of) cached icon if possible.
const iconKey = namespace + ':' + name;
if (this._svgIconConfigs.has(iconKey)) {
return this._getSvgFromConfig(this._svgIconConfigs.get(iconKey));
const key = iconKey(namespace, name);
if (this._svgIconConfigs.has(key)) {
return this._getSvgFromConfig(this._svgIconConfigs.get(key));
}
// See if we have any icon sets registered for the namespace.
const iconSetConfigs = this._iconSetConfigs.get(namespace);
if (iconSetConfigs) {
return this._getSvgFromIconSetConfigs(name, this._iconSetConfigs.get(namespace));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason we're not using the iconSetConfigs const we just created? e.g.

return this._getSvgFromIconSetConfigs(name, iconSetConfigs);

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No reason at all, changed.

}
return Observable.throw(new MdIconNameNotFoundException(iconKey));
return Observable.throw(new MdIconNameNotFoundException(key));
}

/**
Expand All @@ -190,8 +179,8 @@ export class MdIconRegistry {
} else {
// Fetch the icon from the config's URL, cache it, and return a copy.
return this._loadSvgIconFromConfig(config)
.do((svg: SVGElement) => config.svgElement = svg)
.map((svg: SVGElement) => svg.cloneNode(true));
.do(svg => config.svgElement = svg)
.map(svg => svg.cloneNode(true));
}
}

Expand Down Expand Up @@ -226,7 +215,7 @@ export class MdIconRegistry {
console.log(`Loading icon set URL: ${iconSetConfig.url} failed: ${err}`);
return Observable.of(null);
})
.do((svg: SVGElement) => {
.do(svg => {
// Cache SVG element.
if (svg) {
iconSetConfig.svgElement = svg;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Optional nitpick: collapse onto one line?

if (svg) iconSetConfig.svgElement = svg;

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I actually prefer always having braces and newlines.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Really? Don't like the waste of vertical space :-p But completely subjective!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I reluctantly agree with @jelbourn. (On a related note, I'm very pleased with the 100-char line length).

Expand Down Expand Up @@ -278,6 +267,7 @@ export class MdIconRegistry {
* from it.
*/
private _loadSvgIconSetFromConfig(config: SvgIconConfig): Observable<SVGElement> {
// TODO: Document that icons should only be loaded from trusted sources.
return this._fetchUrl(config.url)
.map((svgText) => this._svgElementFromString(svgText));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My XSS sense is tingling here. I'm trying to imagine a situation where an app would use md-icon to load an icon based on user input, but nothing is really coming to mind. Could you add a TODO for something like "Document that icons should only be loaded from trusted sources."

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that felt sketchy to me also. Although I don't think it's any worse than ngMaterial 1.x.

}
Expand Down
32 changes: 22 additions & 10 deletions src/components/icon/icon.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import {
AfterContentChecked,
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
ElementRef,
Input,
Expand All @@ -12,10 +11,18 @@ import {
ViewEncapsulation,
} from 'angular2/core';
import {NgClass} from 'angular2/common';
import {BaseException} from 'angular2/src/facade/exceptions';

import {MdIconRegistry} from './icon-registry';


/** Exception thrown when an invalid icon name is passed to an md-icon component. */
export class MdIconInvalidNameException extends BaseException {
constructor(iconName: string) {
super(`Invalid icon name: "${name}"`);
}
}

/**
* Component to display an icon. It can be used in the following ways:
* - Specify the svgSrc input to load an SVG icon from a URL. The SVG content is directly inlined
Expand Down Expand Up @@ -67,15 +74,14 @@ export class MdIcon implements OnChanges, OnInit, AfterContentChecked {
@Input() fontIcon: string;
@Input() alt: string;

@Input('aria-label') ariaLabelFromParent: string = '';
@Input('aria-label') hostAriaLabel: string = '';

private _previousFontSetClass: string;
private _previousFontIconClass: string;

constructor(
private _element: ElementRef,
private _renderer: Renderer,
private _changeDetectorRef: ChangeDetectorRef,
private _mdIconRegistry: MdIconRegistry) {
}

Expand All @@ -85,20 +91,27 @@ export class MdIcon implements OnChanges, OnInit, AfterContentChecked {
* The separator for the two fields is ':'. If there is no separator, an empty
* string is returned for the icon set and the entire value is returned for
* the icon name. If the argument is falsy, returns an array of two empty strings.
* Throws a MdIconInvalidNameException if the name contains two or more ':' separators.
* Examples:
* 'social:cake' -> ['social', 'cake']
* 'penguin' -> ['', 'penguin']
* null -> ['', '']
* 'a:b:c' -> (throws MdIconInvalidNameException)
*/
private _splitIconName(iconName: string): [string, string] {
if (!iconName) {
return ['', ''];
}
const separatorIndex = this.svgIcon.indexOf(':');
if (separatorIndex == -1) {
return ['', iconName];
const parts = iconName.split(':');
switch (parts.length) {
case 1:
// Use default namespace.
return ['', parts[0]];
case 2:
return <[string, string]>parts;
default:
throw new MdIconInvalidNameException(iconName);
}
return [iconName.substring(0, separatorIndex), iconName.substring(separatorIndex + 1)];
}

ngOnChanges(changes: { [propertyName: string]: SimpleChange }) {
Expand Down Expand Up @@ -140,7 +153,6 @@ export class MdIcon implements OnChanges, OnInit, AfterContentChecked {
const ariaLabel = this._getAriaLabel();
if (ariaLabel) {
this._renderer.setElementAttribute(this._element.nativeElement, 'aria-label', ariaLabel);
this._changeDetectorRef.detectChanges();
}
}

Expand All @@ -149,7 +161,7 @@ export class MdIcon implements OnChanges, OnInit, AfterContentChecked {
// reasonable value from the alt attribute, font icon name, SVG icon name, or (for ligatures)
// the text content of the directive.
const label =
this.ariaLabelFromParent ||
this.hostAriaLabel ||
this.alt ||
this.fontIcon ||
this._splitIconName(this.svgIcon)[1];
Expand All @@ -163,7 +175,7 @@ export class MdIcon implements OnChanges, OnInit, AfterContentChecked {
return text;
}
}
// Warn here?
// TODO: Warn here in dev mode.
return null;
Copy link
Member

@jelbourn jelbourn Apr 19, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're holding off on logging aria warnings now because we want to be able to only output them in dev mode (don't currently have the tooling for that).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added TODO

}

Expand Down