From 72ccd8b83139507c3f21ba987ce94dbb8fa3c846 Mon Sep 17 00:00:00 2001 From: Paul Gschwendtner Date: Thu, 4 Oct 2018 20:53:40 +0200 Subject: [PATCH] feat(schematics): create drag-drop schematic (#13368) * Creates a CDK schematic that can be used to generate a component that uses the CDK Drag & Drop module. The code is based on the `sorting` example of the drag-drop (just a bit trimmed to keep the code simple) --- src/cdk/schematics/BUILD.bazel | 6 +- src/cdk/schematics/collection.json | 6 + .../__name@dasherize__.component.__styleext__ | 53 +++++++++ .../__name@dasherize__.component.html | 15 +++ .../__name@dasherize__.component.spec.ts | 29 +++++ .../__name@dasherize__.component.ts | 43 +++++++ .../ng-generate/drag-drop/index.spec.ts | 105 ++++++++++++++++++ .../schematics/ng-generate/drag-drop/index.ts | 32 ++++++ .../ng-generate/drag-drop/schema.json | 97 ++++++++++++++++ .../ng-generate/drag-drop/schema.ts | 11 ++ src/cdk/schematics/tsconfig.json | 1 + tslint.json | 1 + 12 files changed, 396 insertions(+), 3 deletions(-) create mode 100644 src/cdk/schematics/ng-generate/drag-drop/files/__path__/__name@dasherize@if-flat__/__name@dasherize__.component.__styleext__ create mode 100644 src/cdk/schematics/ng-generate/drag-drop/files/__path__/__name@dasherize@if-flat__/__name@dasherize__.component.html create mode 100644 src/cdk/schematics/ng-generate/drag-drop/files/__path__/__name@dasherize@if-flat__/__name@dasherize__.component.spec.ts create mode 100644 src/cdk/schematics/ng-generate/drag-drop/files/__path__/__name@dasherize@if-flat__/__name@dasherize__.component.ts create mode 100644 src/cdk/schematics/ng-generate/drag-drop/index.spec.ts create mode 100644 src/cdk/schematics/ng-generate/drag-drop/index.ts create mode 100644 src/cdk/schematics/ng-generate/drag-drop/schema.json create mode 100644 src/cdk/schematics/ng-generate/drag-drop/schema.ts diff --git a/src/cdk/schematics/BUILD.bazel b/src/cdk/schematics/BUILD.bazel index 87d9dead403..8f29378ff5a 100644 --- a/src/cdk/schematics/BUILD.bazel +++ b/src/cdk/schematics/BUILD.bazel @@ -9,13 +9,13 @@ exports_files(["#bazel_workaround.txt"]) filegroup( name = "schematics_assets", - srcs = glob(["**/*.json"]) + ["README.md"], + srcs = glob(["**/files/**/*", "**/*.json"]) + ["README.md"], ) ts_library( name = "schematics", module_name = "@angular/cdk/schematics", - srcs = glob(["**/*.ts"], exclude=["**/*.spec.ts"]), + srcs = glob(["**/*.ts"], exclude=["**/files/**/*.ts", "**/*.spec.ts"]), tsconfig = ":tsconfig.json", deps = [ "@npm//:@schematics/angular", @@ -51,7 +51,7 @@ jasmine_node_test( ts_library( name = "schematics_test_sources", - srcs = glob(["**/*.spec.ts"]), + srcs = glob(["**/*.spec.ts"], exclude = ["**/files/**/*.spec.ts"]), deps = [ ":schematics", "@npm//:@schematics/angular", diff --git a/src/cdk/schematics/collection.json b/src/cdk/schematics/collection.json index c7ddf889ad0..6786d0c0401 100644 --- a/src/cdk/schematics/collection.json +++ b/src/cdk/schematics/collection.json @@ -6,6 +6,12 @@ "factory": "./ng-add/index", "schema": "./ng-add/schema.json", "aliases": ["install"] + }, + "drag-drop": { + "description": "Generates a component using the Drag and Drop module", + "factory": "./ng-generate/drag-drop/index", + "schema": "./ng-generate/drag-drop/schema.json", + "aliases": ["dragdrop", "drag-and-drop"] } } } diff --git a/src/cdk/schematics/ng-generate/drag-drop/files/__path__/__name@dasherize@if-flat__/__name@dasherize__.component.__styleext__ b/src/cdk/schematics/ng-generate/drag-drop/files/__path__/__name@dasherize@if-flat__/__name@dasherize__.component.__styleext__ new file mode 100644 index 00000000000..23d2d43c874 --- /dev/null +++ b/src/cdk/schematics/ng-generate/drag-drop/files/__path__/__name@dasherize@if-flat__/__name@dasherize__.component.__styleext__ @@ -0,0 +1,53 @@ +.container { + width: 400px; + max-width: 100%; + margin: 0 25px 25px 0; + display: inline-block; + vertical-align: top; +} + +.list { + border: solid 1px #ccc; + min-height: 60px; + background: white; + border-radius: 4px; + display: block; + overflow: hidden; +} + +.list-item { + padding: 20px 10px; + border-bottom: solid 1px #ccc; + box-sizing: border-box; + cursor: move; + background: white; + color: black; + font-size: 14px; +} + +.list-item:last-child { + border: none; +} + +/* Highlight the list item that is being dragged. */ +.cdk-drag-preview { + border-radius: 4px; + box-shadow: 0 5px 5px -3px rgba(0, 0, 0, 0.2), + 0 8px 10px 1px rgba(0, 0, 0, 0.14), + 0 3px 14px 2px rgba(0, 0, 0, 0.12); +} + +/* Animate items as they're being sorted. */ +.cdk-drop-dragging .cdk-drag { + transition: transform 250ms cubic-bezier(0, 0, 0.2, 1); +} + +/* Animate an item that has been dropped. */ +.cdk-drag-animating { + transition: transform 300ms cubic-bezier(0, 0, 0.2, 1); +} + +.cdk-drag-placeholder { + opacity: 0; +} + diff --git a/src/cdk/schematics/ng-generate/drag-drop/files/__path__/__name@dasherize@if-flat__/__name@dasherize__.component.html b/src/cdk/schematics/ng-generate/drag-drop/files/__path__/__name@dasherize@if-flat__/__name@dasherize__.component.html new file mode 100644 index 00000000000..b79b0c0d7bf --- /dev/null +++ b/src/cdk/schematics/ng-generate/drag-drop/files/__path__/__name@dasherize@if-flat__/__name@dasherize__.component.html @@ -0,0 +1,15 @@ +
+

To do

+ + +
{{item}}
+
+
+ +
+

Done

+ + +
{{item}}
+
+
diff --git a/src/cdk/schematics/ng-generate/drag-drop/files/__path__/__name@dasherize@if-flat__/__name@dasherize__.component.spec.ts b/src/cdk/schematics/ng-generate/drag-drop/files/__path__/__name@dasherize@if-flat__/__name@dasherize__.component.spec.ts new file mode 100644 index 00000000000..c40fe2653c3 --- /dev/null +++ b/src/cdk/schematics/ng-generate/drag-drop/files/__path__/__name@dasherize@if-flat__/__name@dasherize__.component.spec.ts @@ -0,0 +1,29 @@ +import { DragDropModule } from '@angular/cdk/drag-drop'; +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { NoopAnimationsModule } from '@angular/platform-browser/animations'; +import { <%= classify(name) %>Component } from './<%= dasherize(name) %>.component'; + +describe('<%= classify(name) %>Component', () => { + let component: <%= classify(name) %>Component; + let fixture: ComponentFixture<<%= classify(name) %>Component>; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ <%= classify(name) %>Component ], + imports: [ + NoopAnimationsModule, + DragDropModule, + ] + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(<%= classify(name) %>Component); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should compile', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/cdk/schematics/ng-generate/drag-drop/files/__path__/__name@dasherize@if-flat__/__name@dasherize__.component.ts b/src/cdk/schematics/ng-generate/drag-drop/files/__path__/__name@dasherize@if-flat__/__name@dasherize__.component.ts new file mode 100644 index 00000000000..990aaceba8a --- /dev/null +++ b/src/cdk/schematics/ng-generate/drag-drop/files/__path__/__name@dasherize@if-flat__/__name@dasherize__.component.ts @@ -0,0 +1,43 @@ +import { Component<% if(!!viewEncapsulation) { %>, ViewEncapsulation<% }%><% if(changeDetection !== 'Default') { %>, ChangeDetectionStrategy<% }%> } from '@angular/core'; +import { CdkDragDrop, moveItemInArray, transferArrayItem } from '@angular/cdk/drag-drop'; + +@Component({ + selector: '<%= selector %>',<% if(inlineTemplate) { %> + template: ` + <%= indentTextContent(resolvedFiles.template, 4) %> + `,<% } else { %> + templateUrl: './<%= dasherize(name) %>.component.html',<% } if(inlineStyle) { %> + styles: [` + <%= indentTextContent(resolvedFiles.stylesheet, 4) %> + `],<% } else { %> + styleUrls: ['./<%= dasherize(name) %>.component.<%= styleext %>'],<% } %><% if(!!viewEncapsulation) { %> + encapsulation: ViewEncapsulation.<%= viewEncapsulation %><% } if (changeDetection !== 'Default') { %>, + changeDetection: ChangeDetectionStrategy.<%= changeDetection %><% } %> +}) +export class <%= classify(name) %>Component { + todo = [ + 'Get to work', + 'Pick up groceries', + 'Go home', + 'Fall asleep' + ]; + + done = [ + 'Get up', + 'Brush teeth', + 'Take a shower', + 'Check e-mail', + 'Walk dog' + ]; + + drop(event: CdkDragDrop) { + if (event.previousContainer === event.container) { + moveItemInArray(event.container.data, event.previousIndex, event.currentIndex); + } else { + transferArrayItem(event.previousContainer.data, + event.container.data, + event.previousIndex, + event.currentIndex); + } + } +} diff --git a/src/cdk/schematics/ng-generate/drag-drop/index.spec.ts b/src/cdk/schematics/ng-generate/drag-drop/index.spec.ts new file mode 100644 index 00000000000..814a5323c7c --- /dev/null +++ b/src/cdk/schematics/ng-generate/drag-drop/index.spec.ts @@ -0,0 +1,105 @@ +import {SchematicTestRunner} from '@angular-devkit/schematics/testing'; +import {createTestApp} from '../../testing'; +import {getFileContent} from '@schematics/angular/utility/test'; +import {Schema} from './schema'; + +describe('CDK drag-drop schematic', () => { + let runner: SchematicTestRunner; + + const baseOptions: Schema = { + name: 'foo', + // TODO(devversion): rename project to something that is not tied to Material. This involves + // updating the other tests as well because `createTestApp` is responsible for creating + // the project. + project: 'material', + }; + + beforeEach(() => { + runner = new SchematicTestRunner('schematics', require.resolve('../../collection.json')); + }); + + it('should create drag-drop files and add them to module', () => { + const tree = runner.runSchematic('drag-drop', baseOptions, createTestApp(runner)); + const moduleContent = getFileContent(tree, '/projects/material/src/app/app.module.ts'); + const files = tree.files; + + expect(files).toContain('/projects/material/src/app/foo/foo.component.css'); + expect(files).toContain('/projects/material/src/app/foo/foo.component.html'); + expect(files).toContain('/projects/material/src/app/foo/foo.component.spec.ts'); + expect(files).toContain('/projects/material/src/app/foo/foo.component.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 drag-drop module', () => { + const tree = runner.runSchematic('drag-drop', baseOptions, createTestApp(runner)); + const moduleContent = getFileContent(tree, '/projects/material/src/app/app.module.ts'); + + expect(moduleContent).toContain('DragDropModule'); + }); + + describe('styleext option', () => { + it('should respect the option value', () => { + const tree = runner.runSchematic( + 'drag-drop', {styleext: 'scss', ...baseOptions}, createTestApp(runner)); + + expect(tree.files).toContain('/projects/material/src/app/foo/foo.component.scss'); + }); + + it('should fall back to the @schematics/angular:component option value', () => { + const tree = runner.runSchematic( + 'drag-drop', baseOptions, createTestApp(runner, {style: 'less'})); + + expect(tree.files).toContain('/projects/material/src/app/foo/foo.component.less'); + }); + }); + + describe('inlineStyle option', () => { + it('should respect the option value', () => { + const tree = runner.runSchematic( + 'drag-drop', {inlineStyle: true, ...baseOptions}, createTestApp(runner)); + + expect(tree.files).not.toContain('/projects/material/src/app/foo/foo.component.css'); + }); + + it('should fall back to the @schematics/angular:component option value', () => { + const tree = runner.runSchematic( + 'drag-drop', baseOptions, createTestApp(runner, {inlineStyle: true})); + + expect(tree.files).not.toContain('/projects/material/src/app/foo/foo.component.css'); + }); + }); + + describe('inlineTemplate option', () => { + it('should respect the option value', () => { + const tree = runner.runSchematic( + 'drag-drop', {inlineTemplate: true, ...baseOptions}, createTestApp(runner)); + + expect(tree.files).not.toContain('/projects/material/src/app/foo/foo.component.html'); + }); + + it('should fall back to the @schematics/angular:component option value', () => { + const tree = runner.runSchematic( + 'drag-drop', baseOptions, createTestApp(runner, {inlineTemplate: true})); + + expect(tree.files).not.toContain('/projects/material/src/app/foo/foo.component.html'); + }); + }); + + describe('spec option', () => { + it('should respect the option value', () => { + const tree = runner.runSchematic( + 'drag-drop', {spec: false, ...baseOptions}, createTestApp(runner)); + + expect(tree.files).not.toContain('/projects/material/src/app/foo/foo.component.spec.ts'); + }); + + it('should fall back to the @schematics/angular:component option value', () => { + const tree = runner.runSchematic( + 'drag-drop', baseOptions, createTestApp(runner, {skipTests: true})); + + expect(tree.files).not.toContain('/projects/material/src/app/foo/foo.component.spec.ts'); + }); + }); +}); diff --git a/src/cdk/schematics/ng-generate/drag-drop/index.ts b/src/cdk/schematics/ng-generate/drag-drop/index.ts new file mode 100644 index 00000000000..c8068bdba3f --- /dev/null +++ b/src/cdk/schematics/ng-generate/drag-drop/index.ts @@ -0,0 +1,32 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import {chain, noop, Rule, Tree} from '@angular-devkit/schematics'; +import {addModuleImportToModule, buildComponent, findModuleFromOptions} from '../../utils'; +import {Schema} from './schema'; + +/** Scaffolds a new Angular component that uses the Drag and Drop module. */ +export default function(options: Schema): Rule { + return chain([ + buildComponent({...options}, { + template: './__path__/__name@dasherize@if-flat__/__name@dasherize__.component.html', + stylesheet: './__path__/__name@dasherize@if-flat__/__name@dasherize__.component.__styleext__', + }), + options.skipImport ? noop() : addDragDropModulesToModule(options) + ]); +} + +/** Adds the required modules to the main module of the CLI project. */ +function addDragDropModulesToModule(options: Schema) { + return (host: Tree) => { + const modulePath = findModuleFromOptions(host, options)!; + + addModuleImportToModule(host, modulePath, 'DragDropModule', '@angular/cdk/drag-drop'); + return host; + }; +} diff --git a/src/cdk/schematics/ng-generate/drag-drop/schema.json b/src/cdk/schematics/ng-generate/drag-drop/schema.json new file mode 100644 index 00000000000..528674304b2 --- /dev/null +++ b/src/cdk/schematics/ng-generate/drag-drop/schema.json @@ -0,0 +1,97 @@ +{ + "$schema": "http://json-schema.org/schema", + "id": "CdkSchematicsDragDrop", + "title": "Angular CDK Drag and Drop schematic", + "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.", + "$default": { + "$source": "projectName" + } + }, + "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", + "alias": "s" + }, + "inlineTemplate": { + "description": "Specifies if the template will be in the ts file.", + "type": "boolean", + "alias": "t" + }, + "viewEncapsulation": { + "description": "Specifies the view encapsulation strategy.", + "enum": ["Emulated", "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" + }, + "spec": { + "type": "boolean", + "description": "Specifies if a spec file is generated." + }, + "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." + }, + "entryComponent": { + "type": "boolean", + "default": false, + "description": "Specifies if the component is an entry component of declaring module." + } + }, + "required": [] +} diff --git a/src/cdk/schematics/ng-generate/drag-drop/schema.ts b/src/cdk/schematics/ng-generate/drag-drop/schema.ts new file mode 100644 index 00000000000..38d35b40118 --- /dev/null +++ b/src/cdk/schematics/ng-generate/drag-drop/schema.ts @@ -0,0 +1,11 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import {Schema as ComponentSchema} from '@schematics/angular/component/schema'; + +export interface Schema extends ComponentSchema {} diff --git a/src/cdk/schematics/tsconfig.json b/src/cdk/schematics/tsconfig.json index 31de7d81b11..137ad9553d8 100644 --- a/src/cdk/schematics/tsconfig.json +++ b/src/cdk/schematics/tsconfig.json @@ -18,6 +18,7 @@ ] }, "exclude": [ + "**/files/**/*", "**/*.spec.ts" ], "bazelOptions": { diff --git a/tslint.json b/tslint.json index 215cebf878c..b6f248504e7 100644 --- a/tslint.json +++ b/tslint.json @@ -135,6 +135,7 @@ "exclude": [ // Exclude schematic template files and test cases that can't be linted. "src/lib/schematics/**/files/**/*", + "src/cdk/schematics/**/files/**/*", "src/lib/schematics/ng-update/test-cases/**/*" ] }