Skip to content
Snippets Groups Projects
Commit ed9523e9 authored by Tadeusz Miesiąc's avatar Tadeusz Miesiąc
Browse files

Merge branch 'MIN-189/display-general-overlays' into 'development'

Resolve MIN-189 "/display general overlays"

Closes MIN-189

See merge request !78
parents f51cab97 b2282dfd
No related branches found
No related tags found
2 merge requests!223reset the pin numbers before search results are fetch (so the results will be...,!78Resolve MIN-189 "/display general overlays"
Pipeline #83443 passed
Showing
with 375 additions and 9 deletions
......@@ -10,7 +10,11 @@ export const GeneralOverlays = (): JSX.Element => {
<p className="mb-5 text-sm font-semibold">General Overlays:</p>
<ul>
{generalPublicOverlays.map(overlay => (
<OverlayListItem key={overlay.idObject} name={overlay.name} />
<OverlayListItem
key={overlay.idObject}
name={overlay.name}
overlayId={overlay.idObject}
/>
))}
</ul>
</div>
......
......@@ -12,7 +12,7 @@ const renderComponent = (initialStoreState: InitialStoreState = {}): { store: St
return (
render(
<Wrapper>
<OverlayListItem name="Ageing brain" />
<OverlayListItem name="Ageing brain" overlayId={21} />
</Wrapper>,
),
{
......
import { useAppDispatch } from '@/redux/hooks/useAppDispatch';
import { getOverlayBioEntityForAllModels } from '@/redux/overlayBioEntity/overlayBioEntity.thunk';
import { Button } from '@/shared/Button';
interface OverlayListItemProps {
name: string;
overlayId: number;
}
export const OverlayListItem = ({ name }: OverlayListItemProps): JSX.Element => {
const onViewOverlay = (): void => {};
export const OverlayListItem = ({ name, overlayId }: OverlayListItemProps): JSX.Element => {
const onDownloadOverlay = (): void => {};
const dispatch = useAppDispatch();
const onViewOverlay = (): void => {
dispatch(getOverlayBioEntityForAllModels({ overlayId }));
};
return (
<li className="flex flex-row flex-nowrap justify-between pl-5 [&:not(:last-of-type)]:mb-4">
......
......@@ -68,8 +68,8 @@ describe('MapAdditionalOptions - component', () => {
const actions = store.getActions();
expect(actions[FIRST_ARRAY_ELEMENT]).toStrictEqual({
payload: undefined,
type: 'modal/openOverviewImagesModal',
payload: 0,
type: 'modal/openOverviewImagesModalById',
});
});
});
import { ZERO } from '@/constants/common';
import { GetHex3ColorGradientColorWithAlpha } from '@/hooks/useTriColorLerp';
import { OverlayBioEntityRender } from '@/types/OLrendering';
import { UsePointToProjectionResult } from '@/utils/map/usePointToProjection';
import Feature from 'ol/Feature';
import Polygon, { fromExtent } from 'ol/geom/Polygon';
import { Fill, Style } from 'ol/style';
export const getOverlayFeatures = (
bioEntities: OverlayBioEntityRender[],
pointToProjection: UsePointToProjectionResult,
getHex3ColorGradientColorWithAlpha: GetHex3ColorGradientColorWithAlpha,
): Feature<Polygon>[] =>
bioEntities.map(entity => {
const feature = new Feature({
geometry: fromExtent([
...pointToProjection({ x: entity.x1, y: entity.y1 }),
...pointToProjection({ x: entity.x2, y: entity.y2 }),
]),
});
feature.setStyle(
new Style({
fill: new Fill({ color: getHex3ColorGradientColorWithAlpha(entity.value || ZERO) }),
}),
);
return feature;
});
import Geometry from 'ol/geom/Geometry';
import VectorLayer from 'ol/layer/Vector';
import VectorSource from 'ol/source/Vector';
import { useMemo } from 'react';
import { usePointToProjection } from '@/utils/map/usePointToProjection';
import { useTriColorLerp } from '@/hooks/useTriColorLerp';
import { useAppSelector } from '@/redux/hooks/useAppSelector';
import { overlayBioEntitiesForCurrentModelSelector } from '@/redux/overlayBioEntity/overlayBioEntity.selector';
import { getOverlayFeatures } from './getOverlayFeatures';
export const useOlMapOverlaysLayer = (): VectorLayer<VectorSource<Geometry>> => {
const pointToProjection = usePointToProjection();
const { getHex3ColorGradientColorWithAlpha } = useTriColorLerp();
const bioEntities = useAppSelector(overlayBioEntitiesForCurrentModelSelector);
const features = useMemo(
() => getOverlayFeatures(bioEntities, pointToProjection, getHex3ColorGradientColorWithAlpha),
[bioEntities, getHex3ColorGradientColorWithAlpha, pointToProjection],
);
const vectorSource = useMemo(() => {
return new VectorSource({
features,
});
}, [features]);
const overlaysLayer = useMemo(
() =>
new VectorLayer({
source: vectorSource,
}),
[vectorSource],
);
return overlaysLayer;
};
......@@ -4,6 +4,7 @@ import { MapConfig, MapInstance } from '../../MapViewer.types';
import { useOlMapPinsLayer } from './pinsLayer/useOlMapPinsLayer';
import { useOlMapReactionsLayer } from './reactionsLayer/useOlMapReactionsLayer';
import { useOlMapTileLayer } from './useOlMapTileLayer';
import { useOlMapOverlaysLayer } from './overlaysLayer/useOlMapOverlaysLayer';
interface UseOlMapLayersInput {
mapInstance: MapInstance;
......@@ -13,14 +14,15 @@ export const useOlMapLayers = ({ mapInstance }: UseOlMapLayersInput): MapConfig[
const tileLayer = useOlMapTileLayer();
const pinsLayer = useOlMapPinsLayer();
const reactionsLayer = useOlMapReactionsLayer();
const overlaysLayer = useOlMapOverlaysLayer();
useEffect(() => {
if (!mapInstance) {
return;
}
mapInstance.setLayers([tileLayer, reactionsLayer, pinsLayer]);
}, [reactionsLayer, tileLayer, pinsLayer, mapInstance]);
mapInstance.setLayers([tileLayer, reactionsLayer, pinsLayer, overlaysLayer]);
}, [reactionsLayer, tileLayer, pinsLayer, mapInstance, overlaysLayer]);
return [tileLayer, pinsLayer, reactionsLayer];
return [tileLayer, pinsLayer, reactionsLayer, overlaysLayer];
};
import {
maxColorValSelector,
minColorValSelector,
neutralColorValSelector,
overlayOpacitySelector,
} from '@/redux/configuration/configuration.selectors';
import { useAppSelector } from '@/redux/hooks/useAppSelector';
import { getHexTricolorGradientColorWithAlpha } from '@/utils/convert/getHexTricolorGradientColorWithAlpha';
import { useCallback } from 'react';
export type GetHex3ColorGradientColorWithAlpha = (position: number) => string;
type UseTriColorLerpReturn = {
getHex3ColorGradientColorWithAlpha: GetHex3ColorGradientColorWithAlpha;
};
export const useTriColorLerp = (): UseTriColorLerpReturn => {
const minColorValHexString = useAppSelector(minColorValSelector) || '';
const maxColorValHexString = useAppSelector(maxColorValSelector) || '';
const neutralColorValHexString = useAppSelector(neutralColorValSelector) || '';
const overlayOpacityValue = useAppSelector(overlayOpacitySelector) || '';
const getHex3ColorGradientColorWithAlpha = useCallback(
(position: number) =>
getHexTricolorGradientColorWithAlpha({
leftColor: minColorValHexString,
middleColor: neutralColorValHexString,
rightColor: maxColorValHexString,
position,
alpha: Number(overlayOpacityValue),
}),
[minColorValHexString, neutralColorValHexString, maxColorValHexString, overlayOpacityValue],
);
return { getHex3ColorGradientColorWithAlpha };
};
import { z } from 'zod';
export const configurationOptionSchema = z.object({
idObject: z.number(),
type: z.string(),
valueType: z.string(),
commonName: z.string(),
isServerSide: z.boolean(),
value: z.string().optional(),
group: z.string(),
});
import { ConfigurationOption } from '@/types/models';
export const CONFIGURATION_OPTIONS_TYPES_MOCK: string[] = [
'MIN_COLOR_VAL',
'MAX_COLOR_VAL',
'SIMPLE_COLOR_VAL',
'NEUTRAL_COLOR_VAL',
];
export const CONFIGURATION_OPTIONS_COLOURS_MOCK: ConfigurationOption[] = [
{
idObject: 29,
type: 'MIN_COLOR_VAL',
valueType: 'COLOR',
commonName: 'Overlay color for negative values',
isServerSide: false,
value: 'FF0000',
group: 'Overlays',
},
{
idObject: 30,
type: 'MAX_COLOR_VAL',
valueType: 'COLOR',
commonName: 'Overlay color for postive values',
isServerSide: false,
value: '0000FF',
group: 'Overlays',
},
{
idObject: 31,
type: 'SIMPLE_COLOR_VAL',
valueType: 'COLOR',
commonName: 'Overlay color when no values are defined',
isServerSide: false,
value: '00FF00',
group: 'Overlays',
},
{
idObject: 32,
type: 'NEUTRAL_COLOR_VAL',
valueType: 'COLOR',
commonName: 'Overlay color for value=0',
isServerSide: false,
value: 'FFFFFF',
group: 'Overlays',
},
];
import { z } from 'zod';
import { overlayLeftBioEntitySchema } from './overlayLeftBioEntitySchema';
import { overlayRightBioEntitySchema } from './overlayRightBioEntitySchema';
export const overlayBioEntitySchema = z.object({
left: overlayLeftBioEntitySchema,
right: overlayRightBioEntitySchema,
});
import { z } from 'zod';
import { colorSchema } from './colorSchema';
import { referenceSchema } from './referenceSchema';
export const overlayLeftBioEntitySchema = z.object({
id: z.number(),
model: z.number(),
glyph: z.unknown(),
submodel: z.unknown(),
compartment: z.number().nullable(),
elementId: z.union([z.string(), z.number()]),
x: z.number(),
y: z.number(),
z: z.number(),
width: z.number(),
height: z.number(),
fontSize: z.number().optional(),
fontColor: colorSchema.optional(),
fillColor: colorSchema.optional(),
borderColor: colorSchema,
visibilityLevel: z.string(),
transparencyLevel: z.string(),
notes: z.string(),
symbol: z.string().nullable(),
fullName: z.string().nullable().optional(),
abbreviation: z.unknown(),
formula: z.unknown(),
name: z.string(),
nameX: z.number().optional(),
nameY: z.number().optional(),
nameWidth: z.number().optional(),
nameHeight: z.number().optional(),
nameVerticalAlign: z.string().optional(),
nameHorizontalAlign: z.string().optional(),
synonyms: z.array(z.string()),
formerSymbols: z.array(z.string()).optional(),
activity: z.boolean().optional(),
lineWidth: z.number().optional(),
complex: z.number().nullable().optional(),
initialAmount: z.unknown().nullable(),
charge: z.unknown(),
initialConcentration: z.number().nullable().optional(),
onlySubstanceUnits: z.unknown(),
homodimer: z.number().optional(),
hypothetical: z.unknown(),
boundaryCondition: z.boolean().optional(),
constant: z.boolean().nullable().optional(),
modificationResidues: z.unknown(),
stringType: z.string(),
substanceUnits: z.boolean().nullable().optional(),
references: z.array(referenceSchema),
});
import { z } from 'zod';
import { colorSchema } from './colorSchema';
export const overlayRightBioEntitySchema = z.object({
id: z.number(),
name: z.string(),
modelName: z.boolean().nullable(),
elementId: z.string().nullable(),
reverseReaction: z.boolean().nullable(),
lineWidth: z.number().nullable().optional(),
value: z.number().nullable(),
color: colorSchema.nullable(),
description: z.string().nullable(),
});
......@@ -29,4 +29,7 @@ export const apiPath = {
getAllBackgroundsByProjectIdQuery: (projectId: string): string =>
`projects/${projectId}/backgrounds/`,
getProjectById: (projectId: string): string => `projects/${projectId}`,
getConfigurationOptions: (): string => 'configuration/options/',
getOverlayBioEntity: ({ overlayId, modelId }: { overlayId: number; modelId: number }): string =>
`projects/${PROJECT_ID}/overlays/${overlayId}/models/${modelId}/bioEntities/`,
};
import { DEFAULT_ERROR } from '@/constants/errors';
import { Loading } from '@/types/loadingState';
import { ConfigurationOption } from '@/types/models';
import { createEntityAdapter } from '@reduxjs/toolkit';
export const configurationAdapter = createEntityAdapter<ConfigurationOption>({
selectId: option => option.type,
});
const REQUEST_INITIAL_STATUS: { loading: Loading; error: Error } = {
loading: 'idle',
error: DEFAULT_ERROR,
};
export const CONFIGURATION_INITIAL_STATE =
configurationAdapter.getInitialState(REQUEST_INITIAL_STATUS);
export type ConfigurationState = typeof CONFIGURATION_INITIAL_STATE;
export const MIN_COLOR_VAL_NAME_ID = 'MIN_COLOR_VAL';
export const MAX_COLOR_VAL_NAME_ID = 'MAX_COLOR_VAL';
export const SIMPLE_COLOR_VAL_NAME_ID = 'SIMPLE_COLOR_VAL';
export const NEUTRAL_COLOR_VAL_NAME_ID = 'NEUTRAL_COLOR_VAL';
export const OVERLAY_OPACITY_NAME_ID = 'OVERLAY_OPACITY';
/* eslint-disable no-magic-numbers */
import { DEFAULT_ERROR } from '@/constants/errors';
import {
CONFIGURATION_OPTIONS_TYPES_MOCK,
CONFIGURATION_OPTIONS_COLOURS_MOCK,
} from '@/models/mocks/configurationOptionMock';
import { ConfigurationState } from './configuration.adapter';
export const CONFIGURATION_INITIAL_STORE_MOCK: ConfigurationState = {
ids: [],
entities: {},
loading: 'idle',
error: DEFAULT_ERROR,
};
/** IMPORTANT MOCK IDS MUST MATCH KEYS IN ENTITIES */
export const CONFIGURATION_INITIAL_STORE_MOCKS: ConfigurationState = {
ids: CONFIGURATION_OPTIONS_TYPES_MOCK,
entities: {
[CONFIGURATION_OPTIONS_TYPES_MOCK[0]]: CONFIGURATION_OPTIONS_COLOURS_MOCK[0],
[CONFIGURATION_OPTIONS_TYPES_MOCK[1]]: CONFIGURATION_OPTIONS_COLOURS_MOCK[1],
[CONFIGURATION_OPTIONS_TYPES_MOCK[2]]: CONFIGURATION_OPTIONS_COLOURS_MOCK[2],
[CONFIGURATION_OPTIONS_TYPES_MOCK[3]]: CONFIGURATION_OPTIONS_COLOURS_MOCK[3],
},
loading: 'idle',
error: DEFAULT_ERROR,
};
import { ActionReducerMapBuilder } from '@reduxjs/toolkit';
import { getConfigurationOptions } from './configuration.thunks';
import { ConfigurationState, configurationAdapter } from './configuration.adapter';
export const getConfigurationOptionsReducer = (
builder: ActionReducerMapBuilder<ConfigurationState>,
): void => {
builder.addCase(getConfigurationOptions.pending, state => {
state.loading = 'pending';
});
builder.addCase(getConfigurationOptions.fulfilled, (state, action) => {
if (action.payload) {
state.loading = 'succeeded';
configurationAdapter.addMany(state, action.payload);
}
});
builder.addCase(getConfigurationOptions.rejected, state => {
state.loading = 'failed';
// TODO to discuss manage state of failure
});
};
import { createSelector } from '@reduxjs/toolkit';
import { configurationAdapter } from './configuration.adapter';
import { rootSelector } from '../root/root.selectors';
import {
MAX_COLOR_VAL_NAME_ID,
MIN_COLOR_VAL_NAME_ID,
NEUTRAL_COLOR_VAL_NAME_ID,
OVERLAY_OPACITY_NAME_ID,
} from './configuration.constants';
const configurationSelector = createSelector(rootSelector, state => state.configuration);
const configurationAdapterSelectors = configurationAdapter.getSelectors();
export const minColorValSelector = createSelector(
configurationSelector,
state => configurationAdapterSelectors.selectById(state, MIN_COLOR_VAL_NAME_ID)?.value,
);
export const maxColorValSelector = createSelector(
configurationSelector,
state => configurationAdapterSelectors.selectById(state, MAX_COLOR_VAL_NAME_ID)?.value,
);
export const neutralColorValSelector = createSelector(
configurationSelector,
state => configurationAdapterSelectors.selectById(state, NEUTRAL_COLOR_VAL_NAME_ID)?.value,
);
export const overlayOpacitySelector = createSelector(
configurationSelector,
state => configurationAdapterSelectors.selectById(state, OVERLAY_OPACITY_NAME_ID)?.value,
);
import { createSlice } from '@reduxjs/toolkit';
import { getConfigurationOptionsReducer } from './configuration.reducers';
import { CONFIGURATION_INITIAL_STATE } from './configuration.adapter';
export const configurationSlice = createSlice({
name: 'configuration',
initialState: CONFIGURATION_INITIAL_STATE,
reducers: {},
extraReducers: builder => {
getConfigurationOptionsReducer(builder);
},
});
export default configurationSlice.reducer;
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment