diff --git a/src/components/FunctionalArea/Modal/LayerFactoryModal/LayerFactoryModal.component.tsx b/src/components/FunctionalArea/Modal/LayerFactoryModal/LayerFactoryModal.component.tsx index 8d925951e04c0ed72650e50001e43f6b416d7038..b806d2b6a77e07a288e39d87c591d5f8ebb1ad43 100644 --- a/src/components/FunctionalArea/Modal/LayerFactoryModal/LayerFactoryModal.component.tsx +++ b/src/components/FunctionalArea/Modal/LayerFactoryModal/LayerFactoryModal.component.tsx @@ -19,11 +19,13 @@ import { SerializedError } from '@reduxjs/toolkit'; import { layerFactoryStateSelector } from '@/redux/modal/modal.selector'; import './LayerFactoryModal.styles.css'; import { LoadingIndicator } from '@/shared/LoadingIndicator'; +import { maxLayerZIndexAboveDiagramSelector } from '@/redux/layers/layers.selectors'; export const LayerFactoryModal: React.FC = () => { const dispatch = useAppDispatch(); const currentModelId = useAppSelector(currentModelIdSelector); const layerFactoryState = useAppSelector(layerFactoryStateSelector); + const maxLayerZIndexAboveDiagram = useAppSelector(maxLayerZIndexAboveDiagramSelector); const [loaded, setLoaded] = useState<boolean>(false); const [data, setData] = useState<LayerStoreInterface>({ @@ -31,6 +33,7 @@ export const LayerFactoryModal: React.FC = () => { visible: false, locked: false, modelId: currentModelId, + zIndex: maxLayerZIndexAboveDiagram, }); const fetchData = useMemo(() => { @@ -42,6 +45,7 @@ export const LayerFactoryModal: React.FC = () => { visible: layer.visible, locked: layer.locked, modelId: currentModelId, + zIndex: layer.z, }); } setLoaded(true); diff --git a/src/components/FunctionalArea/Modal/LayerImageObjectModal/LayerImageObjectFactoryModal.component.tsx b/src/components/FunctionalArea/Modal/LayerImageObjectModal/LayerImageObjectFactoryModal.component.tsx index 1efb7d8874cc8b998f5edf00a05ce2d40084d2d5..4d1513e92c02b78669ff3783d3637b87632dd943 100644 --- a/src/components/FunctionalArea/Modal/LayerImageObjectModal/LayerImageObjectFactoryModal.component.tsx +++ b/src/components/FunctionalArea/Modal/LayerImageObjectModal/LayerImageObjectFactoryModal.component.tsx @@ -4,7 +4,10 @@ import { useAppSelector } from '@/redux/hooks/useAppSelector'; import { layerImageObjectFactoryStateSelector } from '@/redux/modal/modal.selector'; import { useAppDispatch } from '@/redux/hooks/useAppDispatch'; import { currentModelIdSelector } from '@/redux/models/models.selectors'; -import { highestZIndexSelector, layersDrawLayerSelector } from '@/redux/layers/layers.selectors'; +import { + layersDrawLayerSelector, + maxObjectZIndexForLayerSelector, +} from '@/redux/layers/layers.selectors'; import { addLayerImageObject } from '@/redux/layers/layers.thunks'; import { addGlyph } from '@/redux/glyphs/glyphs.thunks'; import { SerializedError } from '@reduxjs/toolkit'; @@ -22,7 +25,7 @@ export const LayerImageObjectFactoryModal: React.FC = () => { const drawLayer = useAppSelector(layersDrawLayerSelector); const layerImageObjectFactoryState = useAppSelector(layerImageObjectFactoryStateSelector); const dispatch = useAppDispatch(); - const highestZIndex = useAppSelector(highestZIndexSelector); + const maxZIndex = useAppSelector(state => maxObjectZIndexForLayerSelector(state, drawLayer)); const { mapInstance } = useMapInstance(); const [selectedGlyph, setSelectedGlyph] = useState<number | null>(null); @@ -49,7 +52,7 @@ export const LayerImageObjectFactoryModal: React.FC = () => { layerId: drawLayer, x: layerImageObjectFactoryState.x, y: layerImageObjectFactoryState.y, - z: highestZIndex + 1, + z: maxZIndex + 1, width: layerImageObjectFactoryState.width, height: layerImageObjectFactoryState.height, glyph: glyphId, diff --git a/src/components/FunctionalArea/Modal/LayerTextFactoryModal/LayerTextFactoryModal.component.tsx b/src/components/FunctionalArea/Modal/LayerTextFactoryModal/LayerTextFactoryModal.component.tsx index e1ccf6122d31395dca1a127417f02bcc672da7bf..bfb80aff2c7686feb3ab0d443034ec81ed396027 100644 --- a/src/components/FunctionalArea/Modal/LayerTextFactoryModal/LayerTextFactoryModal.component.tsx +++ b/src/components/FunctionalArea/Modal/LayerTextFactoryModal/LayerTextFactoryModal.component.tsx @@ -13,7 +13,10 @@ import { LayerTextFactoryForm } from '@/components/FunctionalArea/Modal/LayerTex import { Color } from '@/types/models'; import { useAppSelector } from '@/redux/hooks/useAppSelector'; import { layerTextFactoryStateSelector } from '@/redux/modal/modal.selector'; -import { highestZIndexSelector, layersDrawLayerSelector } from '@/redux/layers/layers.selectors'; +import { + layersDrawLayerSelector, + maxObjectZIndexForLayerSelector, +} from '@/redux/layers/layers.selectors'; import { useAppDispatch } from '@/redux/hooks/useAppDispatch'; import { currentModelIdSelector } from '@/redux/models/models.selectors'; import { showToast } from '@/utils/showToast'; @@ -31,7 +34,7 @@ export const LayerTextFactoryModal: React.FC = () => { const currentModelId = useAppSelector(currentModelIdSelector); const layerTextFactoryState = useAppSelector(layerTextFactoryStateSelector); const dispatch = useAppDispatch(); - const highestZIndex = useAppSelector(highestZIndexSelector); + const maxZIndex = useAppSelector(state => maxObjectZIndexForLayerSelector(state, drawLayer)); const { mapInstance } = useMapInstance(); const [isSending, setIsSending] = useState<boolean>(false); @@ -55,7 +58,7 @@ export const LayerTextFactoryModal: React.FC = () => { layerId: drawLayer, boundingBox: layerTextFactoryState, textData: data, - z: highestZIndex + 1, + z: maxZIndex + 1, }), ).unwrap(); if (!textData) { diff --git a/src/components/Map/Drawer/LayersDrawer/LayerDrawerTextItem.component.tsx b/src/components/Map/Drawer/LayersDrawer/LayerDrawerTextItem.component.tsx index 6f57c76263e042ba0009c55879578125496cdaae..a9c3d3b3c7040a597dfbf9a9e25dea813c7b93b9 100644 --- a/src/components/Map/Drawer/LayersDrawer/LayerDrawerTextItem.component.tsx +++ b/src/components/Map/Drawer/LayersDrawer/LayerDrawerTextItem.component.tsx @@ -10,8 +10,8 @@ import { hasPrivilegeToWriteProjectSelector } from '@/redux/user/user.selectors' interface LayersDrawerTextItemProps { layerText: LayerText; - bringToFront: () => void; - bringToBack: () => void; + moveToFront: () => void; + moveToBack: () => void; removeObject: () => void; centerObject: () => void; editObject: () => void; @@ -21,8 +21,8 @@ interface LayersDrawerTextItemProps { export const LayersDrawerTextItem = ({ layerText, - bringToFront, - bringToBack, + moveToFront, + moveToBack, removeObject, centerObject, editObject, @@ -68,8 +68,8 @@ export const LayersDrawerTextItem = ({ </div> {showActions && ( <LayersDrawerObjectActions - bringToFront={bringToFront} - bringToBack={bringToBack} + moveToFront={moveToFront} + moveToBack={moveToBack} removeObject={removeObject} centerObject={centerObject} editObject={editObject} diff --git a/src/components/Map/Drawer/LayersDrawer/LayersDrawer.component.tsx b/src/components/Map/Drawer/LayersDrawer/LayersDrawer.component.tsx index 12bef081cb69be66edc08ac8bdf2eb448540ce31..33c259f3e0793659100d2a3145e177557bbcfc21 100644 --- a/src/components/Map/Drawer/LayersDrawer/LayersDrawer.component.tsx +++ b/src/components/Map/Drawer/LayersDrawer/LayersDrawer.component.tsx @@ -4,7 +4,7 @@ import { DrawerHeading } from '@/shared/DrawerHeading'; import { useAppDispatch } from '@/redux/hooks/useAppDispatch'; import { layersForCurrentModelSelector } from '@/redux/layers/layers.selectors'; import { Button } from '@/shared/Button'; -import { JSX, useEffect, useRef } from 'react'; +import { JSX, useEffect, useMemo, 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'; @@ -21,6 +21,19 @@ export const LayersDrawer = (): JSX.Element => { dispatch(openLayerFactoryModal()); }; + const sortedLayers = useMemo(() => { + return [...layersForCurrentModel].sort((layerA, layerB) => layerB.details.z - layerA.details.z); + }, [layersForCurrentModel]); + + const negativeZLayers = useMemo( + () => sortedLayers.filter(layer => layer.details.z < 0), + [sortedLayers], + ); + const positiveZLayers = useMemo( + () => sortedLayers.filter(layer => layer.details.z >= 0), + [sortedLayers], + ); + useEffect(() => { if (!mapEditToolsLayerImageObject || !layersDrawerRef.current) { return; @@ -61,20 +74,33 @@ export const LayersDrawer = (): JSX.Element => { ref={layersDrawerRef} > {hasPrivilegeToWriteProject && ( - <div className="flex justify-start pt-2"> + <div className="flex justify-start py-2"> <Button icon="plus" isIcon isFrontIcon onClick={addNewLayer}> Add layer </Button> </div> )} - <div className="flex flex-col gap-4"> - {layersForCurrentModel.map(layer => ( - <LayersDrawerLayer - key={layer.details.id} - layerId={layer.details.id} - layerName={layer.details.name} - /> - ))} + <div className="flex flex-col gap-2"> + {Boolean(positiveZLayers.length) && ( + <span className="border-b-2 border-dashed border-b-gray-400 text-center text-lg font-semibold"> + Layers above the diagram + </span> + )} + <div className="flex flex-col gap-5"> + {positiveZLayers.map(layer => ( + <LayersDrawerLayer key={layer.details.id} layerDetails={layer.details} /> + ))} + </div> + {Boolean(negativeZLayers.length) && ( + <span className="border-b-2 border-dashed border-b-gray-400 text-center text-lg font-semibold"> + Layers below the diagram + </span> + )} + <div className="flex flex-col gap-5"> + {negativeZLayers.map(layer => ( + <LayersDrawerLayer key={layer.details.id} layerDetails={layer.details} /> + ))} + </div> </div> </div> </div> diff --git a/src/components/Map/Drawer/LayersDrawer/LayersDrawerImageItem.component.tsx b/src/components/Map/Drawer/LayersDrawer/LayersDrawerImageItem.component.tsx index 0622f1416bb018d4192399beb33bf26b4f0bf0fe..e160b807ba976f0f153c55ae8a9cbd47acab586e 100644 --- a/src/components/Map/Drawer/LayersDrawer/LayersDrawerImageItem.component.tsx +++ b/src/components/Map/Drawer/LayersDrawer/LayersDrawerImageItem.component.tsx @@ -11,8 +11,8 @@ import { hasPrivilegeToWriteProjectSelector } from '@/redux/user/user.selectors' interface LayersDrawerImageItemProps { layerImage: LayerImage; - bringToFront: () => void; - bringToBack: () => void; + moveToFront: () => void; + moveToBack: () => void; removeObject: () => void; centerObject: () => void; editObject: () => void; @@ -22,8 +22,8 @@ interface LayersDrawerImageItemProps { export const LayersDrawerImageItem = ({ layerImage, - bringToFront, - bringToBack, + moveToFront, + moveToBack, removeObject, centerObject, editObject, @@ -72,8 +72,8 @@ export const LayersDrawerImageItem = ({ </div> {showActions && ( <LayersDrawerObjectActions - bringToFront={bringToFront} - bringToBack={bringToBack} + moveToFront={moveToFront} + moveToBack={moveToBack} removeObject={removeObject} centerObject={centerObject} editObject={editObject} diff --git a/src/components/Map/Drawer/LayersDrawer/LayersDrawerLayer.component.tsx b/src/components/Map/Drawer/LayersDrawer/LayersDrawerLayer.component.tsx index 49b8536fea4d643cc4158a6889d46539c51f858f..8b0f9b016ff89a661c612a9b2e8207840c6a5691 100644 --- a/src/components/Map/Drawer/LayersDrawer/LayersDrawerLayer.component.tsx +++ b/src/components/Map/Drawer/LayersDrawer/LayersDrawerLayer.component.tsx @@ -1,14 +1,19 @@ +/* eslint-disable no-magic-numbers */ import { useAppSelector } from '@/redux/hooks/useAppSelector'; import { useAppDispatch } from '@/redux/hooks/useAppDispatch'; import { layersActiveLayersSelector, layersVisibilityForCurrentModelSelector, + maxLayerZIndexAboveDiagramSelector, + maxLayerZIndexBelowDiagramSelector, + minLayerZIndexAboveDiagramSelector, + minLayerZIndexBelowDiagramSelector, } from '@/redux/layers/layers.selectors'; import { currentModelIdSelector } from '@/redux/models/models.selectors'; import { openLayerFactoryModal } from '@/redux/modal/modal.slice'; import QuestionModal from '@/components/FunctionalArea/Modal/QuestionModal/QustionModal.component'; import { useState, JSX, useMemo } from 'react'; -import { getLayersForModel, removeLayer } from '@/redux/layers/layers.thunks'; +import { getLayersForModel, removeLayer, updateLayer } from '@/redux/layers/layers.thunks'; import { showToast } from '@/utils/showToast'; import { SerializedError } from '@reduxjs/toolkit'; import { LayersDrawerLayerActions } from '@/components/Map/Drawer/LayersDrawer/LayersDrawerLayerActions.component'; @@ -21,38 +26,42 @@ import { import { LayersDrawerObjectsList } from '@/components/Map/Drawer/LayersDrawer/LayersDrawerObjectsList.component'; import { mapEditToolsSetActiveAction } from '@/redux/mapEditTools/mapEditTools.slice'; import { MAP_EDIT_ACTIONS } from '@/redux/mapEditTools/mapEditTools.constants'; +import { Layer } from '@/types/models'; interface LayersDrawerLayerProps { - layerId: number; - layerName: string; + layerDetails: Layer; } -export const LayersDrawerLayer = ({ layerId, layerName }: LayersDrawerLayerProps): JSX.Element => { +export const LayersDrawerLayer = ({ layerDetails }: LayersDrawerLayerProps): JSX.Element => { const layersVisibilityForCurrentModel = useAppSelector(layersVisibilityForCurrentModelSelector); const activeLayers = useAppSelector(layersActiveLayersSelector); const currentModelId = useAppSelector(currentModelIdSelector); + const maxLayerZIndexAboveDiagram = useAppSelector(maxLayerZIndexAboveDiagramSelector); + const maxLayerZIndexBelowDiagram = useAppSelector(maxLayerZIndexBelowDiagramSelector); + const minLayerZIndexAboveDiagram = useAppSelector(minLayerZIndexAboveDiagramSelector); + const minLayerZIndexBelowDiagram = useAppSelector(minLayerZIndexBelowDiagramSelector); const dispatch = useAppDispatch(); const [isModalOpen, setIsModalOpen] = useState(false); const isLayerVisible = useMemo(() => { - return layersVisibilityForCurrentModel[layerId]; - }, [layerId, layersVisibilityForCurrentModel]); + return layersVisibilityForCurrentModel[layerDetails.id]; + }, [layerDetails.id, layersVisibilityForCurrentModel]); const isLayerActive = useMemo(() => { - return activeLayers.includes(layerId); - }, [activeLayers, layerId]); + return activeLayers.includes(layerDetails.id); + }, [activeLayers, layerDetails.id]); const editLayer = (): void => { - dispatch(openLayerFactoryModal(layerId)); + dispatch(openLayerFactoryModal(layerDetails.id)); }; const addImage = (): void => { - dispatch(setDrawLayer({ modelId: currentModelId, layerId })); + dispatch(setDrawLayer({ modelId: currentModelId, layerId: layerDetails.id })); dispatch(mapEditToolsSetActiveAction(MAP_EDIT_ACTIONS.DRAW_IMAGE)); }; const addText = (): void => { - dispatch(setDrawLayer({ modelId: currentModelId, layerId })); + dispatch(setDrawLayer({ modelId: currentModelId, layerId: layerDetails.id })); dispatch(mapEditToolsSetActiveAction(MAP_EDIT_ACTIONS.ADD_TEXT)); }; @@ -61,11 +70,11 @@ export const LayersDrawerLayer = ({ layerId, layerName }: LayersDrawerLayerProps }; const confirmRemove = async (): Promise<void> => { - if (!layerId) { + if (!layerDetails.id) { return; } try { - await dispatch(removeLayer({ modelId: currentModelId, layerId })).unwrap(); + await dispatch(removeLayer({ modelId: currentModelId, layerId: layerDetails.id })).unwrap(); showToast({ type: 'success', message: 'The layer has been successfully removed', @@ -87,12 +96,61 @@ export const LayersDrawerLayer = ({ layerId, layerName }: LayersDrawerLayerProps const toggleActiveLayer = (value: boolean): void => { if (value) { - dispatch(setLayerToActive({ modelId: currentModelId, layerId })); + dispatch(setLayerToActive({ modelId: currentModelId, layerId: layerDetails.id })); } else { - dispatch(setLayerToInactive({ modelId: currentModelId, layerId })); + dispatch(setLayerToInactive({ modelId: currentModelId, layerId: layerDetails.id })); } }; + const updateLayerZIndex = async (zIndex: number): Promise<void> => { + try { + dispatch( + updateLayer({ + name: layerDetails.name, + visible: layerDetails.visible, + locked: layerDetails.locked, + modelId: currentModelId, + layerId: layerDetails.id, + zIndex, + }), + ); + } catch (error) { + const typedError = error as SerializedError; + showToast({ + type: 'error', + message: typedError.message || 'An error occurred while updating the layer', + }); + } + }; + + const moveToFront = (): void => { + if (layerDetails.z > 0) { + updateLayerZIndex(maxLayerZIndexAboveDiagram); + } else if (layerDetails.z < 0) { + const zIndex = Math.min(maxLayerZIndexBelowDiagram, -1); + updateLayerZIndex(zIndex); + } + }; + + const moveToBack = (): void => { + if (layerDetails.z > 0) { + const zIndex = Math.max(minLayerZIndexAboveDiagram, 1); + updateLayerZIndex(zIndex); + } else if (layerDetails.z < 0) { + updateLayerZIndex(minLayerZIndexBelowDiagram); + } + }; + + const moveAboveDiagram = (): void => { + const zIndex = Math.max(minLayerZIndexAboveDiagram - 1, 1); + updateLayerZIndex(zIndex); + }; + + const moveBelowDiagram = (): void => { + const zIndex = Math.min(minLayerZIndexBelowDiagram + 1, -1); + updateLayerZIndex(zIndex); + }; + return ( <div> <QuestionModal @@ -101,9 +159,13 @@ export const LayersDrawerLayer = ({ layerId, layerName }: LayersDrawerLayerProps onConfirm={confirmRemove} question="Are you sure you want to remove the layer?" /> - <div className="flex items-center justify-between py-3"> - <span className={`font-semibold ${isLayerVisible ? 'opacity-100' : 'opacity-40'}`}> - {layerName} + <div className="flex items-center justify-between pb-3"> + <span + className={`font-semibold ${ + isLayerVisible ? 'opacity-100' : 'opacity-40' + } min-w-0 flex-1 truncate `} + > + {layerDetails.name} </span> <LayersDrawerLayerActions toggleVisibility={() => @@ -111,7 +173,7 @@ export const LayersDrawerLayer = ({ layerId, layerName }: LayersDrawerLayerProps setLayerVisibility({ modelId: currentModelId, visible: !isLayerVisible, - layerId, + layerId: layerDetails.id, }), ) } @@ -120,12 +182,17 @@ export const LayersDrawerLayer = ({ layerId, layerName }: LayersDrawerLayerProps removeLayer={onRemoveLayer} addImage={addImage} addText={addText} + moveToFront={moveToFront} + moveToBack={moveToBack} + moveAboveDiagram={moveAboveDiagram} + moveBelowDiagram={moveBelowDiagram} + zIndex={layerDetails.z} isVisible={isLayerVisible} isActive={isLayerActive} /> </div> <LayersDrawerObjectsList - layerId={layerId} + layerId={layerDetails.id} isLayerVisible={isLayerVisible} isLayerActive={isLayerActive} /> diff --git a/src/components/Map/Drawer/LayersDrawer/LayersDrawerLayerActions.component.tsx b/src/components/Map/Drawer/LayersDrawer/LayersDrawerLayerActions.component.tsx index 18627b1325e8ed5afed261cbf82e2c4c501f9152..814351003bc756a741d58a138ca2cb79aea0d00f 100644 --- a/src/components/Map/Drawer/LayersDrawer/LayersDrawerLayerActions.component.tsx +++ b/src/components/Map/Drawer/LayersDrawer/LayersDrawerLayerActions.component.tsx @@ -1,3 +1,4 @@ +/* eslint-disable no-magic-numbers */ import { IconButton } from '@/shared/IconButton'; import { JSX } from 'react'; import { LayerDrawerLayerContextMenu } from '@/components/Map/Drawer/LayersDrawer/LayerDrawerLayerContextMenu.component'; @@ -9,10 +10,15 @@ type LayersDrawerLayerActionsProps = { removeLayer: () => void; isVisible: boolean; isActive: boolean; + zIndex: number; toggleVisibility: () => void; toggleActiveLayer: (value: boolean) => void; addImage: () => void; addText: () => void; + moveToFront: () => void; + moveToBack: () => void; + moveAboveDiagram: () => void; + moveBelowDiagram: () => void; }; export const LayersDrawerLayerActions = ({ @@ -20,10 +26,15 @@ export const LayersDrawerLayerActions = ({ removeLayer, isVisible, isActive, + zIndex, toggleVisibility, toggleActiveLayer, addImage, addText, + moveToFront, + moveToBack, + moveAboveDiagram, + moveBelowDiagram, }: LayersDrawerLayerActionsProps): JSX.Element => { const hasPrivilegeToWriteProject = useAppSelector(hasPrivilegeToWriteProjectSelector); @@ -43,6 +54,34 @@ export const LayersDrawerLayerActions = ({ className="h-auto w-auto bg-transparent p-0" onClick={() => toggleActiveLayer(!isActive)} /> + <IconButton + title="Move to front" + icon="move-front" + className="h-auto w-auto bg-transparent p-0" + onClick={moveToFront} + /> + <IconButton + title="Move to back" + icon="move-back" + className="h-auto w-auto bg-transparent p-0" + onClick={moveToBack} + /> + {zIndex < 0 && ( + <IconButton + title="Move above the diagram" + icon="layer-arrow-up" + className="h-auto w-auto bg-transparent p-0" + onClick={moveAboveDiagram} + /> + )} + {zIndex > 0 && ( + <IconButton + title="Move below the diagram" + icon="layer-arrow-down" + className="h-auto w-auto bg-transparent p-0" + onClick={moveBelowDiagram} + /> + )} <LayerDrawerLayerContextMenu removeLayer={removeLayer} editLayer={editLayer} diff --git a/src/components/Map/Drawer/LayersDrawer/LayersDrawerObjectActions.component.tsx b/src/components/Map/Drawer/LayersDrawer/LayersDrawerObjectActions.component.tsx index 4cf6871e8f0b84a80ca0cae05e64e2fd526a7a77..a97189ea9e305dc1516882818b345143d12d2bc7 100644 --- a/src/components/Map/Drawer/LayersDrawer/LayersDrawerObjectActions.component.tsx +++ b/src/components/Map/Drawer/LayersDrawer/LayersDrawerObjectActions.component.tsx @@ -2,16 +2,16 @@ import { JSX } from 'react'; import { IconButton } from '@/shared/IconButton'; interface LayersDrawerObjectActionsProps { - bringToFront: () => void; - bringToBack: () => void; + moveToFront: () => void; + moveToBack: () => void; removeObject: () => void; centerObject: () => void; editObject: () => void; } export const LayersDrawerObjectActions = ({ - bringToFront, - bringToBack, + moveToFront, + moveToBack, removeObject, centerObject, editObject, @@ -31,16 +31,16 @@ export const LayersDrawerObjectActions = ({ onClick={editObject} /> <IconButton - icon="bring-front" + icon="move-front" className="h-auto w-auto bg-transparent p-0" - title="Bring to front" - onClick={bringToFront} + title="Move to front" + onClick={moveToFront} /> <IconButton - icon="bring-back" + icon="move-back" className="h-auto w-auto bg-transparent p-0" - title="Bring to back" - onClick={bringToBack} + title="Move to back" + onClick={moveToBack} /> <IconButton icon="trash" diff --git a/src/components/Map/Drawer/LayersDrawer/LayersDrawerObjectsList.component.tsx b/src/components/Map/Drawer/LayersDrawer/LayersDrawerObjectsList.component.tsx index 70a6acf5bef351a8820841fc93db2a70312b048c..9ec19fe41385ad686dad7c438071e3198d64e12a 100644 --- a/src/components/Map/Drawer/LayersDrawer/LayersDrawerObjectsList.component.tsx +++ b/src/components/Map/Drawer/LayersDrawer/LayersDrawerObjectsList.component.tsx @@ -1,9 +1,9 @@ /* eslint-disable no-magic-numbers */ import { useAppSelector } from '@/redux/hooks/useAppSelector'; import { - highestZIndexSelector, layerByIdSelector, - lowestZIndexSelector, + maxObjectZIndexForLayerSelector, + minObjectZIndexForLayerSelector, } from '@/redux/layers/layers.selectors'; import { JSX, useState } from 'react'; import { LayersDrawerImageItem } from '@/components/Map/Drawer/LayersDrawer/LayersDrawerImageItem.component'; @@ -64,8 +64,8 @@ export const LayersDrawerObjectsList = ({ isLayerActive, }: LayersDrawerObjectsListProps): JSX.Element | null => { const currentModelId = useAppSelector(mapModelIdSelector); - const highestZIndex = useAppSelector(highestZIndexSelector); - const lowestZIndex = useAppSelector(lowestZIndexSelector); + const maxZIndex = useAppSelector(state => maxObjectZIndexForLayerSelector(state, layerId)); + const minZIndex = useAppSelector(state => minObjectZIndexForLayerSelector(state, layerId)); const layer = useAppSelector(state => layerByIdSelector(state, layerId)); const mapEditToolsLayerImageObject = useAppSelector(mapEditToolsLayerObjectSelector); const [removeModalState, setRemoveModalState] = useState<undefined | 'text' | 'image'>(undefined); @@ -174,12 +174,12 @@ export const LayersDrawerObjectsList = ({ } }; - const bringImageToFront = async (layerImage: LayerImage): Promise<void> => { - await updateImageZIndex({ zIndex: highestZIndex + 1, layerImage }); + const moveImageToFront = async (layerImage: LayerImage): Promise<void> => { + await updateImageZIndex({ zIndex: maxZIndex + 1, layerImage }); }; - const bringImageToBack = async (layerImage: LayerImage): Promise<void> => { - await updateImageZIndex({ zIndex: lowestZIndex - 1, layerImage }); + const moveImageToBack = async (layerImage: LayerImage): Promise<void> => { + await updateImageZIndex({ zIndex: minZIndex - 1, layerImage }); }; const updateTextZIndex = async ({ @@ -210,12 +210,12 @@ export const LayersDrawerObjectsList = ({ } }; - const bringTextToFront = async (layerText: LayerText): Promise<void> => { - await updateTextZIndex({ zIndex: highestZIndex + 1, layerText }); + const moveTextToFront = async (layerText: LayerText): Promise<void> => { + await updateTextZIndex({ zIndex: maxZIndex + 1, layerText }); }; - const bringTextToBack = async (layerText: LayerText): Promise<void> => { - await updateTextZIndex({ zIndex: lowestZIndex - 1, layerText }); + const moveTextToBack = async (layerText: LayerText): Promise<void> => { + await updateTextZIndex({ zIndex: minZIndex - 1, layerText }); }; const centerObject = (layerObject: LayerImage | LayerText): void => { @@ -257,8 +257,8 @@ export const LayersDrawerObjectsList = ({ <LayersDrawerTextItem layerText={layerText} key={layerText.id} - bringToFront={() => bringTextToFront(layerText)} - bringToBack={() => bringTextToBack(layerText)} + moveToFront={() => moveTextToFront(layerText)} + moveToBack={() => moveTextToBack(layerText)} removeObject={() => removeObject(layerText)} centerObject={() => centerObject(layerText)} editObject={() => editText()} @@ -270,8 +270,8 @@ export const LayersDrawerObjectsList = ({ <LayersDrawerImageItem layerImage={layerImage} key={layerImage.id} - bringToFront={() => bringImageToFront(layerImage)} - bringToBack={() => bringImageToBack(layerImage)} + moveToFront={() => moveImageToFront(layerImage)} + moveToBack={() => moveImageToBack(layerImage)} removeObject={() => removeObject(layerImage)} centerObject={() => centerObject(layerImage)} editObject={() => editImage()} diff --git a/src/components/Map/MapViewer/utils/config/additionalLayers/useOlMapAdditionalLayers.ts b/src/components/Map/MapViewer/utils/config/additionalLayers/useOlMapAdditionalLayers.ts index 5e3782938c6cc08f2bc06c077bfe514d06c02201..6d41944f6f693124ec607afcec6ef5293f01c2da 100644 --- a/src/components/Map/MapViewer/utils/config/additionalLayers/useOlMapAdditionalLayers.ts +++ b/src/components/Map/MapViewer/utils/config/additionalLayers/useOlMapAdditionalLayers.ts @@ -125,6 +125,7 @@ export const useOlMapAdditionalLayers = ( const vectorLayers = useMemo(() => { return layersState.map(layer => { const additionalLayer = new Layer({ + zIndex: layer.details.z, texts: layer.texts, rects: layer.rects, ovals: layer.ovals, diff --git a/src/components/Map/MapViewer/utils/config/commentsLayer/useOlMapCommentsLayer.ts b/src/components/Map/MapViewer/utils/config/commentsLayer/useOlMapCommentsLayer.ts index 7ac3b73b9a89368660ce511ba65dd64fbb36ae85..a364002086019aa50a36dae875b56590ffa4a33e 100644 --- a/src/components/Map/MapViewer/utils/config/commentsLayer/useOlMapCommentsLayer.ts +++ b/src/components/Map/MapViewer/utils/config/commentsLayer/useOlMapCommentsLayer.ts @@ -36,6 +36,7 @@ export const useOlMapCommentsLayer = (): VectorLayer<VectorSource<Feature<Geomet return useMemo(() => { const vectorLayer = new VectorLayer({ + zIndex: Infinity, source: vectorSource, }); vectorLayer.set('type', LAYER_TYPE.COMMENTS_LAYER); diff --git a/src/components/Map/MapViewer/utils/config/mapCardLayer/useOlMapCardLayer.ts b/src/components/Map/MapViewer/utils/config/mapCardLayer/useOlMapCardLayer.ts index 69cec807a1265f4436031878e8f4150393c0a4ea..db3dbad4227e5adc39d04e91ddebd445b39097d8 100644 --- a/src/components/Map/MapViewer/utils/config/mapCardLayer/useOlMapCardLayer.ts +++ b/src/components/Map/MapViewer/utils/config/mapCardLayer/useOlMapCardLayer.ts @@ -38,6 +38,7 @@ export const useOlMapCardLayer = (): VectorLayer<VectorSource<Feature<Polygon>>> return useMemo(() => { const vectorLayer = new VectorLayer({ + zIndex: -Infinity, source: vectorSource, style: new Style({ fill: new Fill({ diff --git a/src/components/Map/MapViewer/utils/config/pinsLayer/useOlMapPinsLayer.ts b/src/components/Map/MapViewer/utils/config/pinsLayer/useOlMapPinsLayer.ts index f7b6001c8936ad65c06f65dff888fb822c781831..4cd5a32bd1985437a10e89ad94e65f33a3c763be 100644 --- a/src/components/Map/MapViewer/utils/config/pinsLayer/useOlMapPinsLayer.ts +++ b/src/components/Map/MapViewer/utils/config/pinsLayer/useOlMapPinsLayer.ts @@ -77,6 +77,7 @@ export const useOlMapPinsLayer = (): VectorLayer<VectorSource<Feature<Geometry>> return useMemo(() => { const vectorLayer = new VectorLayer({ + zIndex: Infinity, source: vectorSource, }); vectorLayer.set('type', LAYER_TYPE.PINS_LAYER); diff --git a/src/components/Map/MapViewer/utils/config/processLayer/useOlMapProcessLayer.ts b/src/components/Map/MapViewer/utils/config/processLayer/useOlMapProcessLayer.ts index 2811ec3067ad8b61391793561966ce6b9bd6ba31..98cb6e1ed174e1e0e767537e9d42d8ad79c02817 100644 --- a/src/components/Map/MapViewer/utils/config/processLayer/useOlMapProcessLayer.ts +++ b/src/components/Map/MapViewer/utils/config/processLayer/useOlMapProcessLayer.ts @@ -274,6 +274,7 @@ export const useOlMapProcessLayer = ({ return useMemo(() => { const vectorLayer = new VectorLayer({ + zIndex: 0, source: vectorSource, updateWhileAnimating: true, updateWhileInteracting: true, diff --git a/src/components/Map/MapViewer/utils/config/reactionsLayer/useOlMapReactionsLayer.ts b/src/components/Map/MapViewer/utils/config/reactionsLayer/useOlMapReactionsLayer.ts index bfb2668ed1ec46a20d6149ec928c9f2b367c5e19..df52627135f7495c1a5ad0f9e80a4e4720c6c4a8 100644 --- a/src/components/Map/MapViewer/utils/config/reactionsLayer/useOlMapReactionsLayer.ts +++ b/src/components/Map/MapViewer/utils/config/reactionsLayer/useOlMapReactionsLayer.ts @@ -63,6 +63,7 @@ export const useOlMapReactionsLayer = (): VectorLayer<VectorSource<Feature<Geome return useMemo(() => { const vectorLayer = new VectorLayer({ + zIndex: Infinity, source: vectorSource, style: new Style({ fill: new Fill({ color: LINE_COLOR }), diff --git a/src/components/Map/MapViewer/utils/shapes/layer/Layer.test.ts b/src/components/Map/MapViewer/utils/shapes/layer/Layer.test.ts index 7b8bb9715d0301419dcbaeee9ff3e9acadca1b7d..10349c6dd0905702ebe3aaf65bafbb5c469b4e0a 100644 --- a/src/components/Map/MapViewer/utils/shapes/layer/Layer.test.ts +++ b/src/components/Map/MapViewer/utils/shapes/layer/Layer.test.ts @@ -133,6 +133,7 @@ describe('Layer', () => { pointToProjection: jest.fn(point => [point.x, point.y]), mapInstance, mapSize, + zIndex: 1, lineTypes: {}, arrowTypes: {}, }; diff --git a/src/components/Map/MapViewer/utils/shapes/layer/Layer.ts b/src/components/Map/MapViewer/utils/shapes/layer/Layer.ts index 7f697a728a0d69c560730771d4ae182ea95c9ceb..14d5f8b56ab00909733c472066b525b3b83bf410 100644 --- a/src/components/Map/MapViewer/utils/shapes/layer/Layer.ts +++ b/src/components/Map/MapViewer/utils/shapes/layer/Layer.ts @@ -34,6 +34,7 @@ import LayerText from '@/components/Map/MapViewer/utils/shapes/layer/elements/La import LayerImage from '@/components/Map/MapViewer/utils/shapes/layer/elements/LayerImage'; export interface LayerProps { + zIndex: number; texts: { [key: string]: LayerTextModel }; rects: Array<LayerRect>; ovals: Array<LayerOval>; @@ -78,6 +79,7 @@ export default class Layer { >; constructor({ + zIndex, texts, rects, ovals, @@ -118,6 +120,7 @@ export default class Layer { this.vectorSource.addFeatures(arrowsFeatures); this.vectorLayer = new VectorLayer({ + zIndex, source: this.vectorSource, visible, updateWhileAnimating: true, diff --git a/src/models/layerSchema.ts b/src/models/layerSchema.ts index 380146a7d8abc2d72931291915a4012d67746dbc..11fcd88d7621af24763b748d212f141c968288a5 100644 --- a/src/models/layerSchema.ts +++ b/src/models/layerSchema.ts @@ -5,4 +5,5 @@ export const layerSchema = z.object({ name: z.string(), visible: z.boolean(), locked: z.boolean(), + z: z.number(), }); diff --git a/src/redux/layers/layers.reducers.test.ts b/src/redux/layers/layers.reducers.test.ts index 7bcb10bafd7f54305f08846cfbec804fb43436a2..d93fb4f8b6ae8feac408feeabc4e49e3e7a27ec6 100644 --- a/src/redux/layers/layers.reducers.test.ts +++ b/src/redux/layers/layers.reducers.test.ts @@ -92,7 +92,7 @@ describe('layers reducer', () => { const { type } = await store.dispatch(getLayersForModel(1)); const { data, loading, error } = store.getState().layers[1]; - expect(type).toBe('vectorMap/getLayers/fulfilled'); + expect(type).toBe('layers/getLayers/fulfilled'); expect(loading).toEqual('succeeded'); expect(error).toEqual({ message: '', name: '' }); expect(data).toEqual({ @@ -120,7 +120,7 @@ describe('layers reducer', () => { const action = await store.dispatch(getLayersForModel(1)); const { data, loading, error } = store.getState().layers[1]; - expect(action.type).toBe('vectorMap/getLayers/rejected'); + expect(action.type).toBe('layers/getLayers/rejected'); expect(() => unwrapResult(action)).toThrow( "Failed to fetch layers: The page you're looking for doesn't exist. Please verify the URL and try again.", ); diff --git a/src/redux/layers/layers.selectors.ts b/src/redux/layers/layers.selectors.ts index 8f016c8aa15c09cc7a866f887217ad2df2f3b3fd..8123fa00e957fea949f0f1fbd4d4b0c75ec97d2a 100644 --- a/src/redux/layers/layers.selectors.ts +++ b/src/redux/layers/layers.selectors.ts @@ -2,6 +2,7 @@ import { createSelector } from '@reduxjs/toolkit'; import { rootSelector } from '@/redux/root/root.selectors'; import { currentModelIdSelector } from '@/redux/models/models.selectors'; +import { LayerState } from '@/redux/layers/layers.types'; export const layersSelector = createSelector(rootSelector, state => state.layers); @@ -18,7 +19,7 @@ export const layersActiveLayersSelector = createSelector( export const layersDrawLayerSelector = createSelector( layersStateForCurrentModelSelector, - state => state?.data?.drawLayer, + state => state?.data?.drawLayer || null, ); export const layerByIdSelector = createSelector( @@ -41,40 +42,112 @@ export const layersForCurrentModelSelector = createSelector( state => state?.data?.layers || [], ); -export const highestZIndexSelector = createSelector(layersForCurrentModelSelector, layers => { - if (!layers || layers.length === 0) return 0; - - const getMaxZFromItems = <T extends { z?: number }>(items: T[] = []): number => - items.length > 0 ? Math.max(...items.map(item => item.z || 0)) : 0; - - return layers.reduce((maxZ, layer) => { - const textsMaxZ = getMaxZFromItems(Object.values(layer.texts)); - const rectsMaxZ = getMaxZFromItems(layer.rects); - const ovalsMaxZ = getMaxZFromItems(layer.ovals); - const linesMaxZ = getMaxZFromItems(layer.lines); - const imagesMaxZ = getMaxZFromItems(Object.values(layer.images)); - - const layerMaxZ = Math.max(textsMaxZ, rectsMaxZ, ovalsMaxZ, linesMaxZ, imagesMaxZ); - - return Math.max(maxZ, layerMaxZ); - }, 0); -}); +export const maxLayerZIndexAboveDiagramSelector = createSelector( + layersForCurrentModelSelector, + layers => { + if (!layers || layers.length === 0) { + return 1000; + } + let maxZIndex = -Infinity; + layers.forEach((layer: LayerState) => { + if (layer.details.z > 0 && layer.details.z > maxZIndex) { + maxZIndex = layer.details.z; + } + }); + return maxZIndex; + }, +); -export const lowestZIndexSelector = createSelector(layersForCurrentModelSelector, layers => { - if (!layers || layers.length === 0) return 0; +export const maxLayerZIndexBelowDiagramSelector = createSelector( + layersForCurrentModelSelector, + layers => { + if (!layers || layers.length === 0) { + return -1000; + } + let maxZIndex = -Infinity; + layers.forEach((layer: LayerState) => { + if (layer.details.z < 0 && layer.details.z > maxZIndex) { + maxZIndex = layer.details.z; + } + }); + return maxZIndex; + }, +); - const getMinZFromItems = <T extends { z?: number }>(items: T[] = []): number => - items.length > 0 ? Math.min(...items.map(item => item.z || 0)) : 0; +export const minLayerZIndexAboveDiagramSelector = createSelector( + layersForCurrentModelSelector, + layers => { + if (!layers || layers.length === 0) { + return 1000; + } + let minZIndex = Infinity; + layers.forEach((layer: LayerState) => { + if (layer.details.z > 0 && layer.details.z < minZIndex) { + minZIndex = layer.details.z; + } + }); + return minZIndex; + }, +); - return layers.reduce((minZ, layer) => { - const textsMinZ = getMinZFromItems(Object.values(layer.texts)); - const rectsMinZ = getMinZFromItems(layer.rects); - const ovalsMinZ = getMinZFromItems(layer.ovals); - const linesMinZ = getMinZFromItems(layer.lines); - const imagesMinZ = getMinZFromItems(Object.values(layer.images)); +export const minLayerZIndexBelowDiagramSelector = createSelector( + layersForCurrentModelSelector, + layers => { + if (!layers || layers.length === 0) { + return -1000; + } + let minZIndex = Infinity; + layers.forEach((layer: LayerState) => { + if (layer.details.z < 0 && layer.details.z < minZIndex) { + minZIndex = layer.details.z; + } + }); + return minZIndex; + }, +); - const layerMinZ = Math.min(textsMinZ, rectsMinZ, ovalsMinZ, linesMinZ, imagesMinZ); +export const maxObjectZIndexForLayerSelector = createSelector( + [layersForCurrentModelSelector, (_state, layerId: number | null): number | null => layerId], + (layers, layerId) => { + if (!layers || layers.length === 0 || !layerId) { + return 0; + } + const foundLayer = layers.find(layer => layer.details.id === layerId); + if (!foundLayer) { + return 0; + } + const getMaxZFromItems = <T extends { z?: number }>(items: T[] = []): number => + items.length > 0 ? Math.max(...items.map(item => item.z || 0)) : 0; + + const textsMaxZ = getMaxZFromItems(Object.values(foundLayer.texts)); + const rectsMaxZ = getMaxZFromItems(foundLayer.rects); + const ovalsMaxZ = getMaxZFromItems(foundLayer.ovals); + const linesMaxZ = getMaxZFromItems(foundLayer.lines); + const imagesMaxZ = getMaxZFromItems(Object.values(foundLayer.images)); + + return Math.max(textsMaxZ, rectsMaxZ, ovalsMaxZ, linesMaxZ, imagesMaxZ); + }, +); - return Math.min(minZ, layerMinZ); - }, 0); -}); +export const minObjectZIndexForLayerSelector = createSelector( + [layersForCurrentModelSelector, (_state, layerId: number | null): number | null => layerId], + (layers, layerId) => { + if (!layers || layers.length === 0 || !layerId) { + return 0; + } + const foundLayer = layers.find(layer => layer.details.id === layerId); + if (!foundLayer) { + return 0; + } + const getMinZFromItems = <T extends { z?: number }>(items: T[] = []): number => + items.length > 0 ? Math.min(...items.map(item => item.z || 0)) : 0; + + const textsMinZ = getMinZFromItems(Object.values(foundLayer.texts)); + const rectsMinZ = getMinZFromItems(foundLayer.rects); + const ovalsMinZ = getMinZFromItems(foundLayer.ovals); + const linesMinZ = getMinZFromItems(foundLayer.lines); + const imagesMinZ = getMinZFromItems(Object.values(foundLayer.images)); + + return Math.min(textsMinZ, rectsMinZ, ovalsMinZ, linesMinZ, imagesMinZ); + }, +); diff --git a/src/redux/layers/layers.thunks.test.ts b/src/redux/layers/layers.thunks.test.ts index 77e10bdb3dba0dee10813778ab1ad3316cf8822f..0902827286dbb2f083882e67afaceccb001b1fe8 100644 --- a/src/redux/layers/layers.thunks.test.ts +++ b/src/redux/layers/layers.thunks.test.ts @@ -111,7 +111,13 @@ describe('layers thunks', () => { mockedAxiosClient.onPost(apiPath.storeLayer(1)).reply(HttpStatusCode.Created, layerFixture); const { payload } = await store.dispatch( - addLayerForModel({ name: 'New Layer', visible: true, locked: false, modelId: 1 }), + addLayerForModel({ + name: 'New Layer', + visible: true, + locked: false, + modelId: 1, + zIndex: 1, + }), ); expect(payload).toEqual(layerFixture); }); @@ -122,7 +128,13 @@ describe('layers thunks', () => { .reply(HttpStatusCode.Created, { invalid: 'data' }); const { payload } = await store.dispatch( - addLayerForModel({ name: 'New Layer', visible: true, locked: false, modelId: 1 }), + addLayerForModel({ + name: 'New Layer', + visible: true, + locked: false, + modelId: 1, + zIndex: 1, + }), ); expect(payload).toBeNull(); }); @@ -139,6 +151,7 @@ describe('layers thunks', () => { locked: true, modelId: 1, layerId: 2, + zIndex: 1, }), ); expect(payload).toEqual(layerFixture); @@ -156,6 +169,7 @@ describe('layers thunks', () => { locked: true, modelId: 1, layerId: 2, + zIndex: 1, }), ); expect(payload).toBeNull(); diff --git a/src/redux/layers/layers.thunks.ts b/src/redux/layers/layers.thunks.ts index 66035fd8362c4d412df45932263d47761a022a55..bda72fce6a36e7bbb85ab05820866e86efa318b5 100644 --- a/src/redux/layers/layers.thunks.ts +++ b/src/redux/layers/layers.thunks.ts @@ -32,7 +32,7 @@ export const getLayer = createAsyncThunk< Layer | null, { modelId: number; layerId: number }, ThunkConfig ->('vectorMap/getLayer', async ({ modelId, layerId }) => { +>('layers/getLayer', async ({ modelId, layerId }) => { try { const { data } = await axiosInstanceNewAPI.get<Layer>(apiPath.getLayer(modelId, layerId)); @@ -48,7 +48,7 @@ export const getLayersForModel = createAsyncThunk< LayersVisibilitiesState | undefined, number, ThunkConfig ->('vectorMap/getLayers', async (modelId: number) => { +>('layers/getLayers', async (modelId: number) => { try { const { data } = await axiosInstanceNewAPI.get<Layers>(apiPath.getLayers(modelId)); const isDataValid = validateDataUsingZodSchema(data, pageableSchema(layerSchema)); @@ -103,13 +103,14 @@ export const getLayersForModel = createAsyncThunk< }); export const addLayerForModel = createAsyncThunk<Layer | null, LayerStoreInterface, ThunkConfig>( - 'vectorMap/addLayer', - async ({ name, visible, locked, modelId }) => { + 'layers/addLayer', + async ({ name, visible, locked, modelId, zIndex }) => { try { const { data } = await axiosInstanceNewAPI.post<Layer>(apiPath.storeLayer(modelId), { name, visible, locked, + z: zIndex, }); const isDataValid = validateDataUsingZodSchema(data, layerSchema); @@ -122,13 +123,14 @@ export const addLayerForModel = createAsyncThunk<Layer | null, LayerStoreInterfa ); export const updateLayer = createAsyncThunk<Layer | null, LayerUpdateInterface, ThunkConfig>( - 'vectorMap/updateLayer', - async ({ name, visible, locked, modelId, layerId }) => { + 'layers/updateLayer', + async ({ name, visible, locked, modelId, layerId, zIndex }) => { try { const { data } = await axiosInstanceNewAPI.put<Layer>(apiPath.updateLayer(modelId, layerId), { name, visible, locked, + z: zIndex, }); const isDataValid = validateDataUsingZodSchema(data, layerSchema); @@ -144,7 +146,7 @@ export const removeLayer = createAsyncThunk< null, { modelId: number; layerId: number }, ThunkConfig ->('vectorMap/removeLayer', async ({ modelId, layerId }) => { +>('layers/removeLayer', async ({ modelId, layerId }) => { try { await axiosInstanceNewAPI.delete<void>(apiPath.removeLayer(modelId, layerId)); return null; @@ -161,7 +163,7 @@ export const getLayerImage = createAsyncThunk< imageId: number; }, ThunkConfig ->('vectorMap/getLayerImage', async ({ modelId, layerId, imageId }) => { +>('layers/getLayerImage', async ({ modelId, layerId, imageId }) => { try { const { data } = await axiosInstanceNewAPI.get<LayerImage>( apiPath.getLayerImageObject(modelId, layerId, imageId), @@ -187,7 +189,7 @@ export const addLayerImageObject = createAsyncThunk< glyph: number | null; }, ThunkConfig ->('vectorMap/addLayerImageObject', async ({ modelId, layerId, x, y, z, width, height, glyph }) => { +>('layers/addLayerImageObject', async ({ modelId, layerId, x, y, z, width, height, glyph }) => { try { const { data } = await axiosInstanceNewAPI.post<LayerImage>( apiPath.addLayerImageObject(modelId, layerId), @@ -223,7 +225,7 @@ export const updateLayerImageObject = createAsyncThunk< }, ThunkConfig >( - 'vectorMap/updateLayerImageObject', + 'layers/updateLayerImageObject', async ({ modelId, layerId, id, x, y, z, width, height, glyph }) => { try { const { data } = await axiosInstanceNewAPI.put<LayerImage>( @@ -252,7 +254,7 @@ export const removeLayerImage = createAsyncThunk< null, { modelId: number; layerId: number; imageId: number }, ThunkConfig ->('vectorMap/removeLayerImage', async ({ modelId, layerId, imageId }) => { +>('layers/removeLayerImage', async ({ modelId, layerId, imageId }) => { try { await axiosInstanceNewAPI.delete<void>( apiPath.removeLayerImageObject(modelId, layerId, imageId), @@ -273,7 +275,7 @@ export const addLayerText = createAsyncThunk< textData: LayerTextFactoryForm; }, ThunkConfig ->('vectorMap/addLayerText', async ({ modelId, layerId, z, boundingBox, textData }) => { +>('layers/addLayerText', async ({ modelId, layerId, z, boundingBox, textData }) => { try { const { data } = await axiosInstanceNewAPI.post<LayerText>( apiPath.addLayerText(modelId, layerId), @@ -299,7 +301,7 @@ export const getLayerText = createAsyncThunk< textId: number; }, ThunkConfig ->('vectorMap/getLayerText', async ({ modelId, layerId, textId }) => { +>('layers/getLayerText', async ({ modelId, layerId, textId }) => { try { const { data } = await axiosInstanceNewAPI.get<LayerText>( apiPath.getLayerText(modelId, layerId, textId), diff --git a/src/redux/layers/layers.types.ts b/src/redux/layers/layers.types.ts index 60e42717dfce7d988376e591468ba449484e14e7..05b4636e7519d0e29a4990488639d5ed2ea34306 100644 --- a/src/redux/layers/layers.types.ts +++ b/src/redux/layers/layers.types.ts @@ -6,6 +6,7 @@ export interface LayerStoreInterface { visible: boolean; locked: boolean; modelId: number; + zIndex: number; } export interface LayerUpdateInterface { @@ -14,6 +15,7 @@ export interface LayerUpdateInterface { visible: boolean; locked: boolean; modelId: number; + zIndex: number; } export type LayerState = { diff --git a/src/shared/Icon/Icon.component.tsx b/src/shared/Icon/Icon.component.tsx index 5745b6ec67ac5612ab8866556fd11aa65b51bc5b..aed961c3f91635e3edad3f1ec106dff9228fc275 100644 --- a/src/shared/Icon/Icon.component.tsx +++ b/src/shared/Icon/Icon.component.tsx @@ -31,8 +31,10 @@ import { PadlockOpenIcon } from '@/shared/Icon/Icons/PadlockOpenIcon'; import { PadlockLockedIcon } from '@/shared/Icon/Icons/PadlockLockedIcon'; import { CrossedEyeIcon } from '@/shared/Icon/Icons/CrossedEyeIcon'; import { CenterIcon } from '@/shared/Icon/Icons/CenterIcon'; -import { BringFrontIcon } from '@/shared/Icon/Icons/BringFrontIcon'; -import { BringBackIcon } from '@/shared/Icon/Icons/BringBackIcon'; +import { MoveFrontIcon } from '@/shared/Icon/Icons/MoveFrontIcon'; +import { MoveBackIcon } from '@/shared/Icon/Icons/MoveBackIcon'; +import { LayerArrowUpIcon } from '@/shared/Icon/Icons/LayerArrowUpIcon'; +import { LayerArrowDownIcon } from '@/shared/Icon/Icons/LayerArrowDownIcon'; import { LocationIcon } from './Icons/LocationIcon'; import { MaginfierZoomInIcon } from './Icons/MagnifierZoomIn'; import { MaginfierZoomOutIcon } from './Icons/MagnifierZoomOut'; @@ -87,8 +89,10 @@ const icons: Record<IconTypes, IconComponentType> = { 'padlock-open': PadlockOpenIcon, 'padlock-locked': PadlockLockedIcon, center: CenterIcon, - 'bring-front': BringFrontIcon, - 'bring-back': BringBackIcon, + 'move-front': MoveFrontIcon, + 'move-back': MoveBackIcon, + 'layer-arrow-up': LayerArrowUpIcon, + 'layer-arrow-down': LayerArrowDownIcon, } as const; export const Icon = ({ name, className = '', ...rest }: IconProps): JSX.Element => { diff --git a/src/shared/Icon/Icons/ArrowDoubleDownIcon.tsx b/src/shared/Icon/Icons/ArrowDoubleDownIcon.tsx index d7ab4e38d5c047e12d2a89159481e942d6770e3d..cf5ce5f7878a5dbc7bc8a19b04934c77431f97b2 100644 --- a/src/shared/Icon/Icons/ArrowDoubleDownIcon.tsx +++ b/src/shared/Icon/Icons/ArrowDoubleDownIcon.tsx @@ -13,26 +13,16 @@ export const ArrowDoubleDownIcon = ({ className }: ArrowDoubleDownIconProps): JS > <path d="M8 8L8 18M8 18L5 15M8 18L11 15" - stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" /> <path d="M16 8L16 18M16 18L13 15M16 18L19 15" - stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" /> - <line - x1="4" - y1="6" - x2="20" - y2="6" - stroke="currentColor" - strokeWidth="1.5" - strokeLinecap="round" - /> + <line x1="4" y1="6" x2="20" y2="6" strokeWidth="1.5" strokeLinecap="round" /> </svg> ); diff --git a/src/shared/Icon/Icons/ArrowDoubleUpIcon.tsx b/src/shared/Icon/Icons/ArrowDoubleUpIcon.tsx index ed51a6021ab3163e987db190b0b5d0d8b9ac10f9..861773cdd76cf7a627bacf6d65228b4192f3d2f6 100644 --- a/src/shared/Icon/Icons/ArrowDoubleUpIcon.tsx +++ b/src/shared/Icon/Icons/ArrowDoubleUpIcon.tsx @@ -13,26 +13,16 @@ export const ArrowDoubleUpIcon = ({ className }: ArrowDoubleUpIconProps): JSX.El > <path d="M8 16L8 6M8 6L5 9M8 6L11 9" - stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" /> <path d="M16 16L16 6M16 6L13 9M16 6L19 9" - stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" /> - <line - x1="4" - y1="18" - x2="20" - y2="18" - stroke="currentColor" - strokeWidth="1.5" - strokeLinecap="round" - /> + <line x1="4" y1="18" x2="20" y2="18" strokeWidth="1.5" strokeLinecap="round" /> </svg> ); diff --git a/src/shared/Icon/Icons/LayerArrowDownIcon.tsx b/src/shared/Icon/Icons/LayerArrowDownIcon.tsx new file mode 100644 index 0000000000000000000000000000000000000000..71223a313c013b585937c98d1322955e3b1bd5d5 --- /dev/null +++ b/src/shared/Icon/Icons/LayerArrowDownIcon.tsx @@ -0,0 +1,18 @@ +interface LayerArrowDownIconProps { + className?: string; +} + +export const LayerArrowDownIcon = ({ className }: LayerArrowDownIconProps): JSX.Element => ( + <svg + width="20" + height="20" + viewBox="2 3 20 17" + fill="none" + xmlns="http://www.w3.org/2000/svg" + className={className} + > + <path d="M12 21L7 16H10V10H14V16H17L12 21Z" /> + <rect x="4" y="3" width="16" height="2" /> + <rect x="4" y="7" width="16" height="2" /> + </svg> +); diff --git a/src/shared/Icon/Icons/LayerArrowUpIcon.tsx b/src/shared/Icon/Icons/LayerArrowUpIcon.tsx new file mode 100644 index 0000000000000000000000000000000000000000..bbcf233f9cf790a8657e20fc36950a44e67ec1cd --- /dev/null +++ b/src/shared/Icon/Icons/LayerArrowUpIcon.tsx @@ -0,0 +1,18 @@ +interface LayerArrowUpIconProps { + className?: string; +} + +export const LayerArrowUpIcon = ({ className }: LayerArrowUpIconProps): JSX.Element => ( + <svg + width="20" + height="20" + viewBox="2 3 20 19" + fill="none" + xmlns="http://www.w3.org/2000/svg" + className={className} + > + <path d="M12 3L7 8H10V14H14V8H17L12 3Z" /> + <rect x="4" y="16" width="16" height="2" /> + <rect x="4" y="20" width="16" height="2" /> + </svg> +); diff --git a/src/shared/Icon/Icons/BringBackIcon.tsx b/src/shared/Icon/Icons/MoveBackIcon.tsx similarity index 78% rename from src/shared/Icon/Icons/BringBackIcon.tsx rename to src/shared/Icon/Icons/MoveBackIcon.tsx index d086ac9b556df8701815ac3aac5147f44afe611f..734bd201d58a9c2a5e3a81ad5f7ef663773d03a8 100644 --- a/src/shared/Icon/Icons/BringBackIcon.tsx +++ b/src/shared/Icon/Icons/MoveBackIcon.tsx @@ -1,8 +1,8 @@ -interface BringBackIconProps { +interface MoveBackIconProps { className?: string; } -export const BringBackIcon = ({ className }: BringBackIconProps): JSX.Element => ( +export const MoveBackIcon = ({ className }: MoveBackIconProps): JSX.Element => ( <svg width="20" height="20" @@ -23,6 +23,6 @@ export const BringBackIcon = ({ className }: BringBackIconProps): JSX.Element => stroke="white" fill="none" /> - <rect x="4" y="4" width="64" height="64" rx="10" ry="10" strokeWidth="8" fill="black" /> + <rect x="4" y="4" width="64" height="64" rx="10" ry="10" strokeWidth="8" /> </svg> ); diff --git a/src/shared/Icon/Icons/BringFrontIcon.tsx b/src/shared/Icon/Icons/MoveFrontIcon.tsx similarity index 77% rename from src/shared/Icon/Icons/BringFrontIcon.tsx rename to src/shared/Icon/Icons/MoveFrontIcon.tsx index e5aa50175f5605a1695f8f308987170da28c1491..037f5cb322f88234e965c0441d5aaa169cb334ce 100644 --- a/src/shared/Icon/Icons/BringFrontIcon.tsx +++ b/src/shared/Icon/Icons/MoveFrontIcon.tsx @@ -1,8 +1,8 @@ -interface BringFrontIconProps { +interface MoveFrontIconProps { className?: string; } -export const BringFrontIcon = ({ className }: BringFrontIconProps): JSX.Element => ( +export const MoveFrontIcon = ({ className }: MoveFrontIconProps): JSX.Element => ( <svg width="20" height="20" @@ -11,7 +11,7 @@ export const BringFrontIcon = ({ className }: BringFrontIconProps): JSX.Element fill="none" className={className} > - <rect x="4" y="4" width="64" height="64" rx="10" ry="10" strokeWidth="8" fill="black" /> + <rect x="4" y="4" width="64" height="64" rx="10" ry="10" strokeWidth="8" /> <rect x="30" y="30" diff --git a/src/shared/IconButton/IconButton.component.tsx b/src/shared/IconButton/IconButton.component.tsx index 1330e460889a6c21a9a8b6e2cf26155a569286bb..e71c6211e214a66e0f5f6876c7fd1d087c9edee3 100644 --- a/src/shared/IconButton/IconButton.component.tsx +++ b/src/shared/IconButton/IconButton.component.tsx @@ -29,8 +29,8 @@ export const IconButton = ({ const isStrokeIcon = [ 'plugin', - 'bring-back', - 'bring-front', + 'move-back', + 'move-front', 'center', 'eye', 'crossed-eye', @@ -38,6 +38,8 @@ export const IconButton = ({ 'padlock-locked', 'layers', 'edit', + 'arrow-double-up', + 'arrow-double-down', ].includes(icon); return ( @@ -57,6 +59,8 @@ export const IconButton = ({ isStrokeIcon ? 'stroke-font-400 group-hover:stroke-primary-500 group-active:stroke-primary-500' : 'fill-font-400 group-hover:fill-primary-500 group-active:fill-primary-500', + ['move-back', 'move-front'].includes(icon) && + 'fill-font-400 stroke-font-400 group-hover:fill-primary-500 group-hover:stroke-primary-500 group-active:fill-primary-500 group-active:stroke-primary-500', isActive && getActiveFillOrStrokeColor(icon), classNameIcon, )} diff --git a/src/types/iconTypes.ts b/src/types/iconTypes.ts index 0d74dd786dade26d36a99f5b83bd44d2cd448ba5..0083a62236d557be7b84b8b604c51e83912bb989 100644 --- a/src/types/iconTypes.ts +++ b/src/types/iconTypes.ts @@ -38,7 +38,9 @@ export type IconTypes = | 'padlock-open' | 'padlock-locked' | 'center' - | 'bring-front' - | 'bring-back'; + | 'move-front' + | 'move-back' + | 'layer-arrow-up' + | 'layer-arrow-down'; export type IconComponentType = ({ className }: { className: string }) => JSX.Element;