{
+ this._categories = jsonRpcResponse.result;
+ this._categories.push({name:'Uncategorised', id: 'uncategorised'});
+ });
+
+ if(!this._extensions){
+ this.hotReload();
+ }
+ }
+
+ hotReload(){
+ this.jsonRpc.getInstallableExtensions().then(jsonRpcResponse => {
+ this._extensions = jsonRpcResponse.result;
+ this._filteredExtensions = this._extensions;
+ });
+ }
+
+ render() {
+ if(this._filteredExtensions){
+ return html`
+ ${this._renderFilterBar()}
+ ${this._renderGrid()}
+
`;
+ }else{
+ return html``;
+ }
+ }
+
+ _renderGrid(){
+ return html` {
+ const prop = event.detail.value;
+ this._detailsOpenedItem = prop ? [prop] : [];
+ }}"
+ ${gridRowDetailsRenderer(this._descriptionRenderer, [])}>
+
+
+ `;
+ }
+
+ _renderFilterBar(){
+ return html`
+ this._filterTextChanged(e)}">
+
+ ${this._filteredExtensions.length}
+
+ ${this._renderCategoryDropdown()}
+
`;
+
+ }
+
+ _renderCategoryDropdown(){
+ if(this._categories){
+ return html` this._filterCategoryChanged(e)}"
+ clear-button-visible
+ >`;
+ }
+ }
+
+ _filterCategoryChanged(e){
+ this._filteredCategory = (e.detail.value || '').trim();
+ return this._filterGrid();
+ }
+
+ _filterTextChanged(e) {
+ this._filteredValue = (e.detail.value || '').trim();
+ return this._filterGrid();
+ }
+
+ _filterGrid(){
+ this._filteredExtensions = this._extensions.filter((prop) => {
+ if(this._filteredValue && this._filteredValue !== '' && this._filteredCategory && this._filteredCategory !== ''){
+ return this._filterByTerm(prop) && this._filterByCategory(prop);
+ }else if(this._filteredValue && this._filteredValue !== ''){
+ return this._filterByTerm(prop);
+ }else if(this._filteredCategory && this._filteredCategory !== ''){
+ return this._filterByCategory(prop);
+ }else{
+ return true;
+ }
+ });
+ }
+
+ _filterByTerm(prop){
+ if(prop.metadata && prop.metadata.keywords){
+ return this._match(prop.name, this._filteredValue) || this._match(prop.description, this._filteredValue) || prop.metadata.keywords.includes(this._filteredValue);
+ }else{
+ return this._match(prop.name, this._filteredValue) || this._match(prop.description, this._filteredValue);
+ }
+ }
+
+ _filterByCategory(prop){
+ if(prop.metadata && prop.metadata.categories){
+ return prop.metadata.categories.includes(this._filteredCategory);
+ }else if(this._filteredCategory === "uncategorised"){
+ return true;
+ }else {
+ return false;
+ }
+ }
+
+ _match(value, term) {
+ if (! value) {
+ return false;
+ }
+
+ return value.toLowerCase().includes(term.toLowerCase());
+ }
+
+ _descriptionRenderer(prop) {
+
+ return html`
+
+ Artifact: ${prop.artifact.groupId}:${prop.artifact.artifactId}
+ Version: ${prop.artifact.version}
+ ${this._renderIsPlatform(prop)}
+ ${this._renderMetadata1(prop)}
+
+
+ ${this._renderMetadata2(prop)}
+
+
+ this._install(prop)}">
+
+ Add Extension
+
+ `;
+ }
+
+ _renderMetadata1(prop){
+ if(prop.metadata){
+ return html`${this._renderGuide(prop.metadata)}
+ ${this._renderScmUrl(prop.metadata)}
+ ${this._renderStatus(prop.metadata)}
+ ${this._renderMinJavaVersion(prop.metadata)}`;
+ }
+ }
+
+ _renderIsPlatform(prop){
+ if (prop.origins && prop.origins.some(str => str.startsWith("io.quarkus:quarkus-bom-quarkus-platform"))){
+ return html`Platform: `;
+ } else {
+ return html`Platform: `;
+ }
+ }
+
+ _renderGuide(metadata){
+ if (metadata.guide){
+ return html`Guide: ${metadata.guide}`;
+ }
+ }
+
+ _renderScmUrl(metadata){
+ if (metadata['scm-url']){
+ return html`SCM: ${metadata['scm-url']}`;
+ }
+ }
+
+ _renderStatus(metadata){
+ if(metadata.status){
+ return html`Status: ${metadata.status.toUpperCase()}`;
+ }
+ }
+
+ _renderMinJavaVersion(metadata){
+ if(metadata['minimum-java-version']){
+ return html`Minimum Java version: ${metadata['minimum-java-version']}`;
+ }
+ }
+
+ _renderMetadata2(prop){
+ if(prop.metadata){
+ return html`${this._renderKeywords(prop.metadata)}
+ ${this._renderCategories(prop.metadata)}
+ ${this._renderExtensionDependencies(prop.metadata)}`;
+ }
+ }
+
+
+
+
+
+ _statusLevel(s){
+ if(s === "stable") {
+ return "success";
+ } else if(s === "experimental") {
+ return "warning";
+ } else if(s === "preview") {
+ return "contrast";
+ }
+ return null;
+ }
+
+ _renderCategories(metadata){
+ if(metadata.categories){
+ return this._renderList("Categories", metadata.categories);
+ }
+ }
+
+ _renderKeywords(metadata){
+ if(metadata.keywords){
+ return this._renderList("Keywords", metadata.keywords);
+ }
+ }
+
+ _renderExtensionDependencies(metadata){
+ if(metadata['extension-dependencies']){
+ return html`
+
+ ${this._renderExtensionDependenciesLines(metadata['extension-dependencies'])}
+
+ `;
+ }
+ }
+
+ _renderExtensionDependenciesLines(lines){
+ return html`
+ ${lines.map((line) =>
+ html`${line}`
+ )}
+ `;
+ }
+
+ _renderList(heading, list) {
+ return html`${heading}: ${this._renderListLines(list)}`;
+ }
+
+ _renderListLines(list) {
+ return html`
+
+ ${list.map((item) =>
+ html`- ${item}
`
+ )}
+
+ `;
+ }
+
+ _install(prop){
+ let extensionArtifactId = prop.artifact.groupId + ':' + prop.artifact.artifactId;
+ this.jsonRpc.addExtension({extensionArtifactId:extensionArtifactId}).then(jsonRpcResponse => {
+ let outcome = jsonRpcResponse.result;
+
+ const options = {
+ detail: {outcome: outcome, name: prop.name},
+ bubbles: true,
+ composed: true,
+ };
+ this.dispatchEvent(new CustomEvent('inprogress', options));
+
+ });
+ }
+}
+customElements.define('qwc-extension-add', QwcExtensionAdd);
diff --git a/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-extension.js b/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-extension.js
index 5c76cb0bfe8bd..7f719cca05077 100644
--- a/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-extension.js
+++ b/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-extension.js
@@ -1,14 +1,19 @@
import { LitElement, html, css} from 'lit';
+import { observeState } from 'lit-element-state';
import '@vaadin/icon';
import '@vaadin/dialog';
import { dialogHeaderRenderer, dialogRenderer } from '@vaadin/dialog/lit.js';
import '@qomponent/qui-badge';
+import { JsonRpc } from 'jsonrpc';
+import { notifier } from 'notifier';
+import { connectionState } from 'connection-state';
/**
* This component represent one extension
* It's a card on the extension board
*/
-export class QwcExtension extends LitElement {
+export class QwcExtension extends observeState(LitElement) {
+ jsonRpc = new JsonRpc("devui-extensions", false);
static styles = css`
.card {
@@ -95,13 +100,15 @@ export class QwcExtension extends LitElement {
builtWith: {type: String},
providesCapabilities: {},
extensionDependencies: {},
- favourite: {type: Boolean},
+ favourite: {type: Boolean},
+ installed: {type: Boolean},
};
constructor() {
super();
this._dialogOpened = false;
this.favourite = false;
+ this.installed = false;
}
render() {
@@ -260,9 +267,30 @@ export class QwcExtension extends LitElement {
${this._renderExtensionDependencies()} |
+ ${this._renderUninstallButton()}
`;
}
+ _renderUninstallButton(){
+ if(connectionState.current.isConnected && this.installed){
+ return html`
+
+ Remove this extension
+ `;
+ }
+ }
+
+ _uninstall(){
+ this._dialogOpened = false;
+ notifier.showInfoMessage(this.name + " removal in progress");
+ this.jsonRpc.removeExtension({extensionArtifactId:this.artifact}).then(jsonRpcResponse => {
+ let outcome = jsonRpcResponse.result;
+ if(!outcome){
+ notifier.showErrorMessage(name + " removal failed");
+ }
+ });
+ }
+
_renderGuideDetails() {
return this.guide
? html`${this.guide}`
diff --git a/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-extensions.js b/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-extensions.js
index d555580dcaf02..0acd7c98466f0 100644
--- a/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-extensions.js
+++ b/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-extensions.js
@@ -5,15 +5,21 @@ import { devuiState } from 'devui-state';
import { observeState } from 'lit-element-state';
import 'qwc/qwc-extension.js';
import 'qwc/qwc-extension-link.js';
+import 'qwc/qwc-extension-add.js';
import { StorageController } from 'storage-controller';
-
+import '@vaadin/dialog';
+import { dialogHeaderRenderer, dialogRenderer } from '@vaadin/dialog/lit.js';
+import { notifier } from 'notifier';
+import { connectionState } from 'connection-state';
+import { JsonRpc } from 'jsonrpc';
+import '@vaadin/progress-bar';
/**
* This component create cards of all the extensions
*/
export class QwcExtensions extends observeState(LitElement) {
routerController = new RouterController(this);
storageController = new StorageController(this);
-
+ jsonRpc = new JsonRpc("devui-extensions", false);
static styles = css`
.grid {
display: flex;
@@ -49,22 +55,51 @@ export class QwcExtensions extends observeState(LitElement) {
qwc-extension-link {
cursor: grab;
}
+ .addExtensionButton {
+ position: absolute;
+ bottom: 40px;
+ right: 40px;
+ width: 3em;
+ height: 3em;
+ box-shadow: var(--lumo-shade) 5px 5px 15px 3px;
+ }
+ .addExtensionIcon {
+ width: 2em;
+ height: 2em;
+ }
`;
static properties = {
_favourites: {state: true},
+ _addDialogOpened: {state: true},
+ _installedExtensions: {state: true, type: Array},
}
constructor() {
super();
this._favourites = this._getStoredFavourites();
+ this._addDialogOpened = false;
+ this._installedExtensions = null;
+ }
+
+ connectedCallback() {
+ super.connectedCallback();
+ this.jsonRpc.getInstalledNamespaces().then(jsonRpcResponse => {
+ this._installedExtensions = jsonRpcResponse.result;
+ });
}
render() {
- return html`
- ${this._renderActives(devuiState.cards.active)}
- ${devuiState.cards.inactive.map(extension => this._renderInactive(extension))}
-
`;
+ if(this._installedExtensions){
+ return html`
+ ${this._renderActives(devuiState.cards.active)}
+ ${devuiState.cards.inactive.map(extension => this._renderInactive(extension))}
+
+ ${this._renderAddDialog()}`;
+ }else{
+ return html`
+ `;
+ }
}
_renderActives(extensions){
@@ -97,7 +132,7 @@ export class QwcExtensions extends observeState(LitElement) {
}
_renderActive(extension, fav){
-
+ let installed = this._installedExtensions.includes(extension.namespace);
return html`
@@ -243,5 +279,55 @@ export class QwcExtensions extends observeState(LitElement) {
`;
}
}
+
+ _renderAddDialog(){
+ return html`
+ {
+ this._addDialogOpened = event.detail.value;
+ }}"
+ ${dialogHeaderRenderer(
+ () => html`
+ (this._addDialogOpened = false)}">
+
+
+ `,
+ []
+ )}
+ ${dialogRenderer(
+ () => html``
+ )}
+ >
+ ${this._renderAddExtensionButton()}
+ `;
+ }
+
+ _renderAddExtensionButton(){
+ if(connectionState.current.isConnected){
+ return html`
+
+ `;
+ }
+ }
+
+ _installRequest(e){
+ this._addDialogOpened = false;
+ let name = e.detail.name;
+ if(e.detail.outcome){
+ notifier.showInfoMessage(name + " installation in progress");
+ }else{
+ notifier.showErrorMessage(name + " installation failed");
+ }
+ }
+
+ _openAddDialog() {
+ this._addDialogOpened = true;
+ }
+
}
customElements.define('qwc-extensions', QwcExtensions);
diff --git a/extensions/vertx-http/dev-ui-tests/pom.xml b/extensions/vertx-http/dev-ui-tests/pom.xml
index 2e4d76670e19d..d90876fa4f26f 100644
--- a/extensions/vertx-http/dev-ui-tests/pom.xml
+++ b/extensions/vertx-http/dev-ui-tests/pom.xml
@@ -24,6 +24,10 @@
com.fasterxml.jackson.core
jackson-databind