Skip to content

Commit 6273d6a

Browse files
amcdnljelbourn
authored andcommitted
feat(schematics): dashboard schematic (#10011)
1 parent 0a619e9 commit 6273d6a

10 files changed

+336
-2
lines changed

schematics/collection.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,13 @@
22
{
33
"$schema": "./node_modules/@angular-devkit/schematics/collection-schema.json",
44
"schematics": {
5+
// Create a dashboard component
6+
"materialDashboard": {
7+
"description": "Create a dashboard component",
8+
"factory": "./dashboard/index",
9+
"schema": "./dashboard/schema.json",
10+
"aliases": [ "material-dashboard" ]
11+
},
512
// Creates a table component
613
"materialTable": {
714
"description": "Create a table component",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
.grid-container {
2+
margin: 20px;
3+
}
4+
5+
.dashboard-card {
6+
position: absolute;
7+
top: 15px;
8+
left: 15px;
9+
right: 15px;
10+
bottom: 15px;
11+
}
12+
13+
.more-button {
14+
position: absolute;
15+
top: 5px;
16+
right: 10px;
17+
}
18+
19+
.dashboard-card-content {
20+
text-align: center;
21+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<div class="grid-container">
2+
<h1 class="mat-h1">Dashboard</h1>
3+
<mat-grid-list cols="2" rowHeight="350px">
4+
<mat-grid-tile *ngFor="let card of cards" [colspan]="card.cols" [rowspan]="card.rows">
5+
<mat-card class="dashboard-card">
6+
<mat-card-header>
7+
<mat-card-title>
8+
{{card.title}}
9+
<button mat-icon-button class="more-button" [matMenuTriggerFor]="menu" aria-label="Toggle menu">
10+
<mat-icon>more_vert</mat-icon>
11+
</button>
12+
<mat-menu #menu="matMenu" xPosition="before">
13+
<button mat-menu-item>Expand</button>
14+
<button mat-menu-item>Remove</button>
15+
</mat-menu>
16+
</mat-card-title>
17+
</mat-card-header>
18+
<mat-card-content class="dashboard-card-content">
19+
<div>Card Content Here</div>
20+
</mat-card-content>
21+
</mat-card>
22+
</mat-grid-tile>
23+
</mat-grid-list>
24+
</div>
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
2+
import { fakeAsync, ComponentFixture, TestBed } from '@angular/core/testing';
3+
4+
import { <%= classify(name) %>Component } from './<%= dasherize(name) %>.component';
5+
6+
describe('<%= classify(name) %>Component', () => {
7+
let component: <%= classify(name) %>Component;
8+
let fixture: ComponentFixture<<%= classify(name) %>Component>;
9+
10+
beforeEach(fakeAsync(() => {
11+
TestBed.configureTestingModule({
12+
declarations: [ <%= classify(name) %>Component ]
13+
})
14+
.compileComponents();
15+
16+
fixture = TestBed.createComponent(<%= classify(name) %>Component);
17+
component = fixture.componentInstance;
18+
fixture.detectChanges();
19+
}));
20+
21+
it('should compile', () => {
22+
expect(component).toBeTruthy();
23+
});
24+
});
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import { Component, <% if(!!viewEncapsulation) { %>, ViewEncapsulation<% }%><% if(changeDetection !== 'Default') { %>, ChangeDetectionStrategy<% }%> } from '@angular/core';
2+
3+
@Component({
4+
selector: '<%= selector %>',<% if(inlineTemplate) { %>
5+
template: `
6+
<div class="grid-container">
7+
<h1 class="mat-h1">Dashboard</h1>
8+
<mat-grid-list cols="2" rowHeight="350px">
9+
<mat-grid-tile *ngFor="let card of cards" [colspan]="card.cols" [rowspan]="card.rows">
10+
<mat-card class="dashboard-card">
11+
<mat-card-header>
12+
<mat-card-title>
13+
{{card.title}}
14+
<button mat-icon-button class="more-button" [matMenuTriggerFor]="menu" aria-label="Toggle menu">
15+
<mat-icon>more_vert</mat-icon>
16+
</button>
17+
<mat-menu #menu="matMenu" xPosition="before">
18+
<button mat-menu-item>Expand</button>
19+
<button mat-menu-item>Remove</button>
20+
</mat-menu>
21+
</mat-card-title>
22+
</mat-card-header>
23+
<mat-card-content class="dashboard-card-content">
24+
<div>Card Content Here</div>
25+
</mat-card-content>
26+
</mat-card>
27+
</mat-grid-tile>
28+
</mat-grid-list>
29+
</div>
30+
`,<% } else { %>
31+
templateUrl: './<%= dasherize(name) %>.component.html',<% } if(inlineStyle) { %>
32+
styles: [
33+
`
34+
.grid-container {
35+
margin: 20px;
36+
}
37+
38+
.dashboard-card {
39+
position: absolute;
40+
top: 15px;
41+
left: 15px;
42+
right: 15px;
43+
bottom: 15px;
44+
}
45+
46+
.more-button {
47+
position: absolute;
48+
top: 5px;
49+
right: 10px;
50+
}
51+
52+
.dashboard-card-content {
53+
text-align: center;
54+
}
55+
`
56+
]<% } else { %>
57+
styleUrls: ['./<%= dasherize(name) %>.component.<%= styleext %>']<% } %><% if(!!viewEncapsulation) { %>,
58+
encapsulation: ViewEncapsulation.<%= viewEncapsulation %><% } if (changeDetection !== 'Default') { %>,
59+
changeDetection: ChangeDetectionStrategy.<%= changeDetection %><% } %>
60+
})
61+
export class <%= classify(name) %>Component {
62+
cards = [
63+
{ title: 'Card 1', cols: 2, rows: 1 },
64+
{ title: 'Card 2', cols: 1, rows: 1 },
65+
{ title: 'Card 3', cols: 1, rows: 2 },
66+
{ title: 'Card 4', cols: 1, rows: 1 }
67+
];
68+
}

schematics/dashboard/index.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import {chain, Rule, noop, Tree, SchematicContext} from '@angular-devkit/schematics';
2+
import {Schema} from './schema';
3+
import {addModuleImportToModule} from '../utils/ast';
4+
import {findModuleFromOptions} from '../utils/devkit-utils/find-module';
5+
import {buildComponent} from '../utils/devkit-utils/component';
6+
7+
/**
8+
* Scaffolds a new navigation component.
9+
* Internally it bootstraps the base component schematic
10+
*/
11+
export default function(options: Schema): Rule {
12+
return chain([
13+
buildComponent({ ...options }),
14+
options.skipImport ? noop() : addNavModulesToModule(options)
15+
]);
16+
}
17+
18+
/**
19+
* Adds the required modules to the relative module.
20+
*/
21+
function addNavModulesToModule(options: Schema) {
22+
return (host: Tree) => {
23+
const modulePath = findModuleFromOptions(host, options);
24+
addModuleImportToModule(host, modulePath, 'MatGridListModule', '@angular/material');
25+
addModuleImportToModule(host, modulePath, 'MatCardModule', '@angular/material');
26+
addModuleImportToModule(host, modulePath, 'MatMenuModule', '@angular/material');
27+
addModuleImportToModule(host, modulePath, 'MatIconModule', '@angular/material');
28+
addModuleImportToModule(host, modulePath, 'MatButtonModule', '@angular/material');
29+
return host;
30+
};
31+
}

schematics/dashboard/index_spec.ts

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import {SchematicTestRunner} from '@angular-devkit/schematics/testing';
2+
import {join} from 'path';
3+
import {Tree} from '@angular-devkit/schematics';
4+
import {createTestApp} from '../utils/testing';
5+
import {getFileContent} from '@schematics/angular/utility/test';
6+
7+
const collectionPath = join(__dirname, '../collection.json');
8+
9+
describe('material-dashboard-schematic', () => {
10+
let runner: SchematicTestRunner;
11+
const options = {
12+
name: 'foo',
13+
path: 'app',
14+
sourceDir: 'src',
15+
inlineStyle: false,
16+
inlineTemplate: false,
17+
changeDetection: 'Default',
18+
styleext: 'css',
19+
spec: true,
20+
module: undefined,
21+
export: false,
22+
prefix: undefined,
23+
viewEncapsulation: undefined,
24+
};
25+
26+
beforeEach(() => {
27+
runner = new SchematicTestRunner('schematics', collectionPath);
28+
});
29+
30+
it('should create dashboard files and add them to module', () => {
31+
const tree = runner.runSchematic('materialDashboard', { ...options }, createTestApp());
32+
const files = tree.files;
33+
34+
expect(files).toContain('/src/app/foo/foo.component.css');
35+
expect(files).toContain('/src/app/foo/foo.component.html');
36+
expect(files).toContain('/src/app/foo/foo.component.spec.ts');
37+
expect(files).toContain('/src/app/foo/foo.component.ts');
38+
39+
const moduleContent = getFileContent(tree, '/src/app/app.module.ts');
40+
expect(moduleContent).toMatch(/import.*Foo.*from '.\/foo\/foo.component'/);
41+
expect(moduleContent).toMatch(/declarations:\s*\[[^\]]+?,\r?\n\s+FooComponent\r?\n/m);
42+
});
43+
44+
it('should add dashboard imports to module', () => {
45+
const tree = runner.runSchematic('materialDashboard', { ...options }, createTestApp());
46+
const moduleContent = getFileContent(tree, '/src/app/app.module.ts');
47+
48+
expect(moduleContent).toContain('MatGridListModule');
49+
expect(moduleContent).toContain('MatCardModule');
50+
expect(moduleContent).toContain('MatMenuModule');
51+
expect(moduleContent).toContain('MatIconModule');
52+
expect(moduleContent).toContain('MatButtonModule');
53+
54+
expect(moduleContent).toContain(
55+
// tslint:disable-next-line
56+
`import { MatGridListModule, MatCardModule, MatMenuModule, MatIconModule, MatButtonModule } from '@angular/material';`);
57+
});
58+
59+
});

schematics/dashboard/schema.d.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import {Schema as ComponentSchema} from '@schematics/angular/component/schema';
2+
3+
export interface Schema extends ComponentSchema {}

schematics/dashboard/schema.json

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
{
2+
"$schema": "http://json-schema.org/schema",
3+
"id": "SchematicsMaterialDashboard",
4+
"title": "Material Dashboard Options Schema",
5+
"type": "object",
6+
"properties": {
7+
"path": {
8+
"type": "string",
9+
"description": "The path to create the component.",
10+
"default": "app",
11+
"visible": false
12+
},
13+
"sourceDir": {
14+
"type": "string",
15+
"description": "The path of the source directory.",
16+
"default": "src",
17+
"alias": "sd",
18+
"visible": false
19+
},
20+
"appRoot": {
21+
"type": "string",
22+
"description": "The root of the application.",
23+
"visible": false
24+
},
25+
"name": {
26+
"type": "string",
27+
"description": "The name of the component."
28+
},
29+
"inlineStyle": {
30+
"description": "Specifies if the style will be in the ts file.",
31+
"type": "boolean",
32+
"default": false,
33+
"alias": "is"
34+
},
35+
"inlineTemplate": {
36+
"description": "Specifies if the template will be in the ts file.",
37+
"type": "boolean",
38+
"default": false,
39+
"alias": "it"
40+
},
41+
"viewEncapsulation": {
42+
"description": "Specifies the view encapsulation strategy.",
43+
"enum": ["Emulated", "Native", "None"],
44+
"type": "string",
45+
"default": "Emulated",
46+
"alias": "ve"
47+
},
48+
"changeDetection": {
49+
"description": "Specifies the change detection strategy.",
50+
"enum": ["Default", "OnPush"],
51+
"type": "string",
52+
"default": "Default",
53+
"alias": "cd"
54+
},
55+
"prefix": {
56+
"type": "string",
57+
"description": "The prefix to apply to generated selectors.",
58+
"default": "app",
59+
"alias": "p"
60+
},
61+
"styleext": {
62+
"description": "The file extension to be used for style files.",
63+
"type": "string",
64+
"default": "css"
65+
},
66+
"spec": {
67+
"type": "boolean",
68+
"description": "Specifies if a spec file is generated.",
69+
"default": true
70+
},
71+
"flat": {
72+
"type": "boolean",
73+
"description": "Flag to indicate if a dir is created.",
74+
"default": false
75+
},
76+
"skipImport": {
77+
"type": "boolean",
78+
"description": "Flag to skip the module import.",
79+
"default": false
80+
},
81+
"selector": {
82+
"type": "string",
83+
"description": "The selector to use for the component."
84+
},
85+
"module": {
86+
"type": "string",
87+
"description": "Allows specification of the declaring module.",
88+
"alias": "m"
89+
},
90+
"export": {
91+
"type": "boolean",
92+
"default": false,
93+
"description": "Specifies if declaring module exports the component."
94+
}
95+
},
96+
"required": [
97+
"name"
98+
]
99+
}

schematics/tsconfig.json

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,12 @@
11
{
22
"compilerOptions": {
3-
"baseUrl": "tsconfig",
43
"lib": [
54
"es2017",
65
"dom"
76
],
87
"module": "commonjs",
98
"moduleResolution": "node",
109
"noEmitOnError": false,
11-
"rootDir": "src/",
1210
"skipDefaultLibCheck": true,
1311
"skipLibCheck": true,
1412
"sourceMap": true,

0 commit comments

Comments
 (0)