Skip to content
This repository was archived by the owner on Dec 18, 2024. It is now read-only.

Commit d6bbac9

Browse files
devversionjelbourn
authored andcommitted
feat(example-viewer): support rendering additional files (#516)
* Currently we only render the `HTML`, `TS` and `CSS` files in the view-source tab group. This is fine for most examples, but there are also examples like the `form-field-custom-control` that prefer to have the template of the "custom control" externally. We should support rendering these additional files in the tab group instead of just magically adding them to the external StackBlitz that will be generated. Related to #13021
1 parent d15b77f commit d6bbac9

File tree

5 files changed

+83
-37
lines changed

5 files changed

+83
-37
lines changed

package-lock.json

Lines changed: 6 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,13 @@
2020
"private": true,
2121
"dependencies": {
2222
"@angular/animations": "^6.0.9",
23-
"@angular/cdk": "^6.4.6",
23+
"@angular/cdk": "^6.4.7",
2424
"@angular/common": "^6.0.9",
2525
"@angular/compiler": "^6.0.9",
2626
"@angular/core": "^6.0.9",
2727
"@angular/forms": "^6.0.9",
2828
"@angular/http": "^6.0.9",
29-
"@angular/material": "^6.4.6",
29+
"@angular/material": "^6.4.7",
3030
"@angular/material-moment-adapter": "^6.4.6",
3131
"@angular/platform-browser": "^6.0.9",
3232
"@angular/platform-browser-dynamic": "^6.0.9",

src/app/shared/example-viewer/example-viewer.html

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,15 @@
1717

1818
<div class="docs-example-viewer-source" *ngIf="showSource">
1919
<mat-tab-group>
20-
<!-- TODO(jelbourn): don't hard-code the html + ts + css structure -->
21-
<mat-tab *ngFor="let extension of ['HTML', 'TS', 'CSS']" [label]="extension">
20+
<mat-tab *ngFor="let tabName of _getExampleTabNames()" [label]="tabName">
2221
<div class="docs-example-source-wrapper">
2322
<button mat-icon-button type="button" class="docs-example-source-copy"
2423
title="Copy example source" aria-label="Copy example source to clipboard"
2524
(click)="copySource(viewer.textContent)">
2625
<mat-icon>content_copy</mat-icon>
2726
</button>
2827
<pre class="docs-example-source"><doc-viewer
29-
#viewer [documentUrl]="exampleFileUrl(extension)"></doc-viewer></pre>
28+
#viewer [documentUrl]="exampleTabs[tabName]"></doc-viewer></pre>
3029
</div>
3130
</mat-tab>
3231
</mat-tab-group>

src/app/shared/example-viewer/example-viewer.spec.ts

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -61,12 +61,12 @@ describe('ExampleViewer', () => {
6161
expect(data).toEqual(EXAMPLE_COMPONENTS[exampleKey] as any);
6262
}));
6363

64-
it('should log message about missing example', async(() => {
65-
spyOn(console, 'log');
64+
it('should print an error message about missing example', async(() => {
65+
spyOn(console, 'error');
6666
component.example = 'foobar';
6767
fixture.detectChanges();
68-
expect(console.log).toHaveBeenCalled();
69-
expect(console.log).toHaveBeenCalledWith('MISSING EXAMPLE: ', 'foobar');
68+
expect(console.error).toHaveBeenCalled();
69+
expect(console.error).toHaveBeenCalledWith('Could not find example: foobar');
7070
}));
7171

7272
it('should return assets path for example based on extension', async(() => {
@@ -77,13 +77,38 @@ describe('ExampleViewer', () => {
7777
// get example file path for each extension
7878
const extensions = ['ts', 'css', 'html'];
7979
const basePath = '/assets/examples/';
80-
extensions.forEach(ext => {
81-
const expected = `${basePath}${exampleKey}-example-${ext}.html`;
82-
const actual = component.exampleFileUrl(ext);
80+
81+
extensions.forEach(extension => {
82+
const expected = `${basePath}${exampleKey}-example-${extension}.html`;
83+
const actual = component.exampleTabs[extension.toUpperCase()];
84+
8385
expect(actual).toEqual(expected);
8486
});
8587
}));
8688

89+
describe('view-source tab group', () => {
90+
91+
it('should only render HTML, TS and CSS files if no additional files are specified', () => {
92+
component.example = exampleKey;
93+
fixture.detectChanges();
94+
95+
expect(component._getExampleTabNames()).toEqual(['HTML', 'TS', 'CSS']);
96+
});
97+
98+
it('should be able to render additional files', () => {
99+
EXAMPLE_COMPONENTS['additional-files'] = {
100+
additionalFiles: ['some-additional-file.html'],
101+
...EXAMPLE_COMPONENTS[exampleKey]
102+
};
103+
104+
component.example = 'additional-files';
105+
fixture.detectChanges();
106+
107+
expect(component._getExampleTabNames())
108+
.toEqual(['HTML', 'TS', 'CSS', 'some-additional-file.html']);
109+
});
110+
});
111+
87112
describe('copy button', () => {
88113
let button: HTMLElement;
89114

src/app/shared/example-viewer/example-viewer.ts

Lines changed: 41 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import {ComponentPortal} from '@angular/cdk/portal';
55
import {EXAMPLE_COMPONENTS, LiveExample} from '@angular/material-examples';
66
import {CopierService} from '../copier/copier.service';
77

8+
/** Regular expression that matches a file name and its extension */
9+
const fileExtensionRegex = /(.*)\.(\w+)/;
810

911
@Component({
1012
selector: 'example-viewer',
@@ -15,46 +17,66 @@ export class ExampleViewer {
1517
/** Component portal for the currently displayed example. */
1618
selectedPortal: ComponentPortal<any>;
1719

18-
/** String key of the currently displayed example. */
19-
_example: string;
20+
/** Map of example files that should be displayed in the view-source tab. */
21+
exampleTabs: {[tabName: string]: string};
2022

23+
/** Data for the currently selected example. */
2124
exampleData: LiveExample;
2225

2326
/** Whether the source for the example is being displayed. */
2427
showSource = false;
2528

26-
constructor(
27-
private snackbar: MatSnackBar,
28-
private copier: CopierService) { }
29-
30-
get example() {
31-
return this._example;
32-
}
33-
29+
/** String key of the currently displayed example. */
3430
@Input()
35-
set example(example: string) {
36-
if (example && EXAMPLE_COMPONENTS[example]) {
37-
this._example = example;
38-
this.exampleData = EXAMPLE_COMPONENTS[example];
31+
get example() { return this._example; }
32+
set example(exampleName: string) {
33+
if (exampleName && EXAMPLE_COMPONENTS[exampleName]) {
34+
this._example = exampleName;
35+
this.exampleData = EXAMPLE_COMPONENTS[exampleName];
3936
this.selectedPortal = new ComponentPortal(this.exampleData.component);
37+
this._generateExampleTabs();
4038
} else {
41-
console.log('MISSING EXAMPLE: ', example);
39+
console.error(`Could not find example: ${exampleName}`);
4240
}
4341
}
42+
private _example: string;
43+
44+
constructor(private snackbar: MatSnackBar,private copier: CopierService) {}
4445

4546
toggleSourceView(): void {
4647
this.showSource = !this.showSource;
4748
}
4849

49-
exampleFileUrl(extension: string) {
50-
return `/assets/examples/${this.example}-example-${extension.toLowerCase()}.html`;
51-
}
52-
5350
copySource(text: string) {
5451
if (this.copier.copyText(text)) {
5552
this.snackbar.open('Code copied', '', {duration: 2500});
5653
} else {
5754
this.snackbar.open('Copy failed. Please try again!', '', {duration: 2500});
5855
}
5956
}
57+
58+
_getExampleTabNames() {
59+
return Object.keys(this.exampleTabs);
60+
}
61+
62+
private resolveExampleFile(fileName: string) {
63+
return `/assets/examples/${fileName}`;
64+
}
65+
66+
private _generateExampleTabs() {
67+
this.exampleTabs = {
68+
HTML: this.resolveExampleFile(`${this.example}-example-html.html`),
69+
TS: this.resolveExampleFile(`${this.example}-example-ts.html`),
70+
CSS: this.resolveExampleFile(`${this.example}-example-css.html`),
71+
};
72+
73+
const additionalFiles = this.exampleData.additionalFiles || [];
74+
75+
additionalFiles.forEach(fileName => {
76+
// Since the additional files refer to the original file name, we need to transform
77+
// the file name to match the highlighted HTML file that displays the source.
78+
const fileSourceName = fileName.replace(fileExtensionRegex, '$1-$2.html');
79+
this.exampleTabs[fileName] = this.resolveExampleFile(fileSourceName);
80+
})
81+
}
6082
}

0 commit comments

Comments
 (0)