diff --git a/index.d.ts b/index.d.ts index 38a0345e9a3f3ffd18ea6eb6cb25a0d2baf6eafc..4e8bc2679b821e8af543715e13631f6813a498db 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 6aaa491fc0284e752eb320ecb1cf72d376013581..d767f29ca9bff1e622a7e7d4fd8723c6ed9b3050 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 9dfc18639e19217d7fc8d00c5c6e190a45aa8c11..135deed5556663ba159229693520417aa80f608f 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 0000000000000000000000000000000000000000..b2f77651ac6adbb8059f2d38eb87ee8d2a257420 --- /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 0000000000000000000000000000000000000000..8df7a52d55142598573cfc316c2ee9bb184f2368 --- /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 0000000000000000000000000000000000000000..f31afe30c9e4723d406021242730c75a08a46777 --- /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 bb01c303a137ab0e849e708734c3bbb6fa007cc0..18d526951217960970944215adc5d5afee56d3ef 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 0000000000000000000000000000000000000000..aaded05cd2102dfcec2292c5c7d8ab48bb38d006 --- /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 0000000000000000000000000000000000000000..519e3a35b42f68d348674bf85db65526ce54cad9 --- /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 1fca4a2f8c2de28b0b381623f043a87373c99173..0b8647d0f49ce4b381fd5cc5f5e7ba659f67817f 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 620354deb5292401b119aef8d203249f61f9fde4..d9547782cd25fec6f575ecebaa9bae3603bc61e2 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 b25ab80d963ee3b8e6fa12ba1d4bc3c9014f041b..6faa85fa8bb171eccf964e2945911a810c91a5bf 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], );