Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • minerva/frontend
1 result
Show changes
Commits on Source (32)
Showing
with 950 additions and 49 deletions
minerva-front (19.0.0~alpha.0) stable; urgency=medium
* Feature: support for matomo (#289)
* Feature: overlays can be organized with groups (#131)
* Feature: allow plugin to not have a panel (#306)
* Feature: allow plugin to add menu option to context menu (#307)
* Feature: allow plugin to access info about opened panel (#309)
......@@ -14,6 +15,13 @@ minerva-front (19.0.0~alpha.0) stable; urgency=medium
-- Piotr Gawron <piotr.gawron@uni.lu> Fri, 18 Oct 2024 13:00:00 +0200
minerva-front (18.1.1) stable; urgency=medium
* Bug fix: styling of notes reset only for a href (#334)
* Bug fix: disable searching for chemicals in projects without disease (#347)
* Bug fix: public overlays were not sorted (#349)
-- Piotr Gawron <piotr.gawron@uni.lu> Tue, 04 Feb 2025 16:00:00 +0200
minerva-front (18.1.0) stable; urgency=medium
* Small improvement: support for links that should be opened immediately
(#342)
......@@ -22,7 +30,6 @@ minerva-front (18.1.0) stable; urgency=medium
* Bug fix: submap download did not download selected map (#337)
* Bug fix: styling of notes contains original styling for links (#344)
-- Piotr Gawron <piotr.gawron@uni.lu> Thu, 30 Jan 2025 15:00:00 +0200
minerva-front (18.0.7) stable; urgency=medium
......
// const root = 'https://minerva-dev.lcsb.uni.lu';
// const root = 'https://scimap.lcsb.uni.lu';
// const root = 'https://imsavar.elixir-luxembourg.org';
// const root = 'https://pdmap.uni.lu';
const root = 'https://lux1.atcomp.pl';
// const root = 'http://localhost:8080';
......
import { render, screen, fireEvent } from '@testing-library/react';
import { StoreType } from '@/redux/store';
import {
InitialStoreState,
getReduxWrapperWithStore,
} from '@/utils/testing/getReduxWrapperWithStore';
import { overlayFixture, overlaysPageFixture } from '@/models/fixtures/overlaysFixture';
import { mockNetworkNewAPIResponse } from '@/utils/mockNetworkResponse';
import { apiPath } from '@/redux/apiPath';
import { HttpStatusCode } from 'axios';
import { DEFAULT_ERROR } from '@/constants/errors';
import { act } from 'react-dom/test-utils';
import { OVERLAYS_INITIAL_STATE_MOCK } from '@/redux/overlays/overlays.mock';
import { showToast } from '@/utils/showToast';
import { overlayGroupFixture } from '@/models/fixtures/overlayGroupsFixture';
import { Modal } from '../Modal.component';
const mockedAxiosNewClient = mockNetworkNewAPIResponse();
jest.mock('../../../../utils/showToast');
const renderComponent = (initialStoreState: InitialStoreState = {}): { store: StoreType } => {
const { Wrapper, store } = getReduxWrapperWithStore(initialStoreState);
return (
render(
<Wrapper>
<Modal />
</Wrapper>,
),
{
store,
}
);
};
const overlayGroup = {
...overlayGroupFixture,
id: 1,
};
describe('EditOverlayModal - component', () => {
beforeEach(() => {
jest.clearAllMocks();
});
it('should render modal with correct data', () => {
renderComponent({
modal: {
isOpen: true,
modalTitle: overlayFixture.name,
modalName: 'edit-overlay-group',
editOverlayState: overlayFixture,
editOverlayGroupState: overlayGroupFixture,
molArtState: {},
overviewImagesState: {},
errorReportState: {},
layerFactoryState: { id: undefined },
layerImageObjectFactoryState: undefined,
layerTextFactoryState: undefined,
},
});
expect(screen.getByLabelText('Name')).toBeVisible();
expect(screen.getByLabelText('Order')).toBeVisible();
expect(screen.getByTestId('overlay-group-name')).toHaveValue(overlayGroupFixture.name);
expect(screen.getByTestId('overlay-group-order')).toHaveValue(overlayGroupFixture.order);
});
it('should handle input change correctly', () => {
renderComponent({
modal: {
isOpen: true,
modalTitle: overlayFixture.name,
modalName: 'edit-overlay-group',
editOverlayState: overlayFixture,
editOverlayGroupState: overlayGroup,
molArtState: {},
overviewImagesState: {},
errorReportState: {},
layerFactoryState: { id: undefined },
layerImageObjectFactoryState: undefined,
layerTextFactoryState: undefined,
},
});
const overlayNameInput: HTMLInputElement = screen.getByTestId('overlay-group-name');
const overlayOrderInput: HTMLInputElement = screen.getByTestId('overlay-group-order');
fireEvent.change(overlayNameInput, { target: { value: 'Test name' } });
fireEvent.change(overlayOrderInput, { target: { value: `11` } });
expect(overlayNameInput.value).toBe('Test name');
expect(overlayOrderInput.value).toBe('11');
});
it('should handle remove user overlay', async () => {
const { store } = renderComponent({
user: {
authenticated: true,
loading: 'succeeded',
error: DEFAULT_ERROR,
login: 'test',
role: 'user',
userData: null,
token: null,
},
modal: {
isOpen: true,
modalTitle: overlayFixture.name,
modalName: 'edit-overlay-group',
editOverlayState: overlayFixture,
editOverlayGroupState: overlayGroup,
molArtState: {},
overviewImagesState: {},
errorReportState: {},
layerFactoryState: { id: undefined },
layerImageObjectFactoryState: undefined,
layerTextFactoryState: undefined,
},
overlays: OVERLAYS_INITIAL_STATE_MOCK,
});
mockedAxiosNewClient
.onDelete(apiPath.removeOverlayGroup(overlayGroup.id))
.reply(HttpStatusCode.Ok, {});
const page = {
...overlaysPageFixture,
data: [],
};
mockedAxiosNewClient.onGet(apiPath.getOverlayGroups()).reply(HttpStatusCode.Ok, page);
const removeButton = screen.getByTestId('remove-button');
expect(removeButton).toBeVisible();
await act(() => {
removeButton.click();
});
const { loading } = store.getState().overlayGroups;
expect(loading).toBe('succeeded');
expect(removeButton).not.toBeVisible();
});
it('should show toast after successful removing user overlay', async () => {
renderComponent({
user: {
authenticated: true,
loading: 'succeeded',
error: DEFAULT_ERROR,
login: 'test',
role: 'user',
userData: null,
token: null,
},
modal: {
isOpen: true,
modalTitle: overlayFixture.name,
modalName: 'edit-overlay-group',
editOverlayState: overlayFixture,
editOverlayGroupState: overlayGroup,
molArtState: {},
overviewImagesState: {},
errorReportState: {},
layerFactoryState: { id: undefined },
layerImageObjectFactoryState: undefined,
layerTextFactoryState: undefined,
},
overlays: OVERLAYS_INITIAL_STATE_MOCK,
});
mockedAxiosNewClient
.onDelete(apiPath.removeOverlayGroup(overlayGroup.id))
.reply(HttpStatusCode.Ok, {});
const removeButton = screen.getByTestId('remove-button');
expect(removeButton).toBeVisible();
await act(() => {
removeButton.click();
});
expect(showToast).toHaveBeenCalledWith({
message: 'User overlay group removed successfully',
type: 'success',
});
});
it('should handle save edited user overlay', async () => {
const { store } = renderComponent({
user: {
authenticated: true,
loading: 'succeeded',
error: DEFAULT_ERROR,
login: 'test',
role: 'user',
userData: null,
token: null,
},
modal: {
isOpen: true,
modalTitle: overlayGroup.name,
modalName: 'edit-overlay-group',
editOverlayState: null,
editOverlayGroupState: overlayGroup,
molArtState: {},
overviewImagesState: {},
errorReportState: {},
layerFactoryState: { id: undefined },
layerImageObjectFactoryState: undefined,
layerTextFactoryState: undefined,
},
overlays: OVERLAYS_INITIAL_STATE_MOCK,
});
mockedAxiosNewClient
.onPut(apiPath.updateOverlay(overlayGroup.id))
.reply(HttpStatusCode.Ok, overlayGroup);
const page = {
...overlaysPageFixture,
data: [overlayGroup],
};
mockedAxiosNewClient.onGet(apiPath.getOverlayGroups()).reply(HttpStatusCode.Ok, page);
const saveButton = screen.getByTestId('save-button');
expect(saveButton).toBeVisible();
await act(() => {
saveButton.click();
});
const { loading } = store.getState().overlayGroups;
expect(loading).toBe('succeeded');
expect(saveButton).not.toBeVisible();
});
it('should show toast after successful editing user overlay', async () => {
renderComponent({
user: {
authenticated: true,
loading: 'succeeded',
error: DEFAULT_ERROR,
login: 'test',
role: 'user',
userData: null,
token: null,
},
modal: {
isOpen: true,
modalTitle: overlayGroup.name,
modalName: 'edit-overlay-group',
editOverlayState: null,
editOverlayGroupState: overlayGroup,
molArtState: {},
overviewImagesState: {},
errorReportState: {},
layerFactoryState: { id: undefined },
layerImageObjectFactoryState: undefined,
layerTextFactoryState: undefined,
},
overlays: OVERLAYS_INITIAL_STATE_MOCK,
});
mockedAxiosNewClient
.onPut(apiPath.updateOverlayGroup(overlayGroup.id))
.reply(HttpStatusCode.Ok, overlayGroup);
const saveButton = screen.getByTestId('save-button');
expect(saveButton).toBeVisible();
await act(() => {
saveButton.click();
});
expect(showToast).toHaveBeenCalledWith({
message: 'User overlay group updated successfully',
type: 'success',
});
});
it('should handle cancel edit user overlay', async () => {
const { store } = renderComponent({
modal: {
isOpen: true,
modalTitle: overlayFixture.name,
modalName: 'edit-overlay-group',
editOverlayState: overlayFixture,
editOverlayGroupState: overlayGroup,
molArtState: {},
overviewImagesState: {},
errorReportState: {},
layerFactoryState: { id: undefined },
layerImageObjectFactoryState: undefined,
layerTextFactoryState: undefined,
},
});
const cancelButton = screen.getByTestId('cancel-button');
expect(cancelButton).toBeVisible();
await act(() => {
cancelButton.click();
});
const { isOpen } = store.getState().modal;
expect(isOpen).toBe(false);
expect(cancelButton).not.toBeVisible();
});
});
import { Button } from '@/shared/Button';
import { Input } from '@/shared/Input';
import React from 'react';
import { useEditOverlayGroup } from './hooks/useEditOverlayGroup';
export const EditOverlayGroupModal = (): React.ReactNode => {
const {
name,
order,
handleCancelEdit,
handleNameChange,
handleOrderChange,
handleRemoveOverlayGroup,
handleSaveEditedOverlayGroup,
} = useEditOverlayGroup();
return (
<div className="w-full border border-t-[#E1E0E6] bg-white p-[24px]">
<form>
<label className="text-sm font-semibold" htmlFor="overlayGroupName">
Name
<Input
type="text"
value={name}
onChange={handleNameChange}
className="mt-2.5 text-sm font-medium"
data-testid="overlay-group-name"
name="overlayGroupName"
id="overlayGroupName"
/>
</label>
<label className="mt-5 block text-sm font-semibold" htmlFor="overlayGroupOrder">
Order
<Input
type="number"
value={order}
onChange={handleOrderChange}
className="mt-2.5 text-sm font-medium"
data-testid="overlay-group-order"
name="overlayGroupOrder"
id="overlayGroupOrder"
/>
</label>
<div className="mt-10 flex items-center justify-between gap-5 text-center">
<Button
type="button"
variantStyles="ghost"
className="flex-1 justify-center"
onClick={handleCancelEdit}
data-testid="cancel-button"
>
Cancel
</Button>
<Button
type="button"
variantStyles="ghost"
className="flex-1 justify-center"
onClick={handleRemoveOverlayGroup}
data-testid="remove-button"
>
Remove
</Button>
<Button
type="button"
className="flex-1 justify-center"
onClick={handleSaveEditedOverlayGroup}
data-testid="save-button"
>
Save
</Button>
</div>
</form>
</div>
);
};
/* eslint-disable no-magic-numbers */
import { DEFAULT_ERROR } from '@/constants/errors';
import { overlayFixture } from '@/models/fixtures/overlaysFixture';
import { getReduxStoreWithActionsListener } from '@/utils/testing/getReduxStoreActionsListener';
import { renderHook } from '@testing-library/react';
import { overlayGroupFixture } from '@/models/fixtures/overlayGroupsFixture';
import { useEditOverlayGroup } from './useEditOverlayGroup';
const overlayGroup = { ...overlayGroupFixture, id: 109 };
describe('useEditOverlayGroup', () => {
it('should handle cancel edit overlay', () => {
const { Wrapper, store } = getReduxStoreWithActionsListener({
user: {
authenticated: true,
loading: 'succeeded',
error: DEFAULT_ERROR,
login: 'test',
role: 'user',
userData: null,
token: null,
},
modal: {
isOpen: true,
modalTitle: overlayFixture.name,
modalName: 'edit-overlay-group',
editOverlayState: overlayFixture,
editOverlayGroupState: overlayGroup,
molArtState: {},
overviewImagesState: {},
errorReportState: {},
layerFactoryState: { id: undefined },
layerImageObjectFactoryState: undefined,
layerTextFactoryState: undefined,
},
});
const {
result: {
current: { handleCancelEdit },
},
} = renderHook(() => useEditOverlayGroup(), {
wrapper: Wrapper,
});
handleCancelEdit();
const actions = store.getActions();
expect(actions[0].type).toBe('modal/closeModal');
});
it('should handle handleRemoveOverlay if proper data is provided', () => {
const { Wrapper, store } = getReduxStoreWithActionsListener({
user: {
authenticated: true,
loading: 'succeeded',
error: DEFAULT_ERROR,
login: 'test',
role: 'user',
userData: null,
token: null,
},
modal: {
isOpen: true,
modalTitle: overlayFixture.name,
modalName: 'edit-overlay-group',
editOverlayState: null,
editOverlayGroupState: overlayGroup,
molArtState: {},
overviewImagesState: {},
errorReportState: {},
layerFactoryState: { id: undefined },
layerImageObjectFactoryState: undefined,
layerTextFactoryState: undefined,
},
});
const {
result: {
current: { handleRemoveOverlayGroup },
},
} = renderHook(() => useEditOverlayGroup(), {
wrapper: Wrapper,
});
handleRemoveOverlayGroup();
const actions = store.getActions();
expect(actions[0].type).toBe('overlayGroups/removeOverlayGroup/pending');
const { overlayGroupId } = actions[0].meta.arg;
expect(overlayGroupId).toBe(overlayGroup.id);
});
it('should not handle handleRemoveOverlay if proper data is not provided', () => {
const { Wrapper, store } = getReduxStoreWithActionsListener({
user: {
authenticated: true,
loading: 'failed',
error: DEFAULT_ERROR,
login: null,
role: 'user',
userData: null,
token: null,
},
modal: {
isOpen: true,
modalTitle: overlayFixture.name,
modalName: 'edit-overlay-group',
editOverlayState: overlayFixture,
editOverlayGroupState: overlayGroupFixture,
molArtState: {},
overviewImagesState: {},
errorReportState: {},
layerFactoryState: { id: undefined },
layerImageObjectFactoryState: undefined,
layerTextFactoryState: undefined,
},
});
const {
result: {
current: { handleRemoveOverlayGroup },
},
} = renderHook(() => useEditOverlayGroup(), {
wrapper: Wrapper,
});
handleRemoveOverlayGroup();
const actions = store.getActions();
expect(actions.length).toBe(0);
});
it('should handle handleSaveEditedOverlay if proper data is provided', () => {
const { Wrapper, store } = getReduxStoreWithActionsListener({
user: {
authenticated: true,
loading: 'succeeded',
error: DEFAULT_ERROR,
login: 'test',
role: 'user',
userData: null,
token: null,
},
modal: {
isOpen: true,
modalTitle: overlayGroup.name,
modalName: 'edit-overlay-group',
editOverlayState: null,
editOverlayGroupState: overlayGroup,
molArtState: {},
overviewImagesState: {},
errorReportState: {},
layerFactoryState: { id: undefined },
layerImageObjectFactoryState: undefined,
layerTextFactoryState: undefined,
},
});
const {
result: {
current: { handleSaveEditedOverlayGroup },
},
} = renderHook(() => useEditOverlayGroup(), {
wrapper: Wrapper,
});
handleSaveEditedOverlayGroup();
const actions = store.getActions();
expect(actions[0].type).toBe('overlayGroups/updateOverlayGroups/pending');
expect(actions[0].meta.arg).toEqual([overlayGroup]);
});
it('should not handle handleSaveEditedOverlay if proper data is not provided', () => {
const { Wrapper, store } = getReduxStoreWithActionsListener({
user: {
authenticated: true,
loading: 'succeeded',
error: DEFAULT_ERROR,
login: null,
role: 'user',
userData: null,
token: null,
},
modal: {
isOpen: true,
modalTitle: overlayFixture.name,
modalName: 'edit-overlay-group',
editOverlayState: overlayFixture,
editOverlayGroupState: overlayGroupFixture,
molArtState: {},
overviewImagesState: {},
errorReportState: {},
layerFactoryState: { id: undefined },
layerImageObjectFactoryState: undefined,
layerTextFactoryState: undefined,
},
});
const {
result: {
current: { handleSaveEditedOverlayGroup },
},
} = renderHook(() => useEditOverlayGroup(), {
wrapper: Wrapper,
});
handleSaveEditedOverlayGroup();
const actions = store.getActions();
expect(actions.length).toBe(0);
});
});
import { useAppDispatch } from '@/redux/hooks/useAppDispatch';
import { useAppSelector } from '@/redux/hooks/useAppSelector';
import { currentEditedOverlayGroupSelector } from '@/redux/modal/modal.selector';
import { closeModal } from '@/redux/modal/modal.slice';
import { loginUserSelector } from '@/redux/user/user.selectors';
import { OverlayGroup } from '@/types/models';
import React, { useState } from 'react';
import { ONE } from '@/constants/common';
import {
getOverlayGroups,
removeOverlayGroup,
updateOverlayGroups,
} from '@/redux/overlayGroup/overlayGroup.thunks';
type UseEditOverlayGroupReturn = {
name: string | undefined;
order: number | undefined;
handleCancelEdit: () => void;
handleRemoveOverlayGroup: () => void;
handleSaveEditedOverlayGroup: () => Promise<void>;
handleNameChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
handleOrderChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
};
type UpdatedOverlayGroup = {
editedOverlayGroup: OverlayGroup;
overlayName: string;
overlayOrder: number;
};
export const useEditOverlayGroup = (): UseEditOverlayGroupReturn => {
const currentEditedOverlayGroup = useAppSelector(currentEditedOverlayGroupSelector);
const login = useAppSelector(loginUserSelector);
const dispatch = useAppDispatch();
const [name, setName] = useState(currentEditedOverlayGroup?.name);
const [order, setOrder] = useState(currentEditedOverlayGroup?.order);
const handleCancelEdit = (): void => {
dispatch(closeModal());
};
const handleNameChange = (e: React.ChangeEvent<HTMLInputElement>): void => {
setName(e.target.value);
};
const handleOrderChange = (e: React.ChangeEvent<HTMLInputElement>): void => {
setOrder(Number(e.target.value));
};
const handleRemoveOverlayGroup = (): void => {
if (!login || !currentEditedOverlayGroup || !currentEditedOverlayGroup.id) return;
dispatch(removeOverlayGroup({ overlayGroupId: currentEditedOverlayGroup.id }));
};
const handleUpdateOverlay = async ({
overlayName,
overlayOrder,
editedOverlayGroup,
}: UpdatedOverlayGroup): Promise<void> => {
await dispatch(
updateOverlayGroups([
{
...editedOverlayGroup,
name: overlayName,
order: overlayOrder,
},
]),
);
};
const refreshOverlayGroups = async (): Promise<void> => {
await dispatch(getOverlayGroups());
};
const handleCloseModal = (): void => {
dispatch(closeModal());
};
const handleSaveEditedOverlayGroup = async (): Promise<void> => {
if (!currentEditedOverlayGroup || !name || !login) return;
await handleUpdateOverlay({
editedOverlayGroup: currentEditedOverlayGroup,
overlayName: name,
overlayOrder: order || ONE,
});
await refreshOverlayGroups();
handleCloseModal();
};
return {
handleCancelEdit,
handleRemoveOverlayGroup,
handleSaveEditedOverlayGroup,
handleNameChange,
handleOrderChange,
name,
order,
};
};
export { EditOverlayGroupModal } from './EditOverlayGroupModal.component';
......@@ -12,6 +12,7 @@ import { DEFAULT_ERROR } from '@/constants/errors';
import { act } from 'react-dom/test-utils';
import { OVERLAYS_INITIAL_STATE_MOCK } from '@/redux/overlays/overlays.mock';
import { showToast } from '@/utils/showToast';
import { overlayGroupFixture } from '@/models/fixtures/overlayGroupsFixture';
import { Modal } from '../Modal.component';
const mockedAxiosClient = mockNetworkResponse();
......@@ -35,6 +36,10 @@ const renderComponent = (initialStoreState: InitialStoreState = {}): { store: St
);
};
const overlay = {
...overlayFixture,
group: null,
};
describe('EditOverlayModal - component', () => {
beforeEach(() => {
jest.clearAllMocks();
......@@ -46,6 +51,7 @@ describe('EditOverlayModal - component', () => {
modalTitle: overlayFixture.name,
modalName: 'edit-overlay',
editOverlayState: overlayFixture,
editOverlayGroupState: overlayGroupFixture,
molArtState: {},
overviewImagesState: {},
errorReportState: {},
......@@ -67,6 +73,7 @@ describe('EditOverlayModal - component', () => {
modalTitle: overlayFixture.name,
modalName: 'edit-overlay',
editOverlayState: overlayFixture,
editOverlayGroupState: overlayGroupFixture,
molArtState: {},
overviewImagesState: {},
errorReportState: {},
......@@ -101,6 +108,7 @@ describe('EditOverlayModal - component', () => {
modalTitle: overlayFixture.name,
modalName: 'edit-overlay',
editOverlayState: overlayFixture,
editOverlayGroupState: overlayGroupFixture,
molArtState: {},
overviewImagesState: {},
errorReportState: {},
......@@ -111,7 +119,7 @@ describe('EditOverlayModal - component', () => {
overlays: OVERLAYS_INITIAL_STATE_MOCK,
});
mockedAxiosClient
.onDelete(apiPath.removeOverlay(overlayFixture.idObject))
.onDelete(apiPath.removeOverlay(overlayFixture.id))
.reply(HttpStatusCode.Ok, {});
const removeButton = screen.getByTestId('remove-button');
......@@ -140,6 +148,7 @@ describe('EditOverlayModal - component', () => {
modalTitle: overlayFixture.name,
modalName: 'edit-overlay',
editOverlayState: overlayFixture,
editOverlayGroupState: overlayGroupFixture,
molArtState: {},
overviewImagesState: {},
errorReportState: {},
......@@ -150,7 +159,7 @@ describe('EditOverlayModal - component', () => {
overlays: OVERLAYS_INITIAL_STATE_MOCK,
});
mockedAxiosClient
.onDelete(apiPath.removeOverlay(overlayFixture.idObject))
.onDelete(apiPath.removeOverlay(overlayFixture.id))
.reply(HttpStatusCode.Ok, {});
const removeButton = screen.getByTestId('remove-button');
......@@ -177,9 +186,10 @@ describe('EditOverlayModal - component', () => {
},
modal: {
isOpen: true,
modalTitle: overlayFixture.name,
modalTitle: overlay.name,
modalName: 'edit-overlay',
editOverlayState: overlayFixture,
editOverlayState: overlay,
editOverlayGroupState: overlayGroupFixture,
molArtState: {},
overviewImagesState: {},
errorReportState: {},
......@@ -189,13 +199,11 @@ describe('EditOverlayModal - component', () => {
},
overlays: OVERLAYS_INITIAL_STATE_MOCK,
});
mockedAxiosClient
.onPatch(apiPath.updateOverlay(overlayFixture.idObject))
.reply(HttpStatusCode.Ok, overlayFixture);
mockedAxiosNewClient.onPut(apiPath.updateOverlay(overlay.id)).reply(HttpStatusCode.Ok, overlay);
const page = {
...overlaysPageFixture,
data: [overlayFixture],
data: [overlay],
};
mockedAxiosNewClient
......@@ -225,9 +233,10 @@ describe('EditOverlayModal - component', () => {
},
modal: {
isOpen: true,
modalTitle: overlayFixture.name,
modalTitle: overlay.name,
modalName: 'edit-overlay',
editOverlayState: overlayFixture,
editOverlayState: overlay,
editOverlayGroupState: overlayGroupFixture,
molArtState: {},
overviewImagesState: {},
errorReportState: {},
......@@ -237,9 +246,7 @@ describe('EditOverlayModal - component', () => {
},
overlays: OVERLAYS_INITIAL_STATE_MOCK,
});
mockedAxiosClient
.onPatch(apiPath.updateOverlay(overlayFixture.idObject))
.reply(HttpStatusCode.Ok, overlayFixture);
mockedAxiosNewClient.onPut(apiPath.updateOverlay(overlay.id)).reply(HttpStatusCode.Ok, overlay);
const saveButton = screen.getByTestId('save-button');
expect(saveButton).toBeVisible();
......@@ -260,6 +267,7 @@ describe('EditOverlayModal - component', () => {
modalTitle: overlayFixture.name,
modalName: 'edit-overlay',
editOverlayState: overlayFixture,
editOverlayGroupState: overlayGroupFixture,
molArtState: {},
overviewImagesState: {},
errorReportState: {},
......
......@@ -2,15 +2,19 @@ import { Button } from '@/shared/Button';
import { Input } from '@/shared/Input';
import { Textarea } from '@/shared/Textarea';
import React from 'react';
import { OverlayGroupSelector } from '@/components/Map/Drawer/OverlaysDrawer/UserOverlayForm/OverlaySelector/OverlayGroupSelector.component';
import { useEditOverlay } from './hooks/useEditOverlay';
export const EditOverlayModal = (): React.ReactNode => {
const {
overlayGroups,
description,
name,
group,
handleCancelEdit,
handleDescriptionChange,
handleNameChange,
handleGroupChange,
handleRemoveOverlay,
handleSaveEditedOverlay,
} = useEditOverlay();
......@@ -42,6 +46,12 @@ export const EditOverlayModal = (): React.ReactNode => {
data-testid="overlay-description"
/>
</label>
<OverlayGroupSelector
value={group}
onChange={handleGroupChange}
items={overlayGroups}
label="Select group"
/>
<div className="mt-10 flex items-center justify-between gap-5 text-center">
<Button
type="button"
......
......@@ -3,6 +3,7 @@ import { DEFAULT_ERROR } from '@/constants/errors';
import { overlayFixture } from '@/models/fixtures/overlaysFixture';
import { getReduxStoreWithActionsListener } from '@/utils/testing/getReduxStoreActionsListener';
import { renderHook } from '@testing-library/react';
import { overlayGroupFixture } from '@/models/fixtures/overlayGroupsFixture';
import { useEditOverlay } from './useEditOverlay';
describe('useEditOverlay', () => {
......@@ -22,6 +23,7 @@ describe('useEditOverlay', () => {
modalTitle: overlayFixture.name,
modalName: 'edit-overlay',
editOverlayState: overlayFixture,
editOverlayGroupState: overlayGroupFixture,
molArtState: {},
overviewImagesState: {},
errorReportState: {},
......@@ -62,6 +64,7 @@ describe('useEditOverlay', () => {
modalTitle: overlayFixture.name,
modalName: 'edit-overlay',
editOverlayState: overlayFixture,
editOverlayGroupState: overlayGroupFixture,
molArtState: {},
overviewImagesState: {},
errorReportState: {},
......@@ -87,7 +90,7 @@ describe('useEditOverlay', () => {
const { overlayId } = actions[0].meta.arg;
expect(overlayId).toBe(overlayFixture.idObject);
expect(overlayId).toBe(overlayFixture.id);
});
it('should not handle handleRemoveOverlay if proper data is not provided', () => {
const { Wrapper, store } = getReduxStoreWithActionsListener({
......@@ -105,6 +108,7 @@ describe('useEditOverlay', () => {
modalTitle: overlayFixture.name,
modalName: 'edit-overlay',
editOverlayState: overlayFixture,
editOverlayGroupState: overlayGroupFixture,
molArtState: {},
overviewImagesState: {},
errorReportState: {},
......@@ -129,6 +133,7 @@ describe('useEditOverlay', () => {
expect(actions.length).toBe(0);
});
it('should handle handleSaveEditedOverlay if proper data is provided', () => {
const overlay = { ...overlayFixture, group: null };
const { Wrapper, store } = getReduxStoreWithActionsListener({
user: {
authenticated: true,
......@@ -141,9 +146,10 @@ describe('useEditOverlay', () => {
},
modal: {
isOpen: true,
modalTitle: overlayFixture.name,
modalTitle: overlay.name,
modalName: 'edit-overlay',
editOverlayState: overlayFixture,
editOverlayState: overlay,
editOverlayGroupState: overlayGroupFixture,
molArtState: {},
overviewImagesState: {},
errorReportState: {},
......@@ -166,7 +172,7 @@ describe('useEditOverlay', () => {
const actions = store.getActions();
expect(actions[0].type).toBe('overlays/updateOverlays/pending');
expect(actions[0].meta.arg).toEqual([overlayFixture]);
expect(actions[0].meta.arg).toEqual([overlay]);
});
it('should not handle handleSaveEditedOverlay if proper data is not provided', () => {
const { Wrapper, store } = getReduxStoreWithActionsListener({
......@@ -184,6 +190,7 @@ describe('useEditOverlay', () => {
modalTitle: overlayFixture.name,
modalName: 'edit-overlay',
editOverlayState: overlayFixture,
editOverlayGroupState: overlayGroupFixture,
molArtState: {},
overviewImagesState: {},
errorReportState: {},
......
......@@ -3,21 +3,28 @@ import { useAppSelector } from '@/redux/hooks/useAppSelector';
import { currentEditedOverlaySelector } from '@/redux/modal/modal.selector';
import { closeModal } from '@/redux/modal/modal.slice';
import {
getAllPublicOverlaysByProjectId,
getAllUserOverlaysByCreator,
removeOverlay,
updateOverlays,
} from '@/redux/overlays/overlays.thunks';
import { loginUserSelector } from '@/redux/user/user.selectors';
import { MapOverlay } from '@/types/models';
import { useState } from 'react';
import { MapOverlay, OverlayGroup } from '@/types/models';
import React, { useState } from 'react';
import { overlayGroupsSelector } from '@/redux/overlayGroup/overlayGroup.selectors';
import { ZERO } from '@/constants/common';
import { PROJECT_ID } from '@/constants';
type UseEditOverlayReturn = {
name: string | undefined;
description: string | undefined;
group: OverlayGroup;
overlayGroups: OverlayGroup[];
handleCancelEdit: () => void;
handleRemoveOverlay: () => void;
handleSaveEditedOverlay: () => Promise<void>;
handleNameChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
handleGroupChange: (value: OverlayGroup) => void;
handleDescriptionChange: (e: React.ChangeEvent<HTMLTextAreaElement>) => void;
};
......@@ -25,14 +32,19 @@ type UpdatedOverlay = {
editedOverlay: MapOverlay;
overlayName: string;
overlayDescription: string;
overlayGroup: OverlayGroup;
};
export const useEditOverlay = (): UseEditOverlayReturn => {
const overlayGroups = useAppSelector(overlayGroupsSelector);
const currentEditedOverlay = useAppSelector(currentEditedOverlaySelector);
const login = useAppSelector(loginUserSelector);
const dispatch = useAppDispatch();
const [name, setName] = useState(currentEditedOverlay?.name);
const [description, setDescription] = useState(currentEditedOverlay?.description);
const [group, setGroup] = useState<OverlayGroup>(
overlayGroups.filter(overlayGroup => overlayGroup.id === currentEditedOverlay?.group)[ZERO],
);
const handleCancelEdit = (): void => {
dispatch(closeModal());
......@@ -45,16 +57,20 @@ export const useEditOverlay = (): UseEditOverlayReturn => {
const handleDescriptionChange = (e: React.ChangeEvent<HTMLTextAreaElement>): void => {
setDescription(e.target.value);
};
const handleGroupChange = (value: OverlayGroup): void => {
setGroup(value);
};
const handleRemoveOverlay = (): void => {
if (!login || !currentEditedOverlay) return;
dispatch(removeOverlay({ overlayId: currentEditedOverlay.idObject }));
dispatch(removeOverlay({ overlayId: currentEditedOverlay.id }));
};
const handleUpdateOverlay = async ({
editedOverlay,
overlayDescription,
overlayName,
overlayGroup,
}: UpdatedOverlay): Promise<void> => {
await dispatch(
updateOverlays([
......@@ -62,6 +78,7 @@ export const useEditOverlay = (): UseEditOverlayReturn => {
...editedOverlay,
name: overlayName,
description: overlayDescription,
group: overlayGroup.id,
},
]),
);
......@@ -81,9 +98,11 @@ export const useEditOverlay = (): UseEditOverlayReturn => {
editedOverlay: currentEditedOverlay,
overlayDescription: description || '',
overlayName: name,
overlayGroup: group,
});
await getUserOverlaysByCreator();
dispatch(getAllPublicOverlaysByProjectId(PROJECT_ID));
handleCloseModal();
};
......@@ -94,7 +113,10 @@ export const useEditOverlay = (): UseEditOverlayReturn => {
handleSaveEditedOverlay,
handleNameChange,
handleDescriptionChange,
handleGroupChange,
name,
description,
group,
overlayGroups,
};
};
......@@ -21,6 +21,7 @@ import { MAP_EDIT_TOOLS_STATE_INITIAL_MOCK } from '@/redux/mapEditTools/mapEditT
import { MAP_EDIT_ACTIONS } from '@/redux/mapEditTools/mapEditTools.constants';
import { Feature } from 'ol';
import Polygon from 'ol/geom/Polygon';
import { overlayGroupFixture } from '@/models/fixtures/overlayGroupsFixture';
import { LayerImageObjectEditFactoryModal } from './LayerImageObjectEditFactoryModal.component';
const mockedAxiosNewClient = mockNetworkNewAPIResponse();
......@@ -52,6 +53,7 @@ const renderComponent = (
modalTitle: overlayFixture.name,
modalName: 'edit-overlay',
editOverlayState: overlayFixture,
editOverlayGroupState: overlayGroupFixture,
molArtState: {},
overviewImagesState: {},
errorReportState: {},
......@@ -81,7 +83,13 @@ const renderComponent = (
describe('LayerImageObjectEditFactoryModal - component', () => {
it('should render LayerImageObjectEditFactoryModal component with initial state', () => {
renderComponent();
renderComponent({
activeAction: null,
layerObject: {
...layerImageFixture,
glyph: null,
},
});
expect(screen.getByText(/Glyph:/i)).toBeInTheDocument();
expect(screen.getByText(/File:/i)).toBeInTheDocument();
......@@ -90,7 +98,13 @@ describe('LayerImageObjectEditFactoryModal - component', () => {
});
it('should display a list of glyphs in the dropdown', async () => {
renderComponent();
renderComponent({
activeAction: null,
layerObject: {
...layerImageFixture,
glyph: null,
},
});
const dropdown = screen.getByTestId('autocomplete');
if (!dropdown.firstChild) {
......@@ -102,7 +116,13 @@ describe('LayerImageObjectEditFactoryModal - component', () => {
});
it('should update the selected glyph on dropdown change', async () => {
renderComponent();
renderComponent({
activeAction: null,
layerObject: {
...layerImageFixture,
glyph: null,
},
});
const dropdown = screen.getByTestId('autocomplete');
if (!dropdown.firstChild) {
......@@ -142,13 +162,13 @@ describe('LayerImageObjectEditFactoryModal - component', () => {
};
const getGlyphDataMock = jest.fn(() => glyphData);
jest.spyOn(layerObjectFeature, 'get').mockImplementation(key => {
if (key === 'update') return (): void => {};
if (key === 'getGlyphData') return getGlyphDataMock;
if (key === 'updateElement') return (): void => {};
if (key === 'getObjectData') return getGlyphDataMock;
return undefined;
});
renderComponent({
activeAction: MAP_EDIT_ACTIONS.TRANSFORM_IMAGE,
layerImageObject: glyphData,
layerObject: glyphData,
});
const submitButton = screen.getByText(/Submit/i);
......@@ -164,7 +184,13 @@ describe('LayerImageObjectEditFactoryModal - component', () => {
});
it('should display "No Image" when there is no image file', () => {
const { store } = renderComponent();
const { store } = renderComponent({
activeAction: null,
layerObject: {
...layerImageFixture,
glyph: null,
},
});
store.dispatch({
type: 'glyphs/clearGlyphData',
......
......@@ -2,7 +2,7 @@
import React, { useState } from 'react';
import { useAppSelector } from '@/redux/hooks/useAppSelector';
import { mapEditToolsLayerImageObjectSelector } from '@/redux/mapEditTools/mapEditTools.selectors';
import { mapEditToolsLayerObjectSelector } from '@/redux/mapEditTools/mapEditTools.selectors';
import { LayerImageObjectForm } from '@/components/FunctionalArea/Modal/LayerImageObjectModal/LayerImageObjectForm.component';
import { currentModelIdSelector } from '@/redux/models/models.selectors';
import { addGlyph } from '@/redux/glyphs/glyphs.thunks';
......@@ -13,24 +13,25 @@ import { showToast } from '@/utils/showToast';
import { closeModal } from '@/redux/modal/modal.slice';
import { SerializedError } from '@reduxjs/toolkit';
import { useMapInstance } from '@/utils/context/mapInstanceContext';
import updateGlyph from '@/components/Map/MapViewer/utils/shapes/elements/Glyph/updateGlyph';
import updateElement from '@/components/Map/MapViewer/utils/shapes/layer/utils/updateElement';
import { mapEditToolsSetLayerObject } from '@/redux/mapEditTools/mapEditTools.slice';
export const LayerImageObjectEditFactoryModal: React.FC = () => {
const layerImageObject = useAppSelector(mapEditToolsLayerImageObjectSelector);
const layerObject = useAppSelector(mapEditToolsLayerObjectSelector);
const { mapInstance } = useMapInstance();
if (!layerObject || !('glyph' in layerObject)) {
throw new Error('Invalid layer image object');
}
const currentModelId = useAppSelector(currentModelIdSelector);
const dispatch = useAppDispatch();
const [selectedGlyph, setSelectedGlyph] = useState<number | null>(
layerImageObject?.glyph || null,
);
const [selectedGlyph, setSelectedGlyph] = useState<number | null>(layerObject?.glyph || null);
const [file, setFile] = useState<File | null>(null);
const [isSending, setIsSending] = useState<boolean>(false);
const handleSubmit = async (): Promise<void> => {
if (!layerImageObject) {
if (!layerObject) {
return;
}
setIsSending(true);
......@@ -47,8 +48,8 @@ export const LayerImageObjectEditFactoryModal: React.FC = () => {
const layerImage = await dispatch(
updateLayerImageObject({
modelId: currentModelId,
layerId: layerImageObject.layer,
...layerImageObject,
layerId: layerObject.layer,
...layerObject,
glyph: glyphId,
}),
).unwrap();
......@@ -57,7 +58,7 @@ export const LayerImageObjectEditFactoryModal: React.FC = () => {
layerUpdateImage({ modelId: currentModelId, layerId: layerImage.layer, layerImage }),
);
dispatch(mapEditToolsSetLayerObject(layerImage));
updateGlyph(mapInstance, layerImage.layer, layerImage);
updateElement(mapInstance, layerImage.layer, layerImage);
}
showToast({
type: 'success',
......@@ -68,7 +69,7 @@ export const LayerImageObjectEditFactoryModal: React.FC = () => {
const typedError = error as SerializedError;
showToast({
type: 'error',
message: typedError.message || 'An error occurred while adding a new image',
message: typedError.message || 'An error occurred while editing the layer image',
});
} finally {
setIsSending(false);
......
......@@ -16,6 +16,7 @@ import {
import { MODELS_DATA_MOCK_WITH_MAIN_MAP } from '@/redux/models/models.mock';
import { overlayFixture } from '@/models/fixtures/overlaysFixture';
import { showToast } from '@/utils/showToast';
import { overlayGroupFixture } from '@/models/fixtures/overlayGroupsFixture';
import { LayerImageObjectFactoryModal } from './LayerImageObjectFactoryModal.component';
const mockedAxiosNewClient = mockNetworkNewAPIResponse();
......@@ -46,6 +47,7 @@ const renderComponent = (): { store: StoreType } => {
modalTitle: overlayFixture.name,
modalName: 'edit-overlay',
editOverlayState: overlayFixture,
editOverlayGroupState: overlayGroupFixture,
molArtState: {},
overviewImagesState: {},
errorReportState: {},
......
/* eslint-disable no-magic-numbers */
import React, { useState } from 'react';
import './LayerTextFactoryModal.styles.css';
import { LayerTextForm } from '@/components/FunctionalArea/Modal/LayerTextFactoryModal/LayerTextForm.component';
import { LoadingIndicator } from '@/shared/LoadingIndicator';
import { Button } from '@/shared/Button';
import { LayerTextFactoryForm } from '@/components/FunctionalArea/Modal/LayerTextFactoryModal/LayerTextFactory.types';
import { Color } from '@/types/models';
import { useAppSelector } from '@/redux/hooks/useAppSelector';
import { useAppDispatch } from '@/redux/hooks/useAppDispatch';
import { currentModelIdSelector } from '@/redux/models/models.selectors';
import { showToast } from '@/utils/showToast';
import { closeModal } from '@/redux/modal/modal.slice';
import { SerializedError } from '@reduxjs/toolkit';
import { updateLayerText } from '@/redux/layers/layers.thunks';
import { layerUpdateText } from '@/redux/layers/layers.slice';
import { useMapInstance } from '@/utils/context/mapInstanceContext';
import { mapEditToolsSetLayerObject } from '@/redux/mapEditTools/mapEditTools.slice';
import { mapEditToolsLayerObjectSelector } from '@/redux/mapEditTools/mapEditTools.selectors';
import updateElement from '@/components/Map/MapViewer/utils/shapes/layer/utils/updateElement';
export const LayerTextEditFactoryModal: React.FC = () => {
const layerObject = useAppSelector(mapEditToolsLayerObjectSelector);
const currentModelId = useAppSelector(currentModelIdSelector);
const dispatch = useAppDispatch();
const { mapInstance } = useMapInstance();
if (!layerObject || !('notes' in layerObject)) {
throw new Error('Invalid layer text object');
}
const [isSending, setIsSending] = useState<boolean>(false);
const [data, setData] = useState<LayerTextFactoryForm>({
notes: layerObject.notes,
fontSize: layerObject.fontSize,
horizontalAlign: layerObject.horizontalAlign,
verticalAlign: layerObject.verticalAlign,
color: layerObject.color,
borderColor: layerObject.borderColor,
});
const handleSubmit = async (): Promise<void> => {
if (!layerObject) {
return;
}
try {
const layerText = await dispatch(
updateLayerText({
modelId: currentModelId,
layerId: layerObject.layer,
id: layerObject.id,
x: layerObject.x,
y: layerObject.y,
z: layerObject.z,
width: layerObject.width,
height: layerObject.height,
notes: data.notes,
fontSize: data.fontSize,
horizontalAlign: data.horizontalAlign,
verticalAlign: data.verticalAlign,
color: data.color,
borderColor: data.borderColor,
}),
).unwrap();
if (layerText) {
dispatch(layerUpdateText({ modelId: currentModelId, layerId: layerText.layer, layerText }));
dispatch(mapEditToolsSetLayerObject(layerText));
updateElement(mapInstance, layerText.layer, layerText);
}
showToast({
type: 'success',
message: 'The text has been successfully updated',
});
dispatch(closeModal());
} catch (error) {
const typedError = error as SerializedError;
showToast({
type: 'error',
message: typedError.message || 'An error occurred while editing the layer text',
});
} finally {
setIsSending(false);
}
};
const changeValues = (value: string | number | Color, key: string): void => {
setData(prevData => ({ ...prevData, [key]: value }));
};
return (
<div className="relative w-[900px] border border-t-[#E1E0E6] bg-white p-[24px]">
{isSending && (
<div className="c-layer-text-factory-modal-loader">
<LoadingIndicator width={44} height={44} />
</div>
)}
<LayerTextForm onChange={changeValues} data={data} />
<hr className="py-2" />
<Button
type="button"
onClick={handleSubmit}
className="justify-center self-end justify-self-end text-base font-medium"
>
Submit
</Button>
</div>
);
};
/* eslint-disable no-magic-numbers */
import { HorizontalAlign, VerticalAlign } from '@/components/Map/MapViewer/MapViewer.types';
export const TEXT_FONT_SIZES = [8, 9, 10, 11, 12, 14, 16, 18, 24, 30, 36, 48, 60, 72, 96];
export const TEXT_HORIZONTAL_ALIGNMENTS = [
{ id: 'LEFT', name: 'left' },
{ id: 'RIGHT', name: 'right' },
{ id: 'CENTER', name: 'center' },
{ id: 'END', name: 'end' },
{ id: 'START', name: 'start' },
];
] as const;
export const TEXT_VERTICAL_ALIGNMENTS = [
{ id: 'TOP', name: 'top' },
{ id: 'MIDDLE', name: 'middle' },
{ id: 'BOTTOM', name: 'bottom' },
];
] as const;
export const DEFAULT_TEXT_FONT_SIZE = 12;
export const DEFAULT_HORIZONTAL_ALIGNMENT = TEXT_HORIZONTAL_ALIGNMENTS[0].id;
export const DEFAULT_VERTICAL_ALIGNMENT = TEXT_VERTICAL_ALIGNMENTS[0].id;
export const DEFAULT_HORIZONTAL_ALIGNMENT: HorizontalAlign = TEXT_HORIZONTAL_ALIGNMENTS[0].id;
export const DEFAULT_VERTICAL_ALIGNMENT: VerticalAlign = TEXT_VERTICAL_ALIGNMENTS[0].id;
import { Color } from '@/types/models';
import { HorizontalAlign, VerticalAlign } from '@/components/Map/MapViewer/MapViewer.types';
export type LayerTextFactoryForm = {
notes: string;
fontSize: number;
horizontalAlign: string;
verticalAlign: string;
horizontalAlign: HorizontalAlign;
verticalAlign: VerticalAlign;
color: Color;
borderColor: Color;
};
......@@ -21,6 +21,7 @@ import {
TEXT_VERTICAL_ALIGNMENTS,
} from '@/components/FunctionalArea/Modal/LayerTextFactoryModal/LayerTextFactory.constants';
import { layerTextFixture } from '@/models/fixtures/layerTextFixture';
import { overlayGroupFixture } from '@/models/fixtures/overlayGroupsFixture';
import { LayerTextFactoryModal } from './LayerTextFactoryModal.component';
const mockedAxiosNewClient = mockNetworkNewAPIResponse();
......@@ -51,6 +52,7 @@ const renderComponent = (): { store: StoreType } => {
modalTitle: overlayFixture.name,
modalName: 'edit-overlay',
editOverlayState: overlayFixture,
editOverlayGroupState: overlayGroupFixture,
molArtState: {},
overviewImagesState: {},
errorReportState: {},
......
......@@ -41,7 +41,7 @@ export const LayerTextForm = ({ data, onChange }: LayerTextFormProps): React.JSX
<div>
<span>Horizontal alignment:</span>
<Select
options={TEXT_HORIZONTAL_ALIGNMENTS}
options={[...TEXT_HORIZONTAL_ALIGNMENTS]}
selectedId={data.horizontalAlign}
testId="horizontal-alignment-select"
onChange={value => onChange(value, 'horizontalAlign')}
......@@ -50,7 +50,7 @@ export const LayerTextForm = ({ data, onChange }: LayerTextFormProps): React.JSX
<div>
<span>Vertical alignment:</span>
<Select
options={TEXT_VERTICAL_ALIGNMENTS}
options={[...TEXT_VERTICAL_ALIGNMENTS]}
selectedId={data.verticalAlign}
testId="vertical-alignment-select"
onChange={value => onChange(value, 'verticalAlign')}
......
......@@ -10,6 +10,8 @@ import {
LayerImageObjectFactoryModal,
} from '@/components/FunctionalArea/Modal/LayerImageObjectModal';
import { LayerTextFactoryModal } from '@/components/FunctionalArea/Modal/LayerTextFactoryModal/LayerTextFactoryModal.component';
import { EditOverlayGroupModal } from '@/components/FunctionalArea/Modal/EditOverlayGroupModal';
import { LayerTextEditFactoryModal } from '@/components/FunctionalArea/Modal/LayerTextFactoryModal/LayerTextEditFactoryModal.component';
import { EditOverlayModal } from './EditOverlayModal';
import { LoginModal } from './LoginModal';
import { ErrorReportModal } from './ErrorReportModal';
......@@ -60,6 +62,11 @@ export const Modal = (): React.ReactNode => {
<EditOverlayModal />
</ModalLayout>
)}
{isOpen && modalName === 'edit-overlay-group' && (
<ModalLayout>
<EditOverlayGroupModal />
</ModalLayout>
)}
{isOpen && modalName === 'logged-in-menu' && (
<ModalLayout>
<LoggedInMenuModal />
......@@ -105,6 +112,11 @@ export const Modal = (): React.ReactNode => {
<LayerTextFactoryModal />
</ModalLayout>
)}
{isOpen && modalName === 'layer-text-edit-factory' && (
<ModalLayout>
<LayerTextEditFactoryModal />
</ModalLayout>
)}
</>
);
};