From 1540e2fa82013e192ef23064d88a4f36e182e217 Mon Sep 17 00:00:00 2001 From: Austin Date: Wed, 18 Jul 2018 10:53:47 -0500 Subject: [PATCH] feat(schematics): tree schematic (#11739) --- src/lib/schematics/collection.json | 8 +- .../__name@dasherize__.component.__styleext__ | 4 + .../__name@dasherize__.component.html | 22 ++++ .../__name@dasherize__.component.spec.ts | 24 ++++ .../__name@dasherize__.component.ts | 113 ++++++++++++++++++ .../example-data.ts | 46 +++++++ src/lib/schematics/tree/index.ts | 28 +++++ src/lib/schematics/tree/schema.json | 93 ++++++++++++++ src/lib/schematics/tree/schema.ts | 3 + 9 files changed, 340 insertions(+), 1 deletion(-) create mode 100644 src/lib/schematics/tree/files/__path__/__name@dasherize@if-flat__/__name@dasherize__.component.__styleext__ create mode 100644 src/lib/schematics/tree/files/__path__/__name@dasherize@if-flat__/__name@dasherize__.component.html create mode 100644 src/lib/schematics/tree/files/__path__/__name@dasherize@if-flat__/__name@dasherize__.component.spec.ts create mode 100644 src/lib/schematics/tree/files/__path__/__name@dasherize@if-flat__/__name@dasherize__.component.ts create mode 100644 src/lib/schematics/tree/files/__path__/__name@dasherize@if-flat__/example-data.ts create mode 100644 src/lib/schematics/tree/index.ts create mode 100644 src/lib/schematics/tree/schema.json create mode 100644 src/lib/schematics/tree/schema.ts diff --git a/src/lib/schematics/collection.json b/src/lib/schematics/collection.json index bf615794d76..879b57bf93a 100644 --- a/src/lib/schematics/collection.json +++ b/src/lib/schematics/collection.json @@ -28,7 +28,13 @@ "description": "Create a component with a responsive sidenav for navigation", "factory": "./nav/index", "schema": "./nav/schema.json", - "aliases": [ "material-nav"] + "aliases": [ "material-nav", "materialNav" ] + }, + // Create a file tree component + "tree": { + "description": "Create a file tree component.", + "factory": "./tree/index", + "schema": "./tree/schema.json" }, // Creates a address form component "addressForm": { diff --git a/src/lib/schematics/tree/files/__path__/__name@dasherize@if-flat__/__name@dasherize__.component.__styleext__ b/src/lib/schematics/tree/files/__path__/__name@dasherize@if-flat__/__name@dasherize__.component.__styleext__ new file mode 100644 index 00000000000..a19d47a2605 --- /dev/null +++ b/src/lib/schematics/tree/files/__path__/__name@dasherize@if-flat__/__name@dasherize__.component.__styleext__ @@ -0,0 +1,4 @@ +.type-icon { + color: #999; + margin-right: 5px; +} diff --git a/src/lib/schematics/tree/files/__path__/__name@dasherize@if-flat__/__name@dasherize__.component.html b/src/lib/schematics/tree/files/__path__/__name@dasherize@if-flat__/__name@dasherize__.component.html new file mode 100644 index 00000000000..babaa384e0d --- /dev/null +++ b/src/lib/schematics/tree/files/__path__/__name@dasherize@if-flat__/__name@dasherize__.component.html @@ -0,0 +1,22 @@ + + + + + {{ node.type === 'file' ? 'description' : 'folder' }} + + {{node.name}} + + + + + + {{ node.type ==='file' ? 'description' : 'folder' }} + + {{node.name}} + + diff --git a/src/lib/schematics/tree/files/__path__/__name@dasherize@if-flat__/__name@dasherize__.component.spec.ts b/src/lib/schematics/tree/files/__path__/__name@dasherize@if-flat__/__name@dasherize__.component.spec.ts new file mode 100644 index 00000000000..ad38814c940 --- /dev/null +++ b/src/lib/schematics/tree/files/__path__/__name@dasherize@if-flat__/__name@dasherize__.component.spec.ts @@ -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(); + }); +}); diff --git a/src/lib/schematics/tree/files/__path__/__name@dasherize@if-flat__/__name@dasherize__.component.ts b/src/lib/schematics/tree/files/__path__/__name@dasherize@if-flat__/__name@dasherize__.component.ts new file mode 100644 index 00000000000..9689a630b71 --- /dev/null +++ b/src/lib/schematics/tree/files/__path__/__name@dasherize@if-flat__/__name@dasherize__.component.ts @@ -0,0 +1,113 @@ +import { Component<% if (!!viewEncapsulation) { %>, ViewEncapsulation<% }%><% if (changeDetection !== 'Default') { %>, ChangeDetectionStrategy<% }%> } from '@angular/core'; +import { MatTreeFlatDataSource, MatTreeFlattener } from '@angular/material/tree'; +import { of as observableOf } from 'rxjs'; +import { FlatTreeControl } from '@angular/cdk/tree'; +import { files } from './example-data'; + +/** File node data with nested structure. */ +export interface FileNode { + name: string; + type: string; + children?: FileNode[]; +} + +/** Flat node with expandable and level information */ +export interface TreeNode { + name: string; + type: string; + level: number; + expandable: boolean; +} + +@Component({ + selector: '<%= selector %>',<% if (inlineTemplate) { %> + template: ` + + + + + {{ node.type ==='file' ? 'description' : 'folder' }} + + {{node.name}} + + + + + + {{ node.type === 'file' ? 'description' : 'folder' }} + + {{node.name}} + + + `,<% } else { %> + templateUrl: './<%= dasherize(name) %>.component.html',<% } if (inlineStyle) { %> + styles: [ + ` + .type-icon { + color: #999; + margin-right: 5px; + } + ` + ]<% } else { %> + styleUrls: ['./<%= dasherize(name) %>.component.<%= styleext %>']<% } %><% if (!!viewEncapsulation) { %>, + encapsulation: ViewEncapsulation.<%= viewEncapsulation %><% } if (changeDetection !== 'Default') { %>, + changeDetection: ChangeDetectionStrategy.<%= changeDetection %><% } %> +}) +export class <%= classify(name) %>Component { + + /** The TreeControl controls the expand/collapse state of tree nodes. */ + treeControl: FlatTreeControl; + + /** The TreeFlattener is used to generate the flat list of items from hierarchical data. */ + treeFlattener: MatTreeFlattener; + + /** The MatTreeFlatDataSource connects the control and flattener to provide data. */ + dataSource: MatTreeFlatDataSource; + + constructor() { + this.treeFlattener = new MatTreeFlattener( + this.transformer, + this.getLevel, + this.isExpandable, + this.getChildren); + + this.treeControl = new FlatTreeControl(this.getLevel, this.isExpandable); + this.dataSource = new MatTreeFlatDataSource(this.treeControl, this.treeFlattener); + this.dataSource.data = files; + } + + /** Transform the data to something the tree can read. */ + transformer(node: FileNode, level: number) { + return { + name: node.name, + type: node.type, + level: level, + expandable: !!node.children + }; + } + + /** Get the level of the node */ + getLevel(node: TreeNode) { + return node.level; + } + + /** Get whether the node is expanded or not. */ + isExpandable(node: TreeNode) { + return node.expandable; + }; + + /** Get the children for the node. */ + getChildren(node: FileNode) { + return observableOf(node.children); + } + + /** Get whether the node has children or not. */ + hasChild(index: number, node: TreeNode){ + return node.expandable; + } +} diff --git a/src/lib/schematics/tree/files/__path__/__name@dasherize@if-flat__/example-data.ts b/src/lib/schematics/tree/files/__path__/__name@dasherize@if-flat__/example-data.ts new file mode 100644 index 00000000000..d7cf7831995 --- /dev/null +++ b/src/lib/schematics/tree/files/__path__/__name@dasherize@if-flat__/example-data.ts @@ -0,0 +1,46 @@ +/** Example file/folder data. */ +export const files = [ + { + name: 'material2', + type: 'folder', + children: [ + { + name: 'src', + type: 'folder', + children: [ + { + name: 'cdk', + children: [ + { name: 'package.json', type: 'file' }, + { name: 'BUILD.bazel', type: 'file' }, + ] + }, + { name: 'lib', type: 'folder' } + ] + } + ] + }, + { + name: 'angular', + type: 'folder', + children: [ + { + name: 'packages', + type: 'folder', + children: [ + { name: '.travis.yml', type: 'file' }, + { name: 'firebase.json', type: 'file' } + ] + }, + { name: 'package.json', type: 'file' } + ] + }, + { + name: 'angularjs', + type: 'folder', + children: [ + { name: 'gulpfile.js', type: 'file' }, + { name: 'README.md', type: 'file' } + ] + } +]; diff --git a/src/lib/schematics/tree/index.ts b/src/lib/schematics/tree/index.ts new file mode 100644 index 00000000000..2d58daa2798 --- /dev/null +++ b/src/lib/schematics/tree/index.ts @@ -0,0 +1,28 @@ +import {chain, Rule, noop, Tree, SchematicContext} from '@angular-devkit/schematics'; +import {Schema} from './schema'; +import {addModuleImportToModule, findModuleFromOptions} from '../utils/ast'; +import {buildComponent} from '../utils/devkit-utils/component'; + +/** + * Scaffolds a new tree component. + * Internally it bootstraps the base component schematic + */ +export default function(options: Schema): Rule { + return chain([ + buildComponent({ ...options }), + options.skipImport ? noop() : addTreeModulesToModule(options) + ]); +} + +/** + * Adds the required modules to the relative module. + */ +function addTreeModulesToModule(options: Schema) { + return (host: Tree) => { + const modulePath = findModuleFromOptions(host, options); + addModuleImportToModule(host, modulePath, 'MatTreeModule', '@angular/material'); + addModuleImportToModule(host, modulePath, 'MatIconModule', '@angular/material'); + addModuleImportToModule(host, modulePath, 'MatButtonModule', '@angular/material'); + return host; + }; +} diff --git a/src/lib/schematics/tree/schema.json b/src/lib/schematics/tree/schema.json new file mode 100644 index 00000000000..5245bbb4d34 --- /dev/null +++ b/src/lib/schematics/tree/schema.json @@ -0,0 +1,93 @@ +{ + "$schema": "http://json-schema.org/schema", + "id": "SchematicsMaterialTree", + "title": "Material Tree Options Schema", + "type": "object", + "properties": { + "path": { + "type": "string", + "format": "path", + "description": "The path to create the component.", + "visible": false + }, + "project": { + "type": "string", + "description": "The name of the project.", + "visible": false + }, + "name": { + "type": "string", + "description": "The name of the component.", + "$default": { + "$source": "argv", + "index": 0 + } + }, + "inlineStyle": { + "description": "Specifies if the style will be in the ts file.", + "type": "boolean", + "default": false, + "alias": "s" + }, + "inlineTemplate": { + "description": "Specifies if the template will be in the ts file.", + "type": "boolean", + "default": false, + "alias": "t" + }, + "viewEncapsulation": { + "description": "Specifies the view encapsulation strategy.", + "enum": ["Emulated", "Native", "None"], + "type": "string", + "alias": "v" + }, + "changeDetection": { + "description": "Specifies the change detection strategy.", + "enum": ["Default", "OnPush"], + "type": "string", + "default": "Default", + "alias": "c" + }, + "prefix": { + "type": "string", + "format": "html-selector", + "description": "The prefix to apply to generated selectors.", + "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.", + "default": false + }, + "skipImport": { + "type": "boolean", + "description": "Flag to skip the module import.", + "default": false + }, + "selector": { + "type": "string", + "format": "html-selector", + "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." + } + } +} diff --git a/src/lib/schematics/tree/schema.ts b/src/lib/schematics/tree/schema.ts new file mode 100644 index 00000000000..49a799dd153 --- /dev/null +++ b/src/lib/schematics/tree/schema.ts @@ -0,0 +1,3 @@ +import {Schema as ComponentSchema} from '@schematics/angular/component/schema'; + +export interface Schema extends ComponentSchema {}