From 71faaa56dc2e35a0e12abed8165ae67c830da706 Mon Sep 17 00:00:00 2001
From: maharsh9100 <114119345+maharsh9100@users.noreply.github.com>
Date: Fri, 4 Nov 2022 17:38:33 -0400
Subject: [PATCH] Feature: add category icons (#301)

* Update setting.yaml mapping

* Implement adding icon to categoryTitle

* Move resolveIcon func to utils for reusability

* Turn off default export eslint rule

* Fix util typo

* Revert "Turn off default export eslint rule"

This reverts commit e8dd853ba6fac1d33253667ffe9e02010a8dcfd6.

* fix resolveIcon export

* Revert "Update setting.yaml mapping"

This reverts commit 78c947766951e14d1f6db290c0ab03ccc8f1ebc3.

* Revert "Implement adding icon to categoryTitle"

* Use settings layout for group icon

* Revert "Fix util typo"

This reverts commit ab49b426ec6d925d7938c3ddec753a0e7c8759af.

* ResolvedIcon component

Co-authored-by: Mindfreak9100 <dhoom_rik@yahoo.com>
Co-authored-by: Michael Shamoon <4887959+shamoon@users.noreply.github.com>
---
 src/components/quicklaunch.jsx    |  4 +--
 src/components/resolvedicon.jsx   | 35 ++++++++++++++++++++++++++
 src/components/services/group.jsx | 10 +++++++-
 src/components/services/item.jsx  | 42 ++++---------------------------
 4 files changed, 51 insertions(+), 40 deletions(-)
 create mode 100644 src/components/resolvedicon.jsx

diff --git a/src/components/quicklaunch.jsx b/src/components/quicklaunch.jsx
index 077e6c5c..1836e9b7 100644
--- a/src/components/quicklaunch.jsx
+++ b/src/components/quicklaunch.jsx
@@ -2,7 +2,7 @@ import { useTranslation } from "react-i18next";
 import { useEffect, useState, useRef, useCallback, useContext } from "react";
 import classNames from "classnames";
 
-import { resolveIcon } from "./services/item";
+import ResolvedIcon from "./resolvedicon";
 
 import { SettingsContext } from "utils/contexts/settings";
 
@@ -135,7 +135,7 @@ export default function QuickLaunch({servicesAndBookmarks, searchString, setSear
                     )} onClick={handleItemClick}>
                     <div className="flex flex-row items-center mr-4 pointer-events-none">
                       <div className="w-5 text-xs mr-4">
-                        {r.icon && resolveIcon(r.icon)}
+                        {r.icon && <ResolvedIcon icon={r.icon} />}
                         {r.abbr && r.abbr}
                       </div>
                       <div className="flex flex-col md:flex-row text-left items-baseline mr-4 pointer-events-none">
diff --git a/src/components/resolvedicon.jsx b/src/components/resolvedicon.jsx
new file mode 100644
index 00000000..d5aa8c88
--- /dev/null
+++ b/src/components/resolvedicon.jsx
@@ -0,0 +1,35 @@
+import Image from "next/future/image";
+
+export default function ResolvedIcon({ icon }) {
+  // direct or relative URLs
+  if (icon.startsWith("http") || icon.startsWith("/")) {
+    return <Image src={`${icon}`} width={32} height={32} alt="logo" />;
+  }
+
+  // mdi- prefixed, material design icons
+  if (icon.startsWith("mdi-")) {
+    const iconName = icon.replace("mdi-", "").replace(".svg", "");
+    return (
+      <div
+        style={{
+          width: 32,
+          height: 32,
+          background: "linear-gradient(180deg, rgb(var(--color-logo-start)), rgb(var(--color-logo-stop)))",
+          mask: `url(https://cdn.jsdelivr.net/npm/@mdi/svg@latest/svg/${iconName}.svg) no-repeat center / contain`,
+          WebkitMask: `url(https://cdn.jsdelivr.net/npm/@mdi/svg@latest/svg/${iconName}.svg) no-repeat center / contain`,
+        }}
+      />
+    );
+  }
+
+  // fallback to dashboard-icons
+  const iconName = icon.replace(".png", "");
+  return (
+    <Image
+      src={`https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/${iconName}.png`}
+      width={32}
+      height={32}
+      alt="logo"
+    />
+  );
+}
\ No newline at end of file
diff --git a/src/components/services/group.jsx b/src/components/services/group.jsx
index daae1909..13b745fd 100644
--- a/src/components/services/group.jsx
+++ b/src/components/services/group.jsx
@@ -1,6 +1,7 @@
 import classNames from "classnames";
 
 import List from "components/services/list";
+import ResolvedIcon from "components/resolvedicon";
 
 export default function ServicesGroup({ services, layout }) {
   return (
@@ -11,7 +12,14 @@ export default function ServicesGroup({ services, layout }) {
         "flex-1 p-1"
       )}
     >
-      <h2 className="text-theme-800 dark:text-theme-300 text-xl font-medium">{services.name}</h2>
+      <div className="flex select-none items-center">
+        {layout?.icon &&
+          <div className="flex-shrink-0 mr-2 w-7 h-7">
+            <ResolvedIcon icon={layout.icon} />
+          </div>
+        }
+        <h2 className="text-theme-800 dark:text-theme-300 text-xl font-medium">{services.name}</h2>
+      </div>
       <List services={services.services} layout={layout} />
     </div>
   );
diff --git a/src/components/services/item.jsx b/src/components/services/item.jsx
index ea3bc6a0..56ed2b4b 100644
--- a/src/components/services/item.jsx
+++ b/src/components/services/item.jsx
@@ -1,4 +1,3 @@
-import Image from "next/future/image";
 import classNames from "classnames";
 import { useContext, useState } from "react";
 
@@ -7,40 +6,7 @@ import Widget from "./widget";
 
 import Docker from "widgets/docker/component";
 import { SettingsContext } from "utils/contexts/settings";
-
-export function resolveIcon(icon) {
-  // direct or relative URLs
-  if (icon.startsWith("http") || icon.startsWith("/")) {
-    return <Image src={`${icon}`} width={32} height={32} alt="logo" />;
-  }
-
-  // mdi- prefixed, material design icons
-  if (icon.startsWith("mdi-")) {
-    const iconName = icon.replace("mdi-", "").replace(".svg", "");
-    return (
-      <div
-        style={{
-          width: 32,
-          height: 32,
-          background: "linear-gradient(180deg, rgb(var(--color-logo-start)), rgb(var(--color-logo-stop)))",
-          mask: `url(https://cdn.jsdelivr.net/npm/@mdi/svg@latest/svg/${iconName}.svg) no-repeat center / contain`,
-          WebkitMask: `url(https://cdn.jsdelivr.net/npm/@mdi/svg@latest/svg/${iconName}.svg) no-repeat center / contain`,
-        }}
-      />
-    );
-  }
-
-  // fallback to dashboard-icons
-  const iconName = icon.replace(".png", "");
-  return (
-    <Image
-      src={`https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/${iconName}.png`}
-      width={32}
-      height={32}
-      alt="logo"
-    />
-  );
-}
+import ResolvedIcon from "components/resolvedicon";
 
 export default function Item({ service }) {
   const hasLink = service.href && service.href !== "#";
@@ -75,10 +41,12 @@ export default function Item({ service }) {
                 rel="noreferrer"
                 className="flex-shrink-0 flex items-center justify-center w-12 "
               >
-                {resolveIcon(service.icon)}
+                <ResolvedIcon icon={service.icon} />
               </a>
             ) : (
-              <div className="flex-shrink-0 flex items-center justify-center w-12 ">{resolveIcon(service.icon)}</div>
+              <div className="flex-shrink-0 flex items-center justify-center w-12 ">
+                <ResolvedIcon icon={service.icon} />
+              </div>
             ))}
 
           {hasLink ? (