Skip to content

Commit

Permalink
Convert external example write to StackBlitz (angular#331)
Browse files Browse the repository at this point in the history
  • Loading branch information
amcdnl authored and jelbourn committed Nov 27, 2017
1 parent f42d5f4 commit f775248
Show file tree
Hide file tree
Showing 21 changed files with 137 additions and 166 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
/tmp
/src/assets/documents
/src/assets/examples
/src/assets/plunker/examples
/src/assets/stackblitz/examples
/src/assets/*.css
/src/assets/*.html

Expand Down
4 changes: 2 additions & 2 deletions src/app/app-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import {ComponentHeaderModule} from './pages/component-page-header/component-pag
import {StyleManager} from './shared/style-manager/style-manager';
import {SvgViewerModule} from './shared/svg-viewer/svg-viewer';
import {ThemePickerModule} from './shared/theme-picker/theme-picker';
import {PlunkerButtonModule} from './shared/plunker/plunker-button';
import {StackblitzButtonModule} from './shared/stackblitz/stackblitz-button';
import {NavBarModule} from './shared/navbar/navbar';
import {ThemeStorage} from './shared/theme-picker/theme-storage/theme-storage';
import {GuideItems} from './shared/guide-items/guide-items';
Expand Down Expand Up @@ -56,7 +56,7 @@ import {HttpClientModule} from '@angular/common/http';
GuideViewerModule,
HomepageModule,
NavBarModule,
PlunkerButtonModule,
StackblitzButtonModule,
SvgViewerModule,
ThemePickerModule,
],
Expand Down
4 changes: 2 additions & 2 deletions src/app/shared/doc-viewer/doc-viewer-module.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {DocViewer} from './doc-viewer';
import {ExampleViewer} from '../example-viewer/example-viewer';
import {PlunkerButtonModule} from '../plunker/plunker-button';
import {StackblitzButtonModule} from '../stackblitz/stackblitz-button';
import {
MatButtonModule,
MatIconModule,
Expand All @@ -25,7 +25,7 @@ import {CopierService} from '../copier/copier.service';
MatTabsModule,
CommonModule,
PortalModule,
PlunkerButtonModule
StackblitzButtonModule
],
providers: [CopierService],
declarations: [DocViewer, ExampleViewer, HeaderLink],
Expand Down
2 changes: 1 addition & 1 deletion src/app/shared/example-viewer/example-viewer.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
</mat-icon>
</button>

<plunker-button [example]="example"></plunker-button>
<stackblitz-button [example]="example"></stackblitz-button>
</div>

<div class="docs-example-viewer-source" *ngIf="showSource">
Expand Down
1 change: 0 additions & 1 deletion src/app/shared/plunker/index.ts

This file was deleted.

1 change: 1 addition & 0 deletions src/app/shared/stackblitz/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './stackblitz-button';
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<!-- TODO: change the template to be plunker icon -->
<div [matTooltip]="isDisabled ? 'Building Plunker example...' : 'Edit in Plunker'">
<!-- TODO: change the template to be stackblitz icon -->
<div [matTooltip]="isDisabled ? 'Building StackBlitz example...' : 'Edit in StackBlitz'">
<button mat-icon-button type="button"
(click)="openPlunker()"
(click)="openStackblitz()"
[disabled]="isDisabled">
<mat-icon>
<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 0 24 24" fit="" preserveAspectRatio="xMidYMid meet" focusable="false">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,53 +1,53 @@
import {Component, Input, NgModule} from '@angular/core';
import {PlunkerWriter} from './plunker-writer';
import {StackblitzWriter} from './stackblitz-writer';
import {ExampleData} from '@angular/material-examples';
import {MatButtonModule, MatIconModule, MatTooltipModule} from '@angular/material';

@Component({
selector: 'plunker-button',
templateUrl: './plunker-button.html',
providers: [PlunkerWriter],
selector: 'stackblitz-button',
templateUrl: './stackblitz-button.html',
providers: [StackblitzWriter],
host: {
'(mouseover)': 'isDisabled = !plunkerForm'
'(mouseover)': 'isDisabled = !stackblitzForm'
}
})
export class PlunkerButton {
export class StackblitzButton {
/**
* The button becomes disabled if the user hovers over the button before the plunker form
* The button becomes disabled if the user hovers over the button before the stackblitz form
* is created. After the form is created, the button becomes enabled again.
* The form creation usually happens extremely quickly, but we handle the case of the
* plunker not yet being ready for people will poor network connections or slow devices.
* stackblitz not yet being ready for people will poor network connections or slow devices.
*/
isDisabled = false;
plunkerForm: HTMLFormElement;
stackblitzForm: HTMLFormElement;

@Input()
set example(example: string) {
const exampleData = new ExampleData(example);

this.plunkerWriter.constructPlunkerForm(exampleData).then(plunkerForm => {
this.plunkerForm = plunkerForm;
this.stackblitzWriter.constructStackblitzForm(exampleData).then(stackblitzForm => {
this.stackblitzForm = stackblitzForm;
this.isDisabled = false;
});
}

constructor(private plunkerWriter: PlunkerWriter) {}
constructor(private stackblitzWriter: StackblitzWriter) {}

openPlunker(): void {
openStackblitz(): void {
// When the form is submitted, it must be in the document body. The standard of forms is not
// to submit if it is detached from the document. See the following chromium commit for
// more details:
// https://chromium.googlesource.com/chromium/src/+/962c2a22ddc474255c776aefc7abeba00edc7470%5E!
document.body.appendChild(this.plunkerForm);
this.plunkerForm.submit();
document.body.removeChild(this.plunkerForm);
document.body.appendChild(this.stackblitzForm);
this.stackblitzForm.submit();
document.body.removeChild(this.stackblitzForm);
}
}

@NgModule({
imports: [MatTooltipModule, MatButtonModule, MatIconModule],
exports: [PlunkerButton],
declarations: [PlunkerButton],
providers: [PlunkerWriter],
exports: [StackblitzButton],
declarations: [StackblitzButton],
providers: [StackblitzWriter],
})
export class PlunkerButtonModule {}
export class StackblitzButtonModule {}
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,19 @@ import {
BaseRequestOptions, Http, HttpModule, Response, ResponseOptions,
XHRBackend
} from '@angular/http';
import {PlunkerWriter} from './plunker-writer';
import {StackblitzWriter} from './stackblitz-writer';
import {ExampleData} from '@angular/material-examples';


describe('PlunkerWriter', () => {
let plunkerWriter: PlunkerWriter;
describe('StackblitzWriter', () => {
let stackblitzWriter: StackblitzWriter;
let data: ExampleData;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [HttpModule],
declarations: [],
providers: [
PlunkerWriter,
StackblitzWriter,
BaseRequestOptions,
MockBackend,
{provide: XHRBackend, useExisting: MockBackend},
Expand All @@ -30,20 +30,20 @@ describe('PlunkerWriter', () => {
connection.mockRespond(getFakeDocResponse(url));
});

plunkerWriter = TestBed.get(PlunkerWriter);
stackblitzWriter = TestBed.get(StackblitzWriter);
data = new ExampleData('');
data.examplePath = 'http://material.angular.io/';
data.exampleFiles = ['test.ts', 'test.html', 'src/detail.ts'];
}));

it('should append correct copyright', () => {
expect(plunkerWriter._appendCopyright('test.ts', 'NoContent')).toBe(`NoContent
expect(stackblitzWriter._appendCopyright('test.ts', 'NoContent')).toBe(`NoContent
/** Copyright 2017 Google Inc. All Rights Reserved.
Use of this source code is governed by an MIT-style license that
can be found in the LICENSE file at http://angular.io/license */`);

expect(plunkerWriter._appendCopyright('test.html', 'NoContent')).toBe(`NoContent
expect(stackblitzWriter._appendCopyright('test.html', 'NoContent')).toBe(`NoContent
<!-- Copyright 2017 Google Inc. All Rights Reserved.
Use of this source code is governed by an MIT-style license that
Expand All @@ -52,26 +52,26 @@ describe('PlunkerWriter', () => {
});

it('should create form element', () => {
expect(plunkerWriter._createFormElement().outerHTML).toBe(
expect(stackblitzWriter._createFormElement().outerHTML).toBe(
`<form action="https://plnkr.co/edit/?p=preview" method="post" target="_blank"></form>`);
});

it('should add files to form input', () => {
let form = plunkerWriter._createFormElement();
let form = stackblitzWriter._createFormElement();

plunkerWriter._addFileToForm(form, data, 'NoContent', 'test.ts', 'path/to/file');
plunkerWriter._addFileToForm(form, data, 'Test', 'test.html', 'path/to/file');
plunkerWriter._addFileToForm(form, data, 'Detail', 'src/detail.ts', 'path/to/file');
stackblitzWriter._addFileToForm(form, data, 'NoContent', 'test.ts', 'path/to/file');
stackblitzWriter._addFileToForm(form, data, 'Test', 'test.html', 'path/to/file');
stackblitzWriter._addFileToForm(form, data, 'Detail', 'src/detail.ts', 'path/to/file');

expect(form.elements.length).toBe(3);
expect(form.elements[0].getAttribute('name')).toBe('files[test.ts]');
expect(form.elements[1].getAttribute('name')).toBe('files[test.html]');
expect(form.elements[2].getAttribute('name')).toBe('files[src/detail.ts]');
});

it('should open a new window with plunker url', fakeAsync(() => {
it('should open a new window with stackblitz url', fakeAsync(() => {
let form;
plunkerWriter.constructPlunkerForm(data).then(result => form = result);
stackblitzWriter.constructStackblitzForm(data).then(result => form = result);
flushMicrotasks();

expect(form.elements.length).toBe(11);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,50 +2,82 @@ import {Injectable} from '@angular/core';
import {Http} from '@angular/http';
import {ExampleData} from '@angular/material-examples';
import 'rxjs/add/operator/toPromise';
import {VERSION} from '@angular/material';

const PLUNKER_URL = 'https://plnkr.co/edit/?p=preview';
const STACKBLITZ_URL = 'https://run.stackblitz.com/api/angular/v1/';

const COPYRIGHT =
`Copyright 2017 Google Inc. All Rights Reserved.
Use of this source code is governed by an MIT-style license that
can be found in the LICENSE file at http://angular.io/license`;

const TEMPLATE_PATH = '/assets/plunker/';
const TEMPLATE_FILES = ['index.html', 'systemjs.config.js', 'main.ts'];
const TEMPLATE_PATH = '/assets/stackblitz/';
const TEMPLATE_FILES = [
'index.html',
'styles.scss',
'polyfills.ts',
'.angular-cli.json',
'main.ts'
];

const TAGS: string[] = ['angular', 'material', 'example'];
const angularVersion = '^5.0.0';
const materialVersion = '^5.0.0-rc.1';

const dependencies = {
'@angular/cdk': materialVersion,
'@angular/material': materialVersion,
'@angular/animations': angularVersion,
'@angular/common': angularVersion,
'@angular/compiler': angularVersion,
'@angular/core': angularVersion,
'@angular/forms': angularVersion,
'@angular/http': angularVersion,
'@angular/platform-browser': angularVersion,
'@angular/platform-browser-dynamic': angularVersion,
'@angular/router': angularVersion,
'angular-in-memory-web-api': '~0.5.0',
'core-js': '^2.4.1',
'rxjs': '^5.5.2',
'web-animations-js': '^2.3.1',
'zone.js': '^0.8.14',
'hammerjs': '^2.0.8'
};

/**
* Plunker writer, write example files to Plunker
* Stackblitz writer, write example files to stackblitz
*
* Plunker API
* URL: http://plnkr.co/edit/?p=preview
* StackBlitz API
* URL: https://run.stackblitz.com/api/aio/v1/
* data: {
* // File name, directory and content of files
* files[file-name1]: file-content1,
* files[directory-name/file-name2]: file-content2,
* // Can add multiple tags
* tags[0]: tag-0,
* // Description of plunker
* // Description of stackblitz
* description: description,
* // Private or not
* private: true
* // Dependencies
* dependencies: dependencies
* }
*/
@Injectable()
export class PlunkerWriter {
export class StackblitzWriter {
constructor(private _http: Http) {}

/**
* Returns an HTMLFormElement that will open a new plunker template with the example data when
* Returns an HTMLFormElement that will open a new stackblitz template with the example data when
* called with submit().
*/
constructPlunkerForm(data: ExampleData): Promise<HTMLFormElement> {
constructStackblitzForm(data: ExampleData): Promise<HTMLFormElement> {
let form = this._createFormElement();

TAGS.forEach((tag, i) => this._appendFormInput(form, `tags[${i}]`, tag));
this._appendFormInput(form, 'private', 'true');
this._appendFormInput(form, 'description', data.description);
this._appendFormInput(form, 'dependencies', JSON.stringify(dependencies));

return new Promise(resolve => {
let templateContents = TEMPLATE_FILES
Expand All @@ -65,10 +97,10 @@ export class PlunkerWriter {
});
}

/** Constructs a new form element that will navigate to the plunker url. */
/** Constructs a new form element that will navigate to the stackblitz url. */
_createFormElement(): HTMLFormElement {
const form = document.createElement('form');
form.action = PLUNKER_URL;
form.action = STACKBLITZ_URL;
form.method = 'post';
form.target = '_blank';
return form;
Expand Down Expand Up @@ -98,12 +130,14 @@ export class PlunkerWriter {
path: string) {
if (path == TEMPLATE_PATH) {
content = this._replaceExamplePlaceholderNames(data, filename, content);
} else {
filename = 'app/' + filename;
}
this._appendFormInput(form, `files[${filename}]`, this._appendCopyright(filename, content));
}

/**
* The Plunker template assets contain placeholder names for the examples:
* The stackblitz template assets contain placeholder names for the examples:
* "<material-docs-example>" and "MaterialDocsExample".
* This will replace those placeholders with the names from the example metadata,
* e.g. "<basic-button-example>" and "BasicButtonExample"
Expand All @@ -116,6 +150,7 @@ export class PlunkerWriter {
// For example, <material-docs-example></material-docs-example> will be replaced as
// <button-demo></button-demo>
fileContent = fileContent.replace(/material-docs-example/g, data.selectorName);
fileContent = fileContent.replace(/{{version}}/g, VERSION.full);
} else if (fileName == 'main.ts') {
// Replace the component name in `main.ts`.
// For example, `import {MaterialDocsExample} from 'material-docs-example'`
Expand Down
32 changes: 0 additions & 32 deletions src/assets/plunker/index.html

This file was deleted.

Loading

0 comments on commit f775248

Please sign in to comment.