Compare commits
260 Commits
6.0.2
...
ivy-svg-sy
Author | SHA1 | Date | |
---|---|---|---|
b1d8784f79 | |||
6a4ad11e87 | |||
a1a3af9e23 | |||
2a78d5e6fe | |||
95074ca303 | |||
1cd9e6c2eb | |||
855d9c00e0 | |||
49d97f1ba0 | |||
62f751cd87 | |||
646b42a113 | |||
24e5c5b425 | |||
42a7295203 | |||
7c39216083 | |||
223882aeb6 | |||
aafb46a8fe | |||
c73196eb59 | |||
d6595ebd39 | |||
5b25c07795 | |||
3ed7fc6686 | |||
7c1bd7170e | |||
2e21690c66 | |||
f6f44edcc0 | |||
90bf5d8961 | |||
b87d650da2 | |||
e53179ef8c | |||
31795b620f | |||
41cd8f3efb | |||
3fd3c2ac4c | |||
1eafd04eb3 | |||
00c4751f37 | |||
c2e131119b | |||
7866684f2b | |||
6e05ae02a2 | |||
431a42a238 | |||
7a9c987e56 | |||
ae86cb3be0 | |||
a6f34be9f5 | |||
d74078fb88 | |||
ddd6124802 | |||
96a0e131bf | |||
e43d3fa4b7 | |||
7657535718 | |||
3de80fc7fb | |||
729c797890 | |||
188ff848d2 | |||
280a784fe3 | |||
4f36340de7 | |||
83bb5d1922 | |||
3e39fef274 | |||
36cc72ee5b | |||
01b5acd7cf | |||
186118e684 | |||
609e6b9787 | |||
68bf8c36c6 | |||
8216657681 | |||
13cb75da8b | |||
23a98b9e51 | |||
bd149e5d67 | |||
fb906a87e8 | |||
0bdd30e34f | |||
373fa78d7f | |||
26fbf1d13c | |||
608c3748e8 | |||
6d8c847e7b | |||
919f42fea1 | |||
1b6b936ef4 | |||
db2329ef6a | |||
de267e97c9 | |||
f8c6947205 | |||
41fea84957 | |||
a7b07defe1 | |||
6e7d071c6b | |||
99d330a1b7 | |||
3cdf5afc6e | |||
ea4321d912 | |||
88ab1d0e55 | |||
20d76374ed | |||
8ee25e6b58 | |||
43597279d6 | |||
55103419e9 | |||
547efb5f4d | |||
091b11a4ab | |||
4042a84ad6 | |||
6a24c02d73 | |||
b7c417f618 | |||
313bdce590 | |||
5cf82f8f3f | |||
e5e5c24d48 | |||
1d378e2987 | |||
017d67cdf8 | |||
83631b28cb | |||
d4b6c41a5f | |||
66b2d78305 | |||
67b8d57a8d | |||
02acb5e3e5 | |||
a2e8b3a6a8 | |||
d4b8b24406 | |||
cfde36da84 | |||
d889f57ae2 | |||
816bc8af17 | |||
d2a86872a9 | |||
474dbf09ec | |||
e129b18d17 | |||
8a27a034c4 | |||
4ecae6449e | |||
5e307d5ba7 | |||
089fe83865 | |||
b1cda3639f | |||
c4221dad11 | |||
fe3679a356 | |||
72eab4d254 | |||
db2d67cc00 | |||
117c7eebc3 | |||
89f64e58c3 | |||
553a680817 | |||
858e48a794 | |||
e942d8b681 | |||
f1e4a153f0 | |||
e0ed59e55f | |||
d6b1466c81 | |||
d1abf4e897 | |||
08e7efc69e | |||
46674d5fac | |||
c5ca5c0d9f | |||
61170856ee | |||
a800ccd922 | |||
971e78dc35 | |||
b0eca85e51 | |||
d01ec03f54 | |||
9e2d87f5b8 | |||
fc034270ce | |||
77ff72f93b | |||
44095d95c9 | |||
e3518967ad | |||
005dc8f68b | |||
7e9649bdf1 | |||
e3e15773ee | |||
b25e15c317 | |||
3b067c8579 | |||
57cf5509e6 | |||
3f20a5c7c8 | |||
14d8a98001 | |||
5cb36ed706 | |||
490e39a23f | |||
33c1c1df36 | |||
d8d4f654a6 | |||
2c4850dc58 | |||
2ef4760ff7 | |||
52f0e3cc3b | |||
61265b42ef | |||
6601d0f7ba | |||
cccc328a52 | |||
65211f46cf | |||
da9ff255dd | |||
2cf6244b1d | |||
b45fa5e263 | |||
d7ed9c9e9e | |||
266d97de95 | |||
d71329d55c | |||
7ba26b140b | |||
297723d0bc | |||
bb07fbde76 | |||
d7e8d15578 | |||
bfad6b4fa1 | |||
fd9d1888ce | |||
94fbe3b5ac | |||
56828e43b6 | |||
c5cfc3a1b6 | |||
b76f5a6a7d | |||
fb41b7dc30 | |||
ca1019a950 | |||
9ebf0c8e5e | |||
8062f7de9e | |||
cc6c4346c2 | |||
4cb46ce10c | |||
7ef9d4a582 | |||
a522bb9f03 | |||
31b96e99ff | |||
b7a6e1fef7 | |||
84b4593d01 | |||
0c6dc45c85 | |||
5b96078624 | |||
1a44a0b4a8 | |||
b1f040f5a2 | |||
eb031c6ff1 | |||
b4c252bcc5 | |||
db77d8dc92 | |||
ab5bc42da0 | |||
f567e1898f | |||
8d0ee34939 | |||
43a49d3f64 | |||
811a7f2863 | |||
9ed5fb6d2c | |||
e1c4930a1a | |||
dab5df9734 | |||
b1d03fe70b | |||
06c0d9666f | |||
1c9200eca8 | |||
ace6440460 | |||
b26ac1c22f | |||
60e5507076 | |||
4cfa571258 | |||
999ab0a690 | |||
ba47997715 | |||
a35bf114eb | |||
6761a64522 | |||
0b47902ad7 | |||
4662878a1f | |||
ca776c59dd | |||
f2563ca800 | |||
9757347e71 | |||
a19e018439 | |||
6ff164be0e | |||
84f024309a | |||
c6b206ee4b | |||
1d1e75ee2b | |||
acf6781ccc | |||
fd48e53986 | |||
fe312ccb4c | |||
764f471dc0 | |||
8b02c0e769 | |||
5a2ee7a6f5 | |||
529d4fc9ee | |||
fac7dde5b1 | |||
1f005908a4 | |||
2278fe8f0e | |||
aad3444a58 | |||
44377adbcc | |||
b28b3acb83 | |||
7493435911 | |||
937f7cea37 | |||
7d1990e4d1 | |||
76f8ae31ad | |||
103846a51d | |||
0a536af093 | |||
4f29287399 | |||
62e6c1f43a | |||
c3c513ed9e | |||
ed495bc9f1 | |||
a3de5f8f20 | |||
2491b7249a | |||
a851ba3781 | |||
0468a649af | |||
47d3acdc49 | |||
acbfb9eb4d | |||
d35f84a167 | |||
87e9f333d4 | |||
08fc4f3ad8 | |||
c6c79ab5dc | |||
6837491f08 | |||
fc5af69fb2 | |||
81ccb718b1 | |||
0c56dfadef | |||
7be7abdebd | |||
5a1ddee88c | |||
99f8e10809 | |||
d665d9a18c | |||
8b2101be9f | |||
0d56cee9e1 | |||
7f612fc828 |
@ -85,7 +85,7 @@ jobs:
|
||||
# This avoids waiting for the slowest build target to finish before running the first test
|
||||
# See https://github.com/bazelbuild/bazel/issues/4257
|
||||
# NOTE: Angular developers should typically just bazel build //packages/... or bazel test //packages/...
|
||||
- run: bazel query --output=label //... | xargs bazel test
|
||||
- run: bazel query --output=label //... | xargs bazel test --build_tag_filters=-ivy-only --test_tag_filters=-manual,-ivy-only
|
||||
|
||||
# CircleCI will allow us to go back and view/download these artifacts from past builds.
|
||||
# Also we can use a service like https://buildsize.org/ to automatically track binary size of these artifacts.
|
||||
|
1
.github/angular-robot.yml
vendored
@ -4,6 +4,7 @@
|
||||
size:
|
||||
disabled: false
|
||||
maxSizeIncrease: 1000
|
||||
circleCiStatusName: "ci/circleci: build-packages-dist"
|
||||
status:
|
||||
disabled: false
|
||||
context: "ci/angular: size"
|
||||
|
@ -163,7 +163,7 @@ groups:
|
||||
files:
|
||||
- "packages/compiler/*"
|
||||
users:
|
||||
- chuckjaz #primary
|
||||
- alxhub #primary
|
||||
- vicb
|
||||
- mhevery
|
||||
- IgorMinar #fallback
|
||||
|
10
CHANGELOG.md
@ -1,3 +1,13 @@
|
||||
<a name="6.0.3"></a>
|
||||
## [6.0.3](https://github.com/angular/angular/compare/6.0.2...6.0.3) (2018-05-22)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **service-worker:** check platformBrowser before accessing navigator.serviceWorker ([#21231](https://github.com/angular/angular/issues/21231)) ([0ee5b7e](https://github.com/angular/angular/commit/0ee5b7e))
|
||||
|
||||
|
||||
|
||||
<a name="6.0.2"></a>
|
||||
## [6.0.2](https://github.com/angular/angular/compare/6.0.1...6.0.2) (2018-05-15)
|
||||
|
||||
|
18
WORKSPACE
@ -13,16 +13,16 @@ http_archive(
|
||||
|
||||
http_archive(
|
||||
name = "io_bazel_rules_webtesting",
|
||||
url = "https://github.com/bazelbuild/rules_webtesting/archive/cfcaaf98553fee8e7063b5f5c11fd1b77e43d683.zip",
|
||||
strip_prefix = "rules_webtesting-cfcaaf98553fee8e7063b5f5c11fd1b77e43d683",
|
||||
sha256 = "636c7a9ac2ca13a04d982c2f9c874876ecc90a7b9ccfe4188156122b26ada7b3",
|
||||
url = "https://github.com/bazelbuild/rules_webtesting/archive/v0.2.0.zip",
|
||||
strip_prefix = "rules_webtesting-0.2.0",
|
||||
sha256 = "cecc12f07e95740750a40d38e8b14b76fefa1551bef9332cb432d564d693723c",
|
||||
)
|
||||
|
||||
http_archive(
|
||||
name = "build_bazel_rules_typescript",
|
||||
url = "https://github.com/bazelbuild/rules_typescript/archive/v0.13.0.zip",
|
||||
strip_prefix = "rules_typescript-0.13.0",
|
||||
sha256 = "8f2767ff56ad68c80c62e9a1cdc2ba2c2ba0b19d350f713365e5333045df02e3",
|
||||
url = "https://github.com/bazelbuild/rules_typescript/archive/3b86d6d46269fb52d4c6f1416868869e847feac2.zip",
|
||||
strip_prefix = "rules_typescript-3b86d6d46269fb52d4c6f1416868869e847feac2",
|
||||
sha256 = "f67e5fbe4a2b34b3ead9fe56f22b713540c23b501bd24d661d3fb047071dc2c1",
|
||||
)
|
||||
|
||||
http_archive(
|
||||
@ -66,9 +66,9 @@ http_archive(
|
||||
|
||||
http_archive(
|
||||
name = "org_brotli",
|
||||
url = "https://github.com/google/brotli/archive/c6333e1e79fb62ea088443f192293f964409b04e.zip",
|
||||
strip_prefix = "brotli-c6333e1e79fb62ea088443f192293f964409b04e",
|
||||
sha256 = "3f781988dee7dd3bcce2bf238294663cfaaf3b6433505bdb762e24d0a284d1dc",
|
||||
url = "https://github.com/google/brotli/archive/f9b8c02673c576a3e807edbf3a9328e9e7af6d7c.zip",
|
||||
strip_prefix = "brotli-f9b8c02673c576a3e807edbf3a9328e9e7af6d7c",
|
||||
sha256 = "8a517806d2b7c8505ba5c53934e7d7c70d341b68ffd268e9044d35b564a48828",
|
||||
)
|
||||
|
||||
#
|
||||
|
@ -3,7 +3,7 @@
|
||||
set -eux -o pipefail
|
||||
exec 3>&1
|
||||
|
||||
echo "\n\n[`date`] - Updating the preview server..."
|
||||
echo -e "\n\n[`date`] - Updating the preview server..."
|
||||
|
||||
# Input
|
||||
readonly HOST_REPO_DIR=$1
|
||||
|
@ -5,7 +5,7 @@
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { HttpModule } from '@angular/http';
|
||||
import { HttpClientModule } from '@angular/common/http';
|
||||
|
||||
import { AppComponent } from './app.component';
|
||||
// #docregion directive-import
|
||||
@ -24,7 +24,7 @@ import { ItemDirective } from './item.directive';
|
||||
imports: [
|
||||
BrowserModule,
|
||||
FormsModule,
|
||||
HttpModule
|
||||
HttpClientModule
|
||||
],
|
||||
providers: [],
|
||||
bootstrap: [AppComponent]
|
||||
|
@ -1,27 +0,0 @@
|
||||
# MasterProject
|
||||
|
||||
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 1.0.0-rc.0.
|
||||
|
||||
## Development server
|
||||
Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files.
|
||||
|
||||
## Code scaffolding
|
||||
|
||||
Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive/pipe/service/class/module`.
|
||||
|
||||
## Build
|
||||
|
||||
Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `-prod` flag for a production build.
|
||||
|
||||
## Running unit tests
|
||||
|
||||
Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
|
||||
|
||||
## Running end-to-end tests
|
||||
|
||||
Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/).
|
||||
Before running the tests make sure you are serving the app via `ng serve`.
|
||||
|
||||
## Further help
|
||||
|
||||
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md).
|
@ -5,18 +5,18 @@ import { Component, EventEmitter, Input, Output } from '@angular/core';
|
||||
selector: 'app-voter',
|
||||
template: `
|
||||
<h4>{{name}}</h4>
|
||||
<button (click)="vote(true)" [disabled]="voted">Agree</button>
|
||||
<button (click)="vote(false)" [disabled]="voted">Disagree</button>
|
||||
<button (click)="vote(true)" [disabled]="didVote">Agree</button>
|
||||
<button (click)="vote(false)" [disabled]="didVote">Disagree</button>
|
||||
`
|
||||
})
|
||||
export class VoterComponent {
|
||||
@Input() name: string;
|
||||
@Output() onVoted = new EventEmitter<boolean>();
|
||||
voted = false;
|
||||
@Output() voted = new EventEmitter<boolean>();
|
||||
didVote = false;
|
||||
|
||||
vote(agreed: boolean) {
|
||||
this.onVoted.emit(agreed);
|
||||
this.voted = true;
|
||||
this.voted.emit(agreed);
|
||||
this.didVote = true;
|
||||
}
|
||||
}
|
||||
// #enddocregion
|
||||
|
@ -8,7 +8,7 @@ import { Component } from '@angular/core';
|
||||
<h3>Agree: {{agreed}}, Disagree: {{disagreed}}</h3>
|
||||
<app-voter *ngFor="let voter of voters"
|
||||
[name]="voter"
|
||||
(onVoted)="onVoted($event)">
|
||||
(voted)="onVoted($event)">
|
||||
</app-voter>
|
||||
`
|
||||
})
|
||||
|
@ -1,7 +1,7 @@
|
||||
// #docregion
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { HttpModule } from '@angular/http';
|
||||
import { HttpClientModule } from '@angular/common/http';
|
||||
|
||||
// import { AppRoutingModule } from './app-routing.module';
|
||||
import { LocationStrategy,
|
||||
@ -54,7 +54,7 @@ const c_components = [
|
||||
imports: [
|
||||
BrowserModule,
|
||||
FormsModule,
|
||||
HttpModule,
|
||||
HttpClientModule,
|
||||
InMemoryWebApiModule.forRoot(HeroData)
|
||||
// AppRoutingModule TODO: add routes
|
||||
],
|
||||
|
@ -3,7 +3,7 @@
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { HttpModule } from '@angular/http';
|
||||
import { HttpClientModule } from '@angular/common/http';
|
||||
|
||||
import { AppComponent } from './app.component';
|
||||
// import the feature module here so you can add it to the imports array below
|
||||
@ -17,7 +17,7 @@ import { CustomerDashboardModule } from './customer-dashboard/customer-dashboard
|
||||
imports: [
|
||||
BrowserModule,
|
||||
FormsModule,
|
||||
HttpModule,
|
||||
HttpClientModule,
|
||||
CustomerDashboardModule // add the feature module here
|
||||
],
|
||||
providers: [],
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { HttpModule } from '@angular/http';
|
||||
import { HttpClientModule } from '@angular/common/http';
|
||||
import { AppRoutingModule } from './app-routing.module';
|
||||
|
||||
import { AppComponent } from './app.component';
|
||||
@ -13,7 +13,7 @@ import { AppComponent } from './app.component';
|
||||
imports: [
|
||||
BrowserModule,
|
||||
FormsModule,
|
||||
HttpModule,
|
||||
HttpClientModule,
|
||||
AppRoutingModule
|
||||
],
|
||||
providers: [],
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { HttpModule } from '@angular/http';
|
||||
import { HttpClientModule } from '@angular/common/http';
|
||||
|
||||
/* App Root */
|
||||
import { AppComponent } from './app.component';
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
|
||||
import { HttpModule } from '@angular/http';
|
||||
import { HttpClientModule } from '@angular/common/http';
|
||||
import { InMemoryWebApiModule } from 'angular-in-memory-web-api';
|
||||
|
||||
import { RouterModule } from '@angular/router';
|
||||
@ -44,7 +44,7 @@ import * as s0901 from '../09-01/app/app.module';
|
||||
@NgModule({
|
||||
imports: [
|
||||
BrowserModule,
|
||||
HttpModule,
|
||||
HttpClientModule,
|
||||
InMemoryWebApiModule.forRoot(HeroData),
|
||||
|
||||
s0101.AppModule,
|
||||
|
@ -1,26 +1,26 @@
|
||||
// Import spec files individually for Stackblitz
|
||||
import 'app/about/about.component.spec.ts';
|
||||
import 'app/app-initial.component.spec.ts';
|
||||
import 'app/app.component.router.spec.ts';
|
||||
import 'app/app.component.spec.ts';
|
||||
import 'app/banner/banner-initial.component.spec.ts';
|
||||
import 'app/banner/banner.component.spec.ts';
|
||||
import 'app/banner/banner.component.detect-changes.spec.ts';
|
||||
import 'app/banner/banner-external.component.spec.ts';
|
||||
import 'app/dashboard/dashboard-hero.component.spec.ts';
|
||||
import 'app/dashboard/dashboard.component.no-testbed.spec.ts';
|
||||
import 'app/dashboard/dashboard.component.spec.ts';
|
||||
import 'app/demo/async-helper.spec.ts';
|
||||
import 'app/demo/demo.spec.ts';
|
||||
import 'app/demo/demo.testbed.spec.ts';
|
||||
import 'app/hero/hero-detail.component.no-testbed.spec.ts';
|
||||
import 'app/hero/hero-detail.component.spec.ts';
|
||||
import 'app/hero/hero-list.component.spec.ts';
|
||||
import 'app/model/hero.service.spec.ts';
|
||||
import 'app/model/http-hero.service.spec.ts';
|
||||
import 'app/model/testing/http-client.spec.ts';
|
||||
import 'app/shared/highlight.directive.spec.ts';
|
||||
import 'app/shared/title-case.pipe.spec.ts';
|
||||
import 'app/twain/twain.component.spec.ts';
|
||||
import 'app/twain/twain.component.marbles.spec.ts';
|
||||
import 'app/welcome/welcome.component.spec.ts';
|
||||
import './app/about/about.component.spec.ts';
|
||||
import './app/app-initial.component.spec.ts';
|
||||
import './app/app.component.router.spec.ts';
|
||||
import './app/app.component.spec.ts';
|
||||
import './app/banner/banner-initial.component.spec.ts';
|
||||
import './app/banner/banner.component.spec.ts';
|
||||
import './app/banner/banner.component.detect-changes.spec.ts';
|
||||
import './app/banner/banner-external.component.spec.ts';
|
||||
import './app/dashboard/dashboard-hero.component.spec.ts';
|
||||
import './app/dashboard/dashboard.component.no-testbed.spec.ts';
|
||||
import './app/dashboard/dashboard.component.spec.ts';
|
||||
import './app/demo/async-helper.spec.ts';
|
||||
import './app/demo/demo.spec.ts';
|
||||
import './app/demo/demo.testbed.spec.ts';
|
||||
import './app/hero/hero-detail.component.no-testbed.spec.ts';
|
||||
import './app/hero/hero-detail.component.spec.ts';
|
||||
import './app/hero/hero-list.component.spec.ts';
|
||||
import './app/model/hero.service.spec.ts';
|
||||
import './app/model/http-hero.service.spec.ts';
|
||||
import './app/model/testing/http-client.spec.ts';
|
||||
import './app/shared/highlight.directive.spec.ts';
|
||||
import './app/shared/title-case.pipe.spec.ts';
|
||||
import './app/twain/twain.component.spec.ts';
|
||||
import './app/twain/twain.component.marbles.spec.ts';
|
||||
import './app/welcome/welcome.component.spec.ts';
|
||||
|
@ -18,8 +18,7 @@ If you use the CLI to generate an app, the default `AppModule` is as follows:
|
||||
/* JavaScript imports */
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { HttpModule } from '@angular/http';
|
||||
|
||||
|
||||
import { AppComponent } from './app.component';
|
||||
|
||||
@ -29,9 +28,7 @@ import { AppComponent } from './app.component';
|
||||
AppComponent
|
||||
],
|
||||
imports: [
|
||||
BrowserModule,
|
||||
FormsModule,
|
||||
HttpModule
|
||||
BrowserModule
|
||||
],
|
||||
providers: [],
|
||||
bootstrap: [AppComponent]
|
||||
@ -137,8 +134,8 @@ It tells Angular about other NgModules that this particular module needs to func
|
||||
|
||||
This list of modules are those that export components, directives, or pipes
|
||||
that the component templates in this module reference. In this case, the component is
|
||||
`AppComponent`, which references components, directives, or pipes in `BrowserModule`,
|
||||
`FormsModule`, or `HttpModule`.
|
||||
`AppComponent`, which references components, directives, or pipes in `BrowserModule`.
|
||||
Other common components in the examples are `FormsModule` and `HttpClientModule`.
|
||||
A component template can reference another component, directive,
|
||||
or pipe when the referenced class is declared in this module or
|
||||
the class was imported from another module.
|
||||
|
@ -36,7 +36,7 @@ The following is an example of specifying a bootstrapped component,
|
||||
imports: [
|
||||
BrowserModule,
|
||||
FormsModule,
|
||||
HttpModule,
|
||||
HttpClientModule,
|
||||
AppRoutingModule
|
||||
],
|
||||
providers: [],
|
||||
|
@ -439,7 +439,7 @@ Observables can deliver single or multiple values of any type to subscribers, ei
|
||||
|
||||
Angular uses a third-party library called [Reactive Extensions (RxJS)](http://reactivex.io/rxjs/).
|
||||
|
||||
To learn more, see the [Observables](guide/glossary#observable) guide.
|
||||
To learn more, see the [Observables](guide/observables) guide.
|
||||
|
||||
|
||||
{@a observer}
|
||||
|
@ -450,7 +450,7 @@ Here is a `searchHeroes` method that queries for heroes whose names contain the
|
||||
|
||||
If there is a search term, the code constructs an options object with an HTML URL-encoded search parameter. If the term were "foo", the GET request URL would be `api/heroes/?name=foo`.
|
||||
|
||||
The `HttpParms` are immutable so you'll have to use the `set()` method to update the options.
|
||||
The `HttpParams` are immutable so you'll have to use the `set()` method to update the options.
|
||||
|
||||
### Debouncing requests
|
||||
|
||||
@ -1034,7 +1034,7 @@ Call `request.flush()` with an error message, as seen in the following example.
|
||||
|
||||
<code-example
|
||||
path="http/src/testing/http-client.spec.ts"
|
||||
region="404"
|
||||
region="network-error"
|
||||
linenums="false">
|
||||
</code-example>
|
||||
|
||||
@ -1044,4 +1044,4 @@ Alternatively, you can call `request.error()` with an `ErrorEvent`.
|
||||
path="http/src/testing/http-client.spec.ts"
|
||||
region="network-error"
|
||||
linenums="false">
|
||||
</code-example>
|
||||
</code-example>
|
||||
|
@ -74,22 +74,58 @@ The following table contains our current target release dates for the next two m
|
||||
|
||||
|
||||
{@a lts}
|
||||
## Long-term support
|
||||
{@a support}
|
||||
## Support policy
|
||||
|
||||
All of our releases are supported actively for about 6 months (until the next major release), and then they are supported through long-term support (LTS) for another 12 months.
|
||||
All of our major releases are supported for 18 months.
|
||||
|
||||
During the LTS period, only critical fixes and security patches will be merged and released.
|
||||
* 6 months of active support, during which regularly-scheduled updates and patches are released, as described above in [Release frequency](#frequency "Release frequency").
|
||||
|
||||
The LTS state of one major version starts on the day of the next major release. LTS status ends approximately one year later, when we release another major version.
|
||||
* 12 months of long-term support (LTS). During the LTS period, only critical fixes and security patches will be released.
|
||||
|
||||
The following table provides the support status and key dates for Angular version 4.0.0 and higher.
|
||||
|
||||
<style>
|
||||
|
||||
Version | LTS Start Date | LTS End Date
|
||||
----------- | -------------- | ------------
|
||||
^4.0.0 | October 2017 | October 2018
|
||||
^5.0.0 | April 2018 | April 2019
|
||||
^6.0.0 | October 2018 | October 2019
|
||||
td, th {vertical-align: top}
|
||||
|
||||
</style>
|
||||
|
||||
<table>
|
||||
|
||||
<tr>
|
||||
<th>Version</th>
|
||||
<th>Status</th>
|
||||
<th>Release Date</th>
|
||||
<th>LTS Start Date</th>
|
||||
<th>LTS End Date</th>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>^4.0.0</td>
|
||||
<td>LTS</td>
|
||||
<td>March 23, 2017</td>
|
||||
<td>September 23, 2017</td>
|
||||
<td>September 23, 2018</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>^5.0.0</td>
|
||||
<td>LTS</td>
|
||||
<td>November 1, 2017</td>
|
||||
<td>May 1, 2018</td>
|
||||
<td>May 1, 2019</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>^6.0.0</td>
|
||||
<td>Active</td>
|
||||
<td>May 3, 2018</td>
|
||||
<td>November 3, 2018</td>
|
||||
<td>November 3, 2019</td>
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
|
||||
|
||||
|
||||
@ -127,8 +163,6 @@ Any changes to the public API surface will be done using the versioning, support
|
||||
{@a labs}
|
||||
## Angular Labs
|
||||
|
||||
Angular Labs is an initiative to cultivate new features and iterate on them quickly. Angular Labs provides a safe place for exploration and experimentation by the Angular team.
|
||||
Angular Labs is an initiative to cultivate new features and iterate on them quickly. Angular Labs provides a safe place for exploration and experimentation by the Angular team.
|
||||
|
||||
Angular Labs projects are are not ready for production use, and no commitment is made to bring them to production. The policies and practices that are described in this document do not apply to Angular Labs projects.
|
||||
|
||||
Angular Labs projects typically are in separate branches in the Angular repo, clearly separated from the main Angular codebase.
|
||||
Angular Labs projects are not ready for production use, and no commitment is made to bring them to production. The policies and practices that are described in this document do not apply to Angular Labs projects.
|
||||
|
@ -3656,7 +3656,7 @@ Lazy loading has multiple benefits.
|
||||
* You can speed up load time for users that only visit certain areas of the application.
|
||||
* You can continue expanding lazy loaded feature areas without increasing the size of the initial load bundle.
|
||||
|
||||
You're already made part way there.
|
||||
You're already part of the way there.
|
||||
By organizing the application into modules—`AppModule`,
|
||||
`HeroesModule`, `AdminModule` and `CrisisCenterModule`—you
|
||||
have natural candidates for lazy loading.
|
||||
|
@ -204,8 +204,7 @@ The test consumes that spy in the same way it did earlier.
|
||||
Most test suites in this guide call `beforeEach()` to set the preconditions for each `it()` test
|
||||
and rely on the `TestBed` to create classes and inject services.
|
||||
|
||||
There's another school of testing that never calls `beforeEach()` and
|
||||
and prefers to create classes explicitly rather than use the `TestBed`.
|
||||
There's another school of testing that never calls `beforeEach()` and prefers to create classes explicitly rather than use the `TestBed`.
|
||||
|
||||
Here's how you might rewrite one of the `MasterService` tests in that style.
|
||||
|
||||
@ -2367,9 +2366,9 @@ The [override metadata object](#metadata-override-object) is a generic defined a
|
||||
|
||||
<code-example format="." language="javascript">
|
||||
type MetadataOverride<T> = {
|
||||
add?: T;
|
||||
remove?: T;
|
||||
set?: T;
|
||||
add?: Partial<T>;
|
||||
remove?: Partial<T>;
|
||||
set?: Partial<T>;
|
||||
};
|
||||
</code-example>
|
||||
|
||||
@ -2725,9 +2724,9 @@ appropriate to the method, that is, the parameter of an `@NgModule`,
|
||||
|
||||
<code-example format="." language="javascript">
|
||||
type MetadataOverride<T> = {
|
||||
add?: T;
|
||||
remove?: T;
|
||||
set?: T;
|
||||
add?: Partial<T>;
|
||||
remove?: Partial<T>;
|
||||
set?: Partial<T>;
|
||||
};
|
||||
</code-example>
|
||||
|
||||
@ -3400,4 +3399,4 @@ accidental corruption of remote resources.
|
||||
It can even be hard to navigate to the component you want to test.
|
||||
|
||||
Because of these many obstacles, you should test DOM interaction
|
||||
with unit testing techniques as much as possible.
|
||||
with unit testing techniques as much as possible.
|
||||
|
@ -180,7 +180,7 @@ npm install --save @angular/platform-server @nguniversal/module-map-ngfactory-lo
|
||||
|
||||
{@a transition}
|
||||
|
||||
### Modify the client app
|
||||
## Modify the client app
|
||||
|
||||
A Universal app can act as a dynamic, content-rich "splash screen" that engages the user.
|
||||
It gives the appearance of a near-instant application.
|
||||
@ -190,7 +190,9 @@ Once loaded, Angular transitions from the static server-rendered page to the dyn
|
||||
|
||||
You must make a few changes to your application code to support both server-side rendering and the transition to the client app.
|
||||
|
||||
#### The root `AppModule`
|
||||
{@a root-app-module}
|
||||
|
||||
### The root `AppModule`
|
||||
|
||||
Open file `src/app/app.module.ts` and find the `BrowserModule` import in the `NgModule` metadata.
|
||||
Replace that import with this one:
|
||||
@ -206,9 +208,29 @@ You can get runtime information about the current platform and the `appId` by in
|
||||
<code-example path="universal/src/app/app.module.ts" region="platform-detection" title="src/app/app.module.ts (platform detection)">
|
||||
</code-example>
|
||||
|
||||
{@a cli-output}
|
||||
|
||||
### Build Destination
|
||||
|
||||
A Universal app is distributed in two parts: the server-side code that serves up the initial application, and the client-side code that's loaded in dynamically.
|
||||
|
||||
The Angular CLI outputs the client-side code in the `dist` directory by default, so you modify the `outputPath` for the __build__ target in the `angular.json` to keep the client-side build outputs separate from the server-side code. The client-side build output will be served by the Express server.
|
||||
|
||||
```
|
||||
...
|
||||
"build": {
|
||||
"builder": "@angular-devkit/build-angular:browser",
|
||||
"options": {
|
||||
"outputPath": "dist/browser",
|
||||
...
|
||||
}
|
||||
}
|
||||
...
|
||||
```
|
||||
|
||||
{@a http-urls}
|
||||
|
||||
#### Absolute HTTP URLs
|
||||
### Absolute HTTP URLs
|
||||
|
||||
The tutorial's `HeroService` and `HeroSearchService` delegate to the Angular `HttpClient` module to fetch application data.
|
||||
These services send requests to _relative_ URLs such as `api/heroes`.
|
||||
@ -262,6 +284,19 @@ The `ModuleMapLoaderModule` is a server-side module that allows lazy-loading of
|
||||
|
||||
This is also the place to register providers that are specific to running your app under Universal.
|
||||
|
||||
{@a app-server-entry-point}
|
||||
|
||||
### App server entry point
|
||||
|
||||
The `Angular CLI` uses the `AppServerModule` to build the server-side bundle.
|
||||
|
||||
Create a `main.server.ts` file in the `src/` directory that exports the `AppServerModule`:
|
||||
|
||||
<code-example path="universal/src/main.server.ts" title="src/main.server.ts">
|
||||
</code-example>
|
||||
|
||||
The `main.server.ts` will be referenced later to add a `server` target to the `Angular CLI` configuration.
|
||||
|
||||
{@a web-server}
|
||||
|
||||
### Universal web server
|
||||
@ -421,6 +456,8 @@ This config extends from the root's `tsconfig.json` file. Certain settings are n
|
||||
* The `angularCompilerOptions` section guides the AOT compiler:
|
||||
* `entryModule` - the root module of the server application, expressed as `path/to/file#ClassName`.
|
||||
|
||||
{@a universal-webpack-configuration}
|
||||
|
||||
### Universal Webpack configuration
|
||||
|
||||
Universal applications doesn't need any extra Webpack configuration, the CLI takes care of that for you,
|
||||
@ -433,13 +470,38 @@ Create a `webpack.server.config.js` file in the project root directory with the
|
||||
|
||||
**Webpack configuration** is a rich topic beyond the scope of this guide.
|
||||
|
||||
{@a universal-cli-configuration}
|
||||
|
||||
### Angular CLI configuration
|
||||
|
||||
The CLI provides builders for different types of __targets__. Commonly known targets such as `build` and `serve` already exist in the `angular.json` configuration. To target a server-side build, add a `server` target to the `architect` configuration object.
|
||||
|
||||
* The `outputPath` tells where the resulting build will be created.
|
||||
* The `main` provides the main entry point to the previously created `main.server.ts`
|
||||
* The `tsConfig` uses the `tsconfig.server.json` as configuration for the TypeScript and AOT compilation.
|
||||
|
||||
```
|
||||
"architect": {
|
||||
...
|
||||
"server": {
|
||||
"builder": "@angular-devkit/build-angular:server",
|
||||
"options": {
|
||||
"outputPath": "dist/server",
|
||||
"main": "src/main.server.ts",
|
||||
"tsConfig": "src/tsconfig.server.json"
|
||||
}
|
||||
}
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
## Build and run with universal
|
||||
|
||||
Now that you've created the TypeScript and Webpack config files, you can build and run the Universal application.
|
||||
Now that you've created the TypeScript and Webpack config files and configured the Angular CLI, you can build and run the Universal application.
|
||||
|
||||
First add the _build_ and _serve_ commands to the `scripts` section of the `package.json`:
|
||||
|
||||
<code-example format="." language="ts">
|
||||
```
|
||||
"scripts": {
|
||||
...
|
||||
"build:ssr": "npm run build:client-and-server-bundles && npm run webpack:server",
|
||||
@ -448,7 +510,7 @@ First add the _build_ and _serve_ commands to the `scripts` section of the `pack
|
||||
"webpack:server": "webpack --config webpack.server.config.js --progress --colors"
|
||||
...
|
||||
}
|
||||
</code-example>
|
||||
```
|
||||
|
||||
{@a build}
|
||||
|
||||
|
BIN
aio/content/images/bios/juristr.jpg
Normal file
After Width: | Height: | Size: 207 KiB |
BIN
aio/content/images/bios/mashhood.jpg
Normal file
After Width: | Height: | Size: 9.4 KiB |
BIN
aio/content/images/bios/mhartington.png
Normal file
After Width: | Height: | Size: 28 KiB |
@ -592,7 +592,7 @@
|
||||
"bio": "Nir is a Principal Frontend Consultant & Head of the Angular department at 500Tech, Google Developer Expert and community leader. He organizes the largest Angular meetup group in Israel (Angular-IL), talks and teaches about front-end technologies around the world. He is also the author of two books about Angular and the founder of the 'Frontend Band'.",
|
||||
"group": "GDE"
|
||||
},
|
||||
|
||||
|
||||
"achautard": {
|
||||
"name": "Alain Chautard",
|
||||
"picture": "alainchautard.png",
|
||||
@ -609,5 +609,32 @@
|
||||
"website": "https://coryrylan.com",
|
||||
"bio": "Cory is a full time front end web developer. He works full time building responsive web applications and progressive web apps. When not building web apps he is busy teaching Angular and other web technologies in workshops and conferences. He loves the web and is optimistic of the places it can take us.",
|
||||
"group": "GDE"
|
||||
},
|
||||
|
||||
"mhartington": {
|
||||
"name": "Mike Hartington",
|
||||
"picture": "mhartington.png",
|
||||
"twitter": "mhartington",
|
||||
"website": "https://mhartington.io",
|
||||
"bio": "Mike is a Developer Advocate for the Ionic Framework and a GDE in Angular. He spends most of his time making fast PWAs and exploring emerging web standards. When not behind a keyboard, you'll probably find him with a guitar and beer.",
|
||||
"group": "GDE"
|
||||
},
|
||||
|
||||
"juristr": {
|
||||
"name": "Juri Strumpflohner",
|
||||
"picture": "juristr.jpg",
|
||||
"twitter": "juristr",
|
||||
"website": "https://juristr.com",
|
||||
"bio": "Juri is a software engineer and freelance trainer and consultant currently mostly focusing on the frontend side using JavaScript, TypeScript and Angular. He has a passion for teaching and sharing his knowledge and experiences with others. This mostly happens by writing tech articles for his personal blog, by creating video courses for Egghead.io, during on-site workshops at companies or by speaking at conferences. In his free time he enjoys practicing Yoseikan Budo, a martial art where he currently owns the 3rd DAN black belt.",
|
||||
"group": "GDE"
|
||||
},
|
||||
|
||||
"mashhoodr": {
|
||||
"name": "Mashhood Rastgar",
|
||||
"picture": "mashhood.jpg",
|
||||
"twitter": "mashhoodr",
|
||||
"website": "http://imars.info/",
|
||||
"bio": "Mashhood is the principal technical consultant at Recurship and a Google Developer Expert. He works with different startups in US and EU to helps them crawl through the technical maze and quickly build amazing products focused around the problems they are trying to solve. He specializes in using the latest web technologies available to execute the best possible solutions.",
|
||||
"group": "GDE"
|
||||
}
|
||||
}
|
||||
|
@ -55,6 +55,12 @@
|
||||
<td>Tokyo, Japan</td>
|
||||
<td>Jun 16, 2018</td>
|
||||
</tr>
|
||||
<!-- Angular Conf Australia-->
|
||||
<tr>
|
||||
<th><a href="https://www.angularconf.com.au/" title="Angular Conf Australia">Angular Conf Australia</a></th>
|
||||
<td>Melbourne, Australia</td>
|
||||
<td>Jun 22, 2018</td>
|
||||
</tr>
|
||||
<!-- AngularConnect-->
|
||||
<tr>
|
||||
<th><a href="http://angularconnect.com" title="AngularConnect">AngularConnect</a></th>
|
||||
|
@ -78,6 +78,440 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="presskit-row">
|
||||
<div class="presskit-inner">
|
||||
<div>
|
||||
<h2>BRAND ICONS</h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="presskit-row">
|
||||
<div class="presskit-inner">
|
||||
<div class="presskit-image-container">
|
||||
<img src="assets/images/logos/concept-icons/animations.png" alt="Animations Icon">
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="l-space-left-3">ANIMATIONS</h3>
|
||||
<ul class="l-space-left-3">
|
||||
<li>
|
||||
<span>Animations Icon (png) - <a href="assets/images/logos/concept-icons/animations.png" download>Download</a></span>
|
||||
</li>
|
||||
<li>
|
||||
<span>Animations Icon (svg) - <a href="assets/images/logos/concept-icons/animations.svg" download>Download</a></span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="presskit-row">
|
||||
<div class="presskit-inner">
|
||||
<div class="presskit-image-container">
|
||||
<img src="assets/images/logos/concept-icons/augury.png" alt="Augury Icon">
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="l-space-left-3">AUGURY</h3>
|
||||
<ul class="l-space-left-3">
|
||||
<li>
|
||||
<span>Augury Icon (png) - <a href="assets/images/logos/concept-icons/augury.png" download>Download</a></span>
|
||||
</li>
|
||||
<li>
|
||||
<span>Augury Icon (svg) - <a href="assets/images/logos/concept-icons/augury.svg" download>Download</a></span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="presskit-row">
|
||||
<div class="presskit-inner">
|
||||
<div class="presskit-image-container">
|
||||
<img src="assets/images/logos/concept-icons/cdk.png" alt="CDK Icon">
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="l-space-left-3">COMPONENT DEV KIT (CDK)</h3>
|
||||
<ul class="l-space-left-3">
|
||||
<li>
|
||||
<span>CDK Icon (png) - <a href="assets/images/logos/concept-icons/cdk.png" download>Download</a></span>
|
||||
</li>
|
||||
<li>
|
||||
<span>CDK Icon (svg) - <a href="assets/images/logos/concept-icons/cdk.svg" download>Download</a></span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="presskit-row">
|
||||
<div class="presskit-inner">
|
||||
<div class="presskit-image-container">
|
||||
<img src="assets/images/logos/concept-icons/cli.png" alt="CLI Icon">
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="l-space-left-3">CLI</h3>
|
||||
<ul class="l-space-left-3">
|
||||
<li>
|
||||
<span>CLI Icon (png) - <a href="assets/images/logos/concept-icons/cli.png" download>Download</a></span>
|
||||
</li>
|
||||
<li>
|
||||
<span>CLI Icon (svg) - <a href="assets/images/logos/concept-icons/cli.svg" download>Download</a></span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="presskit-row">
|
||||
<div class="presskit-inner">
|
||||
<div class="presskit-image-container">
|
||||
<img src="assets/images/logos/concept-icons/compiler.png" alt="Compiler Icon">
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="l-space-left-3">COMPILER</h3>
|
||||
<ul class="l-space-left-3">
|
||||
<li>
|
||||
<span>Compiler Icon (png) - <a href="assets/images/logos/concept-icons/compiler.png" download>Download</a></span>
|
||||
</li>
|
||||
<li>
|
||||
<span>Compiler Icon (svg) - <a href="assets/images/logos/concept-icons/compiler.svg" download>Download</a></span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="presskit-row">
|
||||
<div class="presskit-inner">
|
||||
<div class="presskit-image-container">
|
||||
<img src="assets/images/logos/concept-icons/components.png" alt="Components Icon">
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="l-space-left-3">WEB COMPONENTS</h3>
|
||||
<ul class="l-space-left-3">
|
||||
<li>
|
||||
<span>Web Components Icon (png) - <a href="assets/images/logos/concept-icons/components.png" download>Download</a></span>
|
||||
</li>
|
||||
<li>
|
||||
<span>Web Components Icon (svg) - <a href="assets/images/logos/concept-icons/components.svg" download>Download</a></span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="presskit-row">
|
||||
<div class="presskit-inner">
|
||||
<div class="presskit-image-container">
|
||||
<img src="assets/images/logos/concept-icons/forms.png" alt="Forms Icon">
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="l-space-left-3">FORMS</h3>
|
||||
<ul class="l-space-left-3">
|
||||
<li>
|
||||
<span>Forms Icon (png) - <a href="assets/images/logos/concept-icons/forms.png" download>Download</a></span>
|
||||
</li>
|
||||
<li>
|
||||
<span>Forms Icon (svg) - <a href="assets/images/logos/concept-icons/forms.svg" download>Download</a></span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="presskit-row">
|
||||
<div class="presskit-inner">
|
||||
<div class="presskit-image-container">
|
||||
<img src="assets/images/logos/concept-icons/http.png" alt="HTTP Icon">
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="l-space-left-3">HTTP</h3>
|
||||
<ul class="l-space-left-3">
|
||||
<li>
|
||||
<span>HTTP Icon (png) - <a href="assets/images/logos/concept-icons/http.png" download>Download</a></span>
|
||||
</li>
|
||||
<li>
|
||||
<span>HTTP Icon (svg) - <a href="assets/images/logos/concept-icons/http.svg" download>Download</a></span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="presskit-row">
|
||||
<div class="presskit-inner">
|
||||
<div class="presskit-image-container">
|
||||
<img src="assets/images/logos/concept-icons/i18n.png" alt="i18n Icon">
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="l-space-left-3">i18n</h3>
|
||||
<ul class="l-space-left-3">
|
||||
<li>
|
||||
<span>HTTP Icon (png) - <a href="assets/images/logos/concept-icons/i18n.png" download>Download</a></span>
|
||||
</li>
|
||||
<li>
|
||||
<span>HTTP Icon (svg) - <a href="assets/images/logos/concept-icons/i18n.svg" download>Download</a></span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="presskit-row">
|
||||
<div class="presskit-inner">
|
||||
<div class="presskit-image-container">
|
||||
<img src="assets/images/logos/concept-icons/karma.png" alt="Karma Icon">
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="l-space-left-3">KARMA</h3>
|
||||
<ul class="l-space-left-3">
|
||||
<li>
|
||||
<span>Karma Icon (png) - <a href="assets/images/logos/concept-icons/karma.png" download>Download</a></span>
|
||||
</li>
|
||||
<li>
|
||||
<span>Karma Icon (svg) - <a href="assets/images/logos/concept-icons/karma.svg" download>Download</a></span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="presskit-row">
|
||||
<div class="presskit-inner">
|
||||
<div class="presskit-image-container">
|
||||
<img src="assets/images/logos/concept-icons/labs.png" alt="Labs Icon">
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="l-space-left-3">LABS</h3>
|
||||
<ul class="l-space-left-3">
|
||||
<li>
|
||||
<span>Labs Icon (png) - <a href="assets/images/logos/concept-icons/labs.png" download>Download</a></span>
|
||||
</li>
|
||||
<li>
|
||||
<span>Labs Icon (svg) - <a href="assets/images/logos/concept-icons/labs.svg" download>Download</a></span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="presskit-row">
|
||||
<div class="presskit-inner">
|
||||
<div class="presskit-image-container">
|
||||
<img src="assets/images/logos/concept-icons/language-services.png" alt="Language Services Icon">
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="l-space-left-3">LANGUAGE SERVICES</h3>
|
||||
<ul class="l-space-left-3">
|
||||
<li>
|
||||
<span>Language Services Icon (png) - <a href="assets/images/logos/concept-icons/language-services.png" download>Download</a></span>
|
||||
</li>
|
||||
<li>
|
||||
<span>Language Services Icon (svg) - <a href="assets/images/logos/concept-icons/language-services.svg" download>Download</a></span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="presskit-row">
|
||||
<div class="presskit-inner">
|
||||
<div class="presskit-image-container">
|
||||
<img src="assets/images/logos/concept-icons/material.png" alt="Material Icon">
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="l-space-left-3">MATERIAL</h3>
|
||||
<ul class="l-space-left-3">
|
||||
<li>
|
||||
<span>Material Icon (png) - <a href="assets/images/logos/concept-icons/material.png" download>Download</a></span>
|
||||
</li>
|
||||
<li>
|
||||
<span>Material Icon (svg) - <a href="assets/images/logos/concept-icons/material.svg" download>Download</a></span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="presskit-row">
|
||||
<div class="presskit-inner">
|
||||
<div class="presskit-image-container">
|
||||
<img src="assets/images/logos/concept-icons/protractor.png" alt="Protractor Icon">
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="l-space-left-3">PROTRACTOR</h3>
|
||||
<ul class="l-space-left-3">
|
||||
<li>
|
||||
<span>Protractor Icon (png) - <a href="assets/images/logos/concept-icons/protractor.png" download>Download</a></span>
|
||||
</li>
|
||||
<li>
|
||||
<span>Protractor Icon (svg) - <a href="assets/images/logos/concept-icons/protractor.svg" download>Download</a></span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="presskit-row">
|
||||
<div class="presskit-inner">
|
||||
<div class="presskit-image-container">
|
||||
<img src="assets/images/logos/concept-icons/pwa.png" alt="PWA Icon">
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="l-space-left-3">PWA</h3>
|
||||
<ul class="l-space-left-3">
|
||||
<li>
|
||||
<span>PWA Icon (png) - <a href="assets/images/logos/concept-icons/pwa.png" download>Download</a></span>
|
||||
</li>
|
||||
<li>
|
||||
<span>PWA Icon (svg) - <a href="assets/images/logos/concept-icons/pwa.svg" download>Download</a></span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="presskit-row">
|
||||
<div class="presskit-inner">
|
||||
<div class="presskit-image-container">
|
||||
<img src="assets/images/logos/concept-icons/router.png" alt="Router Icon">
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="l-space-left-3">ROUTER</h3>
|
||||
<ul class="l-space-left-3">
|
||||
<li>
|
||||
<span>Router Icon (png) - <a href="assets/images/logos/concept-icons/router.png" download>Download</a></span>
|
||||
</li>
|
||||
<li>
|
||||
<span>Router Icon (svg) - <a href="assets/images/logos/concept-icons/router.svg" download>Download</a></span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="presskit-row">
|
||||
<div class="presskit-inner">
|
||||
<div class="presskit-image-container">
|
||||
<img src="assets/images/logos/concept-icons/universal.png" alt="Universal Icon">
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="l-space-left-3">UNIVERSAL</h3>
|
||||
<ul class="l-space-left-3">
|
||||
<li>
|
||||
<span>Universal Icon (png) - <a href="assets/images/logos/concept-icons/universal.png" download>Download</a></span>
|
||||
</li>
|
||||
<li>
|
||||
<span>Universal Icon (svg) - <a href="assets/images/logos/concept-icons/universal.svg" download>Download</a></span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="presskit-row">
|
||||
<div class="presskit-inner">
|
||||
<div>
|
||||
<h2>CONCEPT & FEATURE ICONS</h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="presskit-row">
|
||||
<div class="presskit-inner">
|
||||
<div class="presskit-image-container">
|
||||
<img src="assets/images/logos/concept-icons/dependency-injection.png" alt="Dependency Injection Icon">
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="l-space-left-3">DEPENDENCY INJECTION</h3>
|
||||
<ul class="l-space-left-3">
|
||||
<li>
|
||||
<span>Dependency Injection Icon (png) - <a href="assets/images/logos/concept-icons/dependency-injection.png" download>Download</a></span>
|
||||
</li>
|
||||
<li>
|
||||
<span>Dependency Injection Icon (svg) - <a href="assets/images/logos/concept-icons/dependency-injection.svg" download>Download</a></span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="presskit-row">
|
||||
<div class="presskit-inner">
|
||||
<div class="presskit-image-container">
|
||||
<img src="assets/images/logos/concept-icons/lazy-loading.png" alt="Lazy Loading Icon">
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="l-space-left-3">LAZY LOADING</h3>
|
||||
<ul class="l-space-left-3">
|
||||
<li>
|
||||
<span>Lazy Loading Icon (png) - <a href="assets/images/logos/concept-icons/lazy-loading.png" download>Download</a></span>
|
||||
</li>
|
||||
<li>
|
||||
<span>Lazy Loading Icon (svg) - <a href="assets/images/logos/concept-icons/lazy-loading.svg" download>Download</a></span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="presskit-row">
|
||||
<div class="presskit-inner">
|
||||
<div class="presskit-image-container">
|
||||
<img src="assets/images/logos/concept-icons/libraries.png" alt="Libraries Icon">
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="l-space-left-3">LIBRARIES</h3>
|
||||
<ul class="l-space-left-3">
|
||||
<li>
|
||||
<span>Libraries Icon (png) - <a href="assets/images/logos/concept-icons/libraries.png" download>Download</a></span>
|
||||
</li>
|
||||
<li>
|
||||
<span>Libraries Icon (svg) - <a href="assets/images/logos/concept-icons/libraries.svg" download>Download</a></span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="presskit-row">
|
||||
<div class="presskit-inner">
|
||||
<div class="presskit-image-container">
|
||||
<img src="assets/images/logos/concept-icons/performance.png" alt="Performance Icon">
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="l-space-left-3">PERFORMANCE</h3>
|
||||
<ul class="l-space-left-3">
|
||||
<li>
|
||||
<span>Performance Icon (png) - <a href="assets/images/logos/concept-icons/performance.png" download>Download</a></span>
|
||||
</li>
|
||||
<li>
|
||||
<span>Performance Icon (svg) - <a href="assets/images/logos/concept-icons/performance.svg" download>Download</a></span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="presskit-row">
|
||||
<div class="presskit-inner">
|
||||
<div class="presskit-image-container">
|
||||
<img src="assets/images/logos/concept-icons/templates.png" alt="Templates Icon">
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="l-space-left-3">TEMPLATES</h3>
|
||||
<ul class="l-space-left-3">
|
||||
<li>
|
||||
<span>Templates Icon (png) - <a href="assets/images/logos/concept-icons/templates.png" download>Download</a></span>
|
||||
</li>
|
||||
<li>
|
||||
<span>Templates Icon (svg) - <a href="assets/images/logos/concept-icons/templates.svg" download>Download</a></span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="presskit-row">
|
||||
<div class="presskit-inner">
|
||||
<div>
|
||||
|
@ -345,6 +345,19 @@
|
||||
"title": "Angular Material",
|
||||
"url": "https://github.com/angular/material2"
|
||||
},
|
||||
"mcc": {
|
||||
"desc": "Material components made by the community",
|
||||
"logo": "",
|
||||
"rev": true,
|
||||
"title": "Material Community Components",
|
||||
"url": "https://github.com/tiaguinho/material-community-components"
|
||||
},
|
||||
"ngzorro": {
|
||||
"desc": "A set of enterprise-class UI components based on Ant Design and Angular",
|
||||
"rev": true,
|
||||
"title": "Ant Design of Angular (ng-zorro-antd)",
|
||||
"url": "https://ng.ant.design/docs/introduce/en"
|
||||
},
|
||||
"aggrid": {
|
||||
"desc": "A datagrid for Angular with enterprise style features such as sorting, filtering, custom rendering, editing, grouping, aggregation and pivoting.",
|
||||
"rev": true,
|
||||
@ -364,6 +377,13 @@
|
||||
"url": "http://www.amexio.tech/",
|
||||
"logo": "http://www.amexio.org/amexio-logo.png"
|
||||
},
|
||||
"bm": {
|
||||
"desc": "A lightweight Material Design library for Angular, based upon Google's Material Components for the Web",
|
||||
"logo": "https://blox.src.zone/assets/bloxmaterial.03ecfe4fa0147a781487749dc1cc4580.svg",
|
||||
"rev": true,
|
||||
"title": "Blox Material",
|
||||
"url": "https://github.com/src-zone/material"
|
||||
},
|
||||
"essentialjs2": {
|
||||
"desc": "Essential JS 2 for Angular is a collection modern TypeScript based true Angular Components. It has support for Ahead Of Time (AOT) compilation and Tree-Shaking. All the components are developed from the ground up to be lightweight, responsive, modular and touch friendly.",
|
||||
"rev": true,
|
||||
@ -522,13 +542,6 @@
|
||||
"title": "Pluralsight",
|
||||
"url": "https://www.pluralsight.com/search?q=angular+2&categories=all"
|
||||
},
|
||||
"ab": {
|
||||
"desc": "Take this introduction to Angular course, to learn the fundamentals in just two days, free of charge.",
|
||||
"logo": "",
|
||||
"rev": true,
|
||||
"title": "Rangle.io",
|
||||
"url": "https://rangle.io/services/javascript-training/training-angular1-angular2-with-ngupgrade/"
|
||||
},
|
||||
"ab3": {
|
||||
"desc": "Angular courses hosted by Udemy",
|
||||
"logo": "",
|
||||
@ -597,12 +610,6 @@
|
||||
"Workshops & Onsite Training": {
|
||||
"order": 2,
|
||||
"resources": {
|
||||
"-KLIBo3ANF3-1B9wxsoB": {
|
||||
"desc": "Angular Classes from Intertech in Minnesota",
|
||||
"rev": true,
|
||||
"title": "Intertech",
|
||||
"url": "http://www.intertech.com/Training/Web-Dev/AngularJS/AngularJS/Angular-2-Training"
|
||||
},
|
||||
"-KLIBoFWStce29UCwkvY": {
|
||||
"desc": "Private Angular Training and Mentoring",
|
||||
"rev": true,
|
||||
|
@ -175,7 +175,7 @@ This information is called _metadata_
|
||||
Some of the metadata is in the `@Component` decorators that you added to your component classes.
|
||||
Other critical metadata is in [`@NgModule`](guide/ngmodules) decorators.
|
||||
|
||||
The most important `@NgModule`decorator annotates the top-level **AppModule** class.
|
||||
The most important `@NgModule` decorator annotates the top-level **AppModule** class.
|
||||
|
||||
The Angular CLI generated an `AppModule` class in `src/app/app.module.ts` when it created the project.
|
||||
This is where you _opt-in_ to the `FormsModule`.
|
||||
|
@ -2,11 +2,11 @@
|
||||
"aio": {
|
||||
"master": {
|
||||
"uncompressed": {
|
||||
"runtime": 2689,
|
||||
"main": 478529,
|
||||
"runtime": 2768,
|
||||
"main": 475855,
|
||||
"polyfills": 38453,
|
||||
"prettify": 14913
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -58,7 +58,7 @@
|
||||
</mat-sidenav-container>
|
||||
|
||||
<div *ngIf="hasFloatingToc" class="toc-container no-print" [style.max-height.px]="tocMaxHeight" (mousewheel)="restrainScrolling($event)">
|
||||
<aio-toc></aio-toc>
|
||||
<aio-lazy-ce selector="aio-toc"></aio-lazy-ce>
|
||||
</div>
|
||||
|
||||
<footer class="no-print">
|
||||
|
@ -6,7 +6,7 @@ import { HttpClient } from '@angular/common/http';
|
||||
import { MatProgressBar, MatSidenav } from '@angular/material';
|
||||
import { By } from '@angular/platform-browser';
|
||||
|
||||
import { timer } from 'rxjs';
|
||||
import { of, timer } from 'rxjs';
|
||||
import { first, mapTo } from 'rxjs/operators';
|
||||
|
||||
import { AppComponent } from './app.component';
|
||||
@ -14,6 +14,7 @@ import { AppModule } from './app.module';
|
||||
import { DocumentService } from 'app/documents/document.service';
|
||||
import { DocViewerComponent } from 'app/layout/doc-viewer/doc-viewer.component';
|
||||
import { Deployment } from 'app/shared/deployment.service';
|
||||
import { ElementsLoader } from 'app/custom-elements/elements-loader';
|
||||
import { GaService } from 'app/shared/ga.service';
|
||||
import { LocationService } from 'app/shared/location.service';
|
||||
import { Logger } from 'app/shared/logger.service';
|
||||
@ -26,7 +27,6 @@ import { SearchBoxComponent } from 'app/search/search-box/search-box.component';
|
||||
import { SearchResultsComponent } from 'app/shared/search-results/search-results.component';
|
||||
import { SearchService } from 'app/search/search.service';
|
||||
import { SelectComponent } from 'app/shared/select/select.component';
|
||||
import { TocComponent } from 'app/layout/toc/toc.component';
|
||||
import { TocItem, TocService } from 'app/shared/toc.service';
|
||||
|
||||
const sideBySideBreakPoint = 992;
|
||||
@ -92,11 +92,11 @@ describe('AppComponent', () => {
|
||||
});
|
||||
|
||||
describe('hasFloatingToc', () => {
|
||||
it('should initially be true', () => {
|
||||
it('should initially be false', () => {
|
||||
const fixture2 = TestBed.createComponent(AppComponent);
|
||||
const component2 = fixture2.componentInstance;
|
||||
|
||||
expect(component2.hasFloatingToc).toBe(true);
|
||||
expect(component2.hasFloatingToc).toBe(false);
|
||||
});
|
||||
|
||||
it('should be false on narrow screens', () => {
|
||||
@ -621,55 +621,65 @@ describe('AppComponent', () => {
|
||||
});
|
||||
|
||||
describe('aio-toc', () => {
|
||||
let tocDebugElement: DebugElement;
|
||||
let tocContainer: DebugElement|null;
|
||||
let tocContainer: HTMLElement|null;
|
||||
let toc: HTMLElement|null;
|
||||
|
||||
const setHasFloatingToc = (hasFloatingToc: boolean) => {
|
||||
component.hasFloatingToc = hasFloatingToc;
|
||||
fixture.detectChanges();
|
||||
|
||||
tocDebugElement = fixture.debugElement.query(By.directive(TocComponent));
|
||||
tocContainer = tocDebugElement && tocDebugElement.parent;
|
||||
tocContainer = fixture.debugElement.nativeElement.querySelector('.toc-container');
|
||||
toc = tocContainer && tocContainer.querySelector('aio-toc');
|
||||
};
|
||||
|
||||
beforeEach(() => setHasFloatingToc(true));
|
||||
|
||||
|
||||
it('should show/hide `<aio-toc>` based on `hasFloatingToc`', () => {
|
||||
expect(tocDebugElement).toBeTruthy();
|
||||
expect(tocContainer).toBeTruthy();
|
||||
|
||||
setHasFloatingToc(false);
|
||||
expect(tocDebugElement).toBeFalsy();
|
||||
expect(tocContainer).toBeFalsy();
|
||||
expect(toc).toBeFalsy();
|
||||
|
||||
setHasFloatingToc(true);
|
||||
expect(tocDebugElement).toBeTruthy();
|
||||
expect(tocContainer).toBeTruthy();
|
||||
expect(toc).toBeTruthy();
|
||||
|
||||
setHasFloatingToc(false);
|
||||
expect(tocContainer).toBeFalsy();
|
||||
expect(toc).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should have a non-embedded `<aio-toc>` element', () => {
|
||||
expect(tocDebugElement.classes['embedded']).toBeFalsy();
|
||||
setHasFloatingToc(true);
|
||||
expect(toc!.classList.contains('embedded')).toBe(false);
|
||||
});
|
||||
|
||||
it('should update the TOC container\'s `maxHeight` based on `tocMaxHeight`', () => {
|
||||
expect(tocContainer!.styles['max-height']).toBeNull();
|
||||
setHasFloatingToc(true);
|
||||
|
||||
expect(tocContainer!.style['max-height']).toBe('');
|
||||
|
||||
component.tocMaxHeight = '100';
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(tocContainer!.styles['max-height']).toBe('100px');
|
||||
expect(tocContainer!.style['max-height']).toBe('100px');
|
||||
});
|
||||
|
||||
it('should restrain scrolling inside the ToC container', () => {
|
||||
const restrainScrolling = spyOn(component, 'restrainScrolling');
|
||||
const evt = {};
|
||||
const evt = new MouseEvent('mousewheel');
|
||||
|
||||
setHasFloatingToc(true);
|
||||
expect(restrainScrolling).not.toHaveBeenCalled();
|
||||
|
||||
tocContainer!.triggerEventHandler('mousewheel', evt);
|
||||
tocContainer!.dispatchEvent(evt);
|
||||
expect(restrainScrolling).toHaveBeenCalledWith(evt);
|
||||
});
|
||||
|
||||
it('should not be loaded/registered until necessary', () => {
|
||||
const loader: TestElementsLoader = fixture.debugElement.injector.get(ElementsLoader);
|
||||
expect(loader.loadCustomElement).not.toHaveBeenCalled();
|
||||
|
||||
setHasFloatingToc(true);
|
||||
expect(loader.loadCustomElement).toHaveBeenCalledWith('aio-toc');
|
||||
});
|
||||
});
|
||||
|
||||
describe('footer', () => {
|
||||
@ -1280,6 +1290,7 @@ function createTestingModule(initialUrl: string, mode: string = 'stable') {
|
||||
imports: [ AppModule ],
|
||||
providers: [
|
||||
{ provide: APP_BASE_HREF, useValue: '/' },
|
||||
{ provide: ElementsLoader, useClass: TestElementsLoader },
|
||||
{ provide: GaService, useClass: TestGaService },
|
||||
{ provide: HttpClient, useClass: TestHttpClient },
|
||||
{ provide: LocationService, useFactory: () => mockLocationService },
|
||||
@ -1294,6 +1305,14 @@ function createTestingModule(initialUrl: string, mode: string = 'stable') {
|
||||
});
|
||||
}
|
||||
|
||||
class TestElementsLoader {
|
||||
loadContainedCustomElements = jasmine.createSpy('loadContainedCustomElements')
|
||||
.and.returnValue(of(undefined));
|
||||
|
||||
loadCustomElement = jasmine.createSpy('loadCustomElement')
|
||||
.and.returnValue(Promise.resolve());
|
||||
}
|
||||
|
||||
class TestGaService {
|
||||
locationChanged = jasmine.createSpy('locationChanged');
|
||||
}
|
||||
@ -1368,7 +1387,7 @@ class TestHttpClient {
|
||||
const id = match[1]!;
|
||||
// Make up a title for test purposes
|
||||
const title = id.split('/').pop()!.replace(/^([a-z])/, (_, letter) => letter.toUpperCase());
|
||||
const h1 = (id === 'no-title') ? '' : `<h1>${title}</h1>`;
|
||||
const h1 = (id === 'no-title') ? '' : `<h1 class="no-toc">${title}</h1>`;
|
||||
const contents = `${h1}<h2 id="#somewhere">Some heading</h2>`;
|
||||
data = { id, contents };
|
||||
}
|
||||
|
@ -69,7 +69,7 @@ export class AppComponent implements OnInit {
|
||||
topMenuNodes: NavigationNode[];
|
||||
topMenuNarrowNodes: NavigationNode[];
|
||||
|
||||
hasFloatingToc = true;
|
||||
hasFloatingToc = false;
|
||||
private showFloatingToc = new BehaviorSubject(false);
|
||||
private showFloatingTocWidth = 800;
|
||||
tocMaxHeight: string;
|
||||
|
@ -32,7 +32,6 @@ import { ScrollService } from 'app/shared/scroll.service';
|
||||
import { ScrollSpyService } from 'app/shared/scroll-spy.service';
|
||||
import { SearchBoxComponent } from 'app/search/search-box/search-box.component';
|
||||
import { NotificationComponent } from 'app/layout/notification/notification.component';
|
||||
import { TocComponent } from 'app/layout/toc/toc.component';
|
||||
import { TocService } from 'app/shared/toc.service';
|
||||
import { CurrentDateToken, currentDateProvider } from 'app/shared/current-date';
|
||||
import { WindowToken, windowProvider } from 'app/shared/window';
|
||||
@ -111,7 +110,6 @@ export const svgIconProviders = [
|
||||
NavItemComponent,
|
||||
SearchBoxComponent,
|
||||
NotificationComponent,
|
||||
TocComponent,
|
||||
TopMenuComponent,
|
||||
],
|
||||
providers: [
|
||||
@ -133,7 +131,6 @@ export const svgIconProviders = [
|
||||
{ provide: CurrentDateToken, useFactory: currentDateProvider },
|
||||
{ provide: WindowToken, useFactory: windowProvider },
|
||||
],
|
||||
entryComponents: [ TocComponent ],
|
||||
bootstrap: [ AppComponent ]
|
||||
})
|
||||
export class AppModule { }
|
||||
|
@ -25,20 +25,22 @@ export interface TabInfo {
|
||||
<!-- Use content projection so that the provided HTML's code-panes can be split into tabs -->
|
||||
<div #content style="display: none"><ng-content></ng-content></div>
|
||||
|
||||
<mat-tab-group class="code-tab-group" disableRipple>
|
||||
<mat-tab style="overflow-y: hidden;" *ngFor="let tab of tabs">
|
||||
<ng-template mat-tab-label>
|
||||
<span class="{{ tab.class }}">{{ tab.title }}</span>
|
||||
</ng-template>
|
||||
<aio-code class="{{ tab.class }}"
|
||||
[language]="tab.language"
|
||||
[linenums]="tab.linenums"
|
||||
[path]="tab.path"
|
||||
[region]="tab.region"
|
||||
[title]="tab.title">
|
||||
</aio-code>
|
||||
</mat-tab>
|
||||
</mat-tab-group>
|
||||
<mat-card>
|
||||
<mat-tab-group class="code-tab-group" disableRipple>
|
||||
<mat-tab style="overflow-y: hidden;" *ngFor="let tab of tabs">
|
||||
<ng-template mat-tab-label>
|
||||
<span class="{{ tab.class }}">{{ tab.title }}</span>
|
||||
</ng-template>
|
||||
<aio-code class="{{ tab.class }}"
|
||||
[language]="tab.language"
|
||||
[linenums]="tab.linenums"
|
||||
[path]="tab.path"
|
||||
[region]="tab.region"
|
||||
[title]="tab.title">
|
||||
</aio-code>
|
||||
</mat-tab>
|
||||
</mat-tab-group>
|
||||
</mat-card>
|
||||
`,
|
||||
})
|
||||
export class CodeTabsComponent implements OnInit, AfterViewInit {
|
||||
|
@ -1,12 +1,12 @@
|
||||
import { NgModule, Type } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { CodeTabsComponent } from './code-tabs.component';
|
||||
import { MatTabsModule } from '@angular/material';
|
||||
import { MatCardModule, MatTabsModule } from '@angular/material';
|
||||
import { CodeModule } from './code.module';
|
||||
import { WithCustomElementComponent } from '../element-registry';
|
||||
|
||||
@NgModule({
|
||||
imports: [ CommonModule, MatTabsModule, CodeModule ],
|
||||
imports: [ CommonModule, MatCardModule, MatTabsModule, CodeModule ],
|
||||
declarations: [ CodeTabsComponent ],
|
||||
exports: [ CodeTabsComponent ],
|
||||
entryComponents: [ CodeTabsComponent ]
|
||||
|
@ -6,8 +6,11 @@ import {
|
||||
ELEMENT_MODULE_PATHS_AS_ROUTES,
|
||||
ELEMENT_MODULE_PATHS_TOKEN
|
||||
} from './element-registry';
|
||||
import { LazyCustomElementComponent } from './lazy-custom-element.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [ LazyCustomElementComponent ],
|
||||
exports: [ LazyCustomElementComponent ],
|
||||
providers: [
|
||||
ElementsLoader,
|
||||
{ provide: NgModuleFactoryLoader, useClass: SystemJsNgModuleLoader },
|
||||
|
@ -13,8 +13,8 @@ export const ELEMENT_MODULE_PATHS_AS_ROUTES = [
|
||||
loadChildren: './api/api-list.module#ApiListModule'
|
||||
},
|
||||
{
|
||||
selector: 'live-example',
|
||||
loadChildren: './live-example/live-example.module#LiveExampleModule'
|
||||
selector: 'aio-contributor-list',
|
||||
loadChildren: './contributor/contributor-list.module#ContributorListModule'
|
||||
},
|
||||
{
|
||||
selector: 'aio-file-not-found-search',
|
||||
@ -25,25 +25,29 @@ export const ELEMENT_MODULE_PATHS_AS_ROUTES = [
|
||||
loadChildren: './resource/resource-list.module#ResourceListModule'
|
||||
},
|
||||
{
|
||||
selector: 'current-location',
|
||||
loadChildren: './current-location/current-location.module#CurrentLocationModule'
|
||||
},
|
||||
{
|
||||
selector: 'aio-contributor-list',
|
||||
loadChildren: './contributor/contributor-list.module#ContributorListModule'
|
||||
},
|
||||
{
|
||||
selector: 'code-tabs',
|
||||
loadChildren: './code/code-tabs.module#CodeTabsModule'
|
||||
selector: 'aio-toc',
|
||||
loadChildren: './toc/toc.module#TocModule'
|
||||
},
|
||||
{
|
||||
selector: 'code-example',
|
||||
loadChildren: './code/code-example.module#CodeExampleModule'
|
||||
},
|
||||
{
|
||||
selector: 'code-tabs',
|
||||
loadChildren: './code/code-tabs.module#CodeTabsModule'
|
||||
},
|
||||
{
|
||||
selector: 'current-location',
|
||||
loadChildren: './current-location/current-location.module#CurrentLocationModule'
|
||||
},
|
||||
{
|
||||
selector: 'expandable-section',
|
||||
loadChildren: './expandable-section/expandable-section.module#ExpandableSectionModule'
|
||||
}
|
||||
},
|
||||
{
|
||||
selector: 'live-example',
|
||||
loadChildren: './live-example/live-example.module#LiveExampleModule'
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
|
@ -4,49 +4,19 @@ import {
|
||||
NgModuleRef,
|
||||
Type
|
||||
} from '@angular/core';
|
||||
import {TestBed, fakeAsync, tick} from '@angular/core/testing';
|
||||
import { TestBed, fakeAsync, flushMicrotasks } from '@angular/core/testing';
|
||||
|
||||
import { ElementsLoader } from './elements-loader';
|
||||
import { ELEMENT_MODULE_PATHS_TOKEN, WithCustomElementComponent } from './element-registry';
|
||||
|
||||
class FakeComponentFactory extends ComponentFactory<any> {
|
||||
selector: string;
|
||||
componentType: Type<any>;
|
||||
ngContentSelectors: string[];
|
||||
inputs = [{propName: this.identifyingInput, templateName: this.identifyingInput}];
|
||||
outputs = [];
|
||||
|
||||
constructor(private identifyingInput: string) { super(); }
|
||||
|
||||
create(injector: Injector,
|
||||
projectableNodes?: any[][],
|
||||
rootSelectorOrNode?: string | any,
|
||||
ngModule?: NgModuleRef<any>): ComponentRef<any> {
|
||||
return (jasmine.createSpy('ComponentRef') as any) as ComponentRef<any>;
|
||||
};
|
||||
interface Deferred {
|
||||
resolve(): void;
|
||||
reject(err: any): void;
|
||||
}
|
||||
|
||||
const FAKE_COMPONENT_FACTORIES = new Map([
|
||||
['element-a-module-path', new FakeComponentFactory('element-a-input')],
|
||||
['element-b-module-path', new FakeComponentFactory('element-b-input')],
|
||||
]);
|
||||
|
||||
describe('ElementsLoader', () => {
|
||||
let elementsLoader: ElementsLoader;
|
||||
let actualCustomElementsDefine;
|
||||
let fakeCustomElementsDefine;
|
||||
|
||||
// ElementsLoader uses the window's customElements API. Provide a fake for this test.
|
||||
beforeEach(() => {
|
||||
actualCustomElementsDefine = window.customElements.define;
|
||||
|
||||
fakeCustomElementsDefine = jasmine.createSpy('define');
|
||||
|
||||
window.customElements.define = fakeCustomElementsDefine;
|
||||
});
|
||||
afterEach(() => {
|
||||
window.customElements.define = actualCustomElementsDefine;
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
const injector = TestBed.configureTestingModule({
|
||||
@ -63,63 +33,196 @@ describe('ElementsLoader', () => {
|
||||
elementsLoader = injector.get(ElementsLoader);
|
||||
});
|
||||
|
||||
it('should be able to register an element', fakeAsync(() => {
|
||||
// Verify that the elements loader considered `element-a-selector` to be unregistered.
|
||||
expect(elementsLoader.elementsToLoad.has('element-a-selector')).toBeTruthy();
|
||||
describe('loadContainedCustomElements()', () => {
|
||||
let loadCustomElementSpy: jasmine.Spy;
|
||||
|
||||
const hostEl = document.createElement('div');
|
||||
hostEl.innerHTML = `<element-a-selector></element-a-selector>`;
|
||||
beforeEach(() => loadCustomElementSpy = spyOn(elementsLoader, 'loadCustomElement'));
|
||||
|
||||
elementsLoader.loadContainingCustomElements(hostEl);
|
||||
tick();
|
||||
it('should attempt to load and register all contained elements', fakeAsync(() => {
|
||||
expect(loadCustomElementSpy).not.toHaveBeenCalled();
|
||||
|
||||
const defineArgs = fakeCustomElementsDefine.calls.argsFor(0);
|
||||
expect(defineArgs[0]).toBe('element-a-selector');
|
||||
const hostEl = document.createElement('div');
|
||||
hostEl.innerHTML = `
|
||||
<element-a-selector></element-a-selector>
|
||||
<element-b-selector></element-b-selector>
|
||||
`;
|
||||
|
||||
// Verify the right component was loaded/created
|
||||
expect(defineArgs[1].observedAttributes[0]).toBe('element-a-input');
|
||||
elementsLoader.loadContainedCustomElements(hostEl);
|
||||
flushMicrotasks();
|
||||
|
||||
expect(elementsLoader.elementsToLoad.has('element-a-selector')).toBeFalsy();
|
||||
}));
|
||||
expect(loadCustomElementSpy).toHaveBeenCalledTimes(2);
|
||||
expect(loadCustomElementSpy).toHaveBeenCalledWith('element-a-selector');
|
||||
expect(loadCustomElementSpy).toHaveBeenCalledWith('element-b-selector');
|
||||
}));
|
||||
|
||||
it('should be able to register multiple elements', fakeAsync(() => {
|
||||
// Verify that the elements loader considered `element-a-selector` to be unregistered.
|
||||
expect(elementsLoader.elementsToLoad.has('element-a-selector')).toBeTruthy();
|
||||
it('should attempt to load and register only contained elements', fakeAsync(() => {
|
||||
expect(loadCustomElementSpy).not.toHaveBeenCalled();
|
||||
|
||||
const hostEl = document.createElement('div');
|
||||
hostEl.innerHTML = `
|
||||
<element-a-selector></element-a-selector>
|
||||
<element-b-selector></element-b-selector>
|
||||
`;
|
||||
const hostEl = document.createElement('div');
|
||||
hostEl.innerHTML = `
|
||||
<element-b-selector></element-b-selector>
|
||||
`;
|
||||
|
||||
elementsLoader.loadContainingCustomElements(hostEl);
|
||||
tick();
|
||||
elementsLoader.loadContainedCustomElements(hostEl);
|
||||
flushMicrotasks();
|
||||
|
||||
const defineElementA = fakeCustomElementsDefine.calls.argsFor(0);
|
||||
expect(defineElementA[0]).toBe('element-a-selector');
|
||||
expect(defineElementA[1].observedAttributes[0]).toBe('element-a-input');
|
||||
expect(elementsLoader.elementsToLoad.has('element-a-selector')).toBeFalsy();
|
||||
expect(loadCustomElementSpy).toHaveBeenCalledTimes(1);
|
||||
expect(loadCustomElementSpy).toHaveBeenCalledWith('element-b-selector');
|
||||
}));
|
||||
|
||||
const defineElementB = fakeCustomElementsDefine.calls.argsFor(1);
|
||||
expect(defineElementB[0]).toBe('element-b-selector');
|
||||
expect(defineElementB[1].observedAttributes[0]).toBe('element-b-input');
|
||||
expect(elementsLoader.elementsToLoad.has('element-b-selector')).toBeFalsy();
|
||||
}));
|
||||
it('should wait for all contained elements to load and register', fakeAsync(() => {
|
||||
const deferreds = returnPromisesFromSpy(loadCustomElementSpy);
|
||||
|
||||
it('should only register an element one time', fakeAsync(() => {
|
||||
const hostEl = document.createElement('div');
|
||||
hostEl.innerHTML = `<element-a-selector></element-a-selector>`;
|
||||
const hostEl = document.createElement('div');
|
||||
hostEl.innerHTML = `
|
||||
<element-a-selector></element-a-selector>
|
||||
<element-b-selector></element-b-selector>
|
||||
`;
|
||||
|
||||
elementsLoader.loadContainingCustomElements(hostEl);
|
||||
tick(); // Tick for the module factory loader's async `load` function
|
||||
const log: any[] = [];
|
||||
elementsLoader.loadContainedCustomElements(hostEl).subscribe(
|
||||
v => log.push(`emitted: ${v}`),
|
||||
e => log.push(`errored: ${e}`),
|
||||
() => log.push('completed'),
|
||||
);
|
||||
|
||||
// Call again to to check how many times customElements.define was called.
|
||||
elementsLoader.loadContainingCustomElements(hostEl);
|
||||
tick(); // Tick for the module factory loader's async `load` function
|
||||
flushMicrotasks();
|
||||
expect(log).toEqual([]);
|
||||
|
||||
// Should have only been called once, since the second load would not query for element-a
|
||||
expect(window.customElements.define).toHaveBeenCalledTimes(1);
|
||||
}));
|
||||
deferreds[0].resolve();
|
||||
flushMicrotasks();
|
||||
expect(log).toEqual([]);
|
||||
|
||||
deferreds[1].resolve();
|
||||
flushMicrotasks();
|
||||
expect(log).toEqual(['emitted: undefined', 'completed']);
|
||||
}));
|
||||
|
||||
it('should fail if any of the contained elements fails to load and register', fakeAsync(() => {
|
||||
const deferreds = returnPromisesFromSpy(loadCustomElementSpy);
|
||||
|
||||
const hostEl = document.createElement('div');
|
||||
hostEl.innerHTML = `
|
||||
<element-a-selector></element-a-selector>
|
||||
<element-b-selector></element-b-selector>
|
||||
`;
|
||||
|
||||
const log: any[] = [];
|
||||
elementsLoader.loadContainedCustomElements(hostEl).subscribe(
|
||||
v => log.push(`emitted: ${v}`),
|
||||
e => log.push(`errored: ${e}`),
|
||||
() => log.push('completed'),
|
||||
);
|
||||
|
||||
flushMicrotasks();
|
||||
expect(log).toEqual([]);
|
||||
|
||||
deferreds[0].resolve();
|
||||
flushMicrotasks();
|
||||
expect(log).toEqual([]);
|
||||
|
||||
deferreds[1].reject('foo');
|
||||
flushMicrotasks();
|
||||
expect(log).toEqual(['errored: foo']);
|
||||
}));
|
||||
});
|
||||
|
||||
describe('loadCustomElement()', () => {
|
||||
let definedSpy: jasmine.Spy;
|
||||
let whenDefinedSpy: jasmine.Spy;
|
||||
let whenDefinedDeferreds: Deferred[];
|
||||
|
||||
beforeEach(() => {
|
||||
// `loadCustomElement()` uses the `window.customElements` API. Provide mocks for these tests.
|
||||
definedSpy = spyOn(window.customElements, 'define');
|
||||
whenDefinedSpy = spyOn(window.customElements, 'whenDefined');
|
||||
whenDefinedDeferreds = returnPromisesFromSpy(whenDefinedSpy);
|
||||
});
|
||||
|
||||
it('should be able to load and register an element', fakeAsync(() => {
|
||||
elementsLoader.loadCustomElement('element-a-selector');
|
||||
flushMicrotasks();
|
||||
|
||||
expect(definedSpy).toHaveBeenCalledTimes(1);
|
||||
expect(definedSpy).toHaveBeenCalledWith('element-a-selector', jasmine.any(Function));
|
||||
|
||||
// Verify the right component was loaded/registered.
|
||||
const Ctor = definedSpy.calls.argsFor(0)[1];
|
||||
expect(Ctor.observedAttributes).toEqual(['element-a-module-path']);
|
||||
}));
|
||||
|
||||
it('should wait until the element is defined', fakeAsync(() => {
|
||||
let state = 'pending';
|
||||
elementsLoader.loadCustomElement('element-b-selector').then(() => state = 'resolved');
|
||||
flushMicrotasks();
|
||||
|
||||
expect(state).toBe('pending');
|
||||
expect(whenDefinedSpy).toHaveBeenCalledTimes(1);
|
||||
expect(whenDefinedSpy).toHaveBeenCalledWith('element-b-selector');
|
||||
|
||||
whenDefinedDeferreds[0].resolve();
|
||||
flushMicrotasks();
|
||||
expect(state).toBe('resolved');
|
||||
}));
|
||||
|
||||
it('should not load and register the same element more than once', fakeAsync(() => {
|
||||
elementsLoader.loadCustomElement('element-a-selector');
|
||||
flushMicrotasks();
|
||||
expect(definedSpy).toHaveBeenCalledTimes(1);
|
||||
|
||||
definedSpy.calls.reset();
|
||||
|
||||
// While loading/registering is still in progress:
|
||||
elementsLoader.loadCustomElement('element-a-selector');
|
||||
flushMicrotasks();
|
||||
expect(definedSpy).not.toHaveBeenCalled();
|
||||
|
||||
definedSpy.calls.reset();
|
||||
whenDefinedDeferreds[0].resolve();
|
||||
|
||||
// Once loading/registering is already completed:
|
||||
let state = 'pending';
|
||||
elementsLoader.loadCustomElement('element-a-selector').then(() => state = 'resolved');
|
||||
flushMicrotasks();
|
||||
expect(state).toBe('resolved');
|
||||
expect(definedSpy).not.toHaveBeenCalled();
|
||||
}));
|
||||
|
||||
it('should fail if defining the the custom element fails', fakeAsync(() => {
|
||||
let state = 'pending';
|
||||
elementsLoader.loadCustomElement('element-b-selector').catch(e => state = `rejected: ${e}`);
|
||||
flushMicrotasks();
|
||||
expect(state).toBe('pending');
|
||||
|
||||
whenDefinedDeferreds[0].reject('foo');
|
||||
flushMicrotasks();
|
||||
expect(state).toBe('rejected: foo');
|
||||
}));
|
||||
|
||||
it('should be able to load and register an element again if previous attempt failed',
|
||||
fakeAsync(() => {
|
||||
elementsLoader.loadCustomElement('element-a-selector');
|
||||
flushMicrotasks();
|
||||
expect(definedSpy).toHaveBeenCalledTimes(1);
|
||||
|
||||
definedSpy.calls.reset();
|
||||
|
||||
// While loading/registering is still in progress:
|
||||
elementsLoader.loadCustomElement('element-a-selector').catch(() => undefined);
|
||||
flushMicrotasks();
|
||||
expect(definedSpy).not.toHaveBeenCalled();
|
||||
|
||||
whenDefinedDeferreds[0].reject('foo');
|
||||
flushMicrotasks();
|
||||
expect(definedSpy).not.toHaveBeenCalled();
|
||||
|
||||
// Once loading/registering has already failed:
|
||||
elementsLoader.loadCustomElement('element-a-selector');
|
||||
flushMicrotasks();
|
||||
expect(definedSpy).toHaveBeenCalledTimes(1);
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
// TEST CLASSES/HELPERS
|
||||
@ -128,11 +231,28 @@ class FakeCustomElementModule implements WithCustomElementComponent {
|
||||
customElementComponent: Type<any>;
|
||||
}
|
||||
|
||||
class FakeComponentFactory extends ComponentFactory<any> {
|
||||
selector: string;
|
||||
componentType: Type<any>;
|
||||
ngContentSelectors: string[];
|
||||
inputs = [{propName: this.identifyingInput, templateName: this.identifyingInput}];
|
||||
outputs = [];
|
||||
|
||||
constructor(private identifyingInput: string) { super(); }
|
||||
|
||||
create(injector: Injector,
|
||||
projectableNodes?: any[][],
|
||||
rootSelectorOrNode?: string | any,
|
||||
ngModule?: NgModuleRef<any>): ComponentRef<any> {
|
||||
return jasmine.createSpy('ComponentRef') as any;
|
||||
};
|
||||
}
|
||||
|
||||
class FakeComponentFactoryResolver extends ComponentFactoryResolver {
|
||||
constructor(private modulePath) { super(); }
|
||||
|
||||
resolveComponentFactory(component: Type<any>): ComponentFactory<any> {
|
||||
return FAKE_COMPONENT_FACTORIES.get(this.modulePath)!;
|
||||
return new FakeComponentFactory(this.modulePath);
|
||||
}
|
||||
}
|
||||
|
||||
@ -168,3 +288,9 @@ class FakeModuleFactoryLoader extends NgModuleFactoryLoader {
|
||||
return Promise.resolve(fakeModuleFactory);
|
||||
}
|
||||
}
|
||||
|
||||
function returnPromisesFromSpy(spy: jasmine.Spy): Deferred[] {
|
||||
const deferreds: Deferred[] = [];
|
||||
spy.and.callFake(() => new Promise((resolve, reject) => deferreds.push({resolve, reject})));
|
||||
return deferreds;
|
||||
}
|
||||
|
@ -11,11 +11,13 @@ import { createCustomElement } from '@angular/elements';
|
||||
@Injectable()
|
||||
export class ElementsLoader {
|
||||
/** Map of unregistered custom elements and their respective module paths to load. */
|
||||
elementsToLoad: Map<string, string>;
|
||||
private elementsToLoad: Map<string, string>;
|
||||
/** Map of custom elements that are in the process of being loaded and registered. */
|
||||
private elementsLoading = new Map<string, Promise<void>>();
|
||||
|
||||
constructor(private moduleFactoryLoader: NgModuleFactoryLoader,
|
||||
private moduleRef: NgModuleRef<any>,
|
||||
@Inject(ELEMENT_MODULE_PATHS_TOKEN) elementModulePaths) {
|
||||
@Inject(ELEMENT_MODULE_PATHS_TOKEN) elementModulePaths: Map<string, string>) {
|
||||
this.elementsToLoad = new Map(elementModulePaths);
|
||||
}
|
||||
|
||||
@ -24,31 +26,57 @@ export class ElementsLoader {
|
||||
* the browser. Custom elements that are registered will be removed from the list of unregistered
|
||||
* elements so that they will not be queried in subsequent calls.
|
||||
*/
|
||||
loadContainingCustomElements(element: HTMLElement): Observable<void> {
|
||||
const selectors: any[] = Array.from(this.elementsToLoad.keys())
|
||||
loadContainedCustomElements(element: HTMLElement): Observable<void> {
|
||||
const unregisteredSelectors = Array.from(this.elementsToLoad.keys())
|
||||
.filter(s => element.querySelector(s));
|
||||
|
||||
if (!selectors.length) { return of(undefined); }
|
||||
if (!unregisteredSelectors.length) { return of(undefined); }
|
||||
|
||||
// Returns observable that completes when all discovered elements have been registered.
|
||||
return fromPromise(Promise.all(selectors.map(s => this.register(s))).then(result => undefined));
|
||||
const allRegistered = Promise.all(unregisteredSelectors.map(s => this.loadCustomElement(s)));
|
||||
return fromPromise(allRegistered.then(() => undefined));
|
||||
}
|
||||
|
||||
/** Registers the custom element defined on the WithCustomElement module factory. */
|
||||
private register(selector: string) {
|
||||
const modulePath = this.elementsToLoad.get(selector)!;
|
||||
return this.moduleFactoryLoader.load(modulePath).then(elementModuleFactory => {
|
||||
if (!this.elementsToLoad.has(selector)) { return; }
|
||||
/** Loads and registers the custom element defined on the `WithCustomElement` module factory. */
|
||||
loadCustomElement(selector: string): Promise<void> {
|
||||
if (this.elementsLoading.has(selector)) {
|
||||
// The custom element is in the process of being loaded and registered.
|
||||
return this.elementsLoading.get(selector)!;
|
||||
}
|
||||
|
||||
const elementModuleRef = elementModuleFactory.create(this.moduleRef.injector);
|
||||
const CustomElementComponent = elementModuleRef.instance.customElementComponent;
|
||||
const CustomElement =
|
||||
createCustomElement(CustomElementComponent, {injector: elementModuleRef.injector});
|
||||
if (this.elementsToLoad.has(selector)) {
|
||||
// Load and register the custom element (for the first time).
|
||||
const modulePath = this.elementsToLoad.get(selector)!;
|
||||
const loadedAndRegistered = this.moduleFactoryLoader
|
||||
.load(modulePath)
|
||||
.then(elementModuleFactory => {
|
||||
const elementModuleRef = elementModuleFactory.create(this.moduleRef.injector);
|
||||
const injector = elementModuleRef.injector;
|
||||
const CustomElementComponent = elementModuleRef.instance.customElementComponent;
|
||||
const CustomElement = createCustomElement(CustomElementComponent, {injector});
|
||||
|
||||
customElements!.define(selector, CustomElement);
|
||||
this.elementsToLoad.delete(selector);
|
||||
customElements!.define(selector, CustomElement);
|
||||
return customElements.whenDefined(selector);
|
||||
})
|
||||
.then(() => {
|
||||
// The custom element has been successfully loaded and registered.
|
||||
// Remove from `elementsLoading` and `elementsToLoad`.
|
||||
this.elementsLoading.delete(selector);
|
||||
this.elementsToLoad.delete(selector);
|
||||
})
|
||||
.catch(err => {
|
||||
// The custom element has failed to load and register.
|
||||
// Remove from `elementsLoading`.
|
||||
// (Do not remove from `elementsToLoad` in case it was a temporary error.)
|
||||
this.elementsLoading.delete(selector);
|
||||
return Promise.reject(err);
|
||||
});
|
||||
|
||||
return customElements.whenDefined(selector);
|
||||
});
|
||||
this.elementsLoading.set(selector, loadedAndRegistered);
|
||||
return loadedAndRegistered;
|
||||
}
|
||||
|
||||
// The custom element has already been loaded and registered.
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,67 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { Logger } from 'app/shared/logger.service';
|
||||
import { MockLogger } from 'testing/logger.service';
|
||||
import { LazyCustomElementComponent } from './lazy-custom-element.component';
|
||||
import { ElementsLoader } from './elements-loader';
|
||||
|
||||
describe('LazyCustomElementComponent', () => {
|
||||
let mockElementsLoader: jasmine.SpyObj<ElementsLoader>;
|
||||
let mockLogger: MockLogger;
|
||||
let fixture: ComponentFixture<LazyCustomElementComponent>;
|
||||
|
||||
beforeEach(() => {
|
||||
mockElementsLoader = jasmine.createSpyObj<ElementsLoader>('ElementsLoader', [
|
||||
'loadContainedCustomElements',
|
||||
'loadCustomElement',
|
||||
]);
|
||||
|
||||
const injector = TestBed.configureTestingModule({
|
||||
declarations: [ LazyCustomElementComponent ],
|
||||
providers: [
|
||||
{ provide: ElementsLoader, useValue: mockElementsLoader },
|
||||
{ provide: Logger, useClass: MockLogger },
|
||||
],
|
||||
});
|
||||
|
||||
mockLogger = injector.get(Logger);
|
||||
fixture = TestBed.createComponent(LazyCustomElementComponent);
|
||||
});
|
||||
|
||||
it('should set the HTML content based on the selector', () => {
|
||||
const elem = fixture.nativeElement;
|
||||
|
||||
expect(elem.innerHTML).toBe('');
|
||||
|
||||
fixture.componentInstance.selector = 'foo-bar';
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(elem.innerHTML).toBe('<foo-bar></foo-bar>');
|
||||
});
|
||||
|
||||
it('should load the specified custom element', () => {
|
||||
expect(mockElementsLoader.loadCustomElement).not.toHaveBeenCalled();
|
||||
|
||||
fixture.componentInstance.selector = 'foo-bar';
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(mockElementsLoader.loadCustomElement).toHaveBeenCalledWith('foo-bar');
|
||||
});
|
||||
|
||||
it('should log an error (and abort) if the selector is empty', () => {
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(mockElementsLoader.loadCustomElement).not.toHaveBeenCalled();
|
||||
expect(mockLogger.output.error).toEqual([[jasmine.any(Error)]]);
|
||||
expect(mockLogger.output.error[0][0].message).toBe('Invalid selector for \'aio-lazy-ce\': ');
|
||||
});
|
||||
|
||||
it('should log an error (and abort) if the selector is invalid', () => {
|
||||
fixture.componentInstance.selector = 'foo-bar><script></script><foo-bar';
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(mockElementsLoader.loadCustomElement).not.toHaveBeenCalled();
|
||||
expect(mockLogger.output.error).toEqual([[jasmine.any(Error)]]);
|
||||
expect(mockLogger.output.error[0][0].message).toBe(
|
||||
'Invalid selector for \'aio-lazy-ce\': foo-bar><script></script><foo-bar');
|
||||
});
|
||||
});
|
27
aio/src/app/custom-elements/lazy-custom-element.component.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import { Component, ElementRef, Input, OnInit } from '@angular/core';
|
||||
import { Logger } from 'app/shared/logger.service';
|
||||
import { ElementsLoader } from './elements-loader';
|
||||
|
||||
@Component({
|
||||
selector: 'aio-lazy-ce',
|
||||
template: '',
|
||||
})
|
||||
export class LazyCustomElementComponent implements OnInit {
|
||||
@Input() selector = '';
|
||||
|
||||
constructor(
|
||||
private elementRef: ElementRef,
|
||||
private elementsLoader: ElementsLoader,
|
||||
private logger: Logger,
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
if (!this.selector || /[^\w-]/.test(this.selector)) {
|
||||
this.logger.error(new Error(`Invalid selector for 'aio-lazy-ce': ${this.selector}`));
|
||||
return;
|
||||
}
|
||||
|
||||
this.elementRef.nativeElement.innerHTML = `<${this.selector}></${this.selector}>`;
|
||||
this.elementsLoader.loadCustomElement(this.selector);
|
||||
}
|
||||
}
|
@ -1,5 +1,7 @@
|
||||
<!-- Content projection is used to get the content HTML provided to the component. -->
|
||||
<span #content style="display: none"><ng-content></ng-content></span>
|
||||
|
||||
<span [ngSwitch]="mode">
|
||||
<span *ngSwitchCase="'disabled'">{{title}} <em>(not available on this device)</em></span>
|
||||
<span *ngSwitchCase="'embedded'">
|
||||
<div title="{{title}}">
|
||||
<aio-embedded-stackblitz [src]="stackblitz"></aio-embedded-stackblitz>
|
||||
@ -14,7 +16,7 @@
|
||||
<span *ngSwitchDefault>
|
||||
<a [href]="stackblitz" target="_blank" title="{{title}}">{{title}}</a>
|
||||
<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>
|
||||
|
@ -12,7 +12,6 @@ describe('LiveExampleComponent', () => {
|
||||
let liveExampleComponent: LiveExampleComponent;
|
||||
let fixture: ComponentFixture<HostComponent>;
|
||||
let testPath: string;
|
||||
let liveExampleContent: string|null;
|
||||
|
||||
//////// test helpers ////////
|
||||
|
||||
@ -41,12 +40,7 @@ describe('LiveExampleComponent', () => {
|
||||
liveExampleDe = fixture.debugElement.children[0];
|
||||
liveExampleComponent = liveExampleDe.componentInstance;
|
||||
|
||||
// Copy the LiveExample's innerHTML (content)
|
||||
// into the `liveExampleContent` property as the DocViewer does
|
||||
liveExampleDe.nativeElement.liveExampleContent = liveExampleContent;
|
||||
|
||||
fixture.detectChanges();
|
||||
liveExampleComponent.onResize(1033); // wide by default
|
||||
// Trigger `ngAfterContentInit()`.
|
||||
fixture.detectChanges();
|
||||
|
||||
testFn();
|
||||
@ -64,7 +58,6 @@ describe('LiveExampleComponent', () => {
|
||||
.overrideComponent(EmbeddedStackblitzComponent, {set: {template: 'NO IFRAME'}});
|
||||
|
||||
testPath = defaultTestPath;
|
||||
liveExampleContent = null;
|
||||
});
|
||||
|
||||
describe('when not embedded', () => {
|
||||
@ -103,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)', () => {
|
||||
testPath = '/guide/somewhere';
|
||||
setHostTemplate('<live-example name="toh-pt1"></live-example>');
|
||||
@ -150,15 +134,7 @@ describe('LiveExampleComponent', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should be flat style when flat-style requested', () => {
|
||||
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', () => {
|
||||
it('should not have a download link when `noDownload` attr present', () => {
|
||||
setHostTemplate('<live-example noDownload></live-example>');
|
||||
testComponent(() => {
|
||||
const hrefs = getHrefs();
|
||||
@ -167,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>');
|
||||
testComponent(() => {
|
||||
const hrefs = getHrefs();
|
||||
@ -196,12 +172,12 @@ describe('LiveExampleComponent', () => {
|
||||
});
|
||||
|
||||
it('should add title from <live-example> body', () => {
|
||||
liveExampleContent = 'The Greatest Example';
|
||||
setHostTemplate('<live-example title="ignore this title"></live-example>');
|
||||
const expectedTitle = 'The Greatest Example';
|
||||
setHostTemplate(`<live-example title="ignore this title">${expectedTitle}</live-example>`);
|
||||
testComponent(() => {
|
||||
const anchor = getLiveExampleAnchor();
|
||||
expect(anchor.textContent).toBe(liveExampleContent, 'anchor content');
|
||||
expect(anchor.getAttribute('title')).toBe(liveExampleContent, 'title');
|
||||
expect(anchor.textContent).toBe(expectedTitle, 'anchor content');
|
||||
expect(anchor.getAttribute('title')).toBe(expectedTitle, 'title');
|
||||
});
|
||||
});
|
||||
|
||||
@ -251,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,130 +1,131 @@
|
||||
/* tslint:disable component-selector */
|
||||
import { Component, ElementRef, HostListener, Input, OnInit, AfterViewInit, ViewChild } from '@angular/core';
|
||||
import { AfterContentInit, AfterViewInit, Component, ElementRef, Input, ViewChild } from '@angular/core';
|
||||
import { Location } from '@angular/common';
|
||||
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 zipBase = CONTENT_URL_PREFIX + 'zips/';
|
||||
const LIVE_EXAMPLE_BASE = CONTENT_URL_PREFIX + 'live-examples/';
|
||||
const ZIP_BASE = CONTENT_URL_PREFIX + 'zips/';
|
||||
|
||||
/**
|
||||
* Angular.io Live Example Embedded Component
|
||||
*
|
||||
* Renders a link to a live/host example of the doc page.
|
||||
*
|
||||
* All attributes and the text content are optional
|
||||
*
|
||||
* Usage:
|
||||
* <live-example
|
||||
* [name="..."] // name of the example directory
|
||||
* [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)
|
||||
* [noDownload] // no downloadable zip option
|
||||
* [downloadOnly] // just the zip
|
||||
* [title="..."]> // text for live example link and tooltip
|
||||
* text // higher precedence way to specify text for live example link and tooltip
|
||||
* </live-example>
|
||||
* Example:
|
||||
* <p>Run <live-example>Try the live example</live-example></p>.
|
||||
* // ~/resources/live-examples/{page}/stackblitz.json
|
||||
*
|
||||
* <p>Run <live-example name="toh-pt1">this example</live-example></p>.
|
||||
* // ~/resources/live-examples/toh-pt1/stackblitz.json
|
||||
*
|
||||
* // Link to the default stackblitz in the toh-pt1 sample
|
||||
* // 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>.
|
||||
* // ~/resources/live-examples/toh-pt1/stackblitz.json
|
||||
*
|
||||
* <p>Run <live-example stackblitz="minimal"></live-example></p>.
|
||||
* // ~/resources/live-examples/{page}/minimal.stackblitz.json
|
||||
*
|
||||
* // Embed the current page's default stackblitz
|
||||
* // Text within tag is "live example"
|
||||
* // No title (no tooltip)
|
||||
* <live-example embedded title=""></live-example>
|
||||
* // ~/resources/live-examples/{page}/stackblitz.json
|
||||
*
|
||||
* // 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>
|
||||
* // ~/resources/live-examples/toh-pt1/minimal.stackblitz.json
|
||||
*/
|
||||
* Angular.io Live Example Embedded Component
|
||||
*
|
||||
* Renders a link to a live/host example of the doc page.
|
||||
*
|
||||
* All attributes and the text content are optional
|
||||
*
|
||||
* Usage:
|
||||
* <live-example
|
||||
* [name="..."] // name of the example directory
|
||||
* [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)
|
||||
* [noDownload] // no downloadable zip option
|
||||
* [downloadOnly] // just the zip
|
||||
* [title="..."]> // text for live example link and tooltip
|
||||
* text // higher precedence way to specify text for live example link and tooltip
|
||||
* </live-example>
|
||||
* Example:
|
||||
* <p>Run <live-example>Try the live example</live-example></p>.
|
||||
* // ~/resources/live-examples/{page}/stackblitz.json
|
||||
*
|
||||
* <p>Run <live-example name="toh-pt1">this example</live-example></p>.
|
||||
* // ~/resources/live-examples/toh-pt1/stackblitz.json
|
||||
*
|
||||
* // Link to the default stackblitz in the toh-pt1 sample
|
||||
* // 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>.
|
||||
* // ~/resources/live-examples/toh-pt1/stackblitz.json
|
||||
*
|
||||
* <p>Run <live-example stackblitz="minimal"></live-example></p>.
|
||||
* // ~/resources/live-examples/{page}/minimal.stackblitz.json
|
||||
*
|
||||
* // Embed the current page's default stackblitz
|
||||
* // Text within tag is "live example"
|
||||
* // No title (no tooltip)
|
||||
* <live-example embedded title=""></live-example>
|
||||
* // ~/resources/live-examples/{page}/stackblitz.json
|
||||
*
|
||||
* // 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>
|
||||
* // ~/resources/live-examples/toh-pt1/minimal.stackblitz.json
|
||||
*/
|
||||
@Component({
|
||||
selector: 'live-example',
|
||||
templateUrl: 'live-example.component.html'
|
||||
})
|
||||
export class LiveExampleComponent implements OnInit {
|
||||
export class LiveExampleComponent implements AfterContentInit {
|
||||
|
||||
// Will force to embedded-style when viewport width is narrow
|
||||
// "narrow" value was picked based on phone dimensions from http://screensiz.es/phone
|
||||
readonly narrowWidth = 1000;
|
||||
|
||||
attrs: any;
|
||||
enableDownload = true;
|
||||
exampleDir: string;
|
||||
isEmbedded = false;
|
||||
mode = 'disabled';
|
||||
stackblitz: string;
|
||||
stackblitzName: string;
|
||||
readonly mode: 'default' | 'embedded' | 'downloadOnly';
|
||||
readonly enableDownload: boolean;
|
||||
readonly stackblitz: string;
|
||||
readonly zip: string;
|
||||
title: string;
|
||||
zip: string;
|
||||
zipName: string;
|
||||
|
||||
constructor(
|
||||
private elementRef: ElementRef,
|
||||
location: Location ) {
|
||||
@ViewChild('content')
|
||||
private content: ElementRef;
|
||||
|
||||
const attrs = this.attrs = getAttrs(this.elementRef);
|
||||
let exampleDir = attrs.name;
|
||||
constructor(elementRef: ElementRef, location: Location) {
|
||||
const attrs = getAttrs(elementRef);
|
||||
const exampleDir = this.getExampleDir(attrs, location.path(false));
|
||||
const stackblitzName = this.getStackblitzName(attrs);
|
||||
|
||||
this.mode = this.getMode(attrs);
|
||||
this.enableDownload = this.getEnableDownload(attrs);
|
||||
this.stackblitz = this.getStackblitz(exampleDir, stackblitzName, this.mode === 'embedded');
|
||||
this.zip = this.getZip(exampleDir, stackblitzName);
|
||||
this.title = this.getTitle(attrs);
|
||||
}
|
||||
|
||||
ngAfterContentInit() {
|
||||
// Angular will sanitize this title when displayed, so it should be plain text.
|
||||
const textContent = this.content.nativeElement.textContent.trim();
|
||||
if (textContent) {
|
||||
this.title = textContent;
|
||||
}
|
||||
}
|
||||
|
||||
private getEnableDownload(attrs: AttrMap) {
|
||||
const downloadDisabled = boolFromValue(getAttrValue(attrs, 'noDownload'));
|
||||
return !downloadDisabled;
|
||||
}
|
||||
|
||||
private getExampleDir(attrs: AttrMap, path: string) {
|
||||
let exampleDir = getAttrValue(attrs, 'name');
|
||||
if (!exampleDir) {
|
||||
// take last segment, excluding hash fragment and query params
|
||||
exampleDir = (location.path(false).match(/[^\/?\#]+(?=\/?(?:$|\#|\?))/) || [])[0];
|
||||
}
|
||||
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';
|
||||
// Take the last path segment, excluding query params and hash fragment.
|
||||
const match = path.match(/[^/?#]+(?=\/?(?:\?|#|$))/);
|
||||
exampleDir = match ? match[0] : 'index';
|
||||
}
|
||||
return exampleDir.trim();
|
||||
}
|
||||
|
||||
calcStackblitzLink(width: number) {
|
||||
private getMode(this: LiveExampleComponent, attrs: AttrMap): typeof this.mode {
|
||||
const downloadOnly = boolFromValue(getAttrValue(attrs, 'downloadOnly'));
|
||||
const isEmbedded = boolFromValue(getAttrValue(attrs, 'embedded'));
|
||||
|
||||
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}`;
|
||||
return downloadOnly ? 'downloadOnly'
|
||||
: isEmbedded ? 'embedded' :
|
||||
'default';
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
// The `liveExampleContent` property is set by the DocViewer when it builds this component.
|
||||
// It is the original innerHTML of the host element.
|
||||
// Angular will sanitize this title when displayed so should be plain text.
|
||||
const title = this.elementRef.nativeElement.liveExampleContent;
|
||||
this.title = (title || this.attrs.title || 'live example').trim();
|
||||
this.onResize(window.innerWidth);
|
||||
private getStackblitz(exampleDir: string, stackblitzName: string, isEmbedded: boolean) {
|
||||
const urlQuery = isEmbedded ? '?ctl=1' : '';
|
||||
return `${LIVE_EXAMPLE_BASE}${exampleDir}/${stackblitzName}stackblitz.html${urlQuery}`;
|
||||
}
|
||||
|
||||
@HostListener('window:resize', ['$event.target.innerWidth'])
|
||||
onResize(width: number) {
|
||||
if (this.mode !== 'downloadOnly') {
|
||||
this.calcStackblitzLink(width);
|
||||
}
|
||||
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`;
|
||||
}
|
||||
}
|
||||
|
||||
@ -136,7 +137,7 @@ export class LiveExampleComponent implements OnInit {
|
||||
@Component({
|
||||
selector: 'aio-embedded-stackblitz',
|
||||
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 {
|
||||
@Input() src: string;
|
||||
|
@ -4,8 +4,8 @@ import { By } from '@angular/platform-browser';
|
||||
import { asapScheduler as asap, BehaviorSubject } from 'rxjs';
|
||||
|
||||
import { ScrollService } from 'app/shared/scroll.service';
|
||||
import { TocComponent } from './toc.component';
|
||||
import { TocItem, TocService } from 'app/shared/toc.service';
|
||||
import { TocComponent } from './toc.component';
|
||||
|
||||
describe('TocComponent', () => {
|
||||
let tocComponentDe: DebugElement;
|
14
aio/src/app/custom-elements/toc/toc.module.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { NgModule, Type } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { WithCustomElementComponent } from '../element-registry';
|
||||
import { TocComponent } from './toc.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [ CommonModule, MatIconModule ],
|
||||
declarations: [ TocComponent ],
|
||||
entryComponents: [ TocComponent ],
|
||||
})
|
||||
export class TocModule implements WithCustomElementComponent {
|
||||
customElementComponent: Type<any> = TocComponent;
|
||||
}
|
@ -300,7 +300,7 @@ describe('DocViewerComponent', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
const elementsLoader = TestBed.get(ElementsLoader) as MockElementsLoader;
|
||||
loadElementsSpy = elementsLoader.loadContainingCustomElements.and.returnValue(of(undefined));
|
||||
loadElementsSpy = elementsLoader.loadContainedCustomElements.and.returnValue(of(undefined));
|
||||
prepareTitleAndTocSpy = spyOn(docViewer, 'prepareTitleAndToc');
|
||||
swapViewsSpy = spyOn(docViewer, 'swapViews').and.returnValue(of(undefined));
|
||||
});
|
||||
|
@ -136,7 +136,7 @@ export class DocViewerComponent implements OnDestroy {
|
||||
// and is considered to be safe.
|
||||
tap(() => this.nextViewContainer.innerHTML = doc.contents || ''),
|
||||
tap(() => addTitleAndToc = this.prepareTitleAndToc(this.nextViewContainer, doc.id)),
|
||||
switchMap(() => this.elementsLoader.loadContainingCustomElements(this.nextViewContainer)),
|
||||
switchMap(() => this.elementsLoader.loadContainedCustomElements(this.nextViewContainer)),
|
||||
tap(() => this.docReady.emit()),
|
||||
switchMap(() => this.swapViews(addTitleAndToc)),
|
||||
tap(() => this.docRendered.emit()),
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { ElementRef } from '@angular/core';
|
||||
|
||||
import { getAttrs, getAttrValue, getBoolFromAttribute, boolFromValue } from './attribute-utils';
|
||||
import { AttrMap, getAttrs, getAttrValue, getBoolFromAttribute, boolFromValue } from './attribute-utils';
|
||||
|
||||
describe('Attribute Utilities', () => {
|
||||
let testEl: HTMLElement;
|
||||
@ -32,17 +32,17 @@ describe('Attribute Utilities', () => {
|
||||
});
|
||||
|
||||
describe('getAttrValue', () => {
|
||||
let attrMap: { [index: string]: string };
|
||||
let attrMap: AttrMap;
|
||||
|
||||
beforeEach(() => {
|
||||
attrMap = getAttrs(testEl);
|
||||
});
|
||||
|
||||
it('should return empty string value for attribute "a"', () => {
|
||||
it('should return empty string for attribute "a"', () => {
|
||||
expect(getAttrValue(attrMap, 'a')).toBe('');
|
||||
});
|
||||
|
||||
it('should return empty string value for attribute "A"', () => {
|
||||
it('should return empty string for attribute "A"', () => {
|
||||
expect(getAttrValue(attrMap, 'A')).toBe('');
|
||||
});
|
||||
|
||||
@ -50,7 +50,7 @@ describe('Attribute Utilities', () => {
|
||||
expect(getAttrValue(attrMap, 'b')).toBe('true');
|
||||
});
|
||||
|
||||
it('should return empty string value for attribute "d-E"', () => {
|
||||
it('should return empty string for attribute "d-E"', () => {
|
||||
expect(getAttrValue(attrMap, 'd-E')).toBe('');
|
||||
});
|
||||
|
||||
@ -68,12 +68,10 @@ describe('Attribute Utilities', () => {
|
||||
expect(getAttrValue(attrMap, ['d-e', 'd'])).toBe('');
|
||||
});
|
||||
|
||||
it('should return undefined value for non-existent attribute "x"', () => {
|
||||
it('should return undefined for non-existent attributes', () => {
|
||||
expect(getAttrValue(attrMap, 'x')).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should return undefined if no argument', () => {
|
||||
expect(getAttrValue(attrMap)).toBeUndefined();
|
||||
expect(getAttrValue(attrMap, '')).toBeUndefined();
|
||||
expect(getAttrValue(attrMap, ['', 'x'])).toBeUndefined();
|
||||
});
|
||||
|
||||
});
|
||||
|
@ -1,17 +1,19 @@
|
||||
// Utilities for processing HTML element attributes
|
||||
import { ElementRef } from '@angular/core';
|
||||
|
||||
interface StringMap { [index: string]: string; }
|
||||
export interface AttrMap {
|
||||
[key: string]: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get attribute map from element or ElementRef `attributes`.
|
||||
* Attribute map keys are forced lowercase for case-insensitive lookup.
|
||||
* @param el The source of the attributes
|
||||
* @param el The source of the attributes.
|
||||
*/
|
||||
export function getAttrs(el: HTMLElement | ElementRef): StringMap {
|
||||
export function getAttrs(el: HTMLElement | ElementRef): AttrMap {
|
||||
const attrs: NamedNodeMap = el instanceof ElementRef ? el.nativeElement.attributes : el.attributes;
|
||||
const attrMap: StringMap = {};
|
||||
for (const attr of attrs as any /* cast due to https://github.com/Microsoft/TypeScript/issues/2695 */) {
|
||||
const attrMap: AttrMap = {};
|
||||
for (const attr of attrs as any as Attr[] /* cast due to https://github.com/Microsoft/TypeScript/issues/2695 */) {
|
||||
attrMap[attr.name.toLowerCase()] = attr.value;
|
||||
}
|
||||
return attrMap;
|
||||
@ -19,29 +21,29 @@ export function getAttrs(el: HTMLElement | ElementRef): StringMap {
|
||||
|
||||
/**
|
||||
* Return the attribute that matches `attr`.
|
||||
* @param attr Name of the attribute or a string of candidate attribute names
|
||||
* @param attr Name of the attribute or a string of candidate attribute names.
|
||||
*/
|
||||
export function getAttrValue(attrs: StringMap, attr: string | string[] = ''): string {
|
||||
return attrs[typeof attr === 'string' ?
|
||||
attr.toLowerCase() :
|
||||
attr.find(a => attrs[a.toLowerCase()] !== undefined) || ''
|
||||
];
|
||||
export function getAttrValue(attrs: AttrMap, attr: string | string[]): string | undefined {
|
||||
const key = (typeof attr === 'string')
|
||||
? attr
|
||||
: attr.find(a => attrs.hasOwnProperty(a.toLowerCase()));
|
||||
|
||||
return (key === undefined) ? undefined : attrs[key.toLowerCase()];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the boolean state of an attribute value (if supplied)
|
||||
* @param attrValue The string value of some attribute (or undefined if attribute not present)
|
||||
* @param attrValue The string value of some attribute (or undefined if attribute not present).
|
||||
* @param def Default boolean value when attribute is undefined.
|
||||
*/
|
||||
export function boolFromValue(attrValue: string|undefined, def: boolean = false) {
|
||||
// tslint:disable-next-line:triple-equals
|
||||
return attrValue == undefined ? def : attrValue.trim() !== 'false';
|
||||
export function boolFromValue(attrValue: string | undefined, def: boolean = false) {
|
||||
return attrValue === undefined ? def : attrValue.trim() !== 'false';
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the boolean state of attribute from an element
|
||||
* @param el The source of the attributes
|
||||
* @param atty Name of the attribute or a string of candidate attribute names
|
||||
* @param el The source of the attributes.
|
||||
* @param atty Name of the attribute or a string of candidate attribute names.
|
||||
* @param def Default boolean value when attribute is undefined.
|
||||
*/
|
||||
export function getBoolFromAttribute(
|
||||
|
BIN
aio/src/assets/images/logos/concept-icons/animations.png
Executable file
After Width: | Height: | Size: 4.6 KiB |
1
aio/src/assets/images/logos/concept-icons/animations.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 186 200"><defs><style>.cls-1{fill:#ff7043;}.cls-2{fill:#f4511e;}.cls-3{fill:#ffb8a1;}.cls-4{fill:#ffc6b4;}.cls-5{fill:#ffe2d9;}.cls-6{fill:#fff;}</style></defs><title>animations</title><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><polygon class="cls-1" points="92.8 0 92.8 0 92.8 0 0 33.3 14 156.1 92.8 200 92.8 200 92.8 200 171.7 156.1 186 33.3 92.8 0"/><polygon class="cls-2" points="92.8 0 92.8 22.2 92.8 22.2 92.8 123.2 92.8 123.2 92.8 200 92.8 200 171.7 156.1 186 33.3 92.8 0"/><rect class="cls-3" x="43" y="71.2" width="68.4" height="68.84" rx="4" ry="4"/><rect class="cls-4" x="57.4" y="62.3" width="68.8" height="68.84" rx="4" ry="4" transform="translate(38.6 -25.6) rotate(20)"/><rect class="cls-5" x="71.3" y="59.2" width="68.8" height="68.84" rx="4" ry="4" transform="translate(66.9 -42.1) rotate(32.5)"/><rect class="cls-6" x="81.6" y="58.3" width="68.8" height="68.84" rx="4" ry="4" transform="translate(99.6 -54.9) rotate(45)"/></g></g></svg>
|
After Width: | Height: | Size: 1.0 KiB |
BIN
aio/src/assets/images/logos/concept-icons/augury.png
Executable file
After Width: | Height: | Size: 5.2 KiB |
1
aio/src/assets/images/logos/concept-icons/augury.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 185.6 200"><defs><style>.cls-1{fill:#4a3493;}.cls-2{fill:#311b92;}.cls-3{opacity:0.5;}.cls-4{fill:#fff;}</style></defs><title>augury</title><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><polygon class="cls-1" points="92.4 0 92.4 0 92.4 0 0 33.3 14 156.1 92.4 200 92.4 200 92.4 200 171.3 156.1 185.6 33.3 92.4 0"/><polygon class="cls-2" points="92.4 0 92.4 22.2 92.4 22.2 92.4 123.2 92.4 123.2 92.4 200 92.4 200 171.3 156.1 185.6 33.3 92.4 0"/><g id="Group-2"><g id="Group-2-2" class="cls-3"><path id="Combined-Shape" class="cls-4" d="M142.4,90.2A44.1,44.1,0,1,1,92.7,52.8a40.3,40.3,0,0,1,6-.4,44.8,44.8,0,0,1,15.9,2.9,25.8,25.8,0,1,0,27.8,34.9Z"/></g><path id="Combined-Shape-2" class="cls-4" d="M140.6,87.8A44.1,44.1,0,1,1,90.9,50.4a40.3,40.3,0,0,1,6-.4,45.6,45.6,0,0,1,15.9,2.9,25.8,25.8,0,1,0,27.8,34.9Z"/></g></g></g></svg>
|
After Width: | Height: | Size: 908 B |
BIN
aio/src/assets/images/logos/concept-icons/cdk.png
Executable file
After Width: | Height: | Size: 2.9 KiB |
1
aio/src/assets/images/logos/concept-icons/cdk.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 185.6 200"><defs><style>.cls-1{fill:#03a9f4;}.cls-2{fill:#039be5;}.cls-3{fill:#fff;}</style></defs><title>cdk</title><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><polygon class="cls-1" points="92.4 0 92.4 0 92.4 0 0 33.3 14 156.1 92.4 200 92.4 200 92.4 200 171.3 156.1 185.6 33.3 92.4 0"/><polygon class="cls-2" points="92.4 0 92.4 22.2 92.4 22.2 92.4 123.2 92.4 123.2 92.4 200 92.4 200 171.3 156.1 185.6 33.3 92.4 0"/><rect class="cls-3" x="117.8" y="45" width="30" height="30" rx="1" ry="1"/><rect class="cls-3" x="117.8" y="85" width="30" height="30" rx="1" ry="1"/><rect class="cls-3" x="117.8" y="125" width="30" height="30" rx="1" ry="1"/><rect class="cls-3" x="77.8" y="125" width="30" height="30" rx="1" ry="1"/><rect class="cls-3" x="37.8" y="125" width="30" height="30" rx="1" ry="1"/><rect class="cls-3" x="37.8" y="85" width="30" height="30" rx="1" ry="1"/><rect class="cls-3" x="77.8" y="85" width="30" height="30" rx="1" ry="1"/></g></g></svg>
|
After Width: | Height: | Size: 1.0 KiB |
BIN
aio/src/assets/images/logos/concept-icons/cli.png
Executable file
After Width: | Height: | Size: 3.2 KiB |
1
aio/src/assets/images/logos/concept-icons/cli.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 185.6 200"><defs><style>.cls-1{fill:#37474f;}.cls-2{fill:#263238;}.cls-3{fill:#fff;}</style></defs><title>cli</title><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><polygon class="cls-1" points="92.4 0 92.4 0 92.4 0 0 33.3 14 156.1 92.4 200 92.4 200 92.4 200 171.3 156.1 185.6 33.3 92.4 0"/><polygon class="cls-2" points="92.4 0 92.4 22.2 92.4 22.2 92.4 123.2 92.4 123.2 92.4 200 92.4 200 171.3 156.1 185.6 33.3 92.4 0"/><path class="cls-3" d="M42.8,55v90h100V55ZM138,139.9H47.5V60.1h90.6v79.8Z"/><polygon class="cls-3" points="60.1 111.4 63.5 114.9 82.8 95.5 63.2 75.9 59.8 79.3 76 95.5 60.1 111.4"/><rect class="cls-3" x="82.8" y="117.2" width="30" height="4"/></g></g></svg>
|
After Width: | Height: | Size: 757 B |
BIN
aio/src/assets/images/logos/concept-icons/compiler.png
Executable file
After Width: | Height: | Size: 6.1 KiB |
1
aio/src/assets/images/logos/concept-icons/compiler.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 185.6 200"><defs><style>.cls-1{fill:#8bc34a;}.cls-2{fill:#7cb342;}.cls-3{fill:#fff;stroke:#fff;stroke-miterlimit:10;stroke-width:0.08px;}.cls-4{fill:#bed9a1;}</style></defs><title>compiler</title><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><polygon class="cls-1" points="92.4 0 92.4 0 92.4 0 0 33.3 14 156.1 92.4 200 92.4 200 92.4 200 171.3 156.1 185.6 33.3 92.4 0"/><polygon class="cls-2" points="92.4 0 92.4 22.2 92.4 22.2 92.4 123.2 92.4 123.2 92.4 200 92.4 200 171.3 156.1 185.6 33.3 92.4 0"/><path class="cls-3" d="M113.9,144.1l-1.5-5.5a7,7,0,0,1,2.7-7.4,26.2,26.2,0,0,0,4.8-4,36,36,0,0,0,4.1-4.8,6.7,6.7,0,0,1,7.4-2.7l5.4,1.4a1.7,1.7,0,0,0,1.4-.1c.5-.3.9-.7.9-1.1l3.7-13.8a1.9,1.9,0,0,0-1.4-2.2l-5.4-1.5a7.2,7.2,0,0,1-5.2-6,38.7,38.7,0,0,0-3.2-12.1,7.1,7.1,0,0,1,1.3-7.9l4-3.9a1.9,1.9,0,0,0,0-2.6l-10-10a1.8,1.8,0,0,0-2.5,0l-4,4a7,7,0,0,1-7.8,1.3A37.4,37.4,0,0,0,96.5,62a7.3,7.3,0,0,1-6.1-5.2L89,51.4a1.9,1.9,0,0,0-2.3-1.3L73,53.7a2.3,2.3,0,0,0-1.1.9,3.4,3.4,0,0,0-.2,1.4l1.5,5.4a6.9,6.9,0,0,1-2.7,7.4,38.3,38.3,0,0,0-4.8,4.1,47.2,47.2,0,0,0-4.1,4.8,6.8,6.8,0,0,1-7.4,2.7L48.8,79a1.7,1.7,0,0,0-1.4.1,1.9,1.9,0,0,0-.9,1.1L42.9,93.9a1.9,1.9,0,0,0,1.3,2.3l5.4,1.4a6.2,6.2,0,0,1,3,1.8,6.8,6.8,0,0,1,2.1,4.2,38.7,38.7,0,0,0,3.2,12.1,6.8,6.8,0,0,1-1.3,7.9l-4,3.9a1.9,1.9,0,0,0,0,2.6l10,10a1.9,1.9,0,0,0,2.6,0l3.9-4a6.8,6.8,0,0,1,7.9-1.3A36.8,36.8,0,0,0,89.1,138a7.2,7.2,0,0,1,6,5.2l1.5,5.4a1.7,1.7,0,0,0,2.2,1.3l13.8-3.5a2.3,2.3,0,0,0,1.1-.9A1.8,1.8,0,0,0,113.9,144.1ZM76.5,116.3a23,23,0,1,1,32.5,0A23.2,23.2,0,0,1,76.5,116.3Z"/><ellipse class="cls-4" cx="92.8" cy="100" rx="13.3" ry="13.3"/></g></g></svg>
|
After Width: | Height: | Size: 1.6 KiB |
BIN
aio/src/assets/images/logos/concept-icons/components.png
Executable file
After Width: | Height: | Size: 4.7 KiB |
1
aio/src/assets/images/logos/concept-icons/components.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 185.6 200"><defs><style>.cls-1{fill:#448aff;}.cls-2{fill:#2979ff;}.cls-3{fill:#fff;}.cls-4{fill:#a2c5ff;}</style></defs><title>components</title><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><polygon class="cls-1" points="92.4 0 92.4 0 92.4 0 0 33.3 14 156.1 92.4 200 92.4 200 92.4 200 171.3 156.1 185.6 33.3 92.4 0"/><polygon class="cls-2" points="92.4 0 92.4 22.2 92.4 22.2 92.4 123.2 92.4 123.2 92.4 200 92.4 200 171.3 156.1 185.6 33.3 92.4 0"/><polygon class="cls-3" points="73.6 131.4 54.3 98 84.3 46.1 62.8 46.1 32.8 98 62.8 150 84.4 150 73.6 131.4 73.6 131.4"/><polygon class="cls-4" points="152.8 98 122.8 46.1 84.3 46.1 54.3 98 73.6 64.7 112.1 64.7 131.3 98 112.1 131.4 73.6 131.4 84.4 150 122.8 150 152.8 98"/></g></g></svg>
|
After Width: | Height: | Size: 815 B |
BIN
aio/src/assets/images/logos/concept-icons/dependency-injection.png
Executable file
After Width: | Height: | Size: 4.3 KiB |
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200"><defs><style>.cls-1{fill:#546e7a;}.cls-2{fill:#455a64;}.cls-3,.cls-4{fill:#fff;}.cls-3{opacity:0.5;}</style></defs><title>dependency-injection</title><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><path class="cls-1" d="M0,100A100,100,0,0,0,100,200V0A100,100,0,0,0,0,100Z"/><path class="cls-2" d="M100,0V200A100,100,0,0,0,100,0Z"/><polygon class="cls-3" points="131.2 73.2 151 47.7 125.6 67.5 108.6 61.8 59.1 111.3 87.4 139.6 136.9 90.1 131.2 73.2"/><rect class="cls-4" x="74.5" y="74.3" width="40" height="60" transform="translate(101.4 -36.3) rotate(45)"/><rect class="cls-4" x="66.8" y="107" width="20" height="30" transform="translate(108.7 -18.6) rotate(45)"/><rect class="cls-4" x="42.6" y="131.1" width="40" height="10" transform="translate(114.6 -4.4) rotate(45)"/></g></g></svg>
|
After Width: | Height: | Size: 876 B |
BIN
aio/src/assets/images/logos/concept-icons/forms.png
Executable file
After Width: | Height: | Size: 3.4 KiB |
1
aio/src/assets/images/logos/concept-icons/forms.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 186 200"><defs><style>.cls-1{fill:#e57373;}.cls-2{fill:#ef5350;}.cls-3{fill:#fff;}</style></defs><title>forms</title><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><polygon class="cls-1" points="92.8 0 92.8 0 92.8 0 0 33.3 14 156.1 92.8 200 92.8 200 92.8 200 171.7 156.1 186 33.3 92.8 0"/><polygon class="cls-2" points="92.8 0 92.8 22.2 92.8 22.2 92.8 123.2 92.8 123.2 92.8 200 92.8 200 171.7 156.1 186 33.3 92.8 0"/><path class="cls-3" d="M138.7,90H47c-2.4,0-4-2.3-4-5.6V55.6c0-3.3,1.6-5.6,4-5.6h92.1c2.3,0,3.9,2.3,3.9,5.6V84.4C142.6,87.7,141,90,138.7,90Z"/><path class="cls-3" d="M139.8,120H96.3c-2,0-3.3-1.4-3.3-3.6V103.6c0-2.2,1.3-3.6,3.3-3.6h43.5c1.9,0,3.2,1.4,3.2,3.6v12.8A3.4,3.4,0,0,1,139.8,120Z"/><path class="cls-3" d="M139.8,150H96.3c-2,0-3.3-1.5-3.3-3.8V133.8c0-2.3,1.3-3.8,3.3-3.8h43.5c1.9,0,3.2,1.5,3.2,3.8v12.4C143,148.1,141.7,150,139.8,150Z"/><rect class="cls-3" x="43" y="100" width="40" height="50" rx="4" ry="4"/></g></g></svg>
|
After Width: | Height: | Size: 1.0 KiB |
BIN
aio/src/assets/images/logos/concept-icons/http.png
Executable file
After Width: | Height: | Size: 4.7 KiB |
1
aio/src/assets/images/logos/concept-icons/http.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 185.6 200"><defs><style>.cls-1{fill:#2e7d32;}.cls-2{fill:#1b5e20;}.cls-3,.cls-4,.cls-5{fill:#fff;stroke:#fff;stroke-miterlimit:10;}.cls-3{stroke-width:0.24px;}.cls-4{stroke-width:0.27px;}.cls-5{stroke-width:0.27px;}</style></defs><title>http</title><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><polygon class="cls-1" points="92.4 0 92.4 0 92.4 0 0 33.3 14 156.1 92.4 200 92.4 200 92.4 200 171.3 156.1 185.6 33.3 92.4 0"/><polygon class="cls-2" points="92.4 0 92.4 22.2 92.4 22.2 92.4 123.2 92.4 123.2 92.4 200 92.4 200 171.3 156.1 185.6 33.3 92.4 0"/><polygon class="cls-3" points="107.5 150 100.3 148.2 125.7 50 132.9 51.8 107.5 150"/><polygon class="cls-3" points="81.5 150 74.3 148.2 99.7 50 106.8 51.8 81.5 150"/><circle class="cls-4" cx="65.5" cy="82.8" r="7.1"/><circle class="cls-5" cx="65.5" cy="113.1" r="7.1"/></g></g></svg>
|
After Width: | Height: | Size: 916 B |
BIN
aio/src/assets/images/logos/concept-icons/i18n.png
Executable file
After Width: | Height: | Size: 7.1 KiB |
1
aio/src/assets/images/logos/concept-icons/i18n.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 186 200"><defs><style>.cls-1{fill:#4caf50;}.cls-2{fill:#388e3c;}.cls-3{fill:#fff;}</style></defs><title>i18n</title><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><polygon class="cls-1" points="92.8 0 92.8 0 92.8 0 0 33.3 14 156.1 92.8 200 92.8 200 92.8 200 171.7 156.1 186 33.3 92.8 0"/><polygon class="cls-2" points="92.8 0 92.8 22.2 92.8 22.2 92.8 123.2 92.8 123.2 92.8 200 92.8 200 171.7 156.1 186 33.3 92.8 0"/><path class="cls-3" d="M93,150a50,50,0,0,1-50-50A49.3,49.3,0,0,1,71.4,55.1,43.5,43.5,0,0,1,82,51.3,41.1,41.1,0,0,1,92.6,50c27.4,0,50,22.4,50.4,50a49.5,49.5,0,0,1-17.3,37.7,47.7,47.7,0,0,1-21.7,11A42.5,42.5,0,0,1,93,150Zm-.4-95.9a39,39,0,0,0-9.7,1.1,47.2,47.2,0,0,0-9.7,3.5A45.4,45.4,0,0,0,47.1,100,46,46,0,0,0,93,145.9a45.3,45.3,0,0,0,10.1-1.1A43.1,43.1,0,0,0,123,134.6,45.5,45.5,0,0,0,139,100C138.6,74.7,117.8,54.1,92.6,54.1Z"/><rect class="cls-3" x="90.9" y="54.1" width="3.9" height="91.78"/><path class="cls-3" d="M92.8,147.8c-16.3,0-29.7-21.5-29.7-48s13.4-48,29.7-48,29.7,21.5,29.7,48S109.2,147.8,92.8,147.8Zm0-92.1C78.6,55.7,67,75.5,67,99.8s11.6,44.1,25.8,44.1,25.8-19.8,25.8-44.1S107,55.7,92.8,55.7Z"/><rect class="cls-3" x="47.1" y="97.9" width="91.8" height="3.91"/><rect class="cls-3" x="53" y="76" width="80.5" height="3.91"/><rect class="cls-3" x="53" y="117.8" width="80.5" height="3.91"/></g></g></svg>
|
After Width: | Height: | Size: 1.4 KiB |
BIN
aio/src/assets/images/logos/concept-icons/karma.png
Executable file
After Width: | Height: | Size: 5.0 KiB |
1
aio/src/assets/images/logos/concept-icons/karma.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 186 200"><defs><style>.cls-1{fill:#3dbeae;}.cls-2{fill:#349b8b;}.cls-3,.cls-4{fill:#fff;}.cls-4{opacity:0.5;isolation:isolate;}</style></defs><title>karma</title><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><polygon class="cls-1" points="92.8 0 92.8 0 92.8 0 0 33.3 14 156.1 92.8 200 92.8 200 92.8 200 171.7 156.1 186 33.3 92.8 0"/><polygon class="cls-2" points="92.8 0 92.8 22.2 92.8 22.2 92.8 123.2 92.8 123.2 92.8 200 92.8 200 171.7 156.1 186 33.3 92.8 0"/><polygon class="cls-3" points="76.6 59.9 99.7 59.9 99.7 82.2 115.5 59.9 140.1 59.9 99.9 119.2 99.9 139.9 92.4 139.9 76.6 84.2 76.6 59.9"/><polygon class="cls-4" points="143 139.9 114.4 97.8 101.8 116.4 118.2 139.9 143 139.9"/><polygon class="cls-3" points="39 84.2 51.3 128.4 69.6 95.6 57.7 106.3 39 84.2"/><polygon class="cls-4" points="80.7 139.1 69.6 95.6 51.3 128.4 62.8 117.3 80.7 139.1"/></g></g></svg>
|
After Width: | Height: | Size: 950 B |
BIN
aio/src/assets/images/logos/concept-icons/labs.png
Executable file
After Width: | Height: | Size: 5.6 KiB |
1
aio/src/assets/images/logos/concept-icons/labs.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 179.2 192.7"><defs><style>.cls-1{fill:#1de9b6;}.cls-2{fill:#00bfa5;}.cls-3{fill:#bbf8e9;}.cls-4{fill:#fff;}</style></defs><title>labs</title><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><polygon class="cls-1" points="89.4 0 89.4 0 89.4 0 0 32 13.5 150.4 89.4 192.7 89.4 192.7 89.4 192.7 165.4 150.4 179.2 32 89.4 0"/><polygon class="cls-2" points="89.4 0 89.4 21.4 89.4 21.4 89.4 118.7 89.4 118.7 89.4 192.7 89.4 192.7 165.4 150.4 179.2 32 89.4 0"/><path class="cls-3" d="M124.1,75.8l-37-11.5a2.8,2.8,0,0,0-3.5,1.9l-.2.5a2.9,2.9,0,0,0,1.9,3.5l7.1,2.2L85.2,95.5a30.4,30.4,0,0,0,1.7,60.7A30.7,30.7,0,0,0,98,153.8h0l1.5-.7a30.5,30.5,0,0,0,4.8-51.5l7.2-23.3,10.7,3.4a3,3,0,0,0,3.6-1.9v-.5A2.7,2.7,0,0,0,124.1,75.8Z"/><ellipse class="cls-4" cx="90.4" cy="44.1" rx="7.8" ry="7.9"/><ellipse class="cls-4" cx="126.8" cy="60.3" rx="3.8" ry="3.8"/><ellipse class="cls-4" cx="51.5" cy="73.9" rx="4.9" ry="4.9"/><path class="cls-4" d="M111.3,134.2a26.3,26.3,0,1,1-50.2-15.6"/></g></g></svg>
|
After Width: | Height: | Size: 1.0 KiB |
BIN
aio/src/assets/images/logos/concept-icons/language-services.png
Executable file
After Width: | Height: | Size: 5.2 KiB |
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 186 200"><defs><style>.cls-1{fill:#5c6bc0;}.cls-2{fill:#3949ab;}.cls-3{fill:#fff;}.cls-4{fill:#aeb5e0;}</style></defs><title>language-services</title><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><polygon class="cls-1" points="92.8 0 92.8 0 92.8 0 0 33.3 14 156.1 92.8 200 92.8 200 92.8 200 171.7 156.1 186 33.3 92.8 0"/><polygon class="cls-2" points="92.8 0 92.8 22.2 92.8 22.2 92.8 123.2 92.8 123.2 92.8 200 92.8 200 171.7 156.1 186 33.3 92.8 0"/><path class="cls-3" d="M93,50a50,50,0,1,0,50,50A50,50,0,0,0,93,50Zm-5.3,80.3L68.9,111.5l6.7-6.7L86,115.2l24-38.8,8,5Z"/><polygon class="cls-4" points="86 115.2 75.6 104.8 68.9 111.5 87.7 130.3 118 81.4 110 76.4 86 115.2"/></g></g></svg>
|
After Width: | Height: | Size: 766 B |
BIN
aio/src/assets/images/logos/concept-icons/lazy-loading.png
Executable file
After Width: | Height: | Size: 3.7 KiB |
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200"><defs><style>.cls-1{fill:#546e7a;}.cls-2{fill:#455a64;}.cls-3{fill:#fff;}</style></defs><title>lazy-loading</title><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><path class="cls-1" d="M0,100A100,100,0,0,0,100,200V0A100,100,0,0,0,0,100Z"/><path class="cls-2" d="M100,0V200A100,100,0,0,0,100,0Z"/><path class="cls-3" d="M120,93.7a24.4,24.4,0,0,0,10.1,2.1,24.9,24.9,0,1,0-20.3-10.6l-10,10.1-9.8-9.8a24.5,24.5,0,0,0,4.7-14.6,24.7,24.7,0,1,0-15,22.9l10.8,10.8L47.7,147.5l3.7,3.8a7.9,7.9,0,0,0,11.2,0L99.8,114l37.6,37.7a7.9,7.9,0,0,0,11.2,0l3.7-3.8-43.2-43.3ZM109.8,70.9a20.4,20.4,0,1,1,20.3,20.4,20.8,20.8,0,0,1-6.6-1.1,20.5,20.5,0,0,1-13.7-19.3ZM76.2,90.3a18.9,18.9,0,0,1-6.3,1,20.4,20.4,0,1,1,20.3-20.4,20.6,20.6,0,0,1-3.4,11.3A20.3,20.3,0,0,1,76.2,90.3Z"/></g></g></svg>
|
After Width: | Height: | Size: 858 B |
BIN
aio/src/assets/images/logos/concept-icons/libraries.png
Executable file
After Width: | Height: | Size: 4.0 KiB |
1
aio/src/assets/images/logos/concept-icons/libraries.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200"><defs><style>.cls-1{fill:#546e7a;}.cls-2{fill:#455a64;}.cls-3,.cls-4{fill:#fff;}.cls-4{opacity:0.5;}</style></defs><title>libraries</title><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><path class="cls-1" d="M0,100A100,100,0,0,0,100,200V0A100,100,0,0,0,0,100Z"/><path class="cls-2" d="M100,0V200A100,100,0,0,0,100,0Z"/><path class="cls-3" d="M25,63.3v75H58.3v-75Zm25,60H33.3V120H50ZM50,90H33.3V86.6H50Zm0-8.4H33.3V78.3H50Z"/><rect class="cls-4" x="33.3" y="77.9" width="16.7" height="4"/><rect class="cls-4" x="33.3" y="86.6" width="16.7" height="3.34"/><rect class="cls-4" x="33.3" y="120" width="16.7" height="3.34"/><path class="cls-3" d="M65,63.3v75H98.3v-75Zm25,60H73.3V120H90ZM90,90H73.3V86.6H90Zm0-8.4H73.3V78.3H90Z"/><rect class="cls-4" x="73.3" y="78.3" width="16.7" height="3.34"/><rect class="cls-4" x="73.3" y="86.6" width="16.7" height="3.34"/><rect class="cls-4" x="73.3" y="120" width="16.7" height="3.34"/><path class="cls-3" d="M98.3,85.3l53.1,53L175,114.7l-53.1-53ZM158.5,110l-11.8,11.8-2.3-2.3,11.8-11.8ZM134.9,86.4,123.1,98.2l-2.3-2.3,11.8-11.8ZM129,80.5,117.2,92.3,114.9,90l11.8-11.8Z"/><rect class="cls-4" x="113.6" y="83.6" width="16.7" height="3.34" transform="translate(-24.6 111.2) rotate(-45)"/><rect class="cls-4" x="119.5" y="89.5" width="16.7" height="3.34" transform="translate(-27 117.1) rotate(-45)"/><rect class="cls-4" x="143.1" y="113.1" width="16.7" height="3.34" transform="translate(-36.8 140.7) rotate(-45)"/></g></g></svg>
|
After Width: | Height: | Size: 1.5 KiB |
BIN
aio/src/assets/images/logos/concept-icons/material.png
Executable file
After Width: | Height: | Size: 5.2 KiB |
1
aio/src/assets/images/logos/concept-icons/material.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 179.2 192.7"><defs><style>.cls-1{fill:#ffa726;}.cls-2{fill:#fb8c00;}.cls-3{fill:#ffe0b2;}.cls-4{fill:#fff3e0;}.cls-5{fill:#fff;}</style></defs><title>material</title><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><polygon class="cls-1" points="89.4 0 89.4 0 89.4 0 0 32 13.5 150.4 89.4 192.7 89.4 192.7 89.4 192.7 165.4 150.4 179.2 32 89.4 0"/><polygon class="cls-2" points="89.4 0 89.4 21.4 89.4 21.4 89.4 118.7 89.4 118.7 89.4 192.7 89.4 192.7 165.4 150.4 179.2 32 89.4 0"/><polygon class="cls-3" points="102.9 146.3 39.6 115.8 75.9 93.4 139.6 124 102.9 146.3"/><polygon class="cls-4" points="102.9 122.8 39.6 92.2 75.9 69.9 139.6 100.5 102.9 122.8"/><polygon class="cls-5" points="102.9 99.3 39.6 68.7 75.9 46.3 139.6 76.9 102.9 99.3"/></g></g></svg>
|
After Width: | Height: | Size: 833 B |
BIN
aio/src/assets/images/logos/concept-icons/performance.png
Executable file
After Width: | Height: | Size: 5.4 KiB |
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200"><defs><style>.cls-1{fill:#546e7a;}.cls-2{fill:#455a64;}.cls-3{fill:#bbc5ca;}.cls-4{fill:#fff;}.cls-5{fill:none;}</style></defs><title>performance</title><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><path class="cls-1" d="M0,100A100,100,0,0,0,100,200V0A100,100,0,0,0,0,100Z"/><path class="cls-2" d="M100,0V200A100,100,0,0,0,100,0Z"/><circle class="cls-3" cx="100" cy="100" r="50"/><circle class="cls-4" cx="100" cy="100" r="8.33"/><rect class="cls-4" x="94.82" y="85.42" width="35.36" height="4.17" transform="translate(-28.92 105.18) rotate(-45)"/><path class="cls-5" d="M100,59.09A41,41,0,0,0,59.09,100h81.82A41,41,0,0,0,100,59.09Z"/><path class="cls-5" d="M100,140.91A41,41,0,0,0,140.91,100H59.09A41,41,0,0,0,100,140.91Z"/><path class="cls-4" d="M100,50a50,50,0,0,0-50,50h9.09a40.91,40.91,0,0,1,81.82,0H150A50,50,0,0,0,100,50Z"/></g></g></svg>
|
After Width: | Height: | Size: 936 B |
BIN
aio/src/assets/images/logos/concept-icons/protractor.png
Executable file
After Width: | Height: | Size: 4.8 KiB |
1
aio/src/assets/images/logos/concept-icons/protractor.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 186 200"><defs><style>.cls-1{fill:#e13439;}.cls-2{fill:#b52f32;}.cls-3{fill:#fff;}</style></defs><title>protractor</title><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><polygon class="cls-1" points="92.8 0 92.8 0 92.8 0 0 33.3 14 156.1 92.8 200 92.8 200 92.8 200 171.7 156.1 186 33.3 92.8 0"/><polygon class="cls-2" points="92.8 0 92.8 22.2 92.8 22.2 92.8 123.2 92.8 123.2 92.8 200 92.8 200 171.7 156.1 186 33.3 92.8 0"/><path class="cls-3" d="M145.3,117.1v-3.6H158a64.9,64.9,0,0,0-18-43.1L130.4,80l-2.5-2.5,9.5-9.6A64.3,64.3,0,0,0,95.3,50.4V63H91.7V50.3A65,65,0,0,0,48.6,67.9l9.6,9.6L55.6,80,46,70.4a64.8,64.8,0,0,0-17.9,43.1H40.7v3.6H28v14.8H158V117.1Zm-93.5-2.2a41.3,41.3,0,1,1,82.5,0Z"/></g></g></svg>
|
After Width: | Height: | Size: 786 B |
BIN
aio/src/assets/images/logos/concept-icons/pwa.png
Executable file
After Width: | Height: | Size: 4.9 KiB |
1
aio/src/assets/images/logos/concept-icons/pwa.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 186 200"><defs><style>.cls-1{fill:#9c27b0;}.cls-2{fill:#7b1fa2;}.cls-3,.cls-4{fill:#fff;}.cls-4{opacity:0.8;}</style></defs><title>pwa</title><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><polygon class="cls-1" points="92.8 0 92.8 0 92.8 0 0 33.3 14 156.1 92.8 200 92.8 200 92.8 200 171.7 156.1 186 33.3 92.8 0"/><polygon class="cls-2" points="92.8 0 92.8 22.2 92.8 22.2 92.8 123.2 92.8 123.2 92.8 200 92.8 200 171.7 156.1 186 33.3 92.8 0"/><path class="cls-3" d="M123.2,115.4l3.7-9.3h10.7L132.5,92l6.4-16L157,124H143.6l-3.1-8.6Z"/><path class="cls-4" d="M111.8,124l19.4-48H118.4l-13.3,31L95.6,76H85.7L75.6,107,68.4,92.9l-6.5,19.9L68.5,124H81.2l9.1-27.8L99.1,124Z"/><path class="cls-3" d="M41.2,107.5h8a21.2,21.2,0,0,0,6.4-.8l2.1-6.3,5.7-17.6a9,9,0,0,0-1.5-1.9C58.9,77.6,54.6,76,49,76H29v48H41.2ZM51.7,87a6.3,6.3,0,0,1,1.8,4.7A6.7,6.7,0,0,1,52,96.3c-1.2,1.3-3.2,1.9-6.2,1.9H41.2V85.3h4.7C48.6,85.3,50.6,85.9,51.7,87Z"/></g></g></svg>
|
After Width: | Height: | Size: 1014 B |
BIN
aio/src/assets/images/logos/concept-icons/router.png
Executable file
After Width: | Height: | Size: 3.5 KiB |
1
aio/src/assets/images/logos/concept-icons/router.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 185.6 200"><defs><style>.cls-1{fill:#1565c0;}.cls-2{fill:#0d47a1;}.cls-3{fill:#fff;}.cls-4{fill:#86a3d0;}</style></defs><title>router</title><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><polygon class="cls-1" points="92.4 0 92.4 0 92.4 0 0 33.3 14 156.1 92.4 200 92.4 200 92.4 200 171.3 156.1 185.6 33.3 92.4 0"/><polygon class="cls-2" points="92.4 0 92.4 22.2 92.4 22.2 92.4 123.2 92.4 123.2 92.4 200 92.4 200 171.3 156.1 185.6 33.3 92.4 0"/><polygon class="cls-3" points="76.1 128.9 42.8 139.9 42.8 60.9 76.1 49.9 76.1 128.9"/><polygon class="cls-3" points="142.8 128.9 109.5 139.9 109.5 60.9 142.8 49.9 142.8 128.9"/><polygon class="cls-4" points="109.5 139.9 76.1 128.9 76.1 49.9 109.5 60.9 109.5 139.9"/></g></g></svg>
|
After Width: | Height: | Size: 805 B |
BIN
aio/src/assets/images/logos/concept-icons/templates.png
Executable file
After Width: | Height: | Size: 3.0 KiB |
1
aio/src/assets/images/logos/concept-icons/templates.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200"><defs><style>.cls-1{fill:#546e7a;}.cls-2{fill:#455a64;}.cls-3{fill:#a2adb2;}.cls-4{fill:#e3e6e8;}.cls-5{fill:#fff;}</style></defs><title>templates</title><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><path class="cls-1" d="M0,100A100,100,0,0,0,100,200V0A100,100,0,0,0,0,100Z"/><path class="cls-2" d="M100,0V200A100,100,0,0,0,100,0Z"/><rect class="cls-3" x="80" y="70" width="60" height="80"/><rect class="cls-4" x="70" y="60" width="60" height="80"/><rect class="cls-5" x="60" y="50" width="60" height="80"/></g></g></svg>
|
After Width: | Height: | Size: 612 B |
BIN
aio/src/assets/images/logos/concept-icons/universal.png
Executable file
After Width: | Height: | Size: 3.6 KiB |
1
aio/src/assets/images/logos/concept-icons/universal.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 186 200"><defs><style>.cls-1{fill:#00acc1;}.cls-2{fill:#00838f;}.cls-3{fill:#fff;}.cls-4{fill:#80d6e0;}</style></defs><title>universal</title><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><polygon class="cls-1" points="92.8 0 92.8 0 92.8 0 0 33.3 14 156.1 92.8 200 92.8 200 92.8 200 171.7 156.1 186 33.3 92.8 0"/><polygon class="cls-2" points="92.8 0 92.8 22.2 92.8 22.2 92.8 123.2 92.8 123.2 92.8 200 92.8 200 171.7 156.1 186 33.3 92.8 0"/><path class="cls-3" d="M121,40H65A12,12,0,0,0,53,52v96a12,12,0,0,0,12,12h56a12,12,0,0,0,12-12V52A12,12,0,0,0,121,40ZM93,150a10,10,0,1,1,10-10A10,10,0,0,1,93,150Zm30-44a4,4,0,0,1-4,4H67a4,4,0,0,1-4-4V94a4,4,0,0,1,4-4h52a4,4,0,0,1,4,4Zm0-30a4,4,0,0,1-4,4H67a4,4,0,0,1-4-4V64a4,4,0,0,1,4-4h52a4,4,0,0,1,4,4Z"/><rect class="cls-4" x="63" y="60" width="60" height="20" rx="4" ry="4"/><rect class="cls-4" x="63" y="90" width="60" height="20" rx="4" ry="4"/><circle class="cls-4" cx="93" cy="140" r="10"/></g></g></svg>
|
After Width: | Height: | Size: 1.0 KiB |