Skip to content

Commit

Permalink
refactor: map components into individual files
Browse files Browse the repository at this point in the history
  • Loading branch information
doprz committed Feb 26, 2025
1 parent 6d6bb5d commit 6b8724d
Show file tree
Hide file tree
Showing 6 changed files with 313 additions and 300 deletions.
288 changes: 4 additions & 284 deletions src/views/components/map/CampusMap.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
/* eslint-disable no-nested-ternary */
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';

import { Button } from '../common/Button';
import DaySelector from './DaySelector';
import DevToggles from './DevToggles';
import FullscreenButton from './FullscreenButton';
import { graphNodes } from './graphNodes';
import type { ProcessInPersonMeetings } from './Map';
import { Path } from './Path';
import { calcDirectPathStats, PathStats } from './PathStats';
import type { DayCode, NodeId, NodeType } from './types';
import { DAY_MAPPING } from './types';
import { getMidpoint } from './utils';
import ZoomPanControls from './ZoomPanControls';

// Image: 783x753
const UTMapURL = new URL('/src/assets/UT-Map.svg', import.meta.url).href;
Expand All @@ -29,289 +32,6 @@ type SelectedBuildings = {
end: NodeId | null;
};

interface DaySelectorProps {
selectedDay: DayCode | null;
onDaySelect: (day: DayCode) => void;
}

/**
* DaySelector component allows users to select a day from a list of days.
*
* @param selectedDay - The currently selected day.
* @param onDaySelect - Callback function to handle day selection.
*
* @returns The rendered DaySelector component.
*/
const DaySelector = ({ selectedDay, onDaySelect }: DaySelectorProps): JSX.Element => (
<div className='flex gap-2 rounded-md bg-white/90 p-2 shadow-sm'>
{(Object.keys(DAY_MAPPING) as DayCode[]).map(day => (
<Button
key={day}
onClick={() => onDaySelect(day)}
color='ut-burntorange'
variant={selectedDay === day ? 'filled' : 'minimal'}
size='mini'
className='px-3 py-1'
>
{day}
</Button>
))}
</div>
);

/**
* FullscreenButton component provides a toggle for fullscreen mode
*/
const FullscreenButton = ({ containerRef }: { containerRef: React.RefObject<HTMLDivElement> }): JSX.Element => {
const [isFullscreen, setIsFullscreen] = useState<boolean>(false);

useEffect(() => {
const handleFullscreenChange = () => {
setIsFullscreen(!!document.fullscreenElement);
};

document.addEventListener('fullscreenchange', handleFullscreenChange);
return () => {
document.removeEventListener('fullscreenchange', handleFullscreenChange);
};
}, []);

const toggleFullscreen = () => {
if (!document.fullscreenElement && containerRef.current) {
containerRef.current.requestFullscreen().catch(err => {
console.error(`Error attempting to enable fullscreen: ${err.message}`);
});
} else if (document.fullscreenElement) {
document.exitFullscreen().catch(err => {
console.error(`Error attempting to exit fullscreen: ${err.message}`);
});
}
};

return (
<div className='rounded-md bg-white/90 p-2 shadow-sm'>
<Button
onClick={toggleFullscreen}
color='ut-burntorange'
variant='minimal'
size='mini'
className='flex items-center gap-1 px-3 py-1'
>
<svg
xmlns='http://www.w3.org/2000/svg'
width='16'
height='16'
viewBox='0 0 24 24'
fill='none'
stroke='currentColor'
strokeWidth='2'
strokeLinecap='round'
strokeLinejoin='round'
>
{isFullscreen ? (
<path d='M8 3v3a2 2 0 0 1-2 2H3m18 0h-3a2 2 0 0 1-2-2V3m0 18v-3a2 2 0 0 1 2-2h3M3 16h3a2 2 0 0 1 2 2v3' />
) : (
<path d='M8 3H5a2 2 0 0 0-2 2v3m18 0V5a2 2 0 0 0-2-2h-3m0 18h3a2 2 0 0 0 2-2v-3M3 16v3a2 2 0 0 0 2 2h3' />
)}
</svg>
{isFullscreen ? 'Exit Fullscreen' : 'Fullscreen'}
</Button>
</div>
);
};

interface DevTogglesProps {
dynamicRendering: boolean;
showBuildings: boolean;
showIntersections: boolean;
showWalkways: boolean;
showBuildingText: boolean;
showPrioritizedOnly: boolean;
onToggleDynamicRendering: () => void;
onToggleBuildings: () => void;
onToggleIntersections: () => void;
onToggleWalkways: () => void;
onToggleBuildingText: () => void;
onTogglePrioritizedOnly: () => void;
}

/**
* DevToggles component allows developers to toggle visibility of map elements.
*
* @param dynamicRendering - Whether to enable dynamic rendering.
* @param showBuildings - Whether to show buildings on the map.
* @param showIntersections - Whether to show intersections on the map.
* @param showWalkways - Whether to show walkways on the map.
* @param onToggleDynamicRendering - Callback function to toggle dynamic rendering.
* @param onToggleBuildings - Callback function to toggle buildings visibility.
* @param onToggleIntersections - Callback function to toggle intersections visibility.
* @param onToggleWalkways - Callback function to toggle walkways visibility.
*
* @returns The rendered DevToggles component.
*/
const DevToggles = ({
dynamicRendering,
showBuildings,
showIntersections,
showWalkways,
showBuildingText,
showPrioritizedOnly,
onToggleDynamicRendering,
onToggleBuildings,
onToggleIntersections,
onToggleWalkways,
onToggleBuildingText,
onTogglePrioritizedOnly,
}: DevTogglesProps): JSX.Element => {
const [isCollapsed, setIsCollapsed] = useState<boolean>(false);

return (
<div className='flex flex-col gap-2 rounded-md bg-white/90 p-2 shadow-sm'>
<div className='flex items-center justify-between text-xs text-gray-700 font-semibold'>
<span>Dev Controls</span>
<button
onClick={() => setIsCollapsed(prev => !prev)}
className='ml-2 p-1 text-gray-500 hover:text-gray-800'
>
<svg
xmlns='http://www.w3.org/2000/svg'
width='14'
height='14'
viewBox='0 0 24 24'
fill='none'
stroke='currentColor'
strokeWidth='2'
strokeLinecap='round'
strokeLinejoin='round'
>
{isCollapsed ? <polyline points='6 9 12 15 18 9' /> : <polyline points='18 15 12 9 6 15' />}
</svg>
</button>
</div>
{!isCollapsed && (
<div className='flex flex-col gap-1'>
<label className='flex cursor-pointer items-center gap-2 text-xs'>
<input type='checkbox' checked={dynamicRendering} onChange={onToggleDynamicRendering} />
Dynamic Rendering
</label>
<label className='flex cursor-pointer items-center gap-2 text-xs'>
<input type='checkbox' checked={showBuildings} onChange={onToggleBuildings} />
Show Buildings
</label>
<label className='flex cursor-pointer items-center gap-2 text-xs'>
<input type='checkbox' checked={showBuildingText} onChange={onToggleBuildingText} />
Show Building Text
</label>
<label className='flex cursor-pointer items-center gap-2 text-xs'>
<input type='checkbox' checked={showPrioritizedOnly} onChange={onTogglePrioritizedOnly} />
Prioritized Buildings Only
</label>
<label className='flex cursor-pointer items-center gap-2 text-xs'>
<input type='checkbox' checked={showIntersections} onChange={onToggleIntersections} />
Show Intersections
</label>
<label className='flex cursor-pointer items-center gap-2 text-xs'>
<input type='checkbox' checked={showWalkways} onChange={onToggleWalkways} />
Show Walkways
</label>
</div>
)}
</div>
);
};

/**
* TimeWarningLabel component that renders a warning label on a map.
* The label consists of a circle with a text inside it, indicating the number of minutes.
*
* @param x - The x-coordinate for the center of the circle.
* @param y - The y-coordinate for the center of the circle.
* @param minutes - The number of minutes to display inside the circle.
* @returns A JSX element representing the warning label.
*/
const TimeWarningLabel = ({ x, y, minutes }: { x: number; y: number; minutes: number }): JSX.Element => (
<g>
<circle cx={x} cy={y} r={12} fill='white' stroke='#FF4444' strokeWidth={2} />
<text x={x} y={y} textAnchor='middle' dominantBaseline='middle' fill='#FF4444' fontSize='10' fontWeight='bold'>
{minutes}&apos;
</text>
</g>
);

interface ZoomPanControlsProps {
zoomIn: () => void;
zoomOut: () => void;
resetZoomPan: () => void;
zoomLevel: number;
}

/**
* ZoomPanControls component provides buttons for zooming and panning.
*
* @param zoomIn - Function to zoom in the map.
* @param zoomOut - Function to zoom out the map.
* @param resetZoomPan - Function to reset zoom and pan to default.
* @param zoomLevel - Current zoom level.
*
* @returns The rendered ZoomPanControls component.
*/
const ZoomPanControls = ({ zoomIn, zoomOut, resetZoomPan, zoomLevel }: ZoomPanControlsProps): JSX.Element => (
<div className='flex gap-2 rounded-md bg-white/90 p-2 shadow-sm'>
<Button onClick={zoomIn} color='ut-burntorange' variant='minimal' size='mini' className='px-3 py-1'>
<svg
xmlns='http://www.w3.org/2000/svg'
width='16'
height='16'
viewBox='0 0 24 24'
fill='none'
stroke='currentColor'
strokeWidth='2'
strokeLinecap='round'
strokeLinejoin='round'
>
<circle cx='11' cy='11' r='8' />
<line x1='21' y1='21' x2='16.65' y2='16.65' />
<line x1='11' y1='8' x2='11' y2='14' />
<line x1='8' y1='11' x2='14' y2='11' />
</svg>
</Button>
<Button onClick={zoomOut} color='ut-burntorange' variant='minimal' size='mini' className='px-3 py-1'>
<svg
xmlns='http://www.w3.org/2000/svg'
width='16'
height='16'
viewBox='0 0 24 24'
fill='none'
stroke='currentColor'
strokeWidth='2'
strokeLinecap='round'
strokeLinejoin='round'
>
<circle cx='11' cy='11' r='8' />
<line x1='21' y1='21' x2='16.65' y2='16.65' />
<line x1='8' y1='11' x2='14' y2='11' />
</svg>
</Button>
<Button onClick={resetZoomPan} color='ut-burntorange' variant='minimal' size='mini' className='px-3 py-1'>
<svg
xmlns='http://www.w3.org/2000/svg'
width='16'
height='16'
viewBox='0 0 24 24'
fill='none'
stroke='currentColor'
strokeWidth='2'
strokeLinecap='round'
strokeLinejoin='round'
>
<path d='M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z' />
<polyline points='9 22 9 12 15 12 15 22' />
</svg>
</Button>
<span className='flex items-center text-xs font-medium'>{Math.round(zoomLevel * 100)}%</span>
</div>
);

type CampusMapProps = {
processedCourses: ProcessInPersonMeetings[];
};
Expand Down
32 changes: 16 additions & 16 deletions src/views/components/map/DaySelector.tsx
Original file line number Diff line number Diff line change
@@ -1,38 +1,38 @@
import type { ThemeColor } from '@shared/types/ThemeColors';
import React from 'react';

import { Button } from '../common/Button';
import type { DayCode } from './types';
import { DAY_MAPPING } from './types';

type DaySelectorProps = {
interface DaySelectorProps {
selectedDay: DayCode | null;
onDaySelect: (day: DayCode) => void;
};
}

/**
* DaySelector component allows users to select a day from a predefined set of days.
* DaySelector component allows users to select a day from a list of days.
*
* @param selectedDay - The currently selected day.
* @param onDaySelect - Callback function to handle day selection.
*
* @returns The rendered DaySelector component.
*/
export const DaySelector = ({ selectedDay, onDaySelect }: DaySelectorProps) => (
<div className='flex gap-2 rounded-md bg-white/90 p-2 shadow-sm'>
{(Object.keys(DAY_MAPPING) as DayCode[]).map(day => {
const color = (selectedDay === day ? 'ut-burntorange' : 'ut-white') as ThemeColor;
return (
// const DaySelector = ({ selectedDay, onDaySelect }: DaySelectorProps): JSX.Element => (
export default function DaySelector({ selectedDay, onDaySelect }: DaySelectorProps): JSX.Element {
return (
<div className='flex gap-2 rounded-md bg-white/90 p-2 shadow-sm'>
{(Object.keys(DAY_MAPPING) as DayCode[]).map(day => (
<Button
key={day}
onClick={() => onDaySelect(day)}
variant={selectedDay === day ? 'filled' : 'outline'}
color={color}
className='rounded-sm'
color='ut-burntorange'
variant={selectedDay === day ? 'filled' : 'minimal'}
size='mini'
className='px-3 py-1'
>
{day}
</Button>
);
})}
</div>
);
))}
</div>
);
}
Loading

0 comments on commit 6b8724d

Please sign in to comment.