Show workflows in thread list menu

This CL adds the logic for displaying the list of workflows in the menu
which is added to thread lists. Before, the list included some fake
names. It also adds a button to manage workflows.

The next step is to create the components/logic which will allow the
user to execute the workflow in the selected threads.

Bug: twpowertools:74
Change-Id: I22d0be8a101f9e167b9408bb6046299f3bd3c787
diff --git a/src/contentScripts/communityConsole/workflows/components/TwptWorkflowsMenu.js b/src/contentScripts/communityConsole/workflows/components/TwptWorkflowsMenu.js
index 1399228..a706a93 100644
--- a/src/contentScripts/communityConsole/workflows/components/TwptWorkflowsMenu.js
+++ b/src/contentScripts/communityConsole/workflows/components/TwptWorkflowsMenu.js
@@ -1,40 +1,92 @@
+import '@material/web/icon/icon.js';
 import '@material/web/iconbutton/standard-icon-button.js';
-import '@material/web/menu/menu-button.js';
+import '@material/web/list/list-divider.js';
 import '@material/web/menu/menu.js';
+import '@material/web/menu/menu-button.js';
 import '@material/web/menu/menu-item.js';
 
-import {css, html, LitElement} from 'lit';
+import consoleCommonStyles from '!!raw-loader!../../../../static/css/common/console.css';
+
+import {css, html, LitElement, nothing, unsafeCSS} from 'lit';
 import {map} from 'lit/directives/map.js';
-import {range} from 'lit/directives/range.js';
 
 import {SHARED_MD3_STYLES} from '../../../../common/styles/md3.js';
 
 export default class TwptWorkflowsMenu extends LitElement {
+  static properties = {
+    workflows: {type: Object},
+  };
+
   static styles = [
     SHARED_MD3_STYLES,
+    css`${unsafeCSS(consoleCommonStyles)}`,
     css`
       .workflow-item {
         padding-inline: 1em;
       }
+
+      /* Custom styles to override the common button with badge styles */
+      .TWPT-btn--with-badge {
+        padding-bottom: 0!important;
+      }
+
+      .TWPT-btn--with-badge .TWPT-badge {
+        bottom: 8px!important;
+      }
     `,
   ];
 
-  renderMenuItems() {
-    return map(
-        range(8),
-        i => html`
-      <md-menu-item @click="${this._showWorkflow}" data-workflow-id="${
-            i}"><span class="workflow-item" slot="start">Workflow ${
-            i}</span></md-menu-item>
+  renderWorkflowItems() {
+    if (!this.workflows) return nothing;
+    if (this.workflows?.length == 0)
+      return html`
+        <md-menu-item disabled>
+          <span class="workflow-item" slot="start">
+            No workflows
+          </span>
+        </md-menu-item>
+      `;
+    return map(this.workflows, w => html`
+      <md-menu-item
+          @click="${() => this._showWorkflow(w.uuid)}">
+        <span class="workflow-item" slot="start">
+          ${w.proto.getName()}
+        </span>
+      </md-menu-item>
     `);
   }
 
+  renderMenuItems() {
+    return [
+      this.renderWorkflowItems(),
+      html`
+        <md-list-divider></md-list-divider>
+        <md-menu-item
+            @click="${() => this._openWorkflowManager()}">
+          <span class="workflow-item" slot="start">
+            Manage workflows...
+          </span>
+        </md-menu-item>
+      `,
+    ];
+  }
+
+  // Based on createExtBadge() in ../../utils/common.js.
+  renderBadge() {
+    return html`
+      <div class="TWPT-badge">
+        <md-icon>repeat</md-icon>
+      </div>
+    `;
+  }
+
   render() {
-    // The button is based in the button created in the
-    // addButtonToThreadListActions function in file ../../utils/common.js.
     return html`
       <md-menu-button>
-        <md-standard-icon-button slot="button" icon="more_vert"></md-standard-icon-button>
+        <div slot="button" class="TWPT-btn--with-badge">
+          <md-standard-icon-button icon="more_vert"></md-standard-icon-button>
+          ${this.renderBadge()}
+        </div>
         <md-menu slot="menu">
           ${this.renderMenuItems()}
         </md-menu>
@@ -42,9 +94,13 @@
     `;
   }
 
-  _showWorkflow(e) {
-    console.log(
-        `Clicked workflow ${e.target.getAttribute('data-workflow-id')}.`);
+  _showWorkflow(uuid) {
+    console.log(`Clicked workflow ${uuid}.`);
+  }
+
+  _openWorkflowManager() {
+    const e = new Event('twpt-open-workflow-manager');
+    document.dispatchEvent(e);
   }
 }
 window.customElements.define('twpt-workflows-menu', TwptWorkflowsMenu);
diff --git a/src/contentScripts/communityConsole/workflows/components/index.js b/src/contentScripts/communityConsole/workflows/components/index.js
new file mode 100644
index 0000000..8f73fee
--- /dev/null
+++ b/src/contentScripts/communityConsole/workflows/components/index.js
@@ -0,0 +1,29 @@
+import './TwptWorkflowsMenu.js';
+
+import {css, html, LitElement} from 'lit';
+
+import WorkflowsStorage from '../../../../workflows/workflowsStorage.js';
+
+export default class TwptWorkflowsInject extends LitElement {
+  static properties = {
+    _workflows: {type: Object},
+  };
+
+  constructor() {
+    super();
+    this._workflows = null;
+    this.addEventListener('twpt-workflows-update', e => {
+      const workflows = e.detail?.workflows ?? [];
+      WorkflowsStorage.convertRawListToProtobuf(workflows);
+      this._workflows = workflows;
+    });
+  }
+
+  render() {
+    return html`
+      <twpt-workflows-menu .workflows=${this._workflows}></twpt-workflows-menu>
+      <twpt-workflow-dialog></twpt-workflow-dialog>
+    `;
+  }
+}
+window.customElements.define('twpt-workflows-inject', TwptWorkflowsInject);
diff --git a/src/contentScripts/communityConsole/workflows/workflows.js b/src/contentScripts/communityConsole/workflows/workflows.js
index 1c99729..7ba3cbc 100644
--- a/src/contentScripts/communityConsole/workflows/workflows.js
+++ b/src/contentScripts/communityConsole/workflows/workflows.js
@@ -1,16 +1,45 @@
 import {isOptionEnabled} from '../../../common/optionsUtils.js';
+import WorkflowsStorage from '../../../workflows/workflowsStorage.js';
 import {addElementToThreadListActions, shouldAddBtnToActionBar} from '../utils/common.js';
 
 const wfDebugId = 'twpt-workflows';
 
 export default class Workflows {
-  constructor() {}
+  constructor() {
+    this.menu = null;
+    this.workflows = null;
+
+    // Always keep the workflows list updated
+    WorkflowsStorage.watch(workflows => {
+      this.workflows = workflows;
+      this._emitWorkflowsUpdateEvent();
+    }, /* asProtobuf = */ false);
+
+    // Open the workflow manager when instructed to do so.
+    document.addEventListener('twpt-open-workflow-manager', () => {
+      chrome.runtime.sendMessage({
+        message: 'openWorkflowsManager',
+      });
+    });
+  }
+
+  _emitWorkflowsUpdateEvent() {
+    if (!this.menu) return;
+    const e = new CustomEvent('twpt-workflows-update', {
+      detail: {
+        workflows: this.workflows,
+      }
+    });
+    this.menu?.dispatchEvent?.(e);
+  }
 
   addThreadListBtnIfEnabled(readToggle) {
     isOptionEnabled('workflows').then(isEnabled => {
       if (isEnabled) {
-        const menu = document.createElement('twpt-workflows-menu');
-        addElementToThreadListActions(readToggle, menu);
+        this.menu = document.createElement('twpt-workflows-inject');
+        this.menu.setAttribute('debugid', wfDebugId);
+        this._emitWorkflowsUpdateEvent();
+        addElementToThreadListActions(readToggle, this.menu);
       }
     });
   }