diff --git a/CHANGELOG b/CHANGELOG index 60e6ca7d4077f245988718e38c6e041dcedda39b..0992fd8c23a32df9e3b9404cd531e9d1f761330d 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -8,11 +8,27 @@ minerva-front (19.0.0~alpha.0) stable; urgency=medium -- Piotr Gawron <piotr.gawron@uni.lu> Fri, 18 Oct 2024 13:00:00 +0200 +minerva-front (18.0.7) stable; urgency=medium + * Bug fix: export to image did not include overlays (#326) + * Bug fix: missing logos added (#329) + * Bug fix: plugin API did not provide overlay entries when returning list of + visible overlays (#332) + +-- Piotr Gawron <piotr.gawron@uni.lu> Wed, 11 Dec 2024 13:00:00 +0200 + +minerva-front (18.0.6) stable; urgency=medium + + * No changes in frontend + +-- Piotr Gawron <piotr.gawron@uni.lu> Fri, 06 Dec 2024 13:00:00 +0200 + minerva-front (18.0.5) stable; urgency=medium * Bug fix: anchor overlays were disappearing after clicking on anchor and outside of the anchor (#319) + * Bug fix: bioEntity HTML notes in the left panel were not rendered as html + (#323) - -- Piotr Gawron <piotr.gawron@uni.lu> Wed, 27 Nov 2024 13:00:00 +0200 + -- Piotr Gawron <piotr.gawron@uni.lu> Thu, 05 Dec 2024 13:00:00 +0200 minerva-front (18.0.4) stable; urgency=medium * Bug fix: link to search result from overview image caused map not to diff --git a/package-lock.json b/package-lock.json index 77d0a6c9888306f99de8fca6b475bf1d921583c2..93aa9055e22ddae368df2d8f59f6dbbc92ef3785 100644 --- a/package-lock.json +++ b/package-lock.json @@ -39,7 +39,7 @@ "react-redux": "8.1.3", "sonner": "1.4.3", "tailwind-merge": "1.14.0", - "tailwindcss": "3.3.3", + "tailwindcss": "3.4.13", "ts-deepmerge": "6.2.0", "use-debounce": "9.0.4", "uuid": "9.0.1", @@ -13026,19 +13026,19 @@ } }, "node_modules/tailwindcss": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.3.tgz", - "integrity": "sha512-A0KgSkef7eE4Mf+nKJ83i75TMyq8HqY3qmFIJSWy8bNt0v1lG7jUcpGpoTFxAwYcWOphcTBLPPJg+bDfhDf52w==", + "version": "3.4.13", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.13.tgz", + "integrity": "sha512-KqjHOJKogOUt5Bs752ykCeiwvi0fKVkr5oqsFNt/8px/tA8scFPIlkygsf6jXrfCqGHz7VflA6+yytWuM+XhFw==", "dependencies": { "@alloc/quick-lru": "^5.2.0", "arg": "^5.0.2", "chokidar": "^3.5.3", "didyoumean": "^1.2.2", "dlv": "^1.1.3", - "fast-glob": "^3.2.12", + "fast-glob": "^3.3.0", "glob-parent": "^6.0.2", "is-glob": "^4.0.3", - "jiti": "^1.18.2", + "jiti": "^1.21.0", "lilconfig": "^2.1.0", "micromatch": "^4.0.5", "normalize-path": "^3.0.0", @@ -23509,19 +23509,19 @@ "integrity": "sha512-3mFKyCo/MBcgyOTlrY8T7odzZFx+w+qKSMAmdFzRvqBfLlSigU6TZnlFHK0lkMwj9Bj8OYU+9yW9lmGuS0QEnQ==" }, "tailwindcss": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.3.tgz", - "integrity": "sha512-A0KgSkef7eE4Mf+nKJ83i75TMyq8HqY3qmFIJSWy8bNt0v1lG7jUcpGpoTFxAwYcWOphcTBLPPJg+bDfhDf52w==", + "version": "3.4.13", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.13.tgz", + "integrity": "sha512-KqjHOJKogOUt5Bs752ykCeiwvi0fKVkr5oqsFNt/8px/tA8scFPIlkygsf6jXrfCqGHz7VflA6+yytWuM+XhFw==", "requires": { "@alloc/quick-lru": "^5.2.0", "arg": "^5.0.2", "chokidar": "^3.5.3", "didyoumean": "^1.2.2", "dlv": "^1.1.3", - "fast-glob": "^3.2.12", + "fast-glob": "^3.3.0", "glob-parent": "^6.0.2", "is-glob": "^4.0.3", - "jiti": "^1.18.2", + "jiti": "^1.21.0", "lilconfig": "^2.1.0", "micromatch": "^4.0.5", "normalize-path": "^3.0.0", diff --git a/package.json b/package.json index a19fb274a4bdcc004b41353e7c7da027bc5d5cd2..213f3cd7aeefd37073e42e98a791e68064c412c5 100644 --- a/package.json +++ b/package.json @@ -53,7 +53,7 @@ "react-redux": "8.1.3", "sonner": "1.4.3", "tailwind-merge": "1.14.0", - "tailwindcss": "3.3.3", + "tailwindcss": "3.4.13", "ts-deepmerge": "6.2.0", "use-debounce": "9.0.4", "uuid": "9.0.1", diff --git a/src/components/Map/Drawer/BioEntityDrawer/BioEntityDrawer.component.tsx b/src/components/Map/Drawer/BioEntityDrawer/BioEntityDrawer.component.tsx index af9a9f13a513f1469e1c13392f41fd36e312ca42..0108a32af847aa01a459605efab9f1841461fcd8 100644 --- a/src/components/Map/Drawer/BioEntityDrawer/BioEntityDrawer.component.tsx +++ b/src/components/Map/Drawer/BioEntityDrawer/BioEntityDrawer.component.tsx @@ -15,6 +15,7 @@ import { ElementSearchResultType } from '@/types/models'; import { CommentItem } from '@/components/Map/Drawer/BioEntityDrawer/Comments/CommentItem.component'; import { getTypeBySBOTerm } from '@/utils/bioEntity/getTypeBySBOTerm'; import { ModificationResidueItem } from '@/components/Map/Drawer/BioEntityDrawer/ModificationResidueItem'; +import React from 'react'; import { CollapsibleSection } from '../ExportDrawer/CollapsibleSection'; import { AnnotationItem } from './AnnotationItem'; import { AssociatedSubmap } from './AssociatedSubmap'; @@ -75,7 +76,10 @@ export const BioEntityDrawer = (): React.ReactNode => { {bioEntityData.notes && ( <span> <hr className="border-b border-b-divide" /> - <div className="text-sm font-normal">{bioEntityData.notes}</div> + <div + className="text-sm font-normal" + dangerouslySetInnerHTML={{ __html: bioEntityData.notes }} + /> </span> )} {isModificationAvailable && ( diff --git a/src/components/Map/Drawer/ExportDrawer/ExportCompound/CurrentView/CurrentView.component.tsx b/src/components/Map/Drawer/ExportDrawer/ExportCompound/CurrentView/CurrentView.component.tsx new file mode 100644 index 0000000000000000000000000000000000000000..cb7e611dfa42b39a729dfd135eb9722c9183d640 --- /dev/null +++ b/src/components/Map/Drawer/ExportDrawer/ExportCompound/CurrentView/CurrentView.component.tsx @@ -0,0 +1,37 @@ +import { useContext } from 'react'; +import { ExportContext } from '@/components/Map/Drawer/ExportDrawer/ExportCompound/ExportCompound.context'; +import { useAppSelector } from '@/redux/hooks/useAppSelector'; +import { currentModelIdSelector, currentModelNameSelector } from '@/redux/models/models.selectors'; + +export const CurrentView = (): React.ReactNode => { + const { setCurrentView, data, setModels } = useContext(ExportContext); + + const currentMapModelId = useAppSelector(currentModelIdSelector); + const currentMapModelName = useAppSelector(currentModelNameSelector); + + return ( + <div className="flex flex-col gap-4 border-b"> + <label className="flex h-9 items-center gap-4"> + <span>Current view: </span> + <input + className="rounded-[64px] border border-transparent bg-cultured px-4 py-2.5 text-xs font-medium text-font-400 outline-none hover:border-greyscale-600 focus:border-greyscale-600" + name="width" + checked={data.currentView.value} + type="checkbox" + aria-label="export graphics width input" + onChange={(e): void => { + setCurrentView({ value: e.target.checked }); + if (e.target.checked) { + setModels([ + { + id: `${currentMapModelId}`, + label: currentMapModelName, + }, + ]); + } + }} + /> + </label> + </div> + ); +}; diff --git a/src/components/Map/Drawer/ExportDrawer/ExportCompound/CurrentView/CurrentView.constants.ts b/src/components/Map/Drawer/ExportDrawer/ExportCompound/CurrentView/CurrentView.constants.ts new file mode 100644 index 0000000000000000000000000000000000000000..f512b6d76fb6902fa945ec84a7ae710801601f5a --- /dev/null +++ b/src/components/Map/Drawer/ExportDrawer/ExportCompound/CurrentView/CurrentView.constants.ts @@ -0,0 +1,5 @@ +import { CurrentView } from './CurrentView.types'; + +export const DEFAULT_CURRENT_VIEW: CurrentView = { + value: false, +}; diff --git a/src/components/Map/Drawer/ExportDrawer/ExportCompound/CurrentView/CurrentView.types.ts b/src/components/Map/Drawer/ExportDrawer/ExportCompound/CurrentView/CurrentView.types.ts new file mode 100644 index 0000000000000000000000000000000000000000..dc5bc917341152147477b7362f67a62a287fd8f5 --- /dev/null +++ b/src/components/Map/Drawer/ExportDrawer/ExportCompound/CurrentView/CurrentView.types.ts @@ -0,0 +1,3 @@ +export interface CurrentView { + value: boolean; +} diff --git a/src/components/Map/Drawer/ExportDrawer/ExportCompound/CurrentView/index.ts b/src/components/Map/Drawer/ExportDrawer/ExportCompound/CurrentView/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..883104d6df9c38abc94fb7e0039b7c98d21730f1 --- /dev/null +++ b/src/components/Map/Drawer/ExportDrawer/ExportCompound/CurrentView/index.ts @@ -0,0 +1 @@ +export { CurrentView } from './CurrentView.component'; diff --git a/src/components/Map/Drawer/ExportDrawer/ExportCompound/ExportCompound.component.tsx b/src/components/Map/Drawer/ExportDrawer/ExportCompound/ExportCompound.component.tsx index f5803c10f60e163d333a9eadc1fb8b6855d7486c..1c7da28931cbf759afa8276f6c4502cdbcb382d6 100644 --- a/src/components/Map/Drawer/ExportDrawer/ExportCompound/ExportCompound.component.tsx +++ b/src/components/Map/Drawer/ExportDrawer/ExportCompound/ExportCompound.component.tsx @@ -5,6 +5,9 @@ import { useAppDispatch } from '@/redux/hooks/useAppDispatch'; import { useAppSelector } from '@/redux/hooks/useAppSelector'; import { modelsDataSelector, modelsIdsSelector } from '@/redux/models/models.selectors'; import { ReactNode, useCallback, useMemo, useState } from 'react'; +import { activeOverlaysIdSelector } from '@/redux/overlayBioEntity/overlayBioEntity.selector'; +import { CurrentView } from '@/components/Map/Drawer/ExportDrawer/ExportCompound/CurrentView'; +import { DEFAULT_CURRENT_VIEW } from '@/components/Map/Drawer/ExportDrawer/ExportCompound/CurrentView/CurrentView.constants'; import { CheckboxItem } from '../CheckboxFilter/CheckboxFilter.types'; import { Annotations } from './Annotations'; import { DownloadElements } from './DownloadElements/DownloadElements'; @@ -17,6 +20,7 @@ import { ImageFormat } from './ImageFormat'; import { ImageSize } from './ImageSize'; import { DEFAULT_IMAGE_SIZE } from './ImageSize/ImageSize.constants'; import { ImageSize as ImageSizeType } from './ImageSize/ImageSize.types'; +import { CurrentView as CurrentViewType } from './CurrentView/CurrentView.types'; import { IncludedCompartmentPathways } from './IncludedCompartmentPathways '; import { Submap } from './Submap'; import { getDownloadElementsBodyRequest } from './utils/getDownloadElementsBodyRequest'; @@ -33,6 +37,7 @@ export const Export = ({ children }: ExportProps): JSX.Element => { const modelIds = useAppSelector(modelsIdsSelector); const currentModels = useAppSelector(modelsDataSelector); const currentBackground = useAppSelector(currentBackgroundSelector); + const overlays = useAppSelector(activeOverlaysIdSelector); const [annotations, setAnnotations] = useState<CheckboxItem[]>([]); const [includedCompartmentPathways, setIncludedCompartmentPathways] = useState<CheckboxItem[]>( [], @@ -43,6 +48,7 @@ export const Export = ({ children }: ExportProps): JSX.Element => { const [models, setModels] = useState<CheckboxItem[]>([]); const [imageSize, setImageSize] = useState<ImageSizeType>(DEFAULT_IMAGE_SIZE); const [imageFormats, setImageFormats] = useState<CheckboxItem[]>([]); + const [currentView, setCurrentView] = useState<CurrentViewType>(DEFAULT_CURRENT_VIEW); const handleDownloadElements = useCallback(async () => { const body = getDownloadElementsBodyRequest({ @@ -76,6 +82,8 @@ export const Export = ({ children }: ExportProps): JSX.Element => { modelId: models?.[FIRST_ARRAY_ELEMENT]?.id, handler: imageFormats?.[FIRST_ARRAY_ELEMENT]?.id, zoom: getModelExportZoom(imageSize.width, model), + overlayIds: overlays.map(overlayId => `${overlayId}`), + currentView: currentView.value, }); if (url) { @@ -91,6 +99,7 @@ export const Export = ({ children }: ExportProps): JSX.Element => { models, imageSize, imageFormats, + currentView, }), [ annotations, @@ -99,6 +108,7 @@ export const Export = ({ children }: ExportProps): JSX.Element => { models, imageSize, imageFormats, + currentView, ], ); @@ -110,6 +120,7 @@ export const Export = ({ children }: ExportProps): JSX.Element => { setModels, setImageSize, setImageFormats, + setCurrentView, handleDownloadElements, handleDownloadNetwork, handleDownloadGraphics, @@ -130,3 +141,4 @@ Export.ImageSize = ImageSize; Export.ImageFormat = ImageFormat; Export.DownloadNetwork = DownloadNetwork; Export.DownloadGraphics = DownloadGraphics; +Export.CurrentView = CurrentView; diff --git a/src/components/Map/Drawer/ExportDrawer/ExportCompound/ExportCompound.constant.ts b/src/components/Map/Drawer/ExportDrawer/ExportCompound/ExportCompound.constant.ts index 5a7e4f086b680db77987b73ae01317a6d0cb74b4..5381e784a3e7d88eb5895b9fc93ea44c85050904 100644 --- a/src/components/Map/Drawer/ExportDrawer/ExportCompound/ExportCompound.constant.ts +++ b/src/components/Map/Drawer/ExportDrawer/ExportCompound/ExportCompound.constant.ts @@ -1,3 +1,4 @@ +import { DEFAULT_CURRENT_VIEW } from '@/components/Map/Drawer/ExportDrawer/ExportCompound/CurrentView/CurrentView.constants'; import { ExportContextType } from './ExportCompound.types'; import { DEFAULT_IMAGE_SIZE } from './ImageSize/ImageSize.constants'; @@ -54,6 +55,7 @@ export const EXPORT_CONTEXT_DEFAULT_VALUE: ExportContextType = { setModels: () => {}, setImageSize: () => {}, setImageFormats: () => {}, + setCurrentView: () => {}, handleDownloadElements: () => Promise.resolve(), handleDownloadNetwork: () => Promise.resolve(), handleDownloadGraphics: () => {}, @@ -64,5 +66,6 @@ export const EXPORT_CONTEXT_DEFAULT_VALUE: ExportContextType = { models: [], imageFormats: [], imageSize: DEFAULT_IMAGE_SIZE, + currentView: DEFAULT_CURRENT_VIEW, }, }; diff --git a/src/components/Map/Drawer/ExportDrawer/ExportCompound/ExportCompound.types.ts b/src/components/Map/Drawer/ExportDrawer/ExportCompound/ExportCompound.types.ts index 02c87101e98091a30464c03f9d46d9deba6f56a8..b57cc6e01bff11428ca94dbef9503ef53bfc69c3 100644 --- a/src/components/Map/Drawer/ExportDrawer/ExportCompound/ExportCompound.types.ts +++ b/src/components/Map/Drawer/ExportDrawer/ExportCompound/ExportCompound.types.ts @@ -1,3 +1,4 @@ +import { CurrentView } from '@/components/Map/Drawer/ExportDrawer/ExportCompound/CurrentView/CurrentView.types'; import { CheckboxItem } from '../CheckboxFilter/CheckboxFilter.types'; import { ImageSize } from './ImageSize/ImageSize.types'; @@ -6,6 +7,7 @@ export type ExportContextType = { setIncludedCompartmentPathways: React.Dispatch<React.SetStateAction<CheckboxItem[]>>; setExcludedCompartmentPathways: React.Dispatch<React.SetStateAction<CheckboxItem[]>>; setModels: React.Dispatch<React.SetStateAction<CheckboxItem[]>>; + setCurrentView: React.Dispatch<React.SetStateAction<CurrentView>>; setImageSize: React.Dispatch<React.SetStateAction<ImageSize>>; setImageFormats: React.Dispatch<React.SetStateAction<CheckboxItem[]>>; handleDownloadElements: () => Promise<void>; @@ -16,6 +18,7 @@ export type ExportContextType = { includedCompartmentPathways: CheckboxItem[]; excludedCompartmentPathways: CheckboxItem[]; models: CheckboxItem[]; + currentView: CurrentView; imageSize: ImageSize; imageFormats: CheckboxItem[]; }; diff --git a/src/components/Map/Drawer/ExportDrawer/ExportCompound/ImageSize/utils/useImageSize.ts b/src/components/Map/Drawer/ExportDrawer/ExportCompound/ImageSize/utils/useImageSize.ts index f36afdd3864b7a7d284103b19c6da8c8641f20d3..d4869197ef7c1b80e24edddcc98ef7d2bc95eaa6 100644 --- a/src/components/Map/Drawer/ExportDrawer/ExportCompound/ImageSize/utils/useImageSize.ts +++ b/src/components/Map/Drawer/ExportDrawer/ExportCompound/ImageSize/utils/useImageSize.ts @@ -1,9 +1,13 @@ import { MapModel } from '@/types/models'; import { numberToSafeInt } from '@/utils/number/numberToInt'; import { useCallback, useContext, useEffect } from 'react'; +import { + getScreenAspectRatios, + getScreenDimension, +} from '@/components/Map/Drawer/ExportDrawer/ExportCompound/ImageSize/utils/useScreenAspectRatios'; import { ExportContext } from '../../ExportCompound.context'; import { DEFAULT_IMAGE_HEIGHT, DEFAULT_IMAGE_WIDTH } from '../ImageSize.constants'; -import { ImageSize } from '../ImageSize.types'; +import { ImageSize, ModelAspectRatios } from '../ImageSize.types'; import { useExportGraphicsSelectedModel } from './useExportGraphicsSelectedModel'; import { getModelAspectRatios } from './useModelAspectRatios'; @@ -16,9 +20,15 @@ interface UseImageSizeResults { export const useImageSize = (): UseImageSizeResults => { const selectedModel = useExportGraphicsSelectedModel(); - const aspectRatios = getModelAspectRatios(selectedModel); - const { data, setImageSize } = useContext(ExportContext); - const { imageSize } = data; + const { data, setImageSize, setCurrentView } = useContext(ExportContext); + const { imageSize, currentView } = data; + let aspectRatios: ModelAspectRatios; + if (currentView.value) { + aspectRatios = getScreenAspectRatios(); + } else { + aspectRatios = getModelAspectRatios(selectedModel); + } + const maxWidth = selectedModel?.width || DEFAULT_IMAGE_WIDTH; const maxHeight = selectedModel?.height || DEFAULT_IMAGE_HEIGHT; @@ -27,8 +37,8 @@ export const useImageSize = (): UseImageSizeResults => { const newWidth = newImageSize.width; const newHeight = newImageSize.height; - const widthMinMax = Math.min(maxWidth, newWidth); - const heightMinMax = Math.min(maxHeight, newHeight); + const widthMinMax = currentView.value ? newWidth : Math.min(maxWidth, newWidth); + const heightMinMax = currentView.value ? newHeight : Math.min(maxHeight, newHeight); const widthInt = numberToSafeInt(widthMinMax); const heightInt = numberToSafeInt(heightMinMax); @@ -43,14 +53,17 @@ export const useImageSize = (): UseImageSizeResults => { const setDefaultModelImageSize = useCallback( (model: MapModel): void => { + const screenDimension = getScreenDimension(); + const width = currentView.value ? screenDimension.width : model.width; + const height = currentView.value ? screenDimension.height : model.height; const newImageSize = getNormalizedImageSize({ - width: model.width, - height: model.height, + width, + height, }); setImageSize(newImageSize); }, - [getNormalizedImageSize, setImageSize], + [getNormalizedImageSize, setImageSize, setCurrentView, currentView], ); const handleChangeWidth = (width: number): void => { @@ -77,7 +90,7 @@ export const useImageSize = (): UseImageSizeResults => { } setDefaultModelImageSize(selectedModel); - }, [setDefaultModelImageSize, selectedModel]); + }, [setDefaultModelImageSize, selectedModel, setCurrentView, currentView]); return { handleChangeWidth, diff --git a/src/components/Map/Drawer/ExportDrawer/ExportCompound/ImageSize/utils/useScreenAspectRatios.ts b/src/components/Map/Drawer/ExportDrawer/ExportCompound/ImageSize/utils/useScreenAspectRatios.ts new file mode 100644 index 0000000000000000000000000000000000000000..ee1bec83ec0db6998535a0849c8c8c3382de4318 --- /dev/null +++ b/src/components/Map/Drawer/ExportDrawer/ExportCompound/ImageSize/utils/useScreenAspectRatios.ts @@ -0,0 +1,33 @@ +import { getBounds } from '@/services/pluginsManager/map/data/getBounds'; +import { DEFAULT_IMAGE_SIZE, DEFAULT_MODEL_ASPECT_RATIOS } from '../ImageSize.constants'; +import { ImageSize, ModelAspectRatios } from '../ImageSize.types'; + +export const getScreenAspectRatios = (): ModelAspectRatios => { + const bounds = getBounds(); + if (!bounds) { + return DEFAULT_MODEL_ASPECT_RATIOS; + } + const { x1, x2, y1, y2 } = bounds; + const width = x2 - x1; + const height = y2 - y1; + + return { + vertical: height / width, + horizontal: width / height, + }; +}; + +export const getScreenDimension = (): ImageSize => { + const bounds = getBounds(); + if (!bounds) { + return DEFAULT_IMAGE_SIZE; + } + const { x1, x2, y1, y2 } = bounds; + const width = x2 - x1; + const height = y2 - y1; + + return { + width, + height, + }; +}; diff --git a/src/components/Map/Drawer/ExportDrawer/ExportCompound/utils/getGraphicsDownloadUrl.test.ts b/src/components/Map/Drawer/ExportDrawer/ExportCompound/utils/getGraphicsDownloadUrl.test.ts index 2e6c4db9026bd78b950223fa64e5b3afe5161606..a4e4c59df4b2be89286280718a4100991f797fba 100644 --- a/src/components/Map/Drawer/ExportDrawer/ExportCompound/utils/getGraphicsDownloadUrl.test.ts +++ b/src/components/Map/Drawer/ExportDrawer/ExportCompound/utils/getGraphicsDownloadUrl.test.ts @@ -3,10 +3,11 @@ import { GetGraphicsDownloadUrlProps, getGraphicsDownloadUrl } from './getGraphi describe('getGraphicsDownloadUrl - util', () => { const cases: [GetGraphicsDownloadUrlProps, string | undefined][] = [ - [{}, undefined], + [{ overlayIds: [] }, undefined], [ { backgroundId: 50, + overlayIds: [], }, undefined, ], @@ -14,6 +15,7 @@ describe('getGraphicsDownloadUrl - util', () => { { backgroundId: 50, modelId: '30', + overlayIds: [], }, undefined, ], @@ -22,6 +24,7 @@ describe('getGraphicsDownloadUrl - util', () => { backgroundId: 50, modelId: '30', handler: 'any.handler.image', + overlayIds: [], }, undefined, ], @@ -31,8 +34,9 @@ describe('getGraphicsDownloadUrl - util', () => { modelId: '30', handler: 'any.handler.image', zoom: 7, + overlayIds: ['107'], }, - `${BASE_API_URL}/projects/${PROJECT_ID}/models/30:downloadImage?backgroundOverlayId=50&handlerClass=any.handler.image&zoomLevel=7`, + `${BASE_API_URL}/projects/${PROJECT_ID}/models/30:downloadImage?backgroundOverlayId=50&handlerClass=any.handler.image&zoomLevel=7&overlayIds=107`, ], ]; diff --git a/src/components/Map/Drawer/ExportDrawer/ExportCompound/utils/getGraphicsDownloadUrl.ts b/src/components/Map/Drawer/ExportDrawer/ExportCompound/utils/getGraphicsDownloadUrl.ts index c4020e9899f75685c7b732db23dd246077d433e7..2844899535138beda38a96fc209db517bb13c12e 100644 --- a/src/components/Map/Drawer/ExportDrawer/ExportCompound/utils/getGraphicsDownloadUrl.ts +++ b/src/components/Map/Drawer/ExportDrawer/ExportCompound/utils/getGraphicsDownloadUrl.ts @@ -1,10 +1,13 @@ import { BASE_API_URL, PROJECT_ID } from '@/constants'; +import { getBounds } from '@/services/pluginsManager/map/data/getBounds'; export interface GetGraphicsDownloadUrlProps { backgroundId?: number; modelId?: string; handler?: string; zoom?: number; + overlayIds: string[]; + currentView?: boolean; } export const getGraphicsDownloadUrl = ({ @@ -12,6 +15,8 @@ export const getGraphicsDownloadUrl = ({ modelId, handler, zoom, + overlayIds, + currentView, }: GetGraphicsDownloadUrlProps): string | undefined => { const isAllElementsTruthy = [backgroundId, modelId, handler, zoom].reduce( (a, b) => Boolean(a) && Boolean(b), @@ -22,5 +27,16 @@ export const getGraphicsDownloadUrl = ({ return undefined; } - return `${BASE_API_URL}/projects/${PROJECT_ID}/models/${modelId}:downloadImage?backgroundOverlayId=${backgroundId}&handlerClass=${handler}&zoomLevel=${zoom}`; + const overlays = overlayIds.join(','); + + let polygonSuffix = ''; + if (currentView) { + const bounds = getBounds(); + if (bounds) { + const { x1, y1, x2, y2 } = bounds; + polygonSuffix = `&polygonString=${x1},${y1};${x1},${y2};${x2},${y2};${x2},${y1}`; + } + } + + return `${BASE_API_URL}/projects/${PROJECT_ID}/models/${modelId}:downloadImage?backgroundOverlayId=${backgroundId}&handlerClass=${handler}&zoomLevel=${zoom}&overlayIds=${overlays}${polygonSuffix}`; }; diff --git a/src/components/Map/Drawer/ExportDrawer/Graphics/Graphics.component.tsx b/src/components/Map/Drawer/ExportDrawer/Graphics/Graphics.component.tsx index 547c2c14ed2b487a38bb5773552aa386ef84fadd..aafe37a2139b710d8fb3f4b7bdd94f5354e5c043 100644 --- a/src/components/Map/Drawer/ExportDrawer/Graphics/Graphics.component.tsx +++ b/src/components/Map/Drawer/ExportDrawer/Graphics/Graphics.component.tsx @@ -4,6 +4,7 @@ export const Graphics = (): React.ReactNode => { return ( <div data-testid="graphics-tab"> <Export> + <Export.CurrentView /> <Export.Submap /> <Export.ImageSize /> <Export.ImageFormat /> diff --git a/src/components/Map/Drawer/ReactionDrawer/ReactionDrawer.component.tsx b/src/components/Map/Drawer/ReactionDrawer/ReactionDrawer.component.tsx index 8658b1c8946e5d2e5bc99a8f1c596abb4101e668..1965fc9d4aa92834f18afa2dec58cf6ca4a4f6f3 100644 --- a/src/components/Map/Drawer/ReactionDrawer/ReactionDrawer.component.tsx +++ b/src/components/Map/Drawer/ReactionDrawer/ReactionDrawer.component.tsx @@ -9,6 +9,7 @@ import { currentDrawerReactionCommentsSelector } from '@/redux/bioEntity/bioEnti import { CommentItem } from '@/components/Map/Drawer/BioEntityDrawer/Comments/CommentItem.component'; import { ZERO } from '@/constants/common'; import ReactionTypeEnum from '@/utils/reaction/ReactionTypeEnum'; +import React from 'react'; import { ReferenceGroup } from './ReferenceGroup'; import { ConnectedBioEntitiesList } from './ConnectedBioEntitiesList'; @@ -40,7 +41,13 @@ export const ReactionDrawer = (): React.ReactNode => { Type: <b className="font-semibold">{type}</b> </div> <hr className="border-b border-b-divide" /> - {reaction.notes && <div className="text-sm font-normal">{reaction.notes}</div>} + {reaction.notes && ( + <div + className="text-sm font-normal" + dangerouslySetInnerHTML={{ __html: reaction.notes }} + /> + )} + <h3 className="font-semibold">Annotations:</h3> {referencesGrouped.map(group => ( <ReferenceGroup key={group.source} group={group} /> diff --git a/src/components/Map/Map.component.tsx b/src/components/Map/Map.component.tsx index d0927b3ae54f61399bddcae328192bb7eeea5686..02ee36d85777fc55ef92157dfe21deb5ea324593 100644 --- a/src/components/Map/Map.component.tsx +++ b/src/components/Map/Map.component.tsx @@ -6,6 +6,7 @@ import { MapLoader } from '@/components/Map/MapLoader/MapLoader.component'; import { MapVectorBackgroundSelector } from '@/components/Map/MapVectorBackgroundSelector/MapVectorBackgroundSelector.component'; import { useAppSelector } from '@/redux/hooks/useAppSelector'; import { vectorRenderingSelector } from '@/redux/models/models.selectors'; +import { MapAdditionalLogos } from '@/components/Map/MapAdditionalLogos'; import { MapAdditionalActions } from './MapAdditionalActions'; import { MapAdditionalOptions } from './MapAdditionalOptions'; import { PluginsDrawer } from './PluginsDrawer'; @@ -24,6 +25,7 @@ export const Map = (): JSX.Element => { <PluginsDrawer /> <Legend /> <MapAdditionalActions /> + <MapAdditionalLogos /> <MapLoader /> </div> ); diff --git a/src/components/Map/MapAdditionalLogos/MapAdditionalLogos.component.tsx b/src/components/Map/MapAdditionalLogos/MapAdditionalLogos.component.tsx new file mode 100644 index 0000000000000000000000000000000000000000..51b29d2d5f3dac64aef4aa2e60c09e9ec7cb67c8 --- /dev/null +++ b/src/components/Map/MapAdditionalLogos/MapAdditionalLogos.component.tsx @@ -0,0 +1,51 @@ +/* eslint-disable @next/next/no-img-element */ +import { twMerge } from 'tailwind-merge'; +import { + leftLogoImgValSelector, + leftLogoLinkValSelector, + leftLogoTextValSelector, + rightLogoImgValSelector, + rightLogoLinkValSelector, + rightLogoTextValSelector, +} from '@/redux/configuration/configuration.selectors'; +import { useAppSelector } from '@/redux/hooks/useAppSelector'; +import { LinkButton } from '@/shared/LinkButton'; + +export const MapAdditionalLogos = (): JSX.Element => { + const leftLink = useAppSelector(leftLogoLinkValSelector); + const leftText = useAppSelector(leftLogoTextValSelector); + const leftImage = useAppSelector(leftLogoImgValSelector); + + const rightLink = useAppSelector(rightLogoLinkValSelector); + const rightText = useAppSelector(rightLogoTextValSelector); + const rightImage = useAppSelector(rightLogoImgValSelector); + + return ( + <div className={twMerge('absolute bottom-6 left-[102px] grid grid-cols-2 gap-4')}> + {leftLink && ( + <LinkButton + type="button" + className="flex h-auto max-h-20 w-auto max-w-20 cursor-pointer items-center justify-center border-0 bg-gray-200 bg-opacity-20 hover:bg-gray-300 hover:bg-opacity-30" + data-testid="location-button" + title={leftText} + href={leftLink} + target="_blank" + > + <img alt={leftText} src={leftImage} /> + </LinkButton> + )} + {rightLink && ( + <LinkButton + type="button" + className="flex h-auto max-h-20 w-auto max-w-20 cursor-pointer items-center justify-center border-0 bg-gray-200 bg-opacity-20 hover:bg-gray-300 hover:bg-opacity-30" + data-testid="location-button" + title={rightText} + href={rightLink} + target="_blank" + > + <img alt={rightText} src={rightImage} /> + </LinkButton> + )} + </div> + ); +}; diff --git a/src/components/Map/MapAdditionalLogos/index.ts b/src/components/Map/MapAdditionalLogos/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..6ea9ee3139424b623b6496569f9727fa6b41e124 --- /dev/null +++ b/src/components/Map/MapAdditionalLogos/index.ts @@ -0,0 +1 @@ +export { MapAdditionalLogos } from './MapAdditionalLogos.component'; diff --git a/src/redux/configuration/configuration.constants.ts b/src/redux/configuration/configuration.constants.ts index 7fa5ef0ce9efa4148e36d40964f020d7492c4cca..b8d8e543936682bdde5c4eb0725c3ea75b17c90d 100644 --- a/src/redux/configuration/configuration.constants.ts +++ b/src/redux/configuration/configuration.constants.ts @@ -8,6 +8,12 @@ export const REQUEST_ACCOUNT_EMAIL = 'REQUEST_ACCOUNT_EMAIL'; export const TERMS_OF_SERVICE_ID = 'TERMS_OF_USE'; export const MATOMO_URL = 'MATOMO_URL'; export const COOKIE_POLICY_URL = 'COOKIE_POLICY_URL'; +export const LEFT_LOGO_IMG = 'LEFT_LOGO_IMG'; +export const LEFT_LOGO_LINK = 'LEFT_LOGO_LINK'; +export const LEFT_LOGO_TEXT = 'LEFT_LOGO_TEXT'; +export const RIGHT_LOGO_IMG = 'RIGHT_LOGO_IMG'; +export const RIGHT_LOGO_LINK = 'RIGHT_LOGO_LINK'; +export const RIGHT_LOGO_TEXT = 'RIGHT_LOGO_TEXT'; export const LEGEND_FILE_NAMES_IDS = [ 'LEGEND_FILE_1', diff --git a/src/redux/configuration/configuration.selectors.ts b/src/redux/configuration/configuration.selectors.ts index a89c0f72dded451de0829dbf86f1bb8ceefa2f7b..158ac40459b7ed56be24d86e6ca1efb536af5746 100644 --- a/src/redux/configuration/configuration.selectors.ts +++ b/src/redux/configuration/configuration.selectors.ts @@ -21,6 +21,12 @@ import { TERMS_OF_SERVICE_ID, COOKIE_POLICY_URL, MATOMO_URL, + LEFT_LOGO_IMG, + LEFT_LOGO_LINK, + LEFT_LOGO_TEXT, + RIGHT_LOGO_IMG, + RIGHT_LOGO_LINK, + RIGHT_LOGO_TEXT, } from './configuration.constants'; import { ConfigurationHandlersIds, ConfigurationImageHandlersIds } from './configuration.types'; @@ -157,3 +163,29 @@ export const loadingConfigurationMainSelector = createSelector( configurationSelector, state => state?.main?.loading, ); + +export const leftLogoImgValSelector = createSelector( + configurationOptionsSelector, + state => configurationAdapterSelectors.selectById(state, LEFT_LOGO_IMG)?.value, +); +export const leftLogoLinkValSelector = createSelector( + configurationOptionsSelector, + state => configurationAdapterSelectors.selectById(state, LEFT_LOGO_LINK)?.value, +); +export const leftLogoTextValSelector = createSelector( + configurationOptionsSelector, + state => configurationAdapterSelectors.selectById(state, LEFT_LOGO_TEXT)?.value, +); + +export const rightLogoImgValSelector = createSelector( + configurationOptionsSelector, + state => configurationAdapterSelectors.selectById(state, RIGHT_LOGO_IMG)?.value, +); +export const rightLogoLinkValSelector = createSelector( + configurationOptionsSelector, + state => configurationAdapterSelectors.selectById(state, RIGHT_LOGO_LINK)?.value, +); +export const rightLogoTextValSelector = createSelector( + configurationOptionsSelector, + state => configurationAdapterSelectors.selectById(state, RIGHT_LOGO_TEXT)?.value, +); diff --git a/src/services/pluginsManager/map/data/getBounds.ts b/src/services/pluginsManager/map/data/getBounds.ts index 626e13f705c08dcf163ed1a1017e99d857a7723b..a905228e9996f3f447dc301c67d7eb2a1fa3e56c 100644 --- a/src/services/pluginsManager/map/data/getBounds.ts +++ b/src/services/pluginsManager/map/data/getBounds.ts @@ -2,6 +2,7 @@ import { mapDataSizeSelector } from '@/redux/map/map.selectors'; import { store } from '@/redux/store'; import { latLngToPoint } from '@/utils/map/latLngToPoint'; import { toLonLat } from 'ol/proj'; +import { ZERO } from '@/constants/common'; import { MapManager } from '../mapManager'; type GetBoundsReturnType = @@ -29,8 +30,8 @@ export const getBounds = (): GetBoundsReturnType => { const { x: x2, y: y2 } = latLngToPoint([latY2, lngX2], mapSize, { rounded: true }); return { - x1, - y1, + x1: x1 > x2 ? ZERO : x1, // handle lat,lng overflow + y1: y1 > y2 ? ZERO : y1, // handle lat,lng overflow x2, y2, }; diff --git a/src/services/pluginsManager/map/overlays/getVisibleDataOverlays.test.ts b/src/services/pluginsManager/map/overlays/getVisibleDataOverlays.test.ts index 036a668b22bf45829ccc22e4767a40ff0c184697..c0f486b95bf1c15209090f56863fffdbeb120b73 100644 --- a/src/services/pluginsManager/map/overlays/getVisibleDataOverlays.test.ts +++ b/src/services/pluginsManager/map/overlays/getVisibleDataOverlays.test.ts @@ -3,6 +3,7 @@ import { overlaysFixture } from '@/models/fixtures/overlaysFixture'; import { OVERLAYS_INITIAL_STATE_MOCK } from '@/redux/overlays/overlays.mock'; import { RootState, store } from '@/redux/store'; import { OVERLAY_BIO_ENTITY_INITIAL_STATE_MOCK } from '@/redux/overlayBioEntity/overlayBioEntity.mock'; +import { DataOverlay } from '@/services/pluginsManager/map/overlays/types/DataOverlay'; import { getVisibleDataOverlays } from './getVisibleDataOverlays'; const ACTIVE_OVERLAYS_IDS = overlaysFixture.map(overlay => overlay.idObject); @@ -34,7 +35,9 @@ describe('getVisibleDataOverlays', () => { }) as RootState, ); - expect(getVisibleDataOverlays()).toEqual(overlaysFixture); + expect(getVisibleDataOverlays()).toEqual( + overlaysFixture.map(overlay => new DataOverlay(overlay)), + ); }); it('should return empty array if no active overlays', () => { diff --git a/src/services/pluginsManager/map/overlays/getVisibleDataOverlays.ts b/src/services/pluginsManager/map/overlays/getVisibleDataOverlays.ts index 6224d3ffe50af7b5e79591e464a76571ed3b0422..14a69d51ac535c543ed55d85b2d06b7e7b41a7fc 100644 --- a/src/services/pluginsManager/map/overlays/getVisibleDataOverlays.ts +++ b/src/services/pluginsManager/map/overlays/getVisibleDataOverlays.ts @@ -1,9 +1,46 @@ -import { activeOverlaysSelector } from '@/redux/overlayBioEntity/overlayBioEntity.selector'; +import { + activeOverlaysSelector, + overlayBioEntityDataSelector, +} from '@/redux/overlayBioEntity/overlayBioEntity.selector'; import { store } from '@/redux/store'; -import { MapOverlay } from '@/types/models'; +import { DataOverlay } from '@/services/pluginsManager/map/overlays/types/DataOverlay'; +import { modelsDataSelector } from '@/redux/models/models.selectors'; +import { DataOverlayEntry } from '@/services/pluginsManager/map/overlays/types/DataOverlayEntry'; -export const getVisibleDataOverlays = (): MapOverlay[] => { +export const getVisibleDataOverlays = (): DataOverlay[] => { const activeOverlays = activeOverlaysSelector(store.getState()); + const overlayData = overlayBioEntityDataSelector(store.getState()); - return activeOverlays; + const models = modelsDataSelector(store.getState()); + + const dataOverlays = activeOverlays.map(mapOverlay => new DataOverlay(mapOverlay)); + + dataOverlays.forEach(dataOverlay => { + const mapOverlayData = overlayData[dataOverlay.id]; + if (mapOverlayData) { + models.forEach(model => { + const entries = mapOverlayData[model.idObject]; + if (entries) { + entries.forEach(dataEntry => { + if (dataEntry.type === 'submap-link') { + dataOverlay.addEntry( + new DataOverlayEntry( + Number(dataEntry.id), + 'ALIAS', + dataEntry.modelId, + dataEntry.color, + dataEntry.value, + ), + ); + } else { + // eslint-disable-next-line no-console + console.log(`${dataEntry.type} not supported`); + } + }); + } + }); + } + }); + + return dataOverlays; }; diff --git a/src/services/pluginsManager/map/overlays/types/DataOverlay.ts b/src/services/pluginsManager/map/overlays/types/DataOverlay.ts new file mode 100644 index 0000000000000000000000000000000000000000..e79d88ba577df7f5e6881aa5181795db9151d119 --- /dev/null +++ b/src/services/pluginsManager/map/overlays/types/DataOverlay.ts @@ -0,0 +1,44 @@ +import { MapOverlay } from '@/types/models'; +import { DataOverlayEntry } from '@/services/pluginsManager/map/overlays/types/DataOverlayEntry'; + +export class DataOverlay { + id: number; + + idObject: number; + + name: string; + + order: number; + + creator: string; + + description: string; + + genomeType: string | null; + + genomeVersion: string | null; + + publicOverlay: boolean; + + type: string; + + entries: DataOverlayEntry[]; + + constructor(mapOverlay: MapOverlay) { + this.id = mapOverlay.idObject; + this.idObject = mapOverlay.idObject; + this.name = mapOverlay.name; + this.order = mapOverlay.order; + this.creator = mapOverlay.creator; + this.description = mapOverlay.description; + this.genomeType = mapOverlay.genomeType; + this.genomeVersion = mapOverlay.genomeVersion; + this.publicOverlay = mapOverlay.publicOverlay; + this.type = mapOverlay.type; + this.entries = []; + } + + public addEntry(entry: DataOverlayEntry): void { + this.entries.push(entry); + } +} diff --git a/src/services/pluginsManager/map/overlays/types/DataOverlayEntry.ts b/src/services/pluginsManager/map/overlays/types/DataOverlayEntry.ts new file mode 100644 index 0000000000000000000000000000000000000000..2627887c2cde9f251137a816fac4656a50161fdc --- /dev/null +++ b/src/services/pluginsManager/map/overlays/types/DataOverlayEntry.ts @@ -0,0 +1,27 @@ +import { Color } from '@/types/models'; + +export class DataOverlayEntry { + bioEntityId: number; + + bioEntityType: string; + + bioEntityModelId: number; + + color: Color | null; + + value: number | null; + + constructor( + bioEntityId: number, + bioEntityType: string, + bioEntityModelId: number, + color: Color | null, + value: number | null, + ) { + this.bioEntityId = bioEntityId; + this.color = color; + this.value = value; + this.bioEntityType = bioEntityType; + this.bioEntityModelId = bioEntityModelId; + } +}