diff --git a/packages/klingon-ui/package-lock.json b/packages/klingon-ui/package-lock.json index b50cdd6..745f9d4 100644 --- a/packages/klingon-ui/package-lock.json +++ b/packages/klingon-ui/package-lock.json @@ -3724,12 +3724,14 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -3744,17 +3746,20 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -3871,7 +3876,8 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "ini": { "version": "1.3.5", @@ -3883,6 +3889,7 @@ "version": "1.0.0", "bundled": true, "dev": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -3897,6 +3904,7 @@ "version": "3.0.4", "bundled": true, "dev": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -3904,12 +3912,14 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "minipass": { "version": "2.2.4", "bundled": true, "dev": true, + "optional": true, "requires": { "safe-buffer": "^5.1.1", "yallist": "^3.0.0" @@ -3928,6 +3938,7 @@ "version": "0.5.1", "bundled": true, "dev": true, + "optional": true, "requires": { "minimist": "0.0.8" } @@ -4008,7 +4019,8 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "object-assign": { "version": "4.1.1", @@ -4020,6 +4032,7 @@ "version": "1.4.0", "bundled": true, "dev": true, + "optional": true, "requires": { "wrappy": "1" } @@ -4141,6 +4154,7 @@ "version": "1.0.2", "bundled": true, "dev": true, + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", diff --git a/packages/klingon-ui/src/app/_shared/utilities/import.service.ts b/packages/klingon-ui/src/app/_shared/utilities/import.service.ts new file mode 100644 index 0000000..3c2a5d4 --- /dev/null +++ b/packages/klingon-ui/src/app/_shared/utilities/import.service.ts @@ -0,0 +1,198 @@ +import { Injectable } from '@angular/core'; + +export interface DirectoryEntry { + filesystem: any; + fullpath: string; + isDirectory: boolean; + isFile: boolean; + name: string; + createReader(); +} + +export interface FileEntry { + filesystem: any; + fullpath: string; + isDirectory: boolean; + isFile: boolean; + name: string; + file(successCallBack: Function, errorCallBack: Function); +} + +@Injectable({ + providedIn: 'root' +}) +export class ImportService { + + async import(dataTransfer: DataTransfer) { + const entry: any = await new Promise(resolve => setTimeout(resolve, 0, this.validate(dataTransfer))); + const entries: any = await new Promise(resolve => setTimeout(resolve, 0, this.parseDirectoryEntry(entry))); + return await new Promise(resolve => setTimeout(resolve, 0, this.getAngularConfig(entries, entry))); + } + + /** + * validate dropped item + * @param dataTransfer DataTransfer + */ + validate(dataTransfer: DataTransfer) { + return new Promise((resolve, reject) => { + if (dataTransfer.items.length > 0) { + const entry: DirectoryEntry = dataTransfer.items[0].webkitGetAsEntry(); + if (entry.isDirectory) { + resolve(entry); + } else { + reject('Dropped object must be a directory'); + } + } else { + reject('Data transfer item is empty'); + } + }); + } + + /** + * Read directory entries + * @param directoryData DirectoryEntry + */ + parseDirectoryEntry(directoryData: DirectoryEntry) { + const directoryReader = directoryData.createReader(); + return new Promise((resolve, reject) => { + directoryReader.readEntries( + (entries: any[]) => { + resolve(entries); + }, + err => { + reject(err); + } + ); + }); + } + + /** + * return angular project configuration data + * @param directoryData any[] + * @param entry DirectoryEntry + * @param configData any + */ + getData(directoryData: any[], entry: DirectoryEntry, configData) { + return { + directory: entry.name, + 'skip-git': this.getSkipGitFlag(directoryData), + 'app-name': this.getAppName(configData), + prefix: this.getPrefix(configData), + 'inline-style': this.getInlineStyle(configData), + 'inline-template': this.getInlineTemplate(configData), + 'skip-tests': this.getSkipTestsFlag(configData), + style: this.getStyle(configData), + 'skip-install': this.getSkipInstall(directoryData), + 'routing': this.getRoutingFlag(directoryData) + }; + } + + async getRoutingFlag(directoryData: any[]) { + const srcDirectory: DirectoryEntry[] = directoryData.filter((entry: DirectoryEntry) => entry.isDirectory && entry.name === 'src'); + if (srcDirectory.length === 1) { + const srcEntries: any = await new Promise((resolve) => setTimeout(resolve, 0, this.parseDirectoryEntry(srcDirectory[0]))); + const appDirectory: DirectoryEntry[] = srcEntries.filter((entry: DirectoryEntry) => (entry.isDirectory) && entry.name === 'app'); + if (appDirectory.length === 1) { + const appEntries: any = await new Promise((resolve) => setTimeout(resolve, 0, this.parseDirectoryEntry(appDirectory[0]))); + return (appEntries.filter((entry: FileEntry) => entry.name.lastIndexOf('routing.module.ts') > -1 ).length === 1); + } + } + return false; + } + + getSkipInstall(directoryData: any[]) { + return directoryData.filter((entry: any) => (entry.isDirectory && entry.name === 'node_modules')).length === 0; + } + + /** + * get style value + * @param configData any + */ + getStyle(configData) { + const schematics = configData[configData.newProjectRoot][configData.defaultProject]['schematics']; + return schematics['@schematics/angular:component'] ? schematics['@schematics/angular:component']['styleext'] : ''; + } + + /** + * get skip tests flag + * @param configData any + */ + getSkipTestsFlag(configData) { + const schematics = configData[configData.newProjectRoot][configData.defaultProject]['schematics']; + return schematics['@schematics/angular:component'] ? !schematics['@schematics/angular:component']['spec'] : false; + } + + /** + * get inline template flag + * @param configData any + */ + getInlineTemplate(configData) { + const schematics = configData[configData.newProjectRoot][configData.defaultProject]['schematics']; + return schematics['@schematics/angular:component'] ? schematics['@schematics/angular:component']['inlineTemplate'] : false; + } + + /** + * get inline style flag + * @param configData any + */ + getInlineStyle(configData) { + const schematics = configData[configData.newProjectRoot][configData.defaultProject]['schematics']; + return schematics['@schematics/angular:component'] ? schematics['@schematics/angular:component']['inlineStyle'] : false; + } + + /** + * get application name + * @param configData any + */ + getAppName(configData) { + return configData.defaultProject; + } + + /** + * get skip git flag + * @param entries any[] + */ + getSkipGitFlag(entries: any[]) { + return entries.filter((entry: FileEntry) => (entry.isDirectory && entry.name === '.git')).length === 0; + } + + /** + * get application prefix + * @param configData any + */ + getPrefix(configData) { + const projectRoot = configData.newProjectRoot; + return (configData[projectRoot] && configData[projectRoot][configData.defaultProject]) ? + configData[projectRoot][configData.defaultProject].prefix : ''; + } + + /** + * read angular.json file + * @param directoryData any[] + * @param directoryEntry DirectoryEntry + */ + getAngularConfig(directoryData: any[], directoryEntry: DirectoryEntry) { + return new Promise((resolve, reject) => { + const angularConfig: any[] = directoryData.filter( + (entry: FileEntry) => (entry.isFile && entry.name === 'angular.json') + ); + if (angularConfig.length === 1) { + angularConfig[0].file( + file => { + const render = new FileReader(); + render.onloadend = e => { + const target: FileReader = e.currentTarget; + resolve(this.getData(directoryData, directoryEntry, JSON.parse(target.result))); + }; + render.readAsText(file); + }, + error => { + reject(error); + } + ); + } else { + reject('angular.json missing'); + } + }); + } +} diff --git a/packages/klingon-ui/src/app/app.component.html b/packages/klingon-ui/src/app/app.component.html index 9b11586..1dadeee 100644 --- a/packages/klingon-ui/src/app/app.component.html +++ b/packages/klingon-ui/src/app/app.component.html @@ -30,10 +30,10 @@ --> - + New - + Generate diff --git a/packages/klingon-ui/src/app/app.component.ts b/packages/klingon-ui/src/app/app.component.ts index 729c0bd..3abf38d 100644 --- a/packages/klingon-ui/src/app/app.component.ts +++ b/packages/klingon-ui/src/app/app.component.ts @@ -2,6 +2,7 @@ import { Component, ViewChild } from '@angular/core'; import { FormGroup, FormControl, Validators } from '@angular/forms'; import { MatSnackBar } from '@angular/material/snack-bar'; import { OnInit } from '@angular/core/src/metadata/lifecycle_hooks'; +import { CliCreateComponent } from './cli/create/create.component'; @Component({ selector: 'app-snack-bar-error', @@ -59,6 +60,8 @@ export class SnackBarSuccessComponent {} export class AppComponent implements OnInit { selectedIndex = 0; + @ViewChild('appCli') appCli: CliCreateComponent; + constructor( public snackBarError: MatSnackBar, public snackBarSuccess: MatSnackBar @@ -92,4 +95,20 @@ export class AppComponent implements OnInit { }); } } + + + onDrag(event: DragEvent) { + event.preventDefault(); + event.stopPropagation(); + } + + onDragLeave(event: DragEvent) { + event.preventDefault(); + event.stopPropagation(); + } + + onDrop(event: DragEvent) { + this.appCli.import(event); + } + } diff --git a/packages/klingon-ui/src/app/cli/create/create.component.ts b/packages/klingon-ui/src/app/cli/create/create.component.ts index d3a10dc..c89be66 100644 --- a/packages/klingon-ui/src/app/cli/create/create.component.ts +++ b/packages/klingon-ui/src/app/cli/create/create.component.ts @@ -4,17 +4,20 @@ import { Validators } from '@angular/forms'; import { FormControl } from '@angular/forms'; import { FormGroup } from '@angular/forms'; import { Component, OnInit } from '@angular/core'; +import { ImportService } from '../../_shared/utilities/import.service'; @Component({ selector: 'app-cli-create', templateUrl: './create.component.html', - styleUrls: ['./create.component.css', '../flags/flags.component.css'] + styleUrls: ['./create.component.css', '../flags/flags.component.css'], + providers: [ImportService] }) export class CliCreateComponent extends FlagsComponent implements OnInit { form: FormGroup; - styleExt = ['css', 'scss', 'less', 'sass', 'styl']; + defaultStyleExt = 'css'; + styleExt = [this.defaultStyleExt, 'scss', 'less', 'sass', 'styl']; - constructor(public cli: CliService) { + constructor(public cli: CliService, public _import: ImportService) { super(); } @@ -53,4 +56,32 @@ export class CliCreateComponent extends FlagsComponent implements OnInit { } }); } + + import(event: DragEvent) { + this._import.import(event.dataTransfer).then((success: any) => { + this.form.controls['directory'].setValue(success.directory); + this.form.controls['skip-git'].setValue(success['skip-git']); + this.form.controls['app-name'].setValue(success['app-name']); + this.form.controls['prefix'].setValue(success.prefix); + + /** + * Browser doesn't return full path of root directory due to security reasons. + * So, let the root-dir field have current value + */ + this.form.controls['root-dir'].setValue(this.form.controls['root-dir'].value); + this.form.controls['inline-style'].setValue(success['inline-style']); + this.form.controls['inline-template'].setValue(success['inline-template']); + this.form.controls['skip-tests'].setValue(success['skip-tests']); + this.form.controls['style'].setValue(success.style ? success.style : this.defaultStyleExt); + this.form.controls['skip-install'].setValue(success['skip-install']); + this.form.controls['routing'].setValue(success.routing); + + this.form.controls['verbose'].setValue(false); + this.form.controls['dry-run'].setValue(false); + + this.onStdOut.next(success['app-name'] + ' project imported successfully.'); + }).catch(error => { + this.onStdErr.next(error); + }); + } }