fix(aio): avoid unnecessary re-calculations in live-examples (#23960)
With `plnkrs`, we used to choose a different plnkr mode (normal vs embedded) based on the size of the screen. This affected the layout of the plnkr page ("embedded" plnkr mode was usable on small screens, while "normal" mode wasn't). This is not to be confused with the live-example mode we use today to determine whether the live-example should be a link (that open StackBlitz on a new page) or embedded into the document (using an iframe). Since we no longer need to change the live-example URL based on the screen size, there is no need to listen for rezise events on Window. The necessary properties can be computed once and certain variables are obsolete. PR Close #23960
This commit is contained in:
parent
41fea84957
commit
f8c6947205
|
@ -2,7 +2,6 @@
|
||||||
<span #content style="display: none"><ng-content></ng-content></span>
|
<span #content style="display: none"><ng-content></ng-content></span>
|
||||||
|
|
||||||
<span [ngSwitch]="mode">
|
<span [ngSwitch]="mode">
|
||||||
<span *ngSwitchCase="'disabled'">{{title}} <em>(not available on this device)</em></span>
|
|
||||||
<span *ngSwitchCase="'embedded'">
|
<span *ngSwitchCase="'embedded'">
|
||||||
<div title="{{title}}">
|
<div title="{{title}}">
|
||||||
<aio-embedded-stackblitz [src]="stackblitz"></aio-embedded-stackblitz>
|
<aio-embedded-stackblitz [src]="stackblitz"></aio-embedded-stackblitz>
|
||||||
|
@ -17,7 +16,7 @@
|
||||||
<span *ngSwitchDefault>
|
<span *ngSwitchDefault>
|
||||||
<a [href]="stackblitz" target="_blank" title="{{title}}">{{title}}</a>
|
<a [href]="stackblitz" target="_blank" title="{{title}}">{{title}}</a>
|
||||||
<span *ngIf="enableDownload">
|
<span *ngIf="enableDownload">
|
||||||
/ <a [href]="zip" download title="Download example">download example</a>
|
/ <a [href]="zip" download title="Download example">download example</a>
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
|
|
|
@ -43,10 +43,6 @@ describe('LiveExampleComponent', () => {
|
||||||
// Trigger `ngAfterContentInit()`.
|
// Trigger `ngAfterContentInit()`.
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
|
||||||
// Ensure wide-screen by default.
|
|
||||||
liveExampleComponent.onResize(1033);
|
|
||||||
fixture.detectChanges();
|
|
||||||
|
|
||||||
testFn();
|
testFn();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -100,15 +96,6 @@ describe('LiveExampleComponent', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should have expected flat-style stackblitz when has `flat-style`', () => {
|
|
||||||
testPath = '/tutorial/toh-pt1';
|
|
||||||
setHostTemplate('<live-example flat-style></live-example>');
|
|
||||||
testComponent(() => {
|
|
||||||
// The file should be "stackblitz.html", not "stackblitz.html"
|
|
||||||
expect(getLiveExampleAnchor().href).toContain('/stackblitz.html');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should have expected stackblitz & download hrefs when has example directory (name)', () => {
|
it('should have expected stackblitz & download hrefs when has example directory (name)', () => {
|
||||||
testPath = '/guide/somewhere';
|
testPath = '/guide/somewhere';
|
||||||
setHostTemplate('<live-example name="toh-pt1"></live-example>');
|
setHostTemplate('<live-example name="toh-pt1"></live-example>');
|
||||||
|
@ -147,15 +134,7 @@ describe('LiveExampleComponent', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should be flat style when flat-style requested', () => {
|
it('should not have a download link when `noDownload` attr present', () => {
|
||||||
setHostTemplate('<live-example flat-style></live-example>');
|
|
||||||
testComponent(() => {
|
|
||||||
const hrefs = getHrefs();
|
|
||||||
expect(hrefs[0]).toContain(defaultTestPath + '/stackblitz.html');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not have a download link when `noDownload` atty present', () => {
|
|
||||||
setHostTemplate('<live-example noDownload></live-example>');
|
setHostTemplate('<live-example noDownload></live-example>');
|
||||||
testComponent(() => {
|
testComponent(() => {
|
||||||
const hrefs = getHrefs();
|
const hrefs = getHrefs();
|
||||||
|
@ -164,7 +143,7 @@ describe('LiveExampleComponent', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should only have a download link when `downloadOnly` atty present', () => {
|
it('should only have a download link when `downloadOnly` attr present', () => {
|
||||||
setHostTemplate('<live-example downloadOnly>download this</live-example>');
|
setHostTemplate('<live-example downloadOnly>download this</live-example>');
|
||||||
testComponent(() => {
|
testComponent(() => {
|
||||||
const hrefs = getHrefs();
|
const hrefs = getHrefs();
|
||||||
|
@ -248,27 +227,4 @@ describe('LiveExampleComponent', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('when narrow display (mobile)', () => {
|
|
||||||
|
|
||||||
it('should be embedded style when no style defined', () => {
|
|
||||||
setHostTemplate('<live-example></live-example>');
|
|
||||||
testComponent(() => {
|
|
||||||
liveExampleComponent.onResize(600); // narrow
|
|
||||||
fixture.detectChanges();
|
|
||||||
const hrefs = getHrefs();
|
|
||||||
expect(hrefs[0]).toContain(defaultTestPath + '/stackblitz.html');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should be embedded style even when flat-style requested', () => {
|
|
||||||
setHostTemplate('<live-example flat-style></live-example>');
|
|
||||||
testComponent(() => {
|
|
||||||
liveExampleComponent.onResize(600); // narrow
|
|
||||||
fixture.detectChanges();
|
|
||||||
const hrefs = getHrefs();
|
|
||||||
expect(hrefs[0]).toContain(defaultTestPath + '/stackblitz.html');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,131 +1,131 @@
|
||||||
/* tslint:disable component-selector */
|
/* tslint:disable component-selector */
|
||||||
import { AfterContentInit, AfterViewInit, Component, ElementRef, HostListener, Input, ViewChild } from '@angular/core';
|
import { AfterContentInit, AfterViewInit, Component, ElementRef, Input, ViewChild } from '@angular/core';
|
||||||
import { Location } from '@angular/common';
|
import { Location } from '@angular/common';
|
||||||
import { CONTENT_URL_PREFIX } from 'app/documents/document.service';
|
import { CONTENT_URL_PREFIX } from 'app/documents/document.service';
|
||||||
|
import { AttrMap, boolFromValue, getAttrs, getAttrValue } from 'app/shared/attribute-utils';
|
||||||
|
|
||||||
import { boolFromValue, getAttrs, getAttrValue } from 'app/shared/attribute-utils';
|
|
||||||
|
|
||||||
const liveExampleBase = CONTENT_URL_PREFIX + 'live-examples/';
|
const LIVE_EXAMPLE_BASE = CONTENT_URL_PREFIX + 'live-examples/';
|
||||||
const zipBase = CONTENT_URL_PREFIX + 'zips/';
|
const ZIP_BASE = CONTENT_URL_PREFIX + 'zips/';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Angular.io Live Example Embedded Component
|
* Angular.io Live Example Embedded Component
|
||||||
*
|
*
|
||||||
* Renders a link to a live/host example of the doc page.
|
* Renders a link to a live/host example of the doc page.
|
||||||
*
|
*
|
||||||
* All attributes and the text content are optional
|
* All attributes and the text content are optional
|
||||||
*
|
*
|
||||||
* Usage:
|
* Usage:
|
||||||
* <live-example
|
* <live-example
|
||||||
* [name="..."] // name of the example directory
|
* [name="..."] // name of the example directory
|
||||||
* [stackblitz="...""] // name of the stackblitz file (becomes part of zip file name as well)
|
* [stackblitz="...""] // name of the stackblitz file (becomes part of zip file name as well)
|
||||||
* [embedded] // embed the stackblitz in the doc page, else display in new browser tab (default)
|
* [embedded] // embed the stackblitz in the doc page, else display in new browser tab (default)
|
||||||
* [noDownload] // no downloadable zip option
|
* [noDownload] // no downloadable zip option
|
||||||
* [downloadOnly] // just the zip
|
* [downloadOnly] // just the zip
|
||||||
* [title="..."]> // text for live example link and tooltip
|
* [title="..."]> // text for live example link and tooltip
|
||||||
* text // higher precedence way to specify text for live example link and tooltip
|
* text // higher precedence way to specify text for live example link and tooltip
|
||||||
* </live-example>
|
* </live-example>
|
||||||
* Example:
|
* Example:
|
||||||
* <p>Run <live-example>Try the live example</live-example></p>.
|
* <p>Run <live-example>Try the live example</live-example></p>.
|
||||||
* // ~/resources/live-examples/{page}/stackblitz.json
|
* // ~/resources/live-examples/{page}/stackblitz.json
|
||||||
*
|
*
|
||||||
* <p>Run <live-example name="toh-pt1">this example</live-example></p>.
|
* <p>Run <live-example name="toh-pt1">this example</live-example></p>.
|
||||||
* // ~/resources/live-examples/toh-pt1/stackblitz.json
|
* // ~/resources/live-examples/toh-pt1/stackblitz.json
|
||||||
*
|
*
|
||||||
* // Link to the default stackblitz in the toh-pt1 sample
|
* // Link to the default stackblitz in the toh-pt1 sample
|
||||||
* // The title overrides default ("live example") with "Tour of Heroes - Part 1"
|
* // The title overrides default ("live example") with "Tour of Heroes - Part 1"
|
||||||
* <p>Run <live-example name="toh-pt1" title="Tour of Heroes - Part 1"></live-example></p>.
|
* <p>Run <live-example name="toh-pt1" title="Tour of Heroes - Part 1"></live-example></p>.
|
||||||
* // ~/resources/live-examples/toh-pt1/stackblitz.json
|
* // ~/resources/live-examples/toh-pt1/stackblitz.json
|
||||||
*
|
*
|
||||||
* <p>Run <live-example stackblitz="minimal"></live-example></p>.
|
* <p>Run <live-example stackblitz="minimal"></live-example></p>.
|
||||||
* // ~/resources/live-examples/{page}/minimal.stackblitz.json
|
* // ~/resources/live-examples/{page}/minimal.stackblitz.json
|
||||||
*
|
*
|
||||||
* // Embed the current page's default stackblitz
|
* // Embed the current page's default stackblitz
|
||||||
* // Text within tag is "live example"
|
* // Text within tag is "live example"
|
||||||
* // No title (no tooltip)
|
* // No title (no tooltip)
|
||||||
* <live-example embedded title=""></live-example>
|
* <live-example embedded title=""></live-example>
|
||||||
* // ~/resources/live-examples/{page}/stackblitz.json
|
* // ~/resources/live-examples/{page}/stackblitz.json
|
||||||
*
|
*
|
||||||
* // Displays within the document page as an embedded style stackblitz editor
|
* // Displays within the document page as an embedded style stackblitz editor
|
||||||
* <live-example name="toh-pt1" embedded stackblitz="minimal">Tour of Heroes - Part 1</live-example>
|
* <live-example name="toh-pt1" embedded stackblitz="minimal">Tour of Heroes - Part 1</live-example>
|
||||||
* // ~/resources/live-examples/toh-pt1/minimal.stackblitz.json
|
* // ~/resources/live-examples/toh-pt1/minimal.stackblitz.json
|
||||||
*/
|
*/
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'live-example',
|
selector: 'live-example',
|
||||||
templateUrl: 'live-example.component.html'
|
templateUrl: 'live-example.component.html'
|
||||||
})
|
})
|
||||||
export class LiveExampleComponent implements AfterContentInit {
|
export class LiveExampleComponent implements AfterContentInit {
|
||||||
|
|
||||||
// Will force to embedded-style when viewport width is narrow
|
readonly mode: 'default' | 'embedded' | 'downloadOnly';
|
||||||
// "narrow" value was picked based on phone dimensions from http://screensiz.es/phone
|
readonly enableDownload: boolean;
|
||||||
readonly narrowWidth = 1000;
|
readonly stackblitz: string;
|
||||||
|
readonly zip: string;
|
||||||
attrs: any;
|
|
||||||
enableDownload = true;
|
|
||||||
exampleDir: string;
|
|
||||||
isEmbedded = false;
|
|
||||||
mode = 'disabled';
|
|
||||||
stackblitz: string;
|
|
||||||
stackblitzName: string;
|
|
||||||
title: string;
|
title: string;
|
||||||
zip: string;
|
|
||||||
zipName: string;
|
|
||||||
|
|
||||||
@ViewChild('content')
|
@ViewChild('content')
|
||||||
private content: ElementRef;
|
private content: ElementRef;
|
||||||
|
|
||||||
constructor(
|
constructor(elementRef: ElementRef, location: Location) {
|
||||||
private elementRef: ElementRef,
|
const attrs = getAttrs(elementRef);
|
||||||
location: Location ) {
|
const exampleDir = this.getExampleDir(attrs, location.path(false));
|
||||||
|
const stackblitzName = this.getStackblitzName(attrs);
|
||||||
|
|
||||||
const attrs = this.attrs = getAttrs(this.elementRef);
|
this.mode = this.getMode(attrs);
|
||||||
let exampleDir = attrs.name;
|
this.enableDownload = this.getEnableDownload(attrs);
|
||||||
if (!exampleDir) {
|
this.stackblitz = this.getStackblitz(exampleDir, stackblitzName, this.mode === 'embedded');
|
||||||
// take last segment, excluding hash fragment and query params
|
this.zip = this.getZip(exampleDir, stackblitzName);
|
||||||
exampleDir = (location.path(false).match(/[^\/?\#]+(?=\/?(?:$|\#|\?))/) || [])[0];
|
this.title = this.getTitle(attrs);
|
||||||
}
|
|
||||||
this.exampleDir = exampleDir.trim();
|
|
||||||
this.zipName = exampleDir.indexOf('/') === -1 ? this.exampleDir : exampleDir.split('/')[0];
|
|
||||||
this.stackblitzName = attrs.stackblitz ? attrs.stackblitz.trim() + '.' : '';
|
|
||||||
this.zip = `${zipBase}${exampleDir}/${this.stackblitzName}${this.zipName}.zip`;
|
|
||||||
|
|
||||||
this.enableDownload = !boolFromValue(getAttrValue(attrs, 'nodownload'));
|
|
||||||
|
|
||||||
if (boolFromValue(getAttrValue(attrs, 'downloadonly'))) {
|
|
||||||
this.mode = 'downloadOnly';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
calcStackblitzLink(width: number) {
|
|
||||||
|
|
||||||
const attrs = this.attrs;
|
|
||||||
const exampleDir = this.exampleDir;
|
|
||||||
let urlQuery = '';
|
|
||||||
|
|
||||||
this.mode = 'default'; // display in another browser tab by default
|
|
||||||
|
|
||||||
this.isEmbedded = boolFromValue(attrs.embedded);
|
|
||||||
|
|
||||||
if (this.isEmbedded) {
|
|
||||||
this.mode = 'embedded'; // display embedded in the doc
|
|
||||||
urlQuery = '?ctl=1';
|
|
||||||
}
|
|
||||||
|
|
||||||
this.stackblitz = `${liveExampleBase}${exampleDir}/${this.stackblitzName}stackblitz.html${urlQuery}`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ngAfterContentInit() {
|
ngAfterContentInit() {
|
||||||
// Angular will sanitize this title when displayed so should be plain text.
|
// Angular will sanitize this title when displayed, so it should be plain text.
|
||||||
const title = this.content.nativeElement.textContent;
|
const textContent = this.content.nativeElement.textContent.trim();
|
||||||
this.title = (title || this.attrs.title || 'live example').trim();
|
if (textContent) {
|
||||||
this.onResize(window.innerWidth);
|
this.title = textContent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@HostListener('window:resize', ['$event.target.innerWidth'])
|
private getEnableDownload(attrs: AttrMap) {
|
||||||
onResize(width: number) {
|
const downloadDisabled = boolFromValue(getAttrValue(attrs, 'noDownload'));
|
||||||
if (this.mode !== 'downloadOnly') {
|
return !downloadDisabled;
|
||||||
this.calcStackblitzLink(width);
|
}
|
||||||
|
|
||||||
|
private getExampleDir(attrs: AttrMap, path: string) {
|
||||||
|
let exampleDir = getAttrValue(attrs, 'name');
|
||||||
|
if (!exampleDir) {
|
||||||
|
// Take the last path segment, excluding query params and hash fragment.
|
||||||
|
const match = path.match(/[^/?#]+(?=\/?(?:\?|#|$))/);
|
||||||
|
exampleDir = match ? match[0] : 'index';
|
||||||
}
|
}
|
||||||
|
return exampleDir.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
private getMode(this: LiveExampleComponent, attrs: AttrMap): typeof this.mode {
|
||||||
|
const downloadOnly = boolFromValue(getAttrValue(attrs, 'downloadOnly'));
|
||||||
|
const isEmbedded = boolFromValue(getAttrValue(attrs, 'embedded'));
|
||||||
|
|
||||||
|
return downloadOnly ? 'downloadOnly'
|
||||||
|
: isEmbedded ? 'embedded' :
|
||||||
|
'default';
|
||||||
|
}
|
||||||
|
|
||||||
|
private getStackblitz(exampleDir: string, stackblitzName: string, isEmbedded: boolean) {
|
||||||
|
const urlQuery = isEmbedded ? '?ctl=1' : '';
|
||||||
|
return `${LIVE_EXAMPLE_BASE}${exampleDir}/${stackblitzName}stackblitz.html${urlQuery}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getStackblitzName(attrs: AttrMap) {
|
||||||
|
const attrValue = (getAttrValue(attrs, 'stackblitz') || '').trim();
|
||||||
|
return attrValue && `${attrValue}.`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getTitle(attrs: AttrMap) {
|
||||||
|
return (getAttrValue(attrs, 'title') || 'live example').trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
private getZip(exampleDir: string, stackblitzName: string) {
|
||||||
|
const zipName = exampleDir.split('/')[0];
|
||||||
|
return `${ZIP_BASE}${exampleDir}/${stackblitzName}${zipName}.zip`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -137,7 +137,7 @@ export class LiveExampleComponent implements AfterContentInit {
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'aio-embedded-stackblitz',
|
selector: 'aio-embedded-stackblitz',
|
||||||
template: `<iframe #iframe frameborder="0" width="100%" height="100%"></iframe>`,
|
template: `<iframe #iframe frameborder="0" width="100%" height="100%"></iframe>`,
|
||||||
styles: [ 'iframe { min-height: 400px; }']
|
styles: [ 'iframe { min-height: 400px; }' ]
|
||||||
})
|
})
|
||||||
export class EmbeddedStackblitzComponent implements AfterViewInit {
|
export class EmbeddedStackblitzComponent implements AfterViewInit {
|
||||||
@Input() src: string;
|
@Input() src: string;
|
||||||
|
|
Loading…
Reference in New Issue