Skip to content
Snippets Groups Projects
Commit 47071f86 authored by Piotr Gawron's avatar Piotr Gawron
Browse files

render custom plugin context menu entries

parent 2f80506e
No related branches found
No related tags found
1 merge request!293Resolve "Allow plugin to add entries to context menu"
...@@ -7,6 +7,7 @@ import { ...@@ -7,6 +7,7 @@ import {
import { act, render, screen } from '@testing-library/react'; import { act, render, screen } from '@testing-library/react';
import { CONTEXT_MENU_INITIAL_STATE } from '@/redux/contextMenu/contextMenu.constants'; import { CONTEXT_MENU_INITIAL_STATE } from '@/redux/contextMenu/contextMenu.constants';
import { bioEntityContentFixture } from '@/models/fixtures/bioEntityContentsFixture'; import { bioEntityContentFixture } from '@/models/fixtures/bioEntityContentsFixture';
import { PluginsContextMenu } from '@/services/pluginsManager/pluginContextMenu/pluginsContextMenu';
import { ContextMenu } from './ContextMenu.component'; import { ContextMenu } from './ContextMenu.component';
const renderComponent = (initialStore?: InitialStoreState): { store: StoreType } => { const renderComponent = (initialStore?: InitialStoreState): { store: StoreType } => {
...@@ -24,6 +25,13 @@ const renderComponent = (initialStore?: InitialStoreState): { store: StoreType } ...@@ -24,6 +25,13 @@ const renderComponent = (initialStore?: InitialStoreState): { store: StoreType }
}; };
describe('ContextMenu - Component', () => { describe('ContextMenu - Component', () => {
beforeEach(() => {
PluginsContextMenu.menuItems = [];
});
afterEach(() => {
PluginsContextMenu.menuItems = [];
});
describe('when context menu is hidden', () => { describe('when context menu is hidden', () => {
beforeEach(() => { beforeEach(() => {
renderComponent({ renderComponent({
...@@ -183,4 +191,23 @@ describe('ContextMenu - Component', () => { ...@@ -183,4 +191,23 @@ describe('ContextMenu - Component', () => {
expect(modal.modalName).toBe('mol-art'); expect(modal.modalName).toBe('mol-art');
}); });
}); });
it('should render context menu', () => {
const callback = jest.fn();
PluginsContextMenu.addMenu('1324235432', 'Click me', '', true, callback);
renderComponent({
contextMenu: {
...CONTEXT_MENU_INITIAL_STATE,
isOpen: true,
coordinates: [0, 0],
uniprot: '',
},
});
expect(screen.getByTestId('context-modal')).toBeInTheDocument();
expect(screen.getByTestId('context-modal')).not.toHaveClass('hidden');
expect(screen.getByText('Click me')).toBeInTheDocument();
});
}); });
...@@ -7,9 +7,18 @@ import { openAddCommentModal, openMolArtModalById } from '@/redux/modal/modal.sl ...@@ -7,9 +7,18 @@ import { openAddCommentModal, openMolArtModalById } from '@/redux/modal/modal.sl
import React from 'react'; import React from 'react';
import { twMerge } from 'tailwind-merge'; import { twMerge } from 'tailwind-merge';
import { FIRST_ARRAY_ELEMENT, SECOND_ARRAY_ELEMENT } from '@/constants/common'; import { FIRST_ARRAY_ELEMENT, SECOND_ARRAY_ELEMENT, ZERO } from '@/constants/common';
import { PluginsContextMenu } from '@/services/pluginsManager/pluginContextMenu/pluginsContextMenu';
import { BioEntity, Reaction } from '@/types/models';
import { ClickCoordinates } from '@/services/pluginsManager/pluginContextMenu/pluginsContextMenu.types';
import { currentModelSelector } from '@/redux/models/models.selectors';
import { mapDataLastPositionSelector } from '@/redux/map/map.selectors';
import { DEFAULT_ZOOM } from '@/constants/map';
export const ContextMenu = (): React.ReactNode => { export const ContextMenu = (): React.ReactNode => {
const pluginContextMenu = PluginsContextMenu.menuItems;
const model = useAppSelector(currentModelSelector);
const lastPosition = useAppSelector(mapDataLastPositionSelector);
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const { isOpen, coordinates } = useAppSelector(contextMenuSelector); const { isOpen, coordinates } = useAppSelector(contextMenuSelector);
const unitProtId = useAppSelector(searchedBioEntityElementUniProtIdSelector); const unitProtId = useAppSelector(searchedBioEntityElementUniProtIdSelector);
...@@ -32,6 +41,27 @@ export const ContextMenu = (): React.ReactNode => { ...@@ -32,6 +41,27 @@ export const ContextMenu = (): React.ReactNode => {
dispatch(openAddCommentModal()); dispatch(openAddCommentModal());
}; };
const modelId = model ? model.idObject : ZERO;
const handleCallback = (
callback: (coordinates: ClickCoordinates, element: BioEntity | Reaction | undefined) => void,
) => {
return () => {
return callback(
{
modelId,
x: coordinates[FIRST_ARRAY_ELEMENT],
y: coordinates[SECOND_ARRAY_ELEMENT],
zoom: lastPosition.z ? lastPosition.z : DEFAULT_ZOOM,
},
undefined,
);
};
};
// eslint-disable-next-line no-console
console.log(pluginContextMenu);
return ( return (
<div <div
className={twMerge( className={twMerge(
...@@ -64,6 +94,22 @@ export const ContextMenu = (): React.ReactNode => { ...@@ -64,6 +94,22 @@ export const ContextMenu = (): React.ReactNode => {
> >
Add comment Add comment
</button> </button>
{pluginContextMenu.map(contextMenuEntry => (
<button
key={contextMenuEntry.id}
id={contextMenuEntry.id}
className={twMerge(
'cursor-pointer text-xs font-normal',
contextMenuEntry.style,
!contextMenuEntry.enabled ? 'cursor-not-allowed text-greyscale-700' : '',
)}
onClick={handleCallback(contextMenuEntry.callback)}
type="button"
data-testid={contextMenuEntry.id}
>
{contextMenuEntry.name}
</button>
))}
</div> </div>
); );
}; };
/* eslint-disable no-magic-numbers */
import { PLUGINS_MOCK } from '@/models/mocks/pluginsMock';
import { FIRST_ARRAY_ELEMENT } from '@/constants/common';
import { PluginsContextMenu } from '@/services/pluginsManager/pluginContextMenu/pluginsContextMenu';
const plugin = PLUGINS_MOCK[FIRST_ARRAY_ELEMENT];
jest.mock('../../../utils/showToast');
describe('PluginsContextMenu', () => {
beforeEach(() => {
PluginsContextMenu.menuItems = [];
});
afterEach(() => {
PluginsContextMenu.menuItems = [];
});
describe('addMenu', () => {
it('add store context menu', () => {
const callback = jest.fn();
const id = PluginsContextMenu.addMenu(plugin.hash, 'Click me', '', true, callback);
expect(PluginsContextMenu.menuItems).toEqual([
{
hash: plugin.hash,
style: '',
name: 'Click me',
enabled: true,
id,
callback,
},
]);
});
it('update store context menu', () => {
const callback = jest.fn();
const id = PluginsContextMenu.addMenu(plugin.hash, 'Click me', '', true, callback);
PluginsContextMenu.updateMenu(plugin.hash, id, 'New name', '.stop-me', false);
expect(PluginsContextMenu.menuItems).toEqual([
{
hash: plugin.hash,
style: '.stop-me',
name: 'New name',
enabled: false,
id,
callback,
},
]);
});
it('remove from store context menu', () => {
const callback = jest.fn();
const id = PluginsContextMenu.addMenu(plugin.hash, 'Click me', '', true, callback);
PluginsContextMenu.removeMenu(plugin.hash, id);
expect(PluginsContextMenu.menuItems).toEqual([]);
});
});
});
import { PluginsContextMenuType } from '@/services/pluginsManager/pluginContextMenu/pluginsContextMenu.types';
import { v4 as uuidv4 } from 'uuid';
import { ZERO } from '@/constants/common';
export const PluginsContextMenu: PluginsContextMenuType = {
menuItems: [],
addMenu: (hash, name, style, enabled, callback) => {
const uuid = uuidv4();
PluginsContextMenu.menuItems.push({
hash,
callback,
enabled,
name,
style,
id: uuid,
});
return uuid;
},
removeMenu: (hash, id) => {
PluginsContextMenu.menuItems = PluginsContextMenu.menuItems.filter(
item => item.hash !== hash || item.id !== id,
);
},
updateMenu: (hash, id, name, style, enabled) => {
const originalItems = PluginsContextMenu.menuItems.filter(
item => item.hash === hash && item.id === id,
);
if (originalItems.length > ZERO) {
originalItems[ZERO].name = name;
originalItems[ZERO].style = style;
originalItems[ZERO].enabled = enabled;
} else {
throw new Error(`Cannot find menu item with id=${id}`);
}
},
};
import { BioEntity, Reaction } from '@/types/models';
export type ClickCoordinates = {
modelId: number;
x: number;
y: number;
zoom: number;
};
export type PluginContextMenuItemType = {
id: string;
hash: string;
name: string;
style: string;
enabled: boolean;
callback: (coordinates: ClickCoordinates, element: BioEntity | Reaction | undefined) => void;
};
export type PluginsContextMenuType = {
menuItems: PluginContextMenuItemType[];
addMenu: (
hash: string,
name: string,
style: string,
enabled: boolean,
callback: (coordinates: ClickCoordinates, element: BioEntity | Reaction | undefined) => void,
) => string;
removeMenu: (hash: string, id: string) => void;
updateMenu: (hash: string, id: string, name: string, style: string, enabled: boolean) => void;
};
...@@ -8,6 +8,7 @@ import { isPluginHashWithPrefix } from '@/utils/plugins/isPluginHashWithPrefix'; ...@@ -8,6 +8,7 @@ import { isPluginHashWithPrefix } from '@/utils/plugins/isPluginHashWithPrefix';
import { getPluginHashWithoutPrefix } from '@/utils/plugins/getPluginHashWithoutPrefix'; import { getPluginHashWithoutPrefix } from '@/utils/plugins/getPluginHashWithoutPrefix';
import { ONE, ZERO } from '@/constants/common'; import { ONE, ZERO } from '@/constants/common';
import { minervaDefine } from '@/services/pluginsManager/map/minervaDefine'; import { minervaDefine } from '@/services/pluginsManager/map/minervaDefine';
import { PluginsContextMenu } from '@/services/pluginsManager/pluginContextMenu/pluginsContextMenu';
import { bioEntitiesMethods } from './bioEntities'; import { bioEntitiesMethods } from './bioEntities';
import { getModels } from './map/models/getModels'; import { getModels } from './map/models/getModels';
import { openMap } from './map/openMap'; import { openMap } from './map/openMap';
...@@ -56,16 +57,16 @@ export const PluginsManager: PluginsManagerType = { ...@@ -56,16 +57,16 @@ export const PluginsManager: PluginsManagerType = {
pluginsOccurrences: {}, pluginsOccurrences: {},
unloadActivePlugin: hash => { unloadActivePlugin: hash => {
const hashWihtoutPrefix = getPluginHashWithoutPrefix(hash); const hashWithoutPrefix = getPluginHashWithoutPrefix(hash);
PluginsManager.activePlugins[hashWihtoutPrefix] = PluginsManager.activePlugins[hashWithoutPrefix] =
PluginsManager.activePlugins[hashWihtoutPrefix]?.filter(el => el !== hash) || []; PluginsManager.activePlugins[hashWithoutPrefix]?.filter(el => el !== hash) || [];
if ( if (
PluginsManager.activePlugins[hashWihtoutPrefix].length === ZERO && PluginsManager.activePlugins[hashWithoutPrefix].length === ZERO &&
hashWihtoutPrefix in PluginsManager.pluginsOccurrences hashWithoutPrefix in PluginsManager.pluginsOccurrences
) { ) {
PluginsManager.pluginsOccurrences[hashWihtoutPrefix] = ZERO; PluginsManager.pluginsOccurrences[hashWithoutPrefix] = ZERO;
} }
}, },
init() { init() {
...@@ -200,6 +201,11 @@ export const PluginsManager: PluginsManagerType = { ...@@ -200,6 +201,11 @@ export const PluginsManager: PluginsManagerType = {
removeListener: PluginsEventBus.removeListener.bind(this, extendedHash), removeListener: PluginsEventBus.removeListener.bind(this, extendedHash),
removeAllListeners: PluginsEventBus.removeAllListeners.bind(this, extendedHash), removeAllListeners: PluginsEventBus.removeAllListeners.bind(this, extendedHash),
}, },
contextMenu: {
addMenu: PluginsContextMenu.addMenu.bind(this, extendedHash),
updateMenu: PluginsContextMenu.updateMenu.bind(this, extendedHash),
removeMenu: PluginsContextMenu.removeMenu.bind(this, extendedHash),
},
legend: { legend: {
setLegend: setLegend.bind(this, extendedHash), setLegend: setLegend.bind(this, extendedHash),
removeLegend: removeLegend.bind(this, extendedHash), removeLegend: removeLegend.bind(this, extendedHash),
......
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