From bf35ee73429b0d7439a1cf20ab9197efed064b15 Mon Sep 17 00:00:00 2001
From: Mateusz Winiarczyk <mateusz.winiarczyk@appunite.com>
Date: Fri, 26 Jan 2024 23:04:51 +0100
Subject: [PATCH] refactor(plugins): refactor plugins manager

---
 index.d.ts                                    | 15 +--
 pages/_app.tsx                                |  1 +
 .../MapNavigation/MapNavigation.component.tsx |  1 +
 .../LoadPluginInput.component.tsx             | 28 ++++++
 .../LoadPluginInput/hooks/useLoadPlugin.ts    | 47 ++++++++++
 .../PluginDrawer/LoadPluginInput/index.ts     |  1 +
 .../PluginDrawer/PluginsDrawer.component.tsx  | 94 +++----------------
 .../PluginsList/PluginsList.component.tsx     | 33 +++++++
 .../Drawer/PluginDrawer/PluginsList/index.ts  |  1 +
 src/redux/plugins/plugins.thunk.ts            |  4 +-
 src/services/pluginsManager/pluginsManager.ts |  1 +
 .../query-manager/useReduxBusQueryManager.ts  | 10 +-
 12 files changed, 139 insertions(+), 97 deletions(-)
 create mode 100644 src/components/Map/Drawer/PluginDrawer/LoadPluginInput/LoadPluginInput.component.tsx
 create mode 100644 src/components/Map/Drawer/PluginDrawer/LoadPluginInput/hooks/useLoadPlugin.ts
 create mode 100644 src/components/Map/Drawer/PluginDrawer/LoadPluginInput/index.ts
 create mode 100644 src/components/Map/Drawer/PluginDrawer/PluginsList/PluginsList.component.tsx
 create mode 100644 src/components/Map/Drawer/PluginDrawer/PluginsList/index.ts

diff --git a/index.d.ts b/index.d.ts
index 38a0345e..4e8bc267 100644
--- a/index.d.ts
+++ b/index.d.ts
@@ -10,19 +10,20 @@ type RegisterPlugin = ({ pluginName, pluginVersion, pluginUrl }: Plugin) => {
   element: HTMLDivElement;
 };
 
+type HashPlugin = {
+  pluginUrl: string;
+  pluginScript: string;
+};
+
+type SetHashedPlugin = ({ pluginUrl, pluginScript }: HashPlugin) => void;
+
 declare global {
   interface Window {
     minerva: {
       configuration?: MinervaConfiguration;
       plugins: {
         registerPlugin: RegisterPlugin;
-        setHashedPlugin({
-          pluginUrl,
-          pluginScript,
-        }: {
-          pluginUrl: string;
-          pluginScript: string;
-        }): void;
+        setHashedPlugin: SetHashedPlugin;
       };
     };
   }
diff --git a/pages/_app.tsx b/pages/_app.tsx
index 6aaa491f..d767f29c 100644
--- a/pages/_app.tsx
+++ b/pages/_app.tsx
@@ -7,4 +7,5 @@ const MyApp = ({ Component, pageProps }: AppProps): JSX.Element => (
     <Component {...pageProps} />
   </AppWrapper>
 );
+
 export default MyApp;
diff --git a/src/components/FunctionalArea/MapNavigation/MapNavigation.component.tsx b/src/components/FunctionalArea/MapNavigation/MapNavigation.component.tsx
index 9dfc1863..135deed5 100644
--- a/src/components/FunctionalArea/MapNavigation/MapNavigation.component.tsx
+++ b/src/components/FunctionalArea/MapNavigation/MapNavigation.component.tsx
@@ -51,6 +51,7 @@ export const MapNavigation = (): JSX.Element => {
           )}
         </Button>
       ))}
+      {/* TODO: REMOVE WHEN PLUGIN DRAWER IS IMPLEMENTED */}
       <div className="fixed bottom-0 right-0 top-[104px] w-96 bg-white">
         <div id="plugins" />
       </div>
diff --git a/src/components/Map/Drawer/PluginDrawer/LoadPluginInput/LoadPluginInput.component.tsx b/src/components/Map/Drawer/PluginDrawer/LoadPluginInput/LoadPluginInput.component.tsx
new file mode 100644
index 00000000..b2f77651
--- /dev/null
+++ b/src/components/Map/Drawer/PluginDrawer/LoadPluginInput/LoadPluginInput.component.tsx
@@ -0,0 +1,28 @@
+import { Button } from '@/shared/Button';
+import { Input } from '@/shared/Input';
+import { useLoadPlugin } from './hooks/useLoadPlugin';
+
+export const LoadPluginInput = (): React.ReactNode => {
+  const { handleChangePluginUrl, handleLoadPlugin, isPending, pluginUrl } = useLoadPlugin();
+
+  return (
+    <label>
+      URL:
+      <div className="relative mt-2.5">
+        <Input
+          className="h-10 rounded-r-md pr-[70px]"
+          value={pluginUrl}
+          onChange={handleChangePluginUrl}
+        />
+        <Button
+          className="absolute inset-y-0 right-0 w-[60px] justify-center text-xs font-medium text-primary-500 ring-primary-500 hover:ring-primary-500 disabled:text-primary-500 disabled:ring-primary-500"
+          variantStyles="ghost"
+          onClick={handleLoadPlugin}
+          disabled={isPending}
+        >
+          Load
+        </Button>
+      </div>
+    </label>
+  );
+};
diff --git a/src/components/Map/Drawer/PluginDrawer/LoadPluginInput/hooks/useLoadPlugin.ts b/src/components/Map/Drawer/PluginDrawer/LoadPluginInput/hooks/useLoadPlugin.ts
new file mode 100644
index 00000000..8df7a52d
--- /dev/null
+++ b/src/components/Map/Drawer/PluginDrawer/LoadPluginInput/hooks/useLoadPlugin.ts
@@ -0,0 +1,47 @@
+import { PluginsManager } from '@/services/pluginsManager';
+import axios from 'axios';
+import { ChangeEvent, useState } from 'react';
+
+type UseLoadPluginReturnType = {
+  handleChangePluginUrl: (event: ChangeEvent<HTMLInputElement>) => void;
+  handleLoadPlugin: () => Promise<void>;
+  isPending: boolean;
+  pluginUrl: string;
+};
+
+export const useLoadPlugin = (): UseLoadPluginReturnType => {
+  const [pluginUrl, setPluginUrl] = useState('');
+  const [isLoading, setIsLoading] = useState(false);
+  const isPending = isLoading || !pluginUrl;
+
+  const handleLoadPlugin = async (): Promise<void> => {
+    try {
+      setIsLoading(true);
+      const response = await axios(pluginUrl);
+      const pluginScript = response.data;
+
+      /* eslint-disable no-new-func */
+      const loadPlugin = new Function(pluginScript);
+
+      PluginsManager.setHashedPlugin({
+        pluginUrl,
+        pluginScript,
+      });
+
+      loadPlugin();
+      setPluginUrl('');
+    } finally {
+      setIsLoading(false);
+    }
+  };
+  const handleChangePluginUrl = (event: ChangeEvent<HTMLInputElement>): void => {
+    setPluginUrl(event.target.value);
+  };
+
+  return {
+    handleChangePluginUrl,
+    handleLoadPlugin,
+    isPending,
+    pluginUrl,
+  };
+};
diff --git a/src/components/Map/Drawer/PluginDrawer/LoadPluginInput/index.ts b/src/components/Map/Drawer/PluginDrawer/LoadPluginInput/index.ts
new file mode 100644
index 00000000..f31afe30
--- /dev/null
+++ b/src/components/Map/Drawer/PluginDrawer/LoadPluginInput/index.ts
@@ -0,0 +1 @@
+export { LoadPluginInput } from './LoadPluginInput.component';
diff --git a/src/components/Map/Drawer/PluginDrawer/PluginsDrawer.component.tsx b/src/components/Map/Drawer/PluginDrawer/PluginsDrawer.component.tsx
index bb01c303..18d52695 100644
--- a/src/components/Map/Drawer/PluginDrawer/PluginsDrawer.component.tsx
+++ b/src/components/Map/Drawer/PluginDrawer/PluginsDrawer.component.tsx
@@ -1,84 +1,14 @@
-import { Button } from '@/shared/Button';
 import { DrawerHeading } from '@/shared/DrawerHeading';
-import { Input } from '@/shared/Input';
-import axios from 'axios';
-import React, { ChangeEvent, useState } from 'react';
-import { useAppSelector } from '@/redux/hooks/useAppSelector';
-import { activePluginsSelector } from '@/redux/plugins/plugins.selector';
-import { useAppDispatch } from '@/redux/hooks/useAppDispatch';
-import { removePlugin } from '@/redux/plugins/plugins.slice';
-
-export const PluginsDrawer = (): React.ReactNode => {
-  const [pluginUrl, setPluginUrl] = useState('');
-  const [isLoading, setIsLoading] = useState(false);
-  const activePlugins = useAppSelector(activePluginsSelector);
-  const dispatch = useAppDispatch();
-
-  const handleLoadPlugin = async (): Promise<void> => {
-    try {
-      setIsLoading(true);
-      const res = await axios(pluginUrl);
-      const pluginScript = res.data;
-      /* eslint-disable no-new-func */
-      const loadPlugin = new Function(pluginScript);
-      window.minerva.plugins.setHashedPlugin({
-        pluginUrl,
-        pluginScript,
-      });
-      loadPlugin();
-      setPluginUrl('');
-    } finally {
-      setIsLoading(false);
-    }
-  };
-
-  const handleUnloadPlugin = (pluginId: string): void => {
-    dispatch(removePlugin({ pluginId }));
-  };
-
-  const handleChangePluginUrl = (event: ChangeEvent<HTMLInputElement>): void => {
-    setPluginUrl(event.target.value);
-  };
-
-  return (
-    <div data-testid="available-plugins-drawer" className="h-full max-h-full">
-      <DrawerHeading title="Available plugins" />
-      <div className="h-[calc(100%-93px)] max-h-[calc(100%-93px)] overflow-y-auto p-6">
-        <label>
-          URL:
-          <div className="relative mt-2.5">
-            <Input
-              className="h-10 rounded-r-md pr-[70px]"
-              value={pluginUrl}
-              onChange={handleChangePluginUrl}
-            />
-            <Button
-              className="absolute inset-y-0 right-0 w-[60px] justify-center text-xs font-medium text-primary-500 ring-primary-500 hover:ring-primary-500 disabled:text-primary-500 disabled:ring-primary-500"
-              variantStyles="ghost"
-              onClick={handleLoadPlugin}
-              disabled={isLoading || !pluginUrl}
-            >
-              Load
-            </Button>
-          </div>
-        </label>
-        <ul className="mt-8 flex w-full flex-col gap-y-8">
-          {activePlugins.map(plugin => (
-            <li key={plugin.hash} className="flex w-full items-center justify-between">
-              <span className="text-sm">
-                {plugin.name} ({plugin.version})
-              </span>
-              <Button
-                variantStyles="ghost"
-                onClick={() => handleUnloadPlugin(plugin.hash)}
-                className="h-10 w-[60px] justify-center text-xs font-medium text-primary-500 ring-primary-500 hover:ring-primary-500"
-              >
-                Unload
-              </Button>
-            </li>
-          ))}
-        </ul>
-      </div>
+import React from 'react';
+import { LoadPluginInput } from './LoadPluginInput';
+import { PluginsList } from './PluginsList';
+
+export const PluginsDrawer = (): React.ReactNode => (
+  <div data-testid="available-plugins-drawer" className="h-full max-h-full">
+    <DrawerHeading title="Available plugins" />
+    <div className="h-[calc(100%-93px)] max-h-[calc(100%-93px)] overflow-y-auto p-6">
+      <LoadPluginInput />
+      <PluginsList />
     </div>
-  );
-};
+  </div>
+);
diff --git a/src/components/Map/Drawer/PluginDrawer/PluginsList/PluginsList.component.tsx b/src/components/Map/Drawer/PluginDrawer/PluginsList/PluginsList.component.tsx
new file mode 100644
index 00000000..aaded05c
--- /dev/null
+++ b/src/components/Map/Drawer/PluginDrawer/PluginsList/PluginsList.component.tsx
@@ -0,0 +1,33 @@
+import { useAppDispatch } from '@/redux/hooks/useAppDispatch';
+import { useAppSelector } from '@/redux/hooks/useAppSelector';
+import { activePluginsSelector } from '@/redux/plugins/plugins.selector';
+import { removePlugin } from '@/redux/plugins/plugins.slice';
+import { Button } from '@/shared/Button';
+
+export const PluginsList = (): React.ReactNode => {
+  const activePlugins = useAppSelector(activePluginsSelector);
+  const dispatch = useAppDispatch();
+
+  const handleUnloadPlugin = (pluginId: string): void => {
+    dispatch(removePlugin({ pluginId }));
+  };
+
+  return (
+    <ul className="mt-8 flex w-full flex-col gap-y-8">
+      {activePlugins.map(plugin => (
+        <li key={plugin.hash} className="flex w-full items-center justify-between">
+          <span className="text-sm">
+            {plugin.name} ({plugin.version})
+          </span>
+          <Button
+            variantStyles="ghost"
+            onClick={() => handleUnloadPlugin(plugin.hash)}
+            className="h-10 w-[60px] justify-center text-xs font-medium text-primary-500 ring-primary-500 hover:ring-primary-500"
+          >
+            Unload
+          </Button>
+        </li>
+      ))}
+    </ul>
+  );
+};
diff --git a/src/components/Map/Drawer/PluginDrawer/PluginsList/index.ts b/src/components/Map/Drawer/PluginDrawer/PluginsList/index.ts
new file mode 100644
index 00000000..519e3a35
--- /dev/null
+++ b/src/components/Map/Drawer/PluginDrawer/PluginsList/index.ts
@@ -0,0 +1 @@
+export { PluginsList } from './PluginsList.component';
diff --git a/src/redux/plugins/plugins.thunk.ts b/src/redux/plugins/plugins.thunk.ts
index 1fca4a2f..0b8647d0 100644
--- a/src/redux/plugins/plugins.thunk.ts
+++ b/src/redux/plugins/plugins.thunk.ts
@@ -1,4 +1,3 @@
-/* eslint-disable no-await-in-loop */
 /* eslint-disable no-magic-numbers */
 import axios from 'axios';
 import { createAsyncThunk } from '@reduxjs/toolkit';
@@ -56,7 +55,7 @@ type GetInitPluginsProps = { pluginsId: string[] };
 export const getInitPlugins = createAsyncThunk<void, GetInitPluginsProps>(
   'plugins/getInitPlugins',
   async ({ pluginsId }): Promise<void> => {
-    /* eslint-disable no-restricted-syntax */
+    /* eslint-disable no-restricted-syntax, no-await-in-loop */
     for (const pluginId of pluginsId) {
       const res = await axiosInstance<Plugin>(apiPath.getPlugin(pluginId));
 
@@ -67,6 +66,7 @@ export const getInitPlugins = createAsyncThunk<void, GetInitPluginsProps>(
         const scriptRes = await axios(urls[0]);
         const pluginScript = scriptRes.data;
         window.minerva.plugins.setHashedPlugin({ pluginUrl: urls[0], pluginScript });
+
         /* eslint-disable no-new-func */
         const loadPlugin = new Function(pluginScript);
         loadPlugin();
diff --git a/src/services/pluginsManager/pluginsManager.ts b/src/services/pluginsManager/pluginsManager.ts
index 620354de..d9547782 100644
--- a/src/services/pluginsManager/pluginsManager.ts
+++ b/src/services/pluginsManager/pluginsManager.ts
@@ -48,6 +48,7 @@ export const PluginsManager: PluginsManagerType = {
       }),
     );
 
+    // TODO: replace when plugins drawer is implemented
     const element = document.createElement('div');
     const wrapper = document.querySelector('#plugins');
     wrapper?.append(element);
diff --git a/src/utils/query-manager/useReduxBusQueryManager.ts b/src/utils/query-manager/useReduxBusQueryManager.ts
index b25ab80d..6faa85fa 100644
--- a/src/utils/query-manager/useReduxBusQueryManager.ts
+++ b/src/utils/query-manager/useReduxBusQueryManager.ts
@@ -10,8 +10,9 @@ export const useReduxBusQueryManager = (): void => {
   const isDataLoaded = useSelector(initDataAndMapLoadingFinished);
 
   const handleChangeQuery = useCallback(
-    () => {
-      return router.replace(
+    () =>
+      // eslint-disable-next-line react-hooks/exhaustive-deps
+      router.replace(
         {
           query: {
             ...queryData,
@@ -21,10 +22,7 @@ export const useReduxBusQueryManager = (): void => {
         {
           shallow: true,
         },
-      );
-    },
-    // router is not an stable reference
-    // eslint-disable-next-line react-hooks/exhaustive-deps
+      ),
     [queryData],
   );
 
-- 
GitLab