Skip to content

Commit

Permalink
Added tiles widgets for api products and products list (#1162)
Browse files Browse the repository at this point in the history
  • Loading branch information
ygrik authored Feb 23, 2021
1 parent b9e23b3 commit e20fb9d
Show file tree
Hide file tree
Showing 22 changed files with 448 additions and 10 deletions.
4 changes: 4 additions & 0 deletions src/apim.runtime.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ import { ProductSubscribe } from "./components/products/product-subscribe/ko/run
import { DefaultAuthenticator } from "./components/defaultAuthenticator";
import { Spinner } from "./components/spinner/spinner";
import { ProductApis } from "./components/products/product-apis/ko/runtime/product-apis";
import { ProductApisTiles } from "./components/products/product-apis/ko/runtime/product-apis-tiles";
import { OperationList } from "./components/operations/operation-list/ko/runtime/operation-list";
import { ProductSubscriptions } from "./components/products/product-subscriptions/ko/runtime/product-subscriptions";
import { AadService } from "./services/aadService";
Expand All @@ -71,6 +72,7 @@ import { OAuthService } from "./services/oauthService";
import { DefaultSessionManager } from "./authentication/defaultSessionManager";
import { ApiProducts } from "./components/apis/api-products/ko/runtime/api-products";
import { ApiProductsTiles } from "./components/apis/api-products/ko/runtime/api-products-tiles";
import { ProductListTiles } from "./components/products/product-list/ko/runtime/product-list-tiles";

export class ApimRuntimeModule implements IInjectorModule {
public register(injector: IInjector): void {
Expand Down Expand Up @@ -110,11 +112,13 @@ export class ApimRuntimeModule implements IInjectorModule {
injector.bind("subscriptions", Subscriptions);
injector.bind("productList", ProductList);
injector.bind("productListDropdown", ProductListDropdown);
injector.bind("productListTiles", ProductListTiles);
injector.bind("validationSummary", ValidationSummary);
injector.bind("productDetails", ProductDetails);
injector.bind("productSubscribe", ProductSubscribe);
injector.bind("productSubscriptions", ProductSubscriptions);
injector.bind("productApis", ProductApis);
injector.bind("productApisTiles", ProductApisTiles);
injector.bind("operationList", OperationList);
injector.bind("operationDetails", OperationDetails);
injector.bind("usersService", UsersService);
Expand Down
2 changes: 1 addition & 1 deletion src/components/apis/api-products/apiProductsHandlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export class ApiProductsHandlers implements IWidgetHandler {
displayName: "API: products",
iconClass: "paperbits-cheque-3",
requires: ["html"],
createModel: async () => new ApiProductsModel()
createModel: async () => new ApiProductsModel("list")
};

return widgetOrder;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export class ApiProductsViewModelBinder implements ViewModelBinder<ApiProductsMo
}));

viewModel["widgetBinding"] = {
displayName: "API: products",
displayName: "API: products" + (model.layout === "list" ? "" : ` (${model.layout})`),
model: model,
draggable: true,
editor: "api-products-editor",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export class ListOfApisViewModelBinder implements ViewModelBinder<ListOfApisMode
}));

viewModel["widgetBinding"] = {
displayName: "List of APIs",
displayName: "List of APIs" + (model.layout === "list" ? "" : ` (${model.layout})`),
model: model,
draggable: true,
editor: "list-of-apis-editor",
Expand Down
6 changes: 6 additions & 0 deletions src/components/products/product-apis/ko/productApis.html
Original file line number Diff line number Diff line change
@@ -1 +1,7 @@
<!-- ko if: layout() === 'list' -->
<product-apis-runtime data-bind="attr: { params: runtimeConfig }"></product-apis-runtime>
<!-- /ko -->

<!-- ko if: layout() === 'tiles' -->
<product-apis-tiles-runtime data-bind="attr: { params: runtimeConfig }"></product-apis-tiles-runtime>
<!-- /ko -->
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { IInjectorModule, IInjector } from "@paperbits/common/injection";
import { ProductApisHandlers } from "../productApisHandlers";
import { ProductApisHandlers, ProductApisTilesHandlers } from "../productApisHandlers";
import { ProductApisEditor } from "./productApisEditor";

export class ProductApisEditorModule implements IInjectorModule {
public register(injector: IInjector): void {
injector.bind("productApisEditor", ProductApisEditor);
injector.bindToCollection("widgetHandlers", ProductApisHandlers, "productApisHandlers");
injector.bindToCollection("widgetHandlers", ProductApisTilesHandlers, "productApisTilesHandlers");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@ import { Component } from "@paperbits/common/ko/decorators/component.decorator";
template: template
})
export class ProductApisViewModel {
public readonly layout: ko.Observable<string>;
public readonly runtimeConfig: ko.Observable<string>;

constructor() {
this.layout = ko.observable();
this.runtimeConfig = ko.observable();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,16 @@ export class ProductApisViewModelBinder implements ViewModelBinder<ProductApisMo
viewModel = new ProductApisViewModel();
}

viewModel.layout(model.layout);

viewModel.runtimeConfig(JSON.stringify({
detailsPageUrl: model.detailsPageHyperlink
? model.detailsPageHyperlink.href
: undefined
}));

viewModel["widgetBinding"] = {
displayName: "Product: APIs",
displayName: "Product: APIs" + (model.layout === "list" ? "" : ` (${model.layout})`),
model: model,
draggable: true,
editor: "product-apis-editor",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<div class="form-inline search-input">
<div class="input-group">
<input type="search" role="searchbox" aria-label="Search" placeholder="Search APIs" spellcheck="false"
data-bind="textInput: pattern" />
<button type="button" class="search-button" aria-label="Search products">
<i class="icon-emb icon-emb-magnifier"></i>
</button>
</div>
</div>

<div class="cards">
<!-- ko if: working -->
<div class="cards-body">
<spinner class="fit"></spinner>
</div>
<!-- /ko -->

<!-- ko ifnot: working -->
<div class="cards-body animation-fade-in">

<!-- ko foreach: { data: apis, as: 'item' } -->
<a href="#" data-bind="attr: { href: $component.getReferenceUrl(item) }">
<div class="card item-tile">
<h3>
<span data-bind="text: item.displayName"></span>
<!-- ko if: item.type === 'soap' -->
<span class="badge badge-soap">SOAP</span>
<!-- /ko -->
<!-- ko if: item.apiVersion -->
- <span data-bind="text: item.apiVersion"></span>
<!-- /ko -->
</h3>
<div class="tile line-clamp">
<p class="tile-content" data-bind="markdown: { source: item.description, truncateAt: 250 }"></p>
</div>
</div>
</a>
<!-- /ko -->

<!-- ko if: apis().length === 0 -->
<p>This product doesn't have APIs.</p>
<!-- /ko -->

</div>
<!-- /ko -->

<div class="cards-footer">
<!-- ko if: hasPager -->
<ul class="pagination" role="navigation" aria-label="Pagination">
<!-- ko if: hasPrevPage -->
<li class="page-item">
<a href="#" class="page-link" role="button" aria-label="Previous page"
data-bind="click: prevPage, enable: hasPrevPage">
<i class="icon-emb icon-emb-chevron-left"></i>
</a>
</li>
<!-- /ko -->
<li class="page-item">
<span class="page-link" data-bind="text: page"></span>
</li>
<!-- ko if: hasNextPage -->
<li class="page-item">
<a href="#" class="page-link" role="button" aria-label="Next page"
data-bind="click: nextPage, enable: hasNextPage">
<i class="icon-emb icon-emb-chevron-right"></i>
</a>
</li>
<!-- /ko -->
</ul>
<!-- /ko -->
</div>
</div>
120 changes: 120 additions & 0 deletions src/components/products/product-apis/ko/runtime/product-apis-tiles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import * as ko from "knockout";
import * as Constants from "../../../../../constants";
import template from "./product-apis-tiles.html";
import { Component, RuntimeComponent, OnMounted, OnDestroyed, Param } from "@paperbits/common/ko/decorators";
import { Router } from "@paperbits/common/routing";
import { ApiService } from "../../../../../services/apiService";
import { Api } from "../../../../../models/api";
import { SearchQuery } from "../../../../../contracts/searchQuery";
import { RouteHelper } from "../../../../../routing/routeHelper";


@RuntimeComponent({
selector: "product-apis-tiles-runtime"
})
@Component({
selector: "product-apis-tiles-runtime",
template: template
})
export class ProductApisTiles {
public readonly apis: ko.ObservableArray<Api>;
public readonly working: ko.Observable<boolean>;
public readonly pattern: ko.Observable<string>;
public readonly page: ko.Observable<number>;
public readonly hasPager: ko.Computed<boolean>;
public readonly hasPrevPage: ko.Observable<boolean>;
public readonly hasNextPage: ko.Observable<boolean>;

constructor(
private readonly apiService: ApiService,
private readonly router: Router,
private readonly routeHelper: RouteHelper
) {
this.detailsPageUrl = ko.observable();
this.apis = ko.observableArray([]);
this.working = ko.observable();
this.pattern = ko.observable();
this.page = ko.observable(1);
this.hasPrevPage = ko.observable();
this.hasNextPage = ko.observable();
this.hasPager = ko.computed(() => this.hasPrevPage() || this.hasNextPage());
}

@Param()
public detailsPageUrl: ko.Observable<string>;

@OnMounted()
public async initialize(): Promise<void> {
await this.searchApis();

this.router.addRouteChangeListener(this.searchApis);

this.pattern
.extend({ rateLimit: { timeout: Constants.defaultInputDelayMs, method: "notifyWhenChangesStop" } })
.subscribe(this.searchApis);
}

/**
* Initiates searching APIs.
*/
public async searchApis(): Promise<void> {
this.page(1);
this.loadPageOfApis();
}

/**
* Loads page of APIs.
*/
public async loadPageOfApis(): Promise<void> {
const productName = this.routeHelper.getProductName();

if (!productName) {
return;
}

try {
this.working(true);

const pageNumber = this.page() - 1;

const query: SearchQuery = {
pattern: this.pattern(),
skip: pageNumber * Constants.defaultPageSize,
take: Constants.defaultPageSize
};

const pageOfApis = await this.apiService.getProductApis(`products/${productName}`, query);
this.apis(pageOfApis.value);

const nextLink = pageOfApis.nextLink;

this.hasPrevPage(pageNumber > 0);
this.hasNextPage(!!nextLink);
}
catch (error) {
throw new Error(`Unable to load APIs. Error: ${error.message}`);
}
finally {
this.working(false);
}
}

public getReferenceUrl(api: Api): string {
return this.routeHelper.getApiReferenceUrl(api.name, this.detailsPageUrl());
}

public prevPage(): void {
this.page(this.page() - 1);
this.loadPageOfApis();
}

public nextPage(): void {
this.page(this.page() + 1);
this.loadPageOfApis();
}

@OnDestroyed()
public dispose(): void {
this.router.removeRouteChangeListener(this.searchApis);
}
}
5 changes: 5 additions & 0 deletions src/components/products/product-apis/productApisContract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ import { HyperlinkContract } from "@paperbits/common/editing";
* Product API list widget configuration.
*/
export interface ProductApisContract extends Contract {
/**
* List layout. "list" or "tiles"
*/
itemStyleView?: string;

/**
* Link to a page that contains operation details.
*/
Expand Down
17 changes: 16 additions & 1 deletion src/components/products/product-apis/productApisHandlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,22 @@ export class ProductApisHandlers implements IWidgetHandler {
displayName: "Product: APIs",
iconClass: "paperbits-cheque-3",
requires: ["html"],
createModel: async () => new ProductApisModel()
createModel: async () => new ProductApisModel("list")
};

return widgetOrder;
}
}

export class ProductApisTilesHandlers implements IWidgetHandler {
public async getWidgetOrder(): Promise<IWidgetOrder> {
const widgetOrder: IWidgetOrder = {
name: "product-apis-tiles",
category: "Products",
displayName: "Product: APIs (tiles)",
iconClass: "paperbits-cheque-3",
requires: ["html"],
createModel: async () => new ProductApisModel("tiles")
};

return widgetOrder;
Expand Down
9 changes: 9 additions & 0 deletions src/components/products/product-apis/productApisModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,17 @@ import { HyperlinkModel } from "@paperbits/common/permalinks";
* Product API list widget configuration.
*/
export class ProductApisModel {
/**
* List layout. "list" or "tiles"
*/
public layout?: string;

/**
* Link to a page that contains API details.
*/
public detailsPageHyperlink: HyperlinkModel;

constructor(layout?: "list"|"tiles") {
this.layout = layout;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export class ProductApisModelBinder implements IModelBinder<ProductApisModel> {

public async contractToModel(contract: ProductApisContract): Promise<ProductApisModel> {
const model = new ProductApisModel();
model.layout = contract.itemStyleView || "list";

if (contract.detailsPageHyperlink) {
model.detailsPageHyperlink = await this.permalinkResolver.getHyperlinkFromContract(contract.detailsPageHyperlink);
Expand All @@ -28,6 +29,7 @@ export class ProductApisModelBinder implements IModelBinder<ProductApisModel> {
public modelToContract(model: ProductApisModel): ProductApisContract {
const contract: ProductApisContract = {
type: "product-apis",
itemStyleView: model.layout,
detailsPageHyperlink: model.detailsPageHyperlink
? {
target: model.detailsPageHyperlink.target,
Expand Down
4 changes: 4 additions & 0 deletions src/components/products/product-list/ko/productList.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,7 @@
<!-- ko if: layout() === 'dropdown' -->
<product-list-dropdown-runtime data-bind="attr: { params: runtimeConfig }"></product-list-dropdown-runtime>
<!-- /ko -->

<!-- ko if: layout() === 'tiles' -->
<product-list-tiles-runtime data-bind="attr: { params: runtimeConfig }"></product-list-tiles-runtime>
<!-- /ko -->
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { IInjectorModule, IInjector } from "@paperbits/common/injection";
import { ProductListHandlers, ProductListDropdownHandlers } from "../productListHandlers";
import { ProductListHandlers, ProductListDropdownHandlers, ProductListTilesHandlers } from "../productListHandlers";
import { ProductListEditor } from "./productListEditor";

export class ProductListEditorModule implements IInjectorModule {
public register(injector: IInjector): void {
injector.bind("productListEditor", ProductListEditor);
injector.bindToCollection("widgetHandlers", ProductListHandlers, "productListHandlers");
injector.bindToCollection("widgetHandlers", ProductListDropdownHandlers, "productListDropdownHandlers");
injector.bindToCollection("widgetHandlers", ProductListTilesHandlers, "productListTilesHandlers");
}
}
Loading

0 comments on commit e20fb9d

Please sign in to comment.