Browse Source

feat(admin-ui): Implement add existing option group to product

Michael Bromley 7 years ago
parent
commit
92b8d8881c

+ 8 - 2
admin-ui/src/app/catalog/catalog.module.ts

@@ -7,13 +7,19 @@ import { catalogRoutes } from './catalog.routes';
 import { CreateOptionGroupDialogComponent } from './components/create-option-group-dialog/create-option-group-dialog.component';
 import { ProductDetailComponent } from './components/product-detail/product-detail.component';
 import { ProductListComponent } from './components/product-list/product-list.component';
+import { SelectOptionGroupDialogComponent } from './components/select-option-group-dialog/select-option-group-dialog.component';
 import { ProductResolver } from './providers/routing/product-resolver';
 
 @NgModule({
     imports: [SharedModule, RouterModule.forChild(catalogRoutes)],
     exports: [],
-    declarations: [ProductListComponent, ProductDetailComponent, CreateOptionGroupDialogComponent],
-    entryComponents: [CreateOptionGroupDialogComponent],
+    declarations: [
+        ProductListComponent,
+        ProductDetailComponent,
+        CreateOptionGroupDialogComponent,
+        SelectOptionGroupDialogComponent,
+    ],
+    entryComponents: [CreateOptionGroupDialogComponent, SelectOptionGroupDialogComponent],
     providers: [ProductResolver],
 })
 export class CatalogModule {}

+ 5 - 16
admin-ui/src/app/catalog/components/create-option-group-dialog/create-option-group-dialog.component.ts

@@ -7,6 +7,7 @@ import { getDefaultLanguage } from '../../../common/utilities/get-default-langua
 import { normalizeString } from '../../../common/utilities/normalize-string';
 import { DataService } from '../../../data/providers/data.service';
 import {
+    CreateProductOptionGroup,
     CreateProductOptionGroupInput,
     CreateProductOptionInput,
     LanguageCode,
@@ -19,8 +20,8 @@ import { Dialog } from '../../../shared/providers/modal/modal.service';
     styleUrls: ['./create-option-group-dialog.component.scss'],
     changeDetection: ChangeDetectionStrategy.OnPush,
 })
-export class CreateOptionGroupDialogComponent implements Dialog, OnInit {
-    resolveWith: (result?: any) => void;
+export class CreateOptionGroupDialogComponent implements Dialog<CreateProductOptionGroup>, OnInit {
+    resolveWith: (result?: CreateProductOptionGroup) => void;
     optionGroupForm: FormGroup;
     productName = '';
     productId: string;
@@ -47,23 +48,11 @@ export class CreateOptionGroupDialogComponent implements Dialog, OnInit {
     createOptionGroup() {
         this.dataService.product
             .createProductOptionGroups(this.createGroupFromForm())
-            .pipe(
-                mergeMap(data => {
-                    const optionGroup = data.createProductOptionGroup;
-                    if (optionGroup) {
-                        return this.dataService.product.addOptionGroupToProduct({
-                            productId: this.productId,
-                            optionGroupId: optionGroup.id,
-                        });
-                    }
-                    return [];
-                }),
-            )
-            .subscribe(product => this.resolveWith(product));
+            .subscribe(data => this.resolveWith(data));
     }
 
     cancel() {
-        this.resolveWith('cancelled!');
+        this.resolveWith();
     }
 
     private createGroupFromForm(): CreateProductOptionGroupInput {

+ 3 - 1
admin-ui/src/app/catalog/components/product-detail/product-detail.component.html

@@ -39,7 +39,9 @@
                         <clr-icon shape="caret down"></clr-icon>
                     </button>
                     <clr-dropdown-menu clrPosition="bottom-right" *clrIfOpen>
-                        <button type="button" clrDropdownItem>{{ 'catalog.add-existing-option-group' | translate }}</button>
+                        <button type="button"
+                                clrDropdownItem
+                                (click)="addExistingOptionGroup()">{{ 'catalog.add-existing-option-group' | translate }}</button>
                         <button type="button"
                                 clrDropdownItem
                                 (click)="createNewOptionGroup()">{{ 'catalog.create-new-option-group' | translate }}</button>

+ 45 - 9
admin-ui/src/app/catalog/components/product-detail/product-detail.component.ts

@@ -2,13 +2,14 @@ import { Component, OnDestroy } from '@angular/core';
 import { FormBuilder, FormGroup, Validators } from '@angular/forms';
 import { ActivatedRoute, Router } from '@angular/router';
 import { combineLatest, Observable, Subject } from 'rxjs';
-import { map, mergeMap, take, takeUntil } from 'rxjs/operators';
+import { map, mergeMap, take, takeUntil, tap } from 'rxjs/operators';
 
 import { getDefaultLanguage } from '../../../common/utilities/get-default-language';
 import { DataService } from '../../../data/providers/data.service';
 import { GetProductWithVariants_product, LanguageCode } from '../../../data/types/gql-generated-types';
 import { ModalService } from '../../../shared/providers/modal/modal.service';
 import { CreateOptionGroupDialogComponent } from '../create-option-group-dialog/create-option-group-dialog.component';
+import { SelectOptionGroupDialogComponent } from '../select-option-group-dialog/select-option-group-dialog.component';
 
 @Component({
     selector: 'vdr-product-detail',
@@ -73,14 +74,49 @@ export class ProductDetailComponent implements OnDestroy {
                 mergeMap(product => {
                     const nameControl = this.productForm.get('name');
                     const productName = nameControl ? nameControl.value : '';
-                    return this.modalService.fromComponent(CreateOptionGroupDialogComponent, {
-                        closable: true,
-                        size: 'lg',
-                        locals: {
-                            productName,
-                            productId: product.id,
-                        },
-                    });
+                    return this.modalService
+                        .fromComponent(CreateOptionGroupDialogComponent, {
+                            closable: true,
+                            size: 'lg',
+                            locals: {
+                                productName,
+                                productId: product.id,
+                            },
+                        })
+                        .pipe(
+                            mergeMap(({ createProductOptionGroup }) => {
+                                return this.dataService.product.addOptionGroupToProduct({
+                                    productId: product.id,
+                                    optionGroupId: createProductOptionGroup.id,
+                                });
+                            }),
+                        );
+                }),
+            )
+            .subscribe();
+    }
+
+    addExistingOptionGroup() {
+        this.product$
+            .pipe(
+                take(1),
+                mergeMap(product => {
+                    return this.modalService
+                        .fromComponent(SelectOptionGroupDialogComponent, {
+                            closable: true,
+                            size: 'lg',
+                            locals: {
+                                existingOptionGroupIds: product.optionGroups.map(g => g.id),
+                            },
+                        })
+                        .pipe(
+                            mergeMap(optionGroup => {
+                                return this.dataService.product.addOptionGroupToProduct({
+                                    productId: product.id,
+                                    optionGroupId: optionGroup.id,
+                                });
+                            }),
+                        );
                 }),
             )
             .subscribe();

+ 32 - 0
admin-ui/src/app/catalog/components/select-option-group-dialog/select-option-group-dialog.component.html

@@ -0,0 +1,32 @@
+<ng-template vdrDialogTitle>{{ 'catalog.select-option-group' | translate }}</ng-template>
+<input type="text"
+       class="filter-input"
+       [placeholder]="'catalog.filter-by-group-name' | translate"
+       [formControl]="filterInput">
+<div class="group-list">
+    <div class="group" *ngFor="let group of optionGroups$ | async">
+        <div class="select-button">
+            <button class="btn btn-icon btn-primary"
+                    [disabled]="!isAvailable(group)"
+                    (click)="selectGroup(group)">
+                <clr-icon shape="check"></clr-icon>
+            </button>
+        </div>
+        <div class="name-code">
+            <div class="name">{{ group.name }}</div>
+            <div class="code">{{ group.code }}</div>
+        </div>
+        <div class="options">
+            <div class="option" *ngFor="let option of group.options">
+                {{ option.name }}
+            </div>
+        </div>
+    </div>
+</div>
+
+
+<ng-template vdrDialogButtons>
+    <button type="button"
+            class="btn"
+            (click)="cancel()">{{ 'common.cancel' | translate }}</button>
+</ng-template>

+ 42 - 0
admin-ui/src/app/catalog/components/select-option-group-dialog/select-option-group-dialog.component.scss

@@ -0,0 +1,42 @@
+@import "variables";
+
+.filter-input {
+    width: 100%;
+}
+
+.group-list {
+    margin-top: 24px;
+    height: 400px;
+    max-height: 60vh;
+    overflow: auto;
+}
+
+.group {
+    display: flex;
+    padding: 6px 12px;
+    border-bottom: 1px solid $color-grey-2;
+
+    .name-code {
+        flex: 1;
+    }
+
+    .code {
+        color: $color-grey-4;
+    }
+
+    .options {
+        text-align: right;
+    }
+
+    .option {
+        display: inline-block;
+        padding: 0 6px;
+        border: 1px solid $color-grey-3;
+        color: $color-grey-5;
+        border-radius: 3px;
+        margin-right: 3px;
+        &:last-of-type {
+            margin-right: 0;
+        }
+    }
+}

+ 32 - 0
admin-ui/src/app/catalog/components/select-option-group-dialog/select-option-group-dialog.component.spec.ts

@@ -0,0 +1,32 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+import { ReactiveFormsModule } from '@angular/forms';
+import { ClrIconCustomTag } from '@clr/angular';
+
+import { MockTranslatePipe } from '../../../../testing/translate.pipe.mock';
+import { DataService } from '../../../data/providers/data.service';
+import { MockDataService } from '../../../data/providers/data.service.mock';
+
+import { SelectOptionGroupDialogComponent } from './select-option-group-dialog.component';
+
+describe('SelectOptionGroupDialogComponent', () => {
+    let component: SelectOptionGroupDialogComponent;
+    let fixture: ComponentFixture<SelectOptionGroupDialogComponent>;
+
+    beforeEach(async(() => {
+        TestBed.configureTestingModule({
+            imports: [ReactiveFormsModule],
+            declarations: [SelectOptionGroupDialogComponent, ClrIconCustomTag, MockTranslatePipe],
+            providers: [{ provide: DataService, useClass: MockDataService }],
+        }).compileComponents();
+    }));
+
+    beforeEach(() => {
+        fixture = TestBed.createComponent(SelectOptionGroupDialogComponent);
+        component = fixture.componentInstance;
+        fixture.detectChanges();
+    });
+
+    it('should create', () => {
+        expect(component).toBeTruthy();
+    });
+});

+ 57 - 0
admin-ui/src/app/catalog/components/select-option-group-dialog/select-option-group-dialog.component.ts

@@ -0,0 +1,57 @@
+import { ChangeDetectionStrategy, Component, OnDestroy, OnInit } from '@angular/core';
+import { FormControl } from '@angular/forms';
+import { Observable, Subject } from 'rxjs';
+import { debounceTime, map, takeUntil } from 'rxjs/operators';
+
+import { DataService } from '../../../data/providers/data.service';
+import { GetProductOptionGroups_productOptionGroups } from '../../../data/types/gql-generated-types';
+import { Dialog } from '../../../shared/providers/modal/modal.service';
+
+export type ProductOptionGroup = GetProductOptionGroups_productOptionGroups;
+
+@Component({
+    selector: 'vdr-select-option-group-dialog',
+    templateUrl: './select-option-group-dialog.component.html',
+    styleUrls: ['./select-option-group-dialog.component.scss'],
+    changeDetection: ChangeDetectionStrategy.OnPush,
+})
+export class SelectOptionGroupDialogComponent implements Dialog<ProductOptionGroup>, OnInit, OnDestroy {
+    resolveWith: (result?: ProductOptionGroup) => void;
+    existingOptionGroupIds: string[];
+    optionGroups$: Observable<ProductOptionGroup[]>;
+    filterInput = new FormControl();
+    private destroy$ = new Subject<void>();
+
+    constructor(private dataService: DataService) {}
+
+    ngOnInit() {
+        const optionGroupsQuery = this.dataService.product.getProductOptionGroups();
+        this.optionGroups$ = optionGroupsQuery.stream$.pipe(map(data => data.productOptionGroups));
+
+        this.filterInput.valueChanges
+            .pipe(
+                debounceTime(300),
+                takeUntil(this.destroy$),
+            )
+            .subscribe(filterTerm => {
+                optionGroupsQuery.ref.refetch({ filterTerm });
+            });
+    }
+
+    ngOnDestroy() {
+        this.destroy$.next();
+        this.destroy$.complete();
+    }
+
+    isAvailable(group: ProductOptionGroup): boolean {
+        return !this.existingOptionGroupIds.includes(group.id);
+    }
+
+    selectGroup(group: ProductOptionGroup) {
+        this.resolveWith(group);
+    }
+
+    cancel() {
+        this.resolveWith();
+    }
+}

+ 7 - 2
admin-ui/src/app/catalog/providers/routing/product-resolver.ts

@@ -1,8 +1,9 @@
 import { Injectable } from '@angular/core';
 import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router';
 import { Observable, of } from 'rxjs';
-import { finalize, map, take, tap } from 'rxjs/operators';
+import { filter, finalize, map, take, tap } from 'rxjs/operators';
 
+import { notNullOrUndefined } from '../../../../../../shared/shared-utils';
 import { DataService } from '../../../data/providers/data.service';
 import { GetProductWithVariants_product } from '../../../data/types/gql-generated-types';
 /**
@@ -20,7 +21,11 @@ export class ProductResolver implements Resolve<Observable<GetProductWithVariant
         const id = route.paramMap.get('id');
 
         if (id) {
-            const stream = this.dataService.product.getProduct(id).mapStream(data => data.product);
+            const stream = this.dataService.product
+                .getProduct(id)
+                .mapStream(data => data.product)
+                .pipe(filter(notNullOrUndefined));
+
             return stream.pipe(
                 take(1),
                 map(() => stream),

+ 55 - 1
admin-ui/src/app/data/providers/data.service.mock.ts

@@ -1 +1,55 @@
-export class MockDataService {}
+import { of } from 'rxjs';
+
+import { QueryResult } from '../types/query-result';
+
+import { DataService } from './data.service';
+
+export type DataServiceSectionMock<T> = { [K in keyof T]: jasmine.Spy };
+
+export type DataServiceMock = { [K in keyof DataService]: DataServiceSectionMock<DataService[K]> };
+
+export type MockQueryResult = { [K in keyof QueryResult<any>]: any };
+
+export function spyQueryResult(name: string): jasmine.Spy {
+    const mockQueryResult: MockQueryResult = {
+        ref: {},
+        stream$: of({}),
+        single$: of({}),
+        mapStream() {
+            return of({});
+        },
+        mapSingle() {
+            return of({});
+        },
+    };
+    return jasmine.createSpy(name).and.returnValue(mockQueryResult);
+}
+
+export function spyObservable(name: string, returnValue: any = {}): jasmine.Spy {
+    return jasmine.createSpy(name).and.returnValue(of(returnValue));
+}
+
+export class MockDataService implements DataServiceMock {
+    client = {
+        startRequest: spyObservable('startRequest'),
+        completeRequest: spyObservable('completeRequest'),
+        getNetworkStatus: spyObservable('getNetworkStatus'),
+        loginSuccess: spyObservable('loginSuccess'),
+        logOut: spyObservable('logOut'),
+        userStatus: spyQueryResult('userStatus'),
+        uiState: spyQueryResult('uiState'),
+        setUiLanguage: spyObservable('setUiLanguage'),
+    };
+    product = {
+        getProducts: spyQueryResult('getProducts'),
+        getProduct: spyQueryResult('getProduct'),
+        updateProduct: spyObservable('updateProduct'),
+        createProductOptionGroups: spyObservable('createProductOptionGroups'),
+        addOptionGroupToProduct: spyObservable('addOptionGroupToProduct'),
+        getProductOptionGroups: spyQueryResult('getProductOptionGroups'),
+    };
+    user = {
+        checkLoggedIn: spyObservable('checkLoggedIn'),
+        attemptLogin: spyObservable('attemptLogin'),
+    };
+}

+ 19 - 1
admin-ui/src/app/data/providers/product-data.service.ts

@@ -6,7 +6,11 @@ import {
     CREATE_PRODUCT_OPTION_GROUP,
     UPDATE_PRODUCT,
 } from '../mutations/product-mutations';
-import { GET_PRODUCT_LIST, GET_PRODUCT_WITH_VARIANTS } from '../queries/product-queries';
+import {
+    GET_PRODUCT_LIST,
+    GET_PRODUCT_OPTION_GROUPS,
+    GET_PRODUCT_WITH_VARIANTS,
+} from '../queries/product-queries';
 import {
     AddOptionGroupToProduct,
     AddOptionGroupToProductVariables,
@@ -15,6 +19,8 @@ import {
     CreateProductOptionGroupVariables,
     GetProductList,
     GetProductListVariables,
+    GetProductOptionGroups,
+    GetProductOptionGroupsVariables,
     GetProductWithVariants,
     GetProductWithVariantsVariables,
     UpdateProduct,
@@ -77,4 +83,16 @@ export class ProductDataService {
             variables,
         );
     }
+
+    getProductOptionGroups(
+        filterTerm?: string,
+    ): QueryResult<GetProductOptionGroups, GetProductOptionGroupsVariables> {
+        return this.baseDataService.query<GetProductOptionGroups, GetProductOptionGroupsVariables>(
+            GET_PRODUCT_OPTION_GROUPS,
+            {
+                filterTerm,
+                languageCode: getDefaultLanguage(),
+            },
+        );
+    }
 }

+ 17 - 0
admin-ui/src/app/data/queries/product-queries.ts

@@ -25,3 +25,20 @@ export const GET_PRODUCT_LIST = gql`
         }
     }
 `;
+
+export const GET_PRODUCT_OPTION_GROUPS = gql`
+    query GetProductOptionGroups($filterTerm: String, $languageCode: LanguageCode) {
+        productOptionGroups(filterTerm: $filterTerm, languageCode: $languageCode) {
+            id
+            languageCode
+            code
+            name
+            options {
+                id
+                languageCode
+                code
+                name
+            }
+        }
+    }
+`;

+ 59 - 26
admin-ui/src/app/data/types/gql-generated-types.ts

@@ -94,9 +94,9 @@ export interface UpdateProduct_updateProduct_translations {
 export interface UpdateProduct_updateProduct_optionGroups {
     __typename: 'ProductOptionGroup';
     id: string;
-    languageCode: LanguageCode | null;
-    code: string | null;
-    name: string | null;
+    languageCode: LanguageCode;
+    code: string;
+    name: string;
 }
 
 export interface UpdateProduct_updateProduct_variants_options {
@@ -175,15 +175,15 @@ export interface CreateProductOptionGroup_createProductOptionGroup_options {
 export interface CreateProductOptionGroup_createProductOptionGroup {
     __typename: 'ProductOptionGroup';
     id: string;
-    languageCode: LanguageCode | null;
-    code: string | null;
-    name: string | null;
-    translations: (CreateProductOptionGroup_createProductOptionGroup_translations | null)[] | null;
-    options: (CreateProductOptionGroup_createProductOptionGroup_options | null)[] | null;
+    languageCode: LanguageCode;
+    code: string;
+    name: string;
+    translations: CreateProductOptionGroup_createProductOptionGroup_translations[];
+    options: CreateProductOptionGroup_createProductOptionGroup_options[];
 }
 
 export interface CreateProductOptionGroup {
-    createProductOptionGroup: CreateProductOptionGroup_createProductOptionGroup | null; // Create a new ProductOptionGroup
+    createProductOptionGroup: CreateProductOptionGroup_createProductOptionGroup; // Create a new ProductOptionGroup
 }
 
 export interface CreateProductOptionGroupVariables {
@@ -206,8 +206,8 @@ export interface AddOptionGroupToProduct_addOptionGroupToProduct_optionGroups_op
 export interface AddOptionGroupToProduct_addOptionGroupToProduct_optionGroups {
     __typename: 'ProductOptionGroup';
     id: string;
-    code: string | null;
-    options: (AddOptionGroupToProduct_addOptionGroupToProduct_optionGroups_options | null)[] | null;
+    code: string;
+    options: AddOptionGroupToProduct_addOptionGroupToProduct_optionGroups_options[];
 }
 
 export interface AddOptionGroupToProduct_addOptionGroupToProduct {
@@ -293,9 +293,9 @@ export interface GetProductWithVariants_product_translations {
 export interface GetProductWithVariants_product_optionGroups {
     __typename: 'ProductOptionGroup';
     id: string;
-    languageCode: LanguageCode | null;
-    code: string | null;
-    name: string | null;
+    languageCode: LanguageCode;
+    code: string;
+    name: string;
 }
 
 export interface GetProductWithVariants_product_variants_options {
@@ -339,7 +339,7 @@ export interface GetProductWithVariants_product {
 }
 
 export interface GetProductWithVariants {
-    product: GetProductWithVariants_product;
+    product: GetProductWithVariants_product | null;
 }
 
 export interface GetProductWithVariantsVariables {
@@ -382,6 +382,39 @@ export interface GetProductListVariables {
 /* tslint:disable */
 // This file was automatically generated and should not be edited.
 
+// ====================================================
+// GraphQL query operation: GetProductOptionGroups
+// ====================================================
+
+export interface GetProductOptionGroups_productOptionGroups_options {
+    __typename: 'ProductOption';
+    id: string;
+    languageCode: LanguageCode | null;
+    code: string | null;
+    name: string | null;
+}
+
+export interface GetProductOptionGroups_productOptionGroups {
+    __typename: 'ProductOptionGroup';
+    id: string;
+    languageCode: LanguageCode;
+    code: string;
+    name: string;
+    options: GetProductOptionGroups_productOptionGroups_options[];
+}
+
+export interface GetProductOptionGroups {
+    productOptionGroups: GetProductOptionGroups_productOptionGroups[];
+}
+
+export interface GetProductOptionGroupsVariables {
+    filterTerm?: string | null;
+    languageCode?: LanguageCode | null;
+}
+
+/* tslint:disable */
+// This file was automatically generated and should not be edited.
+
 // ====================================================
 // GraphQL fragment: ProductWithVariants
 // ====================================================
@@ -397,9 +430,9 @@ export interface ProductWithVariants_translations {
 export interface ProductWithVariants_optionGroups {
     __typename: 'ProductOptionGroup';
     id: string;
-    languageCode: LanguageCode | null;
-    code: string | null;
-    name: string | null;
+    languageCode: LanguageCode;
+    code: string;
+    name: string;
 }
 
 export interface ProductWithVariants_variants_options {
@@ -470,11 +503,11 @@ export interface ProductOptionGroup_options {
 export interface ProductOptionGroup {
     __typename: 'ProductOptionGroup';
     id: string;
-    languageCode: LanguageCode | null;
-    code: string | null;
-    name: string | null;
-    translations: (ProductOptionGroup_translations | null)[] | null;
-    options: (ProductOptionGroup_options | null)[] | null;
+    languageCode: LanguageCode;
+    code: string;
+    name: string;
+    translations: ProductOptionGroup_translations[];
+    options: ProductOptionGroup_options[];
 }
 
 /* tslint:disable */
@@ -692,8 +725,8 @@ export interface ProductTranslationInput {
 //
 export interface CreateProductOptionGroupInput {
     code: string;
-    translations: (ProductOptionGroupTranslationInput | null)[];
-    options?: (CreateProductOptionInput | null)[] | null;
+    translations: ProductOptionGroupTranslationInput[];
+    options: CreateProductOptionInput[];
 }
 
 //
@@ -706,7 +739,7 @@ export interface ProductOptionGroupTranslationInput {
 //
 export interface CreateProductOptionInput {
     code: string;
-    translations: (ProductOptionGroupTranslationInput | null)[];
+    translations: ProductOptionGroupTranslationInput[];
 }
 
 //==============================================================

+ 1 - 1
admin-ui/src/app/shared/components/modal-dialog/modal-dialog.component.ts

@@ -23,7 +23,7 @@ import { DialogButtonsDirective } from './dialog-buttons.directive';
     templateUrl: './modal-dialog.component.html',
     styleUrls: ['./modal-dialog.component.scss'],
 })
-export class ModalDialogComponent<T extends Dialog> {
+export class ModalDialogComponent<T extends Dialog<any>> {
     childComponentType: Type<T>;
     closeModal: (result?: any) => void;
     titleTemplateRef$ = new Subject<TemplateRef<any>>();

+ 7 - 4
admin-ui/src/app/shared/providers/modal/modal.service.ts

@@ -9,13 +9,13 @@ import { ModalDialogComponent } from '../../components/modal-dialog/modal-dialog
  * Any component intended to be used with the ModalService.fromComponent() method must implement
  * this interface.
  */
-export interface Dialog {
+export interface Dialog<R = any> {
     /**
      * Function to be invoked in order to close the dialog when the action is complete.
      * The Observable returned from the .fromComponent() method will emit the value passed
      * to this method and then complete.
      */
-    resolveWith: (result?: any) => void;
+    resolveWith: (result?: R) => void;
 }
 
 /**
@@ -91,10 +91,13 @@ export class ModalService {
      * </ng-template>
      * ```
      */
-    fromComponent<T extends Dialog>(component: Type<T>, options?: ModalOptions<T>): Observable<any> {
+    fromComponent<T extends Dialog<any>, R>(
+        component: Type<T> & Type<Dialog<R>>,
+        options?: ModalOptions<T>,
+    ): Observable<R> {
         const modalFactory = this.componentFactoryResolver.resolveComponentFactory(ModalDialogComponent);
         const modalComponentRef = this.hostView.createComponent(modalFactory);
-        const modalInstance = modalComponentRef.instance;
+        const modalInstance: ModalDialogComponent<any> = modalComponentRef.instance;
         modalInstance.childComponentType = component;
         modalInstance.options = options;
 

+ 3 - 1
admin-ui/src/tsconfig.app.json

@@ -3,7 +3,9 @@
   "compilerOptions": {
     "outDir": "../out-tsc/app",
     "module": "es2015",
-    "types": []
+    "types": [
+      "jasmine"
+    ]
   },
   "exclude": [
     "src/test.ts",

File diff suppressed because it is too large
+ 0 - 0
schema.json


Some files were not shown because too many files changed in this diff