Skip to content

Commit

Permalink
refactor(frontend): move QR code & improve base map layer selection c…
Browse files Browse the repository at this point in the history
…omponent (#1788)

* feat(images): add layerIcon images

* feat(layerSwitchMenu): custom layer switcher add

* fix(mapControlComponent): layerSwitchMenu add to projectDetails mapControlComponent

* fix(projectDetailsV2): pmTileLayerData pass to child component

* fix(mapComponent): customLayer switch add to remaining maps

* fix(layerSwitcherMenu): use radix dropdown to display layerSwitcher

* fix(layerSwitcher): rename tileLayer to PMTile, default OL layerSwitcher made hidden

* fix(projectDetailsV2): shift qrcode placement on bigger screens

* fix(mapControlComponent): add tooltips on menus

* fix(projectDetailsV2): add qrcode component on map

* fix(projectDetailsV2): remove legend icon

* fix(layerSwitchMenu): cursordefault remove on layer card
  • Loading branch information
NSUWAL123 authored Sep 17, 2024
1 parent c631fdc commit 01e9fd6
Show file tree
Hide file tree
Showing 13 changed files with 240 additions and 48 deletions.
Binary file added src/frontend/src/assets/images/osmLayer.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/frontend/src/assets/images/satelliteLayer.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { useAppSelector } from '@/types/reduxTypes';
import { VectorLayer } from '@/components/MapComponent/OpenLayersComponent/Layers';
import { useDispatch } from 'react-redux';
import { DataConflationActions } from '@/store/slices/DataConflationSlice';
import LayerSwitchMenu from '@/components/MapComponent/OpenLayersComponent/LayerSwitcher/LayerSwitchMenu';

const ConflationMap = () => {
const dispatch = useDispatch();
Expand Down Expand Up @@ -47,6 +48,9 @@ const ConflationMap = () => {
);
}}
/>
<div className="fmtm-absolute fmtm-right-2 fmtm-top-16 fmtm-z-20">
<LayerSwitchMenu map={map} />
</div>
<LayerSwitcherControl visible="osm" />
<div className="fmtm-absolute fmtm-bottom-20 sm:fmtm-bottom-3 fmtm-left-3 fmtm-z-50 fmtm-rounded-lg">
<MapLegend />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
import React, { useEffect, useState } from 'react';
import osmImg from '@/assets/images/osmLayer.png';
import satelliteImg from '@/assets/images/satelliteLayer.png';
import { useAppSelector } from '@/types/reduxTypes';
import { useLocation } from 'react-router-dom';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuTrigger,
DropdownMenuPortal,
} from '@/components/common/Dropdown';
import { Tooltip } from '@mui/material';

export const layerIcons = {
Satellite: satelliteImg,
OSM: osmImg,
'TMS Layer': satelliteImg,
};

type layerCardPropType = {
layer: string;
changeBaseLayerHandler: (layer: string) => void;
active: boolean;
};

const LayerCard = ({ layer, changeBaseLayerHandler, active }: layerCardPropType) => {
return (
<li
className={`fmtm-flex fmtm-justify-center fmtm-items-center fmtm-flex-col fmtm-cursor-pointer fmtm-group/layer`}
onClick={() => changeBaseLayerHandler(layer)}
onKeyDown={() => changeBaseLayerHandler(layer)}
>
{layer === 'None' ? (
<div
className={`fmtm-w-[3.5rem] fmtm-duration-200 fmtm-h-[3.5rem] fmtm-rounded-md group-hover/layer:fmtm-border-primaryRed fmtm-border-[2px] ${
active ? '!fmtm-border-primaryRed' : 'fmtm-border-grey-100'
}`}
></div>
) : (
<img
className={`group-hover/layer:fmtm-border-primaryRed fmtm-w-[3.5rem] fmtm-h-[3.5rem] fmtm-border-[2px] fmtm-bg-contain fmtm-rounded-md ${
active ? '!fmtm-border-primaryRed fmtm-duration-200' : 'fmtm-border-grey-100'
}`}
src={layerIcons[layer] ? layerIcons[layer] : satelliteImg}
alt={`${layer} Layer`}
/>
)}
<p
className={`fmtm-text-sm fmtm-flex fmtm-justify-center group-hover/layer:fmtm-text-primaryRed fmtm-duration-200 ${
active ? 'fmtm-text-primaryRed' : ''
}`}
>
{layer}
</p>
</li>
);
};

const LayerSwitchMenu = ({ map, pmTileLayerData = null }: { map: any; pmTileLayerData?: any }) => {
const { pathname } = useLocation();
const [baseLayers, setBaseLayers] = useState<string[]>(['OSM', 'Satellite', 'None']);
const [hasPMTile, setHasPMTile] = useState(false);
const [activeLayer, setActiveLayer] = useState('OSM');
const [activeTileLayer, setActiveTileLayer] = useState('');
const [isLayerMenuOpen, setIsLayerMenuOpen] = useState(false);
const projectInfo = useAppSelector((state) => state.project.projectInfo);

useEffect(() => {
if (
!projectInfo?.custom_tms_url ||
!pathname.includes('project') ||
baseLayers.includes('TMS Layer') ||
!map ||
baseLayers?.length === 0
)
return;
setBaseLayers((prev) => [...prev, 'TMS Layer']);
}, [projectInfo, pathname, map]);

useEffect(() => {
if (!pmTileLayerData || baseLayers.includes('PMTile')) return;
setHasPMTile(true);
setActiveTileLayer('PMTile');
}, [pmTileLayerData]);

const changeBaseLayer = (baseLayerTitle: string) => {
const allLayers = map.getLayers();
const filteredBaseLayers: Record<string, any> = allLayers.array_.find(
(layer) => layer?.values_?.title == 'Base maps',
);
const baseLayersCollection: Record<string, any>[] = filteredBaseLayers?.values_?.layers.array_;
baseLayersCollection
?.filter((bLayer) => bLayer?.values_?.title !== 'PMTile')
?.forEach((baseLayer) => {
if (baseLayer?.values_?.title === baseLayerTitle) {
baseLayer.setVisible(true);
setActiveLayer(baseLayerTitle);
} else {
baseLayer.setVisible(false);
}
});
};

const toggleTileLayer = (layerTitle: string) => {
const allLayers = map.getLayers();
const filteredBaseLayers: Record<string, any> = allLayers.array_.find(
(layer) => layer?.values_?.title == 'Base maps',
);
const baseLayersCollection: Record<string, any>[] = filteredBaseLayers?.values_?.layers.array_;

const tileLayer = baseLayersCollection?.find((baseLayer) => baseLayer?.values_?.title === layerTitle);
if (tileLayer) {
const isLayerVisible = tileLayer.getVisible();
tileLayer.setVisible(!isLayerVisible);
setActiveTileLayer(!isLayerVisible ? layerTitle : '');
}
};

return (
<div>
<DropdownMenu modal={false} onOpenChange={(status) => setIsLayerMenuOpen(status)}>
<DropdownMenuTrigger className="fmtm-outline-none">
<Tooltip title="Base Maps" placement={isLayerMenuOpen ? 'bottom' : 'left'}>
<div
style={{
backgroundImage: activeLayer === 'None' ? 'none' : `url(${layerIcons[activeLayer] || satelliteImg})`,
backgroundColor: 'white',
}}
className={`fmtm-relative fmtm-group fmtm-order-4 fmtm-w-10 fmtm-h-10 fmtm-border-[1px] fmtm-border-primaryRed hover:fmtm-border-[2px] fmtm-duration-75 fmtm-cursor-pointer fmtm-bg-contain fmtm-rounded-full ${
activeLayer === 'None' ? '!fmtm-border-primaryRed' : ''
}`}
></div>
</Tooltip>
</DropdownMenuTrigger>
<DropdownMenuPortal>
<DropdownMenuContent
className="!fmtm-p-0 fmtm-border-none fmtm-z-[60px]"
align="end"
alignOffset={100}
sideOffset={-42}
>
<div className="fmtm-bg-white fmtm-max-h-[20rem] fmtm-overflow-y-scroll scrollbar fmtm-flex fmtm-flex-col fmtm-gap-3 fmtm-pt-1 fmtm-rounded-lg fmtm-p-3">
<div>
<h6 className="fmtm-text-base fmtm-mb-2">Base Maps</h6>
<div className="fmtm-flex fmtm-flex-wrap fmtm-justify-between fmtm-gap-4">
{baseLayers.map((layer) => (
<LayerCard
key={layer}
layer={layer}
changeBaseLayerHandler={changeBaseLayer}
active={layer === activeLayer}
/>
))}
</div>
</div>
{hasPMTile && (
<div>
<h6 className="fmtm-text-base fmtm-mb-1">Tiles</h6>
<div className="fmtm-flex fmtm-flex-wrap fmtm-justify-between fmtm-gap-y-2">
<LayerCard
key="PMTile"
layer="PMTile"
changeBaseLayerHandler={() => toggleTileLayer('PMTile')}
active={'PMTile' === activeTileLayer}
/>
</div>
</div>
)}
</div>
</DropdownMenuContent>
</DropdownMenuPortal>
</DropdownMenu>
</div>
);
};

export default LayerSwitchMenu;
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ const pmTileLayer = (pmTileLayerData, visible) => {
return image;
}
return new TileLayer({
title: `${pmTileLayerData.name}`,
title: `PMTile`,
type: 'raster pm tiles',
visible: true,
source: new DataTile({
Expand Down Expand Up @@ -220,17 +220,7 @@ const LayerSwitcherControl = ({ map, visible = 'osm', pmTileLayerData = null })
map.addLayer(basemapLayers);
map.addControl(layerSwitcherControl);
const layerSwitcher = document.querySelector('.layer-switcher');
if (layerSwitcher) {
const layerSwitcherButton = layerSwitcher.querySelector('button');
if (layerSwitcherButton) {
layerSwitcherButton.style.width = '38px';
layerSwitcherButton.style.height = '38px';
}
layerSwitcher.style.backgroundColor = 'white';
layerSwitcher.style.display = 'flex';
layerSwitcher.style.justifyContent = 'center';
layerSwitcher.style.alignItems = 'center';
}
layerSwitcher.style.display = 'none';
if (
location.pathname.includes('project/') ||
location.pathname.includes('upload-area') ||
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,23 @@
import React, { useState } from 'react';
import AssetModules from '@/shared/AssetModules';
// @ts-ignore
import VectorLayer from 'ol/layer/Vector';
import CoreModules from '@/shared/CoreModules.js';
import { ProjectActions } from '@/store/slices/ProjectSlice';
import { useAppSelector } from '@/types/reduxTypes';
import { useLocation } from 'react-router-dom';
import ProjectOptions from '@/components/ProjectDetailsV2/ProjectOptions';
import useOutsideClick from '@/hooks/useOutsideClick';
import LayerSwitchMenu from '../MapComponent/OpenLayersComponent/LayerSwitcher/LayerSwitchMenu';
import { Tooltip } from '@mui/material';

type mapControlComponentType = {
map: any;
projectName: string;
pmTileLayerData: any;
};

const MapControlComponent = ({ map, projectName }: mapControlComponentType) => {
const MapControlComponent = ({ map, projectName, pmTileLayerData }: mapControlComponentType) => {
const btnList = [
{
id: 'add',
Expand Down Expand Up @@ -71,20 +75,20 @@ const MapControlComponent = ({ map, projectName }: mapControlComponentType) => {
};

return (
<div className="fmtm-absolute fmtm-top-16 fmtm-right-3 fmtm-z-[99] fmtm-flex fmtm-flex-col fmtm-gap-4">
<div className="fmtm-absolute fmtm-top-4 sm:fmtm-top-56 fmtm-right-3 fmtm-z-[99] fmtm-flex fmtm-flex-col fmtm-gap-4">
{btnList.map((btn) => (
<div key={btn.id}>
<Tooltip title={btn.title} placement="left">
<div
className={`fmtm-bg-white fmtm-rounded-full fmtm-p-2 hover:fmtm-bg-gray-100 fmtm-cursor-pointer fmtm-duration-300 ${
className={`fmtm-bg-white fmtm-rounded-full hover:fmtm-bg-gray-100 fmtm-cursor-pointer fmtm-duration-300 fmtm-w-9 fmtm-h-9 fmtm-min-h-9 fmtm-min-w-9 fmtm-max-w-9 fmtm-max-h-9 fmtm-flex fmtm-justify-center fmtm-items-center ${
geolocationStatus && btn.id === 'currentLocation' ? 'fmtm-text-primaryRed' : ''
}`}
onClick={() => handleOnClick(btn.id)}
title={btn.title}
>
{btn.icon}
<div>{btn.icon}</div>
</div>
</div>
</Tooltip>
))}
<LayerSwitchMenu map={map} pmTileLayerData={pmTileLayerData} />
<div
className={`fmtm-relative ${!pathname.includes('project/') ? 'fmtm-hidden' : 'sm:fmtm-hidden'}`}
ref={divRef}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import CoreModules from '@/shared/CoreModules';
import AssetModules from '@/shared/AssetModules';
import { ProjectActions } from '@/store/slices/ProjectSlice';
import environment from '@/environment';
import { GetProjectQrCode } from '@/api/Files';
import QrcodeComponent from '@/components/QrcodeComponent';
import { useAppSelector } from '@/types/reduxTypes';

Expand All @@ -23,9 +22,6 @@ const TaskSelectionPopup = ({ taskId, body, feature }: TaskSelectionPopupPropTyp
const currentProjectId: string = params.id;
const projectData = useAppSelector((state) => state.project.projectTaskBoundries);
const projectIndex = projectData.findIndex((project) => project.id.toString() === currentProjectId);

const projectName = useAppSelector((state) => state.project.projectInfo.title);
const odkToken = useAppSelector((state) => state.project.projectInfo.odk_token);
const authDetails = CoreModules.useAppSelector((state) => state.login.authDetails);
const selectedTask = {
...projectData?.[projectIndex]?.taskBoundries?.filter((indTask, i) => {
Expand All @@ -35,7 +31,6 @@ const TaskSelectionPopup = ({ taskId, body, feature }: TaskSelectionPopupPropTyp
const checkIfTaskAssignedOrNot =
selectedTask?.locked_by_username === authDetails?.username || selectedTask?.locked_by_username === null;

const { qrcode }: { qrcode: string } = GetProjectQrCode(odkToken, projectName, authDetails?.username);
useEffect(() => {
if (projectIndex != -1) {
const currentStatus = {
Expand Down Expand Up @@ -100,9 +95,12 @@ const TaskSelectionPopup = ({ taskId, body, feature }: TaskSelectionPopupPropTyp
<p className="fmtm-text-base fmtm-text-[#757575]">Locked By: {selectedTask?.locked_by_username}</p>
)}
</div>
{checkIfTaskAssignedOrNot && task_status !== 'LOCKED_FOR_MAPPING' && (
<QrcodeComponent qrcode={qrcode} projectId={currentProjectId} taskIndex={selectedTask.index} />
)}
{/* only display qr code component render inside taskPopup on mobile screen */}
<div className="sm:fmtm-hidden">
{checkIfTaskAssignedOrNot && task_status !== 'LOCKED_FOR_MAPPING' && (
<QrcodeComponent projectId={currentProjectId} taskIndex={selectedTask.index} />
)}
</div>
{body}
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
import { isValidUrl } from '@/utilfunctions/urlChecker';
import { projectInfoType, projectTaskBoundriesType } from '@/models/project/projectModel';
import { useAppSelector } from '@/types/reduxTypes';
import LayerSwitchMenu from '../MapComponent/OpenLayersComponent/LayerSwitcher/LayerSwitchMenu';

export const defaultStyles = {
lineColor: '#000000',
Expand Down Expand Up @@ -250,6 +251,9 @@ const TaskSubmissionsMap = () => {
width: '100%',
}}
>
<div className="fmtm-absolute fmtm-right-2 fmtm-top-3 fmtm-z-20">
<LayerSwitchMenu map={map} />
</div>
<LayerSwitcherControl visible={'osm'} />
{taskBoundaries && (
<VectorLayer
Expand Down
33 changes: 15 additions & 18 deletions src/frontend/src/components/QrcodeComponent.tsx
Original file line number Diff line number Diff line change
@@ -1,39 +1,36 @@
import React from 'react';
import CoreModules from '@/shared/CoreModules';
import AssetModules from '@/shared/AssetModules';
import { useAppSelector } from '@/types/reduxTypes';
import { GetProjectQrCode } from '@/api/Files';

type tasksComponentType = {
qrcode: string;
projectId: string;
taskIndex: number;
projectId?: string;
taskIndex?: number;
};

const TasksComponent = ({ qrcode, projectId, taskIndex }: tasksComponentType) => {
const QrcodeComponent = ({ projectId, taskIndex }: tasksComponentType) => {
const downloadQR = () => {
const downloadLink = document.createElement('a');
downloadLink.href = qrcode;
downloadLink.download = `Project_${projectId}_Task_${taskIndex}`;
downloadLink.click();
};

const projectName = useAppSelector((state) => state.project.projectInfo.title);
const odkToken = useAppSelector((state) => state.project.projectInfo.odk_token);
const authDetails = CoreModules.useAppSelector((state) => state.login.authDetails);
const { qrcode }: { qrcode: string } = GetProjectQrCode(odkToken, projectName, authDetails?.username);

return (
<div className="fmtm-flex fmtm-justify-center sm:fmtm-py-5 fmtm-border-t-[1px]">
<div className="fmtm-p-5 fmtm-border-[1px] fmtm-rounded-lg fmtm-relative fmtm-hidden sm:fmtm-block">
<div className="fmtm-relative fmtm-hidden sm:fmtm-block fmtm-bg-white fmtm-p-2 !fmtm-w-[8.5rem] fmtm-rounded-tl-lg fmtm-rounded-bl-lg">
{qrcode == '' ? (
<CoreModules.Skeleton width={170} height={170} />
) : (
<img id="qrcodeImg" src={qrcode} alt="qrcode" />
<img id="qrcodeImg" src={qrcode} alt="qrcode" className="" />
)}
<div className="fmtm-rounded-full fmtm-w-10 fmtm-h-10 fmtm-flex fmtm-justify-center fmtm-items-center fmtm-shadow-xl fmtm-absolute fmtm-bottom-0 -fmtm-right-5 fmtm-bg-white ">
<button
onClick={downloadQR}
disabled={qrcode == '' ? true : false}
aria-label="download qrcode"
className={` ${qrcode === '' ? 'fmtm-cursor-not-allowed fmtm-opacity-50' : 'fmtm-cursor-pointer'}`}
title="Download QR Code"
>
<AssetModules.FileDownloadIcon />
</button>
</div>
<p className="fmtm-text-center fmtm-leading-4 fmtm-text-sm fmtm-mt-2">Scan to load project on ODK</p>
</div>
<div className="fmtm-block sm:fmtm-hidden">
<button
Expand All @@ -53,4 +50,4 @@ const TasksComponent = ({ qrcode, projectId, taskIndex }: tasksComponentType) =>
);
};

export default TasksComponent;
export default QrcodeComponent;
Loading

0 comments on commit 01e9fd6

Please sign in to comment.