From abf68afefbc9a3aeae1cc224ccf48dcf61aabf51 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= <ludeeus@ludeeus.dev>
Date: Sun, 16 Oct 2022 16:27:03 +0200
Subject: [PATCH] Add icon and overflow (#622)

---
 .../hacs-repository-owerflow-menu.ts          | 186 ++++++++++++++++++
 src/localize/languages/en.json                |   1 +
 src/panels/hacs-experimental-panel.ts         |  41 +++-
 src/panels/hacs-repository-panel.ts           | 154 +--------------
 4 files changed, 230 insertions(+), 152 deletions(-)
 create mode 100644 src/components/hacs-repository-owerflow-menu.ts

diff --git a/src/components/hacs-repository-owerflow-menu.ts b/src/components/hacs-repository-owerflow-menu.ts
new file mode 100644
index 00000000..ca5ea256
--- /dev/null
+++ b/src/components/hacs-repository-owerflow-menu.ts
@@ -0,0 +1,186 @@
+import {
+  mdiAlert,
+  mdiAlertCircleOutline,
+  mdiArrowDownCircle,
+  mdiClose,
+  mdiGithub,
+  mdiInformation,
+  mdiLanguageJavascript,
+  mdiReload,
+} from "@mdi/js";
+import memoizeOne from "memoize-one";
+import { mainWindow } from "../../homeassistant-frontend/src/common/dom/get_main_window";
+import { navigate } from "../../homeassistant-frontend/src/common/navigate";
+import "../../homeassistant-frontend/src/components/ha-icon-overflow-menu";
+import { getConfigEntries } from "../../homeassistant-frontend/src/data/config_entries";
+import { showConfirmationDialog } from "../../homeassistant-frontend/src/dialogs/generic/show-dialog-box";
+import { RepositoryBase } from "../data/repository";
+import {
+  deleteResource,
+  fetchResources,
+  repositoryUninstall,
+  repositoryUpdate,
+} from "../data/websocket";
+import { HacsExperimentalPanel } from "../panels/hacs-experimental-panel";
+import { HacsRepositoryPanel } from "../panels/hacs-repository-panel";
+
+export const repositoryMenuItems = memoizeOne(
+  (element: HacsRepositoryPanel | HacsExperimentalPanel, repository: RepositoryBase) => [
+    ...(element.nodeName === "HACS-EXPERIMENTAL-PANEL"
+      ? [
+          {
+            path: mdiInformation,
+            label: element.hacs.localize("common.show"),
+            action: () => navigate(`/hacs/repository/${repository.id}`),
+          },
+        ]
+      : []),
+    {
+      path: mdiGithub,
+      label: element.hacs.localize("common.repository"),
+      action: () =>
+        mainWindow.open(`https://github.com/${repository.full_name}`, "_blank", "noreferrer=true"),
+    },
+    {
+      path: mdiArrowDownCircle,
+      label: element.hacs.localize("repository_card.update_information"),
+      action: async () => {
+        await repositoryUpdate(element.hass, String(repository.id));
+      },
+    },
+
+    ...(repository.installed_version
+      ? [
+          {
+            path: mdiReload,
+            label: element.hacs.localize("repository_card.redownload"),
+            action: () => _downloadRepositoryDialog(element, repository.id),
+            hideForUninstalled: true,
+          },
+        ]
+      : []),
+    ...(repository.category === "plugin" && repository.installed_version
+      ? [
+          {
+            path: mdiLanguageJavascript,
+            label: element.hacs.localize("repository_card.open_source"),
+            action: () =>
+              mainWindow.open(
+                `/hacsfiles/${repository.local_path.split("/").pop()}/${repository.file_name}`,
+                "_blank",
+                "noreferrer=true"
+              ),
+          },
+        ]
+      : []),
+    { divider: true },
+    {
+      path: mdiAlertCircleOutline,
+      label: element.hacs.localize("repository_card.open_issue"),
+      action: () =>
+        mainWindow.open(
+          `https://github.com/${repository.full_name}/issues`,
+          "_blank",
+          "noreferrer=true"
+        ),
+    },
+    ...(repository.id !== "172733314" && repository.installed_version
+      ? [
+          {
+            path: mdiAlert,
+            label: element.hacs.localize("repository_card.report"),
+            action: () =>
+              mainWindow.open(
+                `https://github.com/hacs/integration/issues/new?assignees=ludeeus&labels=flag&template=removal.yml&repo=${repository.full_name}&title=Request for removal of ${repository.full_name}`,
+                "_blank",
+                "noreferrer=true"
+              ),
+            warning: true,
+          },
+          {
+            path: mdiClose,
+            label: element.hacs.localize("common.remove"),
+            action: async () => {
+              if (repository.category === "integration" && repository.config_flow) {
+                const configFlows = (await getConfigEntries(element.hass)).some(
+                  (entry) => entry.domain === repository.domain
+                );
+                if (configFlows) {
+                  const ignore = await showConfirmationDialog(element, {
+                    title: element.hacs.localize("dialog.configured.title"),
+                    text: element.hacs.localize("dialog.configured.message", {
+                      name: repository.name,
+                    }),
+                    dismissText: element.hacs.localize("common.ignore"),
+                    confirmText: element.hacs.localize("common.navigate"),
+                    confirm: () => {
+                      navigate("/config/integrations", { replace: true });
+                    },
+                  });
+                  if (ignore) {
+                    return;
+                  }
+                }
+              }
+              element.dispatchEvent(
+                new CustomEvent("hacs-dialog", {
+                  detail: {
+                    type: "progress",
+                    title: element.hacs.localize("dialog.remove.title"),
+                    confirmText: element.hacs.localize("dialog.remove.title"),
+                    content: element.hacs.localize("dialog.remove.message", {
+                      name: repository.name,
+                    }),
+                    confirm: async () => {
+                      await _repositoryRemove(element, repository);
+                    },
+                  },
+                  bubbles: true,
+                  composed: true,
+                })
+              );
+            },
+            warning: true,
+          },
+        ]
+      : []),
+  ]
+);
+
+const _downloadRepositoryDialog = (
+  element: HacsRepositoryPanel | HacsExperimentalPanel,
+  repositoryId: string
+) => {
+  element.dispatchEvent(
+    new CustomEvent("hacs-dialog", {
+      detail: {
+        type: "download",
+        repository: repositoryId,
+      },
+      bubbles: true,
+      composed: true,
+    })
+  );
+};
+
+const _repositoryRemove = async (
+  element: HacsRepositoryPanel | HacsExperimentalPanel,
+  repository: RepositoryBase
+) => {
+  if (repository.category === "plugin" && element.hacs.info?.lovelace_mode !== "yaml") {
+    const resources = await fetchResources(element.hass);
+    resources
+      .filter((resource) =>
+        resource.url.startsWith(
+          `/hacsfiles/${repository.full_name.split("/")[1]}/${repository.file_name}`
+        )
+      )
+      .forEach(async (resource) => {
+        await deleteResource(element.hass, String(resource.id));
+      });
+  }
+  await repositoryUninstall(element.hass, String(repository.id));
+  if (element.nodeName === "HACS-REPOSITORY-PANEL") {
+    history.back();
+  }
+};
diff --git a/src/localize/languages/en.json b/src/localize/languages/en.json
index 8ff9a899..b6633c32 100644
--- a/src/localize/languages/en.json
+++ b/src/localize/languages/en.json
@@ -5,6 +5,7 @@
     "cancel": "Cancel",
     "close": "Close",
     "download": "Download",
+    "explore": "Explore & download repositories",
     "ignore": "Ignore",
     "integration_plural": "Integrations",
     "integration": "Integration",
diff --git a/src/panels/hacs-experimental-panel.ts b/src/panels/hacs-experimental-panel.ts
index 2dd134ba..988cba9f 100644
--- a/src/panels/hacs-experimental-panel.ts
+++ b/src/panels/hacs-experimental-panel.ts
@@ -26,14 +26,15 @@ import type {
 
 import "../../homeassistant-frontend/src/components/ha-button-menu";
 import "../../homeassistant-frontend/src/components/ha-check-list-item";
-import "../../homeassistant-frontend/src/components/ha-chip";
 import "../../homeassistant-frontend/src/components/ha-fab";
 import "../../homeassistant-frontend/src/components/ha-menu-button";
 import "../../homeassistant-frontend/src/components/ha-svg-icon";
 import { haStyle } from "../../homeassistant-frontend/src/resources/styles";
 import type { HomeAssistant, Route } from "../../homeassistant-frontend/src/types";
+import { brandsUrl } from "../../homeassistant-frontend/src/util/brands-url";
 import { showDialogAbout } from "../components/dialogs/hacs-about-dialog";
 import { hacsIcon } from "../components/hacs-icon";
+import { repositoryMenuItems } from "../components/hacs-repository-owerflow-menu";
 import "../components/hacs-tabs-subpage-data-table";
 import type { Hacs } from "../data/hacs";
 import type { RepositoryBase } from "../data/repository";
@@ -206,7 +207,7 @@ export class HacsExperimentalPanel extends LitElement {
     ${this.section === "entry"
       ? html`
           <a href="/hacs/explore" slot="fab">
-            <ha-fab .label=${this.hacs.localize("store.explore")} .extended=${!this.narrow}>
+            <ha-fab .label=${this.hacs.localize("common.explore")} .extended=${!this.narrow}>
               <ha-svg-icon slot="icon" .path=${mdiPlus}></ha-svg-icon> </ha-fab
           ></a>
         `
@@ -234,6 +235,26 @@ export class HacsExperimentalPanel extends LitElement {
       narrow: boolean,
       tableColumnsOptions: TableColumnsOptions
     ): DataTableColumnContainer<RepositoryBase> => ({
+      icon: {
+        title: "",
+        label: this.hass.localize("ui.panel.config.lovelace.dashboards.picker.headers.icon"),
+        hidden: this.narrow || this.section !== "entry",
+        type: "icon",
+        template: (_, repository: RepositoryBase) =>
+          html`
+            <img
+              style="height: 32px; width: 32px"
+              slot="item-icon"
+              src=${brandsUrl({
+                domain: repository.domain || "github",
+                type: "icon",
+                useFallback: true,
+                darkOptimized: this.hass.themes?.darkMode,
+              })}
+              referrerpolicy="no-referrer"
+            />
+          `,
+      },
       name: {
         ...defaultKeyData,
         title: this.hacs.localize("column.name"),
@@ -282,6 +303,7 @@ export class HacsExperimentalPanel extends LitElement {
         hidden: narrow || !tableColumnsOptions[this.section].category,
         sortable: true,
         width: "10%",
+        template: (category: string) => this.hacs.localize(`common.${category}`),
       },
       authors: defaultKeyData,
       description: defaultKeyData,
@@ -289,6 +311,21 @@ export class HacsExperimentalPanel extends LitElement {
       full_name: defaultKeyData,
       id: defaultKeyData,
       topics: defaultKeyData,
+      actions: {
+        title: "",
+        width: this.narrow ? undefined : "10%",
+        hidden: this.section !== "entry",
+        type: "overflow-menu",
+        template: (_, repository: RepositoryBase) =>
+          html`
+            <ha-icon-overflow-menu
+              .hass=${this.hass}
+              narrow
+              .items=${repositoryMenuItems(this, repository)}
+            >
+            </ha-icon-overflow-menu>
+          `,
+      },
     })
   );
 
diff --git a/src/panels/hacs-repository-panel.ts b/src/panels/hacs-repository-panel.ts
index 7537aaeb..83561d0e 100644
--- a/src/panels/hacs-repository-panel.ts
+++ b/src/panels/hacs-repository-panel.ts
@@ -1,44 +1,28 @@
 import {
   mdiAccount,
-  mdiAlert,
-  mdiAlertCircleOutline,
   mdiArrowDownBold,
-  mdiArrowDownCircle,
-  mdiClose,
   mdiCube,
   mdiDownload,
   mdiExclamationThick,
-  mdiGithub,
-  mdiLanguageJavascript,
-  mdiReload,
   mdiStar,
 } from "@mdi/js";
 import { css, html, LitElement, PropertyValues, TemplateResult } from "lit";
 import { customElement, property, state } from "lit/decorators";
 import memoizeOne from "memoize-one";
 import { mainWindow } from "../../homeassistant-frontend/src/common/dom/get_main_window";
-import { navigate } from "../../homeassistant-frontend/src/common/navigate";
 import { extractSearchParamsObject } from "../../homeassistant-frontend/src/common/url/search-params";
 import "../../homeassistant-frontend/src/components/ha-chip";
 import "../../homeassistant-frontend/src/components/ha-fab";
-import "../../homeassistant-frontend/src/components/ha-icon-overflow-menu";
-import { getConfigEntries } from "../../homeassistant-frontend/src/data/config_entries";
 import { showConfirmationDialog } from "../../homeassistant-frontend/src/dialogs/generic/show-dialog-box";
 import "../../homeassistant-frontend/src/layouts/hass-error-screen";
 import "../../homeassistant-frontend/src/layouts/hass-loading-screen";
 import "../../homeassistant-frontend/src/layouts/hass-subpage";
 import { HomeAssistant, Route } from "../../homeassistant-frontend/src/types";
 import "../components/hacs-link";
+import { repositoryMenuItems } from "../components/hacs-repository-owerflow-menu";
 import { Hacs } from "../data/hacs";
 import { fetchRepositoryInformation, RepositoryBase, RepositoryInfo } from "../data/repository";
-import {
-  deleteResource,
-  fetchResources,
-  getRepositories,
-  repositoryAdd,
-  repositoryUninstall,
-  repositoryUpdate,
-} from "../data/websocket";
+import { getRepositories, repositoryAdd } from "../data/websocket";
 import { HacsStyles } from "../styles/hacs-common-style";
 import { markdown } from "../tools/markdown/markdown";
 
@@ -198,82 +182,10 @@ export class HacsRepositoryPanel extends LitElement {
         hasFab
       >
         <ha-icon-overflow-menu
+          .hass=${this.hass}
           slot="toolbar-icon"
           narrow
-          .hass=${this.hass}
-          .items=${[
-            {
-              path: mdiGithub,
-              label: this.hacs.localize("common.repository"),
-              action: () =>
-                mainWindow.open(
-                  `https://github.com/${this._repository!.full_name}`,
-                  "_blank",
-                  "noreferrer=true"
-                ),
-            },
-            {
-              path: mdiArrowDownCircle,
-              label: this.hacs.localize("repository_card.update_information"),
-              action: () => this._refreshReopsitoryInfo(),
-            },
-            {
-              path: mdiReload,
-              label: this.hacs.localize("repository_card.redownload"),
-              action: () => this._downloadRepositoryDialog(),
-              hideForUninstalled: true,
-            },
-            {
-              category: "plugin",
-              hideForUninstalled: true,
-              path: mdiLanguageJavascript,
-              label: this.hacs.localize("repository_card.open_source"),
-              action: () =>
-                mainWindow.open(
-                  `/hacsfiles/${this._repository!.local_path.split("/").pop()}/${
-                    this._repository!.file_name
-                  }`,
-                  "_blank",
-                  "noreferrer=true"
-                ),
-            },
-            {
-              path: mdiAlertCircleOutline,
-              label: this.hacs.localize("repository_card.open_issue"),
-              action: () =>
-                mainWindow.open(
-                  `https://github.com/${this._repository!.full_name}/issues`,
-                  "_blank",
-                  "noreferrer=true"
-                ),
-            },
-            {
-              hideForId: "172733314",
-              path: mdiAlert,
-              label: this.hacs.localize("repository_card.report"),
-              hideForUninstalled: true,
-              action: () =>
-                mainWindow.open(
-                  `https://github.com/hacs/integration/issues/new?assignees=ludeeus&labels=flag&template=removal.yml&repo=${
-                    this._repository!.full_name
-                  }&title=Request for removal of ${this._repository!.full_name}`,
-                  "_blank",
-                  "noreferrer=true"
-                ),
-            },
-            {
-              hideForId: "172733314",
-              hideForUninstalled: true,
-              path: mdiClose,
-              label: this.hacs.localize("common.remove"),
-              action: () => this._removeRepositoryDialog(),
-            },
-          ].filter(
-            (entry) =>
-              (!entry.category || this._repository!.category === entry.category) &&
-              (!entry.hideForId || String(this._repository!.id) !== entry.hideForId) &&
-              (!entry.hideForUninstalled || this._repository!.installed_version)
-          )}
+          .items=${repositoryMenuItems(this, this._repository)}
         >
         </ha-icon-overflow-menu>
         <div class="content">
@@ -344,64 +256,6 @@ export class HacsRepositoryPanel extends LitElement {
     );
   }
 
-  private async _removeRepositoryDialog() {
-    if (this._repository!.category === "integration" && this._repository!.config_flow) {
-      const configFlows = (await getConfigEntries(this.hass)).some(
-        (entry) => entry.domain === this._repository!.domain
-      );
-      if (configFlows) {
-        const ignore = await showConfirmationDialog(this, {
-          title: this.hacs.localize("dialog.configured.title"),
-          text: this.hacs.localize("dialog.configured.message", { name: this._repository!.name }),
-          dismissText: this.hacs.localize("common.ignore"),
-          confirmText: this.hacs.localize("common.navigate"),
-          confirm: () => {
-            navigate("/config/integrations", { replace: true });
-          },
-        });
-        if (ignore) {
-          return;
-        }
-      }
-    }
-    this.dispatchEvent(
-      new CustomEvent("hacs-dialog", {
-        detail: {
-          type: "progress",
-          title: this.hacs.localize("dialog.remove.title"),
-          confirmText: this.hacs.localize("dialog.remove.title"),
-          content: this.hacs.localize("dialog.remove.message", { name: this._repository!.name }),
-          confirm: async () => {
-            await this._repositoryRemove();
-          },
-        },
-        bubbles: true,
-        composed: true,
-      })
-    );
-  }
-
-  private async _repositoryRemove() {
-    if (this._repository!.category === "plugin" && this.hacs.info?.lovelace_mode !== "yaml") {
-      const resources = await fetchResources(this.hass);
-      resources
-        .filter((resource) =>
-          resource.url.startsWith(
-            `/hacsfiles/${this._repository!.full_name.split("/")[1]}/${this._repository!.file_name}`
-          )
-        )
-        .forEach(async (resource) => {
-          await deleteResource(this.hass, String(resource.id));
-        });
-    }
-    await repositoryUninstall(this.hass, String(this._repository!.id));
-    history.back();
-  }
-
-  private async _refreshReopsitoryInfo() {
-    await repositoryUpdate(this.hass, String(this._repository!.id));
-  }
-
   static get styles() {
     return [
       HacsStyles,