Skip to content
Snippets Groups Projects
Commit bf35ee73 authored by mateusz-winiarczyk's avatar mateusz-winiarczyk
Browse files

refactor(plugins): refactor plugins manager

parent 97e77b8e
No related branches found
No related tags found
3 merge requests!223reset the pin numbers before search results are fetch (so the results will be...,!122feat: add main plugins drawer with tabs (MIN-232),!116feat(plugins): Plugin loading management (MIN-233)
Pipeline #84920 passed
Showing
with 139 additions and 97 deletions
...@@ -10,19 +10,20 @@ type RegisterPlugin = ({ pluginName, pluginVersion, pluginUrl }: Plugin) => { ...@@ -10,19 +10,20 @@ type RegisterPlugin = ({ pluginName, pluginVersion, pluginUrl }: Plugin) => {
element: HTMLDivElement; element: HTMLDivElement;
}; };
type HashPlugin = {
pluginUrl: string;
pluginScript: string;
};
type SetHashedPlugin = ({ pluginUrl, pluginScript }: HashPlugin) => void;
declare global { declare global {
interface Window { interface Window {
minerva: { minerva: {
configuration?: MinervaConfiguration; configuration?: MinervaConfiguration;
plugins: { plugins: {
registerPlugin: RegisterPlugin; registerPlugin: RegisterPlugin;
setHashedPlugin({ setHashedPlugin: SetHashedPlugin;
pluginUrl,
pluginScript,
}: {
pluginUrl: string;
pluginScript: string;
}): void;
}; };
}; };
} }
......
...@@ -7,4 +7,5 @@ const MyApp = ({ Component, pageProps }: AppProps): JSX.Element => ( ...@@ -7,4 +7,5 @@ const MyApp = ({ Component, pageProps }: AppProps): JSX.Element => (
<Component {...pageProps} /> <Component {...pageProps} />
</AppWrapper> </AppWrapper>
); );
export default MyApp; export default MyApp;
...@@ -51,6 +51,7 @@ export const MapNavigation = (): JSX.Element => { ...@@ -51,6 +51,7 @@ export const MapNavigation = (): JSX.Element => {
)} )}
</Button> </Button>
))} ))}
{/* TODO: REMOVE WHEN PLUGIN DRAWER IS IMPLEMENTED */}
<div className="fixed bottom-0 right-0 top-[104px] w-96 bg-white"> <div className="fixed bottom-0 right-0 top-[104px] w-96 bg-white">
<div id="plugins" /> <div id="plugins" />
</div> </div>
......
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>
);
};
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,
};
};
export { LoadPluginInput } from './LoadPluginInput.component';
import { Button } from '@/shared/Button';
import { DrawerHeading } from '@/shared/DrawerHeading'; import { DrawerHeading } from '@/shared/DrawerHeading';
import { Input } from '@/shared/Input'; import React from 'react';
import axios from 'axios'; import { LoadPluginInput } from './LoadPluginInput';
import React, { ChangeEvent, useState } from 'react'; import { PluginsList } from './PluginsList';
import { useAppSelector } from '@/redux/hooks/useAppSelector';
import { activePluginsSelector } from '@/redux/plugins/plugins.selector'; export const PluginsDrawer = (): React.ReactNode => (
import { useAppDispatch } from '@/redux/hooks/useAppDispatch'; <div data-testid="available-plugins-drawer" className="h-full max-h-full">
import { removePlugin } from '@/redux/plugins/plugins.slice'; <DrawerHeading title="Available plugins" />
<div className="h-[calc(100%-93px)] max-h-[calc(100%-93px)] overflow-y-auto p-6">
export const PluginsDrawer = (): React.ReactNode => { <LoadPluginInput />
const [pluginUrl, setPluginUrl] = useState(''); <PluginsList />
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>
</div> </div>
); </div>
}; );
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>
);
};
export { PluginsList } from './PluginsList.component';
/* eslint-disable no-await-in-loop */
/* eslint-disable no-magic-numbers */ /* eslint-disable no-magic-numbers */
import axios from 'axios'; import axios from 'axios';
import { createAsyncThunk } from '@reduxjs/toolkit'; import { createAsyncThunk } from '@reduxjs/toolkit';
...@@ -56,7 +55,7 @@ type GetInitPluginsProps = { pluginsId: string[] }; ...@@ -56,7 +55,7 @@ type GetInitPluginsProps = { pluginsId: string[] };
export const getInitPlugins = createAsyncThunk<void, GetInitPluginsProps>( export const getInitPlugins = createAsyncThunk<void, GetInitPluginsProps>(
'plugins/getInitPlugins', 'plugins/getInitPlugins',
async ({ pluginsId }): Promise<void> => { async ({ pluginsId }): Promise<void> => {
/* eslint-disable no-restricted-syntax */ /* eslint-disable no-restricted-syntax, no-await-in-loop */
for (const pluginId of pluginsId) { for (const pluginId of pluginsId) {
const res = await axiosInstance<Plugin>(apiPath.getPlugin(pluginId)); const res = await axiosInstance<Plugin>(apiPath.getPlugin(pluginId));
...@@ -67,6 +66,7 @@ export const getInitPlugins = createAsyncThunk<void, GetInitPluginsProps>( ...@@ -67,6 +66,7 @@ export const getInitPlugins = createAsyncThunk<void, GetInitPluginsProps>(
const scriptRes = await axios(urls[0]); const scriptRes = await axios(urls[0]);
const pluginScript = scriptRes.data; const pluginScript = scriptRes.data;
window.minerva.plugins.setHashedPlugin({ pluginUrl: urls[0], pluginScript }); window.minerva.plugins.setHashedPlugin({ pluginUrl: urls[0], pluginScript });
/* eslint-disable no-new-func */ /* eslint-disable no-new-func */
const loadPlugin = new Function(pluginScript); const loadPlugin = new Function(pluginScript);
loadPlugin(); loadPlugin();
......
...@@ -48,6 +48,7 @@ export const PluginsManager: PluginsManagerType = { ...@@ -48,6 +48,7 @@ export const PluginsManager: PluginsManagerType = {
}), }),
); );
// TODO: replace when plugins drawer is implemented
const element = document.createElement('div'); const element = document.createElement('div');
const wrapper = document.querySelector('#plugins'); const wrapper = document.querySelector('#plugins');
wrapper?.append(element); wrapper?.append(element);
......
...@@ -10,8 +10,9 @@ export const useReduxBusQueryManager = (): void => { ...@@ -10,8 +10,9 @@ export const useReduxBusQueryManager = (): void => {
const isDataLoaded = useSelector(initDataAndMapLoadingFinished); const isDataLoaded = useSelector(initDataAndMapLoadingFinished);
const handleChangeQuery = useCallback( const handleChangeQuery = useCallback(
() => { () =>
return router.replace( // eslint-disable-next-line react-hooks/exhaustive-deps
router.replace(
{ {
query: { query: {
...queryData, ...queryData,
...@@ -21,10 +22,7 @@ export const useReduxBusQueryManager = (): void => { ...@@ -21,10 +22,7 @@ export const useReduxBusQueryManager = (): void => {
{ {
shallow: true, shallow: true,
}, },
); ),
},
// router is not an stable reference
// eslint-disable-next-line react-hooks/exhaustive-deps
[queryData], [queryData],
); );
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment