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)
This commit is contained in:
Paul Gschwendtner
2018-10-04 20:53:40 +02:00
committed by vivian-hu
parent 3254992a8e
commit 72ccd8b831
12 changed files with 396 additions and 3 deletions
+3 -3
View File
@@ -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",
+6
View File
@@ -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"]
}
}
}
@@ -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;
}
@@ -0,0 +1,15 @@
<div class="container">
<h2>To do</h2>
<cdk-drop #todoList [data]="todo" [connectedTo]="doneList" class="list" (dropped)="drop($event)">
<div class="list-item" *ngFor="let item of todo" cdkDrag>{{item}}</div>
</cdk-drop>
</div>
<div class="container">
<h2>Done</h2>
<cdk-drop #doneList [data]="done" [connectedTo]="todoList" class="list" (dropped)="drop($event)">
<div class="list-item" *ngFor="let item of done" cdkDrag>{{item}}</div>
</cdk-drop>
</div>
@@ -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();
});
});
@@ -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<string[]>) {
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);
}
}
}
@@ -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');
});
});
});
@@ -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;
};
}
@@ -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": []
}
@@ -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 {}
+1
View File
@@ -18,6 +18,7 @@
]
},
"exclude": [
"**/files/**/*",
"**/*.spec.ts"
],
"bazelOptions": {
+1
View File
@@ -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/**/*"
]
}