Skip to content

Commit

Permalink
Merge pull request #1417 from Vizzuality/MRXN23-265-project-feature-p…
Browse files Browse the repository at this point in the history
…review-layer

[MRXN23-265] Inventory Panel: Feature preview layer
  • Loading branch information
andresgnlez authored Aug 7, 2023
2 parents 118082c + 36215bc commit 73d524a
Show file tree
Hide file tree
Showing 10 changed files with 83 additions and 53 deletions.
6 changes: 4 additions & 2 deletions app/hooks/map/constants.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import Icon from 'components/icon';
import HEXAGON_SVG from 'svgs/map/hexagon.svg?sprite';
import SQUARE_SVG from 'svgs/map/square.svg?sprite';

import { UseLegend } from './types';

export const COLORS = {
primary: '#00BFFF',
'features-preview': {
Expand Down Expand Up @@ -184,7 +186,7 @@ export const LEGEND_LAYERS = {
},
}),

'features-preview': (options) => {
'features-preview': (options: UseLegend['options']) => {
const { items } = options;

return {
Expand All @@ -202,7 +204,7 @@ export const LEGEND_LAYERS = {
: COLORS['features-preview'].ramp[i];

return {
value: item.name,
value: item.featureClassName,
color: COLOR,
};
}),
Expand Down
1 change: 1 addition & 0 deletions app/hooks/map/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -525,6 +525,7 @@ export function usePUGridPreviewLayer({
* PROYECT LAYERS
*******************************************************************
*/

export function useProjectPlanningAreaLayer({
active,
pId,
Expand Down
14 changes: 11 additions & 3 deletions app/hooks/map/types.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { PUAction } from 'store/slices/scenarios/types';

import { ItemProps as SelectedItemProps } from 'components/features/selected-item/component';
import { TargetSPFItemProps } from 'components/features/target-spf-item/types';
import { Feature } from 'types/api/feature';

export interface UseGeoJSONLayer {
cache?: number;
Expand Down Expand Up @@ -74,11 +74,16 @@ export interface UseFeaturePreviewLayer {
id?: string;
}

interface PreviewFeature extends Feature {
splitSelected?: string;
splitFeaturesSelected?: string[];
}

export interface UseFeaturePreviewLayers {
cache?: number;
active?: boolean;
bbox?: number[] | unknown;
features?: SelectedItemProps[];
features?: PreviewFeature[];
options?: {
featuresRecipe?: Record<string, any>[];
featureHoverId?: string;
Expand Down Expand Up @@ -192,7 +197,10 @@ export interface UseLegend {
min: number;
max: number;
};
items?: string[];
items?: {
featureClassName: string;
id: string;
}[];
puAction?: PUAction;
puIncludedValue?: string[];
puExcludedValue?: string[];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export const ProjectFeatureList = (): JSX.Element => {
const { selectedFeatures: visibleFeatures, search } = useAppSelector(
(state) => state['/projects/[id]']
);

const [filters, setFilters] = useState<Parameters<typeof useAllFeatures>[1]>({
sort: 'featureClassName',
});
Expand Down Expand Up @@ -71,8 +72,6 @@ export const ProjectFeatureList = (): JSX.Element => {
[filters.sort]
);

// ! this feature is partially implement until the API is ready
// ! This is about previewing the feature on the map
const toggleSeeOnMap = useCallback(
(featureId: Feature['id']) => {
const newSelectedFeatures = [...visibleFeatures];
Expand Down Expand Up @@ -184,6 +183,8 @@ export const ProjectFeatureList = (): JSX.Element => {
projectId={pid}
onSelectFeature={handleSelectFeature}
isSelected={selectedFeaturesIds.includes(feature.id)}
toggleSeeOnMap={() => toggleSeeOnMap(feature.id)}
isShown={visibleFeatures.includes(feature.id)}
/>
</li>
))}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
import { useCallback, useState, ChangeEvent, useRef, InputHTMLAttributes } from 'react';

import { useQueryClient } from 'react-query';
import { useDispatch, useSelector } from 'react-redux';

import { setSelectedFeatures as setVisibleFeatures } from 'store/slices/projects/[id]';

import { MoreHorizontal } from 'lucide-react';

Expand All @@ -28,16 +25,19 @@ const FeatureItemList = ({
projectId,
isSelected,
onSelectFeature,
toggleSeeOnMap,
isShown,
}: {
feature: Feature;
projectId: Project['id'];
isSelected: boolean;
onSelectFeature: (evt: ChangeEvent<HTMLInputElement>) => void;
toggleSeeOnMap: () => void;
isShown: boolean;
}): JSX.Element => {
const queryClient = useQueryClient();
const { addToast } = useToasts();
const dispatch = useDispatch();
const { selectedFeatures: visibleFeatures } = useSelector((state) => state['/projects/[id]']);

const [isEditable, setEditable] = useState(false);
const nameInputRef = useRef<HTMLInputElement>(null);
const { mutate: editFeature } = useEditFeature();
Expand Down Expand Up @@ -94,21 +94,6 @@ const FeatureItemList = ({
[nameInputRef, projectId, feature.id, editFeature, addToast, queryClient]
);

const toggleSeeOnMap = useCallback(
(featureId: Feature['id']) => {
const newSelectedFeatures = [...visibleFeatures];

if (!newSelectedFeatures.includes(featureId)) {
newSelectedFeatures.push(featureId);
} else {
const i = newSelectedFeatures.indexOf(featureId);
newSelectedFeatures.splice(i, 1);
}
dispatch(setVisibleFeatures(newSelectedFeatures));
},
[dispatch, visibleFeatures]
);

return (
<>
<div className="col-span-3 flex space-x-2">
Expand Down Expand Up @@ -150,8 +135,14 @@ const FeatureItemList = ({
)}
</div>
<div className="col-span-1 flex space-x-3">
<button type="button" onClick={() => toggleSeeOnMap(feature.id)}>
<Icon className="h-4 w-4" icon={true ? SHOW_SVG : HIDE_SVG} />
<button type="button" onClick={toggleSeeOnMap}>
<Icon
className={cn({
'h-4 w-4': true,
'text-blue-400': isShown,
})}
icon={isShown ? SHOW_SVG : HIDE_SVG}
/>
</button>
<Popover>
<PopoverTrigger asChild>
Expand Down
1 change: 0 additions & 1 deletion app/layout/projects/show/map/index.ts

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
import React, { useCallback, useEffect, useMemo, useState } from 'react';

import { useSelector, useDispatch } from 'react-redux';

import cx from 'classnames';

import { useRouter } from 'next/router';

import { useAppSelector } from 'store/hooks';
import { useAppDispatch, useAppSelector } from 'store/hooks';
import { setLayerSettings } from 'store/slices/projects/[id]';

import PluginMapboxGl from '@vizzuality/layer-manager-plugin-mapboxgl';
Expand All @@ -15,12 +11,14 @@ import { AnimatePresence, motion } from 'framer-motion';
import pick from 'lodash/pick';

import { useAccessToken } from 'hooks/auth';
import { useAllFeatures } from 'hooks/features';
import {
useLegend,
usePUCompareLayer,
usePUGridLayer,
useProjectPlanningAreaLayer,
useBBOX,
useFeaturePreviewLayers,
} from 'hooks/map';
import { useProject } from 'hooks/projects';
import { useScenarios } from 'hooks/scenarios';
Expand All @@ -41,13 +39,15 @@ import LegendTypeMatrix from 'components/map/legend/types/matrix';
import HelpBeacon from 'layout/help/beacon';
import { cn } from 'utils/cn';

export interface ProjectMapProps {}

export const ProjectMap: React.FC<ProjectMapProps> = () => {
export const ProjectMap = (): JSX.Element => {
const [open, setOpen] = useState(false);
const [sid1, setSid1] = useState(null);
const [sid2, setSid2] = useState(null);
const { isSidebarOpen } = useAppSelector((state) => state['/projects/[id]']);
const {
isSidebarOpen,
layerSettings,
selectedFeatures: selectedFeaturesIds,
} = useAppSelector((state) => state['/projects/[id]']);

const accessToken = useAccessToken();

Expand All @@ -59,8 +59,10 @@ export const ProjectMap: React.FC<ProjectMapProps> = () => {
const [mapTilesLoaded, setMapTilesLoaded] = useState(false);

const { query } = useRouter();
const { pid } = query as { pid: string };
// !TODO: Type tab correctly
const { pid, tab } = query as { pid: string; tab: string };
const { data = {} } = useProject(pid);

const {
id,
bbox,
Expand All @@ -75,19 +77,20 @@ export const ProjectMap: React.FC<ProjectMapProps> = () => {
bbox,
});

const allFeaturesQuery = useAllFeatures(pid);

const selectedFeaturesData = useMemo(() => {
return allFeaturesQuery.data?.data.filter((f) => selectedFeaturesIds?.includes(f.id));
}, [selectedFeaturesIds, allFeaturesQuery.data?.data]);

const { data: rawScenariosData, isFetched: rawScenariosIsFetched } = useScenarios(pid, {
filters: {
projectId: pid,
},
sort: '-lastModifiedAt',
});

const {
// Settings
layerSettings,
} = useSelector((state) => state['/projects/[id]']);

const dispatch = useDispatch();
const dispatch = useAppDispatch();

const sid = useMemo(() => {
if (sid1) return sid1;
Expand Down Expand Up @@ -131,18 +134,43 @@ export const ProjectMap: React.FC<ProjectMapProps> = () => {
},
});

const LAYERS = [PUGridLayer, PUCompareLayer, PlanningAreaLayer].filter((l) => !!l);
const FeaturePreviewLayers = useFeaturePreviewLayers({
features: selectedFeaturesData,
active: selectedFeaturesIds.length > 0,
bbox,
options: {
selectedFeatures: selectedFeaturesIds,
...layerSettings['features-preview'],
},
});

const selectedPreviewFeatures = useMemo(() => {
return selectedFeaturesData
?.map(({ featureClassName, id }) => ({ featureClassName, id }))
.sort((a, b) => {
const aIndex = selectedFeaturesIds.indexOf(a.id);
const bIndex = selectedFeaturesIds.indexOf(b.id as string);
return aIndex - bIndex;
});
}, [selectedFeaturesIds, selectedFeaturesData]);

const LAYERS = [PUGridLayer, PUCompareLayer, PlanningAreaLayer, ...FeaturePreviewLayers].filter(
(l) => !!l
);

const LEGEND = useLegend({
layers: [
...(!!selectedFeaturesData?.length ? ['features-preview'] : []),
...(!!sid1 && !sid2 ? ['frequency'] : []),

...(!!sid1 && !!sid2 ? ['compare'] : []),
...(rawScenariosIsFetched && rawScenariosData && !!rawScenariosData.length && !sid2
? ['pugrid']
: []),
],
options: {
layerSettings,
items: selectedPreviewFeatures,
},
});

Expand Down Expand Up @@ -441,7 +469,7 @@ export const ProjectMap: React.FC<ProjectMapProps> = () => {
</div>

<div
className={cx({
className={cn({
'invisible opacity-0': SCENARIOS_RUNNED.sid1Options.length <= 1,
})}
>
Expand Down
6 changes: 3 additions & 3 deletions app/layout/scenarios/edit/features/component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,12 @@ export interface ScenariosSidebarFeaturesProps {}

export const ScenariosSidebarFeatures: React.FC<ScenariosSidebarFeaturesProps> = () => {
const { query } = useRouter();
const { pid, sid } = query as { pid: string; sid: string };
const { pid, sid, tab } = query as { pid: string; sid: string; tab: string };

const scenarioSlice = getScenarioEditSlice(sid);
const { setTab, setSubTab } = scenarioSlice.actions;

const { tab, subtab } = useSelector((state) => state[`/scenarios/${sid}/edit`]);
const { subtab } = useSelector((state) => state[`/scenarios/${sid}/edit`]);
const dispatch = useDispatch();

const editable = useCanEditScenario(pid, sid);
Expand Down Expand Up @@ -100,7 +100,7 @@ export const ScenariosSidebarFeatures: React.FC<ScenariosSidebarFeaturesProps> =
);
}, [sid, scenarioData?.metadata, dispatch, setTab, setSubTab, scenarioMutation]);

if (!scenarioData || tab !== ScenarioSidebarTabs.FEATURES) return null;
if (!scenarioData || tab !== 'features') return null;

return (
<div className="flex h-full w-full flex-grow flex-col overflow-hidden">
Expand Down
2 changes: 1 addition & 1 deletion app/layout/scenarios/edit/features/set-up/component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export const ScenariosSidebarSetUp: React.FC<ScenariosSidebarSetUpProps> = () =>

const { data: scenarioData } = useScenario(sid);

if (!scenarioData || tab !== ScenarioSidebarTabs.FEATURES) return null;
// if (!scenarioData || tab !== ScenarioSidebarTabs.FEATURES) return null;

return (
<div className="flex h-full w-full flex-grow flex-col overflow-hidden">
Expand Down
2 changes: 1 addition & 1 deletion app/store/slices/projects/[id].ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ interface ProjectShowStateProps {
search: string;
filters: Record<string, unknown> | [];
sort: string;
layerSettings: Record<string, {}>;
layerSettings: Record<string, any>;
selectedFeatures: string[];
isSidebarOpen: boolean;
}
Expand Down

0 comments on commit 73d524a

Please sign in to comment.