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

feat(example-viewer): support rendering additional files #516

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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);
})
}
}