Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Relocation of QR code & Base map layer selection component #1788

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading