From af150157bf530ff9169e3b315761f8ca99d69bc9 Mon Sep 17 00:00:00 2001 From: John Sullivan Date: Thu, 21 Feb 2019 10:23:30 -0500 Subject: [PATCH] Adding some new tools and enhancements from project work --- .angular-cli.json | 61 +++--- extension-starter-test.iml | 12 ++ package.json | 15 +- src/app/app.component.html | 1 - src/app/app.component.ts | 10 +- src/app/app.module.ts | 24 +-- src/app/sample/sample.component.html | 14 -- src/app/sample/sample.component.scss | 0 src/app/sample/sample.component.spec.ts | 26 --- src/app/sample/sample.component.ts | 33 --- src/app/sample/sample.module.ts | 34 --- src/app/tools/modal/modal.module.ts | 24 +++ src/app/tools/modal/modal.types.ts | 32 +++ .../standard/standard-modal.component.html | 6 + .../standard/standard-modal.component.scss} | 0 .../standard/standard-modal.component.spec.ts | 25 +++ .../standard/standard-modal.component.ts | 36 ++++ src/app/tools/number.util.ts | 19 ++ .../pipes/group-by/group-by.pipe.spec.ts | 8 + src/app/tools/pipes/group-by/group-by.pipe.ts | 29 +++ src/app/tools/pipes/keys/keys.pipe.spec.ts | 8 + src/app/tools/pipes/keys/keys.pipe.ts | 12 ++ src/app/tools/pipes/pipes.module.ts | 22 ++ src/app/tools/pipes/safe/safe.pipe.spec.ts | 8 + src/app/tools/pipes/safe/safe.pipe.ts | 23 +++ .../{ => tools}/service/app-bridge.service.ts | 26 ++- src/app/tools/service/toast/toast.service.ts | 34 +++ .../table/cells/delete-cell.component.ts | 19 ++ .../tools/table/cells/edit-cell.component.ts | 19 ++ .../tools/table/cells/icon-cell.component.ts | 19 ++ .../table/cells/preview-cell.component.ts | 19 ++ .../service/table-data-provider.service.ts | 195 ++++++++++++++++++ src/app/tools/table/table.module.ts | 36 ++++ src/app/tools/table/table.types.ts | 21 ++ src/app/tools/table/table.utils.ts | 86 ++++++++ src/app/tools/tools.types.ts | 7 + src/environments/environment.local.ts | 9 + src/environments/environment.prod.ts | 1 + src/environments/environment.staging.ts | 1 + src/environments/environment.ts | 1 + 40 files changed, 797 insertions(+), 178 deletions(-) create mode 100644 extension-starter-test.iml delete mode 100644 src/app/app.component.html delete mode 100644 src/app/sample/sample.component.html delete mode 100644 src/app/sample/sample.component.scss delete mode 100644 src/app/sample/sample.component.spec.ts delete mode 100644 src/app/sample/sample.component.ts delete mode 100644 src/app/sample/sample.module.ts create mode 100644 src/app/tools/modal/modal.module.ts create mode 100644 src/app/tools/modal/modal.types.ts create mode 100644 src/app/tools/modal/standard/standard-modal.component.html rename src/app/{app.component.scss => tools/modal/standard/standard-modal.component.scss} (100%) create mode 100644 src/app/tools/modal/standard/standard-modal.component.spec.ts create mode 100644 src/app/tools/modal/standard/standard-modal.component.ts create mode 100644 src/app/tools/number.util.ts create mode 100644 src/app/tools/pipes/group-by/group-by.pipe.spec.ts create mode 100644 src/app/tools/pipes/group-by/group-by.pipe.ts create mode 100644 src/app/tools/pipes/keys/keys.pipe.spec.ts create mode 100644 src/app/tools/pipes/keys/keys.pipe.ts create mode 100644 src/app/tools/pipes/pipes.module.ts create mode 100644 src/app/tools/pipes/safe/safe.pipe.spec.ts create mode 100644 src/app/tools/pipes/safe/safe.pipe.ts rename src/app/{ => tools}/service/app-bridge.service.ts (68%) create mode 100644 src/app/tools/service/toast/toast.service.ts create mode 100644 src/app/tools/table/cells/delete-cell.component.ts create mode 100644 src/app/tools/table/cells/edit-cell.component.ts create mode 100644 src/app/tools/table/cells/icon-cell.component.ts create mode 100644 src/app/tools/table/cells/preview-cell.component.ts create mode 100644 src/app/tools/table/service/table-data-provider.service.ts create mode 100644 src/app/tools/table/table.module.ts create mode 100644 src/app/tools/table/table.types.ts create mode 100644 src/app/tools/table/table.utils.ts create mode 100644 src/app/tools/tools.types.ts create mode 100644 src/environments/environment.local.ts diff --git a/.angular-cli.json b/.angular-cli.json index 0d8c41f..c871889 100644 --- a/.angular-cli.json +++ b/.angular-cli.json @@ -3,38 +3,47 @@ "project": { "name": "platform-extension-starter" }, - "apps": [ - { - "root": "src", - "outDir": "dist", - "assets": ["assets", "favicon.ico", "static"], - "index": "index.html", - "main": "main.ts", - "polyfills": "polyfills.ts", - "test": "test.ts", - "tsconfig": "tsconfig.app.json", - "testTsconfig": "tsconfig.spec.json", - "prefix": "platform", - "styles": ["styles.scss"], - "stylePreprocessorOptions": { - "includePaths": ["../node_modules/hint.css/src", "../node_modules/novo-elements"] - }, - "scripts": ["../node_modules/post-robot/dist/post-robot.min.js"], - "environmentSource": "environments/environment.ts", - "environments": { - "dev": "environments/environment.ts", - "prod": "environments/environment.prod.ts", - "staging": "environments/environment.staging.ts" - } + "apps": [{ + "root": "src", + "outDir": "dist", + "assets": [ + "assets", + "favicon.ico", + "static" + ], + "index": "index.html", + "main": "main.ts", + "polyfills": "polyfills.ts", + "test": "test.ts", + "tsconfig": "tsconfig.app.json", + "testTsconfig": "tsconfig.spec.json", + "prefix": "platform", + "styles": [ + "styles.scss" + ], + "stylePreprocessorOptions": { + "includePaths": [ + "../node_modules/hint.css/src", + "../node_modules/novo-elements" + ] + }, + "scripts": [ + "../node_modules/post-robot/dist/post-robot.min.js" + ], + "environmentSource": "environments/environment.ts", + "environments": { + "dev": "environments/environment.ts", + "prod": "environments/environment.prod.ts", + "staging": "environments/environment.staging.ts", + "local": "environments/environment.local.ts" } - ], + }], "e2e": { "protractor": { "config": "./protractor.conf.js" } }, - "lint": [ - { + "lint": [{ "project": "src/tsconfig.app.json" }, { diff --git a/extension-starter-test.iml b/extension-starter-test.iml new file mode 100644 index 0000000..da8860c --- /dev/null +++ b/extension-starter-test.iml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/package.json b/package.json index 048ed2f..a01295f 100644 --- a/package.json +++ b/package.json @@ -5,20 +5,14 @@ "scripts": { "ng": "ng", "start": "ng serve", - "start:aot": "ng serve --aot", + "start:local": "ng serve --env=local", "build": "ng build --aot --prod", "build:staging": "ng build --aot --env=staging", "test": "ng test", "lint": "ng lint", - "e2e": "ng e2e", - "precommit": "lint-staged" + "e2e": "ng e2e" }, "private": true, - "lint-staged": { - "*.ts": [ - "tslint" - ] - }, "dependencies": { "@angular/animations": "5.2.0", "@angular/common": "5.2.0", @@ -31,12 +25,13 @@ "@angular/router": "5.2.0", "core-js": "^2.4.1", "hint.css": "^2.5.0", - "novo-elements": "2.17.0", + "novo-elements": "2.13.0", + "sha.js": "2.4.11", "rxjs": "5.5.6", "zone.js": "0.8.19" }, "devDependencies": { - "@angular/cli": "1.7.4", + "@angular/cli": "1.6.5", "@angular/compiler-cli": "^5.2.0", "@angular/language-service": "^5.2.0", "@types/jasmine": "~2.8.3", diff --git a/src/app/app.component.html b/src/app/app.component.html deleted file mode 100644 index 0680b43..0000000 --- a/src/app/app.component.html +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/app/app.component.ts b/src/app/app.component.ts index fe29bd0..1fc0cc1 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -1,11 +1,13 @@ -// NG +// NG2 import { Component } from '@angular/core'; // Vendor // APP @Component({ selector: 'platform-root', - templateUrl: './app.component.html', - styleUrls: ['./app.component.scss'], + template: ` + + ` }) -export class AppComponent {} +export class AppComponent { +} diff --git a/src/app/app.module.ts b/src/app/app.module.ts index c163450..123eeb4 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -6,36 +6,28 @@ import { Routes, RouterModule } from '@angular/router'; import { NovoElementsModule } from 'novo-elements'; // APP import { AppComponent } from './app.component'; -import { AppBridgeService } from './service/app-bridge.service'; +import {HttpClientModule} from '@angular/common/http'; +import {AppBridgeService} from './tools/service/app-bridge.service'; const routes: Routes = [ - { path: '', redirectTo: 'sample', pathMatch: 'full' }, - { path: 'sample', loadChildren: './sample/sample.module#SampleModule' }, + { path: '', redirectTo: 'sample', pathMatch: 'full' } ]; @NgModule({ declarations: [ - // Main Entry Component - AppComponent, - // Modals/Popovers + AppComponent ], imports: [ - // NG BrowserModule, RouterModule.forRoot(routes), - // Vendor - NovoElementsModule, - // APP + HttpClientModule, + NovoElementsModule ], providers: [ - AppBridgeService, - // Vendor Overrides - // APP + AppBridgeService ], bootstrap: [ - // Main Entry Component - AppComponent, - // Modals/Popovers + AppComponent ], }) export class AppModule {} diff --git a/src/app/sample/sample.component.html b/src/app/sample/sample.component.html deleted file mode 100644 index befe279..0000000 --- a/src/app/sample/sample.component.html +++ /dev/null @@ -1,14 +0,0 @@ -
- - - - -
-
-

Here is where you would put your content...

- -
diff --git a/src/app/sample/sample.component.scss b/src/app/sample/sample.component.scss deleted file mode 100644 index e69de29..0000000 diff --git a/src/app/sample/sample.component.spec.ts b/src/app/sample/sample.component.spec.ts deleted file mode 100644 index 90b72ab..0000000 --- a/src/app/sample/sample.component.spec.ts +++ /dev/null @@ -1,26 +0,0 @@ -// NG -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -// Vendor -// APP -import { SampleComponent } from './sample.component'; - -describe('SampleComponent', () => { - let component: SampleComponent; - let fixture: ComponentFixture; - - beforeEach(async(() => { - TestBed.configureTestingModule({ - declarations: [SampleComponent], - }).compileComponents(); - })); - - beforeEach(() => { - fixture = TestBed.createComponent(SampleComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should be created', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/src/app/sample/sample.component.ts b/src/app/sample/sample.component.ts deleted file mode 100644 index 43aecaf..0000000 --- a/src/app/sample/sample.component.ts +++ /dev/null @@ -1,33 +0,0 @@ -// NG -import { Component, OnInit } from '@angular/core'; -// Vendor -import { AppBridge } from 'novo-elements'; -// APP -import { AppBridgeService } from '../service/app-bridge.service'; - -@Component({ - selector: 'platform-sample', - templateUrl: './sample.component.html', - styleUrls: ['./sample.component.scss'], -}) -export class SampleComponent implements OnInit { - constructor(private appBridge: AppBridgeService) {} - - ngOnInit() {} - - refresh(): void { - this.appBridge.execute((bridge: AppBridge) => { - bridge.refresh().then((success: any) => { - console.log('[AppComponent] - Refresh Success!', success); // tslint:disable-line - }); - }); - } - - close(): void { - this.appBridge.execute((bridge: AppBridge) => { - bridge.close().then((success: any) => { - console.log('[AppComponent] - Close Success!', success); // tslint:disable-line - }); - }); - } -} diff --git a/src/app/sample/sample.module.ts b/src/app/sample/sample.module.ts deleted file mode 100644 index 0708299..0000000 --- a/src/app/sample/sample.module.ts +++ /dev/null @@ -1,34 +0,0 @@ -// NG -import { NgModule } from '@angular/core'; -import { CommonModule } from '@angular/common'; -import { Routes, RouterModule } from '@angular/router'; -import { FormsModule } from '@angular/forms'; -// Vendor -import { NovoElementsModule, NovoElementProviders } from 'novo-elements'; -// APP -import { SampleComponent } from './sample.component'; - -export const routes: Routes = [ - { path: '', component: SampleComponent, pathMatch: 'full' }, -]; - -@NgModule({ - imports: [ - // NG - CommonModule, - RouterModule.forChild(routes), - FormsModule, - // Vendor - NovoElementsModule, - NovoElementProviders.forRoot(), - ], - declarations: [ - // APP - SampleComponent, - ], - providers: [ - // Vendor Overrides - // APP - ], -}) -export class SampleModule {} diff --git a/src/app/tools/modal/modal.module.ts b/src/app/tools/modal/modal.module.ts new file mode 100644 index 0000000..a26b4b9 --- /dev/null +++ b/src/app/tools/modal/modal.module.ts @@ -0,0 +1,24 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; + +import {NovoElementProviders, NovoElementsModule} from 'novo-elements'; + +import {StandardModalComponent} from './standard/standard-modal.component'; + +@NgModule({ + imports: [ + CommonModule, + NovoElementsModule, + NovoElementProviders.forChild() + ], + declarations: [ + StandardModalComponent + ], + entryComponents: [ + StandardModalComponent + ], + exports: [ + StandardModalComponent + ] +}) +export class ModalModule { } diff --git a/src/app/tools/modal/modal.types.ts b/src/app/tools/modal/modal.types.ts new file mode 100644 index 0000000..cd0844d --- /dev/null +++ b/src/app/tools/modal/modal.types.ts @@ -0,0 +1,32 @@ +import {NovoModalParams} from 'novo-elements'; +import {ModalParams} from 'novo-elements/elements/modal/Modal'; + +export class StandardModalParams implements CustomModalParams { + + public message: string; + public isConfirm: boolean = false; + public onClose: (result: boolean) => void = () => {}; + + constructor(message: string, isConfirm: boolean = false, onClose: (result: boolean) => void = () => {}) { + this.message = message; + this.isConfirm = isConfirm; + this.onClose = onClose; + } + + static fromNovo(params: NovoModalParams): StandardModalParams { + return new StandardModalParams( + params['message'], + params['isConfirm'], + params['onClose'] + ); + } + +} + +export interface CustomModalParams extends ModalParams { + + message: string; + isConfirm: boolean; + onClose: (result: boolean) => void; + +} diff --git a/src/app/tools/modal/standard/standard-modal.component.html b/src/app/tools/modal/standard/standard-modal.component.html new file mode 100644 index 0000000..ef2d716 --- /dev/null +++ b/src/app/tools/modal/standard/standard-modal.component.html @@ -0,0 +1,6 @@ + +

{{ modalParams.message }}

+ + + +
diff --git a/src/app/app.component.scss b/src/app/tools/modal/standard/standard-modal.component.scss similarity index 100% rename from src/app/app.component.scss rename to src/app/tools/modal/standard/standard-modal.component.scss diff --git a/src/app/tools/modal/standard/standard-modal.component.spec.ts b/src/app/tools/modal/standard/standard-modal.component.spec.ts new file mode 100644 index 0000000..eb15f43 --- /dev/null +++ b/src/app/tools/modal/standard/standard-modal.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import {StandardModalComponent} from './standard-modal.component'; + +describe('StandardModalComponent', () => { + let component: StandardModalComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ StandardModalComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(StandardModalComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/tools/modal/standard/standard-modal.component.ts b/src/app/tools/modal/standard/standard-modal.component.ts new file mode 100644 index 0000000..a3bbd40 --- /dev/null +++ b/src/app/tools/modal/standard/standard-modal.component.ts @@ -0,0 +1,36 @@ +import {Component} from '@angular/core'; + +import {NovoModalParams, NovoModalRef} from 'novo-elements'; + +import {StandardModalParams} from '../modal.types'; + +@Component({ + selector: 'standard-modal', + templateUrl: './standard-modal.component.html', + styleUrls: ['./standard-modal.component.scss'] +}) +export class StandardModalComponent { + + public modalParams: StandardModalParams; + + constructor(private modalRef: NovoModalRef, modalParams: NovoModalParams) { + this.modalParams = StandardModalParams.fromNovo(modalParams); + } + + close() { + if (this.modalParams.onClose !== undefined) { + this.modalParams.onClose(false); + } + + this.modalRef.close(false); + } + + yes() { + if (this.modalParams.onClose !== undefined) { + this.modalParams.onClose(true); + } + + this.modalRef.close(true); + } + +} diff --git a/src/app/tools/number.util.ts b/src/app/tools/number.util.ts new file mode 100644 index 0000000..76d7c20 --- /dev/null +++ b/src/app/tools/number.util.ts @@ -0,0 +1,19 @@ +export function getNumber(value: any): number { + return parseFloat(value) || 0.00; +} + +export function formatNumber(value: any): string { + return (Math.round(getNumber(value) * 100) / 100).toFixed(2); +} + +export function formatPercentage(value: any): string { + if (value > 0) { + if (value < 1) { + return formatNumber(value); + } + + return formatNumber(value / 100); + } + + return formatNumber(0.00); +} diff --git a/src/app/tools/pipes/group-by/group-by.pipe.spec.ts b/src/app/tools/pipes/group-by/group-by.pipe.spec.ts new file mode 100644 index 0000000..ebd3bd0 --- /dev/null +++ b/src/app/tools/pipes/group-by/group-by.pipe.spec.ts @@ -0,0 +1,8 @@ +import { GroupByPipe } from './group-by.pipe'; + +describe('GroupByPipe', () => { + it('create an instance', () => { + const pipe = new GroupByPipe(); + expect(pipe).toBeTruthy(); + }); +}); diff --git a/src/app/tools/pipes/group-by/group-by.pipe.ts b/src/app/tools/pipes/group-by/group-by.pipe.ts new file mode 100644 index 0000000..effe51c --- /dev/null +++ b/src/app/tools/pipes/group-by/group-by.pipe.ts @@ -0,0 +1,29 @@ +import { Pipe, PipeTransform } from '@angular/core'; +import {getNestedElement} from '../../table/table.utils'; + +@Pipe({ + name: 'groupBy' +}) +export class GroupByPipe implements PipeTransform { + + transform(collection: Array, property: string): any { + if(!collection) { + return null; + } + + const groupedCollection = collection.reduce((previous, current)=> { + const key = getNestedElement(current, property) || ''; + + if(!previous[key]) { + previous[key] = [current]; + } else { + previous[key].push(current); + } + + return previous; + }, {}); + + return Object.keys(groupedCollection).map(key => ({ key, value: groupedCollection[key] })); + } + +} diff --git a/src/app/tools/pipes/keys/keys.pipe.spec.ts b/src/app/tools/pipes/keys/keys.pipe.spec.ts new file mode 100644 index 0000000..9232fbf --- /dev/null +++ b/src/app/tools/pipes/keys/keys.pipe.spec.ts @@ -0,0 +1,8 @@ +import { KeysPipe } from './keys.pipe'; + +describe('KeysPipe', () => { + it('create an instance', () => { + const pipe = new KeysPipe(); + expect(pipe).toBeTruthy(); + }); +}); diff --git a/src/app/tools/pipes/keys/keys.pipe.ts b/src/app/tools/pipes/keys/keys.pipe.ts new file mode 100644 index 0000000..23a08b6 --- /dev/null +++ b/src/app/tools/pipes/keys/keys.pipe.ts @@ -0,0 +1,12 @@ +import { Pipe, PipeTransform } from '@angular/core'; + +@Pipe({ + name: 'keys' +}) +export class KeysPipe implements PipeTransform { + + transform(value: any): any { + return value ? Object.keys(value) : []; + } + +} diff --git a/src/app/tools/pipes/pipes.module.ts b/src/app/tools/pipes/pipes.module.ts new file mode 100644 index 0000000..15322dc --- /dev/null +++ b/src/app/tools/pipes/pipes.module.ts @@ -0,0 +1,22 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import {SafePipe} from './safe/safe.pipe'; +import { GroupByPipe } from './group-by/group-by.pipe'; +import { KeysPipe } from './keys/keys.pipe'; + +@NgModule({ + imports: [ + CommonModule + ], + declarations: [ + SafePipe, + GroupByPipe, + KeysPipe + ], + exports: [ + SafePipe, + GroupByPipe, + KeysPipe + ] +}) +export class PipesModule { } diff --git a/src/app/tools/pipes/safe/safe.pipe.spec.ts b/src/app/tools/pipes/safe/safe.pipe.spec.ts new file mode 100644 index 0000000..49ee0ad --- /dev/null +++ b/src/app/tools/pipes/safe/safe.pipe.spec.ts @@ -0,0 +1,8 @@ +import { SafePipe } from './safe.pipe'; + +describe('SafePipe', () => { + it('create an instance', () => { + const pipe = new SafePipe(); + expect(pipe).toBeTruthy(); + }); +}); diff --git a/src/app/tools/pipes/safe/safe.pipe.ts b/src/app/tools/pipes/safe/safe.pipe.ts new file mode 100644 index 0000000..73eadfc --- /dev/null +++ b/src/app/tools/pipes/safe/safe.pipe.ts @@ -0,0 +1,23 @@ +import { Pipe, PipeTransform } from '@angular/core'; +import {DomSanitizer, SafeHtml, SafeResourceUrl, SafeScript, SafeStyle, SafeUrl} from '@angular/platform-browser'; + +@Pipe({ + name: 'safe' +}) +export class SafePipe implements PipeTransform { + + constructor(protected sanitizer: DomSanitizer) {} + + public transform(value: any, type: string): SafeHtml | SafeStyle | SafeScript | SafeUrl | SafeResourceUrl { + switch (type) { + case 'html': return this.sanitizer.bypassSecurityTrustHtml(value.trim()); + case 'style': return this.sanitizer.bypassSecurityTrustStyle(value.trim()); + case 'script': return this.sanitizer.bypassSecurityTrustScript(value.trim()); + case 'url': return this.sanitizer.bypassSecurityTrustUrl(value.trim()); + case 'resourceUrl': return this.sanitizer.bypassSecurityTrustResourceUrl(value.trim()); + + default: throw new Error(`Invalid safe type specified: ${type}`); + } + } + +} diff --git a/src/app/service/app-bridge.service.ts b/src/app/tools/service/app-bridge.service.ts similarity index 68% rename from src/app/service/app-bridge.service.ts rename to src/app/tools/service/app-bridge.service.ts index b1cb6bb..6b3b479 100644 --- a/src/app/service/app-bridge.service.ts +++ b/src/app/tools/service/app-bridge.service.ts @@ -1,12 +1,12 @@ -// NG import { Injectable } from '@angular/core'; -// Vendor + import { AppBridge } from 'novo-elements'; -// APP -import { environment } from '../../environments/environment'; + +import { environment } from '../../../environments/environment'; @Injectable() export class AppBridgeService { + private bridge: AppBridge; private registered = false; @@ -32,15 +32,13 @@ export class AppBridgeService { } private register() { - this.bridge.register(environment.appBridgeConfig).then( - () => { - this.registered = true; - }, - () => { - setTimeout(() => { - this.register(); - }, 500); - }, - ); + this.bridge.register(environment.appBridgeConfig).then(() => { + this.registered = true; + }, () => { + setTimeout(() => { + this.register(); + }, 500); + }); } + } diff --git a/src/app/tools/service/toast/toast.service.ts b/src/app/tools/service/toast/toast.service.ts new file mode 100644 index 0000000..da145b2 --- /dev/null +++ b/src/app/tools/service/toast/toast.service.ts @@ -0,0 +1,34 @@ +import {Injectable, ViewContainerRef} from '@angular/core'; +import {NovoToastService} from "novo-elements"; + + +export class ToastService { + + private toastService: NovoToastService; + + constructor(toastService: NovoToastService, viewRef: ViewContainerRef) { + toastService.parentViewContainer = viewRef; + this.toastService = toastService; + } + + public success(message: string, title: string = 'Success', icon: string = 'check'): void { + this.toastService.alert({ + title: title, + message: message, + icon: icon, + theme: 'success', + position: 'growlTopRight' + }); + } + + public danger(message: string, title: string = 'Error', icon: string = 'caution'): void { + this.toastService.alert({ + title: title, + message: message, + icon: icon, + theme: 'danger', + position: 'growlTopRight' + }); + } + +} diff --git a/src/app/tools/table/cells/delete-cell.component.ts b/src/app/tools/table/cells/delete-cell.component.ts new file mode 100644 index 0000000..4679d59 --- /dev/null +++ b/src/app/tools/table/cells/delete-cell.component.ts @@ -0,0 +1,19 @@ +import {Component} from '@angular/core'; + +import {BaseRenderer} from 'novo-elements'; + +@Component({ + selector: 'delete-cell', + template: ` + + ` +}) +export class DeleteCellComponent extends BaseRenderer { + + onClick(): void { + if(this.meta.onClick) { + this.meta.onClick(this.data); + } + } + +} diff --git a/src/app/tools/table/cells/edit-cell.component.ts b/src/app/tools/table/cells/edit-cell.component.ts new file mode 100644 index 0000000..77e3bce --- /dev/null +++ b/src/app/tools/table/cells/edit-cell.component.ts @@ -0,0 +1,19 @@ +import {Component} from '@angular/core'; + +import {BaseRenderer} from 'novo-elements'; + +@Component({ + selector: 'edit-cell', + template: ` + + ` +}) +export class EditCellComponent extends BaseRenderer { + + onClick(): void { + if(this.meta.onClick) { + this.meta.onClick(this.data); + } + } + +} diff --git a/src/app/tools/table/cells/icon-cell.component.ts b/src/app/tools/table/cells/icon-cell.component.ts new file mode 100644 index 0000000..27294f5 --- /dev/null +++ b/src/app/tools/table/cells/icon-cell.component.ts @@ -0,0 +1,19 @@ +import {Component} from '@angular/core'; + +import {BaseRenderer} from 'novo-elements'; + +@Component({ + selector: 'icon-cell', + template: ` + + ` +}) +export class IconCellComponent extends BaseRenderer { + + onClick(): void { + if(this.meta.onClick) { + this.meta.onClick(this.data); + } + } + +} diff --git a/src/app/tools/table/cells/preview-cell.component.ts b/src/app/tools/table/cells/preview-cell.component.ts new file mode 100644 index 0000000..2c4819b --- /dev/null +++ b/src/app/tools/table/cells/preview-cell.component.ts @@ -0,0 +1,19 @@ +import {Component} from '@angular/core'; + +import {BaseRenderer} from 'novo-elements'; + +@Component({ + selector: 'preview-cell', + template: ` + + ` +}) +export class PreviewCellComponent extends BaseRenderer { + + onClick(): void { + if(this.meta.onClick) { + this.meta.onClick(this.data); + } + } + +} diff --git a/src/app/tools/table/service/table-data-provider.service.ts b/src/app/tools/table/service/table-data-provider.service.ts new file mode 100644 index 0000000..5b59ada --- /dev/null +++ b/src/app/tools/table/service/table-data-provider.service.ts @@ -0,0 +1,195 @@ +import {EventEmitter} from '@angular/core'; + +import {CollectionEvent, PagedArrayCollection} from 'novo-elements'; + +import {DataService, QueryRequest, QueryResult} from '../table.types'; + +const HALF_SECOND = 500; + +export class TableDataProvider extends PagedArrayCollection { + + totalResults = 0; + initialTotalResults = 0; + pagesLoaded = 0; + _errored = false; + _pageSize = 10; + loading = true; + resetting = false; + forced = 0; + refreshing: EventEmitter = new EventEmitter(); + gettingMore: EventEmitter = new EventEmitter(); + + private lastFilter; + private lastSort; + + private dataProvider: DataService; + + constructor(dataProvider: DataService) { + super([]); + this.dataProvider = dataProvider; + this.refreshing.debounceTime(HALF_SECOND).subscribe((event) => { + this._errored = false; + this.loading = true; + this.gettingMore.emit(true); + this.loadMore().then((results: T[]) => { + this.loading = false; + this.gettingMore.emit(false); + this.onDataChange(new CollectionEvent(CollectionEvent.CHANGE, results)); + }); + }); + } + + get initialTotal(): number { + return this.initialTotalResults; + } + + get total(): number { + return this.totalResults; + } + + isLoading(): boolean { + return this.loading; + } + + hasErrors(): boolean { + return this._errored; + } + + get pageSize(): number { + return this._pageSize; + } + + set pageSize(value: number) { + if (this._pageSize !== value) { + this._pageSize = value; + this.page = 1; + this.pagesLoaded = 0; + this.refresh(); + } + } + + reset(): void { + this.resetting = true; + this.pagesLoaded = 0; + this._page = 1; + this._errored = false; + this.forced++; + this.refresh(); + } + + loadMore(): any { + const start: number = (this.page - 1) * this.pageSize; + + if (this.needMore()) { + this.loading = true; + this.gettingMore.emit(true); + this.pagesLoaded++; + + let count: number = this.pageSize; + let start: number = (this.pagesLoaded - 1) * this.pageSize; + + return new Promise>(resolve => { + let request: QueryRequest = this.getRequest(start, count); + + this.dataProvider.getData(request).then( response => { + this.loading = false; + this.gettingMore.emit(false); + this.addItems(response.data); + this.totalResults = response.total; + + if (this.initialTotalResults === 0 || this.resetting) { + this.resetting = false; + this.initialTotalResults = this.totalResults; + } + + return resolve(this.loadMore()); + }).catch(error => { + console.error(`Error getting data: ${error}`); + + this.loading = false; + this.gettingMore.emit(false); + this._errored = true; + }); + }); + } + + let end: number = start + this.pageSize; + let result: any = this.source.slice(start, end); + + return Promise.resolve(result); + } + + getRequest(start: number, count: number): QueryRequest { + let sort: string = ''; + let sortDirection: 'DESC' | 'ASC' = 'ASC'; + + if(this.sort && this.sort.length > 0 && this.sort[0].field) { + sort = this.sort[0].field; + sortDirection = this.sort[0].reverse ? 'DESC' : 'ASC'; + } + + return { + count: count, + start: start, + sort: sort, + sortDirection: sortDirection, + filters: this.filter + } + } + + removeAll(): void { + this.source = []; + this.filterData = []; + } + + needMore(): boolean { + const sortChanged = this.sortChanged(); + const filterChanged = this.filterChanged(); + + if (this.pagesLoaded === 0 || sortChanged || filterChanged) { + this.removeAll(); + return true; + } + + let recordsNeeded: number = this.page * this.pageSize; + + return !(this.source.length >= Math.min(this.totalResults, recordsNeeded)); + } + + filterChanged(): boolean { + if(this.lastFilter != this.filter) { + this.lastFilter = this.filter; + this.page = 1; + this.pagesLoaded = 0; + return true; + } + + return false; + } + + sortChanged() { + if(this.lastSort != this.sort) { + this.lastSort = this.sort; + this.page = 1; + this.pagesLoaded = 0; + return true; + } + + return false; + } + + refresh(): void { + this.filterData = this.source.slice(); + + let event: any = JSON.stringify({ + page: this.page, + pageSize: this.pageSize, + forced: this.forced, + }); + + this.refreshing.emit(event); + } + + + +} diff --git a/src/app/tools/table/table.module.ts b/src/app/tools/table/table.module.ts new file mode 100644 index 0000000..d30d36f --- /dev/null +++ b/src/app/tools/table/table.module.ts @@ -0,0 +1,36 @@ +import {NgModule} from '@angular/core'; +import {CommonModule} from '@angular/common'; + +import {NovoElementProviders, NovoElementsModule} from 'novo-elements'; + +import {PreviewCellComponent} from './cells/preview-cell.component'; +import {DeleteCellComponent} from './cells/delete-cell.component'; +import {EditCellComponent} from './cells/edit-cell.component'; +import {IconCellComponent} from './cells/icon-cell.component'; + +@NgModule({ + imports: [ + CommonModule, + NovoElementsModule, + NovoElementProviders.forChild() + ], + declarations: [ + PreviewCellComponent, + DeleteCellComponent, + EditCellComponent, + IconCellComponent + ], + entryComponents: [ + PreviewCellComponent, + DeleteCellComponent, + EditCellComponent, + IconCellComponent + ], + exports: [ + PreviewCellComponent, + DeleteCellComponent, + EditCellComponent, + IconCellComponent + ] +}) +export class TableModule { } diff --git a/src/app/tools/table/table.types.ts b/src/app/tools/table/table.types.ts new file mode 100644 index 0000000..e924556 --- /dev/null +++ b/src/app/tools/table/table.types.ts @@ -0,0 +1,21 @@ +export interface DataService { + + getData(request: QueryRequest): Promise>; + +} + +export interface QueryResult { + data: Array; + total: number; + +} + +export interface QueryRequest { + + start: number; + count: number; + sort: string; + sortDirection: 'DESC' | 'ASC'; + filters: { [key: string]: any}; + +} diff --git a/src/app/tools/table/table.utils.ts b/src/app/tools/table/table.utils.ts new file mode 100644 index 0000000..e675b31 --- /dev/null +++ b/src/app/tools/table/table.utils.ts @@ -0,0 +1,86 @@ +export function getNestedElement(data: any, property: string) { + const fields = property.split('.'); + + let result = data[fields[0]]; + + fields.slice(1).forEach( field => { + result = result[field]; + }); + + return result; +} + +export function joinNames(entities, nameProperty = 'name') { + if (entities === undefined || !Array.isArray(entities)) { + return ''; + } + + return entities.map( entity => entity[nameProperty]).join(', '); +} + +export function sortNestedProperty(sort, previous, current, property) { + const first = getNestedElement(previous, property), + second = getNestedElement(current, property); + + return sortValue(sort, first, second); +} + +export function sortBooleanProperty(sort, previous, current, property) { + const first = getNestedElement(previous, property) ? 'Yes' : 'No', + second = getNestedElement(current, property) ? 'Yes' : 'No'; + + return sortValue(sort, first, second); +} + +export function sortArrayProperty(sort, previous, current, property, nameProperty = 'name') { + const previousEntities = getNestedElement(previous, property); + const previousNames = joinNames(previousEntities, nameProperty); + const currentEntities = getNestedElement(current, property); + const currentNames = joinNames(currentEntities, nameProperty); + + return sortValue(sort, previousNames, currentNames); +} + +export function filterNestedProperty(data, filter, property) { + const value = getNestedElement(data, property); + + return filterProperty(value, filter); +} + +export function filterBooleanProperty(data, filter, property) { + const value = getNestedElement(data, property) ? 'Yes' : 'No'; + + return filterProperty(value, filter); +} + +export function filterArrayProperty(data, filter, property, nameProperty = 'name') { + const value = getNestedElement(data, property); + const valueNames = joinNames(value, nameProperty); + + return filterProperty(valueNames, filter); +} + +export function filterProperty(value, filter) { + return value === undefined || filter === undefined || filter.length === 0 || + value.toString().toLowerCase().indexOf(filter.toLowerCase()) !== -1; +} + +export function sortValue(sort, first, second) { + let firstToSort = first; + let secondToSort = second; + + if (typeof first === 'string') { + firstToSort = first.toString().toLowerCase(); + secondToSort = second.toString().toLowerCase(); + } + + if (firstToSort > secondToSort) { + return sort === 'desc' ? -1 : 1; + } + + if (firstToSort < secondToSort) { + return sort === 'asc' ? -1 : 1; + } + + return 0; +} diff --git a/src/app/tools/tools.types.ts b/src/app/tools/tools.types.ts new file mode 100644 index 0000000..630731c --- /dev/null +++ b/src/app/tools/tools.types.ts @@ -0,0 +1,7 @@ +export interface Result { + + entity: T; + success: boolean; + errors?: Array; + +} diff --git a/src/environments/environment.local.ts b/src/environments/environment.local.ts new file mode 100644 index 0000000..9c5100e --- /dev/null +++ b/src/environments/environment.local.ts @@ -0,0 +1,9 @@ +export const environment = { + local: true, + production: false, + appBridgeConfig: { + title: 'CustomAppInStaging', + url: 'https://HOSTED_URL/sample', + color: 'blue' + } +}; diff --git a/src/environments/environment.prod.ts b/src/environments/environment.prod.ts index 81fb417..96465a5 100644 --- a/src/environments/environment.prod.ts +++ b/src/environments/environment.prod.ts @@ -1,4 +1,5 @@ export const environment = { + local: false, production: true, appBridgeConfig: { title: 'CustomApp', diff --git a/src/environments/environment.staging.ts b/src/environments/environment.staging.ts index 58c4459..bb25539 100644 --- a/src/environments/environment.staging.ts +++ b/src/environments/environment.staging.ts @@ -1,4 +1,5 @@ export const environment = { + local: false, production: false, appBridgeConfig: { title: 'CustomAppInStaging', diff --git a/src/environments/environment.ts b/src/environments/environment.ts index 719c80e..463d337 100644 --- a/src/environments/environment.ts +++ b/src/environments/environment.ts @@ -4,6 +4,7 @@ // The list of which env maps to which file can be found in `.angular-cli.json`. export const environment = { + local: false, production: false, appBridgeConfig: { title: 'CustomApp',