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 (5)
Showing
with 728 additions and 183 deletions
......@@ -81,7 +81,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 +96,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 +114,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 +160,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 +182,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',
......
import { JSX } from 'react';
import React, { JSX, useMemo } from 'react';
import { LayerText } from '@/types/models';
import { Icon } from '@/shared/Icon';
import { LayersDrawerObjectActions } from '@/components/Map/Drawer/LayersDrawer/LayersDrawerObjectActions.component';
import { useAppSelector } from '@/redux/hooks/useAppSelector';
import { mapEditToolsLayerObjectSelector } from '@/redux/mapEditTools/mapEditTools.selectors';
import { useAppDispatch } from '@/redux/hooks/useAppDispatch';
import { mapEditToolsSetLayerObject } from '@/redux/mapEditTools/mapEditTools.slice';
import { hasPrivilegeToWriteProjectSelector } from '@/redux/user/user.selectors';
interface LayersDrawerTextItemProps {
layerText: LayerText;
bringToFront: () => void;
bringToBack: () => void;
removeObject: () => void;
centerObject: () => void;
editObject: () => void;
isLayerVisible: boolean;
isLayerActive: boolean;
}
export const LayersDrawerTextItem = ({
layerText,
bringToFront,
bringToBack,
removeObject,
centerObject,
editObject,
isLayerVisible,
isLayerActive,
}: LayersDrawerTextItemProps): JSX.Element | null => {
const dispatch = useAppDispatch();
const activeLayerObject = useAppSelector(mapEditToolsLayerObjectSelector);
const hasPrivilegeToWriteProject = useAppSelector(hasPrivilegeToWriteProjectSelector);
const showActions = useMemo(() => {
return activeLayerObject?.id === layerText.id;
}, [activeLayerObject?.id, layerText.id]);
const canSelectItem = useMemo(() => {
return isLayerVisible && isLayerActive && hasPrivilegeToWriteProject;
}, [isLayerVisible, isLayerActive, hasPrivilegeToWriteProject]);
const selectItem = useMemo(() => {
return (): void => {
if (canSelectItem) {
dispatch(mapEditToolsSetLayerObject(layerText));
}
};
}, [canSelectItem, dispatch, layerText]);
const handleKeyPress = (): void => {};
return (
<div className="flex min-h-[24px] gap-2">
<Icon name="text" className="shrink-0" />
<span className="truncate">{layerText.notes}</span>
<div
className="flex min-h-[24px] items-center justify-between gap-2"
id={`layer-text-item-${layerText.id}`}
>
<div
className={`flex gap-2 ${canSelectItem ? 'cursor-pointer' : 'cursor-default'}`}
onClick={selectItem}
tabIndex={0}
onKeyDown={handleKeyPress}
role="button"
>
<Icon name="text" className="shrink-0" />
<span className="truncate">{layerText.notes}</span>
</div>
{showActions && (
<LayersDrawerObjectActions
bringToFront={bringToFront}
bringToBack={bringToBack}
removeObject={removeObject}
centerObject={centerObject}
editObject={editObject}
/>
)}
</div>
);
};
......@@ -8,14 +8,14 @@ import { JSX, useEffect, useRef } from 'react';
import { openLayerFactoryModal } from '@/redux/modal/modal.slice';
import { hasPrivilegeToWriteProjectSelector } from '@/redux/user/user.selectors';
import { LayersDrawerLayer } from '@/components/Map/Drawer/LayersDrawer/LayersDrawerLayer.component';
import { mapEditToolsLayerImageObjectSelector } from '@/redux/mapEditTools/mapEditTools.selectors';
import { mapEditToolsLayerObjectSelector } from '@/redux/mapEditTools/mapEditTools.selectors';
export const LayersDrawer = (): JSX.Element => {
const layersForCurrentModel = useAppSelector(layersForCurrentModelSelector);
const hasPrivilegeToWriteProject = useAppSelector(hasPrivilegeToWriteProjectSelector);
const dispatch = useAppDispatch();
const layersDrawerRef = useRef<HTMLDivElement>(null);
const mapEditToolsLayerImageObject = useAppSelector(mapEditToolsLayerImageObjectSelector);
const mapEditToolsLayerImageObject = useAppSelector(mapEditToolsLayerObjectSelector);
const addNewLayer = (): void => {
dispatch(openLayerFactoryModal());
......
......@@ -4,7 +4,7 @@ import { Icon } from '@/shared/Icon';
import { useAppSelector } from '@/redux/hooks/useAppSelector';
import { glyphFileNameByIdSelector } from '@/redux/glyphs/glyphs.selectors';
import { LayersDrawerObjectActions } from '@/components/Map/Drawer/LayersDrawer/LayersDrawerObjectActions.component';
import { mapEditToolsLayerImageObjectSelector } from '@/redux/mapEditTools/mapEditTools.selectors';
import { mapEditToolsLayerObjectSelector } from '@/redux/mapEditTools/mapEditTools.selectors';
import { mapEditToolsSetLayerObject } from '@/redux/mapEditTools/mapEditTools.slice';
import { useAppDispatch } from '@/redux/hooks/useAppDispatch';
import { hasPrivilegeToWriteProjectSelector } from '@/redux/user/user.selectors';
......@@ -31,7 +31,7 @@ export const LayersDrawerImageItem = ({
isLayerActive,
}: LayersDrawerImageItemProps): JSX.Element | null => {
const dispatch = useAppDispatch();
const activeLayerImage = useAppSelector(mapEditToolsLayerImageObjectSelector);
const activeLayerImage = useAppSelector(mapEditToolsLayerObjectSelector);
const fileName = useAppSelector(state => glyphFileNameByIdSelector(state, layerImage.glyph));
const hasPrivilegeToWriteProject = useAppSelector(hasPrivilegeToWriteProjectSelector);
......
......@@ -9,19 +9,23 @@ import { JSX, useState } from 'react';
import { LayersDrawerImageItem } from '@/components/Map/Drawer/LayersDrawer/LayersDrawerImageItem.component';
import { LayersDrawerTextItem } from '@/components/Map/Drawer/LayersDrawer/LayerDrawerTextItem.component';
import QuestionModal from '@/components/FunctionalArea/Modal/QuestionModal/QustionModal.component';
import { removeLayerImage, updateLayerImageObject } from '@/redux/layers/layers.thunks';
import { layerDeleteImage, layerUpdateImage } from '@/redux/layers/layers.slice';
import {
removeLayerImage,
removeLayerText,
updateLayerImageObject,
} from '@/redux/layers/layers.thunks';
import { layerDeleteImage, layerDeleteText, layerUpdateImage } from '@/redux/layers/layers.slice';
import removeElementFromLayer from '@/components/Map/MapViewer/utils/shapes/elements/removeElementFromLayer';
import { showToast } from '@/utils/showToast';
import { SerializedError } from '@reduxjs/toolkit';
import { LayerImage } from '@/types/models';
import { LayerImage, LayerText } from '@/types/models';
import { useAppDispatch } from '@/redux/hooks/useAppDispatch';
import { useMapInstance } from '@/utils/context/mapInstanceContext';
import { mapModelIdSelector } from '@/redux/map/map.selectors';
import { mapEditToolsSetLayerObject } from '@/redux/mapEditTools/mapEditTools.slice';
import updateGlyph from '@/components/Map/MapViewer/utils/shapes/elements/Glyph/updateGlyph';
import updateElement from '@/components/Map/MapViewer/utils/shapes/layer/utils/updateElement';
import { useSetBounds } from '@/utils/map/useSetBounds';
import { mapEditToolsLayerImageObjectSelector } from '@/redux/mapEditTools/mapEditTools.selectors';
import { mapEditToolsLayerObjectSelector } from '@/redux/mapEditTools/mapEditTools.selectors';
import { usePointToProjection } from '@/utils/map/usePointToProjection';
import { Coordinate } from 'ol/coordinate';
import { openLayerImageObjectEditFactoryModal } from '@/redux/modal/modal.slice';
......@@ -32,6 +36,19 @@ interface LayersDrawerObjectsListProps {
isLayerActive: boolean;
}
const removeObjectConfig = {
image: {
question: 'Are you sure you want to remove the image?',
successMessage: 'The layer image has been successfully removed',
errorMessage: 'An error occurred while removing the layer text',
},
text: {
question: 'Are you sure you want to remove the text?',
successMessage: 'The layer text has been successfully removed',
errorMessage: 'An error occurred while removing the layer text',
},
};
export const LayersDrawerObjectsList = ({
layerId,
isLayerVisible,
......@@ -41,57 +58,81 @@ export const LayersDrawerObjectsList = ({
const highestZIndex = useAppSelector(highestZIndexSelector);
const lowestZIndex = useAppSelector(lowestZIndexSelector);
const layer = useAppSelector(state => layerByIdSelector(state, layerId));
const mapEditToolsLayerImageObject = useAppSelector(mapEditToolsLayerImageObjectSelector);
const [isImageRemoveModalOpen, setIsImageRemoveModalOpen] = useState(false);
const [layerImageToRemove, setLayerImageToRemove] = useState<LayerImage | null>(null);
const mapEditToolsLayerImageObject = useAppSelector(mapEditToolsLayerObjectSelector);
const [removeModalState, setRemoveModalState] = useState<undefined | 'text' | 'image'>(undefined);
const [layerObjectToRemove, setLayerObjectToRemove] = useState<LayerImage | LayerText | null>(
null,
);
const dispatch = useAppDispatch();
const setBounds = useSetBounds();
const pointToProjection = usePointToProjection();
const { mapInstance } = useMapInstance();
const removeImage = (layerImage: LayerImage): void => {
setLayerImageToRemove(layerImage);
setIsImageRemoveModalOpen(true);
const removeObject = (layerObject: LayerImage | LayerText): void => {
setLayerObjectToRemove(layerObject);
if ('glyph' in layerObject) {
setRemoveModalState('image');
} else {
setRemoveModalState('text');
}
};
const rejectImageRemove = (): void => {
setIsImageRemoveModalOpen(false);
const rejectRemove = (): void => {
setRemoveModalState(undefined);
};
const confirmImageRemove = async (): Promise<void> => {
if (!layerImageToRemove) {
const confirmRemove = async (): Promise<void> => {
if (!layerObjectToRemove || !removeModalState) {
return;
}
try {
await dispatch(
removeLayerImage({
modelId: currentModelId,
layerId: layerImageToRemove.layer,
imageId: layerImageToRemove.id,
}),
).unwrap();
dispatch(
layerDeleteImage({
modelId: currentModelId,
layerId: layerImageToRemove.layer,
imageId: layerImageToRemove.id,
}),
);
if (removeModalState === 'text') {
await dispatch(
removeLayerText({
modelId: currentModelId,
layerId: layerObjectToRemove.layer,
textId: layerObjectToRemove.id,
}),
).unwrap();
dispatch(
layerDeleteText({
modelId: currentModelId,
layerId: layerObjectToRemove.layer,
textId: layerObjectToRemove.id,
}),
);
} else {
await dispatch(
removeLayerImage({
modelId: currentModelId,
layerId: layerObjectToRemove.layer,
imageId: layerObjectToRemove.id,
}),
).unwrap();
dispatch(
layerDeleteImage({
modelId: currentModelId,
layerId: layerObjectToRemove.layer,
imageId: layerObjectToRemove.id,
}),
);
}
removeElementFromLayer({
mapInstance,
layerId: layerImageToRemove.layer,
featureId: layerImageToRemove.id,
layerId: layerObjectToRemove.layer,
featureId: layerObjectToRemove.id,
});
showToast({
type: 'success',
message: 'The layer image has been successfully removed',
message: removeObjectConfig[removeModalState].successMessage,
});
setIsImageRemoveModalOpen(false);
setRemoveModalState(undefined);
} catch (error) {
const typedError = error as SerializedError;
showToast({
type: 'error',
message: typedError.message || 'An error occurred while removing the layer image',
message: typedError.message || removeObjectConfig[removeModalState].errorMessage,
});
}
};
......@@ -120,7 +161,7 @@ export const LayersDrawerObjectsList = ({
}),
);
dispatch(mapEditToolsSetLayerObject(newLayerImage));
updateGlyph(mapInstance, newLayerImage.layer, newLayerImage);
updateElement(mapInstance, newLayerImage.layer, newLayerImage);
}
};
......@@ -132,12 +173,12 @@ export const LayersDrawerObjectsList = ({
await updateImageZIndex({ zIndex: lowestZIndex - 1, layerImage });
};
const centerObject = (layerImage: LayerImage): void => {
if (mapEditToolsLayerImageObject && mapEditToolsLayerImageObject.id === layerImage.id) {
const point1 = pointToProjection({ x: layerImage.x, y: layerImage.y });
const centerObject = (layerObject: LayerImage | LayerText): void => {
if (mapEditToolsLayerImageObject && mapEditToolsLayerImageObject.id === layerObject.id) {
const point1 = pointToProjection({ x: layerObject.x, y: layerObject.y });
const point2 = pointToProjection({
x: layerImage.x + layerImage.width,
y: layerImage.y + layerImage.height,
x: layerObject.x + layerObject.width,
y: layerObject.y + layerObject.height,
});
setBounds([point1, point2] as Coordinate[]);
}
......@@ -154,13 +195,27 @@ export const LayersDrawerObjectsList = ({
return (
<div className={`${isLayerVisible ? 'opacity-100' : 'opacity-40'} flex flex-col gap-1 ps-3`}>
<QuestionModal
isOpen={isImageRemoveModalOpen}
onClose={rejectImageRemove}
onConfirm={confirmImageRemove}
question="Are you sure you want to remove the image?"
isOpen={Boolean(removeModalState)}
onClose={rejectRemove}
onConfirm={confirmRemove}
question={
removeModalState
? removeObjectConfig[removeModalState]?.question
: 'Are you sure you want to remove the object'
}
/>
{Object.values(layer.texts).map(layerText => (
<LayersDrawerTextItem layerText={layerText} key={layerText.id} />
<LayersDrawerTextItem
layerText={layerText}
key={layerText.id}
bringToFront={() => {}}
bringToBack={() => {}}
removeObject={() => removeObject(layerText)}
centerObject={() => centerObject(layerText)}
editObject={() => {}}
isLayerVisible={isLayerVisible}
isLayerActive={isLayerActive}
/>
))}
{Object.values(layer.images).map(layerImage => (
<LayersDrawerImageItem
......@@ -168,7 +223,7 @@ export const LayersDrawerObjectsList = ({
key={layerImage.id}
bringToFront={() => bringImageToFront(layerImage)}
bringToBack={() => bringImageToBack(layerImage)}
removeObject={() => removeImage(layerImage)}
removeObject={() => removeObject(layerImage)}
centerObject={() => centerObject(layerImage)}
editObject={() => editImage()}
isLayerVisible={isLayerVisible}
......
......@@ -3,8 +3,8 @@ import { Drawer } from '@/components/Map/Drawer';
import { Legend } from '@/components/Map/Legend';
import { MapViewer } from '@/components/Map/MapViewer';
import { MapLoader } from '@/components/Map/MapLoader/MapLoader.component';
import { MapVectorBackgroundSelector } from '@/components/Map/MapVectorBackgroundSelector/MapVectorBackgroundSelector.component';
import { MapAdditionalLogos } from '@/components/Map/MapAdditionalLogos';
import { MapBackgroundSwitchComponent } from '@/components/Map/MapBackgroundSwitch/MapBackgroundSwitch.component';
import { MapAdditionalActions } from './MapAdditionalActions';
import { PluginsDrawer } from './PluginsDrawer';
......@@ -15,7 +15,7 @@ export const Map = (): JSX.Element => {
data-testid="map-container"
>
<MapViewer />
<MapVectorBackgroundSelector />
<MapBackgroundSwitchComponent />
<Drawer />
<PluginsDrawer />
<Legend />
......
import { useAppSelector } from '@/redux/hooks/useAppSelector';
import { mapBackgroundSelector } from '@/redux/map/map.selectors';
import { twMerge } from 'tailwind-merge';
import { MAP_BACKGROUND_TYPES } from '@/redux/map/map.constants';
import { setMapBackground } from '@/redux/map/map.slice';
import { useAppDispatch } from '@/redux/hooks/useAppDispatch';
import { Select } from '@/shared/Select';
import './MapBackgroundSwitch.styles.css';
import { JSX } from 'react';
export const MapVectorBackgroundSelector = (): JSX.Element => {
export const MapBackgroundSwitchComponent = (): JSX.Element => {
const dispatch = useAppDispatch();
const backgroundId = useAppSelector(mapBackgroundSelector);
......@@ -15,13 +15,19 @@ export const MapVectorBackgroundSelector = (): JSX.Element => {
};
return (
<div className={twMerge('absolute right-6 top-[calc(64px+40px+24px)] z-10 flex')}>
<Select
options={MAP_BACKGROUND_TYPES}
selectedId={backgroundId}
onChange={handleChange}
width={140}
/>
<div role="group" className="c-map-background-switch">
{MAP_BACKGROUND_TYPES.map(background => (
<button
className={`${
background.id === backgroundId && 'c-map-background-switch__button--active'
} c-map-background-switch__button`}
type="button"
key={background.id}
onClick={() => handleChange(background.id)}
>
{background.name}
</button>
))}
</div>
);
};
.c-map-background-switch {
position: absolute;
right: 1.5rem;
top: 128px;
z-index: 10;
display: flex;
}
.c-map-background-switch__button {
width: 80px;
background-color: #ffffff;
padding: 0.5rem;
color: #070130;
outline: 1px solid currentColor;
}
.c-map-background-switch__button:first-child {
border-top-left-radius: 0.125rem;
border-bottom-left-radius: 0.125rem;
}
.c-map-background-switch__button:last-child {
border-top-right-radius: 0.125rem;
border-bottom-right-radius: 0.125rem;
}
.c-map-background-switch__button:hover {
background-color: #eeeeee;
}
.c-map-background-switch__button--active {
background-color: #ebf4ff;
color: #106ad7;
}
.c-map-background-switch__button--active:hover {
background-color: #d4e5fa;
color: #0055bb;
}
import 'ol/ol.css';
import { twMerge } from 'tailwind-merge';
import { useAppSelector } from '@/redux/hooks/useAppSelector';
import { isMapEditToolsActiveSelector } from '@/redux/mapEditTools/mapEditTools.selectors';
import {
isMapEditToolsActiveSelector,
mapEditToolsLayerObjectSelector,
} from '@/redux/mapEditTools/mapEditTools.selectors';
import { useOlMap } from './utils/useOlMap';
import { MAP_VIEWER_ROLE } from './MapViewer.constants';
export const MapViewer = (): JSX.Element => {
const { mapRef } = useOlMap();
const isMapEditToolsActive = useAppSelector(isMapEditToolsActiveSelector);
const layerObject = useAppSelector(mapEditToolsLayerObjectSelector);
return (
<div
......@@ -15,7 +19,7 @@ export const MapViewer = (): JSX.Element => {
role={MAP_VIEWER_ROLE}
className={twMerge(
'absolute left-[88px] top-[104px] h-[calc(100%-104px)] w-[calc(100%-88px)] bg-white',
isMapEditToolsActive ? 'bg-[#e4e2de]' : 'bg-white',
isMapEditToolsActive || layerObject ? 'bg-[#e4e2de]' : 'bg-white',
)}
/>
);
......
......@@ -16,7 +16,7 @@ import {
} from '@/redux/layers/layers.selectors';
import { usePointToProjection } from '@/utils/map/usePointToProjection';
import { MapInstance } from '@/types/map';
import { Geometry, LineString, MultiPolygon, Point } from 'ol/geom';
import { Geometry, LineString, MultiPolygon } from 'ol/geom';
import Polygon from 'ol/geom/Polygon';
import Layer from '@/components/Map/MapViewer/utils/shapes/layer/Layer';
import { arrowTypesSelector, lineTypesSelector } from '@/redux/shapes/shapes.selectors';
......@@ -25,7 +25,7 @@ import { mapDataSizeSelector } from '@/redux/map/map.selectors';
import { LayerState } from '@/redux/layers/layers.types';
import {
mapEditToolsActiveActionSelector,
mapEditToolsLayerImageObjectSelector,
mapEditToolsLayerObjectSelector,
} from '@/redux/mapEditTools/mapEditTools.selectors';
import { MAP_EDIT_ACTIONS } from '@/redux/mapEditTools/mapEditTools.constants';
import getDrawBoundingBoxInteraction from '@/components/Map/MapViewer/utils/shapes/layer/interaction/getDrawBoundingBoxInteraction';
......@@ -38,7 +38,7 @@ import {
mapEditToolsSetActiveAction,
mapEditToolsSetLayerObject,
} from '@/redux/mapEditTools/mapEditTools.slice';
import getTransformImageInteraction from '@/components/Map/MapViewer/utils/shapes/layer/interaction/getTransformImageInteraction';
import getTransformInteraction from '@/components/Map/MapViewer/utils/shapes/layer/interaction/getTransformInteraction';
import { useWebSocketEntityUpdatesContext } from '@/utils/websocket-entity-updates/webSocketEntityUpdatesProvider';
import processMessage from '@/components/Map/MapViewer/utils/websocket/processMessage';
import { hasPrivilegeToWriteProjectSelector } from '@/redux/user/user.selectors';
......@@ -46,9 +46,7 @@ import { hasPrivilegeToWriteProjectSelector } from '@/redux/user/user.selectors'
export const useOlMapAdditionalLayers = (
mapInstance: MapInstance,
): Array<
VectorLayer<
VectorSource<Feature<Point> | Feature<Polygon> | Feature<LineString> | Feature<MultiPolygon>>
>
VectorLayer<VectorSource<Feature<Polygon> | Feature<LineString> | Feature<MultiPolygon>>>
> => {
const activeAction = useAppSelector(mapEditToolsActiveActionSelector);
const dispatch = useAppDispatch();
......@@ -61,7 +59,7 @@ export const useOlMapAdditionalLayers = (
const activeLayers = useAppSelector(layersActiveLayersSelector);
const drawLayer = useAppSelector(layersDrawLayerSelector);
const hasPrivilegeToWriteProject = useAppSelector(hasPrivilegeToWriteProjectSelector);
const mapEditToolsLayerImageObject = useAppSelector(mapEditToolsLayerImageObjectSelector);
const mapEditToolsLayerObject = useAppSelector(mapEditToolsLayerObjectSelector);
const [layersState, setLayersState] = useState<Array<LayerState>>([]);
const [layersLoadingState, setLayersLoadingState] = useState(false);
......@@ -157,21 +155,23 @@ export const useOlMapAdditionalLayers = (
if (!dispatch || !currentModelId || !activeLayers.length) {
return null;
}
const imagesFeatures: Array<Feature<Geometry>> = [];
const features: Array<Feature<Geometry>> = [];
const activeVectorLayers = vectorLayers.filter(layer => activeLayers.includes(layer.get('id')));
activeVectorLayers.forEach(vectorLayer => {
imagesFeatures.push(...vectorLayer.get('imagesFeatures'));
features.push(...vectorLayer.get('imagesFeatures'));
features.push(...vectorLayer.get('textsFeatures'));
});
const imagesFeaturesCollection = new Collection(imagesFeatures);
return getTransformImageInteraction(
const featuresCollection = new Collection(features);
return getTransformInteraction(
dispatch,
mapSize,
currentModelId,
imagesFeaturesCollection,
featuresCollection,
restrictionExtent,
);
}, [dispatch, mapSize, currentModelId, restrictionExtent, activeLayers, vectorLayers]);
const transformRef = useRef(transformInteraction);
useEffect(() => {
transformRef.current = transformInteraction;
}, [transformInteraction]);
......@@ -202,13 +202,13 @@ export const useOlMapAdditionalLayers = (
}
const transformFeatures = transformRef.current.getFeatures();
if (
mapEditToolsLayerImageObject &&
mapEditToolsLayerObject &&
(!transformFeatures.getLength() ||
transformFeatures.item(0).getId() !== mapEditToolsLayerImageObject.id)
transformFeatures.item(0).getId() !== mapEditToolsLayerObject.id)
) {
const layer = vectorLayers.find(vectorLayer => {
const layerId = vectorLayer.get('id');
return layerId === mapEditToolsLayerImageObject.layer;
return layerId === mapEditToolsLayerObject.layer;
});
if (!layer) {
return;
......@@ -217,13 +217,13 @@ export const useOlMapAdditionalLayers = (
if (!source) {
return;
}
const feature = source.getFeatureById(mapEditToolsLayerImageObject.id);
const feature = source.getFeatureById(mapEditToolsLayerObject.id);
if (!feature) {
return;
}
transformRef.current.setSelection(new Collection<Feature>([feature]));
}
}, [mapEditToolsLayerImageObject, vectorLayers]);
}, [mapEditToolsLayerObject, vectorLayers]);
useEffect(() => {
const activeVectorLayers = vectorLayers.filter(layer => {
......
......@@ -17,7 +17,6 @@ import { LayerImage } from '@/types/models';
import getStyle from '@/components/Map/MapViewer/utils/shapes/style/getStyle';
import getFill from '@/components/Map/MapViewer/utils/shapes/style/getFill';
import getScaledElementStyle from '@/components/Map/MapViewer/utils/shapes/style/getScaledElementStyle';
import getBoundingBoxFromExtent from '@/components/Map/MapViewer/utils/shapes/coords/getBoundingBoxFromExtent';
export type GlyphProps = {
elementId: number;
......@@ -154,7 +153,7 @@ export default class Glyph {
this.feature.set('setCoordinates', this.setCoordinates.bind(this));
this.feature.set('refreshPolygon', this.refreshPolygon.bind(this));
this.feature.set('update', this.update.bind(this));
this.feature.set('updateElement', this.updateElement.bind(this));
this.feature.setId(this.elementId);
this.feature.setStyle(this.getStyle.bind(this));
......@@ -186,7 +185,7 @@ export default class Glyph {
this.feature.changed();
}
private update(imageObject: LayerImage): void {
protected updateElement(imageObject: LayerImage): void {
this.elementId = imageObject.id;
this.x = imageObject.x;
this.y = imageObject.y;
......@@ -219,11 +218,6 @@ export default class Glyph {
const geometry = this.polygonStyle.getGeometry();
if (geometry && geometry instanceof Polygon) {
geometry.setCoordinates(coords);
const boundingBox = getBoundingBoxFromExtent(geometry.getExtent(), this.mapSize);
this.x = boundingBox.x;
this.y = boundingBox.y;
this.width = boundingBox.width;
this.height = boundingBox.height;
}
}
......
......@@ -4,12 +4,12 @@ import {
LayerLine,
LayerOval,
LayerRect,
LayerText,
LayerText as LayerTextModel,
} from '@/types/models';
import { MapInstance } from '@/types/map';
import { UsePointToProjectionResult } from '@/utils/map/usePointToProjection';
import { Feature } from 'ol';
import { LineString, MultiPolygon, Point } from 'ol/geom';
import { LineString, MultiPolygon } from 'ol/geom';
import Polygon from 'ol/geom/Polygon';
import VectorSource from 'ol/source/Vector';
import VectorLayer from 'ol/layer/Vector';
......@@ -30,11 +30,11 @@ import getArrowFeature from '@/components/Map/MapViewer/utils/shapes/elements/ge
import getRotation from '@/components/Map/MapViewer/utils/shapes/coords/getRotation';
import getStyle from '@/components/Map/MapViewer/utils/shapes/style/getStyle';
import getEllipseCoords from '@/components/Map/MapViewer/utils/shapes/coords/getEllipseCoords';
import Text from '@/components/Map/MapViewer/utils/shapes/text/Text';
import LayerImage from '@/components/Map/MapViewer/utils/shapes/elements/Glyph/LayerImage';
import LayerText from '@/components/Map/MapViewer/utils/shapes/layer/elements/LayerText';
import LayerImage from '@/components/Map/MapViewer/utils/shapes/layer/elements/LayerImage';
export interface LayerProps {
texts: { [key: string]: LayerText };
texts: { [key: string]: LayerTextModel };
rects: Array<LayerRect>;
ovals: Array<LayerOval>;
lines: Array<LayerLine>;
......@@ -51,7 +51,7 @@ export interface LayerProps {
export default class Layer {
layerId: number;
texts: { [key: string]: LayerText };
texts: { [key: string]: LayerTextModel };
rects: Array<LayerRect>;
......@@ -71,12 +71,10 @@ export default class Layer {
mapSize: MapSize;
vectorSource: VectorSource<
Feature<Point> | Feature<Polygon> | Feature<LineString> | Feature<MultiPolygon>
>;
vectorSource: VectorSource<Feature<Polygon> | Feature<LineString> | Feature<MultiPolygon>>;
vectorLayer: VectorLayer<
VectorSource<Feature<Point> | Feature<Polygon> | Feature<LineString> | Feature<MultiPolygon>>
VectorSource<Feature<Polygon> | Feature<LineString> | Feature<MultiPolygon>>
>;
constructor({
......@@ -107,7 +105,9 @@ export default class Layer {
this.mapSize = mapSize;
this.layerId = layerId;
this.vectorSource.addFeatures(this.getTextsFeatures());
const textsFeatures = this.getTextsFeatures();
this.vectorSource.addFeatures(textsFeatures);
this.vectorSource.addFeatures(this.getRectsFeatures());
this.vectorSource.addFeatures(this.getOvalsFeatures());
const imagesFeatures = this.getImagesFeatures();
......@@ -127,53 +127,39 @@ export default class Layer {
this.vectorLayer.set('id', layerId);
this.vectorLayer.set('imagesFeatures', imagesFeatures);
this.vectorLayer.set('textsFeatures', textsFeatures);
this.vectorLayer.set('drawImage', this.drawImage.bind(this));
this.vectorLayer.set('drawText', this.drawText.bind(this));
}
private getTextsFeatures = (): Array<Feature<Point>> => {
const textObjects = Object.values(this.texts).map(text => {
return new Text({
x: text.x,
y: text.y,
zIndex: text.z,
width: text.width,
height: text.height,
layer: text.layer,
fontColor: text.color,
borderColor: text.borderColor,
fontSize: text.fontSize,
text: text.notes,
verticalAlign: text.verticalAlign as VerticalAlign,
horizontalAlign: text.horizontalAlign as HorizontalAlign,
pointToProjection: this.pointToProjection,
mapInstance: this.mapInstance,
});
});
return textObjects.map(text => text.feature);
private getTextsFeatures = (): Array<Feature<Polygon>> => {
return Object.values(this.texts).map(text => this.getTextFeature(text));
};
private drawText(text: LayerText): void {
private drawText(text: LayerTextModel): void {
const textFeature = this.getTextFeature(text);
this.vectorSource.addFeature(textFeature);
}
private getTextFeature(text: LayerText): Feature<Point> {
const textObject = new Text({
private getTextFeature(text: LayerTextModel): Feature<Polygon> {
const textObject = new LayerText({
elementId: text.id,
x: text.x,
y: text.y,
zIndex: text.z,
width: text.width,
height: text.height,
layer: text.layer,
fontColor: text.color,
color: text.color,
borderColor: text.borderColor,
backgroundColor: text.backgroundColor,
fontSize: text.fontSize,
text: text.notes,
verticalAlign: text.verticalAlign as VerticalAlign,
horizontalAlign: text.horizontalAlign as HorizontalAlign,
pointToProjection: this.pointToProjection,
mapInstance: this.mapInstance,
mapSize: this.mapSize,
});
return textObject.feature;
}
......
......@@ -3,6 +3,11 @@ import { MapInstance } from '@/types/map';
import { MapSize } from '@/redux/map/map.types';
import { LayerImage as LayerImageModel } from '@/types/models';
import Glyph from '@/components/Map/MapViewer/utils/shapes/elements/Glyph/Glyph';
import { store } from '@/redux/store';
import { updateLayerImageObject } from '@/redux/layers/layers.thunks';
import { layerUpdateImage } from '@/redux/layers/layers.slice';
import { mapEditToolsSetLayerObject } from '@/redux/mapEditTools/mapEditTools.slice';
import { BoundingBox } from '@/components/Map/MapViewer/MapViewer.types';
export type LayerImageProps = {
elementId: number;
......@@ -47,11 +52,35 @@ export default class LayerImage extends Glyph {
mapSize,
});
this.layer = layer;
this.feature.set('getGlyphData', this.getGlyphData.bind(this));
this.feature.set('getObjectData', this.getData.bind(this));
this.feature.set('save', this.save.bind(this));
this.feature.set('layer', layer);
}
private getGlyphData(): LayerImageModel {
private async save({
modelId,
boundingBox,
}: {
modelId: number;
boundingBox: BoundingBox;
}): Promise<void> {
const { dispatch } = store;
const layerImage = await dispatch(
updateLayerImageObject({
modelId,
layerId: this.layer,
...this.getData(),
...boundingBox,
}),
).unwrap();
if (layerImage) {
dispatch(layerUpdateImage({ modelId, layerId: layerImage.layer, layerImage }));
dispatch(mapEditToolsSetLayerObject(layerImage));
this.updateElement(layerImage);
}
}
private getData(): LayerImageModel {
return {
id: this.elementId,
x: this.x,
......
......@@ -3,17 +3,20 @@ import { Map } from 'ol';
import { Style } from 'ol/style';
import View from 'ol/View';
import { BLACK_COLOR } from '@/components/Map/MapViewer/MapViewer.constants';
import Text, { TextProps } from '@/components/Map/MapViewer/utils/shapes/text/Text';
import LayerText, {
LayerTextProps,
} from '@/components/Map/MapViewer/utils/shapes/layer/elements/LayerText';
import getTextStyle from '@/components/Map/MapViewer/utils/shapes/text/getTextStyle';
import { rgbToHex } from '@/components/Map/MapViewer/utils/shapes/style/rgbToHex';
import getTextCoords from '@/components/Map/MapViewer/utils/shapes/text/getTextCoords';
import { DEFAULT_TILE_SIZE } from '@/constants/map';
jest.mock('./getTextCoords');
jest.mock('./getTextStyle');
jest.mock('../style/rgbToHex');
jest.mock('../../text/getTextCoords');
jest.mock('../../text/getTextStyle');
jest.mock('../../style/rgbToHex');
describe('Text', () => {
let props: TextProps;
let props: LayerTextProps;
beforeEach(() => {
const dummyElement = document.createElement('div');
......@@ -26,6 +29,7 @@ describe('Text', () => {
}),
});
props = {
elementId: 1,
x: 0,
y: 0,
width: 100,
......@@ -34,12 +38,20 @@ describe('Text', () => {
layer: 1,
text: 'Test',
fontSize: 12,
fontColor: BLACK_COLOR,
color: BLACK_COLOR,
borderColor: BLACK_COLOR,
backgroundColor: BLACK_COLOR,
verticalAlign: 'MIDDLE',
horizontalAlign: 'CENTER',
pointToProjection: jest.fn(({ x, y }) => [x, y]),
mapInstance,
mapSize: {
minZoom: 1,
maxZoom: 9,
width: 0,
height: 0,
tileSize: DEFAULT_TILE_SIZE,
},
};
(getTextStyle as jest.Mock).mockReturnValue(new Style());
......@@ -48,7 +60,7 @@ describe('Text', () => {
});
it('should apply correct styles to the feature', () => {
const text = new Text(props);
const text = new LayerText(props);
const { feature } = text;
const style = feature.getStyleFunction()?.call(text, feature, 1);
......@@ -61,7 +73,7 @@ describe('Text', () => {
});
it('should hide text when the scaled font size is too small', () => {
const text = new Text(props);
const text = new LayerText(props);
const { feature } = text;
const style = feature.getStyleFunction()?.call(text, feature, 20);
......
......@@ -5,9 +5,13 @@ import { Point } from 'ol/geom';
import { Feature } from 'ol';
import { FeatureLike } from 'ol/Feature';
import { MapInstance } from '@/types/map';
import { Color } from '@/types/models';
import { LayerText as LayerTextModel, Color } from '@/types/models';
import { TEXT_CUTOFF_SCALE } from '@/components/Map/MapViewer/MapViewer.constants';
import { HorizontalAlign, VerticalAlign } from '@/components/Map/MapViewer/MapViewer.types';
import {
BoundingBox,
HorizontalAlign,
VerticalAlign,
} from '@/components/Map/MapViewer/MapViewer.types';
import getTextCoords from '@/components/Map/MapViewer/utils/shapes/text/getTextCoords';
import getTextStyle from '@/components/Map/MapViewer/utils/shapes/text/getTextStyle';
import { rgbToHex } from '@/components/Map/MapViewer/utils/shapes/style/rgbToHex';
......@@ -16,8 +20,16 @@ import getStroke from '@/components/Map/MapViewer/utils/shapes/style/getStroke';
import getStyle from '@/components/Map/MapViewer/utils/shapes/style/getStyle';
import getScaledElementStyle from '@/components/Map/MapViewer/utils/shapes/style/getScaledElementStyle';
import { Stroke } from 'ol/style';
import { MapSize } from '@/redux/map/map.types';
import getBoundingBoxFromExtent from '@/components/Map/MapViewer/utils/shapes/coords/getBoundingBoxFromExtent';
import { Coordinate } from 'ol/coordinate';
import { store } from '@/redux/store';
import { updateLayerText } from '@/redux/layers/layers.thunks';
import { layerUpdateText } from '@/redux/layers/layers.slice';
import { mapEditToolsSetLayerObject } from '@/redux/mapEditTools/mapEditTools.slice';
export interface TextProps {
export interface LayerTextProps {
elementId: number;
x: number;
y: number;
width: number;
......@@ -26,30 +38,63 @@ export interface TextProps {
zIndex: number;
text: string;
fontSize: number;
fontColor: Color;
color: Color;
borderColor: Color;
backgroundColor: Color;
verticalAlign: VerticalAlign;
horizontalAlign: HorizontalAlign;
pointToProjection: UsePointToProjectionResult;
mapInstance: MapInstance;
mapSize: MapSize;
}
export default class Text {
export default class LayerText {
elementId: number;
x: number;
y: number;
zIndex: number;
width: number;
height: number;
layer: number;
text: string;
verticalAlign: VerticalAlign;
horizontalAlign: HorizontalAlign;
backgroundColor: Color;
borderColor: Color;
color: Color;
fontSize: number;
style: Style;
polygonStyle: Style;
polygon: Polygon = new Polygon([]);
strokeStyle: Stroke;
point: Point;
feature: Feature<Point>;
feature: Feature<Polygon>;
mapSize: MapSize;
pointToProjection: UsePointToProjectionResult;
constructor({
elementId,
x,
y,
width,
......@@ -58,15 +103,32 @@ export default class Text {
zIndex,
text,
fontSize,
fontColor,
color,
borderColor,
backgroundColor,
verticalAlign,
horizontalAlign,
pointToProjection,
mapInstance,
}: TextProps) {
mapSize,
}: LayerTextProps) {
this.text = text;
this.fontSize = fontSize;
this.elementId = elementId;
this.x = x;
this.y = y;
this.zIndex = zIndex;
this.width = width;
this.height = height;
this.layer = layer;
this.text = text;
this.verticalAlign = verticalAlign;
this.horizontalAlign = horizontalAlign;
this.backgroundColor = backgroundColor;
this.borderColor = borderColor;
this.color = color;
this.mapSize = mapSize;
this.pointToProjection = pointToProjection;
const textCoords = getTextCoords({
x,
......@@ -78,21 +140,13 @@ export default class Text {
horizontalAlign,
pointToProjection,
});
const borderPolygon = new Polygon([
[
pointToProjection({ x, y }),
pointToProjection({ x: x + width, y }),
pointToProjection({ x: x + width, y: y + height }),
pointToProjection({ x, y: y + height }),
pointToProjection({ x, y }),
],
]);
this.drawPolygon();
this.strokeStyle = getStroke({
color: rgbToHex(borderColor),
width: 1,
});
this.polygonStyle = getStyle({
geometry: borderPolygon,
geometry: this.polygon,
borderColor,
fillColor: { rgb: 0, alpha: 0 },
lineWidth: 1,
......@@ -102,7 +156,7 @@ export default class Text {
const textStyle = getTextStyle({
text,
fontSize,
color: rgbToHex(fontColor),
color: rgbToHex(color),
zIndex,
horizontalAlign,
});
......@@ -111,7 +165,7 @@ export default class Text {
this.style.setGeometry(this.point);
this.feature = new Feature({
geometry: this.point,
geometry: this.polygon,
getScale: (resolution: number): number => {
const maxZoom = mapInstance?.getView().get('originalMaxZoom');
if (maxZoom) {
......@@ -124,16 +178,132 @@ export default class Text {
},
layer,
});
this.feature.setId(this.elementId);
this.feature.set('getObjectData', this.getData.bind(this));
this.feature.set('setCoordinates', this.setCoordinates.bind(this));
this.feature.set('refreshPolygon', this.refreshPolygon.bind(this));
this.feature.set('save', this.save.bind(this));
this.feature.set('updateElement', this.updateElement.bind(this));
this.feature.setStyle(this.getStyle.bind(this));
}
private getData(): LayerTextModel {
return {
id: this.elementId,
x: this.x,
y: this.y,
z: this.zIndex,
width: this.width,
height: this.height,
layer: this.layer,
fontSize: this.fontSize,
notes: this.text,
verticalAlign: this.verticalAlign,
horizontalAlign: this.horizontalAlign,
backgroundColor: this.backgroundColor,
borderColor: this.borderColor,
color: this.color,
};
}
private drawPolygon(): void {
this.polygon = new Polygon([
[
this.pointToProjection({ x: this.x, y: this.y }),
this.pointToProjection({ x: this.x + this.width, y: this.y }),
this.pointToProjection({ x: this.x + this.width, y: this.y + this.height }),
this.pointToProjection({ x: this.x, y: this.y + this.height }),
this.pointToProjection({ x: this.x, y: this.y }),
],
]);
}
private async save({
modelId,
boundingBox,
}: {
modelId: number;
boundingBox: BoundingBox;
}): Promise<void> {
const { dispatch } = store;
const layerText = await dispatch(
updateLayerText({
modelId,
layerId: this.layer,
...this.getData(),
...boundingBox,
}),
).unwrap();
if (layerText) {
dispatch(layerUpdateText({ modelId, layerId: layerText.layer, layerText }));
dispatch(mapEditToolsSetLayerObject(layerText));
this.updateElement(layerText);
}
}
private refreshPolygon(): void {
this.drawPolygon();
this.polygonStyle.setGeometry(this.polygon);
this.feature.setGeometry(this.polygon);
this.feature.changed();
}
private refreshZIndex(): void {
this.polygonStyle.setZIndex(this.zIndex);
this.style.setZIndex(this.zIndex);
this.feature.changed();
}
private updateElement(layerText: LayerTextModel): void {
this.elementId = layerText.id;
this.x = layerText.x;
this.y = layerText.y;
this.zIndex = layerText.z;
this.width = layerText.width;
this.height = layerText.height;
this.text = layerText.notes;
this.fontSize = layerText.fontSize;
this.color = layerText.color;
this.borderColor = layerText.borderColor;
this.verticalAlign = layerText.verticalAlign;
this.horizontalAlign = layerText.horizontalAlign;
this.refreshPolygon();
this.refreshZIndex();
this.feature.changed();
}
private setCoordinates(coords: Coordinate[][]): void {
const geometry = this.polygonStyle.getGeometry();
if (geometry && geometry instanceof Polygon) {
geometry.setCoordinates(coords);
}
}
protected getStyle(feature: FeatureLike, resolution: number): Style | Array<Style> | void {
const getScale = feature.get('getScale');
let scale = 1;
if (getScale instanceof Function) {
scale = getScale(resolution);
}
const geometry = feature.getGeometry();
if (geometry && geometry instanceof Polygon) {
const polygonExtent = geometry.getExtent();
if (polygonExtent) {
const boundingBox = getBoundingBoxFromExtent(polygonExtent, this.mapSize);
const textCoords = getTextCoords({
x: boundingBox.x,
y: boundingBox.y,
height: boundingBox.height,
width: boundingBox.width,
fontSize: this.fontSize,
verticalAlign: this.verticalAlign,
horizontalAlign: this.horizontalAlign,
pointToProjection: this.pointToProjection,
});
this.point.setCoordinates(textCoords);
}
}
if (scale < TEXT_CUTOFF_SCALE) {
return undefined;
}
......
......@@ -10,13 +10,13 @@ import { DEFAULT_TILE_SIZE } from '@/constants/map';
import { Collection, Feature } from 'ol';
import Transform from 'ol-ext/interaction/Transform';
import { Geometry } from 'ol/geom';
import getTransformImageInteraction from '@/components/Map/MapViewer/utils/shapes/layer/interaction/getTransformImageInteraction';
import getTransformInteraction from '@/components/Map/MapViewer/utils/shapes/layer/interaction/getTransformInteraction';
jest.mock('../../../../../../../utils/map/latLngToPoint', () => ({
latLngToPoint: jest.fn(latLng => ({ x: latLng[0], y: latLng[1] })),
}));
describe('getTransformImageInteraction', () => {
describe('getTransformInteraction', () => {
let store = {} as ToolkitStoreWithSingleSlice<ModalState>;
let modelIdMock: number;
let featuresCollectionMock: Collection<Feature<Geometry>>;
......@@ -39,7 +39,7 @@ describe('getTransformImageInteraction', () => {
});
it('returns a Transform interaction', () => {
const transformInteraction = getTransformImageInteraction(
const transformInteraction = getTransformInteraction(
store.dispatch,
mapSize,
modelIdMock,
......
......@@ -5,15 +5,13 @@ import Transform from 'ol-ext/interaction/Transform';
import { Geometry } from 'ol/geom';
import { Collection, Feature } from 'ol';
import BaseEvent from 'ol/events/Event';
import { updateLayerImageObject } from '@/redux/layers/layers.thunks';
import { layerUpdateImage } from '@/redux/layers/layers.slice';
import getBoundingBoxFromExtent from '@/components/Map/MapViewer/utils/shapes/coords/getBoundingBoxFromExtent';
import { MapSize } from '@/redux/map/map.types';
import { Extent } from 'ol/extent';
import { mapEditToolsSetLayerObject } from '@/redux/mapEditTools/mapEditTools.slice';
import { openDrawer } from '@/redux/drawer/drawer.slice';
export default function getTransformImageInteraction(
export default function getTransformInteraction(
dispatch: AppDispatch,
mapSize: MapSize,
modelId: number,
......@@ -86,6 +84,11 @@ export default function getTransformImageInteraction(
newGeometry.scale(1, -1);
feature.setGeometry(newGeometry);
transform.setSelection(new Collection([feature]));
return;
}
const setCoordinates = feature.get('setCoordinates');
if (geometry instanceof Polygon && setCoordinates instanceof Function) {
setCoordinates(geometry.getCoordinates());
}
});
......@@ -96,10 +99,10 @@ export default function getTransformImageInteraction(
dispatch(mapEditToolsSetLayerObject(null));
return;
}
const getGlyphData = features.item(0).get('getGlyphData');
if (getGlyphData && getGlyphData instanceof Function) {
const glyphData = getGlyphData();
dispatch(mapEditToolsSetLayerObject(glyphData));
const getObjectData = features.item(0).get('getObjectData');
if (getObjectData && getObjectData instanceof Function) {
const objectData = getObjectData();
dispatch(mapEditToolsSetLayerObject(objectData));
dispatch(openDrawer('layers'));
}
});
......@@ -107,30 +110,13 @@ export default function getTransformImageInteraction(
transform.on(['scaleend', 'translateend'], async (event: BaseEvent | Event): Promise<void> => {
const transformEvent = event as unknown as { feature: Feature };
const { feature } = transformEvent;
const setCoordinates = feature.get('setCoordinates');
const getGlyphData = feature.get('getGlyphData');
const refreshPolygon = feature.get('refreshPolygon');
const save = feature.get('save');
const geometry = feature.getGeometry();
if (geometry && getGlyphData instanceof Function) {
const glyphData = getGlyphData();
if (geometry && save instanceof Function) {
try {
const boundingBox = getBoundingBoxFromExtent(geometry.getExtent(), mapSize);
const layerImage = await dispatch(
updateLayerImageObject({
modelId,
layerId: glyphData.layer,
...glyphData,
...boundingBox,
}),
).unwrap();
if (layerImage) {
dispatch(layerUpdateImage({ modelId, layerId: layerImage.layer, layerImage }));
dispatch(mapEditToolsSetLayerObject(layerImage));
}
if (geometry instanceof Polygon && setCoordinates instanceof Function) {
setCoordinates(geometry.getCoordinates());
geometry.changed();
}
save({ modelId, boundingBox });
} catch {
if (refreshPolygon instanceof Function) {
refreshPolygon();
......
import VectorSource from 'ol/source/Vector';
import { LayerImage } from '@/types/models';
import { LayerImage, LayerText } from '@/types/models';
import { MapInstance } from '@/types/map';
export default function updateGlyph(
export default function updateElement(
mapInstance: MapInstance,
layerId: number,
layerImage: LayerImage,
layerObject: LayerImage | LayerText,
): void {
mapInstance?.getAllLayers().forEach(layer => {
if (layer.get('id') === layerId) {
const source = layer.getSource();
if (source instanceof VectorSource) {
const feature = source.getFeatureById(layerImage.id);
const update = feature?.get('update');
const feature = source.getFeatureById(layerObject.id);
const update = feature?.get('updateElement');
if (update && update instanceof Function) {
update(layerImage);
update(layerObject);
feature.changed();
}
}
......
......@@ -12,6 +12,7 @@ export default function getTextCoords({
verticalAlign,
horizontalAlign,
pointToProjection,
useProjection = true,
}: {
x: number;
y: number;
......@@ -20,7 +21,8 @@ export default function getTextCoords({
fontSize: number;
verticalAlign: VerticalAlign;
horizontalAlign: HorizontalAlign;
pointToProjection: UsePointToProjectionResult;
pointToProjection?: UsePointToProjectionResult;
useProjection?: boolean;
}): Coordinate {
const minX = x;
const maxX = x + width;
......@@ -41,5 +43,8 @@ export default function getTextCoords({
textX = maxX;
}
return pointToProjection({ x: textX, y: textY });
if (useProjection && pointToProjection) {
return pointToProjection({ x: textX, y: textY });
}
return [textX, textY];
}