-
-
-
+
+
+
+
+
@@ -69,6 +74,7 @@
+
@@ -92,7 +98,7 @@
diff --git a/src/components/apis/list-of-apis/ko/runtime/api-list.ts b/src/components/apis/list-of-apis/ko/runtime/api-list.ts
index 043b4792b..6511be596 100644
--- a/src/components/apis/list-of-apis/ko/runtime/api-list.ts
+++ b/src/components/apis/list-of-apis/ko/runtime/api-list.ts
@@ -24,6 +24,7 @@ export class ApiList {
public readonly pattern: ko.Observable
;
public readonly tags: ko.Observable;
public readonly groupByTag: ko.Observable;
+ public readonly groupTagsExpanded: ko.Observable>;
public readonly pageNumber: ko.Observable;
public readonly totalPages: ko.Observable;
@@ -43,6 +44,7 @@ export class ApiList {
this.apiGroups = ko.observableArray();
this.groupByTag = ko.observable(false);
this.defaultGroupByTagToEnabled = ko.observable(false);
+ this.groupTagsExpanded = ko.observable(new Set());
}
@Param()
@@ -62,8 +64,6 @@ export class ApiList {
this.groupByTag(this.defaultGroupByTagToEnabled());
this.tags.subscribe(this.resetSearch);
- await this.resetSearch();
-
this.pattern
.extend({ rateLimit: { timeout: Constants.defaultInputDelayMs, method: "notifyWhenChangesStop" } })
.subscribe(this.resetSearch);
@@ -127,6 +127,12 @@ export class ApiList {
this.loadPageOfApis();
}
+ public groupTagCollapseToggle(tag: string): void {
+ const newSet = this.groupTagsExpanded();
+ newSet.has(tag) ? newSet.delete(tag) : newSet.add(tag);
+ this.groupTagsExpanded(newSet);
+ }
+
public async onTagsChange(tags: Tag[]): Promise {
this.tags(tags);
}
diff --git a/src/components/apis/list-of-apis/listOfApisContract.ts b/src/components/apis/list-of-apis/listOfApisContract.ts
index 9b1aab45b..df1e8f5ec 100644
--- a/src/components/apis/list-of-apis/listOfApisContract.ts
+++ b/src/components/apis/list-of-apis/listOfApisContract.ts
@@ -1,5 +1,6 @@
import { Contract } from "@paperbits/common";
import { HyperlinkContract } from "@paperbits/common/editing";
+import { LocalStyles } from "@paperbits/common/styles";
/**
@@ -30,4 +31,9 @@ export interface ListOfApisContract extends Contract {
* Link to a page that contains API details.
*/
detailsPageHyperlink?: HyperlinkContract;
- }
+
+ /**
+ * Widget local styles.
+ */
+ styles?: LocalStyles;
+}
diff --git a/src/components/apis/list-of-apis/listOfApisHandlers.ts b/src/components/apis/list-of-apis/listOfApisHandlers.ts
index ea02fa6f4..e847275ed 100644
--- a/src/components/apis/list-of-apis/listOfApisHandlers.ts
+++ b/src/components/apis/list-of-apis/listOfApisHandlers.ts
@@ -1,45 +1,144 @@
-īģŋimport { IWidgetOrder, IWidgetHandler } from "@paperbits/common/editing";
+īģŋimport { IWidgetHandler } from "@paperbits/common/editing";
import { ListOfApisModel } from "./listOfApisModel";
+import { StyleDefinition } from "@paperbits/common/styles";
+import * as DefaultStyleDefinitions from "../../defaultStyleDefinitions";
export class ListOfApisHandlers implements IWidgetHandler {
- public async getWidgetOrder(): Promise {
- const widgetOrder: IWidgetOrder = {
- name: "listOfApis",
- category: "APIs",
- displayName: "List of APIs",
- iconClass: "widget-icon widget-icon-api-management",
- requires: ["html"],
- createModel: async () => new ListOfApisModel("list")
- };
+ public async getWidgetModel(): Promise {
+ return new ListOfApisModel("list");
+ }
- return widgetOrder;
+ public getStyleDefinitions(): StyleDefinition {
+ return {
+ colors: {
+ borderColor: {
+ displayName: "Input border color",
+ defaults: {
+ value: "#505050"
+ }
+ },
+ gridBorderColor: {
+ displayName: "Grid separators color",
+ defaults: {
+ value: "#dee2e6"
+ }
+ },
+ tagButtonColor: {
+ displayName: "Tag button color",
+ defaults: {
+ value: "#555"
+ }
+ }
+ },
+ components: {
+ listOfApis: {
+ displayName: "List of APIs",
+ plugins: ["margin", "padding", "typography"],
+ components: {
+ searchInput: DefaultStyleDefinitions.getSearchInputStyleDefinition(),
+ tableRow: DefaultStyleDefinitions.getTableRowStyleDefinition(),
+ tableHead: DefaultStyleDefinitions.getTableHeadStyleDefinition(),
+ tagInput: DefaultStyleDefinitions.getTagInputStyleDefinition(),
+ toggleButtonLabel: DefaultStyleDefinitions.getToggleButtonLabelStyleDefinition(),
+ tagCard: DefaultStyleDefinitions.getTagCardStyleDefinition(),
+ tagGroupCollapsible: DefaultStyleDefinitions.getIconButtonStyleDefinition(),
+ }
+ }
+ }
+ };
}
}
+
export class ListOfApisTilesHandlers implements IWidgetHandler {
- public async getWidgetOrder(): Promise {
- const widgetOrder: IWidgetOrder = {
- name: "listOfApisTiles",
- category: "APIs",
- displayName: "List of APIs (tiles)",
- iconClass: "widget-icon widget-icon-api-management",
- requires: ["html"],
- createModel: async () => new ListOfApisModel("tiles")
- };
+ public async getWidgetModel(): Promise {
+ return new ListOfApisModel("tiles");
+ }
- return widgetOrder;
+ public getStyleDefinitions(): StyleDefinition {
+ return {
+ colors: {
+ borderColor: {
+ displayName: "Search input border color",
+ defaults: {
+ value: "#505050"
+ }
+ },
+ tagButtonColor: {
+ displayName: "Tag button color",
+ defaults: {
+ value: "#555"
+ }
+ }
+ },
+ components: {
+ listOfApisTiles: {
+ displayName: "List of APIs Tiles",
+ plugins: ["margin", "padding", "background", "typography"],
+ components: {
+ searchInput: DefaultStyleDefinitions.getSearchInputStyleDefinition(),
+ apiCard: DefaultStyleDefinitions.getCardStyleDefinition(),
+ cardTitle: DefaultStyleDefinitions.getCardTitleStyleDefinition(),
+ cardText: DefaultStyleDefinitions.getCardTextStyleDefinition(),
+ widgetText: DefaultStyleDefinitions.getWidgetTextStyleDefinition(),
+ tagInput: DefaultStyleDefinitions.getTagInputStyleDefinition(),
+ toggleButtonLabel: DefaultStyleDefinitions.getToggleButtonLabelStyleDefinition(),
+ tagCard: DefaultStyleDefinitions.getTagCardStyleDefinition(),
+ tagGroupCollapsible: DefaultStyleDefinitions.getIconButtonStyleDefinition(),
+ }
+ }
+ }
+ };
}
}
+
export class ListOfApisDropdownHandlers implements IWidgetHandler {
- public async getWidgetOrder(): Promise {
- const widgetOrder: IWidgetOrder = {
- name: "listOfApisDropdown",
- category: "APIs",
- displayName: "List of APIs (dropdown)",
- iconClass: "widget-icon widget-icon-api-management",
- requires: ["html"],
- createModel: async () => new ListOfApisModel("dropdown")
- };
+ public async getWidgetModel(): Promise {
+ return new ListOfApisModel("dropdown");
+ }
- return widgetOrder;
+ public getStyleDefinitions(): StyleDefinition {
+ return {
+ colors: {
+ displayTextColor: {
+ displayName: "Text color",
+ defaults: {
+ value: "#252525"
+ }
+ },
+ badgeColor: {
+ displayName: "API type badge color",
+ defaults: {
+ value: "#636363"
+ }
+ },
+ borderColor: {
+ displayName: "Search input border color",
+ defaults: {
+ value: "#505050"
+ }
+ },
+ tagButtonColor: {
+ displayName: "Tag button color",
+ defaults: {
+ value: "#555"
+ }
+ }
+ },
+ components: {
+ listOfApisDropdown: {
+ displayName: "List of APIs Dropdown",
+ plugins: ["margin", "padding", "typography", "background"],
+ components: {
+ dropdownInput: DefaultStyleDefinitions.getDropdownInputStyleDefinition(),
+ dropdownInputButton: DefaultStyleDefinitions.getDropdownInputButtonStyleDefinition(),
+ dropdownContainer: DefaultStyleDefinitions.getDropdownContainerStyleDefinition(),
+ searchInput: DefaultStyleDefinitions.getSearchInputStyleDefinition(),
+ tagCard: DefaultStyleDefinitions.getTagCardStyleDefinition(),
+ apiTypeBadge: DefaultStyleDefinitions.getApiTypeBadgeStyleDefinition(),
+ widgetText: DefaultStyleDefinitions.getWidgetTextStyleDefinition()
+ }
+ }
+ }
+ };
}
}
\ No newline at end of file
diff --git a/src/components/apis/list-of-apis/listOfApisModel.ts b/src/components/apis/list-of-apis/listOfApisModel.ts
index 8cfd9c6b2..c869b9f9c 100644
--- a/src/components/apis/list-of-apis/listOfApisModel.ts
+++ b/src/components/apis/list-of-apis/listOfApisModel.ts
@@ -1,4 +1,5 @@
import { HyperlinkModel } from "@paperbits/common/permalinks";
+import { LocalStyles } from "@paperbits/common/styles";
export class ListOfApisModel {
/**
@@ -26,6 +27,11 @@ export class ListOfApisModel {
*/
public detailsPageHyperlink: HyperlinkModel;
+ /**
+ * Widget local styles.
+ */
+ public styles: LocalStyles = {};
+
constructor(layout?: string) {
this.layout = layout;
}
diff --git a/src/components/apis/list-of-apis/listOfApisModelBinder.ts b/src/components/apis/list-of-apis/listOfApisModelBinder.ts
index fa62b0550..381215c2f 100644
--- a/src/components/apis/list-of-apis/listOfApisModelBinder.ts
+++ b/src/components/apis/list-of-apis/listOfApisModelBinder.ts
@@ -7,18 +7,15 @@ import { IPermalinkResolver } from "@paperbits/common/permalinks";
export class ListOfApisModelBinder implements IModelBinder {
constructor(private readonly permalinkResolver: IPermalinkResolver) { }
-
- public canHandleModel(model: Object): boolean {
- return model instanceof ListOfApisModel;
- }
public async contractToModel(contract: ListOfApisContract): Promise {
const model = new ListOfApisModel();
-
+
model.layout = contract.itemStyleView;
model.allowSelection = contract.allowSelection;
model.showApiType = contract.showApiType === undefined ? true : contract.showApiType;
model.defaultGroupByTagToEnabled = contract.defaultGroupByTagToEnabled === true;
+ model.styles = contract.styles ?? {};
if (contract.detailsPageHyperlink) {
model.detailsPageHyperlink = await this.permalinkResolver.getHyperlinkFromContract(contract.detailsPageHyperlink);
@@ -27,10 +24,6 @@ export class ListOfApisModelBinder implements IModelBinder {
return model;
}
- public canHandleContract(contract: Contract): boolean {
- return contract.type === "listOfApis";
- }
-
public modelToContract(model: ListOfApisModel): Contract {
const contract: ListOfApisContract = {
type: "listOfApis",
@@ -43,7 +36,8 @@ export class ListOfApisModelBinder implements IModelBinder {
target: model.detailsPageHyperlink.target,
targetKey: model.detailsPageHyperlink.targetKey
}
- : null
+ : null,
+ styles: model.styles
};
return contract;
diff --git a/src/components/app/app.html b/src/components/app/app.html
index a73bb9c22..88edbd90a 100644
--- a/src/components/app/app.html
+++ b/src/components/app/app.html
@@ -1 +1,4 @@
+
+
+
\ No newline at end of file
diff --git a/src/components/app/app.ts b/src/components/app/app.ts
index 98a34ca09..51b29cbad 100644
--- a/src/components/app/app.ts
+++ b/src/components/app/app.ts
@@ -6,6 +6,7 @@ import { Component, OnMounted } from "@paperbits/common/ko/decorators";
import { ISettingsProvider } from "@paperbits/common/configuration";
import { ISiteService } from "@paperbits/common/sites";
import { IAuthenticator } from "../../authentication";
+import { DeveloperPortalType, SettingNames, WarningBackendUrlMissing } from "../../constants";
const startupError = `Unable to start the portal`;
@@ -31,6 +32,17 @@ export class App {
return;
}
+ if (!settings["backendUrl"]) {
+ const developerPortalType = settings[SettingNames.developerPortalType] || DeveloperPortalType.selfHosted;
+
+ if (developerPortalType === DeveloperPortalType.selfHosted) {
+ const toast = this.viewManager.notifyInfo("Settings", WarningBackendUrlMissing, [{
+ title: "Got it",
+ action: async () => this.viewManager.removeToast(toast)
+ }]);
+ }
+ }
+
try {
const token = await this.authenticator.getAccessTokenAsString();
diff --git a/src/components/content/resetDetails.html b/src/components/content/resetDetails.html
index b507df8ed..133e332b0 100644
--- a/src/components/content/resetDetails.html
+++ b/src/components/content/resetDetails.html
@@ -12,7 +12,7 @@
-
+
Reset content
diff --git a/src/components/custom-html/constants.ts b/src/components/custom-html/constants.ts
new file mode 100644
index 000000000..856cee730
--- /dev/null
+++ b/src/components/custom-html/constants.ts
@@ -0,0 +1,7 @@
+export const widgetName = "custom-html-code";
+export const widgetDisplayName = "Custom HTML code";
+export const widgetCategory = "Custom widgets";
+export const widgetSelector = "custom-html";
+export const widgetRuntimeSelector = "custom-html-runtime";
+export const widgetEditorSelector = "custom-html-editor";
+export const widgetIconClass = "widget-icon widget-icon-api-management";
\ No newline at end of file
diff --git a/src/components/custom-html/customHtml.design.module.ts b/src/components/custom-html/customHtml.design.module.ts
index 57e78bbd4..53755c19f 100644
--- a/src/components/custom-html/customHtml.design.module.ts
+++ b/src/components/custom-html/customHtml.design.module.ts
@@ -2,14 +2,34 @@ import { IInjectorModule, IInjector } from "@paperbits/common/injection";
import { CustomHtmlEditorViewModel } from "./ko/customHtmlEditorViewModel";
import { HTMLInjectionHandlers } from "./customHtmlHandlers";
import { CustomHtmlViewModel, CustomHtmlViewModelBinder } from "./ko";
-import { HTMLInjectionModelBinder } from ".";
+import { CustomHtmlModelBinder, CustomHtmlModel, widgetCategory, widgetDisplayName, widgetName } from ".";
+import { IWidgetService } from "@paperbits/common/widgets";
+import { KnockoutComponentBinder } from "@paperbits/core/ko";
export class CustomHtmlDesignModule implements IInjectorModule {
public register(injector: IInjector): void {
- injector.bind("customHtmlViewModel", CustomHtmlViewModel);
- injector.bind("customHtmlViewEditorModel", CustomHtmlEditorViewModel);
- injector.bindToCollection("modelBinders", HTMLInjectionModelBinder);
- injector.bindToCollection("viewModelBinders", CustomHtmlViewModelBinder);
- injector.bindToCollection("widgetHandlers", HTMLInjectionHandlers);
+ injector.bind("customHtmlEditor", CustomHtmlEditorViewModel);
+ injector.bindSingleton("customHtmlModelBinder", CustomHtmlModelBinder);
+ injector.bindSingleton("customHtmlViewModelBinder", CustomHtmlViewModelBinder)
+ injector.bindSingleton("customHtmlHandlers", HTMLInjectionHandlers);
+
+ const widgetService = injector.resolve("widgetService");
+
+ widgetService.registerWidget(widgetName, {
+ modelDefinition: CustomHtmlModel,
+ componentBinder: KnockoutComponentBinder,
+ componentDefinition: CustomHtmlViewModel,
+ modelBinder: CustomHtmlModelBinder,
+ viewModelBinder: CustomHtmlViewModelBinder
+ });
+
+ widgetService.registerWidgetEditor(widgetName, {
+ displayName: widgetDisplayName,
+ category: widgetCategory,
+ iconClass: "widget-icon widget-icon-api-management",
+ componentBinder: KnockoutComponentBinder,
+ componentDefinition: CustomHtmlEditorViewModel,
+ handlerComponent: HTMLInjectionHandlers
+ });
}
}
\ No newline at end of file
diff --git a/src/components/custom-html/customHtml.publish.module.ts b/src/components/custom-html/customHtml.publish.module.ts
index 5212e2f5b..fa3964aef 100644
--- a/src/components/custom-html/customHtml.publish.module.ts
+++ b/src/components/custom-html/customHtml.publish.module.ts
@@ -1,13 +1,26 @@
import { IInjectorModule, IInjector } from "@paperbits/common/injection";
import { CustomHtmlViewModel } from "./ko/customHtmlViewModel";
-import { HTMLInjectionModelBinder } from "./customHtmlModelBinder";
+import { CustomHtmlModelBinder } from "./customHtmlModelBinder";
import { CustomHtmlViewModelBinder } from "./ko/customHtmlViewModelBinder";
+import { widgetName } from "../custom-widget";
+import { IWidgetService } from "@paperbits/common/widgets";
+import { CustomHtmlModel } from "./customHtmlModel";
+import { KnockoutComponentBinder } from "@paperbits/core/ko";
export class CustomHtmlPublishModule implements IInjectorModule {
- public register(injector: IInjector): void {
- injector.bind("customHtmlViewModel", CustomHtmlViewModel);
- injector.bindToCollection("modelBinders", HTMLInjectionModelBinder);
- injector.bindToCollection("viewModelBinders", CustomHtmlViewModelBinder);
+ public register(injector: IInjector): void {
+ injector.bindSingleton("customHtmlModelBinder", CustomHtmlModelBinder);
+ injector.bindSingleton("customHtmlViewModelBinder", CustomHtmlViewModelBinder)
+
+ const widgetService = injector.resolve("widgetService");
+
+ widgetService.registerWidget(widgetName, {
+ modelDefinition: CustomHtmlModel,
+ componentBinder: KnockoutComponentBinder,
+ componentDefinition: CustomHtmlViewModel,
+ modelBinder: CustomHtmlModelBinder,
+ viewModelBinder: CustomHtmlViewModelBinder
+ });
}
}
\ No newline at end of file
diff --git a/src/components/custom-html/customHtmlContract.ts b/src/components/custom-html/customHtmlContract.ts
index d8a7600d7..1ff4c810c 100644
--- a/src/components/custom-html/customHtmlContract.ts
+++ b/src/components/custom-html/customHtmlContract.ts
@@ -1,7 +1,7 @@
import { LocalStyles } from "@paperbits/common/styles";
import { Contract } from "@paperbits/common";
-export interface HTMLInjectionContract extends Contract {
+export interface CustomHtmlContract extends Contract {
htmlCode: string;
inheritStyling: boolean;
styles: LocalStyles;
diff --git a/src/components/custom-html/customHtmlHandlers.ts b/src/components/custom-html/customHtmlHandlers.ts
index d8c5f6aa5..509bb4203 100644
--- a/src/components/custom-html/customHtmlHandlers.ts
+++ b/src/components/custom-html/customHtmlHandlers.ts
@@ -1,27 +1,14 @@
-īģŋimport { widgetName, widgetDisplayName, widgetCategory, widgetIconClass } from "./constants";
-import { IWidgetOrder, IWidgetHandler } from "@paperbits/common/editing";
-import { HTMLInjectionModel } from "./customHtmlModel";
-import { htmlCodeInitial, htmlCodeSizeStylesInitial } from "./ko/constants";
+īģŋimport { IWidgetHandler } from "@paperbits/common/editing";
import { StyleHelper } from "@paperbits/styles";
+import { CustomHtmlModel } from "./customHtmlModel";
+import { htmlCodeInitial, htmlCodeSizeStylesInitial } from "./ko/constants";
export class HTMLInjectionHandlers implements IWidgetHandler {
- public async getWidgetOrder(): Promise {
- const widgetOrder: IWidgetOrder = {
- name: widgetName,
- category: widgetCategory,
- requires: [],
- displayName: widgetDisplayName,
- iconClass: widgetIconClass,
-
- createModel: async () => {
- const model = new HTMLInjectionModel();
- model.htmlCode = htmlCodeInitial;
- model.inheritStyling = true;
- StyleHelper.setPluginConfigForLocalStyles(model.styles, "size", htmlCodeSizeStylesInitial);
- return model;
- }
- };
-
- return widgetOrder;
+ public async getWidgetModel(): Promise {
+ const model = new CustomHtmlModel();
+ model.htmlCode = htmlCodeInitial;
+ model.inheritStyling = true;
+ StyleHelper.setPluginConfigForLocalStyles(model.styles, "size", htmlCodeSizeStylesInitial);
+ return model;
}
}
\ No newline at end of file
diff --git a/src/components/custom-html/customHtmlModel.ts b/src/components/custom-html/customHtmlModel.ts
index e72263004..d5a0ba17f 100644
--- a/src/components/custom-html/customHtmlModel.ts
+++ b/src/components/custom-html/customHtmlModel.ts
@@ -1,6 +1,6 @@
import { LocalStyles } from "@paperbits/common/styles";
-export class HTMLInjectionModel {
+export class CustomHtmlModel {
public htmlCode: string;
public inheritStyling: boolean;
diff --git a/src/components/custom-html/customHtmlModelBinder.ts b/src/components/custom-html/customHtmlModelBinder.ts
index b38c830a2..6db791c61 100644
--- a/src/components/custom-html/customHtmlModelBinder.ts
+++ b/src/components/custom-html/customHtmlModelBinder.ts
@@ -1,35 +1,26 @@
import { widgetName } from "./constants";
import { IModelBinder } from "@paperbits/common/editing";
-import { HTMLInjectionModel } from "./customHtmlModel";
+import { CustomHtmlModel } from "./customHtmlModel";
import { Contract } from "@paperbits/common";
-import { HTMLInjectionContract } from "./customHtmlContract";
+import { CustomHtmlContract } from "./customHtmlContract";
import { htmlCodeInitial } from "./ko/constants";
-export class HTMLInjectionModelBinder implements IModelBinder {
- public canHandleContract(contract: Contract): boolean {
- return contract.type === widgetName;
- }
-
- public canHandleModel(model: any): boolean {
- return model instanceof HTMLInjectionModel;
- }
-
- public async contractToModel(contract: HTMLInjectionContract): Promise {
- const model = new HTMLInjectionModel();
+export class CustomHtmlModelBinder implements IModelBinder {
+ public async contractToModel(contract: CustomHtmlContract): Promise {
+ const model = new CustomHtmlModel();
model.htmlCode = contract.htmlCode ?? htmlCodeInitial;
model.inheritStyling = contract.inheritStyling ?? true;
model.styles = contract.styles || {};
return model;
}
- public modelToContract(model: HTMLInjectionModel): Contract {
- const contract: HTMLInjectionContract = {
+ public modelToContract(model: CustomHtmlModel): Contract {
+ const contract: CustomHtmlContract = {
type: widgetName,
htmlCode: model.htmlCode,
inheritStyling: model.inheritStyling,
styles: model.styles
};
-
return contract;
}
}
diff --git a/src/components/custom-html/ko/constants.ts b/src/components/custom-html/ko/constants.ts
new file mode 100644
index 000000000..b9ae61bf2
--- /dev/null
+++ b/src/components/custom-html/ko/constants.ts
@@ -0,0 +1,27 @@
+import { SizeStylePluginConfig } from "@paperbits/styles/plugins";
+
+export const htmlCodeInitial =
+`
+
+
+
+
+
+ Custom HTML code example
+ Replace this content with custom HTML code. It will be rendered in an iframe in the developer portal.
+
+ Sample button
+
+
+
+`;
+
+export const htmlCodeSizeStylesInitial: SizeStylePluginConfig = {
+ height: "300px",
+ width: "100%",
+};
\ No newline at end of file
diff --git a/src/components/custom-html/ko/customHtmlEditorView.html b/src/components/custom-html/ko/customHtmlEditorView.html
index 518890632..8f499bd78 100644
--- a/src/components/custom-html/ko/customHtmlEditorView.html
+++ b/src/components/custom-html/ko/customHtmlEditorView.html
@@ -1,4 +1,4 @@
-
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/src/components/operations/operation-details/ko/operationDetailsEditor.module.ts b/src/components/operations/operation-details/ko/operationDetailsEditor.module.ts
new file mode 100644
index 000000000..03613a90d
--- /dev/null
+++ b/src/components/operations/operation-details/ko/operationDetailsEditor.module.ts
@@ -0,0 +1,38 @@
+import { IInjector, IInjectorModule } from "@paperbits/common/injection";
+import { IWidgetService } from "@paperbits/common/widgets";
+import { KnockoutComponentBinder } from "@paperbits/core/ko";
+import { OperationDetailsEditor } from "./operationDetailsEditor";
+import { OperationDetailsViewModel } from "./operationDetailsViewModel";
+import { OperationDetailsViewModelBinder } from "./operationDetailsViewModelBinder";
+import { OperationDetailsHandlers } from "../operationDetailsHandlers";
+import { OperationDetailsModel } from "../operationDetailsModel";
+import { OperationDetailsModelBinder } from "../operationDetailsModelBinder";
+
+
+export class OperationDetailsDesignModule implements IInjectorModule {
+ public register(injector: IInjector): void {
+ injector.bind("operationDetailsEditor", OperationDetailsEditor);
+ injector.bindSingleton("operationDetailsModelBinder", OperationDetailsModelBinder);
+ injector.bindSingleton("operationDetailsViewModelBinder", OperationDetailsViewModelBinder)
+ injector.bindSingleton("operationDetailsHandlers", OperationDetailsHandlers);
+
+ const widgetService = injector.resolve
("widgetService");
+
+ widgetService.registerWidget("operationDetails", {
+ modelDefinition: OperationDetailsModel,
+ componentBinder: KnockoutComponentBinder,
+ componentDefinition: OperationDetailsViewModel,
+ modelBinder: OperationDetailsModelBinder,
+ viewModelBinder: OperationDetailsViewModelBinder
+ });
+
+ widgetService.registerWidgetEditor("operationDetails", {
+ displayName: "Operation: Details",
+ category: "APIs",
+ iconClass: "widget-icon widget-icon-api-management",
+ componentBinder: KnockoutComponentBinder,
+ componentDefinition: OperationDetailsEditor,
+ handlerComponent: OperationDetailsHandlers
+ });
+ }
+}
\ No newline at end of file
diff --git a/src/components/operations/operation-details/ko/operationDetailsEditor.ts b/src/components/operations/operation-details/ko/operationDetailsEditor.ts
index b0c7ece6b..cb4a4616d 100644
--- a/src/components/operations/operation-details/ko/operationDetailsEditor.ts
+++ b/src/components/operations/operation-details/ko/operationDetailsEditor.ts
@@ -12,12 +12,16 @@ export class OperationDetailsEditor {
public readonly enableScrollTo: ko.Observable;
public readonly defaultSchemaView: ko.Observable;
public readonly useCorsProxy: ko.Observable;
+ public readonly includeAllHostnames: ko.Observable;
+ public readonly showExamples: ko.Observable;
constructor() {
this.enableConsole = ko.observable();
this.enableScrollTo = ko.observable();
this.defaultSchemaView = ko.observable();
this.useCorsProxy = ko.observable();
+ this.includeAllHostnames = ko.observable();
+ this.showExamples = ko.observable();
}
@Param()
@@ -30,20 +34,26 @@ export class OperationDetailsEditor {
public async initialize(): Promise {
this.enableConsole(this.model.enableConsole);
this.useCorsProxy(this.model.useCorsProxy);
+ this.includeAllHostnames(this.model.includeAllHostnames);
this.enableScrollTo(this.model.enableScrollTo);
this.defaultSchemaView(this.model.defaultSchemaView || "table");
-
+ this.showExamples(this.model.showExamples);
+
this.enableConsole.subscribe(this.applyChanges);
this.useCorsProxy.subscribe(this.applyChanges);
+ this.includeAllHostnames.subscribe(this.applyChanges);
this.enableScrollTo.subscribe(this.applyChanges);
this.defaultSchemaView.subscribe(this.applyChanges);
+ this.showExamples.subscribe(this.applyChanges);
}
private applyChanges(): void {
this.model.enableConsole = this.enableConsole();
this.model.useCorsProxy = this.useCorsProxy();
+ this.model.includeAllHostnames = this.includeAllHostnames();
this.model.enableScrollTo = this.enableScrollTo();
this.model.defaultSchemaView = this.defaultSchemaView();
+ this.model.showExamples = this.showExamples();
this.onChange(this.model);
}
}
\ No newline at end of file
diff --git a/src/components/operations/operation-details/ko/operationDetailsViewModel.ts b/src/components/operations/operation-details/ko/operationDetailsViewModel.ts
index 7bfdaa1bf..7c430e78e 100644
--- a/src/components/operations/operation-details/ko/operationDetailsViewModel.ts
+++ b/src/components/operations/operation-details/ko/operationDetailsViewModel.ts
@@ -1,15 +1,18 @@
import * as ko from "knockout";
import template from "./operationDetails.html";
import { Component } from "@paperbits/common/ko/decorators";
+import { StyleModel } from "@paperbits/common/styles";
@Component({
selector: "operationDetails",
template: template
})
export class OperationDetailsViewModel {
- public readonly config?: ko.Observable;
+ public readonly config?: ko.Observable;
+ public readonly styles: ko.Observable;
constructor() {
this.config = ko.observable();
+ this.styles = ko.observable();
}
}
\ No newline at end of file
diff --git a/src/components/operations/operation-details/ko/operationDetailsViewModelBinder.ts b/src/components/operations/operation-details/ko/operationDetailsViewModelBinder.ts
index bae9b314e..f9dd355ec 100644
--- a/src/components/operations/operation-details/ko/operationDetailsViewModelBinder.ts
+++ b/src/components/operations/operation-details/ko/operationDetailsViewModelBinder.ts
@@ -1,44 +1,34 @@
-import { ViewModelBinder } from "@paperbits/common/widgets";
-import { EventManager, Events } from "@paperbits/common/events";
+import { ViewModelBinder, WidgetState } from "@paperbits/common/widgets";
import { OperationDetailsViewModel } from "./operationDetailsViewModel";
import { OperationDetailsModel } from "../operationDetailsModel";
-import { Bag } from "@paperbits/common";
-import { ComponentFlow } from "@paperbits/common/editing";
-
+import { StyleCompiler } from "@paperbits/common/styles";
export class OperationDetailsViewModelBinder implements ViewModelBinder {
- constructor(private readonly eventManager: EventManager) { }
+ constructor(private readonly styleCompiler: StyleCompiler) { }
+
+ public stateToInstance(state: WidgetState, componentInstance: OperationDetailsViewModel): void {
+ componentInstance.styles(state.styles);
+
+ componentInstance.config(JSON.stringify({
+ enableConsole: state.enableConsole,
+ enableScrollTo: state.enableScrollTo,
+ defaultSchemaView: state.defaultSchemaView,
+ useCorsProxy: state.useCorsProxy,
+ includeAllHostnames: state.includeAllHostnames,
+ showExamples: state.showExamples
+ }));
+ }
- public async modelToViewModel(model: OperationDetailsModel, viewModel?: OperationDetailsViewModel, bindingContext?: Bag): Promise {
- if (!viewModel) {
- viewModel = new OperationDetailsViewModel();
+ public async modelToState(model: OperationDetailsModel, state: WidgetState): Promise {
+ state.enableConsole = model.enableConsole;
+ state.enableScrollTo = model.enableScrollTo;
+ state.defaultSchemaView = model.defaultSchemaView;
+ state.useCorsProxy = model.useCorsProxy;
+ state.includeAllHostnames = model.includeAllHostnames;
+ state.showExamples = model.showExamples;
- viewModel["widgetBinding"] = {
- displayName: "Operation: Details",
- model: model,
- draggable: true,
- flow: ComponentFlow.Block,
- editor: "operation-details-editor",
- applyChanges: async (updatedModel: OperationDetailsModel) => {
- await this.modelToViewModel(updatedModel, viewModel, bindingContext);
- this.eventManager.dispatchEvent(Events.ContentUpdate);
- }
- };
+ if (model.styles) {
+ state.styles = await this.styleCompiler.getStyleModelAsync(model.styles);
}
-
- const runtimeConfig = {
- enableConsole: model.enableConsole,
- enableScrollTo: model.enableScrollTo,
- defaultSchemaView: model.defaultSchemaView,
- useCorsProxy: model.useCorsProxy
- };
-
- viewModel.config(JSON.stringify(runtimeConfig));
-
- return viewModel;
- }
-
- public canHandleModel(model: OperationDetailsModel): boolean {
- return model instanceof OperationDetailsModel;
}
}
\ No newline at end of file
diff --git a/src/components/operations/operation-details/ko/runtime/authorization.html b/src/components/operations/operation-details/ko/runtime/authorization.html
index 1b8f6be1a..ad6a0acf0 100644
--- a/src/components/operations/operation-details/ko/runtime/authorization.html
+++ b/src/components/operations/operation-details/ko/runtime/authorization.html
@@ -1,94 +1,97 @@
-Authorization
-
-
-
-
-
-
-
-
-
+
+
+
+ Authorization
+
+
+
-
-
-
-
-
-
-
-
-
-
-
- Username
-
-
-
-
- Password
-
-
-
-
-
+
-
-
-
-
+
+
-
-
-
-
- Subscription key
-
-
-
-
-
-
+
+
\ No newline at end of file
diff --git a/src/components/operations/operation-details/ko/runtime/authorization.ts b/src/components/operations/operation-details/ko/runtime/authorization.ts
index d006faece..53654b143 100644
--- a/src/components/operations/operation-details/ko/runtime/authorization.ts
+++ b/src/components/operations/operation-details/ko/runtime/authorization.ts
@@ -10,7 +10,6 @@ import { ConsoleHeader } from "../../../../../models/console/consoleHeader";
import { KnownHttpHeaders } from "../../../../../models/knownHttpHeaders";
import { ConsoleOperation } from "../../../../../models/console/consoleOperation";
import { templates } from "./templates/templates";
-import { TemplatingService } from "../../../../../services/templatingService";
import { GrantTypes, TypeOfApi, oauthSessionKey } from "./../../../../../constants";
import { OAuthService } from "../../../../../services/oauthService";
import { Product } from "../../../../../models/product";
@@ -36,8 +35,8 @@ export class Authorization {
public readonly authorizationError: ko.Observable
;
public readonly products: ko.Observable;
public readonly selectedSubscriptionKey: ko.Observable;
- public readonly collapsedAuth: ko.Observable;
-
+ public readonly subscriptionKeyRevealed: ko.Observable;
+ private deleteAuthorizationHeader: boolean = false;
constructor(
private readonly sessionManager: SessionManager,
@@ -46,15 +45,13 @@ export class Authorization {
private readonly apiService: ApiService,
private readonly productService: ProductService,
) {
- this.collapsedAuth = ko.observable(false);
this.authorizationServer = ko.observable();
this.selectedGrantType = ko.observable();
this.api = ko.observable();
- this.headers = ko.observableArray();;
+ this.headers = ko.observableArray();
+ this.queryParameters = ko.observableArray();
this.consoleOperation = ko.observable();
this.templates = templates;
- this.codeSample = ko.observable();
- this.selectedLanguage = ko.observable();
this.authenticated = ko.observable(false);
this.subscriptionKeyRequired = ko.observable();
this.username = ko.observable();
@@ -62,6 +59,7 @@ export class Authorization {
this.authorizationError = ko.observable();
this.products = ko.observable();
this.selectedSubscriptionKey = ko.observable();
+ this.subscriptionKeyRevealed = ko.observable(false);
}
@Param()
@@ -74,13 +72,13 @@ export class Authorization {
public consoleOperation: ko.Observable;
@Param()
- public headers: ko.ObservableArray;
+ public headers: ko.ObservableArray;
@Param()
- public codeSample: ko.Observable;
+ public queryParameters: ko.ObservableArray;
@Param()
- public selectedLanguage: ko.Observable;
+ public updateRequestSummary: () => Promise;
@OnMounted()
@@ -93,6 +91,18 @@ export class Authorization {
if (this.api().subscriptionRequired) {
await this.loadSubscriptionKeys();
}
+
+ if (this.subscriptionKeyRequired()) {
+ if (this.api().type === TypeOfApi.webSocket || this.isGraphQL()) {
+ this.setSubscriptionKeyParameter();
+ } else {
+ this.setSubscriptionKeyHeader();
+ }
+ }
+ }
+
+ public toggleSubscriptionKey(): void {
+ this.subscriptionKeyRevealed(!this.subscriptionKeyRevealed());
}
private isGraphQL(): boolean {
@@ -140,25 +150,32 @@ export class Authorization {
}
private setAuthorizationHeader(accessToken: string): void {
- this.removeAuthorizationHeader();
+ const authorizationHeader = this.getAuthorizationHeader();
+
+ if (authorizationHeader) {
+ authorizationHeader.value(accessToken);
+ this.deleteAuthorizationHeader = false;
+ return;
+ }
+ this.deleteAuthorizationHeader = true;
const keyHeader = new ConsoleHeader();
keyHeader.name(KnownHttpHeaders.Authorization);
keyHeader.description = "Subscription key.";
- keyHeader.secret = true;
+ keyHeader.secret(true);
keyHeader.inputTypeValue("password");
keyHeader.type = "string";
- keyHeader.required = true;
+ keyHeader.required = false;
keyHeader.value(accessToken);
- if(!this.isGraphQL()) {
+ if (!this.isGraphQL()) {
this.consoleOperation().request.headers.push(keyHeader);
this.updateRequestSummary();
}
else {
this.headers.push(keyHeader);
}
-
+
this.authenticated(true);
}
@@ -177,25 +194,25 @@ export class Authorization {
return this.findHeader(subscriptionKeyHeaderName);
}
- private setSubscriptionKeyHeader(subscriptionKey: string): void {
- this.removeSubscriptionKeyHeader();
-
- if (!subscriptionKey) {
- return;
- }
+ private getAuthorizationHeader(): ConsoleHeader {
+ return this.findHeader(KnownHttpHeaders.Authorization);
+ }
+ private setSubscriptionKeyHeader(subscriptionKey?: string): void {
+ this.removeSubscriptionKeyHeader();
const subscriptionKeyHeaderName = this.getSubscriptionKeyHeaderName();
const keyHeader = new ConsoleHeader();
keyHeader.name(subscriptionKeyHeaderName);
keyHeader.description = "Subscription key.";
- keyHeader.secret = true;
+ keyHeader.secret(true);
keyHeader.inputTypeValue("password");
keyHeader.type = "string";
keyHeader.required = true;
+ keyHeader.value.extend({ required: { message: `Value is required.` } });
keyHeader.value(subscriptionKey);
- if(!this.isGraphQL()) {
+ if (!this.isGraphQL()) {
this.consoleOperation().request.headers.push(keyHeader);
this.updateRequestSummary();
}
@@ -204,15 +221,21 @@ export class Authorization {
}
}
-
private async clearStoredCredentials(): Promise {
await this.sessionManager.removeItem(oauthSessionKey);
- this.removeAuthorizationHeader();
}
private removeAuthorizationHeader(): void {
- const authorizationHeader = this.findHeader(KnownHttpHeaders.Authorization);
- this.removeHeader(authorizationHeader);
+ const authorizationHeader = this.getAuthorizationHeader();
+
+ if (authorizationHeader) {
+ if (!this.deleteAuthorizationHeader) {
+ authorizationHeader.value(null);
+ } else {
+ this.removeHeader(authorizationHeader);
+ }
+ }
+
this.authenticated(false);
}
@@ -220,6 +243,7 @@ export class Authorization {
await this.clearStoredCredentials();
if (!grantType || grantType === GrantTypes.password) {
+ this.removeAuthorizationHeader();
return;
}
@@ -230,7 +254,7 @@ export class Authorization {
* Initiates specified authentication flow.
* @param grantType OAuth grant type, e.g. "implicit" or "authorization_code".
*/
- public async authenticateOAuth(grantType: string): Promise {
+ public async authenticateOAuth(grantType: string): Promise {
const api = this.api();
const authorizationServer = this.authorizationServer();
const scopeOverride = api.authenticationSettings?.oAuth2?.scope;
@@ -240,22 +264,21 @@ export class Authorization {
authorizationServer.scopes = [scopeOverride];
}
- const accessToken = await this.oauthService.authenticate(grantType, authorizationServer);
+ const accessToken = await this.oauthService.authenticate(grantType, authorizationServer, api.name);
+
+ if (!accessToken) {
+ return;
+ }
+
await this.setStoredCredentials(serverName, scopeOverride, grantType, accessToken);
this.setAuthorizationHeader(accessToken);
}
- public async updateRequestSummary(): Promise {
- const template = templates[this.selectedLanguage()];
- const codeSample = await TemplatingService.render(template, ko.toJS(this.consoleOperation));
- this.codeSample(codeSample);
- }
-
private findHeader(name: string): ConsoleHeader {
const searchName = name.toLocaleLowerCase();
- const headers = (this.isGraphQL()) ? this.headers() : this.consoleOperation().request.headers()
+ const headers = (this.isGraphQL()) ? this.headers() : this.consoleOperation().request.headers();
return headers.find(x => x.name()?.toLocaleLowerCase() === searchName);
}
@@ -276,7 +299,7 @@ export class Authorization {
}
public removeHeader(header: ConsoleHeader): void {
- if(!this.isGraphQL()) {
+ if (!this.isGraphQL()) {
this.consoleOperation().request.headers.remove(header);
this.updateRequestSummary();
}
@@ -284,7 +307,7 @@ export class Authorization {
this.headers.remove(header);
}
}
-
+
private async setStoredCredentials(serverName: string, scopeOverride: string, grantType: string, accessToken: string): Promise {
const oauthSession = await this.sessionManager.getItem(oauthSessionKey) || {};
const recordKey = this.getSessionRecordKey(serverName, scopeOverride);
@@ -324,7 +347,7 @@ export class Authorization {
this.authorizationError("Oops, something went wrong. Try again later.");
}
}
-
+
private async loadSubscriptionKeys(): Promise {
const userId = await this.usersService.getCurrentUserId();
@@ -376,45 +399,46 @@ export class Authorization {
return;
}
- if (this.api().type === TypeOfApi.webSocket) {
- this.setSubscriptionKeyParameter(subscriptionKey)
+ if (this.api().type === TypeOfApi.webSocket || this.isGraphQL()) {
+ this.setSubscriptionKeyParameter(subscriptionKey);
} else {
this.setSubscriptionKeyHeader(subscriptionKey);
}
-
- if(!this.isGraphQL()) {
+
+ if (!this.isGraphQL()) {
this.updateRequestSummary();
}
}
- private setSubscriptionKeyParameter(subscriptionKey: string): void {
+ private setSubscriptionKeyParameter(subscriptionKey?: string): void {
const subscriptionKeyParam = this.getSubscriptionKeyParam();
this.removeQueryParameter(subscriptionKeyParam);
-
- if (!subscriptionKey) {
- return;
- }
-
const subscriptionKeyParamName = this.getSubscriptionKeyParamName();
const keyParameter = new ConsoleParameter();
keyParameter.name(subscriptionKeyParamName);
- keyParameter.value(subscriptionKey);
keyParameter.secret = true;
keyParameter.type = "string";
keyParameter.canRename = false;
keyParameter.required = true;
keyParameter.inputType("password");
+ keyParameter.value.extend({ required: { message: `Value is required.` } });
+ keyParameter.value(subscriptionKey);
- this.consoleOperation().request.queryParameters.push(keyParameter);
- this.updateRequestSummary();
+ if (this.isGraphQL()) {
+ this.queryParameters.push(keyParameter);
+ }
+ else {
+ this.consoleOperation().request.queryParameters.push(keyParameter);
+ this.updateRequestSummary();
+ }
}
private getSubscriptionKeyParam(): ConsoleParameter {
const subscriptionKeyParamName = this.getSubscriptionKeyParamName();
const searchName = subscriptionKeyParamName.toLocaleLowerCase();
-
- return this.consoleOperation().request.queryParameters().find(x => x.name()?.toLocaleLowerCase() === searchName);
+ const queryParameters = this.isGraphQL() ? this.queryParameters() : this.consoleOperation().request.queryParameters();
+ return queryParameters.find(x => x.name()?.toLocaleLowerCase() === searchName);
}
private getSubscriptionKeyParamName(): string {
@@ -428,11 +452,12 @@ export class Authorization {
}
public removeQueryParameter(parameter: ConsoleParameter): void {
- this.consoleOperation().request.queryParameters.remove(parameter);
- this.updateRequestSummary();
- }
-
- public collapseAuth(): void {
- this.collapsedAuth(!this.collapsedAuth());
+ if (this.isGraphQL()) {
+ this.queryParameters.remove(parameter);
+ }
+ else {
+ this.consoleOperation().request.queryParameters.remove(parameter);
+ this.updateRequestSummary();
+ }
}
}
\ No newline at end of file
diff --git a/src/components/operations/operation-details/ko/runtime/code-snippet.html b/src/components/operations/operation-details/ko/runtime/code-snippet.html
index 11f42eaf0..5c4ce9f0c 100644
--- a/src/components/operations/operation-details/ko/runtime/code-snippet.html
+++ b/src/components/operations/operation-details/ko/runtime/code-snippet.html
@@ -1,3 +1,5 @@
+
+
diff --git a/src/components/operations/operation-details/ko/runtime/code-snippet.ts b/src/components/operations/operation-details/ko/runtime/code-snippet.ts
index 6f83c5e00..aecd414d2 100644
--- a/src/components/operations/operation-details/ko/runtime/code-snippet.ts
+++ b/src/components/operations/operation-details/ko/runtime/code-snippet.ts
@@ -14,6 +14,7 @@ export class CodeSnippet {
this.title = ko.observable();
this.content = ko.observable();
this.language = ko.observable();
+ this.description = ko.observable();
this.label = ko.pureComputed(() => this.joinStringParts(this.title(), this.language()));
}
@@ -26,6 +27,9 @@ export class CodeSnippet {
@Param()
public readonly language: ko.Observable
;
+ @Param()
+ public readonly description: ko.Observable;
+
private joinStringParts(...parts: string[]): string {
return parts.filter(x => !!x).join(" - ");
}
diff --git a/src/components/operations/operation-details/ko/runtime/graphql-console.html b/src/components/operations/operation-details/ko/runtime/graphql-console.html
index 288723720..652efa5fe 100644
--- a/src/components/operations/operation-details/ko/runtime/graphql-console.html
+++ b/src/components/operations/operation-details/ko/runtime/graphql-console.html
@@ -5,149 +5,313 @@
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Headers
-
-
-
-
-
-
-
+