diff --git a/src/components/FunctionalArea/Modal/LayerFactoryModal/LayerFactoryModal.component.test.tsx b/src/components/FunctionalArea/Modal/LayerFactoryModal/LayerFactoryModal.component.test.tsx index e8b7b95f30da6538a5ab3c25bd779fdbf849c9fa..91d4fb0ea7e37dd16ce001a1b62a61d57c7646c5 100644 --- a/src/components/FunctionalArea/Modal/LayerFactoryModal/LayerFactoryModal.component.test.tsx +++ b/src/components/FunctionalArea/Modal/LayerFactoryModal/LayerFactoryModal.component.test.tsx @@ -15,6 +15,7 @@ import { layerRectsFixture } from '@/models/fixtures/layerRectsFixture'; import { layerOvalsFixture } from '@/models/fixtures/layerOvalsFixture'; import { layerLinesFixture } from '@/models/fixtures/layerLinesFixture'; import { act } from 'react-dom/test-utils'; +import { layerImagesFixture } from '@/models/fixtures/layerImagesFixture'; import { LayerFactoryModal } from './LayerFactoryModal.component'; const mockedAxiosNewClient = mockNetworkNewAPIResponse(); @@ -71,6 +72,9 @@ describe('LayerFactoryModal - component', () => { mockedAxiosNewClient .onGet(apiPath.getLayerLines(0, layersFixture.content[0].id)) .reply(HttpStatusCode.Ok, layerLinesFixture); + mockedAxiosNewClient + .onGet(apiPath.getLayerImages(0, layersFixture.content[0].id)) + .reply(HttpStatusCode.Ok, layerImagesFixture); const { store } = renderComponent(); const nameInput: HTMLInputElement = screen.getByTestId('layer-factory-name'); diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/config/additionalLayers/useOlMapAdditionalLayers.ts b/src/components/Map/MapViewer/MapViewerVector/utils/config/additionalLayers/useOlMapAdditionalLayers.ts index f0dfaa9fc3ccd4d993bfe3d174ad6a4525689842..6b841f53d586921bb3644aa1bd60caf6755dd8d0 100644 --- a/src/components/Map/MapViewer/MapViewerVector/utils/config/additionalLayers/useOlMapAdditionalLayers.ts +++ b/src/components/Map/MapViewer/MapViewerVector/utils/config/additionalLayers/useOlMapAdditionalLayers.ts @@ -54,6 +54,7 @@ export const useOlMapAdditionalLayers = ( rects: layer.rects, ovals: layer.ovals, lines: layer.lines, + images: layer.images, visible: layer.details.visible, layerId: layer.details.id, lineTypes, diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/layer/Layer.test.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/layer/Layer.test.ts index 4fd1b57f0e0bc4800ad6492d5f75ce917b684abf..eeabb89c506ce0c4ffba0a0caebab0a7c1bbea95 100644 --- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/layer/Layer.test.ts +++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/layer/Layer.test.ts @@ -113,6 +113,17 @@ describe('Layer', () => { lineType: 'SOLID', }, ], + images: [ + { + id: 1, + glyph: 1, + x: 1, + y: 1, + width: 1, + height: 1, + z: 1, + }, + ], visible: true, layerId: 23, pointToProjection: jest.fn(point => [point.x, point.y]), diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/layer/Layer.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/layer/Layer.ts index 0b8d6403d8ebc05700fe6e32a8ce71e988000aaf..ef12c427e4fecdb40d9ff5929abd162f15da1c2c 100644 --- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/layer/Layer.ts +++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/layer/Layer.ts @@ -1,5 +1,5 @@ /* eslint-disable no-magic-numbers */ -import { LayerLine, LayerOval, LayerRect, LayerText } from '@/types/models'; +import { LayerImage, LayerLine, LayerOval, LayerRect, LayerText } from '@/types/models'; import { MapInstance } from '@/types/map'; import { UsePointToProjectionResult } from '@/utils/map/usePointToProjection'; import { Feature } from 'ol'; @@ -26,12 +26,14 @@ import { } from '@/components/Map/MapViewer/MapViewerVector/MapViewerVector.constants'; import getScaledElementStyle from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/style/getScaledElementStyle'; import { Stroke } from 'ol/style'; +import Glyph from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/Glyph'; export interface LayerProps { texts: Array<LayerText>; rects: Array<LayerRect>; ovals: Array<LayerOval>; lines: Array<LayerLine>; + images: Array<LayerImage>; visible: boolean; layerId: number; lineTypes: LineTypeDict; @@ -49,6 +51,8 @@ export default class Layer { lines: Array<LayerLine>; + images: Array<LayerImage>; + lineTypes: LineTypeDict; arrowTypes: ArrowTypeDict; @@ -59,6 +63,8 @@ export default class Layer { ovalFeatures: Array<Feature<Polygon>>; + imageFeatures: Array<Feature<Polygon>>; + lineFeatures: Array<Feature<LineString>>; arrowFeatures: Array<Feature<MultiPolygon>>; @@ -80,6 +86,7 @@ export default class Layer { rects, ovals, lines, + images, visible, layerId, lineTypes, @@ -91,6 +98,7 @@ export default class Layer { this.rects = rects; this.ovals = ovals; this.lines = lines; + this.images = images; this.lineTypes = lineTypes; this.arrowTypes = arrowTypes; this.pointToProjection = pointToProjection; @@ -98,6 +106,7 @@ export default class Layer { this.textFeatures = this.getTextsFeatures(); this.rectFeatures = this.getRectsFeatures(); this.ovalFeatures = this.getOvalsFeatures(); + this.imageFeatures = this.getImagesFeatures(); const { linesFeatures, arrowsFeatures } = this.getLinesFeatures(); this.lineFeatures = linesFeatures; this.arrowFeatures = arrowsFeatures; @@ -108,6 +117,7 @@ export default class Layer { ...this.ovalFeatures, ...this.lineFeatures, ...this.arrowFeatures, + ...this.imageFeatures, ], }); this.vectorLayer = new VectorLayer({ @@ -293,6 +303,23 @@ export default class Layer { return { linesFeatures, arrowsFeatures }; }; + private getImagesFeatures = (): Array<Feature<Polygon>> => { + return this.images.map(image => { + const glyph = new Glyph({ + elementId: image.id, + glyphId: image.glyph, + x: image.x, + y: image.y, + width: image.width, + height: image.height, + zIndex: image.z, + pointToProjection: this.pointToProjection, + mapInstance: this.mapInstance, + }); + return glyph.feature; + }); + }; + protected getStyle(feature: FeatureLike, resolution: number): Style | Array<Style> | void { const styles: Array<Style> = []; const maxZoom = this.mapInstance?.getView().get('originalMaxZoom'); diff --git a/src/models/fixtures/layerImagesFixture.ts b/src/models/fixtures/layerImagesFixture.ts new file mode 100644 index 0000000000000000000000000000000000000000..382b5f926d997699015f8f6c7e3aa080428a8abd --- /dev/null +++ b/src/models/fixtures/layerImagesFixture.ts @@ -0,0 +1,10 @@ +import { ZOD_SEED } from '@/constants'; +// eslint-disable-next-line import/no-extraneous-dependencies +import { createFixture } from 'zod-fixture'; +import { pageableSchema } from '@/models/pageableSchema'; +import { layerImageSchema } from '@/models/layerImageSchema'; + +export const layerImagesFixture = createFixture(pageableSchema(layerImageSchema), { + seed: ZOD_SEED, + array: { min: 3, max: 3 }, +}); diff --git a/src/models/layerImageSchema.ts b/src/models/layerImageSchema.ts new file mode 100644 index 0000000000000000000000000000000000000000..61a6df2dcb417cd9f4b5e389aed50345882ab0b3 --- /dev/null +++ b/src/models/layerImageSchema.ts @@ -0,0 +1,11 @@ +import { z } from 'zod'; + +export const layerImageSchema = z.object({ + id: z.number(), + x: z.number(), + y: z.number(), + z: z.number(), + width: z.number(), + height: z.number(), + glyph: z.number(), +}); diff --git a/src/redux/apiPath.ts b/src/redux/apiPath.ts index 760ccb7a5e8dc3391fed144af0cd6977205c3c20..1e9f3a81a520446a85d529b5cde0c6fbe373ddd0 100644 --- a/src/redux/apiPath.ts +++ b/src/redux/apiPath.ts @@ -58,6 +58,8 @@ export const apiPath = { `projects/${PROJECT_ID}/maps/${modelId}/layers/${layerId}/ovals/`, getLayerLines: (modelId: number, layerId: number): string => `projects/${PROJECT_ID}/maps/${modelId}/layers/${layerId}/lines/`, + getLayerImages: (modelId: number, layerId: number): string => + `projects/${PROJECT_ID}/maps/${modelId}/layers/${layerId}/images/`, storeLayer: (modelId: number): string => `projects/${PROJECT_ID}/maps/${modelId}/layers/`, updateLayer: (modelId: number, layerId: number): string => `projects/${PROJECT_ID}/maps/${modelId}/layers/${layerId}`, diff --git a/src/redux/layers/layers.reducers.test.ts b/src/redux/layers/layers.reducers.test.ts index 158ed1b037f696342238bacd298fafe2483a045b..938b7f3e049b2fcfa9099dfdab1a473f8daa4c03 100644 --- a/src/redux/layers/layers.reducers.test.ts +++ b/src/redux/layers/layers.reducers.test.ts @@ -14,6 +14,7 @@ import { layerTextsFixture } from '@/models/fixtures/layerTextsFixture'; import { layerRectsFixture } from '@/models/fixtures/layerRectsFixture'; import { layerOvalsFixture } from '@/models/fixtures/layerOvalsFixture'; import { layerLinesFixture } from '@/models/fixtures/layerLinesFixture'; +import { layerImagesFixture } from '@/models/fixtures/layerImagesFixture'; import { LayersState } from './layers.types'; import layersReducer from './layers.slice'; @@ -47,6 +48,9 @@ describe('layers reducer', () => { mockedAxiosClient .onGet(apiPath.getLayerLines(1, layersFixture.content[0].id)) .reply(HttpStatusCode.Ok, layerLinesFixture); + mockedAxiosClient + .onGet(apiPath.getLayerImages(1, layersFixture.content[0].id)) + .reply(HttpStatusCode.Ok, layerImagesFixture); const { type } = await store.dispatch(getLayersForModel(1)); const { data, loading, error } = store.getState().layers[1]; @@ -61,6 +65,7 @@ describe('layers reducer', () => { rects: layerRectsFixture.content, ovals: layerOvalsFixture.content, lines: layerLinesFixture.content, + images: layerImagesFixture.content, }, ], layersVisibility: { @@ -98,6 +103,9 @@ describe('layers reducer', () => { mockedAxiosClient .onGet(apiPath.getLayerLines(1, layersFixture.content[0].id)) .reply(HttpStatusCode.Ok, layerLinesFixture); + mockedAxiosClient + .onGet(apiPath.getLayerImages(1, layersFixture.content[0].id)) + .reply(HttpStatusCode.Ok, layerImagesFixture); const layersPromise = store.dispatch(getLayersForModel(1)); @@ -116,6 +124,7 @@ describe('layers reducer', () => { rects: layerRectsFixture.content, ovals: layerOvalsFixture.content, lines: layerLinesFixture.content, + images: layerImagesFixture.content, }, ], layersVisibility: { diff --git a/src/redux/layers/layers.thunks.test.ts b/src/redux/layers/layers.thunks.test.ts index c8b9b2bcc44d00428e0b3adaf11150d159774c21..975e8ec9e00604497e3bb8c4fe40d016f4e30976 100644 --- a/src/redux/layers/layers.thunks.test.ts +++ b/src/redux/layers/layers.thunks.test.ts @@ -20,6 +20,7 @@ import { layerRectsFixture } from '@/models/fixtures/layerRectsFixture'; import { layerOvalsFixture } from '@/models/fixtures/layerOvalsFixture'; import { layerLinesFixture } from '@/models/fixtures/layerLinesFixture'; import { layerFixture } from '@/models/fixtures/layerFixture'; +import { layerImagesFixture } from '@/models/fixtures/layerImagesFixture'; import layersReducer from './layers.slice'; const mockedAxiosClient = mockNetworkNewAPIResponse(); @@ -45,6 +46,9 @@ describe('layers thunks', () => { mockedAxiosClient .onGet(apiPath.getLayerLines(1, layersFixture.content[0].id)) .reply(HttpStatusCode.Ok, layerLinesFixture); + mockedAxiosClient + .onGet(apiPath.getLayerImages(1, layersFixture.content[0].id)) + .reply(HttpStatusCode.Ok, layerImagesFixture); const { payload } = await store.dispatch(getLayersForModel(1)); expect(payload).toEqual({ @@ -55,6 +59,7 @@ describe('layers thunks', () => { rects: layerRectsFixture.content, ovals: layerOvalsFixture.content, lines: layerLinesFixture.content, + images: layerImagesFixture.content, }, ], layersVisibility: { diff --git a/src/redux/layers/layers.thunks.ts b/src/redux/layers/layers.thunks.ts index c8ee4fd5abbac494634b2a881df5c5b6c4f2fb4c..1fd5d6d443d08f83993450e9886d513163713ef4 100644 --- a/src/redux/layers/layers.thunks.ts +++ b/src/redux/layers/layers.thunks.ts @@ -18,6 +18,7 @@ import { layerRectSchema } from '@/models/layerRectSchema'; import { pageableSchema } from '@/models/pageableSchema'; import { layerOvalSchema } from '@/models/layerOvalSchema'; import { layerLineSchema } from '@/models/layerLineSchema'; +import { layerImageSchema } from '@/models/layerImageSchema'; export const getLayer = createAsyncThunk< Layer | null, @@ -48,12 +49,14 @@ export const getLayersForModel = createAsyncThunk< } let layers = await Promise.all( data.content.map(async (layer: Layer) => { - const [textsResponse, rectsResponse, ovalsResponse, linesResponse] = await Promise.all([ - axiosInstanceNewAPI.get(apiPath.getLayerTexts(modelId, layer.id)), - axiosInstanceNewAPI.get(apiPath.getLayerRects(modelId, layer.id)), - axiosInstanceNewAPI.get(apiPath.getLayerOvals(modelId, layer.id)), - axiosInstanceNewAPI.get(apiPath.getLayerLines(modelId, layer.id)), - ]); + const [textsResponse, rectsResponse, ovalsResponse, linesResponse, imagesResponse] = + await Promise.all([ + axiosInstanceNewAPI.get(apiPath.getLayerTexts(modelId, layer.id)), + axiosInstanceNewAPI.get(apiPath.getLayerRects(modelId, layer.id)), + axiosInstanceNewAPI.get(apiPath.getLayerOvals(modelId, layer.id)), + axiosInstanceNewAPI.get(apiPath.getLayerLines(modelId, layer.id)), + axiosInstanceNewAPI.get(apiPath.getLayerImages(modelId, layer.id)), + ]); return { details: layer, @@ -61,6 +64,7 @@ export const getLayersForModel = createAsyncThunk< rects: rectsResponse.data.content, ovals: ovalsResponse.data.content, lines: linesResponse.data.content, + images: imagesResponse.data.content, }; }), ); @@ -69,7 +73,8 @@ export const getLayersForModel = createAsyncThunk< z.array(layerTextSchema).safeParse(layer.texts).success && z.array(layerRectSchema).safeParse(layer.rects).success && z.array(layerOvalSchema).safeParse(layer.ovals).success && - z.array(layerLineSchema).safeParse(layer.lines).success + z.array(layerLineSchema).safeParse(layer.lines).success && + z.array(layerImageSchema).safeParse(layer.images).success ); }); const layersVisibility = layers.reduce((acc: { [key: string]: boolean }, layer) => { diff --git a/src/redux/layers/layers.types.ts b/src/redux/layers/layers.types.ts index a5c1b30e1f7f1978e447831649ec8896c3b92eeb..f228310603a20d38ebfd315d2757f2010126adee 100644 --- a/src/redux/layers/layers.types.ts +++ b/src/redux/layers/layers.types.ts @@ -1,5 +1,5 @@ import { KeyedFetchDataState } from '@/types/fetchDataState'; -import { Layer, LayerLine, LayerOval, LayerRect, LayerText } from '@/types/models'; +import { Layer, LayerImage, LayerLine, LayerOval, LayerRect, LayerText } from '@/types/models'; export interface LayerStoreInterface { name: string; @@ -22,6 +22,7 @@ export type LayerState = { rects: LayerRect[]; ovals: LayerOval[]; lines: LayerLine[]; + images: LayerImage[]; }; export type LayerVisibilityState = { diff --git a/src/types/models.ts b/src/types/models.ts index b01270f9516341a00fe5263ef10c89aa9e3bd7c9..6e84987532c71383c962942ab3d95ee476b6a092 100644 --- a/src/types/models.ts +++ b/src/types/models.ts @@ -83,6 +83,7 @@ import { reactionProduct } from '@/models/reactionProduct'; import { operatorSchema } from '@/models/operatorSchema'; import { modificationResiduesSchema } from '@/models/modificationResiduesSchema'; import { segmentSchema } from '@/models/segmentSchema'; +import { layerImageSchema } from '@/models/layerImageSchema'; export type Project = z.infer<typeof projectSchema>; export type OverviewImageView = z.infer<typeof overviewImageView>; @@ -103,6 +104,7 @@ export type LayerText = z.infer<typeof layerTextSchema>; export type LayerRect = z.infer<typeof layerRectSchema>; export type LayerOval = z.infer<typeof layerOvalSchema>; export type LayerLine = z.infer<typeof layerLineSchema>; +export type LayerImage = z.infer<typeof layerImageSchema>; export type Arrow = z.infer<typeof arrowSchema>; const modelElementsSchema = pageableSchema(modelElementSchema); export type ModelElements = z.infer<typeof modelElementsSchema>;