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