Skip to content

feat(schematics): dashboard schematic #10011

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Mar 10, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions schematics/collection.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@
{
"$schema": "./node_modules/@angular-devkit/schematics/collection-schema.json",
"schematics": {
// Create a dashboard component
"materialDashboard": {
"description": "Create a dashboard component",
"factory": "./dashboard/index",
"schema": "./dashboard/schema.json",
"aliases": [ "material-dashboard" ]
},
// Creates a table component
"materialTable": {
"description": "Create a table component",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
.grid-container {
margin: 20px;
}

.dashboard-card {
position: absolute;
top: 15px;
left: 15px;
right: 15px;
bottom: 15px;
}

.more-button {
position: absolute;
top: 5px;
right: 10px;
}

.dashboard-card-content {
text-align: center;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<div class="grid-container">
<h1 class="mat-h1">Dashboard</h1>
<mat-grid-list cols="2" rowHeight="350px">
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Grid-list is only useful when you want items with heterogenous rowspans. We could either mix that in, or just use cards laid out with flex styles.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll update it to have spans :)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are the rowspans meaningful here? A screenshot would be helpful

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

<mat-grid-tile *ngFor="let card of cards" [colspan]="card.cols" [rowspan]="card.rows">
<mat-card class="dashboard-card">
<mat-card-header>
<mat-card-title>
{{card.title}}
<button mat-icon-button class="more-button" [matMenuTriggerFor]="menu" aria-label="Toggle menu">
<mat-icon>more_vert</mat-icon>
</button>
<mat-menu #menu="matMenu" xPosition="before">
<button mat-menu-item>Expand</button>
<button mat-menu-item>Remove</button>
</mat-menu>
</mat-card-title>
</mat-card-header>
<mat-card-content class="dashboard-card-content">
<div>Card Content Here</div>
</mat-card-content>
</mat-card>
</mat-grid-tile>
</mat-grid-list>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@

import { fakeAsync, ComponentFixture, TestBed } from '@angular/core/testing';

import { <%= classify(name) %>Component } from './<%= dasherize(name) %>.component';

describe('<%= classify(name) %>Component', () => {
let component: <%= classify(name) %>Component;
let fixture: ComponentFixture<<%= classify(name) %>Component>;

beforeEach(fakeAsync(() => {
TestBed.configureTestingModule({
declarations: [ <%= classify(name) %>Component ]
})
.compileComponents();

fixture = TestBed.createComponent(<%= classify(name) %>Component);
component = fixture.componentInstance;
fixture.detectChanges();
}));

it('should compile', () => {
expect(component).toBeTruthy();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { Component, <% if(!!viewEncapsulation) { %>, ViewEncapsulation<% }%><% if(changeDetection !== 'Default') { %>, ChangeDetectionStrategy<% }%> } from '@angular/core';

@Component({
selector: '<%= selector %>',<% if(inlineTemplate) { %>
template: `
<div class="grid-container">
<h1 class="mat-h1">Dashboard</h1>
<mat-grid-list cols="2" rowHeight="350px">
<mat-grid-tile *ngFor="let card of cards" [colspan]="card.cols" [rowspan]="card.rows">
<mat-card class="dashboard-card">
<mat-card-header>
<mat-card-title>
{{card.title}}
<button mat-icon-button class="more-button" [matMenuTriggerFor]="menu" aria-label="Toggle menu">
<mat-icon>more_vert</mat-icon>
</button>
<mat-menu #menu="matMenu" xPosition="before">
<button mat-menu-item>Expand</button>
<button mat-menu-item>Remove</button>
</mat-menu>
</mat-card-title>
</mat-card-header>
<mat-card-content class="dashboard-card-content">
<div>Card Content Here</div>
</mat-card-content>
</mat-card>
</mat-grid-tile>
</mat-grid-list>
</div>
`,<% } else { %>
templateUrl: './<%= dasherize(name) %>.component.html',<% } if(inlineStyle) { %>
styles: [
`
.grid-container {
margin: 20px;
}

.dashboard-card {
position: absolute;
top: 15px;
left: 15px;
right: 15px;
bottom: 15px;
}

.more-button {
position: absolute;
top: 5px;
right: 10px;
}

.dashboard-card-content {
text-align: center;
}
`
]<% } else { %>
styleUrls: ['./<%= dasherize(name) %>.component.<%= styleext %>']<% } %><% if(!!viewEncapsulation) { %>,
encapsulation: ViewEncapsulation.<%= viewEncapsulation %><% } if (changeDetection !== 'Default') { %>,
changeDetection: ChangeDetectionStrategy.<%= changeDetection %><% } %>
})
export class <%= classify(name) %>Component {
cards = [
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Condense this?

  cards = [
    {title: 'Card 1', cols: 2, rows: 1},
    {title: 'Card 2', cols: 1, rows: 1},
    {title: 'Card 3', cols: 1, rows: 2},
    {title: 'Card 4', cols: 1, rows: 1}
  ];

{ title: 'Card 1', cols: 2, rows: 1 },
{ title: 'Card 2', cols: 1, rows: 1 },
{ title: 'Card 3', cols: 1, rows: 2 },
{ title: 'Card 4', cols: 1, rows: 1 }
];
}
31 changes: 31 additions & 0 deletions schematics/dashboard/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import {chain, Rule, noop, Tree, SchematicContext} from '@angular-devkit/schematics';
import {Schema} from './schema';
import {addModuleImportToModule} from '../utils/ast';
import {findModuleFromOptions} from '../utils/devkit-utils/find-module';
import {buildComponent} from '../utils/devkit-utils/component';

/**
* Scaffolds a new navigation component.
* Internally it bootstraps the base component schematic
*/
export default function(options: Schema): Rule {
return chain([
buildComponent({ ...options }),
options.skipImport ? noop() : addNavModulesToModule(options)
]);
}

/**
* Adds the required modules to the relative module.
*/
function addNavModulesToModule(options: Schema) {
return (host: Tree) => {
const modulePath = findModuleFromOptions(host, options);
addModuleImportToModule(host, modulePath, 'MatGridListModule', '@angular/material');
addModuleImportToModule(host, modulePath, 'MatCardModule', '@angular/material');
addModuleImportToModule(host, modulePath, 'MatMenuModule', '@angular/material');
addModuleImportToModule(host, modulePath, 'MatIconModule', '@angular/material');
addModuleImportToModule(host, modulePath, 'MatButtonModule', '@angular/material');
return host;
};
}
59 changes: 59 additions & 0 deletions schematics/dashboard/index_spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import {SchematicTestRunner} from '@angular-devkit/schematics/testing';
import {join} from 'path';
import {Tree} from '@angular-devkit/schematics';
import {createTestApp} from '../utils/testing';
import {getFileContent} from '@schematics/angular/utility/test';

const collectionPath = join(__dirname, '../collection.json');

describe('material-dashboard-schematic', () => {
let runner: SchematicTestRunner;
const options = {
name: 'foo',
path: 'app',
sourceDir: 'src',
inlineStyle: false,
inlineTemplate: false,
changeDetection: 'Default',
styleext: 'css',
spec: true,
module: undefined,
export: false,
prefix: undefined,
viewEncapsulation: undefined,
};

beforeEach(() => {
runner = new SchematicTestRunner('schematics', collectionPath);
});

it('should create dashboard files and add them to module', () => {
const tree = runner.runSchematic('materialDashboard', { ...options }, createTestApp());
const files = tree.files;

expect(files).toContain('/src/app/foo/foo.component.css');
expect(files).toContain('/src/app/foo/foo.component.html');
expect(files).toContain('/src/app/foo/foo.component.spec.ts');
expect(files).toContain('/src/app/foo/foo.component.ts');

const moduleContent = getFileContent(tree, '/src/app/app.module.ts');
expect(moduleContent).toMatch(/import.*Foo.*from '.\/foo\/foo.component'/);
expect(moduleContent).toMatch(/declarations:\s*\[[^\]]+?,\r?\n\s+FooComponent\r?\n/m);
});

it('should add dashboard imports to module', () => {
const tree = runner.runSchematic('materialDashboard', { ...options }, createTestApp());
const moduleContent = getFileContent(tree, '/src/app/app.module.ts');

expect(moduleContent).toContain('MatGridListModule');
expect(moduleContent).toContain('MatCardModule');
expect(moduleContent).toContain('MatMenuModule');
expect(moduleContent).toContain('MatIconModule');
expect(moduleContent).toContain('MatButtonModule');

expect(moduleContent).toContain(
// tslint:disable-next-line
`import { MatGridListModule, MatCardModule, MatMenuModule, MatIconModule, MatButtonModule } from '@angular/material';`);
});

});
3 changes: 3 additions & 0 deletions schematics/dashboard/schema.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import {Schema as ComponentSchema} from '@schematics/angular/component/schema';

export interface Schema extends ComponentSchema {}
99 changes: 99 additions & 0 deletions schematics/dashboard/schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
{
"$schema": "http://json-schema.org/schema",
"id": "SchematicsMaterialDashboard",
"title": "Material Dashboard Options Schema",
"type": "object",
"properties": {
"path": {
"type": "string",
"description": "The path to create the component.",
"default": "app",
"visible": false
},
"sourceDir": {
"type": "string",
"description": "The path of the source directory.",
"default": "src",
"alias": "sd",
"visible": false
},
"appRoot": {
"type": "string",
"description": "The root of the application.",
"visible": false
},
"name": {
"type": "string",
"description": "The name of the component."
},
"inlineStyle": {
"description": "Specifies if the style will be in the ts file.",
"type": "boolean",
"default": false,
"alias": "is"
},
"inlineTemplate": {
"description": "Specifies if the template will be in the ts file.",
"type": "boolean",
"default": false,
"alias": "it"
},
"viewEncapsulation": {
"description": "Specifies the view encapsulation strategy.",
"enum": ["Emulated", "Native", "None"],
"type": "string",
"default": "Emulated",
"alias": "ve"
},
"changeDetection": {
"description": "Specifies the change detection strategy.",
"enum": ["Default", "OnPush"],
"type": "string",
"default": "Default",
"alias": "cd"
},
"prefix": {
"type": "string",
"description": "The prefix to apply to generated selectors.",
"default": "app",
"alias": "p"
},
"styleext": {
"description": "The file extension to be used for style files.",
"type": "string",
"default": "css"
},
"spec": {
"type": "boolean",
"description": "Specifies if a spec file is generated.",
"default": true
},
"flat": {
"type": "boolean",
"description": "Flag to indicate if a dir is created.",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unclear what dir is referring to.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code was copied from the base component schematic.

"default": false
},
"skipImport": {
"type": "boolean",
"description": "Flag to skip the module import.",
"default": false
},
"selector": {
"type": "string",
"description": "The selector to use for the component."
},
"module": {
"type": "string",
"description": "Allows specification of the declaring module.",
"alias": "m"
},
"export": {
"type": "boolean",
"default": false,
"description": "Specifies if declaring module exports the component."
}
},
"required": [
"name"
]
}
2 changes: 0 additions & 2 deletions schematics/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
{
"compilerOptions": {
"baseUrl": "tsconfig",
"lib": [
"es2017",
"dom"
],
"module": "commonjs",
"moduleResolution": "node",
"noEmitOnError": false,
"rootDir": "src/",
"skipDefaultLibCheck": true,
"skipLibCheck": true,
"sourceMap": true,
Expand Down