Skip to content

Commit

Permalink
feat(example-viewer): support rendering additional files (angular#516)
Browse files Browse the repository at this point in the history
* 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 angular#13021
  • Loading branch information
devversion authored and jelbourn committed Sep 11, 2018
1 parent d15b77f commit d6bbac9
Show file tree
Hide file tree
Showing 5 changed files with 83 additions and 37 deletions.
12 changes: 6 additions & 6 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,13 @@
"private": true,
"dependencies": {
"@angular/animations": "^6.0.9",
"@angular/cdk": "^6.4.6",
"@angular/cdk": "^6.4.7",
"@angular/common": "^6.0.9",
"@angular/compiler": "^6.0.9",
"@angular/core": "^6.0.9",
"@angular/forms": "^6.0.9",
"@angular/http": "^6.0.9",
"@angular/material": "^6.4.6",
"@angular/material": "^6.4.7",
"@angular/material-moment-adapter": "^6.4.6",
"@angular/platform-browser": "^6.0.9",
"@angular/platform-browser-dynamic": "^6.0.9",
Expand Down
5 changes: 2 additions & 3 deletions src/app/shared/example-viewer/example-viewer.html
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,15 @@

<div class="docs-example-viewer-source" *ngIf="showSource">
<mat-tab-group>
<!-- TODO(jelbourn): don't hard-code the html + ts + css structure -->
<mat-tab *ngFor="let extension of ['HTML', 'TS', 'CSS']" [label]="extension">
<mat-tab *ngFor="let tabName of _getExampleTabNames()" [label]="tabName">
<div class="docs-example-source-wrapper">
<button mat-icon-button type="button" class="docs-example-source-copy"
title="Copy example source" aria-label="Copy example source to clipboard"
(click)="copySource(viewer.textContent)">
<mat-icon>content_copy</mat-icon>
</button>
<pre class="docs-example-source"><doc-viewer
#viewer [documentUrl]="exampleFileUrl(extension)"></doc-viewer></pre>
#viewer [documentUrl]="exampleTabs[tabName]"></doc-viewer></pre>
</div>
</mat-tab>
</mat-tab-group>
Expand Down
39 changes: 32 additions & 7 deletions src/app/shared/example-viewer/example-viewer.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,12 +61,12 @@ describe('ExampleViewer', () => {
expect(data).toEqual(EXAMPLE_COMPONENTS[exampleKey] as any);
}));

it('should log message about missing example', async(() => {
spyOn(console, 'log');
it('should print an error message about missing example', async(() => {
spyOn(console, 'error');
component.example = 'foobar';
fixture.detectChanges();
expect(console.log).toHaveBeenCalled();
expect(console.log).toHaveBeenCalledWith('MISSING EXAMPLE: ', 'foobar');
expect(console.error).toHaveBeenCalled();
expect(console.error).toHaveBeenCalledWith('Could not find example: foobar');
}));

it('should return assets path for example based on extension', async(() => {
Expand All @@ -77,13 +77,38 @@ describe('ExampleViewer', () => {
// get example file path for each extension
const extensions = ['ts', 'css', 'html'];
const basePath = '/assets/examples/';
extensions.forEach(ext => {
const expected = `${basePath}${exampleKey}-example-${ext}.html`;
const actual = component.exampleFileUrl(ext);

extensions.forEach(extension => {
const expected = `${basePath}${exampleKey}-example-${extension}.html`;
const actual = component.exampleTabs[extension.toUpperCase()];

expect(actual).toEqual(expected);
});
}));

describe('view-source tab group', () => {

it('should only render HTML, TS and CSS files if no additional files are specified', () => {
component.example = exampleKey;
fixture.detectChanges();

expect(component._getExampleTabNames()).toEqual(['HTML', 'TS', 'CSS']);
});

it('should be able to render additional files', () => {
EXAMPLE_COMPONENTS['additional-files'] = {
additionalFiles: ['some-additional-file.html'],
...EXAMPLE_COMPONENTS[exampleKey]
};

component.example = 'additional-files';
fixture.detectChanges();

expect(component._getExampleTabNames())
.toEqual(['HTML', 'TS', 'CSS', 'some-additional-file.html']);
});
});

describe('copy button', () => {
let button: HTMLElement;

Expand Down
60 changes: 41 additions & 19 deletions src/app/shared/example-viewer/example-viewer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import {ComponentPortal} from '@angular/cdk/portal';
import {EXAMPLE_COMPONENTS, LiveExample} from '@angular/material-examples';
import {CopierService} from '../copier/copier.service';

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

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

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

/** Data for the currently selected example. */
exampleData: LiveExample;

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

constructor(
private snackbar: MatSnackBar,
private copier: CopierService) { }

get example() {
return this._example;
}

/** String key of the currently displayed example. */
@Input()
set example(example: string) {
if (example && EXAMPLE_COMPONENTS[example]) {
this._example = example;
this.exampleData = EXAMPLE_COMPONENTS[example];
get example() { return this._example; }
set example(exampleName: string) {
if (exampleName && EXAMPLE_COMPONENTS[exampleName]) {
this._example = exampleName;
this.exampleData = EXAMPLE_COMPONENTS[exampleName];
this.selectedPortal = new ComponentPortal(this.exampleData.component);
this._generateExampleTabs();
} else {
console.log('MISSING EXAMPLE: ', example);
console.error(`Could not find example: ${exampleName}`);
}
}
private _example: string;

constructor(private snackbar: MatSnackBar,private copier: CopierService) {}

toggleSourceView(): void {
this.showSource = !this.showSource;
}

exampleFileUrl(extension: string) {
return `/assets/examples/${this.example}-example-${extension.toLowerCase()}.html`;
}

copySource(text: string) {
if (this.copier.copyText(text)) {
this.snackbar.open('Code copied', '', {duration: 2500});
} else {
this.snackbar.open('Copy failed. Please try again!', '', {duration: 2500});
}
}

_getExampleTabNames() {
return Object.keys(this.exampleTabs);
}

private resolveExampleFile(fileName: string) {
return `/assets/examples/${fileName}`;
}

private _generateExampleTabs() {
this.exampleTabs = {
HTML: this.resolveExampleFile(`${this.example}-example-html.html`),
TS: this.resolveExampleFile(`${this.example}-example-ts.html`),
CSS: this.resolveExampleFile(`${this.example}-example-css.html`),
};

const additionalFiles = this.exampleData.additionalFiles || [];

additionalFiles.forEach(fileName => {
// Since the additional files refer to the original file name, we need to transform
// the file name to match the highlighted HTML file that displays the source.
const fileSourceName = fileName.replace(fileExtensionRegex, '$1-$2.html');
this.exampleTabs[fileName] = this.resolveExampleFile(fileSourceName);
})
}
}

0 comments on commit d6bbac9

Please sign in to comment.