feat(screenshot): add control panel for pixacto (#4055)

This commit is contained in:
tinayuangao
2017-04-17 16:17:41 -06:00
committed by Jeremy Elbourn
parent ee1a5a7aa3
commit b2ca0f5d84
25 changed files with 863 additions and 2 deletions
+8 -2
View File
@@ -2,7 +2,7 @@ import gulp = require('gulp');
import {execNodeTask} from '../util/task_helpers';
import {DIST_MATERIAL} from '../constants';
gulp.task('lint', ['tslint', 'stylelint', 'madge']);
gulp.task('lint', ['tslint', 'stylelint', 'madge', 'dashboardlint']);
/** Task that runs madge to detect circular dependencies. */
gulp.task('madge', ['library:clean-build'], execNodeTask('madge', ['--circular', DIST_MATERIAL]));
@@ -12,5 +12,11 @@ gulp.task('stylelint', execNodeTask(
'stylelint', ['src/**/*.scss', '--config', 'stylelint-config.json', '--syntax', 'scss']
));
gulp.task('dashboardlint', execNodeTask(
'stylelint', ['tools/screenshot-test/**/*.css', '--config', 'stylelint-config.json',
'--syntax', 'scss']
));
/** Task to run TSLint against the e2e/ and src/ directories. */
gulp.task('tslint', execNodeTask('tslint', ['-c', 'tslint.json', '+(src|e2e|tools)/**/*.ts']));
gulp.task('tslint', execNodeTask('tslint', ['-c', 'tslint.json', '+(src|e2e|tools)/**/*.ts',
'--exclude', '**/node_modules/**/*']));
+47
View File
@@ -0,0 +1,47 @@
{
"project": {
"version": "1.0.0-beta.26",
"name": "screenshot-test"
},
"apps": [
{
"root": "src",
"outDir": "dist",
"assets": [
"assets",
"favicon.ico"
],
"index": "index.html",
"main": "main.ts",
"test": "test.ts",
"tsconfig": "tsconfig.json",
"prefix": "app",
"mobile": false,
"styles": [
"styles.css"
],
"scripts": [],
"environmentSource": "environments/environment.ts",
"environment": {
"dev": "environments/environment.ts",
"prod": "environments/environment.prod.ts"
}
}
],
"defaults": {
"styleExt": "css",
"prefixInterfaces": false,
"inline": {
"style": false,
"template": false
},
"spec": {
"class": false,
"component": true,
"directive": true,
"module": false,
"pipe": true,
"service": true
}
}
}
+36
View File
@@ -0,0 +1,36 @@
{
"name": "screenshot-test",
"version": "0.0.0",
"license": "MIT",
"angular-cli": {},
"scripts": {
"ng": "ng",
"start": "ng serve"
},
"private": true,
"dependencies": {
"@angular/animations": "^4.0.2",
"@angular/common": "^4.0.0",
"@angular/compiler": "^4.0.0",
"@angular/compiler-cli": "^4.0.0",
"@angular/core": "^4.0.0",
"@angular/forms": "^4.0.0",
"@angular/http": "^4.0.0",
"@angular/material": "^2.0.0-beta.3",
"@angular/platform-browser": "^4.0.0",
"@angular/platform-browser-dynamic": "^4.0.0",
"@angular/router": "^4.0.0",
"core-js": "^2.4.1",
"firebase": "^3.7.6",
"rxjs": "^5.0.1",
"ts-helpers": "^1.1.1",
"zone.js": "^0.8.4"
},
"devDependencies": {
"@angular/cli": "^1.0.0",
"@angular/compiler-cli": "^4.0.0",
"@types/node": "^6.0.42",
"ts-node": "1.2.1",
"typescript": "~2.2.1"
}
}
@@ -0,0 +1,176 @@
import {Injectable} from '@angular/core';
import {Http} from '@angular/http';
import * as firebase from 'firebase';
import 'rxjs/add/operator/toPromise';
const config = require('../config.json');
import {ScreenshotResultSummary} from './screenshot-result';
/** The service to fetch data from firebase database */
@Injectable()
export class FirebaseService {
/** The current user */
user: firebase.User;
/** The screenshot results */
screenshotResultSummary: ScreenshotResultSummary;
constructor(private _http: Http) {
// Initialize Firebase
firebase.initializeApp(config.firebase);
firebase.auth().onAuthStateChanged((user: firebase.User) => {
this.user = user;
});
}
/** Get the firebase storage test image folder url */
getTestScreenshotImageUrl(filename: string): firebase.Promise<string> {
return this._storageRef().child('test').child(filename).getDownloadURL();
}
/** Get the firebase storage diff image folder url */
getDiffScreenshotImageUrl(filename: string): firebase.Promise<string> {
return this._storageRef().child('diff').child(filename).getDownloadURL();
}
/** Get the firebase storage golden image folder url */
getGoldScreenshotImageUrl(filename: string): firebase.Promise<string> {
return firebase.storage().ref('goldens').child(filename).getDownloadURL();
}
/** Set pull request number. All test information and pull request information will be retrived
* from database
*/
set prNumber(prNumber: string){
this.screenshotResultSummary = new ScreenshotResultSummary();
this.screenshotResultSummary.prNumber = prNumber;
this._readPullRequestScreenshotReport();
}
_readPullRequestScreenshotReport() {
if (!this.screenshotResultSummary.prNumber) {
return;
}
this._databaseRef().on('value', (snapshot: firebase.database.DataSnapshot) => {
let counter = 0;
snapshot.forEach((childSnapshot: firebase.database.DataSnapshot) => {
let childValue = childSnapshot.val();
switch (childSnapshot.key) {
case 'sha':
this._readSha(childValue);
break;
case 'travis':
this.screenshotResultSummary.travis = childValue;
break;
case 'results':
this._readResults(childSnapshot);
break;
}
counter++;
if (counter === snapshot.numChildren()) {
return true;
}
});
});
}
signIntoGithub(): firebase.Promise<void> {
return firebase.auth().signInWithRedirect(new firebase.auth.GithubAuthProvider());
}
signOutFromGithub() {
firebase.auth().signOut();
}
/** Change the PR status to approved to let Firebase Functions copy test images to goldens */
approvePullRequest() {
return this._databaseRef().child('approved').child(this.screenshotResultSummary.sha)
.set(Date.now());
}
/**
* Change the commit's screenshot test status on GitHub
* The value in result/$sha/$result will trigger GitHub status update:
* true - The GitHub status for this SHA will become `success` for Screenshot Test
* false - The GitHub status for this SHA will become `failure` for Screenshot Test
* In this dashboard, we only approve pull requests, so the value is always `true`
*/
updatePullRequestResult() {
return this._databaseRef().child('result').child(this.screenshotResultSummary.sha)
.set(true);
}
/** Reference to the firebase database where the JSON test results and meatadate is stored. */
_databaseRef(): firebase.database.Reference {
return firebase.database().ref('screenshot').child('reports')
.child(this.screenshotResultSummary.prNumber);
}
/** Reference to the firebase storage bucket where the screenshot PNG files are stored. */
_storageRef(): firebase.storage.Reference {
return firebase.storage().ref('screenshots').child(this.screenshotResultSummary.prNumber);
}
/** Read the results from database adn put the results in screenshotReusltSummary */
_readResults(childSnapshot: firebase.database.DataSnapshot) {
let childCounter = 0;
this.screenshotResultSummary.collapse = [];
this.screenshotResultSummary.testNames = [];
this.screenshotResultSummary.testResultsByName.clear();
childSnapshot.forEach((resultSnapshot: firebase.database.DataSnapshot) => {
this._addTestResults(resultSnapshot.key, resultSnapshot.val());
childCounter++;
if (childCounter === childSnapshot.numChildren()) {
return true;
}
});
}
_addTestResults(name: string, value: boolean) {
this.screenshotResultSummary.testResultsByName.set(name, value);
this.screenshotResultSummary.testNames.push(name);
this.screenshotResultSummary.collapse.push(value);
}
/** Read SHA and approved status from database and put them in ScreenshotResultSummary */
_readSha(childValue) {
this.screenshotResultSummary.sha = childValue;
// Get github status
this.getGithubStatus();
// Get test allTestsPassedOrApproved
this._databaseRef().child(`result/${childValue}`).once('value')
.then((dataSnapshot: firebase.database.DataSnapshot) => {
this.screenshotResultSummary.allTestsPassedOrApproved = dataSnapshot.val();
});
// Get the approved SHA and date time
this._databaseRef().child(`approved/${childValue}`).once('value')
.then((dataSnapshot: firebase.database.DataSnapshot) => {
this.screenshotResultSummary.approvedTime = dataSnapshot.val();
});
}
getGithubStatus() {
let url =
`https://api.github.com/repos/${config.repoSlug}/commits/` +
`${this.screenshotResultSummary.sha}/status`;
return this._http.get(url).toPromise()
.then((response) => {
let statusResponse = response.json();
let screenshotStatus = statusResponse.statuses.find((status) =>
status.context === 'Screenshot Tests');
switch (screenshotStatus && screenshotStatus.state) {
case 'success':
this.screenshotResultSummary.githubStatus = true;
break;
case 'failure':
this.screenshotResultSummary.githubStatus = false;
return;
}
});
}
}
@@ -0,0 +1,6 @@
<md-toolbar color="primary">
<div *ngIf="user">{{user.email}}</div>
<button md-button (click)="signIntoGithub()" *ngIf="!user">Sign In with Github</button>
<button md-button (click)="signOutFromGithub()" *ngIf="user">Sign out</button>
</md-toolbar>
@@ -0,0 +1,24 @@
import {Component} from '@angular/core';
import {FirebaseService} from '../firebase.service';
/** Nav bar with ability to sign in or sign out Github account */
@Component({
selector: 'app-nav',
templateUrl: './nav.component.html'
})
export class NavComponent {
get user() {
return this._service.user;
}
constructor(private _service: FirebaseService) { }
signIntoGithub() {
this._service.signIntoGithub();
}
signOutFromGithub() {
this._service.signOutFromGithub();
}
}
@@ -0,0 +1,10 @@
import {Component} from '@angular/core';
@Component({
selector: 'app-root',
template: `
<app-nav></app-nav>
<router-outlet></router-outlet>
`
})
export class PixactoDashboardComponent {}
@@ -0,0 +1,33 @@
import {BrowserModule} from '@angular/platform-browser';
import {NgModule} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {HttpModule} from '@angular/http';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
import {MaterialModule} from '@angular/material';
import {FirebaseService} from './firebase.service';
import {routing} from './routes';
import {PixactoDashboardComponent} from './pixacto.dashboard.component';
import {ViewerComponent} from './viewer/viewer.component';
import {ResultComponent} from './result/result.component';
import {NavComponent} from './nav/nav.component';
@NgModule({
declarations: [
PixactoDashboardComponent,
ViewerComponent,
ResultComponent,
NavComponent
],
imports: [
BrowserModule,
BrowserAnimationsModule,
FormsModule,
HttpModule,
MaterialModule,
routing,
],
providers: [FirebaseService],
bootstrap: [PixactoDashboardComponent]
})
export class PixactoDashboardModule { }
@@ -0,0 +1,58 @@
img {
border: 1px solid #666;
width: 100%;
}
.images-section {
display:flex;
}
.image-title {
text-transform: uppercase;
}
.image-section {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.side-by-side .diff-section {
display: none;
}
.flip .diff-section {
visibility: hidden;
}
.flip .test-section,
.flip .gold-section {
position: absolute;
top: 80px;
left: 0;
}
.flip .test-section {
visibility: visible;
}
.flip .gold-section {
visibility: hidden;
}
.flip.flipping .test-section {
visibility: hidden;
}
.flip.flipping .gold-section {
visibility: visible;
}
.failed {
color: red;
}
.passed {
color: forestgreen;
}
@@ -0,0 +1,39 @@
<md-card>
<md-card-header>
<md-card-title>
<md-icon (click)="toggleCollapse()">{{collapseIcon}}</md-icon>
{{testName}}
</md-card-title>
<md-card-subtitle [ngClass]="resultText.toLowerCase()">{{resultText}}</md-card-subtitle>
</md-card-header>
<md-card-content *ngIf="!collapse">
<div class="images-section" [ngClass]="mode" [class.flipping]="flipping">
<div class="image-section test-section" >
<div class="image-title">Test image</div>
<div><img [src]="testImageUrl"/></div>
</div>
<div class="image-section diff-section" >
<div class="image-title">Diff image</div>
<div><img [src]="diffImageUrl"/></div>
</div>
<div class="image-section gold-section">
<div class="image-title">Reference image</div>
<div><img [src]="goldImageUrl"/></div>
</div>
</div>
</md-card-content>
<md-card-actions>
<md-button-toggle-group [(ngModel)]="mode" class="mode-section">
<md-button-toggle value="diff">
Diff
</md-button-toggle>
<md-button-toggle value="flip">
Flip
</md-button-toggle>
<md-button-toggle value="side">
Side by side
</md-button-toggle>
</md-button-toggle-group>
<button md-button color="primary" (click)="flip()" *ngIf="mode === 'flip'">Flip</button>
</md-card-actions>
</md-card>
@@ -0,0 +1,126 @@
import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
Input,
Output,
EventEmitter
} from '@angular/core';
import {FirebaseService} from '../firebase.service';
/**
* Component to display test result, test name, test image, reference image, and diff image.
* User can change the view mode, or collapse the panel to hide the images.
*/
@Component({
selector: 'app-result',
templateUrl: './result.component.html',
styleUrls: ['./result.component.css'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ResultComponent {
/** Test name, display on top of the result card */
testName: string;
/** Test, diff and golden image urls */
testImageUrl: string;
diffImageUrl: string;
goldImageUrl: string;
/**
* Test result, auto set collapse to be the same as the result value
*/
@Input()
get result() {
return this._result;
}
set result(value: boolean) {
this._result = value;
this.collapse = value;
}
_result: boolean;
get resultText() {
return this._result ? 'Passed' : 'Failed';
}
/** Collapse: whether collapse or expand the card to show images */
@Input() collapse: boolean = true;
/** Mode: the result card has three modes, flip, side by side, and diff */
@Input()
get mode() {
return this._mode;
}
set mode(value: 'flip' | 'side-by-side' | 'diff') {
this._mode = value;
this.modeEvent.emit(value);
this._changeDetectorRef.markForCheck();
}
_mode: 'flip' | 'side-by-side' | 'diff' = 'diff';
/** When mode is "flip" whether we show the test image or the golden image */
@Input()
get flipping() {
return this._flipping;
}
set flipping(value: boolean) {
this._flipping = value;
this.flippingEvent.emit(value);
this._changeDetectorRef.markForCheck();
}
_flipping: boolean = false;
get collapseIcon() {
return this.collapse ? 'keyboard_arrow_right' : 'keyboard_arrow_down';
}
@Input()
set filename(filename: string) {
if (this._filename != filename) {
this._filename = filename;
this.setFilenameAndFetchImages();
}
}
_filename: string;
setFilenameAndFetchImages() {
this.testName = this._filename.replace(/[_]/g, ' ');
let imageFilename = `${this._filename}.screenshot.png`;
this.service.getTestScreenshotImageUrl(imageFilename)
.then((url) => {
this.testImageUrl = url;
this._changeDetectorRef.markForCheck();
});
this.service.getDiffScreenshotImageUrl(imageFilename)
.then((url) => {
this.diffImageUrl = url;
this._changeDetectorRef.markForCheck();
});
this.service.getGoldScreenshotImageUrl(imageFilename)
.then((url) => {
this.goldImageUrl = url;
this._changeDetectorRef.markForCheck();
});
}
@Output('flippingChange') flippingEvent = new EventEmitter<boolean>();
@Output('modeChange') modeEvent = new EventEmitter<'flip' | 'side-by-side' | 'diff'>();
@Output('collapseChange') collapseEvent = new EventEmitter<boolean>();
constructor(public service: FirebaseService, private _changeDetectorRef: ChangeDetectorRef) { }
flip() {
this.service.screenshotResultSummary.isFlipped =
!this.service.screenshotResultSummary.isFlipped;
}
toggleCollapse() {
this.collapse = !this.collapse;
this.collapseEvent.emit(this.collapse);
}
}
+10
View File
@@ -0,0 +1,10 @@
import {ModuleWithProviders} from '@angular/core';
import {Routes, RouterModule} from '@angular/router';
import {ViewerComponent} from './viewer/viewer.component';
const SCREENSHOT_ROUTES: Routes = [
{path: '', component: ViewerComponent, pathMatch: 'full'},
{path: ':id', component: ViewerComponent}
];
export const routing: ModuleWithProviders = RouterModule.forRoot(SCREENSHOT_ROUTES);
@@ -0,0 +1,58 @@
const config = require('../config.json');
/**
* Results for a screenshot test
* Has all related information including Travis Job ID, SHA, pull request number.
* And the test results for each test.
*/
export class ScreenshotResultSummary {
/** PR information: the pull request number */
prNumber: string;
/** PR information: the sha of the pull request commit */
sha: string;
/** PR information: The travis job ID */
travis: string;
/** The names of the tests that have screenshot results */
testNames: string[];
/**
* Test result: passed or failed. The value can be true if test failed but
* PR approved by user
*/
allTestsPassedOrApproved: boolean;
/** Test results: passed or failed for each test */
testResultsByName: Map<string, boolean> = new Map<string, boolean>();
/**
* Test approved: whether the test images are copied to goldens.
* The value is the date/time of approval of current commit. Null if never approved.
*/
approvedTime: number;
githubStatus: boolean;
/** Viewing mode, can be flip, diff, side */
mode: 'diff' | 'side' | 'flip' = 'diff';
/** When in "Flip" mode, whether the test result or the gold screenshot is being shown. */
isFlipped: boolean = false;
/** Viewing collapsed: whether the result should be collapsed/expanded */
collapse: boolean[];
setCollapse(value: boolean) {
if (this.collapse) {
for (let i = 0; i < this.collapse.length; i++) {
this.collapse[i] = value;
}
}
}
get prUrl() {
return `https://github.com/${config.repoSlug}/pull/${this.prNumber}`;
}
get commitLink() {
return `https://github.com/${config.repoSlug}/commit/${this.sha}`;
}
get travisLink() {
return `https://travis-ci.org/${config.repoSlug}/jobs/${this.travis}`;
}
}
@@ -0,0 +1,19 @@
.mode-section {
display: flex;
flex-direction: column;
margin: 10px 40px 10px 10px;
}
.description {
font-size: 11px;
color: grey;
padding-left: 20px;
}
.failed {
color: red;
}
.passed {
color: forestgreen;
}
@@ -0,0 +1,65 @@
<div *ngIf="screenshotResultSummary.prNumber">
<md-card>
<p>
<a md-button [href]="screenshotResultSummary.prUrl" target="_blank" color="primary">
Pull Request: {{screenshotResultSummary.prNumber}}
</a>
<a md-button [href]="screenshotResultSummary.commitLink" target="_blank" color="primary">
Commit: {{screenshotResultSummary.sha}}
</a>
<a md-button [href]="screenshotResultSummary.travisLink" target="_blank" color="primary">
Travis: {{screenshotResultSummary.travis}}
</a>
</p>
<p *ngIf="!isApproved">
<button md-button (click)="approve()" color="primary" >
Copy To Goldens
</button>
<span class="description">
This will copy all failed test result images to goldens.
Please go to Travis to restart the job manually after approval.
</span>
</p>
<p *ngIf="isApproved">
<button md-button disabled>Approved.</button>
<button md-button (click)="approve()">Copy to Goldens (Again)</button>
<span class="description">
Test images should be the same as golden images. Expand all to verify.
</span>
</p>
<p>
<button md-button
[disabled]="screenshotResultSummary.allTestsPassedOrApproved" color="primary"
(click)="updateGithubStatus()">
Set Github to Success
</button>
<a md-icon-button *ngIf="githubStatus" [ngClass]="githubClass">
<md-icon>{{githubIcon}}</md-icon>
</a>
<a md-tooltip="Refresh Github Status" (click)="refreshGithubStatus()" md-icon-button>
<md-icon>refresh</md-icon>
</a>
<br>
<span class="description">
This will not update golds, but only change the screenshot test status on current commit.
Once the PR checked in, the golds will be updated automatically.
</span>
</p>
<md-card-actions>
<button md-raised-button color="primary" (click)="screenshotResultSummary.setCollapse(true)">
Collapse All
</button>
<button md-raised-button color="primary" (click)="screenshotResultSummary.setCollapse(false)">
Expand All
</button>
</md-card-actions>
</md-card>
<div *ngFor="let testname of screenshotResultSummary.testNames; let i = index">
<app-result [filename]="testname"
[result]="screenshotResultSummary.testResultsByName.get(testname)"
[(mode)]="screenshotResultSummary.mode"
[(flipping)]="screenshotResultSummary.isFlipped"
[(collapse)]="screenshotResultSummary.collapse[i]"></app-result>
</div>
</div>
@@ -0,0 +1,67 @@
import {Component, Input} from '@angular/core';
import {ActivatedRoute} from '@angular/router';
import {MdSnackBar} from '@angular/material';
import {FirebaseService} from '../firebase.service';
/**
* Component to show test results for one commit. The github status, pull request and travis job
* information, test result and commit SHA are visible.
* User can approve the change by updating the github status, or by updating the goldens.
*/
@Component({
selector: 'app-viewer',
templateUrl: './viewer.component.html',
styleUrls: ['./viewer.component.css']
})
export class ViewerComponent {
messageDuration = {duration: 10000};
get isApproved() {
return !!this._service.screenshotResultSummary.approvedTime;
}
get screenshotResultSummary() {
return this._service.screenshotResultSummary;
}
get githubIcon(): string {
return this.githubStatus ? 'check' : 'close';
}
@Input()
get githubStatus() {
return this.screenshotResultSummary.githubStatus;
}
get githubClass(): string {
return this.githubStatus ? 'passed' : 'failed';
}
constructor(private _service: FirebaseService,
private _route: ActivatedRoute,
public snackBar: MdSnackBar) {
_route.params.subscribe(p => {
this._service.prNumber = p['id'];
});
}
approve() {
this._service.approvePullRequest().then((result) => {
this.snackBar.open(`Approved`, '', this.messageDuration);
}).catch((error) => {
this.snackBar.open(`Error ${error}`, '', this.messageDuration);
});
}
updateGithubStatus() {
this._service.updatePullRequestResult().then((result) => {
this.snackBar.open(`Approved`, '', this.messageDuration);
}).catch((error) => {
this.snackBar.open(error.message, '', this.messageDuration);
});
}
refreshGithubStatus() {
this._service.getGithubStatus();
}
}
+10
View File
@@ -0,0 +1,10 @@
{
"firebase": {
"apiKey": "AIzaSyBekh5ZSi1vEhaE2qetH4RU91gHmUmpqgg",
"authDomain": "material2-screenshots.firebaseapp.com",
"databaseURL": "https://material2-screenshots.firebaseio.com",
"storageBucket": "material2-screenshots.appspot.com",
"messagingSenderId": "975527407245"
},
"repoSlug": "angular/material2"
}
@@ -0,0 +1,3 @@
export const environment = {
production: true
};
@@ -0,0 +1,8 @@
// The file contents for the current environment will overwrite these during build.
// The build system defaults to the dev environment which uses `environment.ts`, but if you do
// `ng build --env=prod` then `environment.prod.ts` will be used instead.
// The list of which env maps to which file can be found in `angular-cli.json`.
export const environment = {
production: false
};
Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

+16
View File
@@ -0,0 +1,16 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>ScreenshotTest</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
<link href="https://fonts.googleapis.com/icon?family=Material+Icons"
rel="stylesheet">
</head>
<body>
<app-root>Loading...</app-root>
</body>
</html>
+12
View File
@@ -0,0 +1,12 @@
import './polyfills.ts';
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
import {enableProdMode} from '@angular/core';
import {environment} from './environments/environment';
import {PixactoDashboardModule} from './app/pixacto.dashboard.module';
if (environment.production) {
enableProdMode();
}
platformBrowserDynamic().bootstrapModule(PixactoDashboardModule);
+13
View File
@@ -0,0 +1,13 @@
// This file includes polyfills needed by Angular and is loaded before
// the app. You can add your own extra polyfills to this file.
import 'core-js/es6/symbol';
import 'core-js/es6/object';
import 'core-js/es6/function';
import 'core-js/es6/number';
import 'core-js/es6/string';
import 'core-js/es6/array';
import 'core-js/es6/map';
import 'core-js/es6/reflect';
import 'core-js/es7/reflect';
import 'zone.js/dist/zone';
+1
View File
@@ -0,0 +1 @@
@import '~@angular/material/prebuilt-themes/indigo-pink.css';
+18
View File
@@ -0,0 +1,18 @@
{
"compilerOptions": {
"baseUrl": "",
"declaration": false,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"lib": ["es6", "dom"],
"mapRoot": "./",
"module": "es6",
"moduleResolution": "node",
"outDir": "../dist/out-tsc",
"sourceMap": true,
"target": "es5",
"typeRoots": [
"../node_modules/@types"
]
}
}