diff options
Diffstat (limited to 'catalog-ui/src/app/ng2')
105 files changed, 5310 insertions, 0 deletions
diff --git a/catalog-ui/src/app/ng2/app.component.css b/catalog-ui/src/app/ng2/app.component.css new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/catalog-ui/src/app/ng2/app.component.css diff --git a/catalog-ui/src/app/ng2/app.component.html b/catalog-ui/src/app/ng2/app.component.html new file mode 100644 index 0000000000..ed90b3deda --- /dev/null +++ b/catalog-ui/src/app/ng2/app.component.html @@ -0,0 +1,6 @@ +<!--<nav> + <app-navbar></app-navbar> +</nav> +<main> + <router-outlet></router-outlet> +</main>-->
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/app.component.ts b/catalog-ui/src/app/ng2/app.component.ts new file mode 100644 index 0000000000..0499045a79 --- /dev/null +++ b/catalog-ui/src/app/ng2/app.component.ts @@ -0,0 +1,15 @@ +import { Component, Inject } from '@angular/core'; +import { AuthenticationService } from './services/authentication.service'; + +@Component({ + selector: 'app-root', + templateUrl: './app.component.html', + styleUrls: ['./app.component.css'] +}) +export class AppComponent { + + constructor(auth:AuthenticationService){ + + } + +} diff --git a/catalog-ui/src/app/ng2/app.module.ts b/catalog-ui/src/app/ng2/app.module.ts new file mode 100644 index 0000000000..ea73d382e2 --- /dev/null +++ b/catalog-ui/src/app/ng2/app.module.ts @@ -0,0 +1,76 @@ +import {BrowserModule} from '@angular/platform-browser'; +import {NgModule, APP_INITIALIZER} from '@angular/core'; +import {FormsModule} from '@angular/forms'; +import {forwardRef} from '@angular/core'; +import {AppComponent} from './app.component'; +import {UpgradeAdapter} from '@angular/upgrade'; +import {UpgradeModule} from '@angular/upgrade/static'; +import {PropertiesAssignmentModule} from './pages/properties-assignment/properties-assignment.module'; +import { + DataTypesServiceProvider, SharingServiceProvider, CookieServiceProvider, + StateParamsServiceFactory, CacheServiceProvider, EventListenerServiceProvider +} from "./utils/ng1-upgraded-provider"; +import {ConfigService} from "./services/config.service"; +import {HttpService} from "./services/http.service"; +import {HttpModule} from '@angular/http'; +import {AuthenticationService} from './services/authentication.service'; +import {Cookie2Service} from "./services/cookie.service"; +import {ComponentServiceNg2} from "./services/component-services/component.service"; +import {ServiceServiceNg2} from "./services/component-services/service.service"; +import {ComponentInstanceServiceNg2} from "./services/component-instance-services/component-instance.service"; + +export const upgradeAdapter = new UpgradeAdapter(forwardRef(() => AppModule)); + +export function configServiceFactory(config:ConfigService) { + return () => config.loadValidationConfiguration(); +} + +// export function httpServiceFactory(backend: XHRBackend, options: RequestOptions) { +// return new HttpService(backend, options); +// } + +@NgModule({ + declarations: [ + AppComponent + ], + imports: [ + BrowserModule, + UpgradeModule, + FormsModule, + HttpModule, + PropertiesAssignmentModule + ], + exports: [], + entryComponents: [], + providers: [ + HttpService, + DataTypesServiceProvider, + SharingServiceProvider, + CookieServiceProvider, + StateParamsServiceFactory, + CacheServiceProvider, + EventListenerServiceProvider, + AuthenticationService, + Cookie2Service, + ConfigService, + ComponentServiceNg2, + ServiceServiceNg2, + ComponentInstanceServiceNg2, + { + provide: APP_INITIALIZER, + useFactory: configServiceFactory, + deps: [ConfigService], + multi: true + } + ], + bootstrap: [AppComponent] +}) + + +export class AppModule { + // ngDoBootstrap() {} + constructor(public upgrade:UpgradeModule) { + + + } +} diff --git a/catalog-ui/src/app/ng2/app.routing.ts b/catalog-ui/src/app/ng2/app.routing.ts new file mode 100644 index 0000000000..38bc92619f --- /dev/null +++ b/catalog-ui/src/app/ng2/app.routing.ts @@ -0,0 +1,20 @@ +import { RouterModule, Route } from '@angular/router'; +import { ModuleWithProviders } from '@angular/core'; +// import { Page1Component } from "./pages/page1/page1.component"; +// import { Page2Component } from "./pages/page2/page2.component"; +import { PageNotFoundComponent } from "./pages/page404/page404.component"; + +const routes: Route[] = [ + // { path: 'page1', component: Page1Component }, + // { path: 'page2', component: Page2Component }, + // { path: '', pathMatch: 'full', redirectTo: 'page1'}, + { path: '**', component: PageNotFoundComponent } + /*{ loadChildren: './pages/dashboard/dashboard.module#DashboardModule', path: 'dashboard' }*/ +]; + +export const routing: ModuleWithProviders = RouterModule.forRoot( + routes, + { + useHash: true + } +); diff --git a/catalog-ui/src/app/ng2/components/dynamic-element/dynamic-element.component.less b/catalog-ui/src/app/ng2/components/dynamic-element/dynamic-element.component.less new file mode 100644 index 0000000000..e219d49aa4 --- /dev/null +++ b/catalog-ui/src/app/ng2/components/dynamic-element/dynamic-element.component.less @@ -0,0 +1,3 @@ +dynamic-element { + +} diff --git a/catalog-ui/src/app/ng2/components/dynamic-element/dynamic-element.component.ts b/catalog-ui/src/app/ng2/components/dynamic-element/dynamic-element.component.ts new file mode 100644 index 0000000000..0c74765944 --- /dev/null +++ b/catalog-ui/src/app/ng2/components/dynamic-element/dynamic-element.component.ts @@ -0,0 +1,125 @@ +import { Component, Compiler, EventEmitter, ViewContainerRef, ViewChild, Input, Output, ElementRef, ComponentRef, ComponentFactory, ComponentFactoryResolver } from '@angular/core' +import { UiElementCheckBoxComponent } from './elements-ui/checkbox/ui-element-checkbox.component'; +import { UiElementDropDownComponent, DropdownValue } from './elements-ui/dropdown/ui-element-dropdown.component'; +import { UiElementInputComponent } from './elements-ui/input/ui-element-input.component'; +import {UiElementPopoverInputComponent} from "./elements-ui/popover-input/ui-element-popover-input.component"; +import {ValidationConfiguration} from "app/models"; +import {UiElementIntegerInputComponent} from "./elements-ui/integer-input/ui-element-integer-input.component"; + +@Component({ + selector: 'dynamic-element', + template: `<div #target></div>`, + styleUrls: ['./dynamic-element.component.less'], + entryComponents: [ + UiElementInputComponent, + UiElementDropDownComponent, + UiElementCheckBoxComponent, + UiElementPopoverInputComponent, + UiElementIntegerInputComponent + ] +}) +export class DynamicElementComponent { + + @ViewChild('target', { read: ViewContainerRef }) target: any; + @Input() type: any; + @Input() name: string; + @Input() readonly:boolean; + @Input() path:string;//optional param. used only for for subnetpoolid type + value:any; + + // Two way binding for value (need to write the "Change" word like this) + @Output('valueChange') emitter: EventEmitter<string> = new EventEmitter<any>(); + @Input('value') set setValueValue(value) { + this.value = value; + } + + cmpRef: ComponentRef<any>; + private isViewInitialized: boolean = false; + validation = ValidationConfiguration.validation; + + constructor( + private componentFactoryResolver: ComponentFactoryResolver, + private compiler: Compiler, + private el: ElementRef) { + } + + updateComponent() { + if (!this.isViewInitialized) { + return; + } + if (this.cmpRef) { + this.cmpRef.destroy(); + } + + // Factory to create component based on type or peroperty name. + switch(this.type) { + case 'list': + case 'integer': + this.createComponent(UiElementIntegerInputComponent); + this.cmpRef.instance.pattern = this.validation.validationPatterns.integer; + break; + case 'string': + if (this.path && this.path.toUpperCase().indexOf("SUBNETPOOLID") !== -1) { + if(this.name.toUpperCase().indexOf("SUBNETPOOLID") == -1){//if it's an item of subnetpoolid list get the parent name + let pathArray = this.path.split("#"); + this.name = pathArray[pathArray.length - 2]; + } + this.createComponent(UiElementPopoverInputComponent); + } + else { + this.createComponent(UiElementInputComponent); + } + break; + case 'boolean': + //this.createComponent(UiElementCheckBoxComponent); + + this.createComponent(UiElementDropDownComponent); + + // Build drop down values + let tmp = []; + tmp.push(new DropdownValue('true','TRUE')); + tmp.push(new DropdownValue('false','FALSE')); + this.cmpRef.instance.values = tmp; + break; + default: + this.createComponent(UiElementInputComponent); + console.log("ERROR: No ui component to handle type: " + this.type); + } + + // Additional attributes in base element class + if (this.cmpRef) { + this.cmpRef.instance.name = this.name; + this.cmpRef.instance.type = this.type; + this.cmpRef.instance.value = this.value; + this.cmpRef.instance.readonly = this.readonly; + } + + // Subscribe to change event of of ui-element-basic and fire event to change the value + this.cmpRef.instance.baseEmitter.subscribe((value):void => { + this.emitter.emit(value) + }); + + } + + createComponent(ComponentToCreate:any):void { + let factory = this.componentFactoryResolver.resolveComponentFactory(ComponentToCreate); + this.cmpRef = this.target.createComponent(factory); + } + + ngOnChanges() { + this.updateComponent(); + } + + ngAfterContentInit() { + //console.log("DynamicElementComponent: ngAfterContentInit: type: " + this.type + " value: " + this.value); + this.isViewInitialized = true; + this.updateComponent(); + } + + ngOnDestroy() { + if (this.cmpRef) { + this.cmpRef.destroy(); + } + } + +} diff --git a/catalog-ui/src/app/ng2/components/dynamic-element/dynamic-element.module.ts b/catalog-ui/src/app/ng2/components/dynamic-element/dynamic-element.module.ts new file mode 100644 index 0000000000..f53b8616ac --- /dev/null +++ b/catalog-ui/src/app/ng2/components/dynamic-element/dynamic-element.module.ts @@ -0,0 +1,36 @@ +import { NgModule } from "@angular/core"; +import { UiElementCheckBoxComponent } from './elements-ui/checkbox/ui-element-checkbox.component'; +import { UiElementDropDownComponent } from './elements-ui/dropdown/ui-element-dropdown.component'; +import { UiElementInputComponent } from './elements-ui/input/ui-element-input.component'; +import { DynamicElementComponent } from "app/ng2/components/dynamic-element/dynamic-element.component"; +import { BrowserModule } from '@angular/platform-browser' +import { FormsModule, ReactiveFormsModule } from '@angular/forms' +import { UiElementPopoverInputComponent } from "./elements-ui/popover-input/ui-element-popover-input.component"; +import {PopoverModule} from "../popover/popover.module"; +import {TooltipModule} from "../tooltip/tooltip.module"; +import {UiElementIntegerInputComponent} from "./elements-ui/integer-input/ui-element-integer-input.component"; + +@NgModule({ + declarations: [ + DynamicElementComponent, + UiElementInputComponent, + UiElementCheckBoxComponent, + UiElementDropDownComponent, + UiElementPopoverInputComponent, + UiElementIntegerInputComponent + ], + imports: [ + BrowserModule, + FormsModule, + PopoverModule, + ReactiveFormsModule, + TooltipModule + ], + exports: [ + DynamicElementComponent + ], + providers: [] +}) +export class DynamicElementModule { + +} diff --git a/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/checkbox/ui-element-checkbox.component.html b/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/checkbox/ui-element-checkbox.component.html new file mode 100644 index 0000000000..a3e28c5f0b --- /dev/null +++ b/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/checkbox/ui-element-checkbox.component.html @@ -0,0 +1 @@ +<input #{{name}} [(ngModel)]="value" type="checkbox" (change)="onSave(value)" [ngClass]="{'disabled':readonly}"/> diff --git a/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/checkbox/ui-element-checkbox.component.less b/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/checkbox/ui-element-checkbox.component.less new file mode 100644 index 0000000000..bed097fe5e --- /dev/null +++ b/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/checkbox/ui-element-checkbox.component.less @@ -0,0 +1,2 @@ +/deep/ ui-element-checkbox { +} diff --git a/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/checkbox/ui-element-checkbox.component.ts b/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/checkbox/ui-element-checkbox.component.ts new file mode 100644 index 0000000000..152303aee7 --- /dev/null +++ b/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/checkbox/ui-element-checkbox.component.ts @@ -0,0 +1,27 @@ +import { Component, ViewChild, ElementRef, ContentChildren, Input } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser' +import { UiElementBase, UiElementBaseInterface } from './../ui-element-base.component'; + +@Component({ + selector: 'ui-element-checkbox', + templateUrl: './ui-element-checkbox.component.html', + styleUrls: ['./ui-element-checkbox.component.less'], +}) +export class UiElementCheckBoxComponent extends UiElementBase implements UiElementBaseInterface { + + constructor() { + super(); + } + + ngAfterContentInit() { + // Convert the value to boolean (instanceOf does not work, the type is undefined). + if (this.value==='true' || this.value==='false') { + this.value = this.value==='true'?true:false; + } + } + + onSave() { + this.baseEmitter.emit(this.value); + } + +} diff --git a/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/dropdown/ui-element-dropdown.component.html b/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/dropdown/ui-element-dropdown.component.html new file mode 100644 index 0000000000..bfb927af71 --- /dev/null +++ b/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/dropdown/ui-element-dropdown.component.html @@ -0,0 +1,3 @@ +<select name='{{name}}' [(ngModel)]="value" #t (change)="onSave()" [ngClass]="{'disabled':readonly}"> + <option *ngFor="let ddvalue of values" [value]="ddvalue.value">{{ddvalue.label}}</option> +</select> diff --git a/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/dropdown/ui-element-dropdown.component.less b/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/dropdown/ui-element-dropdown.component.less new file mode 100644 index 0000000000..ea3e35140e --- /dev/null +++ b/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/dropdown/ui-element-dropdown.component.less @@ -0,0 +1,11 @@ +@import '../../../../../../assets/styles/variables'; + +/deep/ ui-element-dropdown { + + select { + text-indent: 6px; + border: solid 1px @main_color_o; + width: 100%; + } + +} diff --git a/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/dropdown/ui-element-dropdown.component.ts b/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/dropdown/ui-element-dropdown.component.ts new file mode 100644 index 0000000000..b1fb37a186 --- /dev/null +++ b/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/dropdown/ui-element-dropdown.component.ts @@ -0,0 +1,32 @@ +import { Component, EventEmitter, Output, Input } from '@angular/core' +import { BrowserModule } from '@angular/platform-browser' +import { UiElementBase, UiElementBaseInterface } from './../ui-element-base.component'; + +export class DropdownValue { + value:string; + label:string; + + constructor(value:string,label:string) { + this.value = value; + this.label = label; + } +} + +@Component({ + selector: 'ui-element-dropdown', + templateUrl: './ui-element-dropdown.component.html', + styleUrls: ['./ui-element-dropdown.component.less'], +}) +export class UiElementDropDownComponent extends UiElementBase implements UiElementBaseInterface { + @Input() + values: DropdownValue[]; + + constructor() { + super(); + } + + onSave() { + this.baseEmitter.emit(JSON.parse(this.value)); + } + +} diff --git a/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/input/ui-element-input.component.html b/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/input/ui-element-input.component.html new file mode 100644 index 0000000000..814ebfd28b --- /dev/null +++ b/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/input/ui-element-input.component.html @@ -0,0 +1,15 @@ +<input + class="value-input" + [ngClass]="{'error': control.invalid}" + type="text" + [name]="name" + [(ngModel)]="value" + (change)="onSave()" + [attr.maxlength]="validation.propertyValue.max" + [attr.minlength]="validation.propertyValue.min" + [pattern]="pattern" + [formControl]="control" + tooltip="{{value}}" + [readonly]="readonly" + [ngClass]="{'disabled':readonly}" + /> diff --git a/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/input/ui-element-input.component.less b/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/input/ui-element-input.component.less new file mode 100644 index 0000000000..d320c7ff8b --- /dev/null +++ b/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/input/ui-element-input.component.less @@ -0,0 +1,17 @@ +@import '../../../../../../assets/styles/variables'; + +/deep/ ui-element-input { + + input { + text-indent: 6px; + border: solid 1px @main_color_o; + } + + .error { + border: solid 1px @func_color_q; + color: @func_color_q; + outline: none; + box-sizing: border-box; + } + +} diff --git a/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/input/ui-element-input.component.ts b/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/input/ui-element-input.component.ts new file mode 100644 index 0000000000..2d64d9b713 --- /dev/null +++ b/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/input/ui-element-input.component.ts @@ -0,0 +1,21 @@ +import {Component, ViewChild, ElementRef, ContentChildren, Input} from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser' +import { UiElementBase, UiElementBaseInterface } from './../ui-element-base.component'; + +@Component({ + selector: 'ui-element-input', + templateUrl: './ui-element-input.component.html', + styleUrls: ['./ui-element-input.component.less'], +}) +export class UiElementInputComponent extends UiElementBase implements UiElementBaseInterface { + constructor() { + super(); + this.pattern = this.validation.validationPatterns.comment; + } + + onSave() { + if (!this.control.invalid){ + this.baseEmitter.emit(this.value); + } + } +} diff --git a/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/integer-input/ui-element-integer-input.component.html b/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/integer-input/ui-element-integer-input.component.html new file mode 100644 index 0000000000..e5518d453f --- /dev/null +++ b/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/integer-input/ui-element-integer-input.component.html @@ -0,0 +1,15 @@ +<input + class="value-input" + [ngClass]="{'error': control.invalid}" + type="text" + [name]="name" + [(ngModel)]="value" + (change)="onSave()" + [attr.maxlength]="validation.propertyValue.max" + [attr.minlength]="validation.propertyValue.min" + [pattern]="pattern" + [formControl]="control" + tooltip="{{value}}" + [readonly]="readonly" + [ngClass]="{'disabled':readonly}" +/> diff --git a/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/integer-input/ui-element-integer-input.component.less b/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/integer-input/ui-element-integer-input.component.less new file mode 100644 index 0000000000..8073c3858e --- /dev/null +++ b/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/integer-input/ui-element-integer-input.component.less @@ -0,0 +1,17 @@ +@import '../../../../../../assets/styles/variables'; + +/deep/ ui-element-integer-input { + + input { + text-indent: 6px; + border: solid 1px @main_color_o; + } + + .error { + border: solid 1px @func_color_q; + color: @func_color_q; + outline: none; + box-sizing: border-box; + } + +} diff --git a/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/integer-input/ui-element-integer-input.component.ts b/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/integer-input/ui-element-integer-input.component.ts new file mode 100644 index 0000000000..d42c80a89e --- /dev/null +++ b/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/integer-input/ui-element-integer-input.component.ts @@ -0,0 +1,21 @@ +import {Component, ViewChild, ElementRef, ContentChildren, Input} from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser' +import { UiElementBase, UiElementBaseInterface } from './../ui-element-base.component'; + +@Component({ + selector: 'ui-element-integer-input', + templateUrl: './ui-element-integer-input.component.html', + styleUrls: ['./ui-element-integer-input.component.less'], +}) +export class UiElementIntegerInputComponent extends UiElementBase implements UiElementBaseInterface { + constructor() { + super(); + this.pattern = this.validation.validationPatterns.comment; + } + + onSave() { + if (!this.control.invalid){ + this.baseEmitter.emit(JSON.parse(this.value)); + } + } +} diff --git a/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/popover-input/ui-element-popover-input.component.html b/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/popover-input/ui-element-popover-input.component.html new file mode 100644 index 0000000000..3bd51b4e36 --- /dev/null +++ b/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/popover-input/ui-element-popover-input.component.html @@ -0,0 +1,26 @@ +<div class="popover-input-wrapper" tooltip="{{value}}"> + <input + class="value-input" + type="text" + [ngClass]="{'error': control.invalid}" + [name]="name" + [value]="value!=undefined?value:''" + disabled + /> + <button [popover]="popoverForm" [ngClass]="{'disabled':readonly}">Edit</button> +</div> + +<popover-content #popoverForm [title]="name" [buttons]="buttonsArray" [placement]="'top'" [closeOnClickOutside]="true"> + <div class="edit-subnet-wrapper"> + <textarea rows="5" + #textArea + class="subnet-value" + [ngClass]="{'error': control.invalid}" + [(ngModel)]="value" + [attr.maxlength]="validation.propertyValue.max" + [attr.minlength]="validation.propertyValue.min" + [pattern]="pattern" + [formControl]="control" + ></textarea> + </div> +</popover-content> diff --git a/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/popover-input/ui-element-popover-input.component.less b/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/popover-input/ui-element-popover-input.component.less new file mode 100644 index 0000000000..5be443f7b6 --- /dev/null +++ b/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/popover-input/ui-element-popover-input.component.less @@ -0,0 +1,36 @@ +@import '../../../../../../assets/styles/variables'; + +.popover-input-wrapper { + display: flex; +} + +/deep/ ui-element-popover-input { + + .popover { + max-width: 350px; + width: 350px; + } + + .edit-subnet-wrapper { + padding: 12px; + + .subnet-value { + width: 100%; + resize: none; + } + } + + input { + padding-right: 6px; + padding-left: 6px; + border: solid 1px @main_color_o; + } + + .error { + border: solid 1px @func_color_q; + color: @func_color_q; + outline: none; + box-sizing: border-box; + } + +} diff --git a/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/popover-input/ui-element-popover-input.component.ts b/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/popover-input/ui-element-popover-input.component.ts new file mode 100644 index 0000000000..84dd884d1f --- /dev/null +++ b/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/popover-input/ui-element-popover-input.component.ts @@ -0,0 +1,38 @@ +import {Component, ViewChild, ElementRef, Input} from '@angular/core'; +import {UiElementBase, UiElementBaseInterface} from "../ui-element-base.component"; +import {ButtonsModelMap, ButtonModel} from "app/models"; +import { PopoverContentComponent } from "app/ng2/components/popover/popover-content.component" +import { PopoverComponent } from "app/ng2/components/popover/popover.component" + +@Component({ + selector: 'ui-element-popover-input', + templateUrl: './ui-element-popover-input.component.html', + styleUrls: ['./ui-element-popover-input.component.less'] +}) +export class UiElementPopoverInputComponent extends UiElementBase implements UiElementBaseInterface { + @ViewChild('textArea') textArea: ElementRef; + @ViewChild('popoverForm') popoverContentComponent: PopoverContentComponent; + + saveButton: ButtonModel; + buttonsArray: ButtonsModelMap; + + onSave = ():void => { + if (!this.control.invalid){ + this.baseEmitter.emit(this.value); + this.popoverContentComponent.hide(); + } + } + + constructor() { + super(); + // Create Save button and insert to buttons map + this.saveButton = new ButtonModel('save', 'blue', this.onSave); + this.buttonsArray = { 'test': this.saveButton }; + + // Define the regex pattern for this controller + this.pattern = this.validation.validationPatterns.comment; + + // Disable / Enable button according to validation + //this.control.valueChanges.subscribe(data => this.saveButton.disabled = this.control.invalid); + } +} diff --git a/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/ui-element-base.component.ts b/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/ui-element-base.component.ts new file mode 100644 index 0000000000..fa2be1048c --- /dev/null +++ b/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/ui-element-base.component.ts @@ -0,0 +1,35 @@ +import { Component, EventEmitter, Input, Output } from '@angular/core' +import { ValidationConfiguration } from "app/models"; +import { FormControl, Validators } from '@angular/forms'; + +export interface UiElementBaseInterface { + onSave(); +} + +@Component({ + template: ``, + styles: [] +}) +export class UiElementBase { + + protected validation = ValidationConfiguration.validation; + protected control: FormControl; + + // Two way binding for value (need to write the "Change" word like this) + @Output('valueChange') baseEmitter: EventEmitter<string> = new EventEmitter<any>(); + @Input('value') set setValueValue(value) { + this.value = value; + } + + protected name: string; + protected type: string; + protected value: any; + protected pattern: any; + protected readonly:boolean; + + constructor() { + //this.control = new FormControl('', [Validators.required]); + this.control = new FormControl('', []); + } + +} diff --git a/catalog-ui/src/app/ng2/components/filter-properties-assignment/filter-properties-assignment.component.html b/catalog-ui/src/app/ng2/components/filter-properties-assignment/filter-properties-assignment.component.html new file mode 100644 index 0000000000..4d2b91f3b4 --- /dev/null +++ b/catalog-ui/src/app/ng2/components/filter-properties-assignment/filter-properties-assignment.component.html @@ -0,0 +1,26 @@ +<popover-content #filterPopover [title]="'Filters'" [buttons]="footerButtons" placement="bottom-right" [hideArrow]="true"> + <!--<form [formGroup]="filterForm">--> + <form> + <div class="field"> + <label>Resource Type</label> + <div> + <checkbox [label]="'All'" [(checked)]="allSelected" (checkedChange)="selectAll()"></checkbox> + </div> + <div *ngFor="let type of typesOptions"> + <checkbox [label]="type" [(checked)]="selectedTypes[type]" (checkedChange)="onTypeSelected(type)"></checkbox> + </div> + </div> + <div class="field"> + <label>Property Name</label> + <input class="i-sdc-form-input" + name="propertyName" + [(ngModel)]="filterData.propertyName" + placeholder="Type here" + required + /> + </div> + </form> +</popover-content> +<div class="open-filter-button" [popover]="filterPopover" [ngClass]="{'open':showPopover}" (onShown)="showPopover = true" (onHidden)="showPopover = false"> + <div class="sprite-new filter-icon"></div> +</div> diff --git a/catalog-ui/src/app/ng2/components/filter-properties-assignment/filter-properties-assignment.component.less b/catalog-ui/src/app/ng2/components/filter-properties-assignment/filter-properties-assignment.component.less new file mode 100644 index 0000000000..07f38d3011 --- /dev/null +++ b/catalog-ui/src/app/ng2/components/filter-properties-assignment/filter-properties-assignment.component.less @@ -0,0 +1,45 @@ +@import '../../../../assets/styles/variables'; +form{ + margin: 0 20px; + .field{ + padding:20px 0; + &:not(:last-child){ + border-bottom: solid 1px @main_color_o; + } + input{ + &::-webkit-input-placeholder { font-style: italic; } /* Safari, Chrome and Opera */ + &:-moz-placeholder { font-style: italic; } /* Firefox 18- */ + &::-moz-placeholder { font-style: italic; } /* Firefox 19+ */ + &:-ms-input-placeholder { font-style: italic; } /* IE 10+ */ + &:-ms-input-placeholder { font-style: italic; } /* Edge */ + } + } + /deep/ [ng-reflect-checked="true"]{ + /deep/ .checkbox-label-content{ + color: @main_color_a; + } + } +} + +.open-filter-button{ + cursor: pointer; + width: 32px; + height: 34px; + margin-left:5px; + + &.open{ + z-index: 1061; + background-color: @main_color_p; + border: solid 1px @main_color_c; + border-bottom: none; + } + .filter-icon{ + top: 8px; + right: 2px; + position: relative; + } +} + +/deep/ .popover{ + border: solid 1px @main_color_c !important; +} diff --git a/catalog-ui/src/app/ng2/components/filter-properties-assignment/filter-properties-assignment.component.ts b/catalog-ui/src/app/ng2/components/filter-properties-assignment/filter-properties-assignment.component.ts new file mode 100644 index 0000000000..c23e08bc0d --- /dev/null +++ b/catalog-ui/src/app/ng2/components/filter-properties-assignment/filter-properties-assignment.component.ts @@ -0,0 +1,77 @@ +/** + * Created by rc2122 on 5/16/2017. + */ +import {Component, Input, Output, EventEmitter, ViewChild} from '@angular/core'; +import {ButtonModel, ButtonsModelMap, FilterPropertiesAssignmentData} from "app/models"; +import {PopoverComponent} from "../popover/popover.component"; +import * as sdcConfig from "../../../../../configurations/dev" + +@Component({ + selector: 'filter-properties-assignment', + templateUrl: './filter-properties-assignment.component.html', + styleUrls: ['./filter-properties-assignment.component.less'] +}) + +export class FilterPropertiesAssignmentComponent { + @Input() componentType: string; + @Output() searchProperties: EventEmitter<FilterPropertiesAssignmentData> = new EventEmitter<FilterPropertiesAssignmentData>(); + footerButtons:ButtonsModelMap = {}; + typesOptions:Array<string>;//All optional types + selectedTypes:Object = {}; + allSelected:boolean = false;//if all option selected + filterData:FilterPropertiesAssignmentData = new FilterPropertiesAssignmentData(); + @ViewChild('filterPopover') filterPopover: PopoverComponent; + + ngOnInit() { + this.footerButtons['Apply'] = new ButtonModel('Apply', 'blue', this.search, this.someTypesSelectedAndThereIsPropertyName); + this.footerButtons['Close'] = new ButtonModel('Close', 'grey', this.close); + this.componentType = this.componentType.toLocaleLowerCase(); + this.typesOptions = sdcConfig.resourceTypesFilter[this.componentType]; + } + + selectAll = () => { + _.forEach(this.typesOptions, (type) => { + this.selectedTypes[type] = this.allSelected; + }); + } + + onTypeSelected = (type:string) => { + if(!this.selectedTypes[type]){ + this.allSelected = false;//unselected 'All' + } + }; + + search = () => { + console.log('search props'); + this.filterData.selectedTypes = []; + _.forEach(sdcConfig.resourceTypesFilter[this.componentType], (type) => { + if(this.selectedTypes[type]){ + this.filterData.selectedTypes.push(type); + } + }); + this.searchProperties.emit(this.filterData); + this.filterPopover.hide(); + } + + close = () => { + this.filterPopover.hide(); + } + + someTypesSelectedAndThereIsPropertyName = ():boolean => { + if( _.find(Object.keys(this.selectedTypes),(key) => { + return this.selectedTypes[key]; + }) && this.filterData.propertyName ){ + return null + } + return true; + } + + clearAll = ():void => { + this.filterData.propertyName = ""; + _.forEach(this.selectedTypes,(value, key) => { + this.selectedTypes[key] = false; + }); + this.allSelected = false; + } + +} diff --git a/catalog-ui/src/app/ng2/components/hierarchy-navigtion/hierarchy-display-options.ts b/catalog-ui/src/app/ng2/components/hierarchy-navigtion/hierarchy-display-options.ts new file mode 100644 index 0000000000..7045286ccd --- /dev/null +++ b/catalog-ui/src/app/ng2/components/hierarchy-navigtion/hierarchy-display-options.ts @@ -0,0 +1,12 @@ +export class HierarchyDisplayOptions { + idProperty: string; + valueProperty: string; + childrenProperty: string; + searchText:string; + constructor(idProperty:string, valueProperty:string, childrenProperty?:string, searchText?:string) { + this.idProperty = idProperty; + this.valueProperty = valueProperty; + this.childrenProperty = childrenProperty; + this.searchText = searchText; + } +} diff --git a/catalog-ui/src/app/ng2/components/hierarchy-navigtion/hierarchy-navigation.component.html b/catalog-ui/src/app/ng2/components/hierarchy-navigtion/hierarchy-navigation.component.html new file mode 100644 index 0000000000..40a1c37cee --- /dev/null +++ b/catalog-ui/src/app/ng2/components/hierarchy-navigtion/hierarchy-navigation.component.html @@ -0,0 +1,13 @@ +<div class="navigation-wrapper"> + <div class="node-item" *ngFor="let item of displayData" (click)="onClick($event, item)"> + <div class="node-data-wrapper" [ngClass]="{'selected': selectedItem && selectedItem === item[displayOptions.idProperty]}"> + <span class="node-data" [ngClass]="{'mark':item[displayOptions.valueProperty] === displayOptions.searchText}">{{item[displayOptions.valueProperty]}}</span> + </div> + <div class="children-node" *ngIf="item[displayOptions.childrenProperty]"> + <hierarchy-navigation class="children-hierarchy" [displayData]="item[displayOptions.childrenProperty]" + [selectedItem]="selectedItem" + [displayOptions]="displayOptions" + (updateSelected)="onSelectedUpdate($event)"></hierarchy-navigation> + </div> + </div> +</div> diff --git a/catalog-ui/src/app/ng2/components/hierarchy-navigtion/hierarchy-navigation.component.less b/catalog-ui/src/app/ng2/components/hierarchy-navigtion/hierarchy-navigation.component.less new file mode 100644 index 0000000000..a9174fd73d --- /dev/null +++ b/catalog-ui/src/app/ng2/components/hierarchy-navigtion/hierarchy-navigation.component.less @@ -0,0 +1,51 @@ +.navigation-wrapper { + text-align: left; +} + +.node-item { + border: 1px dotted; + border-right: none; + border-bottom: none; +} + +.node-item:last-child { + border-left: none; +} + +.node-data-wrapper { + cursor: default; + height: 39px; + line-height: 39px; + position: relative; + top: -20px; + background-color: white; + margin-left: 0.7em; +} + +.children-node { + padding-left: 40px; +} + +.node-data { + margin-left: 10px; + margin-right: 10px; +} + +.node-data-wrapper.selected { + background-color: #e6f6fb; + + .node-data { + color: #009fdb; + } +} + +.node-data-wrapper:hover { + background-color: #eaeaea; +} + +.mark{ + background-color: yellow; +} + + + diff --git a/catalog-ui/src/app/ng2/components/hierarchy-navigtion/hierarchy-navigation.component.ts b/catalog-ui/src/app/ng2/components/hierarchy-navigtion/hierarchy-navigation.component.ts new file mode 100644 index 0000000000..428bbb4b04 --- /dev/null +++ b/catalog-ui/src/app/ng2/components/hierarchy-navigtion/hierarchy-navigation.component.ts @@ -0,0 +1,28 @@ +import {Component, Input, Output, EventEmitter} from '@angular/core'; +import {HierarchyDisplayOptions} from './hierarchy-display-options'; + + +@Component({ + selector: 'hierarchy-navigation', + templateUrl: './hierarchy-navigation.component.html', + styleUrls: ['./hierarchy-navigation.component.less'] +}) + +export class HierarchyNavigationComponent { + @Input() displayData: Array<any>; + @Input() selectedItem: any; + @Input() displayOptions: HierarchyDisplayOptions; + + @Output() updateSelected:EventEmitter<any> = new EventEmitter(); + + onClick = ($event, item) => { + $event.stopPropagation(); + this.selectedItem = item; + this.updateSelected.emit(item); + }; + + onSelectedUpdate = ($event) => { + this.selectedItem = $event; + this.updateSelected.emit($event); + } +} diff --git a/catalog-ui/src/app/ng2/components/inputs-table/confirmation-delete-input/confirmation-delete-input.component.html b/catalog-ui/src/app/ng2/components/inputs-table/confirmation-delete-input/confirmation-delete-input.component.html new file mode 100644 index 0000000000..7fdd95b304 --- /dev/null +++ b/catalog-ui/src/app/ng2/components/inputs-table/confirmation-delete-input/confirmation-delete-input.component.html @@ -0,0 +1,3 @@ +<modal #confirmationModal title="Delete Input" size="sm" [buttons]="footerButtons"> + Are you sure you want to delete this input? +</modal> diff --git a/catalog-ui/src/app/ng2/components/inputs-table/confirmation-delete-input/confirmation-delete-input.component.ts b/catalog-ui/src/app/ng2/components/inputs-table/confirmation-delete-input/confirmation-delete-input.component.ts new file mode 100644 index 0000000000..24c37b5636 --- /dev/null +++ b/catalog-ui/src/app/ng2/components/inputs-table/confirmation-delete-input/confirmation-delete-input.component.ts @@ -0,0 +1,38 @@ +/** + * Created by rc2122 on 6/1/2017. + */ +import {Component, Output, EventEmitter, ViewChild} from "@angular/core"; +import {ButtonsModelMap, ButtonModel} from "app/models/button"; +import {ModalComponent} from "app/ng2/components/modal/modal.component"; + +@Component({ + selector: 'confirm-delete-input', + templateUrl: './confirmation-delete-input.component.html' +}) +export class ConfirmationDeleteInputComponent { + + @Output() deleteInput: EventEmitter<any> = new EventEmitter<any>(); + @ViewChild ('confirmationModal') confirmationModal:ModalComponent; + footerButtons:ButtonsModelMap = {}; + + constructor (){ + } + + ngOnInit() { + this.footerButtons['Delete'] = new ButtonModel('Delete', 'blue', this.onDeleteInput); + this.footerButtons['Close'] = new ButtonModel('Close', 'grey', this.closeModal); + } + + onDeleteInput = (input) => { + this.deleteInput.emit(input); + this.closeModal(); + }; + + openModal = () => { + this.confirmationModal.open(); + } + + closeModal = () => { + this.confirmationModal.close(); + } +} diff --git a/catalog-ui/src/app/ng2/components/inputs-table/inputs-table.component.html b/catalog-ui/src/app/ng2/components/inputs-table/inputs-table.component.html new file mode 100644 index 0000000000..5467c94de7 --- /dev/null +++ b/catalog-ui/src/app/ng2/components/inputs-table/inputs-table.component.html @@ -0,0 +1,46 @@ +<div class="properties-table"> + <loader [display]="isLoading" size="large" [relative]="false"></loader> + <div class="table-header"> + <div class="table-cell col1">Property Name</div> + <div class="table-cell col2">Type</div> + <div class="table-cell col3">ES</div> + <div class="table-cell valueCol">Value</div> + </div> + <div class="table-body"> + <div class="no-data" *ngIf="!inputs || !inputs.length">No data to display</div> + <div> + <div class="table-row" *ngFor="let input of inputs" (click)="selectedInputId = input.path" [ngClass]="{'selected': selectedInputId && selectedInputId === input.path}"> + <div class="table-cell col1"> + <div class="inner-cell-div" tooltip="{{input.name}}"><span class="property-name">{{input.name}}</span></div> + <span *ngIf="input.description" + class="property-description-icon sprite-new show-desc" + tooltip="{{input.description}}" tooltipDelay="0"></span> + </div> + <div class="table-cell col2"> + <div class="inner-cell-div" tooltip="{{input.type | contentAfterLastDot}}"> + <span>{{input.type | contentAfterLastDot}}</span> + </div> + </div> + <div class="table-cell col3">{{input.schema && input.schema.property && input.schema.property.type ? (input.schema.property.type | contentAfterLastDot) : ''}}</div> + <div class="table-cell valueCol input-value-col" [class.inner-table-container]="input.childrenProperties || !input.isSimpleType"> + <dynamic-element class="value-input" + *ngIf="input.isSimpleType" + pattern="validationUtils.getValidationPattern(input.type)" + [(value)]="input.defaultValue" + [type]="input.type" + [name]="input.name" + (change)="onInputValueChanged(input);" + [readonly]="readonly"> + </dynamic-element> + <div class="delete-button-container"> + <span *ngIf="!input.ownerId && !readonly" class="sprite-new delete-btn" (click)="openDeleteModal(input)"></span> + </div> + </div> + + </div> + </div> + </div> +</div> +<confirm-delete-input #deleteInputConfirmation (deleteInput)="onDeleteInput()"></confirm-delete-input> + + diff --git a/catalog-ui/src/app/ng2/components/inputs-table/inputs-table.component.less b/catalog-ui/src/app/ng2/components/inputs-table/inputs-table.component.less new file mode 100644 index 0000000000..96d4d0a4eb --- /dev/null +++ b/catalog-ui/src/app/ng2/components/inputs-table/inputs-table.component.less @@ -0,0 +1,164 @@ + +@import './../../../../assets/styles/variables.less'; + +:host /deep/ input { width:100%;} + +.properties-table { + display:flex; + flex-direction:column; + flex: 1; + height:100%; + text-align:left; + + .inner-cell-div{ + width: 100%; + text-overflow: ellipsis; + overflow: hidden; + height: 20px; + } + + + .table-header { + font-weight:bold; + border-top: #d2d2d2 solid 1px; + background-color: #eaeaea; + color:#191919; + + .table-cell { + font-size: 14px; + } + .valueCol { + justify-content: flex-start; + padding: 10px; + } + } + .table-header, .table-row { + display: flex; + flex-direction:row; + flex: 0 0 auto; + } + + .table-body { + display:flex; + flex-direction: column; + overflow-y:auto; + flex: 1; + + .no-data { + border: #d2d2d2 solid 1px; + border-top:none; + text-align: center; + height: 100%; + padding: 20px; + } + /deep/.selected{ + background-color: #e6f6fb; + color: #009fdb; + } + } + + .table-row { + &:hover { + background-color:#f8f8f8; cursor:pointer; + } + + &:last-child { + flex: 1 0 auto; + } + .selected-row { + background-color:#e6f6fb; + } + } + + .table-cell { + font-size:13px; + flex:1; + border: #d2d2d2 solid 1px; + border-right:none; + border-top:none; + padding: 10px; + text-overflow: ellipsis; + white-space: nowrap; + + + &:last-child { + border-right:#d2d2d2 solid 1px; + } + &.col1 { + flex: 0 0 300px; + max-width:300px; + display: flex; + justify-content: space-between; + + .property-name { + flex: 1; + } + + .property-description-icon { + float: right; + margin-top: 4px; + margin-left: 5px; + flex: 0 0 auto; + } + } + &.col2 { + flex: 0 0 150px; + max-width:150px; + } + + &.col3 { + flex:0 0 120px; + max-width:120px; + } + + &.valueCol { + flex: 1 0 auto; + min-width: 350px; + display: flex; + justify-content: flex-end; + padding: 0px; + align-items: center; + + .value-input { + flex: 1; + max-height: 24px; + border: none; + background-color: inherit; + + &:focus, &:active { + border:none; + outline:none; + } + } + + .delete-btn { + flex: 0 0 auto; + } + + .delete-button-container { + max-height: 24px; + } + + &.inner-table-container { + padding: 0px; + + .delete-button-container { + padding: 3px 5px 0 0 ; + } + } + } + + &.input-value-col { + padding: 8px; + } + + + } + + .filtered { + /deep/ .checkbox-label-content{ + background-color: yellow; + } + } + +} diff --git a/catalog-ui/src/app/ng2/components/inputs-table/inputs-table.component.ts b/catalog-ui/src/app/ng2/components/inputs-table/inputs-table.component.ts new file mode 100644 index 0000000000..83c0bda991 --- /dev/null +++ b/catalog-ui/src/app/ng2/components/inputs-table/inputs-table.component.ts @@ -0,0 +1,41 @@ +/** + * Created by rc2122 on 5/4/2017. + */ +import {Component, Input, Output, EventEmitter, ViewChild} from "@angular/core"; +import {InputFEModel} from "app/models"; +import {ConfirmationDeleteInputComponent} from "./confirmation-delete-input/confirmation-delete-input.component"; + +@Component({ + selector: 'inputs-table', + templateUrl: './inputs-table.component.html', + styleUrls: ['../inputs-table/inputs-table.component.less'] +}) +export class InputsTableComponent { + + @Input() inputs: Array<InputFEModel>; + @Input() readonly:boolean; + @Input() isLoading:boolean; + @Output() inputValueChanged: EventEmitter<any> = new EventEmitter<any>(); + @Output() deleteInput: EventEmitter<any> = new EventEmitter<any>(); + @ViewChild ('deleteInputConfirmation') deleteInputConfirmation:ConfirmationDeleteInputComponent; + + selectedInputToDelete:InputFEModel; + + constructor (){ + } + + onInputValueChanged = (input) => { + this.inputValueChanged.emit(input); + }; + + onDeleteInput = () => { + this.deleteInput.emit(this.selectedInputToDelete); + }; + + openDeleteModal = (input:InputFEModel) => { + this.selectedInputToDelete = input; + this.deleteInputConfirmation.openModal(); + } +} + + diff --git a/catalog-ui/src/app/ng2/components/loader/loader.component.html b/catalog-ui/src/app/ng2/components/loader/loader.component.html new file mode 100644 index 0000000000..0e13cee674 --- /dev/null +++ b/catalog-ui/src/app/ng2/components/loader/loader.component.html @@ -0,0 +1,5 @@ +<div *ngIf="display" data-tests-id="tlv-loader"> + <div class="tlv-loader-back" [ngClass]="{'tlv-loader-relative':relative}"></div> + <div class="tlv-loader {{size}}"></div> +</div> + diff --git a/catalog-ui/src/app/ng2/components/loader/loader.component.less b/catalog-ui/src/app/ng2/components/loader/loader.component.less new file mode 100644 index 0000000000..054b6021a1 --- /dev/null +++ b/catalog-ui/src/app/ng2/components/loader/loader.component.less @@ -0,0 +1,75 @@ +@import '../../../../assets/styles/variables'; +.tlv-loader-back { + background-color: @main_color_p; + position: fixed; + top: 50px; + left: 0; + right: 0; + bottom: 0; + z-index: 9999; + opacity: 0.5; +} + +.tlv-loader-relative { position: absolute; top: 0;} + +.tlv-loader { + z-index: 10002; +} + +@keyframes fadein { + from { opacity: 0; } + to { opacity: 0.8; } +} + +/* Firefox < 16 */ +@-moz-keyframes fadein { + from { opacity: 0; } + to { opacity: 0.8; } +} + +/* Safari, Chrome and Opera > 12.1 */ +@-webkit-keyframes fadein { + from { opacity: 0; } + to { opacity: 0.8; } +} + +/* Internet Explorer */ +@-ms-keyframes fadein { + from { opacity: 0; } + to { opacity: 0.8; } +} + +/* Opera < 12.1 */ +@-o-keyframes fadein { + from { opacity: 0; } + to { opacity: 0.8; } +} + +@keyframes fadeout { + from { opacity: 0.8; } + to { opacity: 0; } +} + +/* Firefox < 16 */ +@-moz-keyframes fadeout { + from { opacity: 0.8; } + to { opacity: 0; } +} + +/* Safari, Chrome and Opera > 12.1 */ +@-webkit-keyframes fadeout { + from { opacity: 0.8; } + to { opacity: 0; } +} + +/* Internet Explorer */ +@-ms-keyframes fadeout { + from { opacity: 0.8; } + to { opacity: 0; } +} + +/* Opera < 12.1 */ +@-o-keyframes fadeout { + from { opacity: 0.8; } + to { opacity: 0; } +} diff --git a/catalog-ui/src/app/ng2/components/loader/loader.component.ts b/catalog-ui/src/app/ng2/components/loader/loader.component.ts new file mode 100644 index 0000000000..4af92eca24 --- /dev/null +++ b/catalog-ui/src/app/ng2/components/loader/loader.component.ts @@ -0,0 +1,74 @@ +/** + * Created by rc2122 on 6/6/2017. + */ +import {Component, Input, ElementRef, Renderer, SimpleChanges} from "@angular/core"; +@Component({ + selector: 'loader', + templateUrl: './loader.component.html', + styleUrls: ['./loader.component.less'] +}) +export class LoaderComponent { + + @Input() display:boolean; + @Input() size:string;// small || medium || large + @Input() relative: boolean; + @Input() elementSelector: string; // optional. If is relative is set to true, option to pass in element that loader should be relative to. Otherwise, will be relative to parent element. + + + constructor (private el: ElementRef, private renderer: Renderer){ + } + + ngOnInit() { + if (!this.size) { + this.size = 'large'; + } + if (this.display === true) { + this.changeLoaderDisplay(true); + } + } + + ngOnChanges(changes: SimpleChanges) { + if(changes.display){ + if (this.display) { + this.changeLoaderDisplay(false); //display is set to true, so loader will appear unless we explicitly tell it not to. + window.setTimeout((): void => { + this.display && this.changeLoaderDisplay(true); //only show loader if we still need to display it. + }, 500); + } else { + window.setTimeout(():void => { + this.changeLoaderDisplay(false); + }, 0); + } + } + } + + changeLoaderDisplay = (display: boolean): void => { + if (display) { + this.calculateLoaderPosition(); + this.renderer.setElementStyle(this.el.nativeElement, 'display', 'block'); + } else { + this.renderer.setElementStyle(this.el.nativeElement, 'display', 'none'); + } + } + + calculateLoaderPosition = () => { + if (this.relative === true) { // Can change the parent position to relative without causing style issues. + let parent = (this.elementSelector) ? angular.element(this.elementSelector).get(0) : this.el.nativeElement.parentElement; + this.renderer.setElementStyle(parent, 'position', 'relative'); + this.setLoaderPosition(0, 0); //will be relative to parent and appear over specified element + //TODO: DONT force parent to have position relative; set inner div's style instead of outer element + // let parentPos: ClientRect = this.el.nativeElement.parentElement.getBoundingClientRect(); + // this.setLoaderPosition(parentPos.top, parentPos.left, parentPos.width, parentPos.height); + } else { + this.setLoaderPosition(0, 0); //will appear over whole page + } + } + + setLoaderPosition = (top:number, left:number, width?:number, height?:number): void => { + this.renderer.setElementStyle(this.el.nativeElement, 'position', 'absolute'); + this.renderer.setElementStyle(this.el.nativeElement, 'top', top? top.toString() : "0"); + this.renderer.setElementStyle(this.el.nativeElement, 'left', left? left.toString() : "0"); + this.renderer.setElementStyle(this.el.nativeElement, 'width', width? width.toString() : "100%"); + this.renderer.setElementStyle(this.el.nativeElement, 'height', height? height.toString() : "100%"); + }; +} diff --git a/catalog-ui/src/app/ng2/components/modal/modal.component.html b/catalog-ui/src/app/ng2/components/modal/modal.component.html new file mode 100644 index 0000000000..4882449596 --- /dev/null +++ b/catalog-ui/src/app/ng2/components/modal/modal.component.html @@ -0,0 +1,18 @@ +<div class="custom-modal {{size}}"> + <div class="ng2-modal-content"> + <div class="ng2-modal-header"> + <span class="title">{{ title }}</span> + <span class="close-button" (click)="close()"></span> + </div> + <div class="ng2-modal-body"> + <ng-content></ng-content> + </div> + <div class="ng2-modal-footer"> + <button *ngFor="let buttonName of buttonsNames" + class="tlv-btn {{buttons[buttonName].cssClass}}" + [disabled] = "buttons[buttonName].getDisabled && buttons[buttonName].getDisabled()" + (click) = "buttons[buttonName].callback()">{{buttons[buttonName].text}}</button> + </div> + </div> +</div> +<div class="modal-background"></div> diff --git a/catalog-ui/src/app/ng2/components/modal/modal.component.less b/catalog-ui/src/app/ng2/components/modal/modal.component.less new file mode 100644 index 0000000000..a35f829e6a --- /dev/null +++ b/catalog-ui/src/app/ng2/components/modal/modal.component.less @@ -0,0 +1,115 @@ +@import '../../../../assets/styles/variables'; +@import '../../../../assets/styles/mixins'; +@import '../../../../assets/styles/sprite-old'; +/deep/ modal { + display: none; + + .custom-modal { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 1000; + overflow: auto; + margin: auto; + display: flex; + align-items: center; + + .ng2-modal-content { + background: #fff; + width: 100%; + box-shadow: 0 5px 15px rgba(0,0,0,.5); + border-radius: 4px; + .ng2-modal-body{ + padding: 20px; + } + + .ng2-modal-header{ + .m_18_m; + font-weight: bold; + -webkit-box-flex: 1; + -ms-flex-positive: 1; + flex-grow: 1; + height: 50px; + line-height: 50px; + display: -webkit-box; + display: -ms-flexbox; + display: flex; + text-align: left; + border-bottom: solid 1px @main_color_o; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + margin: 0px 20px; + .title{ + -webkit-box-flex: 999; + -ms-flex-positive: 999; + flex-grow: 999; + } + .close-button{ + .sprite; + .sprite.x-btn-black; + cursor: pointer; + } + } + + .ng2-modal-footer{ + background-color: @tlv_color_t; + padding: 17px 30px; + clear: both; + -webkit-box-flex: 1; + -ms-flex-positive: 1; + flex-grow: 1; + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: end; + -ms-flex-pack: end; + justify-content: flex-end; + border-radius: 4px; + button{ + margin: 0 12px 0 6px; + } + } + } + } + + .modal-background { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + background-color: #000; + opacity: 0.5; + z-index: 900; + } +} + +.xl { + width: 1200px; +} + +.l { + width: 875px; +} + +.md { + width: 650px; +} + +.sm { + width: 552px; +} + +.xsm { + width: 432px; +} + +body.modal-open { + overflow: hidden; +} diff --git a/catalog-ui/src/app/ng2/components/modal/modal.component.ts b/catalog-ui/src/app/ng2/components/modal/modal.component.ts new file mode 100644 index 0000000000..4a00871b21 --- /dev/null +++ b/catalog-ui/src/app/ng2/components/modal/modal.component.ts @@ -0,0 +1,46 @@ +/** + * Created by rc2122 on 6/1/2017. + */ +import { Component, ElementRef, Input, OnInit, OnDestroy } from '@angular/core'; +import * as $ from 'jquery'; +import {ButtonsModelMap} from "app/models/button"; + +@Component({ + selector: 'modal', + templateUrl: './modal.component.html', + styleUrls:['modal.component.less'] +}) + +export class ModalComponent implements OnInit, OnDestroy { + @Input() size: string; 'xl|l|md|sm|xsm' + @Input() title: string; + @Input() public buttons:ButtonsModelMap; + private modalElement: JQuery; + private buttonsNames:Array<string>; + + constructor( el: ElementRef ) { + this.modalElement = $(el.nativeElement); + } + + ngOnInit(): void { + let modal = this; + this.modalElement.appendTo('body'); + if(this.buttons){ + this.buttonsNames = Object.keys(this.buttons); + } + } + + ngOnDestroy(): void { + this.modalElement.remove(); + } + + open(): void { + this.modalElement.show(); + $('body').addClass('modal-open'); + } + + close(): void { + this.modalElement.hide(); + $('body').removeClass('modal-open'); + } +} diff --git a/catalog-ui/src/app/ng2/components/popover/popover-content.component.html b/catalog-ui/src/app/ng2/components/popover/popover-content.component.html new file mode 100644 index 0000000000..6d76f0ad06 --- /dev/null +++ b/catalog-ui/src/app/ng2/components/popover/popover-content.component.html @@ -0,0 +1,24 @@ +<div #popoverDiv class="popover {{ effectivePlacement }}" + [style.top]="top + 'px'" + [style.left]="left + 'px'" + [class.in]="isIn" + [class.fade]="animation" + style="display: block" + role="popover" + [ngClass]="{'hide-arrow':hideArrow}"> + <div [hidden]="!closeOnMouseOutside" class="virtual-area"></div> + <div class="arrow" *ngIf="!hideArrow"></div> + <div class="popover-header"> + <span class="title">{{ title }}</span> + <span class="close-button" (click)="popover.hide()"></span> + </div> + <ng-content></ng-content> + <div class="popover-footer"> + <button *ngFor="let buttonName of buttonsNames" + class="tlv-btn {{buttons[buttonName].cssClass}}" + [disabled] = "buttons[buttonName].getDisabled && buttons[buttonName].getDisabled()" + (click) = "buttons[buttonName].callback()">{{buttons[buttonName].text}}</button> + </div> +</div> + + diff --git a/catalog-ui/src/app/ng2/components/popover/popover-content.component.less b/catalog-ui/src/app/ng2/components/popover/popover-content.component.less new file mode 100644 index 0000000000..f7b62e91f7 --- /dev/null +++ b/catalog-ui/src/app/ng2/components/popover/popover-content.component.less @@ -0,0 +1,73 @@ +@import '../../../../assets/styles/variables'; +@import '../../../../assets/styles/mixins'; +@import '../../../../assets/styles/sprite-old'; +.popover .virtual-area { + height: 11px; + width: 100%; + position: absolute; +} +.popover.top .virtual-area { + bottom: -11px; +} +.popover.bottom .virtual-area { + top: -11px; +} +.popover.left .virtual-area { + right: -11px; +} +.popover.right .virtual-area { + left: -11px; +} +.popover.hide-arrow{ + margin: 0; +} + +.popover-header{ + .m_14_m; + font-weight: bold; + -webkit-box-flex: 1; + -ms-flex-positive: 1; + flex-grow: 1; + height: 40px; + line-height: 48px; + display: -webkit-box; + display: -ms-flexbox; + display: flex; + text-align: left; + border-bottom: solid 1px @main_color_o; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + margin: 0px 20px; + .title{ + -webkit-box-flex: 999; + -ms-flex-positive: 999; + flex-grow: 999; + } + .close-button{ + .sprite; + .sprite.x-btn-black; + cursor: pointer; + } +} + +.popover-footer{ + background-color: @tlv_color_t; + height: 40px; + clear: both; + -webkit-box-flex: 1; + -ms-flex-positive: 1; + flex-grow: 1; + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: end; + -ms-flex-pack: end; + justify-content: flex-end; + button{ + margin: 8px 12px 8px 6px; + } +} diff --git a/catalog-ui/src/app/ng2/components/popover/popover-content.component.ts b/catalog-ui/src/app/ng2/components/popover/popover-content.component.ts new file mode 100644 index 0000000000..c4489f59b9 --- /dev/null +++ b/catalog-ui/src/app/ng2/components/popover/popover-content.component.ts @@ -0,0 +1,258 @@ +import {Component, Input, Output, AfterViewInit, ElementRef, ChangeDetectorRef, OnDestroy, ViewChild, EventEmitter, Renderer } from "@angular/core"; +import {ButtonsModelMap} from "app/models"; +import {PopoverComponent} from "./popover.component"; + +@Component({ + selector: "popover-content", + templateUrl:'./popover-content.component.html', + styleUrls:['popover-content.component.less'] +}) +export class PopoverContentComponent implements AfterViewInit, OnDestroy { + + @Input() public title: string; + @Input() public buttons:ButtonsModelMap; + + @Input() + content: string; + + @Input() + placement: "top"|"bottom"|"left"|"right"|"auto"|"auto top"|"auto bottom"|"auto left"|"auto right" = "bottom"; + + @Input() + animation: boolean = true; + + @Input() + closeOnClickOutside: boolean = false; + + @Input() + closeOnMouseOutside: boolean = false; + + @Input() + hideArrow: boolean = false; + + @ViewChild("popoverDiv") + popoverDiv: ElementRef; + + buttonsNames:Array<string>; + + popover: PopoverComponent; + onCloseFromOutside = new EventEmitter(); + top: number = -10000; + left: number = -10000; + isIn: boolean = false; + displayType: string = "none"; + effectivePlacement: string; + + onDocumentMouseDown = (event: any) => { + const element = this.element.nativeElement; + if (!element || !this.popover) return; + if (element.contains(event.target) || this.popover.getElement().contains(event.target)) return; + this.hide(); + this.onCloseFromOutside.emit(undefined); + }; + + + constructor(protected element: ElementRef, + protected cdr: ChangeDetectorRef, + protected renderer: Renderer) { + } + + listenClickFunc: any; + listenMouseFunc: any; + + ngAfterViewInit(): void { + this.buttonsNames = Object.keys(this.buttons); + if (this.closeOnClickOutside) + this.listenClickFunc = this.renderer.listenGlobal("document", "mousedown", (event: any) => this.onDocumentMouseDown(event)); + if (this.closeOnMouseOutside) + this.listenMouseFunc = this.renderer.listenGlobal("document", "mouseover", (event: any) => this.onDocumentMouseDown(event)); + + this.show(); + this.cdr.detectChanges(); + } + + ngOnDestroy() { + if (this.closeOnClickOutside) + this.listenClickFunc(); + if (this.closeOnMouseOutside) + this.listenMouseFunc(); + } + + // ------------------------------------------------------------------------- + // Public Methods + // ------------------------------------------------------------------------- + + show(): void { + if (!this.popover || !this.popover.getElement()) + return; + + const p = this.positionElements(this.popover.getElement(), this.popoverDiv.nativeElement, this.placement); + this.displayType = "block"; + this.top = p.top; + this.left = p.left; + this.isIn = true; + } + + hide(): void { + this.top = -10000; + this.left = -10000; + this.isIn = true; + this.popover.hide(); + } + + hideFromPopover() { + this.top = -10000; + this.left = -10000; + this.isIn = true; + } + + // ------------------------------------------------------------------------- + // Protected Methods + // ------------------------------------------------------------------------- + + protected positionElements(hostEl: HTMLElement, targetEl: HTMLElement, positionStr: string, appendToBody: boolean = false): { top: number, left: number } { + let positionStrParts = positionStr.split("-"); + let pos0 = positionStrParts[0]; + let pos1 = positionStrParts[1] || "center"; + let hostElPos = appendToBody ? this.offset(hostEl) : this.position(hostEl); + let targetElWidth = targetEl.offsetWidth; + let targetElHeight = targetEl.offsetHeight; + + this.effectivePlacement = pos0 = this.getEffectivePlacement(pos0, hostEl, targetEl); + + let shiftWidth: any = { + center: function (): number { + return hostElPos.left + hostElPos.width / 2 - targetElWidth / 2; + }, + left: function (): number { + return hostElPos.left; + }, + right: function (): number { + return hostElPos.left + hostElPos.width - targetElWidth; + } + }; + + let shiftHeight: any = { + center: function (): number { + return hostElPos.top + hostElPos.height / 2 - targetElHeight / 2; + }, + top: function (): number { + return hostElPos.top; + }, + bottom: function (): number { + return hostElPos.top + hostElPos.height - targetElHeight; + } + }; + + let targetElPos: { top: number, left: number }; + switch (pos0) { + case "right": + targetElPos = { + top: shiftHeight[pos1](), + left: hostElPos.left + hostElPos.width + }; + break; + + case "left": + targetElPos = { + top: shiftHeight[pos1](), + left: hostElPos.left - targetElWidth + }; + break; + + case "bottom": + targetElPos = { + top: hostElPos.top + hostElPos.height, + left: shiftWidth[pos1]() + }; + break; + + default: + targetElPos = { + top: hostElPos.top - targetElHeight, + left: shiftWidth[pos1]() + }; + break; + } + + return targetElPos; + } + + protected position(nativeEl: HTMLElement): { width: number, height: number, top: number, left: number } { + let offsetParentBCR = { top: 0, left: 0 }; + const elBCR = this.offset(nativeEl); + const offsetParentEl = this.parentOffsetEl(nativeEl); + if (offsetParentEl !== window.document) { + offsetParentBCR = this.offset(offsetParentEl); + offsetParentBCR.top += offsetParentEl.clientTop - offsetParentEl.scrollTop; + offsetParentBCR.left += offsetParentEl.clientLeft - offsetParentEl.scrollLeft; + } + + const boundingClientRect = nativeEl.getBoundingClientRect(); + return { + width: boundingClientRect.width || nativeEl.offsetWidth, + height: boundingClientRect.height || nativeEl.offsetHeight, + top: elBCR.top - offsetParentBCR.top, + left: elBCR.left - offsetParentBCR.left + }; + } + + protected offset(nativeEl: any): { width: number, height: number, top: number, left: number } { + const boundingClientRect = nativeEl.getBoundingClientRect(); + return { + width: boundingClientRect.width || nativeEl.offsetWidth, + height: boundingClientRect.height || nativeEl.offsetHeight, + top: boundingClientRect.top + (window.pageYOffset || window.document.documentElement.scrollTop), + left: boundingClientRect.left + (window.pageXOffset || window.document.documentElement.scrollLeft) + }; + } + + protected getStyle(nativeEl: HTMLElement, cssProp: string): string { + if ((nativeEl as any).currentStyle) // IE + return (nativeEl as any).currentStyle[cssProp]; + + if (window.getComputedStyle) + return (window.getComputedStyle as any)(nativeEl)[cssProp]; + + // finally try and get inline style + return (nativeEl.style as any)[cssProp]; + } + + protected isStaticPositioned(nativeEl: HTMLElement): boolean { + return (this.getStyle(nativeEl, "position") || "static" ) === "static"; + } + + protected parentOffsetEl(nativeEl: HTMLElement): any { + let offsetParent: any = nativeEl.offsetParent || window.document; + while (offsetParent && offsetParent !== window.document && this.isStaticPositioned(offsetParent)) { + offsetParent = offsetParent.offsetParent; + } + return offsetParent || window.document; + } + + protected getEffectivePlacement(placement: string, hostElement: HTMLElement, targetElement: HTMLElement): string { + const placementParts = placement.split(" "); + if (placementParts[0] !== "auto") { + return placement; + } + + const hostElBoundingRect = hostElement.getBoundingClientRect(); + + const desiredPlacement = placementParts[1] || "bottom"; + + if (desiredPlacement === "top" && hostElBoundingRect.top - targetElement.offsetHeight < 0) { + return "bottom"; + } + if (desiredPlacement === "bottom" && hostElBoundingRect.bottom + targetElement.offsetHeight > window.innerHeight) { + return "top"; + } + if (desiredPlacement === "left" && hostElBoundingRect.left - targetElement.offsetWidth < 0) { + return "right"; + } + if (desiredPlacement === "right" && hostElBoundingRect.right + targetElement.offsetWidth > window.innerWidth) { + return "left"; + } + + return desiredPlacement; + } +} diff --git a/catalog-ui/src/app/ng2/components/popover/popover.component.ts b/catalog-ui/src/app/ng2/components/popover/popover.component.ts new file mode 100644 index 0000000000..a7e2881b29 --- /dev/null +++ b/catalog-ui/src/app/ng2/components/popover/popover.component.ts @@ -0,0 +1,159 @@ +import { Directive, HostListener, ComponentRef, ViewContainerRef, ComponentFactoryResolver, ComponentFactory, Input, OnChanges, SimpleChange, Output, EventEmitter } from "@angular/core"; +import {PopoverContentComponent} from "./popover-content.component"; + +@Directive({ + selector: "[popover]", + exportAs: "popover" +}) +export class PopoverComponent implements OnChanges { + + protected PopoverComponent = PopoverContentComponent; + protected popover: ComponentRef<PopoverContentComponent>; + protected visible: boolean; + + + constructor(protected viewContainerRef: ViewContainerRef, + protected resolver: ComponentFactoryResolver) { + } + + @Input("popover") + content: string|PopoverContentComponent; + + @Input() + popoverDisabled: boolean; + + @Input() + popoverAnimation: boolean; + + @Input() + popoverPlacement: "top"|"bottom"|"left"|"right"|"auto"|"auto top"|"auto bottom"|"auto left"|"auto right"; + + @Input() + popoverTitle: string; + + @Input() + popoverOnHover: boolean = false; + + @Input() + popoverCloseOnClickOutside: boolean; + + @Input() + popoverCloseOnMouseOutside: boolean; + + @Input() + popoverDismissTimeout: number = 0; + + @Output() + onShown = new EventEmitter<PopoverComponent>(); + + @Output() + onHidden = new EventEmitter<PopoverComponent>(); + + @HostListener("click") + showOrHideOnClick(): void { + if (this.popoverOnHover) return; + if (this.popoverDisabled) return; + this.toggle(); + } + + @HostListener("focusin") + @HostListener("mouseenter") + showOnHover(): void { + if (!this.popoverOnHover) return; + if (this.popoverDisabled) return; + this.show(); + } + + @HostListener("focusout") + @HostListener("mouseleave") + hideOnHover(): void { + if (this.popoverCloseOnMouseOutside) return; + if (!this.popoverOnHover) return; + if (this.popoverDisabled) return; + this.hide(); + } + + ngOnChanges(changes: {[propertyName: string]: SimpleChange}) { + if (changes["popoverDisabled"]) { + if (changes["popoverDisabled"].currentValue) { + this.hide(); + } + } + } + + toggle() { + if (!this.visible) { + this.show(); + } else { + this.hide(); + } + } + + show() { + if (this.visible) return; + + this.visible = true; + if (typeof this.content === "string") { + const factory = this.resolver.resolveComponentFactory(this.PopoverComponent); + if (!this.visible) + return; + + this.popover = this.viewContainerRef.createComponent(factory); + const popover = this.popover.instance as PopoverContentComponent; + popover.popover = this; + popover.content = this.content as string; + if (this.popoverPlacement !== undefined) + popover.placement = this.popoverPlacement; + if (this.popoverAnimation !== undefined) + popover.animation = this.popoverAnimation; + if (this.popoverTitle !== undefined) + popover.title = this.popoverTitle; + if (this.popoverCloseOnClickOutside !== undefined) + popover.closeOnClickOutside = this.popoverCloseOnClickOutside; + if (this.popoverCloseOnMouseOutside !== undefined) + popover.closeOnMouseOutside = this.popoverCloseOnMouseOutside; + + popover.onCloseFromOutside.subscribe(() => this.hide()); + if (this.popoverDismissTimeout > 0) + setTimeout(() => this.hide(), this.popoverDismissTimeout); + } else { + const popover = this.content as PopoverContentComponent; + popover.popover = this; + if (this.popoverPlacement !== undefined) + popover.placement = this.popoverPlacement; + if (this.popoverAnimation !== undefined) + popover.animation = this.popoverAnimation; + if (this.popoverTitle !== undefined) + popover.title = this.popoverTitle; + if (this.popoverCloseOnClickOutside !== undefined) + popover.closeOnClickOutside = this.popoverCloseOnClickOutside; + if (this.popoverCloseOnMouseOutside !== undefined) + popover.closeOnMouseOutside = this.popoverCloseOnMouseOutside; + + popover.onCloseFromOutside.subscribe(() => this.hide()); + if (this.popoverDismissTimeout > 0) + setTimeout(() => this.hide(), this.popoverDismissTimeout); + popover.show(); + } + + this.onShown.emit(this); + } + + hide() { + if (!this.visible) return; + + this.visible = false; + if (this.popover) + this.popover.destroy(); + + if (this.content instanceof PopoverContentComponent) + (this.content as PopoverContentComponent).hideFromPopover(); + + this.onHidden.emit(this); + } + + getElement() { + return this.viewContainerRef.element.nativeElement; + } + +} diff --git a/catalog-ui/src/app/ng2/components/popover/popover.module.ts b/catalog-ui/src/app/ng2/components/popover/popover.module.ts new file mode 100644 index 0000000000..4bd8426ce1 --- /dev/null +++ b/catalog-ui/src/app/ng2/components/popover/popover.module.ts @@ -0,0 +1,27 @@ +/** + * Created by rc2122 on 5/17/2017. + */ +import {NgModule} from "@angular/core"; +import { CommonModule } from '@angular/common'; +import {PopoverComponent} from "./popover.component"; +import {PopoverContentComponent} from "./popover-content.component"; + +@NgModule({ + declarations: [ + PopoverComponent, + PopoverContentComponent + ], + imports: [ + // PopoverComponent, + // PopoverContentComponent + CommonModule + ], + exports: [ + PopoverComponent, + PopoverContentComponent + ], + providers: [] +}) +export class PopoverModule { + +} diff --git a/catalog-ui/src/app/ng2/components/properties-table/dynamic-property/dynamic-property.component.html b/catalog-ui/src/app/ng2/components/properties-table/dynamic-property/dynamic-property.component.html new file mode 100644 index 0000000000..5aa0052cc3 --- /dev/null +++ b/catalog-ui/src/app/ng2/components/properties-table/dynamic-property/dynamic-property.component.html @@ -0,0 +1,65 @@ +<div *ngIf="!property.hidden" class="dynamic-property-row nested-level-{{nestedLevel}}" [@fadeIn] + [ngClass]="{'selected': selectedPropertyId && selectedPropertyId === property.propertiesName }" + [class.with-top-border]="property.isChildOfListOrMap" + (click)="onClickPropertyRow(property, $event)"> + <!-- LEFT CELL --> + <ng-container *ngIf="!isPropertyFEModel"> + <div class="table-cell" *ngIf="canBeDeclared" [ngClass]="{'filtered':property.name === propertyNameSearchText}" [class.round-checkbox]="property.isDeclared"> <!-- simple children of complex type [@checkEffect]="property.isDeclared"--> + <checkbox [(checked)]="property.isSelected" [disabled]="property.isDisabled ||property.isDeclared || readonly" (checkedChange)="checkProperty.emit(property.propertiesName)" ></checkbox> + <div class="inner-cell-div" tooltip="{{property.name}}"><span>{{property.name}}</span></div> + </div> + <div class="table-cell" *ngIf="!canBeDeclared && !property.isChildOfListOrMap">{{property.name}}</div> <!-- simple children of complex type within map or list --> + <div class="table-cell map-entry" *ngIf="property.isChildOfListOrMap && propType == derivedPropertyTypes.MAP"><!-- map left cell --> + <input [value]="property.mapKey" #mapKey (change)="mapKeyChanged.emit(mapKey.value)" [readonly]="readonly" type="text" [ngClass]="{'disabled':readonly}" /> + </div> + </ng-container> + <!-- RIGHT CELL OR FULL WIDTH CELL--> + <ng-container *ngIf="propType == derivedPropertyTypes.SIMPLE || property.isDeclared || (property.isChildOfListOrMap && propType == derivedPropertyTypes.MAP && property.schema.property.isSimpleType)"> + <div class="table-cell"> + <dynamic-element class="value-input" + pattern="validationUtils.getValidationPattern(property.type)" + [(value)]="property.valueObj" + [type]="property.isDeclared ? 'string' : property.type" + [name]="property.name" + [path]="property.propertiesName" + (valueChange)="valueChanged.emit();" + [readonly]="readonly||property.isDeclared||property.isDisabled" + ></dynamic-element> + </div> + </ng-container> + <ng-container *ngIf="!isPropertyFEModel && propType != derivedPropertyTypes.SIMPLE && !property.isDeclared"> <!-- right cell for complex elements, or list complex --> + <div class="table-cell" *ngIf="propType == derivedPropertyTypes.COMPLEX">{{property.type | contentAfterLastDot }}</div> + <div class="table-cell" *ngIf="propType == derivedPropertyTypes.MAP && !property.schema.property.isSimpleType">{{property.schema.property.type | contentAfterLastDot }}</div> + </ng-container> + <ng-container *ngIf="isPropertyFEModel && (propType == derivedPropertyTypes.LIST || propType == derivedPropertyTypes.MAP) && !property.isDeclared"><!-- empty, full-width table cell - for PropertyFEModel of type list or map --> + <div class="table-cell empty"></div> + </ng-container> + <!-- ICONS: add, delete, and expand --> + <ng-container *ngIf="!property.isDeclared"> + <a *ngIf="(propType == derivedPropertyTypes.LIST || propType == derivedPropertyTypes.MAP) && !property.isChildOfListOrMap" class="property-icon add-item" (click)="createNewChildProperty();" [ngClass]="{'disabled':readonly}">Add value to list</a> + <span *ngIf="property.isChildOfListOrMap" (click)="deleteItem.emit(property);" class="property-icon sprite-new delete-item-icon" [ngClass]="{'disabled':readonly}"></span> + <span *ngIf="!isPropertyFEModel && (propType == derivedPropertyTypes.COMPLEX || ((propType == derivedPropertyTypes.LIST || propType == derivedPropertyTypes.MAP) && hasChildren()))" (click)="expandChildById(propPath)" class="property-icon sprite-new round-expand-icon" [class.open]="propPath == expandedChildId"></span> + </ng-container> + +</div> +<!-- FLAT CHILDREN --> +<div class="flat-children-container" *ngIf="isPropertyFEModel && !property.isDeclared"> + <ng-container *ngFor="let prop of property.flattenedChildren | filterChildProperties: expandedChildId; trackBy:prop?.propertiesName"> + <dynamic-property + [selectedPropertyId]="selectedPropertyId" + [canBeDeclared]="prop.canBeDeclared" + [property]="prop" + [expandedChildId]="expandedChildId" + [propertyNameSearchText]="propertyNameSearchText" + [readonly]="readonly" + (valueChanged)="childValueChanged(prop)" + (mapKeyChanged)="removeValueFromParent(prop, $event)" + (expandChild)="expandChildById($event)" + (deleteItem)="deleteListOrMapItem($event)" + (clickOnPropertyRow)="onClickPropertyRow($event)" + (checkProperty)="checkedChange($event)" + (addChildPropsToParent)="addChildProps($event, prop.propertiesName)" + > + </dynamic-property> + </ng-container> +</div> diff --git a/catalog-ui/src/app/ng2/components/properties-table/dynamic-property/dynamic-property.component.less b/catalog-ui/src/app/ng2/components/properties-table/dynamic-property/dynamic-property.component.less new file mode 100644 index 0000000000..4da98ec736 --- /dev/null +++ b/catalog-ui/src/app/ng2/components/properties-table/dynamic-property/dynamic-property.component.less @@ -0,0 +1,67 @@ +.flat-children-container { + .dynamic-property-row { + /*create nested left border classes for up to 10 levels of nesting*/ + .nested-border-loop(@i) when (@i > 0) { + @size: (@i - 1) *2; + &.nested-level-@{i} .table-cell:first-child { + border-left: ~"solid @{size}px #009fdb"; + } + .nested-border-loop(@i - 1) + } + .nested-border-loop(10); + } + dynamic-property { + &:first-child .dynamic-property-row.with-top-border { + border-top:solid 1px #d2d2d2; + } + &:not(:last-child) .dynamic-property-row { + border-bottom:solid 1px #d2d2d2; + } + } +} +.dynamic-property-row { + display:flex; + flex-direction:row; + align-items: stretch; + + .table-cell { + flex: 1; + padding:9px; + justify-content: center; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + + &:first-child { + flex: 0 0 50%; + border-right:#d2d2d2 solid 1px; + &:only-of-type { + flex: 1 1 100%; + border-right:none; + } + } + &.empty { + height:40px; + } + } + .property-icon { + flex: 0 0 auto; + margin-right:10px; + align-self:center; + cursor:pointer; + } + +} + +.filtered { + /deep/ .checkbox-label-content{ + background-color: yellow; + } +} +.inner-cell-div{ + max-width: 100%; + text-overflow: ellipsis; + overflow: hidden; + display: inline; + padding-left: 8px; +}
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/components/properties-table/dynamic-property/dynamic-property.component.ts b/catalog-ui/src/app/ng2/components/properties-table/dynamic-property/dynamic-property.component.ts new file mode 100644 index 0000000000..3713676040 --- /dev/null +++ b/catalog-ui/src/app/ng2/components/properties-table/dynamic-property/dynamic-property.component.ts @@ -0,0 +1,130 @@ +import {Component, Input, Output, EventEmitter} from "@angular/core"; +import { PropertyBEModel, PropertyFEModel, DerivedFEProperty, DerivedPropertyType, SchemaPropertyGroupModel, DataTypeModel } from "app/models"; +import { PROPERTY_DATA, PROPERTY_TYPES } from 'app/utils'; +import { PropertiesUtils } from "app/ng2/pages/properties-assignment/properties.utils"; +import { DataTypeService } from "../../../services/data-type.service"; +import { trigger, state, style, transition, animate } from '@angular/core'; + + +@Component({ + selector: 'dynamic-property', + templateUrl: './dynamic-property.component.html', + styleUrls: ['./dynamic-property.component.less'], + animations: [trigger('fadeIn', [transition(':enter', [style({ opacity: '0' }), animate('.7s ease-out', style({ opacity: '1' }))])])] +}) +export class DynamicPropertyComponent { + + derivedPropertyTypes = DerivedPropertyType; //http://stackoverflow.com/questions/35835984/how-to-use-a-typescript-enum-value-in-an-angular2-ngswitch-statement + propType: DerivedPropertyType; + propPath: string; + isPropertyFEModel: boolean; + nestedLevel: number; + + @Input() canBeDeclared: boolean; + @Input() property: PropertyFEModel | DerivedFEProperty; + @Input() expandedChildId: string; + @Input() selectedPropertyId: string; + @Input() propertyNameSearchText: string; + @Input() readonly: boolean; + + @Output() valueChanged: EventEmitter<any> = new EventEmitter<any>(); + @Output() expandChild: EventEmitter<string> = new EventEmitter<string>(); + @Output() checkProperty: EventEmitter<string> = new EventEmitter<string>(); + @Output() deleteItem: EventEmitter<string> = new EventEmitter<string>(); + @Output() clickOnPropertyRow: EventEmitter<PropertyFEModel | DerivedFEProperty> = new EventEmitter<PropertyFEModel | DerivedFEProperty>(); + @Output() mapKeyChanged: EventEmitter<string> = new EventEmitter<string>(); + @Output() addChildPropsToParent: EventEmitter<Array<DerivedFEProperty>> = new EventEmitter<Array<DerivedFEProperty>>(); + + + constructor(private propertiesUtils: PropertiesUtils, private dataTypeService: DataTypeService) { + } + + ngOnInit() { + this.isPropertyFEModel = this.property instanceof PropertyFEModel; + this.propType = this.property.derivedDataType; + this.propPath = (this.property instanceof PropertyFEModel) ? this.property.name : this.property.propertiesName; + this.nestedLevel = (this.property.propertiesName.match(/#/g) || []).length; + } + + + onClickPropertyRow = (property, event) => { + // Because DynamicPropertyComponent is recrusive second time the event is fire event.stopPropagation = undefined + event && event.stopPropagation && event.stopPropagation(); + this.clickOnPropertyRow.emit(property); + } + + + expandChildById = (id: string) => { + this.expandedChildId = id; + this.expandChild.emit(id); + } + + checkedChange = (propName: string) => { + this.checkProperty.emit(propName); + } + + hasChildren = (): number => { + return (this.property.valueObj && typeof this.property.valueObj == 'object') ? Object.keys(this.property.valueObj).length : 0; + } + + createNewChildProperty = (): void => { + + let newProps: Array<DerivedFEProperty> = this.propertiesUtils.createListOrMapChildren(this.property, "", undefined); + if (this.property instanceof PropertyFEModel) { + this.addChildProps(newProps, this.property.name); + } else { + this.addChildPropsToParent.emit(newProps); + } + } + + addChildProps = (newProps: Array<DerivedFEProperty>, childPropName: string) => { + + if (this.property instanceof PropertyFEModel) { + let insertIndex: number = this.property.getIndexOfChild(childPropName) + this.property.getCountOfChildren(childPropName); //insert after parent prop and existing children + this.property.flattenedChildren.splice(insertIndex, 0, ...newProps); //using ES6 spread operator + this.expandChildById(newProps[0].propertiesName); + } + } + + childValueChanged = (property: DerivedFEProperty) => { //value of child property changed + + if (this.property instanceof PropertyFEModel) { // will always be the case + this.property.childPropUpdated(property); + this.dataTypeService.checkForCustomBehavior(this.property); + this.valueChanged.emit(this.property.name); + } + } + + deleteListOrMapItem = (item: DerivedFEProperty) => { + if (this.property instanceof PropertyFEModel) { + this.removeValueFromParent(item); + this.property.flattenedChildren.splice(this.property.getIndexOfChild(item.propertiesName), this.property.getCountOfChildren(item.propertiesName)); + this.expandChildById(item.propertiesName); + } + } + + removeValueFromParent = (item: DerivedFEProperty, replaceKey?: string) => { + if (this.property instanceof PropertyFEModel) { + let itemParent = (item.parentName == this.property.name) ? this.property : this.property.flattenedChildren.find(prop => prop.propertiesName == item.parentName); + + if (item.derivedDataType == DerivedPropertyType.MAP) { + let oldKey = item.mapKey; + if (typeof replaceKey == 'string') { //allow saving empty string + _.set(itemParent.valueObj, replaceKey, itemParent.valueObj[oldKey]); + item.mapKey = replaceKey; + } + delete itemParent.valueObj[oldKey]; + } else { + let itemIndex: number = this.property.flattenedChildren.filter(prop => prop.parentName == item.parentName).map(prop => prop.propertiesName).indexOf(item.propertiesName); + itemParent.valueObj.splice(itemIndex, 1); + } + + if (itemParent instanceof PropertyFEModel) { //direct child + this.valueChanged.emit(this.property.name); + } else { //nested child - need to update parent prop by getting flattened name (recurse through parents and replace map/list keys, etc) + this.childValueChanged(itemParent); + } + } + } + +} diff --git a/catalog-ui/src/app/ng2/components/properties-table/properties-table.component.html b/catalog-ui/src/app/ng2/components/properties-table/properties-table.component.html new file mode 100644 index 0000000000..f3259ab3a2 --- /dev/null +++ b/catalog-ui/src/app/ng2/components/properties-table/properties-table.component.html @@ -0,0 +1,63 @@ +<div class="properties-table"> + <loader [display]="isLoading" size="large" [relative]="false"></loader> + <div class="table-header"> + <div class="table-cell col1">Property Name</div> + <div class="table-cell col2">Type</div> + <div class="table-cell col3">ES</div> + <div class="table-cell valueCol">Value</div> + </div> + <div class="table-body"> + <div class="no-data" *ngIf="!feInstancesNames || !feInstancesNames.length">No data to display</div> + + <ng-container *ngFor="let instanceName of feInstancesNames; trackBy:instanceName"> + <div class="table-rows-header white-sub-header">{{instanceName | contentAfterLastDot}}</div> + + <div class="table-row" + *ngFor="let property of fePropertiesMap[instanceName] | searchFilter:'name':searchTerm; trackBy:property?.name" + (click)="onClickPropertyRow(property, instanceName, $event)" + [ngClass]="{'selected': selectedPropertyId && selectedPropertyId === property.name }"> + + <div class="table-cell col1" [ngClass]="{'filtered':property.name === propertyNameSearchText}" [class.round-checkbox]="property.isDeclared"> + <div class="property-name"> + <checkbox [(checked)]="property.isSelected" + [disabled]="property.isDisabled || property.isDeclared || readonly" + (checkedChange)="propertyChecked(property)"></checkbox> + <div class="inner-cell-div" tooltip="{{property.name}}"> + <span>{{property.name}}</span> + </div> + </div> + <span *ngIf="property.description" class="property-description-icon sprite-new show-desc" tooltip="{{property.description}}" tooltipDelay="0"></span> + </div> + <div class="table-cell col2"> + <div class="inner-cell-div" tooltip="{{property.type | contentAfterLastDot}}"> + <span>{{property.type | contentAfterLastDot}}</span> + </div> + </div> + <div class="table-cell col3"> + <div *ngIf="property.schema && property.schema.property && property.schema.property.type" class="inner-cell-div" tooltip="{{property.schema.property.type | contentAfterLastDot}}"> + <span>{{property.schema.property.type | contentAfterLastDot}}</span> + </div> + </div> + <div class="table-cell valueCol"> + <!-- [ngClass]="{'filtered':property.name === propertyNameSearchText}" (selectProperty)="propertySelected(property, $event, flatProperty.propertiesName)" [propType]="property.type" [propSchema]="property.schema" [propKey]="" [propValue]="property.value"--> + <dynamic-property + [selectedPropertyId]="selectedPropertyId" + [canBeDeclared]="true" + [property]="property" + [expandedChildId]="property.expandedChildPropertyId" + [propertyNameSearchText]="propertyNameSearchText" + [readonly]="readonly" + (valueChanged)="propValueChanged(property);" + (expandChild)="property.updateExpandedChildPropertyId($event)" + (clickOnPropertyRow)="onClickPropertyInnerRow($event, instanceName)" + (checkProperty)="propertyChecked(property, $event)" + > + </dynamic-property> + + </div> + </div> + + </ng-container> + + </div> +</div> diff --git a/catalog-ui/src/app/ng2/components/properties-table/properties-table.component.less b/catalog-ui/src/app/ng2/components/properties-table/properties-table.component.less new file mode 100644 index 0000000000..41ff5ede13 --- /dev/null +++ b/catalog-ui/src/app/ng2/components/properties-table/properties-table.component.less @@ -0,0 +1,166 @@ +@import './../../../../assets/styles/variables.less'; +@import '../../../../assets/styles/sprite'; +@smaller-screen: ~"only screen and (max-width: 1580px)"; + +:host /deep/ input { width:100%;} + +.properties-table { + display:flex; + flex-direction:column; + flex: 1; + height:100%; + text-align:left; + + + .inner-cell-div{ + max-width: 100%; + text-overflow: ellipsis; + overflow: hidden; + height: 20px; + } + + .table-header { + display: flex; + flex-direction:row; + flex: 0 0 auto; + font-weight:bold; + border-top: #d2d2d2 solid 1px; + background-color: #f2f2f2; + + .table-cell { + color:#191919; + font-size:14px; + } + } + + .table-rows-header { + border: #d2d2d2 solid 1px; + border-top:none; + } + + .table-body { + display:flex; + flex-direction: column; + overflow-y:auto; + flex: 1; + + .no-data { + border: #d2d2d2 solid 1px; + border-top:none; + text-align: center; + height: 100%; + padding: 20px; + } + /deep/.selected{ + background-color: #e6f6fb; + color: #009fdb; + } + .table-row { + display: flex; + flex-direction:row; + flex: 0 0 auto; + + &:hover:not(.selected){ + background-color:#f8f8f8; cursor:pointer; + } + + .selected-row { + background-color:#e6f6fb; + } + + .table-cell.valueCol { + padding:0px; + + } + } + } + .table-cell { + font-size:13px; + flex:1; + border: #d2d2d2 solid 1px; + border-right:none; + border-top:none; + padding:10px; + text-overflow: ellipsis; + white-space: nowrap; + overflow:hidden; + display: flex; + min-height:40px; + + &:last-child { + border-right:#d2d2d2 solid 1px; + } + &.col1 { + flex: 0 0 300px; + max-width:300px; + display: flex; + justify-content: space-between; + @media @smaller-screen { flex: 0 0 25%;} + + .property-name { + flex: 1; + display: flex; + max-width: 270px; + } + + .property-description-icon { + float: right; + margin-top: 4px; + margin-left: 5px; + flex: 0 0 auto; + } + } + &.col2 { + flex: 0 0 150px; + max-width:150px; + @media @smaller-screen { flex: 0 0 20%;} + } + + &.col3 { + flex:0 0 120px; + max-width:120px; + @media @smaller-screen { flex: 0 0 15%;} + } + + &.valueCol { + flex: 1 0 350px; + display: flex; + @media @smaller-screen { flex: 1 0 40%;} + } + + + /deep/ .checkbox-container { + margin-right: 10px; + } + + /deep/ &.round-checkbox { + .checkbox-container input[type=checkbox].checkbox-hidden { + &:checked ~ .checkbox-icon::before { + .sprite-new; + .round-checked-icon; + } + &[disabled] ~ .checkbox-icon::before { + .sprite-new; + .round-checked-icon.disabled; + background-color:inherit; + border:none; + //animation: addDisabledCheck 4s linear; + } + } + } + } + + .filtered { + /deep/ .checkbox-label-content{ + background-color: yellow; + } + } + + dynamic-property { + width:100%; + &:last-child /deep/ .dynamic-property-row { + border-bottom:none; + } + } + +}
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/components/properties-table/properties-table.component.ts b/catalog-ui/src/app/ng2/components/properties-table/properties-table.component.ts new file mode 100644 index 0000000000..463de4f018 --- /dev/null +++ b/catalog-ui/src/app/ng2/components/properties-table/properties-table.component.ts @@ -0,0 +1,84 @@ +import { Component, Input, Output, EventEmitter, SimpleChanges, ViewChild, ElementRef } from "@angular/core"; +import {PropertyFEModel, DerivedFEProperty, DerivedPropertyType, InstanceFePropertiesMap} from "app/models"; +import {PropertiesService} from "../../services/properties.service"; +import { DynamicElementComponent } from 'app/ng2/components/dynamic-element/dynamic-element.component'; +import { KeysPipe } from 'app/ng2/pipes/keys.pipe'; + +@Component({ + selector: 'properties-table', + templateUrl: './properties-table.component.html', + styleUrls: ['./properties-table.component.less'] +}) +export class PropertiesTableComponent { + + @Input() fePropertiesMap: InstanceFePropertiesMap; + @Input() selectedPropertyId: string; + @Input() displayDeleteButton: boolean; + @Input() propertyNameSearchText:string; + @Input() searchTerm:string; + @Input() readonly:boolean; + @Input() isLoading:boolean; + + @Output() valueChanged: EventEmitter<any> = new EventEmitter<any>(); + @Output() selectPropertyRow: EventEmitter<PropertyRowSelectedEvent> = new EventEmitter<PropertyRowSelectedEvent>(); + @Output() updateCheckedPropertyCount: EventEmitter<boolean> = new EventEmitter<boolean>(); + //@Output() selectInstanceRow: EventEmitter<string> = new EventEmitter<string>(); + + feInstancesNames: Array<string>; + + constructor ( private propertiesService:PropertiesService ){ + } + + /** + * Update feInstancesNames when fePropertiesMap: InstanceFePropertiesMap change (after getting response from server) + */ + ngOnChanges(changes: SimpleChanges) { + if (changes['fePropertiesMap']) { + if (changes['fePropertiesMap'].currentValue) { + let keysPipe = new KeysPipe(); + let fiteredArr = keysPipe.transform(changes['fePropertiesMap'].currentValue,[]); + this.feInstancesNames = fiteredArr; + } + } + } + + propValueChanged = (property) => { + !property.isDeclared && this.valueChanged.emit(property); + }; + + // Click on main row (row of propertyFEModel) + onClickPropertyRow = (property:PropertyFEModel, instanceName:string, event?) => { + //event && event.stopPropagation(); + this.selectedPropertyId = property.name; + let propertyRowSelectedEvent:PropertyRowSelectedEvent = new PropertyRowSelectedEvent(property, instanceName); + this.selectPropertyRow.emit(propertyRowSelectedEvent); + }; + + // Click on inner row (row of DerivedFEProperty) + onClickPropertyInnerRow = (property:DerivedFEProperty, instanceName:string) => { + let propertyRowSelectedEvent:PropertyRowSelectedEvent = new PropertyRowSelectedEvent(property, instanceName); + this.selectPropertyRow.emit(propertyRowSelectedEvent); + } + + propertyChecked = (prop: PropertyFEModel, childPropName?: string) => { + let isChecked: boolean = (!childPropName)? prop.isSelected : prop.flattenedChildren.find(prop => prop.propertiesName == childPropName).isSelected; + + if (!isChecked) { + this.propertiesService.undoDisableRelatedProperties(prop, childPropName); + } else { + this.propertiesService.disableRelatedProperties(prop, childPropName); + } + this.updateCheckedPropertyCount.emit(isChecked); + } + +} + +export class PropertyRowSelectedEvent { + propertyModel:PropertyFEModel | DerivedFEProperty; + instanceName:string; + constructor ( propertyModel:PropertyFEModel | DerivedFEProperty, instanceName:string ){ + this.propertyModel = propertyModel; + this.instanceName = instanceName; + } +} + diff --git a/catalog-ui/src/app/ng2/components/tooltip/tooltip-content.component.html b/catalog-ui/src/app/ng2/components/tooltip/tooltip-content.component.html new file mode 100644 index 0000000000..1fbf45e39f --- /dev/null +++ b/catalog-ui/src/app/ng2/components/tooltip/tooltip-content.component.html @@ -0,0 +1,12 @@ +<div class="tooltip {{ placement }}" + [style.top]="top + 'px'" + [style.left]="left + 'px'" + [class.in]="isIn" + [class.fade]="isFade" + role="tooltip"> + <div class="tooltip-arrow"></div> + <div class="tooltip-inner"> + <ng-content></ng-content> + {{ content }} + </div> +</div> diff --git a/catalog-ui/src/app/ng2/components/tooltip/tooltip-content.component.less b/catalog-ui/src/app/ng2/components/tooltip/tooltip-content.component.less new file mode 100644 index 0000000000..1ff496f840 --- /dev/null +++ b/catalog-ui/src/app/ng2/components/tooltip/tooltip-content.component.less @@ -0,0 +1,11 @@ +.tooltip-inner { + word-wrap: break-word; + max-width: 300px; +} + +.tooltip.bottom .tooltip-arrow { + border-bottom-color: #000 !important; +} + + + diff --git a/catalog-ui/src/app/ng2/components/tooltip/tooltip-content.component.ts b/catalog-ui/src/app/ng2/components/tooltip/tooltip-content.component.ts new file mode 100644 index 0000000000..6e3e8065bb --- /dev/null +++ b/catalog-ui/src/app/ng2/components/tooltip/tooltip-content.component.ts @@ -0,0 +1,195 @@ +import {Component, AfterViewInit, Input, ElementRef, ChangeDetectorRef} from "@angular/core"; + +@Component +({ + selector: "tooltip-content", + templateUrl: "./tooltip-content.component.html", + styleUrls: ["./tooltip-content.component.less"] +}) + +export class TooltipContentComponent implements AfterViewInit { + + // ------------------------------------------------------------------------- + // Inputs / Outputs + // ------------------------------------------------------------------------- + + @Input() hostElement: HTMLElement; + @Input() content: string; + @Input() placement: "top"|"bottom"|"left"|"right" = "bottom"; + @Input() animation: boolean = true; + + // ------------------------------------------------------------------------- + // Properties + // ------------------------------------------------------------------------- + + top: number = -100000; + left: number = -100000; + isIn: boolean = false; + isFade: boolean = false; + + // ------------------------------------------------------------------------- + // Constructor + // ------------------------------------------------------------------------- + + constructor(private element: ElementRef, + private cdr: ChangeDetectorRef) { + } + + // ------------------------------------------------------------------------- + // Lifecycle callbacks + // ------------------------------------------------------------------------- + + ngAfterViewInit(): void { + this.show(); + this.cdr.detectChanges(); + } + + // ------------------------------------------------------------------------- + // Public Methods + // ------------------------------------------------------------------------- + + show(): void { + if(!this.hostElement) { + return; + } + + const position = this.positionElement(this.hostElement, this.element.nativeElement.children[0], this.placement); + this.top = position.top; + this.left = position.left; + this.isIn = true; + if (this.animation) { + this.isFade = true; + } + } + + hide(): void { + this.top = -100000; + this.left = -100000; + this.isIn = true; + if(this.animation) { + this.isFade = false; + } + } + + // ------------------------------------------------------------------------- + // Private Methods + // ------------------------------------------------------------------------- + + private positionElement(hostElem: HTMLElement, targetElem: HTMLElement, positionStr: string, appendToBody: boolean = false): {top: number, left: number} { + let positionStrParts = positionStr.split("-"); + let pos0 = positionStrParts[0]; + let pos1 = positionStrParts[1] || "center"; + let hostElemPosition = appendToBody ? this.offset(hostElem) : this.position(hostElem); + let targetElemWidth = targetElem.offsetWidth; + let targetElemHeight = targetElem.offsetHeight; + let shiftWidth: any = { + center(): number { + return hostElemPosition.left + hostElemPosition.width / 2 - targetElemWidth / 2; + }, + left(): number { + return hostElemPosition.left; + }, + right(): number { + return hostElemPosition.left + hostElemPosition.width; + } + }; + + let shiftHeight: any = { + center: function (): number { + return hostElemPosition.top + hostElemPosition.height / 2 - targetElemHeight / 2; + }, + top: function (): number { + return hostElemPosition.top; + }, + bottom: function (): number { + return hostElemPosition.top + hostElemPosition.height; + } + } + + let targetElemPosition: {top: number, left: number}; + + switch (pos0) { + case "right": + targetElemPosition = { + top: shiftHeight[pos1](), + left: shiftWidth[pos0]() + }; + break; + + case "left": + targetElemPosition = { + top: shiftHeight[pos1](), + left: hostElemPosition.left - targetElemWidth + }; + break; + + case "bottom": + targetElemPosition = { + top: shiftHeight[pos0](), + left: shiftWidth[pos1]() + }; + break; + + default: + targetElemPosition = { + top: hostElemPosition.top - targetElemHeight, + left: shiftWidth[pos1]() + }; + break; + } + + return targetElemPosition; + } + + + private position(nativeElem: HTMLElement): {width: number, height: number, top: number, left: number} { + let offsetParentCBR = {top: 0, left: 0}; + const elemBCR = this.offset(nativeElem); + const offsetParentElem = this.parentOffsetElem(nativeElem); + if(offsetParentElem !== window.document) { + offsetParentCBR = this.offset(offsetParentElem); + offsetParentCBR.top += offsetParentElem.clientTop - offsetParentElem.scrollTop; + offsetParentCBR.left += offsetParentElem.clientLeft - offsetParentElem.scrollTop; + } + + const boundingClientRect = nativeElem.getBoundingClientRect(); + + return { + width: boundingClientRect.width || nativeElem.offsetWidth, + height: boundingClientRect.height || nativeElem.offsetHeight, + top: elemBCR.top - offsetParentCBR.top, + left: elemBCR.left - offsetParentCBR.left + }; + } + + private offset(nativeElem:any): {width: number, height: number, top: number, left: number} { + const boundingClientRect = nativeElem.getBoundingClientRect(); + return { + width: boundingClientRect.width || nativeElem.offsetWidth, + height: boundingClientRect.height || nativeElem.offsetHeight, + top: boundingClientRect.top + (window.pageYOffset || window.document.documentElement.scrollTop), + left: boundingClientRect.left + (window.pageXOffset || window.document.documentElement.scrollLeft) + }; + } + + private getStyle(nativeElem: HTMLElement, cssProperty: string): string { + if(window.getComputedStyle) { + return (window.getComputedStyle(nativeElem) as any)[cssProperty]; + } + + return (nativeElem.style as any)[cssProperty]; + } + + private isStaticPositioned(nativeElem: HTMLElement): boolean { + return (this.getStyle(nativeElem, "position") || "static") === "static"; + } + + private parentOffsetElem(nativeElem: HTMLElement): any { + let offsetParent: any = nativeElem.offsetParent || window.document; + while (offsetParent && offsetParent !== window.document && this.isStaticPositioned(offsetParent)) { + offsetParent = offsetParent.offsetParent; + } + + return offsetParent || window.document; + } +} diff --git a/catalog-ui/src/app/ng2/components/tooltip/tooltip.component.ts b/catalog-ui/src/app/ng2/components/tooltip/tooltip.component.ts new file mode 100644 index 0000000000..891aa60860 --- /dev/null +++ b/catalog-ui/src/app/ng2/components/tooltip/tooltip.component.ts @@ -0,0 +1,89 @@ +import { + Directive, ComponentRef, ViewContainerRef, ComponentFactoryResolver, Input, HostListener +} from "@angular/core"; +import {TooltipContentComponent} from "./tooltip-content.component"; + +@Directive ({ + selector: "[tooltip]" +}) +export class TooltipComponent { + + // ------------------------------------------------------------------------- + // Properties + // ------------------------------------------------------------------------- + + private tooltip: ComponentRef<TooltipContentComponent>; + private visible: boolean; + private delayInProgress: boolean = false; + + // ------------------------------------------------------------------------- + // Constructor + // ------------------------------------------------------------------------- + + constructor(private viewContainerRef: ViewContainerRef, + private resolver: ComponentFactoryResolver) { + } + + // ------------------------------------------------------------------------- + // Inputs / Outputs + // ------------------------------------------------------------------------- + + @Input("tooltip") content: string|TooltipContentComponent; + @Input() tooltipDisabled: boolean; + @Input() tooltipAnimation: boolean = true; + @Input() tooltipPlacement: "top"|"bottom"|"left"|"right" = "bottom"; + @Input() tooltipDelay: number = 1500; + + // ------------------------------------------------------------------------- + // Public Methods + // ------------------------------------------------------------------------- + + @HostListener("mouseenter") + show(): void { + if(this.tooltipDisabled || this.visible || this.content === "") { + return; + } + if (this.tooltipDelay && !this.delayInProgress) { + this.delayInProgress = true; + setTimeout(() => { this.delayInProgress && this.show() }, this.tooltipDelay); + return; + } + + this.visible = true; + if (typeof this.content === "string") { + const factory = this.resolver.resolveComponentFactory(TooltipContentComponent); + if (!this.visible) { + return; + } + + this.tooltip = this.viewContainerRef.createComponent(factory); + this.tooltip.instance.hostElement = this.viewContainerRef.element.nativeElement; + this.tooltip.instance.content = this.content as string; + this.tooltip.instance.placement = this.tooltipPlacement; + this.tooltip.instance.animation = this.tooltipAnimation; + } else { + const tooltip = this.content as TooltipContentComponent; + tooltip.hostElement = this.viewContainerRef.element.nativeElement; + tooltip.placement = this.tooltipPlacement; + tooltip.animation = this.tooltipAnimation; + tooltip.show(); + } + } + + @HostListener("mouseleave") + hide(): void { + this.delayInProgress = false; + if (!this.visible) { + return; + } + + this.visible = false; + if (this.tooltip) { + this.tooltip.destroy(); + } + if (this.content instanceof TooltipContentComponent) { + (this.content as TooltipContentComponent).hide(); + } + } +} + diff --git a/catalog-ui/src/app/ng2/components/tooltip/tooltip.module.ts b/catalog-ui/src/app/ng2/components/tooltip/tooltip.module.ts new file mode 100644 index 0000000000..69976da6af --- /dev/null +++ b/catalog-ui/src/app/ng2/components/tooltip/tooltip.module.ts @@ -0,0 +1,25 @@ +import {NgModule} from "@angular/core"; +import {TooltipContentComponent} from "./tooltip-content.component"; +import {TooltipComponent} from "./tooltip.component"; +import {CommonModule} from "@angular/common"; + +@NgModule({ + declarations: [ + TooltipComponent, + TooltipContentComponent, + ], + imports: [ + CommonModule + ], + exports: [ + TooltipComponent, + TooltipContentComponent, + ], + entryComponents: [ + TooltipContentComponent + ], + providers: [] +}) +export class TooltipModule { + +} diff --git a/catalog-ui/src/app/ng2/pages/page404/page404.component.html b/catalog-ui/src/app/ng2/pages/page404/page404.component.html new file mode 100644 index 0000000000..d488587154 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/page404/page404.component.html @@ -0,0 +1,3 @@ +<div class="page404"> + Page404 +</div> diff --git a/catalog-ui/src/app/ng2/pages/page404/page404.component.less b/catalog-ui/src/app/ng2/pages/page404/page404.component.less new file mode 100644 index 0000000000..2672f22f27 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/page404/page404.component.less @@ -0,0 +1,4 @@ +.page404 { + font-size: 100px; + color: #ff0000; +} diff --git a/catalog-ui/src/app/ng2/pages/page404/page404.component.ts b/catalog-ui/src/app/ng2/pages/page404/page404.component.ts new file mode 100644 index 0000000000..a3baf4fd02 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/page404/page404.component.ts @@ -0,0 +1,9 @@ +import { Component, Inject } from '@angular/core'; + +@Component({ + templateUrl: './page404.component.html', + styleUrls: ['./page404.component.less'] +}) +export class PageNotFoundComponent { + +} diff --git a/catalog-ui/src/app/ng2/pages/properties-assignment/properties-assignment.module.ts b/catalog-ui/src/app/ng2/pages/properties-assignment/properties-assignment.module.ts new file mode 100644 index 0000000000..b59ef9dbda --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/properties-assignment/properties-assignment.module.ts @@ -0,0 +1,70 @@ +import { NgModule } from "@angular/core"; +import { PropertiesAssignmentComponent } from "./properties-assignment.page.component"; +import { HierarchyNavigationComponent } from "./../../components/hierarchy-navigtion/hierarchy-navigation.component"; +import { BrowserModule } from "@angular/platform-browser"; +import { FormsModule } from "@angular/forms"; +import { HttpModule } from "@angular/http"; +import { TabModule } from '../../shared/tabs/tabs.module'; +import { CheckboxModule} from '../../shared/checkbox/checkbox.module'; +import { PropertiesTableComponent } from '../../components/properties-table/properties-table.component'; +import { InputsTableComponent } from '../../components/inputs-table/inputs-table.component'; +import { ContentAfterLastDotPipe } from "../../pipes/contentAfterLastDot.pipe"; +import { SearchFilterPipe } from "../../pipes/searchFilter.pipe"; +import { FilterChildPropertiesPipe } from "../../pipes/filterChildProperties.pipe"; +import { DataTypeService } from './../../services/data-type.service'; +import { PropertiesService } from './../../services/properties.service'; +import { HierarchyNavService } from './../../services/hierarchy-nav.service'; +import { PropertiesUtils } from './properties.utils'; +import { PostsService } from "../../services/posts.service"; +import { DynamicElementModule } from 'app/ng2/components/dynamic-element/dynamic-element.module'; +import { DynamicPropertyComponent } from './../../components/properties-table/dynamic-property/dynamic-property.component'; +import {ConfirmationDeleteInputComponent} from "app/ng2/components/inputs-table/confirmation-delete-input/confirmation-delete-input.component" +import { PopoverModule } from "../../components/popover/popover.module" +import { FilterPropertiesAssignmentComponent } from "./../../components/filter-properties-assignment/filter-properties-assignment.component"; +import { GroupByPipe } from 'app/ng2/pipes/groupBy.pipe'; +import { KeysPipe } from 'app/ng2/pipes/keys.pipe'; +import {TooltipModule} from "../../components/tooltip/tooltip.module"; +import { ComponentModeService } from "app/ng2/services/component-mode.service" +import { ModalComponent } from "app/ng2/components/modal/modal.component" +import {LoaderComponent} from "app/ng2/components/loader/loader.component" + +@NgModule({ + declarations: [ + PropertiesAssignmentComponent, + PropertiesTableComponent, + InputsTableComponent, + ContentAfterLastDotPipe, + GroupByPipe, + KeysPipe, + SearchFilterPipe, + FilterChildPropertiesPipe, + HierarchyNavigationComponent, + DynamicPropertyComponent, + // PopoverContentComponent, + // PopoverComponent, + FilterPropertiesAssignmentComponent, + ModalComponent, + ConfirmationDeleteInputComponent, + LoaderComponent + ], + imports: [ + BrowserModule, + FormsModule, + HttpModule, + TabModule, + CheckboxModule, + DynamicElementModule, + PopoverModule, + TooltipModule + ], + entryComponents: [PropertiesAssignmentComponent], + exports: [ + PropertiesAssignmentComponent + // PopoverContentComponent, + // PopoverComponent + ], + providers: [PropertiesService, HierarchyNavService, PropertiesUtils, DataTypeService, PostsService, ContentAfterLastDotPipe, GroupByPipe, KeysPipe, ComponentModeService] +}) +export class PropertiesAssignmentModule { + +} diff --git a/catalog-ui/src/app/ng2/pages/properties-assignment/properties-assignment.page.component.html b/catalog-ui/src/app/ng2/pages/properties-assignment/properties-assignment.page.component.html new file mode 100644 index 0000000000..fa3270ec77 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/properties-assignment/properties-assignment.page.component.html @@ -0,0 +1,72 @@ +<div class="properties-assignment-page"> + <div class="main-content"> + <div class="left-column"> + <tabs #propertyInputTabs tabStyle="round-tabs" (tabChanged)="tabChanged($event)" [hideIndicationOnTabChange]="true"> + <tab tabTitle="Properties"> + <properties-table class="properties-table" + [fePropertiesMap]="instanceFePropertiesMap" + [searchTerm]="searchQuery" + [selectedPropertyId]="selectedFlatProperty.path" + [propertyNameSearchText]="searchPropertyName" + [readonly]="isReadonly" + [isLoading]="loadingProperties" + (valueChanged)="propertyValueChanged($event)" + (propertySelected)="propertySelected($event)" + (selectPropertyRow)="selectPropertyRow($event)" + (selectChildProperty)="selectChildProperty($event)" + (updateCheckedPropertyCount)="updateCheckedPropertyCount($event)" + (selectInstanceRow)="selectInstanceRow($event)"> + </properties-table> + </tab> + <tab tabTitle="Inputs"> + <inputs-table class="properties-table" + [readonly]="isReadonly" + [inputs]="inputs | searchFilter:'name':searchQuery" + [isLoading]="loadingInputs" + (deleteInput)="deleteInput($event)" + (inputValueChanged)="inputValueChanged($event)"></inputs-table> + </tab> + </tabs> + <div class="header"> + <div class="search-filter-container" [class.without-filter]="isInpusTabSelected"> + <input type="text" class="search-box" placeholder="Search" [(ngModel)]="searchQuery" /> + <span class="sprite search-icon"></span> + <filter-properties-assignment *ngIf="!isInpusTabSelected" #advanceSearch class="advance-search" [componentType]="component.componentType" (searchProperties)="searchPropertiesInstances($event)"></filter-properties-assignment> + <span *ngIf="displayClearSearch && !isInpusTabSelected" (click)="clickOnClearSearch()" class="clear-filter">Clear All</span> + </div> + <button class="tlv-btn blue declare-button" [disabled]="!checkedPropertiesCount || isReadonly" (click)="declareProperties()">Declare</button> + </div> + </div> + <div class="right-column gray-border"> + <tabs #hierarchyNavTabs tabStyle="simple-tabs"> + <tab tabTitle="Composition"> + <div class="hierarchy-nav-container"> + <loader [display]="loadingInstances" size="medium" [relative]="true"></loader> + <div class="hierarchy-header white-sub-header"> + <span tooltip="{{component.name}}">{{component.name}}</span> + </div> + <div *ngIf="!instancesNavigationData || instancesNavigationData.length === 0 || isInpusTabSelected">No data to display</div> + <hierarchy-navigation class="hierarchy-nav" + (updateSelected)="onInstanceSelectedUpdate($event)" + [displayData]="isInpusTabSelected ? []: instancesNavigationData" + [selectedItem]="selectedInstanceData.uniqueId" + [displayOptions]="hierarchyInstancesDisplayOptions"></hierarchy-navigation> + </div> + </tab> + <tab tabTitle="Property Structure"> + <div class="hierarchy-nav-container"> + <div class="hierarchy-header white-sub-header" [class.selected]="selectedFlatProperty.path == propertyStructureHeader"> + <span tooltip="{{!isInpusTabSelected ? propertyStructureHeader : ''}}">{{!isInpusTabSelected ? (propertyStructureHeader || "No Property Selected") : "No Property Selected"}}</span> + </div> + <div *ngIf="!propertiesNavigationData || propertiesNavigationData.length === 0 || isInpusTabSelected">No data to display</div> + <hierarchy-navigation class="hierarchy-nav" + (updateSelected)="onPropertySelectedUpdate($event)" + [displayData]="isInpusTabSelected ? [] : propertiesNavigationData" + [selectedItem]="selectedFlatProperty.path" + [displayOptions]="hierarchyPropertiesDisplayOptions"></hierarchy-navigation> + </div> + </tab> + </tabs> + </div> + </div> +</div> diff --git a/catalog-ui/src/app/ng2/pages/properties-assignment/properties-assignment.page.component.less b/catalog-ui/src/app/ng2/pages/properties-assignment/properties-assignment.page.component.less new file mode 100644 index 0000000000..8df479ffa6 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/properties-assignment/properties-assignment.page.component.less @@ -0,0 +1,197 @@ +@import '../../../../assets/styles/variables'; +//@import url('https://fonts.googleapis.com/css?family=Open+Sans'); +@ng2-shadow-gray: #f8f8f8; +@ng2-light-gray: #eaeaea; +@ng2-medium-gray: #d2d2d2; +@ng2-med-dark-gray: #999999; +@ng2-dark-gray: #5a5a5a; +@ng2-shadow-blue: #e6f6fb; +@ng2-bold-blue: #009fdb; +@ng2-success-green:#4ca90c; +@ng2-title-font-size:16px; +@ng2-text-font-size: 14px; + +:host { display:block; height: 100%; } +/deep/ tabs {display:flex; flex-direction:column; height:100%; } + +.properties-assignment-page { + height: 100%; + font-family: 'Open Sans', omnes-regular, sans-serif; + + .main-content { + display:flex; + flex-direction:row; + height: 100%; + } + + .left-column { + flex: 1 0 500px; + position: relative; + margin: 0 0 1em 0; + + /deep/ .tabs { + width:33%; + text-align:center; + } + + /deep/ .tab { + padding: 12px; + flex: 1; + font-weight:bold; + + &.active { + color:#009fdb; + border-color: #d2d2d2; + border-top: solid 4px #009fdb; + background-color: white; + padding-top:9px; + } + + .tab-indication { + background-color:#4ca90c; + border:solid 2px #fff; + border-radius:50%; + font-size:12px; + } + } + + .header { + position:absolute; + top:0; + right:0; + } + + .search-filter-container{ + position: absolute; + right: 100px; + display:flex; + flex-direction:row; + + .search-box { + border: 1px solid @ng2-medium-gray; + border-radius: 3px; + height: 32px; + margin: 0; + padding: 2px 20px 4px 10px; + outline: none; + font-style: italic; + color:@ng2-med-dark-gray; + + &::-moz-placeholder { color:@ng2-med-dark-gray;} + &::-webkit-input-placeholder{ color:@ng2-med-dark-gray;} + } + + .search-icon { + background-position: -48px -3137px; + width: 14px; + height: 14px; + position: absolute; + right:42px; + top: 8px; + } + + &.without-filter { + margin-right:10px; + .search-icon { + right: 4px; + } + } + + } + .advance-search{ + + } + .clear-filter{ + cursor: pointer; + color: @main_color_c; + font-family: @font-omnes-medium-italic; + text-decoration: underline; + position: relative; + top: 4px; + right: 16px; + } + + .declare-button{ + position: absolute; + top: 0; + right: 0; + } + } + + .right-column { + display:flex; + flex:0 0 350px; + flex-direction:column; + margin: 45px 0 1em 1em; + overflow-x:auto; + + /deep/ .tabs { + border-bottom: solid 1px #d0d0d0; + } + + /deep/ .tab { + flex: none; + padding: 8px 20px 0; + font-size: 14px; + font-weight:bold; + line-height:30px; + } + } + + .hierarchy-tabs { + flex: 0 0 40px; + } + + .gray-border { + border: 1px solid #ddd; + } + + /deep/ .white-sub-header { + background-color: #fffefe; + box-shadow: 0px 1px 0.99px 0.01px rgba(34, 31, 31, 0.15); + border-bottom: #d2d2d2 solid 1px; + color:#009fdb; + font-weight:bold; + font-size:14px; + text-align:left; + flex:0 0 auto; + padding: 10px; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + + &.hierarchy-header { + padding-left:20px; + &.selected { + background-color: #e6f6fb; + } + } + + } + + .hierarchy-nav-container { + flex:1; + overflow: auto; + flex-direction: column; + height: 100%; + } + + .hierarchy-header { + + span{ + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + max-width: 290px; + } + } + + .hierarchy-nav { + display: grid; + margin-top: 1em; + margin-left: 1em; + font-size: 12px; + padding-top: 1em; + } +} + diff --git a/catalog-ui/src/app/ng2/pages/properties-assignment/properties-assignment.page.component.ts b/catalog-ui/src/app/ng2/pages/properties-assignment/properties-assignment.page.component.ts new file mode 100644 index 0000000000..22d6f2fe51 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/properties-assignment/properties-assignment.page.component.ts @@ -0,0 +1,413 @@ +import {Component, ViewChild, ElementRef, Renderer, Inject} from "@angular/core"; +import {PostsService} from "../../services/posts.service"; +import { PropertiesService } from "../../services/properties.service"; +import { HierarchyNavService } from "../../services/hierarchy-nav.service"; +import { PropertiesUtils } from './properties.utils'; +import { PropertyFEModel, InstanceFePropertiesMap, InstanceBePropertiesMap, InstancePropertiesAPIMap, Component as ComponentData, FilterPropertiesAssignmentData } from "app/models"; +import { PROPERTY_TYPES, ResourceType } from "app/utils"; +import property = require("lodash/property"); +import {ComponentServiceNg2} from "../../services/component-services/component.service"; +import {ComponentInstanceServiceNg2} from "../../services/component-instance-services/component-instance.service" +import { InputFEModel, ComponentInstance, PropertyBEModel, DerivedPropertyType, DerivedFEProperty, ResourceInstance, SimpleFlatProperty } from "app/models"; +import {HierarchyDisplayOptions} from "../../components/hierarchy-navigtion/hierarchy-display-options" +import {PropertyRowSelectedEvent} from "./../../components/properties-table/properties-table.component"; +import { KeysPipe } from 'app/ng2/pipes/keys.pipe'; +import {FilterPropertiesAssignmentComponent} from "../../components/filter-properties-assignment/filter-properties-assignment.component"; +import { ComponentModeService } from "app/ng2/services/component-mode.service" +import {WorkspaceMode, EVENTS} from "../../../utils/constants"; +import {ComponentInstanceProperty, InputBEModel} from "app/models" +import {ComponentInstanceInput} from "../../../models/properties-inputs/input-be-model"; +import {EventListenerService} from "app/services/event-listener-service" +@Component({ + templateUrl: './properties-assignment.page.component.html', + styleUrls: ['./properties-assignment.page.component.less'] +}) +export class PropertiesAssignmentComponent { + title = "Properties & Inputs"; + + component:ComponentData; + + propertiesNavigationData = []; + instancesNavigationData = []; + + instanceFePropertiesMap:InstanceFePropertiesMap; + inputs: Array<InputFEModel> = []; + instances: Array<ComponentInstance> = []; + searchQuery: string; + propertyStructureHeader: string; + + selectedFlatProperty: SimpleFlatProperty = new SimpleFlatProperty(); + selectedInstanceType: string; + selectedInstanceData: ComponentInstance = new ComponentInstance(); + checkedPropertiesCount: number = 0; + + hierarchyPropertiesDisplayOptions:HierarchyDisplayOptions = new HierarchyDisplayOptions('path', 'name', 'childrens'); + hierarchyInstancesDisplayOptions:HierarchyDisplayOptions = new HierarchyDisplayOptions('uniqueId', 'name'); + displayClearSearch = false; + searchPropertyName:string; + isInpusTabSelected:boolean; + isReadonly:boolean; + loadingInstances:boolean = false; + loadingInputs:boolean = false; + loadingProperties:boolean = false; + + @ViewChild('hierarchyNavTabs') hierarchyNavTabs: ElementRef; + @ViewChild('propertyInputTabs') propertyInputTabs: ElementRef; + @ViewChild('advanceSearch') advanceSearch: FilterPropertiesAssignmentComponent; + + constructor(private propertiesService: PropertiesService, + private hierarchyNavService: HierarchyNavService, + private propertiesUtils:PropertiesUtils, + private componentServiceNg2:ComponentServiceNg2, + private componentInstanceServiceNg2:ComponentInstanceServiceNg2, + @Inject("$stateParams") _stateParams, + private renderer: Renderer, + private componentModeService:ComponentModeService, + private EventListenerService:EventListenerService) { + + this.instanceFePropertiesMap = new InstanceFePropertiesMap(); + + /* This is the way you can access the component data, please do not use any data except metadata, all other data should be received from the new api calls on the first time + than if the data is already exist, no need to call the api again - Ask orit if you have any questions*/ + this.component = _stateParams.component; + this.EventListenerService.registerObserverCallback(EVENTS.ON_CHECKOUT, this.onCheckout); + this.updateViewMode(); + } + + ngOnInit() { + console.log("==>" + this.constructor.name + ": ngOnInit"); + this.loadingInputs = true; + this.loadingInstances = true; + this.loadingProperties = true; + this.componentServiceNg2 + .getComponentInputs(this.component) + .subscribe(response => { + _.forEach(response.inputs, (input: InputBEModel) => { + this.inputs.push(new InputFEModel(input)); + }); + this.loadingInputs = false; + + }); + this.componentServiceNg2 + .getComponentResourceInstances(this.component) + .subscribe(response => { + this.instances = response.componentInstances; + + _.forEach(this.instances, (instance) => { + this.instancesNavigationData.push(instance); + }); + this.loadingInstances = false; + if (this.instancesNavigationData[0] == undefined) { + this.loadingProperties = false; + } + this.selectFirstInstanceByDefault(); + }); + }; + + ngOnDestroy() { + this.EventListenerService.unRegisterObserver(EVENTS.ON_CHECKOUT); + } + + selectFirstInstanceByDefault = () => { + if (this.instancesNavigationData[0] !== undefined) { + this.onInstanceSelectedUpdate(this.instancesNavigationData[0]); + } + }; + + updateViewMode = () => { + this.isReadonly = this.componentModeService.getComponentMode(this.component) === WorkspaceMode.VIEW; + } + + onCheckout = (component:ComponentData) => { + this.component = component; + this.updateViewMode(); + } + + + onInstanceSelectedUpdate = (resourceInstance: ResourceInstance) => { + console.log("==>" + this.constructor.name + ": onInstanceSelectedUpdate"); + let instanceBePropertiesMap: InstanceBePropertiesMap = new InstanceBePropertiesMap(); + this.selectedInstanceData = resourceInstance; + this.selectedInstanceType = resourceInstance.originType; + + this.loadingProperties = true; + if(resourceInstance.originType === ResourceType.VF) { + this.componentInstanceServiceNg2 + .getComponentInstanceInputs(this.component, resourceInstance) + .subscribe(response => { + instanceBePropertiesMap[resourceInstance.uniqueId] = response; + this.processInstancePropertiesResponse(instanceBePropertiesMap); + this.loadingProperties = false; + + }); + } else { + this.componentInstanceServiceNg2 + .getComponentInstanceProperties(this.component, resourceInstance.uniqueId) + .subscribe(response => { + instanceBePropertiesMap[resourceInstance.uniqueId] = response; + this.processInstancePropertiesResponse(instanceBePropertiesMap); + this.loadingProperties = false; + }); + } + + if( this.searchPropertyName ){ + this.clearSearch(); + } + //clear selected property from the navigation + this.selectedFlatProperty = new SimpleFlatProperty(); + this.propertiesNavigationData = []; + }; + + /** + * Entry point handling response from server + */ + processInstancePropertiesResponse = (instanceBePropertiesMap:InstanceBePropertiesMap) => { + this.instanceFePropertiesMap = this.propertiesUtils.convertPropertiesMapToFEAndCreateChildren(instanceBePropertiesMap, this.inputs); //create flattened children, disable declared props, and init values + this.checkedPropertiesCount = 0; + }; + + + /*** VALUE CHANGE EVENTS ***/ + propertyValueChanged = (event: PropertyFEModel) => { + console.log("==>" + this.constructor.name + ": propertyValueChanged " + event); + // Copying the actual value from the object ref into the value if it's from a complex type + event.value = event.getJSONValue(); + + if (this.selectedInstanceData.originType === ResourceType.VF) { + console.log("I want to update input value on the resource instance"); + let inputToUpdate = new PropertyBEModel(event); + this.componentInstanceServiceNg2 + .updateInstanceInput(this.component, this.selectedInstanceData.uniqueId, inputToUpdate) + .subscribe(response => { + console.log("update resource instance input and got this response: ", response); + }) + } + else { + let propertyBe = new PropertyBEModel(event); + this.componentInstanceServiceNg2 + .updateInstanceProperty(this.component, this.selectedInstanceData.uniqueId, propertyBe) + .subscribe(response => { + console.log("updated resource instance property and got this response: ", response); + }); + console.log(event); + } + + }; + + inputValueChanged = (event) => { + console.log("==>" + this.constructor.name + ": inputValueChanged"); + let inputToUpdate = new PropertyBEModel(event); + + this.componentServiceNg2 + .updateComponentInput(this.component, inputToUpdate) + .subscribe(response => { + console.log("updated the component input and got this response: ", response); + }) + }; + + + /*** HEIRARCHY/NAV RELATED FUNCTIONS ***/ + + /** + * Handle select node in navigation area, and select the row in table + */ + onPropertySelectedUpdate = ($event) => { + console.log("==>" + this.constructor.name + ": onPropertySelectedUpdate"); + this.selectedFlatProperty = $event; + let parentProperty:PropertyFEModel = this.propertiesService.getParentPropertyFEModelFromPath(this.instanceFePropertiesMap[this.selectedFlatProperty.instanceName], this.selectedFlatProperty.path); + parentProperty.expandedChildPropertyId = this.selectedFlatProperty.path; + }; + + /** + * When user select row in table, this will prepare the hirarchy object for the tree. + */ + selectPropertyRow = (propertyRowSelectedEvent:PropertyRowSelectedEvent) => { + console.log("==>" + this.constructor.name + ": selectPropertyRow " + propertyRowSelectedEvent.propertyModel.name); + let property = propertyRowSelectedEvent.propertyModel; + let instanceName = propertyRowSelectedEvent.instanceName; + this.propertyStructureHeader = null; + + // Build hirarchy tree for the navigation and update propertiesNavigationData with it. + if(this.selectedInstanceData.originType !== ResourceType.VF) { + let simpleFlatProperty:Array<SimpleFlatProperty>; + if (property instanceof PropertyFEModel) { + simpleFlatProperty = this.hierarchyNavService.getSimplePropertiesTree(property, instanceName); + } else if (property instanceof DerivedFEProperty) { + // Need to find parent PropertyFEModel + let parentPropertyFEModel:PropertyFEModel = _.find(this.instanceFePropertiesMap[instanceName], (tmpFeProperty):boolean => { + return property.propertiesName.indexOf(tmpFeProperty.name)===0; + }); + simpleFlatProperty = this.hierarchyNavService.getSimplePropertiesTree(parentPropertyFEModel, instanceName); + } + this.propertiesNavigationData = simpleFlatProperty; + } + + // Update the header in the navigation tree with property name. + this.propertyStructureHeader = (property.propertiesName.split('#'))[0]; + + // Set selected property in table + this.selectedFlatProperty = this.hierarchyNavService.createSimpleFlatProperty(property, instanceName); + this.renderer.invokeElementMethod(this.hierarchyNavTabs, 'triggerTabChange', ['Property Structure']); + }; + + + selectInstanceRow = ($event) => {//get instance name + this.selectedInstanceData = _.find(this.instancesNavigationData, (instance:ComponentInstance) => { + return instance.name == $event; + }); + this.renderer.invokeElementMethod( + this.hierarchyNavTabs, 'triggerTabChange', ['Composition']); + }; + + tabChanged = (event) => { + console.log("==>" + this.constructor.name + ": tabChanged " + event); + this.isInpusTabSelected = event.title === "Inputs"; + this.propertyStructureHeader = null; + this.searchQuery = ''; + }; + + + + /*** DECLARE PROPERTIES/INPUTS ***/ + declareProperties = (): void => { + console.log("==>" + this.constructor.name + ": declareProperties"); + + let selectedProperties: InstanceBePropertiesMap = new InstanceBePropertiesMap(); + + let instancesNames = new KeysPipe().transform(this.instanceFePropertiesMap, []); + angular.forEach(instancesNames, (instanceName: string): void => { + selectedProperties[instanceName] = this.propertiesService.getCheckedProperties(this.instanceFePropertiesMap[instanceName]); + //selectedProperties[this.selectedInstanceData.uniqueId] = this.propertiesService.getCheckedProperties(this.properties); + }); + + let inputsToCreate: InstancePropertiesAPIMap; + if (this.selectedInstanceType !== ResourceType.VF) { + inputsToCreate = new InstancePropertiesAPIMap(null, selectedProperties); + } else { + inputsToCreate = new InstancePropertiesAPIMap(selectedProperties, null); + } + this.componentServiceNg2 + .createInput(this.component, inputsToCreate) + .subscribe(response => { + this.setInputTabIndication(response.length); + this.checkedPropertiesCount = 0; + _.forEach(response, (input: InputBEModel) => { + this.inputs.push(new InputFEModel(input)); + this.updatePropertyValueAfterDeclare(input); + }); + }); + }; + + updatePropertyValueAfterDeclare = (input: InputBEModel) => { + _.forEach(input.properties, (property: ComponentInstanceProperty) => { + this.updatePropertyOrInputValueAfterDeclare(property, input); + }); + + _.forEach(input.inputs, (inputInstance: ComponentInstanceInput) => { + this.updatePropertyOrInputValueAfterDeclare(inputInstance, input); + }); + } + + updatePropertyOrInputValueAfterDeclare = (inputSource: ComponentInstanceProperty | ComponentInstanceInput, input: InputBEModel) => { + if (this.instanceFePropertiesMap[inputSource.componentInstanceId]) { + let propertyForUpdatindVal = _.find(this.instanceFePropertiesMap[inputSource.componentInstanceId], (feProperty: PropertyFEModel) => { + return feProperty.name == inputSource.name; + }); + + if (input.inputPath == propertyForUpdatindVal.name) input.inputPath = null; //Fix - if inputPath is sent for parent props, remove it + + propertyForUpdatindVal.setAsDeclared(input.inputPath); //set prop as declared before assigning value + this.propertiesService.disableRelatedProperties(propertyForUpdatindVal, input.inputPath); + this.propertiesUtils.resetPropertyValue(propertyForUpdatindVal, inputSource.value, input.inputPath); + // if (input.inputPath) { + // let childProp = _.find(propertyForUpdatindVal.flattenedChildren, (child: DerivedFEProperty) => { + // return child.propertiesName == input.inputPath; + // }); + // this.propertiesUtils.assignFlattenedChildrenValues(JSON.parse(inputSource.value), [childProp], inputSource.name); + // } else { + // propertyForUpdatindVal.valueObj = inputSource.value; + // } + } + } + + //used for declare button, to keep count of newly checked properties (and ignore declared properties) + updateCheckedPropertyCount = (increment: boolean): void => { + this.checkedPropertiesCount += (increment) ? 1 : -1; + console.log("CheckedProperties count is now.... " + this.checkedPropertiesCount); + }; + + setInputTabIndication = (numInputs: number): void => { + this.renderer.invokeElementMethod(this.propertyInputTabs, 'setTabIndication', ['Inputs', numInputs]); + }; + + deleteInput = (input: InputFEModel) => { + console.log("==>" + this.constructor.name + ": deleteInput"); + let inputToDelete = new PropertyBEModel(input); + + this.componentServiceNg2 + .deleteInput(this.component, inputToDelete) + .subscribe(response => { + this.inputs = this.inputs.filter(input => input.uniqueId !== response.uniqueId); + + //Reload the whole instance for now - TODO: CHANGE THIS after the BE starts returning properties within the response, use commented code below instead! + this.onInstanceSelectedUpdate(this.selectedInstanceData); + // let instanceFeProperties = this.instanceFePropertiesMap[this.getInstanceUniqueId(input.instanceName)]; + + // if (instanceFeProperties) { + // let propToEnable: PropertyFEModel = instanceFeProperties.find((prop) => { + // return prop.name == input.propertyName; + // }); + + // if (propToEnable) { + // if (propToEnable.name == response.inputPath) response.inputPath = null; + // propToEnable.setNonDeclared(response.inputPath); + // //this.propertiesUtils.resetPropertyValue(propToEnable, newValue, response.inputPath); + // this.propertiesService.undoDisableRelatedProperties(propToEnable, response.inputPath); + // } + // } + }); + }; + + getInstanceUniqueId = (instanceName: string): string => { + let wantedInstance: ComponentInstance = this.instances.find((instance) => { + return instance.normalizedName === instanceName; + }); + + return wantedInstance.uniqueId; + }; + + + + /*** SEARCH RELATED FUNCTIONS ***/ + searchPropertiesInstances = (filterData:FilterPropertiesAssignmentData) => { + let instanceBePropertiesMap:InstanceBePropertiesMap; + this.componentServiceNg2 + .filterComponentInstanceProperties(this.component, filterData) + .subscribe(response => { + + this.processInstancePropertiesResponse(response); + this.hierarchyPropertiesDisplayOptions.searchText = filterData.propertyName;//mark results in tree + this.searchPropertyName = filterData.propertyName;//mark in table + this.renderer.invokeElementMethod(this.hierarchyNavTabs, 'triggerTabChange', ['Composition']); + this.propertiesNavigationData = []; + this.displayClearSearch = true; + }); + + }; + + clearSearch = () => { + this.instancesNavigationData = this.instances; + this.searchPropertyName = ""; + this.hierarchyPropertiesDisplayOptions.searchText = ""; + this.displayClearSearch = false; + this.advanceSearch.clearAll(); + }; + + clickOnClearSearch = () => { + this.clearSearch(); + this.selectFirstInstanceByDefault(); + this.renderer.invokeElementMethod( + this.hierarchyNavTabs, 'triggerTabChange', ['Composition']); + }; + +} diff --git a/catalog-ui/src/app/ng2/pages/properties-assignment/properties.utils.ts b/catalog-ui/src/app/ng2/pages/properties-assignment/properties.utils.ts new file mode 100644 index 0000000000..0eb8534595 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/properties-assignment/properties.utils.ts @@ -0,0 +1,155 @@ +import { Injectable } from '@angular/core'; +import { DataTypeModel, PropertyFEModel, PropertyBEModel, InstanceBePropertiesMap, InstanceFePropertiesMap, SchemaProperty, DerivedFEProperty, DerivedFEPropertyMap, DerivedPropertyType, InputFEModel} from "app/models"; +import { DataTypeService } from "app/ng2/services/data-type.service"; +import { PropertiesService } from "app/ng2/services/properties.service"; +import { PROPERTY_TYPES } from "app/utils"; +import { UUID } from "angular2-uuid"; + +@Injectable() +export class PropertiesUtils { + + constructor(private dataTypeService:DataTypeService, private propertiesService: PropertiesService) {} + + /** + * Entry point when getting properties from server + * For each instance, loop through each property, and: + * 1. Create flattened children + * 2. Check against inputs to see if any props are declared and disable them + * 3. Initialize valueObj (which also creates any new list/map flattened children as needed) + * Returns InstanceFePropertiesMap + */ + public convertPropertiesMapToFEAndCreateChildren = (instancePropertiesMap:InstanceBePropertiesMap, inputs:Array<InputFEModel>): InstanceFePropertiesMap => { + let instanceFePropertiesMap:InstanceFePropertiesMap = new InstanceFePropertiesMap(); + angular.forEach(instancePropertiesMap, (properties:Array<PropertyBEModel>, instanceName:string) => { + let instanceInputs: Array<InputFEModel> = inputs.filter(input => input.instanceName == instanceName.split('.').pop()); + let propertyFeArray: Array<PropertyFEModel> = []; + _.forEach(properties, (property: PropertyBEModel) => { + + if (!this.dataTypeService.getDataTypeByTypeName(property.type)) { // if type not exist in data types remove property from list + console.log("ERROR: missing type " + property.type + " in dataTypes , of property ", property); + } else { + + let newFEProp: PropertyFEModel = new PropertyFEModel(property); //Convert property to FE + + if (newFEProp.derivedDataType == DerivedPropertyType.COMPLEX) { //Create children if prop is not simple, list, or map. + newFEProp.flattenedChildren = this.createFlattenedChildren(newFEProp.type, newFEProp.name); + } + if (instanceInputs.length) { //if this prop (or any children) are declared, set isDeclared and disable checkbox on parents/children + instanceInputs.filter(input => input.propertyName == newFEProp.name).forEach((input) => { + newFEProp.setAsDeclared(input.inputPath); //if a path was sent, its a child prop. this param is optional + this.propertiesService.disableRelatedProperties(newFEProp, input.inputPath); + }); + } + this.initValueObjectRef(newFEProp); //initialize valueObj. + propertyFeArray.push(newFEProp); + newFEProp.updateExpandedChildPropertyId(newFEProp.name); //display only the first level of children + this.dataTypeService.checkForCustomBehavior(newFEProp); + } + }); + instanceFePropertiesMap[instanceName] = propertyFeArray; + + }); + return instanceFePropertiesMap; + } + private createListOrMapChildrenFromValueObj = (property: PropertyFEModel) => { + if ((property.derivedDataType == DerivedPropertyType.LIST || property.derivedDataType == DerivedPropertyType.MAP) + && Object.keys(property.valueObj).length) { + + Object.keys(property.valueObj).forEach((key) => { + let newProps: Array<DerivedFEProperty> = this.createListOrMapChildren(property, key, property.valueObj[key]); + property.flattenedChildren.push(...newProps); + }); + + } + } + + public createListOrMapChildren = (property:PropertyBEModel, key: string, valueObj: any): Array<DerivedFEProperty> => { + let newProps: Array<DerivedFEProperty> = []; + let parentProp = new DerivedFEProperty(property, property.propertiesName, true, key, valueObj); + newProps.push(parentProp); + + if (!property.schema.property.isSimpleType) { + let additionalChildren:Array<DerivedFEProperty> = this.createFlattenedChildren(property.schema.property.type, parentProp.propertiesName); + this.assignFlattenedChildrenValues(parentProp.valueObj, additionalChildren, parentProp.propertiesName); + additionalChildren.forEach(prop => prop.canBeDeclared = false); + newProps.push(...additionalChildren); + } + return newProps; + } + + /** + * Creates derivedFEProperties of a specified type and returns them. + */ + private createFlattenedChildren = (type: string, parentName: string):Array<DerivedFEProperty> => { + let tempProps: Array<DerivedFEProperty> = []; + let dataTypeObj: DataTypeModel = this.dataTypeService.getDataTypeByTypeName(type); + this.dataTypeService.getDerivedDataTypeProperties(dataTypeObj, tempProps, parentName); + return tempProps; + } + + /* Sets the valueObj of parent property and its children. + * Note: This logic is different than assignflattenedchildrenvalues - here we merge values, there we pick either the parents value, props value, or default value - without merging. + */ + public initValueObjectRef = (property: PropertyFEModel): void => { + if (property.derivedDataType == DerivedPropertyType.SIMPLE || property.isDeclared) { //if property is declared, it gets a simple input instead. List and map values and pseudo-children will be handled in property component + property.valueObj = property.value || property.defaultValue; + + if (property.isDeclared && typeof property.valueObj == 'object') property.valueObj = JSON.stringify(property.valueObj); + } else { + if (property.derivedDataType == DerivedPropertyType.LIST) { + property.valueObj = _.merge([], JSON.parse(property.defaultValue || '[]'), JSON.parse(property.value || '[]')); //value object should be merged value and default value. Value takes higher precendence. Set valueObj to empty obj if undefined. + } else { + property.valueObj = _.merge({}, JSON.parse(property.defaultValue || '{}'), JSON.parse(property.value || '{}')); //value object should be merged value and default value. Value takes higher precendence. Set valueObj to empty obj if undefined. + } + if (property.derivedDataType == DerivedPropertyType.COMPLEX) { + this.assignFlattenedChildrenValues(property.valueObj, property.flattenedChildren, property.name); + } else { + this.createListOrMapChildrenFromValueObj(property); + } + } + } + + /* + * Loops through flattened properties array and to assign values + * Then, convert any neccessary strings to objects, and vis-versa + * For list or map property, creates new children props if valueObj has values + */ + public assignFlattenedChildrenValues = (parentValueJSON: any, derivedPropArray: Array<DerivedFEProperty>, parentName: string) => { + if (!derivedPropArray || !parentName) return; + derivedPropArray.forEach((prop, index) => { + + let propNameInObj = prop.propertiesName.substring(prop.propertiesName.indexOf(parentName) + parentName.length + 1).split('#').join('.'); //extract everything after parent name + prop.valueObj = _.get(parentValueJSON, propNameInObj, prop.value || prop.defaultValue); //assign value -first value of parent if exists. If not, prop.value if not, prop.defaultvalue + + if ((prop.derivedDataType == DerivedPropertyType.SIMPLE || prop.isDeclared) && typeof prop.valueObj == 'object') { //Stringify objects that should be strings + prop.valueObj = JSON.stringify(prop.valueObj); + } else { //parse strings that should be objects + if ((prop.derivedDataType == DerivedPropertyType.COMPLEX || prop.derivedDataType == DerivedPropertyType.MAP) && typeof prop.valueObj != 'object') { + prop.valueObj = JSON.parse(prop.valueObj || '{}'); + } else if (prop.derivedDataType == DerivedPropertyType.LIST && typeof prop.valueObj != 'object') { + prop.valueObj = JSON.parse(prop.valueObj || '[]'); + } + if ((prop.derivedDataType == DerivedPropertyType.LIST || prop.derivedDataType == DerivedPropertyType.MAP) && Object.keys(prop.valueObj).length) { + let newProps: Array<DerivedFEProperty> = []; + Object.keys(prop.valueObj).forEach((key) => { + newProps.push(...this.createListOrMapChildren(prop, key, prop.valueObj[key]));//create new children, assign their values, and then add to array + }); + derivedPropArray.splice(index + 1, 0, ...newProps); + } + } + }); + } + + public resetPropertyValue = (property: PropertyFEModel, newValue: string, inputPath?: string): void => { + property.value = newValue; + if (inputPath) { + let newProp = property.flattenedChildren.find(prop => prop.propertiesName == inputPath); + newProp && this.assignFlattenedChildrenValues(JSON.parse(newValue), [newProp], property.name); + } else { + this.initValueObjectRef(property); + } + } + + + +} diff --git a/catalog-ui/src/app/ng2/pipes/contentAfterLastDot.pipe.ts b/catalog-ui/src/app/ng2/pipes/contentAfterLastDot.pipe.ts new file mode 100644 index 0000000000..68fba92b77 --- /dev/null +++ b/catalog-ui/src/app/ng2/pipes/contentAfterLastDot.pipe.ts @@ -0,0 +1,8 @@ +import { Pipe, PipeTransform } from '@angular/core'; + +@Pipe({ name: 'contentAfterLastDot' }) +export class ContentAfterLastDotPipe implements PipeTransform { + transform(value:string): string { + return value.split('.').pop(); + } +}
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/pipes/filterChildProperties.pipe.ts b/catalog-ui/src/app/ng2/pipes/filterChildProperties.pipe.ts new file mode 100644 index 0000000000..d2eaef0391 --- /dev/null +++ b/catalog-ui/src/app/ng2/pipes/filterChildProperties.pipe.ts @@ -0,0 +1,18 @@ +import { Pipe, PipeTransform } from '@angular/core'; +import { DerivedFEProperty } from 'app/models'; + +@Pipe({ + name: 'filterChildProperties', +}) +export class FilterChildPropertiesPipe implements PipeTransform { + public transform(childProperties: Array<DerivedFEProperty>, parentId: string) { + if (!parentId || !childProperties) return childProperties; + + let validParents: Array<string> = [parentId]; + while (parentId.lastIndexOf('#') > 0) { + parentId = parentId.substring(0, parentId.lastIndexOf('#')); + validParents.push(parentId); + } + return childProperties.filter(derivedProp => validParents.indexOf(derivedProp.parentName) > -1); + } +}
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/pipes/groupBy.pipe.ts b/catalog-ui/src/app/ng2/pipes/groupBy.pipe.ts new file mode 100644 index 0000000000..17ccc0ca75 --- /dev/null +++ b/catalog-ui/src/app/ng2/pipes/groupBy.pipe.ts @@ -0,0 +1,19 @@ +/** + * Created by rc2122 on 5/17/2017. + */ +import {Pipe, PipeTransform} from '@angular/core'; + +@Pipe({name: 'groupBy'}) +export class GroupByPipe implements PipeTransform { + transform(value: Array<any>, field: string): Array<any> { + const groupedObj = value.reduce((prev, cur)=> { + if(!prev[cur[field]]) { + prev[cur[field]] = [cur]; + } else { + prev[cur[field]].push(cur); + } + return prev; + }, {}); + return Object.keys(groupedObj).map((key:string) => {return { key, value: groupedObj[key] }; }); + } +} diff --git a/catalog-ui/src/app/ng2/pipes/keys.pipe.ts b/catalog-ui/src/app/ng2/pipes/keys.pipe.ts new file mode 100644 index 0000000000..13bd26969c --- /dev/null +++ b/catalog-ui/src/app/ng2/pipes/keys.pipe.ts @@ -0,0 +1,12 @@ +import { Pipe, PipeTransform } from '@angular/core'; + +@Pipe({name: 'keys'}) +export class KeysPipe implements PipeTransform { + transform(value, args:string[]) : any { + let keys = []; + for (let key in value) { + keys.push(key); + } + return keys; + } +} diff --git a/catalog-ui/src/app/ng2/pipes/searchFilter.pipe.ts b/catalog-ui/src/app/ng2/pipes/searchFilter.pipe.ts new file mode 100644 index 0000000000..3a0c85dda2 --- /dev/null +++ b/catalog-ui/src/app/ng2/pipes/searchFilter.pipe.ts @@ -0,0 +1,17 @@ +import { Pipe, PipeTransform } from '@angular/core'; + +@Pipe({ + name: 'searchFilter', +}) +export class SearchFilterPipe implements PipeTransform { + public transform(value, key: string, term: string) { + if (!term || !term.length) return value; + return value.filter((item) => { + if (item.hasOwnProperty(key)) { + return item[key].indexOf(term) > -1; + } else { + return false; + } + }); + } +} diff --git a/catalog-ui/src/app/ng2/services/authentication.service.ts b/catalog-ui/src/app/ng2/services/authentication.service.ts new file mode 100644 index 0000000000..7fe3e22f4c --- /dev/null +++ b/catalog-ui/src/app/ng2/services/authentication.service.ts @@ -0,0 +1,40 @@ +import { Injectable } from '@angular/core'; +import { sdc2Config } from './../../../main'; +import {IAppConfigurtaion, ICookie} from "../../models/app-config"; +import {Response, Headers, RequestOptions, Http} from '@angular/http'; +import {Cookie2Service} from "./cookie.service"; +import { Observable } from 'rxjs/Observable'; + +@Injectable() +export class AuthenticationService { + + private cookieService:Cookie2Service; + private http:Http; + + constructor(cookieService:Cookie2Service, http: Http) { + this.cookieService = cookieService; + this.http = http; + } + + private getAuthHeaders():any { + let cookie:ICookie = sdc2Config.cookie; + let authHeaders:any = {}; + authHeaders[cookie.userFirstName] = this.cookieService.getFirstName(); + authHeaders[cookie.userLastName] = this.cookieService.getLastName(); + authHeaders[cookie.userEmail] = this.cookieService.getEmail(); + authHeaders[cookie.userIdSuffix] = this.cookieService.getUserId(); + return authHeaders; + } + + public authenticate(): Observable<JSON> { + let options = new RequestOptions({ + headers: new Headers(this.getAuthHeaders()) + }); + + let authUrl = sdc2Config.api.root + sdc2Config.api.GET_user_authorize; + return this.http + .get(authUrl, options) + .map((res: Response) => res.json()); + } + +}
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/services/component-instance-services/component-instance.service.ts b/catalog-ui/src/app/ng2/services/component-instance-services/component-instance.service.ts new file mode 100644 index 0000000000..85709894ff --- /dev/null +++ b/catalog-ui/src/app/ng2/services/component-instance-services/component-instance.service.ts @@ -0,0 +1,51 @@ +import {Injectable} from '@angular/core'; +import {Response, RequestOptions, Headers} from '@angular/http'; +import { Observable } from 'rxjs/Observable'; +import {HttpService} from "../http.service"; +import {sdc2Config} from "../../../../main"; +import {PropertyBEModel} from "app/models"; +import {CommonUtils} from "app/utils"; +import {Component, ComponentInstance, InputModel} from "app/models"; + +@Injectable() +export class ComponentInstanceServiceNg2 { + + protected baseUrl; + + constructor(private http: HttpService) { + this.baseUrl = sdc2Config.api.root + sdc2Config.api.component_api_root; + } + + getComponentInstanceProperties(component: Component, componentInstanceId: string): Observable<Array<PropertyBEModel>> { + + return this.http.get(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/componentInstances/' + componentInstanceId + '/properties') + .map((res: Response) => { + return CommonUtils.initBeProperties(res.json()); + }) + } + + getComponentInstanceInputs(component: Component, componentInstance: ComponentInstance): Observable<Array<PropertyBEModel>> { + return this.http.get(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/componentInstances/' + componentInstance.uniqueId + '/' + componentInstance.componentUid + '/inputs') + .map((res: Response) => { + return CommonUtils.initInputs(res.json()); + }) + } + + updateInstanceProperty(component: Component, componentInstanceId: string, property: PropertyBEModel): Observable<PropertyBEModel> { + + return this.http.post(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/resourceInstance/' + componentInstanceId + '/property', property) + .map((res: Response) => { + return new PropertyBEModel(res.json()); + }) + } + + updateInstanceInput(component: Component, componentInstanceId: string, input: PropertyBEModel): Observable<PropertyBEModel> { + + return this.http.post(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/resourceInstance/' + componentInstanceId + '/input', input) + .map((res: Response) => { + return new PropertyBEModel(res.json()); + }) + } + + +} diff --git a/catalog-ui/src/app/ng2/services/component-mode.service.ts b/catalog-ui/src/app/ng2/services/component-mode.service.ts new file mode 100644 index 0000000000..12a581e5f9 --- /dev/null +++ b/catalog-ui/src/app/ng2/services/component-mode.service.ts @@ -0,0 +1,29 @@ +/** + * Created by rc2122 on 5/23/2017. + */ +import { Injectable } from '@angular/core'; +import {WorkspaceMode, ComponentState, Role} from "../../utils/constants"; +import { Component as ComponentData } from "app/models"; +import { CacheService } from "app/services/cache-service" + +@Injectable() + +export class ComponentModeService { + + constructor(private cacheService:CacheService) { + } + + public getComponentMode = (component:ComponentData):WorkspaceMode => {//return if is edit or view for resource or service + let mode = WorkspaceMode.VIEW; + + let user = this.cacheService.get("user"); + if (component.lifecycleState === ComponentState.NOT_CERTIFIED_CHECKOUT && + component.lastUpdaterUserId === user.userId) { + if ((component.isService() || component.isResource()) && user.role == Role.DESIGNER) { + mode = WorkspaceMode.EDIT; + } + } + return mode; + } +} + diff --git a/catalog-ui/src/app/ng2/services/component-services/component.service.ts b/catalog-ui/src/app/ng2/services/component-services/component.service.ts new file mode 100644 index 0000000000..3fa9fde40c --- /dev/null +++ b/catalog-ui/src/app/ng2/services/component-services/component.service.ts @@ -0,0 +1,149 @@ +import {Injectable, Query} from '@angular/core'; +import {Observable} from 'rxjs/Observable'; +import 'rxjs/add/operator/map'; +import 'rxjs/add/operator/toPromise'; +import {Response, URLSearchParams} from '@angular/http'; +import { Component, PropertyBEModel, InstancePropertiesAPIMap, FilterPropertiesAssignmentData} from "app/models"; +import {downgradeInjectable} from '@angular/upgrade/static'; +import {HttpService} from "../http.service"; +import {COMPONENT_FIELDS} from "app/utils"; +import {ComponentGenericResponse} from "../responses/component-generic-response"; +import {sdc2Config} from "../../../../main"; +import {InstanceBePropertiesMap} from "../../../models/properties-inputs/property-fe-map"; +import {API_QUERY_PARAMS} from "app/utils"; +import {ComponentType, ServerTypeUrl} from "../../../utils/constants"; + +declare var angular:angular.IAngularStatic; + +@Injectable() +export class ComponentServiceNg2 { + + protected baseUrl; + + constructor(private http:HttpService) { + this.baseUrl = sdc2Config.api.root + sdc2Config.api.component_api_root; + } + + private getComponentDataByFieldsName(componentType:string, componentId: string, fields:Array<string>):Observable<ComponentGenericResponse> { + + let params:URLSearchParams = new URLSearchParams(); + _.forEach(fields, (field:string):void => { + params.append(API_QUERY_PARAMS.INCLUDE, field); + }); + + return this.http.get(this.baseUrl + this.getServerTypeUrl(componentType) + componentId + '/filteredDataByParams', {search: params}) + .map((res:Response) => { + return new ComponentGenericResponse().deserialize(res.json()); + }).do(error => console.log('server data:', error)); + } + + private getServerTypeUrl = (componentType:string):string => { + switch (componentType) { + case ComponentType.PRODUCT: + return ServerTypeUrl.PRODUCTS; + case ComponentType.SERVICE: + return ServerTypeUrl.SERVICES; + default: + return ServerTypeUrl.RESOURCES; + } + } + + getComponentMetadata(component:Component):Observable<ComponentGenericResponse> { + return this.getComponentDataByFieldsName(component.componentType, component.uniqueId, [COMPONENT_FIELDS.COMPONENT_METADATA]); + } + + getComponentInstanceAttributesAndProperties(component:Component):Observable<ComponentGenericResponse> { + return this.getComponentDataByFieldsName(component.componentType, component.uniqueId, [COMPONENT_FIELDS.COMPONENT_INSTANCES_PROPERTIES, COMPONENT_FIELDS.COMPONENT_INSTANCES_ATTRIBUTES]); + } + + getComponentAttributes(component:Component):Observable<ComponentGenericResponse> { + return this.getComponentDataByFieldsName(component.componentType, component.uniqueId, [COMPONENT_FIELDS.COMPONENT_ATTRIBUTES]); + } + + getComponentInstancesAndRelation(component:Component):Observable<ComponentGenericResponse> { + return this.getComponentDataByFieldsName(component.componentType, component.uniqueId, [COMPONENT_FIELDS.COMPONENT_INSTANCES_RELATION, COMPONENT_FIELDS.COMPONENT_INSTANCES]); + } + + getComponentResourceInstances(component:Component):Observable<ComponentGenericResponse> { + return this.getComponentDataByFieldsName(component.componentType, component.uniqueId, [COMPONENT_FIELDS.COMPONENT_INSTANCES]); + } + + getComponentInputs(component:Component):Observable<ComponentGenericResponse> { + return this.getComponentDataByFieldsName(component.componentType, component.uniqueId, [COMPONENT_FIELDS.COMPONENT_INPUTS]); + } + + getComponentDeploymentArtifacts(component:Component):Observable<ComponentGenericResponse> { + return this.getComponentDataByFieldsName(component.componentType, component.uniqueId, [COMPONENT_FIELDS.COMPONENT_DEPLOYMENT_ARTIFACTS]); + } + + getComponentInformationalArtifacts(component:Component):Observable<ComponentGenericResponse> { + return this.getComponentDataByFieldsName(component.componentType, component.uniqueId, [COMPONENT_FIELDS.COMPONENT_INFORMATIONAL_ARTIFACTS]); + } + + getComponentInformationalArtifactsAndInstances(component:Component):Observable<ComponentGenericResponse> { + return this.getComponentDataByFieldsName(component.componentType, component.uniqueId, [COMPONENT_FIELDS.COMPONENT_INFORMATIONAL_ARTIFACTS, COMPONENT_FIELDS.COMPONENT_INSTANCES]); + } + + getComponentToscaArtifacts(component:Component):Observable<ComponentGenericResponse> { + return this.getComponentDataByFieldsName(component.componentType, component.uniqueId, [COMPONENT_FIELDS.COMPONENT_TOSCA_ARTIFACTS]); + } + + getComponentProperties(component:Component):Observable<ComponentGenericResponse> { + return this.getComponentDataByFieldsName(component.componentType, component.uniqueId, [COMPONENT_FIELDS.COMPONENT_PROPERTIES]); + } + + getCapabilitiesAndRequirements(componentType: string, componentId:string):Observable<ComponentGenericResponse> { + return this.getComponentDataByFieldsName(componentType, componentId, [COMPONENT_FIELDS.COMPONENT_REQUIREMENTS, COMPONENT_FIELDS.COMPONENT_CAPABILITIES]); + } + + getDeploymentGraphData(component:Component):Observable<ComponentGenericResponse> { + return this.getComponentDataByFieldsName(component.componentType, component.uniqueId, [COMPONENT_FIELDS.COMPONENT_INSTANCES_RELATION, COMPONENT_FIELDS.COMPONENT_INSTANCES, COMPONENT_FIELDS.COMPONENT_GROUPS]); + } + + createInput(component:Component, inputsToCreate:InstancePropertiesAPIMap):Observable<any> { + return this.http.post(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/create/inputs', inputsToCreate) + .map(res => { + return res.json(); + }) + } + + deleteInput(component:Component, input:PropertyBEModel):Observable<PropertyBEModel> { + + return this.http.delete(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/delete/' + input.uniqueId + '/input') + .map((res:Response) => { + return new PropertyBEModel(res.json()); + }) + } + + updateComponentInput(component:Component, input:PropertyBEModel):Observable<PropertyBEModel> { + + return this.http.post(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/update/inputs', input) + .map((res:Response) => { + return new PropertyBEModel(res.json()) + }) + } + + filterComponentInstanceProperties(component: Component, filterData:FilterPropertiesAssignmentData): Observable<InstanceBePropertiesMap> {//instance-property-be-map + let params: URLSearchParams = new URLSearchParams(); + _.forEach(filterData.selectedTypes, (type:string) => { + params.append('resourceType', type); + }); + + return this.http.get(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/filteredproperties/' + filterData.propertyName, {search: params}) + .map((res: Response) => { + return res.json(); + }); + + // return {'ExtVL 0':[{definition: false,name:"network_assignments",password:false,required:true,type:"org.openecomp.datatypes.network.NetworkAssignments",uniqueId:"623cca1c-d605-4c9c-9f2b-935ec85ebcf8.network_assignments"}, + // {definition: false,name: "exVL_naming",password: false,required: true,type: "org.openecomp.datatypes.Naming",uniqueId: "623cca1c-d605-4c9c-9f2b-935ec85ebcf8.exVL_naming"}, + // {definition: false,name: "network_flows",password: false,required: false,type: "org.openecomp.datatypes.network.NetworkFlows",uniqueId: "623cca1c-d605-4c9c-9f2b-935ec85ebcf8.network_flows"}, + // {definition: false,name: "provider_network",password: false,required: true,type: "org.openecomp.datatypes.network.ProviderNetwork",uniqueId: "623cca1c-d605-4c9c-9f2b-935ec85ebcf8.provider_network"}, + // {definition: false,name: "network_homing",password: false,required: true,type: "org.openecomp.datatypes.EcompHoming",uniqueId: "623cca1c-d605-4c9c-9f2b-935ec85ebcf8.network_homing"}], + // 'NetworkCP 0':[{definition: false,description: "identifies MAC address assignments to the CP",name: "mac_requirements",password: false,required: false,type: "org.openecomp.datatypes.network.MacRequirements",uniqueId: "26ec2bfd-b904-46c7-87ed-b32775120f2c.mac_requirements"}], + // 'NetworkCP 1':[{definition: false,description: "identifies MAC address assignments to the CP",name: "mac_requirements",password: false,required: false,type: "org.openecomp.datatypes.network.MacRequirements",uniqueId: "26ec2bfd-b904-46c7-87ed-b32775120f2c.mac_requirements"}]}; + + + } +} + +angular.module('Sdc.Services').factory('ComponentServiceNg2', downgradeInjectable(ComponentServiceNg2)); // This is in order to use the service in angular1 till we finish remove all angular1 code diff --git a/catalog-ui/src/app/ng2/services/component-services/resource.service.ts b/catalog-ui/src/app/ng2/services/component-services/resource.service.ts new file mode 100644 index 0000000000..650f244d38 --- /dev/null +++ b/catalog-ui/src/app/ng2/services/component-services/resource.service.ts @@ -0,0 +1,18 @@ +import { Injectable } from '@angular/core'; +import 'rxjs/add/operator/map'; +import 'rxjs/add/operator/toPromise'; +import { Http, Response, Headers, RequestOptions } from '@angular/http'; + +@Injectable() +export class ResourceServiceNg2 { + + protected baseUrl = ""; + + constructor(private http: Http) { + + } + + + + +} diff --git a/catalog-ui/src/app/ng2/services/component-services/service.service.ts b/catalog-ui/src/app/ng2/services/component-services/service.service.ts new file mode 100644 index 0000000000..d2f7078599 --- /dev/null +++ b/catalog-ui/src/app/ng2/services/component-services/service.service.ts @@ -0,0 +1,31 @@ +import { Injectable } from '@angular/core'; +import { Observable } from 'rxjs/Observable'; +import 'rxjs/add/operator/map'; +import 'rxjs/add/operator/toPromise'; +import { Response } from '@angular/http'; +import {Service} from "app/models"; +import { downgradeInjectable } from '@angular/upgrade/static'; +import {sdc2Config} from "../../../../main"; +import {HttpService} from "../http.service"; + + +@Injectable() +export class ServiceServiceNg2 { + + protected baseUrl = ""; + + constructor(private http: HttpService) { + this.baseUrl = sdc2Config.api.root + sdc2Config.api.component_api_root; + } + + validateConformanceLevel(service: Service): Observable<boolean> { + + return this.http.get(this.baseUrl + service.getTypeUrl() + service.uuid + '/conformanceLevelValidation') + .map((res: Response) => { + return res.json(); + }); + } + +} + +angular.module('Sdc.Services').factory('ServiceServiceNg2', downgradeInjectable(ServiceServiceNg2)); // This is in order to use the service in angular1 till we finish remove all angular1 code diff --git a/catalog-ui/src/app/ng2/services/config.service.ts b/catalog-ui/src/app/ng2/services/config.service.ts new file mode 100644 index 0000000000..0ac3b5a397 --- /dev/null +++ b/catalog-ui/src/app/ng2/services/config.service.ts @@ -0,0 +1,51 @@ +/** + * Created by ob0695 on 4/9/2017. + */ + +import { Injectable } from '@angular/core'; +import { Http, Response } from '@angular/http'; +import 'rxjs/add/operator/toPromise'; +import {IAppConfigurtaion, ValidationConfiguration, Validations} from "app/models"; +import {sdc2Config} from './../../../main'; + +declare var __ENV__: string; + +@Injectable() +export class ConfigService { + + private baseUrl; + public configuration: IAppConfigurtaion; + + constructor(private http: Http) { + this.baseUrl = sdc2Config.api.root + sdc2Config.api.component_api_root; + } + + loadValidationConfiguration(): Promise<ValidationConfiguration> { + let url: string = sdc2Config.validationConfigPath; + let promise: Promise<ValidationConfiguration> = this.http.get(url).map((res: Response) => res.json()).toPromise(); + promise.then((validationData: Validations) => { + ValidationConfiguration.validation = validationData; + }).catch((ex) => { + console.error('Error loading validation.json configuration file, using fallback data', ex); + + let fallback:Validations = { + "propertyValue": { + "max": 2500, + "min": 0 + }, + + "validationPatterns": { + "string": "^[\\sa-zA-Z0-9+-]+$", + "comment": "^[\\sa-zA-Z0-9+-_\\{\\}\"]+$", + "integer": "^(([-+]?\\d+)|([-+]?0x[0-9a-fA-F]+))$" + } + }; + + ValidationConfiguration.validation = fallback; + + }); + + return promise; + } + +} diff --git a/catalog-ui/src/app/ng2/services/cookie.service.ts b/catalog-ui/src/app/ng2/services/cookie.service.ts new file mode 100644 index 0000000000..2dc2ac3e6b --- /dev/null +++ b/catalog-ui/src/app/ng2/services/cookie.service.ts @@ -0,0 +1,65 @@ +import { Injectable } from '@angular/core'; +import {IAppConfigurtaion, ICookie} from "../../models/app-config"; +import {sdc2Config} from './../../../main'; + +@Injectable() +export class Cookie2Service { + + private cookie:ICookie; + private cookiePrefix:string; + + constructor() { + this.cookie = sdc2Config.cookie; + + this.cookiePrefix = ''; + let junctionName:string = this.getCookieByName(this.cookie.junctionName); + if ((junctionName !== null) && (junctionName !== '')) { + this.cookiePrefix = this.cookie.prefix + junctionName + '!'; + } + console.log("junctionName:" + junctionName); + } + + private getCookieByName = (cookieName:string):string => { + cookieName += '='; + let cookies:Array<string> = document.cookie.split(';'); + let cookieVal:string = ''; + cookies.forEach((cookie:string) => { + while (cookie.charAt(0) === ' ') { + cookie = cookie.substring(1); + } + if (cookie.indexOf(cookieName) === 0) { + cookieVal = cookie.substring(cookieName.length, cookie.length); + return; + } + }); + return cookieVal; + }; + + public getUserIdSuffix = ():string => { + return this.cookie.userIdSuffix; + }; + + public getUserId = ():string => { + let userIdCookieName:string = this.cookiePrefix + this.cookie.userIdSuffix; + let userId:string = this.getCookieByName(userIdCookieName); + return userId; + }; + + public getFirstName = ():string => { + let firstNameCookieName:string = this.cookiePrefix + this.cookie.userFirstName; + let firstName:string = this.getCookieByName(firstNameCookieName); + return firstName; + }; + + public getLastName = ():string => { + let lastNameCookieName:string = this.cookiePrefix + this.cookie.userLastName; + let lastName:string = this.getCookieByName(lastNameCookieName); + return lastName; + }; + + public getEmail = ():string => { + let emailCookieName:string = this.cookiePrefix + this.cookie.userEmail; + let email:string = this.getCookieByName(emailCookieName); + return email; + } +} diff --git a/catalog-ui/src/app/ng2/services/data-type.service.ts b/catalog-ui/src/app/ng2/services/data-type.service.ts new file mode 100644 index 0000000000..821c215be5 --- /dev/null +++ b/catalog-ui/src/app/ng2/services/data-type.service.ts @@ -0,0 +1,62 @@ +import { Injectable } from '@angular/core'; +import { DataTypeModel, DataTypesMap, PropertyBEModel, PropertyFEModel, DerivedFEProperty, DerivedFEPropertyMap } from "app/models"; +import { DataTypesService } from "app/services/data-types-service"; +import { PROPERTY_DATA, PROPERTY_TYPES } from "app/utils"; + +/** This is a new service for NG2, to eventually replace app/services/data-types-service.ts + * + * This service is a singleton that holds a map of all DataTypes, recieved from server on load. + * It also contains convenience methods to check if a string is a valid dataType, and to retrieve a dataType's properties recursively + */ + +@Injectable() +export class DataTypeService { + private dataTypes: DataTypesMap; + + constructor(private dataTypeService: DataTypesService) { + this.dataTypes = dataTypeService.getAllDataTypes(); //This should eventually be replaced by an NG2 call to the backend instead of utilizing Angular1 downgraded component. + } + + public getDataTypeByTypeName(typeName: string): DataTypeModel { + return this.dataTypes[typeName]; + } + + + public getDerivedDataTypeProperties(dataTypeObj: DataTypeModel, propertiesArray: Array<DerivedFEProperty>, parentName: string) { + //push all child properties to array + if (dataTypeObj.properties) { + dataTypeObj.properties.forEach((derivedProperty) => { + if(dataTypeObj.name !== PROPERTY_DATA.OPENECOMP_ROOT || derivedProperty.name !== PROPERTY_DATA.SUPPLEMENTAL_DATA){//The requirement is to not display the property supplemental_data + propertiesArray.push(new DerivedFEProperty(derivedProperty, parentName)); + } + let derivedDataTypeObj: DataTypeModel = this.getDataTypeByTypeName(derivedProperty.type); + this.getDerivedDataTypeProperties(derivedDataTypeObj, propertiesArray, parentName + "#" + derivedProperty.name); + }); + } + //recurse parent (derivedFrom), in case one of parents contains properties + if (PROPERTY_DATA.ROOT_DATA_TYPE !== dataTypeObj.derivedFrom.name) { + this.getDerivedDataTypeProperties(dataTypeObj.derivedFrom, propertiesArray, parentName); + } + } + + /** + * Checks for custom behavior for a given data type by checking if a function exists within data-type.service with that name + * Additional custom behavior can be added by adding a function with the given dataType name + */ + public checkForCustomBehavior = (property:PropertyFEModel) => { + let shortTypeName:string = property.type.split('.').pop(); + if (this[shortTypeName]) { + this[shortTypeName](property); //execute function for given type, pass property as param + } + } + + public Naming = (property: PropertyFEModel) => { + let generatedNamingVal: boolean = _.get(property.valueObj, 'ecomp_generated_naming', true); + property.flattenedChildren.forEach((prop) => { + if (prop.name == 'naming_policy') prop.hidden = !generatedNamingVal; + if (prop.name == 'instance_name') prop.hidden = generatedNamingVal; + }); + } + +} + diff --git a/catalog-ui/src/app/ng2/services/hierarchy-nav.service.ts b/catalog-ui/src/app/ng2/services/hierarchy-nav.service.ts new file mode 100644 index 0000000000..512505d7c6 --- /dev/null +++ b/catalog-ui/src/app/ng2/services/hierarchy-nav.service.ts @@ -0,0 +1,63 @@ +import { Injectable } from '@angular/core'; +import { SimpleFlatProperty, PropertyFEModel, DerivedFEProperty } from 'app/models'; + + +@Injectable() +export class HierarchyNavService { + /** + * Build hirarchy structure for the tree when user selects on table row. + * First create Array<SimpleFlatProperty> and insert also the parent (PropertyFEModel) to this array. + * The Array is flat and contains SimpleFlatProperty that has parentName and uniqueId. + * Now we build hirarchy from this Array (that includes childrens) and return it for the tree + * + * @argument property: PropertyFEModel - property contains flattenedChildren array of DerivedFEProperty + * @returns Array<SimpleFlatProperty> - containing childrens Array<SimpleFlatProperty>, augmantin childrens to SimpleFlatProperty. + */ + public getSimplePropertiesTree(property: PropertyFEModel, instanceName: string): Array<SimpleFlatProperty> { + // Build Array of SimpleFlatProperty before unflatten function + let flattenProperties: Array<SimpleFlatProperty> = []; + flattenProperties.push(this.createSimpleFlatProperty(property, instanceName)); // Push the root property + _.each(property.flattenedChildren, (child: DerivedFEProperty): void => { + if (child.isChildOfListOrMap && child.schema.property.isSimpleType) return; //do not display non-complex children of list or map + flattenProperties.push(this.createSimpleFlatProperty(child, instanceName)); + }); + + let tree = this.unflatten(flattenProperties, '', []); + return tree[0].childrens; // Return the childrens without the root. + } + + public createSimpleFlatProperty = (property: PropertyFEModel | DerivedFEProperty, instanceName:string): SimpleFlatProperty => { + if (property instanceof PropertyFEModel) { + return new SimpleFlatProperty(property.uniqueId, property.name, property.name, '', instanceName); + } else { + let propName: string = (property.isChildOfListOrMap) ? property.mapKey : property.name; + return new SimpleFlatProperty(property.uniqueId, property.propertiesName, propName, property.parentName, instanceName); + } + + } + + /** + * Unflatten Array<SimpleFlatProperty> and build hirarchy. + * The result will be Array<SimpleFlatProperty> that augmantin with childrens for each SimpleFlatProperty. + */ + private unflatten(array: Array<SimpleFlatProperty>, parent: any, tree?: any): any { + tree = typeof tree !== 'undefined' ? tree : []; + parent = typeof parent !== 'undefined' && parent !== '' ? parent : { path: '' }; + + var childrens = _.filter(array, (child: SimpleFlatProperty): boolean => { + return child.parentName == parent.path; + }); + + if (!_.isEmpty(childrens)) { + if (parent.path == '') { + tree = childrens; + } else { + parent['childrens'] = childrens; + } + _.each(childrens, (child): void => { + this.unflatten(array, child); + }); + } + return tree; + } +}
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/services/http.service.ts b/catalog-ui/src/app/ng2/services/http.service.ts new file mode 100644 index 0000000000..92e8ced142 --- /dev/null +++ b/catalog-ui/src/app/ng2/services/http.service.ts @@ -0,0 +1,73 @@ +import {Injectable} from '@angular/core'; +import {Http, XHRBackend, RequestOptions, Request, RequestOptionsArgs, Response, Headers} from '@angular/http'; +import {Observable} from 'rxjs/Observable'; +import {UUID} from 'angular2-uuid'; +import 'rxjs/add/operator/map'; +import 'rxjs/add/operator/catch'; +import {Dictionary} from "../../utils/dictionary/dictionary"; +import {SharingService, CookieService} from "app/services"; +import {sdc2Config} from './../../../main'; + +@Injectable() +export class HttpService extends Http { + + constructor(backend:XHRBackend, options:RequestOptions, private sharingService:SharingService, private cookieService: CookieService) { + super(backend, options); + this._defaultOptions.withCredentials = true; + this._defaultOptions.headers.append(cookieService.getUserIdSuffix(), cookieService.getUserId()); + } + + request(request:string|Request, options?:RequestOptionsArgs):Observable<Response> { + /** + * For every request to the server, that the service id, or resource id is sent in the URL, need to pass UUID in the header. + * Check if the unique id exists in uuidMap, and if so get the UUID and add it to the header. + */ + if (typeof request === 'string') { // meaning we have to add the token to the options, not in url + if (!options) { + // make option object + options = {headers: new Headers()}; + } + + var uuidValue = this.getUuidValue(request); + if(uuidValue!= ''){ + options.headers['X-ECOMP-ServiceID'] = uuidValue; + + } + options.headers.set('X-ECOMP-RequestID', UUID.UUID()); + + } else { + // we have to add the token to the url object + var uuidValue = this.getUuidValue((<Request>request).url); + if(uuidValue!= ''){ + request.headers.set('X-ECOMP-ServiceID',uuidValue); + + } + request.headers.set('X-ECOMP-RequestID', UUID.UUID()); + } + return super.request(request, options).catch(this.catchAuthError(this)); + } + + private getUuidValue = (url: string) :string => { + let map:Dictionary<string, string> = this.sharingService.getUuidMap(); + if (map && url.indexOf(sdc2Config.api.root) > 0) { + map.forEach((key:string) => { + if (url.indexOf(key) !== -1) { + return this.sharingService.getUuidValue(key); + } + }); + } + return ''; + } + + private catchAuthError(self:HttpService) { + // we have to pass HttpService's own instance here as `self` + return (res:Response) => { + console.log(res); + if (res.status === 401 || res.status === 403) { + // if not authenticated + console.log(res); + } + return Observable.throw(res); + }; + } +} diff --git a/catalog-ui/src/app/ng2/services/mocks/properties.mock.ts b/catalog-ui/src/app/ng2/services/mocks/properties.mock.ts new file mode 100644 index 0000000000..0ce253e80b --- /dev/null +++ b/catalog-ui/src/app/ng2/services/mocks/properties.mock.ts @@ -0,0 +1,16 @@ +// import { PropertiesResponse } from './../../services/responses/properties.response'; +// +// export const PROPERTIES_RESPONSE: PropertiesResponse = { +// "status":"ok", +// "errorcode":0, +// "properties": [ +// {"name": "name1"}, +// {"name": "name2"} +// ] +// }; + + +export const COMPONENT_INSTANCE_RESPONSE: any = [{"uniqueId":"23b78e64-1734-4898-889d-eab9eba50019.724ef51f-7595-4336-a8ef-b144e1c937da.contrailv2vlansubinterface5","name":"CP2","normalizedName":"cp2","componentUid":"724ef51f-7595-4336-a8ef-b144e1c937da","creationTime":1489586037851,"modificationTime":1489586037851,"posX":"549","posY":"531","propertyValueCounter":1,"inputValueCounter":1,"originType":"CP","customizationUUID":"0222925a-06c6-48ca-8573-13ba3b650539"},{"uniqueId":"23b78e64-1734-4898-889d-eab9eba50019.8ac5a229-60d8-4d7f-8e0f-b630da55c3ae.ldsa_os27","name":"VF22","normalizedName":"vf22","componentUid":"8ac5a229-60d8-4d7f-8e0f-b630da55c3ae","creationTime":1489590640842,"modificationTime":1489590640842,"posX":"644","posY":"359","propertyValueCounter":1,"inputValueCounter":1,"originType":"VF","customizationUUID":"fb34e6a1-f0cc-42e3-853f-178fb122d670"},{"uniqueId":"23b78e64-1734-4898-889d-eab9eba50019.00ac92c4-a0c5-4567-aefb-f2b7f3fecba9.vl4","name":"VL1","normalizedName":"vl1","componentUid":"00ac92c4-a0c5-4567-aefb-f2b7f3fecba9","creationTime":1489586006630,"modificationTime":1489586006630,"posX":"486","posY":"530","propertyValueCounter":1,"inputValueCounter":1,"originType":"VL","customizationUUID":"8d156a97-38ca-4637-a6a6-9268dd708431"},{"uniqueId":"23b78e64-1734-4898-889d-eab9eba50019.39bf293d-2aa9-4596-b85d-a6fdef18aadc.newvf428","name":"VF11","normalizedName":"vf11","componentUid":"39bf293d-2aa9-4596-b85d-a6fdef18aadc","creationTime":1489590742501,"modificationTime":1489590742501,"posX":"350","posY":"76","propertyValueCounter":1,"inputValueCounter":1,"originType":"VF","customizationUUID":"253f646d-87d0-4b84-bc20-20343b6e28a2"},{"uniqueId":"23b78e64-1734-4898-889d-eab9eba50019.f8f30484-5761-4de8-9de8-12f288ee0a54.contrailport7","name":"CP1","normalizedName":"cp1","componentUid":"f8f30484-5761-4de8-9de8-12f288ee0a54","creationTime":1489587207447,"modificationTime":1489587207447,"posX":"418","posY":"531","propertyValueCounter":1,"inputValueCounter":1,"originType":"CP","customizationUUID":"206b4ffa-e520-496a-b57b-0c103eff8c17"},{"uniqueId":"23b78e64-1734-4898-889d-eab9eba50019.16022d8c-80cf-46e5-a730-5b4f634ea905.extcp38","name":"ExtCP 38","normalizedName":"extcp38","componentUid":"16022d8c-80cf-46e5-a730-5b4f634ea905","creationTime":1489654844116,"modificationTime":1489654844116,"posX":"473","posY":"419","propertyValueCounter":5,"inputValueCounter":1,"originType":"CP","customizationUUID":"21ebc94a-65b9-463e-b696-07cea08f789a"},{"uniqueId":"23b78e64-1734-4898-889d-eab9eba50019.f8f30484-5761-4de8-9de8-12f288ee0a54.contrailport44","name":"ContrailPort 44","normalizedName":"contrailport44","componentUid":"f8f30484-5761-4de8-9de8-12f288ee0a54","creationTime":1489939897435,"modificationTime":1489939897435,"posX":"480","posY":"265","propertyValueCounter":1,"inputValueCounter":1,"originType":"CP","customizationUUID":"607e5819-fa19-4aac-97f6-04f092493a02"},{"uniqueId":"23b78e64-1734-4898-889d-eab9eba50019.8ac5a229-60d8-4d7f-8e0f-b630da55c3ae.ldsa_os6","name":"VF2","normalizedName":"vf2","componentUid":"8ac5a229-60d8-4d7f-8e0f-b630da55c3ae","creationTime":1489586613957,"modificationTime":1489586613957,"posX":"638","posY":"531","propertyValueCounter":1,"inputValueCounter":1,"originType":"VF","customizationUUID":"12501356-44cc-427b-8749-c114d3434271"},{"uniqueId":"23b78e64-1734-4898-889d-eab9eba50019.8ac5a229-60d8-4d7f-8e0f-b630da55c3ae.ldsa_os8","name":"VF1","normalizedName":"vf1","componentUid":"8ac5a229-60d8-4d7f-8e0f-b630da55c3ae","creationTime":1489587393612,"modificationTime":1489587393612,"posX":"334","posY":"529","propertyValueCounter":1,"inputValueCounter":1,"originType":"VF","customizationUUID":"cbca1972-e64c-4119-a430-ec90aa1397a7"},{"uniqueId":"23b78e64-1734-4898-889d-eab9eba50019.8ac5a229-60d8-4d7f-8e0f-b630da55c3ae.ldsa_os32","name":"VF12","normalizedName":"vf12","componentUid":"8ac5a229-60d8-4d7f-8e0f-b630da55c3ae","creationTime":1489591429630,"modificationTime":1489591429630,"posX":"367","posY":"269","propertyValueCounter":1,"inputValueCounter":1,"originType":"VF","customizationUUID":"ac9c77c1-e84d-4967-9ea2-d929b56d10a8"}]; +export const COMPONENT_INPUT_RESPONSE: any = [{"uniqueId":"23b78e64-1734-4898-889d-eab9eba50019.extcp38_mac_requirements_mac_range_plan","type":"string","required":true,"definition":false,"description":"reference to a MAC address range plan","password":false,"name":"extcp38_mac_requirements_mac_range_plan","parentUniqueId":"23b78e64-1734-4898-889d-eab9eba50019"},{"uniqueId":"23b78e64-1734-4898-889d-eab9eba50019.extcp38_order","type":"integer","required":true,"definition":false,"description":"The order of the CP on the compute instance (e.g. eth2).","schema":{"property":{"definition":true,"password":false}},"password":false,"name":"extcp38_order","parentUniqueId":"23b78e64-1734-4898-889d-eab9eba50019"},{"uniqueId":"23b78e64-1734-4898-889d-eab9eba50019.extcp38_network_role_tag","type":"string","required":true,"definition":false,"description":"Must correlate to the set of defined “network-role” tag identifiers from the associated HEAT template","schema":{"property":{"definition":true,"password":false}},"password":false,"name":"extcp38_network_role_tag","parentUniqueId":"23b78e64-1734-4898-889d-eab9eba50019"}]; +export const COMPONENT_PROPERTIES_RESPONSE: any =[{"schema":{"property":{"password":false,"definition":false}},"password":false,"parentUniqueId":"b80765f0-ef2b-43b1-b658-04e13970b5cf","defaultValue":"{\"naming_policy\":\"ggg\",\"ecomp_generated_naming\":false,\"supplemental_data\":{\"fff\":\"44\",\"uuu\":\"56\"}}","name":"inner-map-1","definition":false,"type":"org.openecomp.datatypes.EcompNaming","uniqueId":"property.b80765f0-ef2b-43b1-b658-04e13970b5cf.inner-map-1","required":false},{"schema":{"property":{"password":false,"definition":false}},"password":false,"parentUniqueId":"b80765f0-ef2b-43b1-b658-04e13970b5cf","defaultValue":"{\"min_subnets_count\":1,\"supplemental_data\":{\"aa\":\"11\",\"bb\":\"22\"},\"cidr_mask\":23,\"dhcp_enabled\":false,\"ip_version\":3}","name":"inner-simple-map","definition":false,"type":"org.openecomp.datatypes.network.IPv4SubnetAssignments","uniqueId":"property.b80765f0-ef2b-43b1-b658-04e13970b5cf.inner-simple-map","required":false},{"schema":{"property":{"password":false,"definition":false,"type":"org.openecomp.datatypes.heat.network.AddressPair"}},"password":false,"parentUniqueId":"b80765f0-ef2b-43b1-b658-04e13970b5cf","defaultValue":"{\"aaa\":{\"mac_address\":\"34\",\"ip_address\":\"56\"},\"bbb\":{\"mac_address\":\"sddf\",\"ip_address\":\"dfdg\"}}","name":"data-type-map","definition":false,"type":"map","uniqueId":"property.b80765f0-ef2b-43b1-b658-04e13970b5cf.data-type-map","required":false},{"schema":{"property":{"password":false,"definition":false,"type":"string"}},"password":false,"parentUniqueId":"b80765f0-ef2b-43b1-b658-04e13970b5cf","defaultValue":"{\"www\":\"dfsdf\",\"aaaa\":\"ert\"}","name":"simple-map","definition":false,"type":"map","uniqueId":"property.b80765f0-ef2b-43b1-b658-04e13970b5cf.simple-map","required":false}]; + diff --git a/catalog-ui/src/app/ng2/services/posts.service.ts b/catalog-ui/src/app/ng2/services/posts.service.ts new file mode 100644 index 0000000000..dbfd44f219 --- /dev/null +++ b/catalog-ui/src/app/ng2/services/posts.service.ts @@ -0,0 +1,54 @@ +import { Injectable } from '@angular/core'; +import { Observable } from 'rxjs/Observable'; +import 'rxjs/add/operator/map'; +import 'rxjs/add/operator/toPromise'; +import 'rxjs/Rx'; +import {Response, Headers, RequestOptions, Http} from '@angular/http'; +import { COMPONENT_INSTANCE_RESPONSE,COMPONENT_INPUT_RESPONSE,COMPONENT_PROPERTIES_RESPONSE } from './mocks/properties.mock'; +import { HttpService } from './http.service'; +import { sdc2Config } from './../../../main'; +import {IAppConfigurtaion} from "../../models/app-config"; + +@Injectable() +export class PostsService { + + private base; + + constructor(private http: HttpService) { + this.base = sdc2Config.api.root; + } + + getAppVersion(): Observable<JSON> { + return this.http + .get(this.base + sdc2Config.api.GET_SDC_Version) + .map((res: Response) => res.json()); + } + + // getProperties(id:string): Observable<any> { + // return this.http + // .get(this.base + sdc2Config.api.GET_SDC_Version) + // .map((res: Response) => res.json()); + // } + + getProperties(): Observable<any> { + return Observable.create(observer => { + observer.next(COMPONENT_PROPERTIES_RESPONSE); + observer.complete(); + }); + } + + getInstance(): Observable<any> { + return Observable.create(observer => { + observer.next(COMPONENT_INSTANCE_RESPONSE); + observer.complete(); + }); + } + + getInputs(): Observable<any> { + return Observable.create(observer => { + observer.next(COMPONENT_INPUT_RESPONSE); + observer.complete(); + }); + } + +} diff --git a/catalog-ui/src/app/ng2/services/properties.service.ts b/catalog-ui/src/app/ng2/services/properties.service.ts new file mode 100644 index 0000000000..a22e2aed20 --- /dev/null +++ b/catalog-ui/src/app/ng2/services/properties.service.ts @@ -0,0 +1,68 @@ +import { Injectable } from '@angular/core'; +import { DataTypeModel, PropertyFEModel, PropertyBEModel, SchemaProperty, DerivedFEProperty, DerivedFEPropertyMap, DerivedPropertyType, InputFEModel} from "app/models"; +import { DataTypeService } from "./data-type.service"; +import { PROPERTY_TYPES } from "app/utils"; +import { ContentAfterLastDotPipe } from "../pipes/contentAfterLastDot.pipe"; +import { UUID } from "angular2-uuid"; + +@Injectable() +export class PropertiesService { + + constructor(private dataTypeService: DataTypeService, private contentAfterLastDotPipe: ContentAfterLastDotPipe) { + } + + public getParentPropertyFEModelFromPath = (properties: Array<PropertyFEModel>, path: string) => { + let parent: PropertyFEModel = _.find(properties, (property: PropertyFEModel): boolean => { + return property.name === path.substring(0, path.indexOf('#')); + }); + return parent; + } + + //undo disabling of parent and child props= + public undoDisableRelatedProperties = (property: PropertyFEModel, childPath?: string): void => { + property.isDisabled = false; + if (!childPath) { + property.isSelected = false; + property.flattenedChildren && property.flattenedChildren.map(child => child.isDisabled = false); + } else { //QND - unselect everything and then re-do the disabling of declared props. TODO: put a flag on propertyFEModel instead to indicate who's causing them to be disabled instead + property.flattenedChildren.filter(child => child.isDisabled && !child.isDeclared).map(child => child.isDisabled = false); + property.flattenedChildren.filter(child => child.isDeclared || child.isSelected).forEach((childProp) => { //handle brothers who are selected - redo their disabled relatives as well + this.disableRelatedProperties(property, childProp.propertiesName); + }); + } + } + + //disable parents and children of prop + public disableRelatedProperties = (property: PropertyFEModel, childPath?: string): void => { + if (!childPath) { //selecting the parent property + property.isSelected = true; + property.flattenedChildren && property.flattenedChildren.map(child => { child.isSelected = false; child.isDisabled = true; }); + } else { + property.isSelected = false; + property.isDisabled = true; + property.flattenedChildren.filter((childProp: DerivedFEProperty) => { + return (childProp.propertiesName.indexOf(childPath + "#") === 0 //is child of prop to disable + || childPath.indexOf(childProp.propertiesName + "#") === 0); //is parent of prop to disable + }).map((child: DerivedFEProperty) => { child.isSelected = false; child.isDisabled = true; }); + } + } + + public getCheckedProperties = (properties: Array<PropertyFEModel>): Array<PropertyBEModel> => { + let selectedProps: Array<PropertyBEModel> = []; + properties.forEach(prop => { + if (prop.isSelected && !prop.isDeclared && !prop.isDisabled) { + selectedProps.push(new PropertyBEModel(prop)); + } else if (prop.flattenedChildren) { + prop.flattenedChildren.forEach((child) => { + if (child.isSelected && !child.isDeclared && !child.isDisabled) { + let childProp = new PropertyBEModel(prop, child); //create it from the parent + selectedProps.push(childProp); + } + }) + } + }); + return selectedProps; + } + + +}
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/services/responses/component-generic-response.ts b/catalog-ui/src/app/ng2/services/responses/component-generic-response.ts new file mode 100644 index 0000000000..7dcd95d712 --- /dev/null +++ b/catalog-ui/src/app/ng2/services/responses/component-generic-response.ts @@ -0,0 +1,77 @@ +/** + * Created by ob0695 on 4/18/2017. + */ + +import { ArtifactGroupModel, PropertyModel, PropertiesGroup, AttributeModel, AttributesGroup, ComponentInstance, + InputModel, Module, ComponentMetadata, RelationshipModel, RequirementsGroup, CapabilitiesGroup,InputFEModel} from "app/models"; +import {CommonUtils} from "app/utils"; +import {Serializable} from "../utils/serializable"; +import {PropertyBEModel} from "../../../models/properties-inputs/property-be-model"; + +export class ComponentGenericResponse implements Serializable<ComponentGenericResponse> { + + public metadata: ComponentMetadata; + public deploymentArtifacts:ArtifactGroupModel; + public artifacts:ArtifactGroupModel; + public toscaArtifacts:ArtifactGroupModel; + public componentInstancesProperties:PropertiesGroup; + public componentInstancesAttributes:AttributesGroup; + public componentInstancesRelations:Array<RelationshipModel>; + public componentInstances:Array<ComponentInstance>; + public inputs:Array<PropertyBEModel>; + public capabilities:CapabilitiesGroup; + public requirements:RequirementsGroup; + public properties:Array<PropertyModel>; + public attributes:Array<AttributeModel>; + public groups:Array<Module>; + public interfaces:any; + public additionalInformation:any; + public derivedList:Array<any>; + + deserialize (response): ComponentGenericResponse { + + if(response.componentInstancesProperties) { + this.componentInstancesProperties = new PropertiesGroup(response.componentInstancesProperties); + } + if(response.componentInstancesAttributes) { + this.componentInstancesAttributes = new AttributesGroup(response.componentInstancesAttributes); + } + if(response.componentInstances) { + this.componentInstances = CommonUtils.initComponentInstances(response.componentInstances); + } + if(response.componentInstancesRelations) { + this.componentInstancesRelations = CommonUtils.initComponentInstanceRelations(response.componentInstancesRelations); + } + if(response.deploymentArtifacts) { + this.deploymentArtifacts = new ArtifactGroupModel(response.deploymentArtifacts); + } + if(response.inputs) { + this.inputs = CommonUtils.initInputs(response.inputs); + } + if(response.attributes) { + this.attributes = CommonUtils.initAttributes(response.attributes); + } + if(response.artifacts) { + this.artifacts = new ArtifactGroupModel(response.artifacts); + } + if(response.properties) { + this.properties = CommonUtils.initProperties(response.properties); + } + if(response.capabilities) { + this.capabilities = new CapabilitiesGroup(response.capabilities); + } + if(response.requirements) { + this.requirements = new RequirementsGroup(response.requirements); + } + if(response.toscaArtifacts) { + this.toscaArtifacts = new ArtifactGroupModel(response.toscaArtifacts); + } + if(response.metadata) { + this.metadata = new ComponentMetadata().deserialize(response.metadata); + } + if(response.groups) { + this.groups = CommonUtils.initModules(response.groups); + } + return this; + } +} diff --git a/catalog-ui/src/app/ng2/services/responses/properties.response.ts b/catalog-ui/src/app/ng2/services/responses/properties.response.ts new file mode 100644 index 0000000000..a3d82500eb --- /dev/null +++ b/catalog-ui/src/app/ng2/services/responses/properties.response.ts @@ -0,0 +1,7 @@ +export class PropertiesResponse { + properties: Array<Property>; +} + +class Property { + name: string +} diff --git a/catalog-ui/src/app/ng2/services/utils/serializable.ts b/catalog-ui/src/app/ng2/services/utils/serializable.ts new file mode 100644 index 0000000000..f8be120613 --- /dev/null +++ b/catalog-ui/src/app/ng2/services/utils/serializable.ts @@ -0,0 +1,6 @@ +/** + * Created by ob0695 on 4/26/2017. + */ +export interface Serializable<T> { + deserialize(input: Object): T; +} diff --git a/catalog-ui/src/app/ng2/shared/checkbox/checkbox.component.html b/catalog-ui/src/app/ng2/shared/checkbox/checkbox.component.html new file mode 100644 index 0000000000..872bf90329 --- /dev/null +++ b/catalog-ui/src/app/ng2/shared/checkbox/checkbox.component.html @@ -0,0 +1,8 @@ +<div class="checkbox-container {{checkboxStyle}}"> + <div class="checkbox-animation"></div><!--[@checkEffect]="checked"--> + <label class="checkbox-label" > + <input type="checkbox" class="checkbox-hidden" [ngModel]="checked" (ngModelChange)="toggleState($event)" [disabled]="disabled" /> + <div class="checkbox-icon"></div> + <span *ngIf="label" class="checkbox-label-content">{{label}}</span> + </label> +</div>
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/shared/checkbox/checkbox.component.less b/catalog-ui/src/app/ng2/shared/checkbox/checkbox.component.less new file mode 100644 index 0000000000..3a28c5fb42 --- /dev/null +++ b/catalog-ui/src/app/ng2/shared/checkbox/checkbox.component.less @@ -0,0 +1,67 @@ + @import '../../../../assets/styles/tlv-sprite'; +@import '../../../../assets/styles/sprite'; + + +.checkbox-container { + display:inline-block; + position:relative; + text-align: left; + height: 20px; + + + .checkbox-icon { + display: inline-block; + } + + .checkbox-label { + font-weight: inherit; + font-size: inherit; + } + + .checkbox-label-content { + margin-left:2px; + } + + .checkbox-icon::before { + .tlv-sprite; + background-position: -10px -60px; + width: 14px; + height: 14px; + content: ''; + display: inline-block; + margin-right: 0px; + margin-top: -2px; + vertical-align: middle; + } + + input[type=checkbox].checkbox-hidden { + width:0; + height:0; + display:none; + &:checked ~ .checkbox-icon::before{ + .sprite-new; + .filled-checkbox-icon + } + &[disabled] ~ .checkbox-icon::before { + /* TODO: add disabled styles here */ + background-image: none; + background-color: #EFEFEF; + border-radius: 2px; + border: solid #CCC 1px; + } + } + + .checkbox-animation { + background-color: #009fdb; + position: absolute; + left: 2px; + top: 4px; + width:10px; + height:10px; + border-radius: 50%; + z-index: 1; + pointer-events: none; + opacity:0; + } + +} diff --git a/catalog-ui/src/app/ng2/shared/checkbox/checkbox.component.ts b/catalog-ui/src/app/ng2/shared/checkbox/checkbox.component.ts new file mode 100644 index 0000000000..c1bb28b6ff --- /dev/null +++ b/catalog-ui/src/app/ng2/shared/checkbox/checkbox.component.ts @@ -0,0 +1,30 @@ +import { Component, Input, Output, EventEmitter, ViewEncapsulation } from '@angular/core'; +//import { trigger, state, style, transition, animate, keyframes } from '@angular/core'; + +@Component({ + selector: 'checkbox', + templateUrl: './checkbox.component.html', + styleUrls: ['./checkbox.component.less'], + encapsulation: ViewEncapsulation.None + // animations: [ + // trigger('checkEffect', [ + // state('true', style({ position: 'absolute', left: '2px', top: '5px', width: '10px', height: '10px', display: 'none', opacity: '.5' })), + // state('false', style({ left: '-18px', top: '-15px', height: '50px', width: '50px', opacity: '0' })), + // transition('1 => 0', animate('150ms ease-out')), + // transition('0 => 1', animate('150ms ease-in')) + // ]) + // ] +}) +export class CheckboxComponent { + + @Input() checkboxStyle: string; + @Input() label: string; + @Input() checked: boolean; + @Input() disabled: boolean; + @Output() checkedChange: EventEmitter<any> = new EventEmitter<any>(); + + toggleState(newValue:boolean) { + this.checkedChange.emit(newValue); + } +} + diff --git a/catalog-ui/src/app/ng2/shared/checkbox/checkbox.module.ts b/catalog-ui/src/app/ng2/shared/checkbox/checkbox.module.ts new file mode 100644 index 0000000000..116aa7f025 --- /dev/null +++ b/catalog-ui/src/app/ng2/shared/checkbox/checkbox.module.ts @@ -0,0 +1,28 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; +import { BrowserModule } from '@angular/platform-browser'; +import { CheckboxComponent } from './checkbox.component'; + + +@NgModule({ + imports: [CommonModule, BrowserModule, FormsModule], + declarations: [CheckboxComponent], + bootstrap: [], + exports: [CheckboxComponent] +}) +export class CheckboxModule { } + +/** README: **/ + +/** USAGE Example: + *In page.module.ts: import CheckboxModule + *In HTML: + *<checkbox checkboxStyle="class-goes-here" [label]="'Text goes here'" [disabled]="variable-goes-here" [(checked)]="default-or-variable-goes-here" (checkedChange)="change-event-goes-here()"></checkbox> + */ + +/**STYLING: (ViewEncapsulation is set to None to allow styles to be overridden or customized) + * + * To create or override styles: + * Use /deep/ or >>> prefix to override styles via other components stylesheets + */
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/shared/navbar/navbar-routes.config.ts b/catalog-ui/src/app/ng2/shared/navbar/navbar-routes.config.ts new file mode 100644 index 0000000000..d8a21e66c8 --- /dev/null +++ b/catalog-ui/src/app/ng2/shared/navbar/navbar-routes.config.ts @@ -0,0 +1,7 @@ +import { MenuType, RouteInfo } from './navbar.metadata'; + +export const ROUTES: RouteInfo[] = [ + { path: 'page1', title: 'Logo', menuType: MenuType.BRAND }, + { path: 'page1', title: 'Page 1', menuType: MenuType.LEFT }, + { path: 'page2', title: 'Page 2', menuType: MenuType.LEFT } +]; diff --git a/catalog-ui/src/app/ng2/shared/navbar/navbar.component.html b/catalog-ui/src/app/ng2/shared/navbar/navbar.component.html new file mode 100644 index 0000000000..d783be4c27 --- /dev/null +++ b/catalog-ui/src/app/ng2/shared/navbar/navbar.component.html @@ -0,0 +1,23 @@ +<nav class="navbar navbar-dark"> + <div class="clearfix"> + <button (click)="isCollapsed = !isCollapsed" + class="navbar-toggler pull-xs-right hidden-sm-up" type="button" + aria-controls="bd-main-nav" + aria-label="Toggle navigation"> + {{menuIcon}} + </button> + <a (click)="isCollapsed = true" class="navbar-brand hidden-sm-up" [routerLink]="[brandMenu.path]"> + {{brandMenu.title}} + </a> + </div> + <div class="navbar-toggleable-xs navbar-collapse" id="bd-main-nav" [attr.aria-expanded]="!isCollapsed" [ngClass]="{collapse: isCollapsed}"> + <ul class="nav navbar-nav"> + <li (click)="isCollapsed = true" class="nav-item" routerLinkActive="active"> + <a class="navbar-brand hidden-xs-down" [routerLink]="[brandMenu.path]">{{brandMenu.title}}</a> + </li> + <li (click)="isCollapsed = true" *ngFor="let menuItem of menuItems" class="nav-item" routerLinkActive="active" [ngClass]="getMenuItemClasses(menuItem)"> + <a class="nav-item nav-link" [routerLink]="[menuItem.path]" routerLinkActive="active">{{menuItem.title}}</a> + </li> + </ul> + </div> +</nav> diff --git a/catalog-ui/src/app/ng2/shared/navbar/navbar.component.less b/catalog-ui/src/app/ng2/shared/navbar/navbar.component.less new file mode 100644 index 0000000000..3e5165b798 --- /dev/null +++ b/catalog-ui/src/app/ng2/shared/navbar/navbar.component.less @@ -0,0 +1,11 @@ +.active { + color: #ffffff; +} +.navbar-toggler { + border: solid 1px #cccccc; + color: #ff0000; +} +.navbar { + background-color: #0000ff; + border-radius: 0; +}
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/shared/navbar/navbar.component.ts b/catalog-ui/src/app/ng2/shared/navbar/navbar.component.ts new file mode 100644 index 0000000000..b174f9d18d --- /dev/null +++ b/catalog-ui/src/app/ng2/shared/navbar/navbar.component.ts @@ -0,0 +1,32 @@ +import {Component, OnInit, ViewEncapsulation} from '@angular/core'; +import { ROUTES } from './navbar-routes.config'; +import { MenuType, RouteInfo } from './navbar.metadata'; + +@Component({ + selector: 'app-navbar', + templateUrl: './navbar.component.html', + styleUrls: [ './navbar.component.less' ], + encapsulation: ViewEncapsulation.None +}) +export class NavbarComponent implements OnInit { + public menuItems: Array<RouteInfo>; + public brandMenu: RouteInfo; + isCollapsed = true; + + constructor() {} + + ngOnInit() { + this.menuItems = ROUTES.filter(menuItem => menuItem.menuType !== MenuType.BRAND); + this.brandMenu = ROUTES.filter(menuItem => menuItem.menuType === MenuType.BRAND)[0]; + } + + public get menuIcon(): string { + return this.isCollapsed ? '☰' : '✖'; + } + + public getMenuItemClasses(menuItem: any) { + return { + 'pull-xs-right': this.isCollapsed && menuItem.menuType === MenuType.RIGHT + }; + } +} diff --git a/catalog-ui/src/app/ng2/shared/navbar/navbar.metadata.ts b/catalog-ui/src/app/ng2/shared/navbar/navbar.metadata.ts new file mode 100644 index 0000000000..245d0e6cfe --- /dev/null +++ b/catalog-ui/src/app/ng2/shared/navbar/navbar.metadata.ts @@ -0,0 +1,11 @@ +export enum MenuType { + BRAND, + LEFT, + RIGHT +} + +export interface RouteInfo { + path: string; + title: string; + menuType: MenuType; +} diff --git a/catalog-ui/src/app/ng2/shared/navbar/navbar.module.ts b/catalog-ui/src/app/ng2/shared/navbar/navbar.module.ts new file mode 100644 index 0000000000..18120a61fb --- /dev/null +++ b/catalog-ui/src/app/ng2/shared/navbar/navbar.module.ts @@ -0,0 +1,16 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { RouterModule } from '@angular/router'; +import { NavbarComponent } from "./navbar.component"; + +@NgModule({ + imports: [ + RouterModule, + CommonModule + ], + declarations: [ NavbarComponent ], + exports: [ + NavbarComponent + ] +}) +export class NavbarModule {} diff --git a/catalog-ui/src/app/ng2/shared/shared.module.ts b/catalog-ui/src/app/ng2/shared/shared.module.ts new file mode 100644 index 0000000000..3e59e04441 --- /dev/null +++ b/catalog-ui/src/app/ng2/shared/shared.module.ts @@ -0,0 +1,20 @@ +import { NgModule } from '@angular/core'; + +import { CommonModule } from '@angular/common'; +import { RouterModule } from '@angular/router'; +import { NavbarModule } from "./navbar/navbar.module"; + +@NgModule({ + declarations: [ + + ], + imports: [ + CommonModule, + RouterModule, + NavbarModule + ], + exports: [ + ] +}) + +export class SharedModule {} diff --git a/catalog-ui/src/app/ng2/shared/tabs/tab/tab.component.ts b/catalog-ui/src/app/ng2/shared/tabs/tab/tab.component.ts new file mode 100644 index 0000000000..06dcfa0b16 --- /dev/null +++ b/catalog-ui/src/app/ng2/shared/tabs/tab/tab.component.ts @@ -0,0 +1,18 @@ +import { Component, Input } from '@angular/core'; +import { ViewEncapsulation } from '@angular/core'; + +@Component({ + selector: 'tab', + template: ` + <div *ngIf="active" class="tab-content"> + <ng-content></ng-content> + </div> + `, + encapsulation: ViewEncapsulation.None +}) +export class Tab { + @Input('tabTitle') title: string; + @Input() active:boolean = false; + @Input() indication?: number; + +}
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/shared/tabs/tabs.component.html b/catalog-ui/src/app/ng2/shared/tabs/tabs.component.html new file mode 100644 index 0000000000..3e263a3862 --- /dev/null +++ b/catalog-ui/src/app/ng2/shared/tabs/tabs.component.html @@ -0,0 +1,9 @@ +<div class="tabs {{tabStyle}}"> + <div class="tab" *ngFor="let tab of tabs" (click)="selectTab(tab)" [class.active]="tab.active"> + {{tab.title}} + <div class="tab-indication" *ngIf="tab.indication" [@indicatorAnimation]="tab.indication">{{tab.indication}}</div> + </div> +</div> +<div class="tab-content-container"> + <ng-content></ng-content> +</div>
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/shared/tabs/tabs.component.less b/catalog-ui/src/app/ng2/shared/tabs/tabs.component.less new file mode 100644 index 0000000000..6f9e57aaf2 --- /dev/null +++ b/catalog-ui/src/app/ng2/shared/tabs/tabs.component.less @@ -0,0 +1,84 @@ +@import '../../../../assets/styles/variables'; + +tab { + height: 100%; +} + +.tabs { + display:flex; + flex: 0 0 auto; + flex-direction:row; +} + +.tab { + flex: 1 0 auto; + cursor: pointer; + padding: .5em; +} + +.tab-content-container { + flex: 1; + width:100%; + overflow-y:hidden; +} + +.tab-content { + height:100%; +} + +/*Tab styles*/ +.tabs{ + &.round-tabs .tab{ + background-color: #f8f8f8; + color: #959595; + border: solid 1px #d2d2d2; + border-bottom:none; + border-left:none; + position:relative; + + &:first-child { + border-left:solid 1px #d2d2d2; + } + + &.active { + background-color:#009fdb; + color:#e9e9e9; + border-color:#009fdb; + } + + .tab-indication { + position: absolute; + top: -10px; + background-color: #009fdb; + right: 10px; + padding: 2px 0; + border-radius: 15px; + border: solid 1px #d2d2d2; + color:white; + width: 25px; + height: 25px; + text-align: center; + + } + } + + &.simple-tabs .tab { + font-size: 12px; + color: @main_color_n; + + &:after { + display:block; + content: ''; + border-bottom: 2px solid @main_color_a; + transform: scaleX(0); + transition: transform 200ms ease-in-out; + } + + &.active { + color: @main_color_a; + &:after { + transform: scaleX(1.2); + } + } + } +} diff --git a/catalog-ui/src/app/ng2/shared/tabs/tabs.component.ts b/catalog-ui/src/app/ng2/shared/tabs/tabs.component.ts new file mode 100644 index 0000000000..dc5616c6cb --- /dev/null +++ b/catalog-ui/src/app/ng2/shared/tabs/tabs.component.ts @@ -0,0 +1,58 @@ +import { Component, ContentChildren, QueryList, AfterContentInit, Input, Output, EventEmitter } from '@angular/core'; +import { Tab } from './tab/tab.component'; +import { ViewEncapsulation } from '@angular/core'; +import { trigger, state, style, transition, animate, keyframes } from '@angular/core'; + +@Component({ + selector: 'tabs', + templateUrl: './tabs.component.html', + styleUrls: ['./tabs.component.less'], + encapsulation: ViewEncapsulation.None, + animations: [ + trigger('indicatorAnimation', [ + transition(':enter', [style({ transform: 'translateY(-50%)', opacity: 0 }), animate('250ms', style({ transform: 'translateY(0)', opacity: 1 })) ]), + transition(':leave', [style({ opacity: 1 }), animate('500ms', style({ opacity: 0 })) ]) + ]) + ] +}) +export class Tabs implements AfterContentInit { + + @Input() tabStyle: string; + @Input() hideIndicationOnTabChange?: boolean = false; + @ContentChildren(Tab) tabs: QueryList<Tab>; + @Output() tabChanged: EventEmitter<Tab> = new EventEmitter<Tab>(); + + + ngAfterContentInit() { + //after contentChildren are set, determine active tab. If no active tab set, activate the first one + let activeTabs = this.tabs.filter((tab) => tab.active); + + if (activeTabs.length === 0) { + this.selectTab(this.tabs.first); + } + } + + selectTab(tab: Tab) { + //activate the tab the user clicked. + this.tabs.toArray().forEach(tab => { + tab.active = false; + if (this.hideIndicationOnTabChange && tab.indication) { + tab.indication = null; + } + }); + tab.active = true; + this.tabChanged.emit(tab); + } + + triggerTabChange(tabTitle) { + this.tabs.toArray().forEach(tab => { + tab.active = (tab.title == tabTitle) ? true : false; + }); + } + + setTabIndication(tabTitle:string, indication?:number) { + let selectedTab: Tab = this.tabs.toArray().find(tab => tab.title == tabTitle); + selectedTab.indication = indication || null; + } + +} diff --git a/catalog-ui/src/app/ng2/shared/tabs/tabs.module.ts b/catalog-ui/src/app/ng2/shared/tabs/tabs.module.ts new file mode 100644 index 0000000000..36c7335fde --- /dev/null +++ b/catalog-ui/src/app/ng2/shared/tabs/tabs.module.ts @@ -0,0 +1,35 @@ +import { Component, NgModule } from '@angular/core' +import { BrowserModule } from '@angular/platform-browser' + +import { Tabs } from './tabs.component'; +import { Tab } from './tab/tab.component'; + + +@NgModule({ + imports: [BrowserModule], + declarations: [Tabs, Tab], + bootstrap: [], + exports: [Tabs, Tab] +}) +export class TabModule { } + +/** README: **/ + +/** USAGE Example: + *In page.module.ts: import TabModule + *In HTML: + *<tabs tabStyle="class-goes-here" (tabChanged)="tabChangedEvent($event) [hideIndicationOnTabChange]="optional-boolean"> + * <tab [tabTitle]="'Tab 1'">Content of tab 1</tab> + * <tab tabTitle="Tab 2" >Content of tab 2</tab> + * ... + *</tabs> + */ + +/**STYLING: (ViewEncapsulation is set to None to allow styles to be overridden or customized) + * Existing options: + * tabStyle="round-tabs" will provide generic rounded tab look + * + * To create or override styles: + * Parent div has class ".tabs". Each tab has class ".tab". Active tab has class ".active". + * Use /deep/ or >>> prefix to override styles via other components stylesheets + */
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/utils/ng1-upgraded-provider.ts b/catalog-ui/src/app/ng2/utils/ng1-upgraded-provider.ts new file mode 100644 index 0000000000..ed1ecd87bd --- /dev/null +++ b/catalog-ui/src/app/ng2/utils/ng1-upgraded-provider.ts @@ -0,0 +1,73 @@ +/** + * Created by rc2122 on 4/6/2017. + */ +import {DataTypesService} from "../../services/data-types-service"; +import ICacheObject = angular.ICacheObject; +import {SharingService} from "../../services/sharing-service"; +import {CookieService} from "../../services/cookie-service"; +import {CacheService} from "../../services/cache-service"; +import {EventListenerService} from "app/services/event-listener-service"; + +/** Services we need to upgrade from angular1 to angular2 - in the future we need to rewrite them all to angular2 **/ + +export function dataTypesServiceFactory(cacheObj: ICacheObject) { + return cacheObj.get('Sdc.Services.DataTypesService'); +} + +export function sharingServiceFactory(cacheObj: ICacheObject) { + return cacheObj.get('Sdc.Services.SharingService'); +} + +export function cookieServiceFactory(cacheObj: ICacheObject) { + return cacheObj.get('Sdc.Services.CookieService'); +} + +export function stateParamsServiceFactory(cacheObj: ICacheObject) { + return cacheObj.get('$stateParams'); +} + +export function cacheServiceFactory(cacheObj: ICacheObject) { + return cacheObj.get('Sdc.Services.CacheService'); +} + +export function eventListenerServiceServiceFactory(cacheObj: ICacheObject) { + return cacheObj.get('EventListenerService'); +} + +export const DataTypesServiceProvider = { + provide: DataTypesService, + useFactory: dataTypesServiceFactory, + deps: ['$injector'] +}; + + +export const SharingServiceProvider = { + provide: SharingService, + useFactory: sharingServiceFactory, + deps: ['$injector'] +}; + + +export const CookieServiceProvider = { + provide: CookieService, + useFactory: cookieServiceFactory, + deps: ['$injector'] +}; + +export const StateParamsServiceFactory = { + provide: '$stateParams', + useFactory: stateParamsServiceFactory, + deps: ['$injector'] +}; + +export const CacheServiceProvider = { + provide: CacheService, + useFactory: cacheServiceFactory, + deps: ['$injector'] +}; + +export const EventListenerServiceProvider = { + provide: EventListenerService, + useFactory: eventListenerServiceServiceFactory, + deps: ['$injector'] +}; |