Skip to content

Commit

Permalink
refactor: move code around and optimize
Browse files Browse the repository at this point in the history
  • Loading branch information
KevinWu098 committed Dec 8, 2024
1 parent 5b4736a commit b8dd5f6
Show file tree
Hide file tree
Showing 7 changed files with 269 additions and 254 deletions.
5 changes: 2 additions & 3 deletions apps/antalmanac/src/components/Calendar/CalendarRoot.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { memo, SyntheticEvent, useCallback, useEffect, useMemo, useState } from
import { Calendar, DateLocalizer, momentLocalizer, Views } from 'react-big-calendar';
import { shallow } from 'zustand/shallow';

import CalendarToolbar from './CalendarToolbar';
import { CalendarToolbar } from './CalendarToolbar';
import CourseCalendarEvent, { CalendarEvent, CourseEvent } from './CourseCalendarEvent';

import { CalendarCourseEvent } from '$components/Calendar/calendar-course-event';
Expand All @@ -17,6 +17,7 @@ import { useHoveredStore } from '$stores/HoveredStore';
import { useTimeFormatStore } from '$stores/SettingsStore';

const localizer = momentLocalizer(moment);
const views = [Views.WEEK, Views.WORK_WEEK];
const components = { event: CalendarCourseEvent };

export const ScheduleCalendar = memo(() => {
Expand Down Expand Up @@ -149,8 +150,6 @@ export const ScheduleCalendar = memo(() => {
[showFinalsSchedule, finalsDateFormat, calendarGutterTimeFormat, calendarTimeFormat]
);

const views = useMemo(() => [Views.WEEK, Views.WORK_WEEK], []);

useEffect(() => {
/**
* If a final is on a Saturday or Sunday, let the calendar start on Saturday
Expand Down
258 changes: 8 additions & 250 deletions apps/antalmanac/src/components/Calendar/CalendarToolbar.tsx
Original file line number Diff line number Diff line change
@@ -1,43 +1,17 @@
import {
Add as AddIcon,
ArrowDropDown as ArrowDropDownIcon,
Edit as EditIcon,
Undo as UndoIcon,
Clear as ClearIcon,
} from '@mui/icons-material';
import { Box, Button, IconButton, Paper, Popover, Tooltip, Typography, useTheme } from '@mui/material';
import { useState, useMemo, useCallback, useEffect } from 'react';
import { Undo as UndoIcon } from '@mui/icons-material';
import { Box, Button, IconButton, Paper, Tooltip } from '@mui/material';
import { useState, useCallback, useEffect, memo } from 'react';

import CustomEventDialog from './Toolbar/CustomEventDialog/CustomEventDialog';

import { changeCurrentSchedule, undoDelete } from '$actions/AppStoreActions';
import { undoDelete } from '$actions/AppStoreActions';
import { SelectSchedulePopover } from '$components/Calendar/Toolbar/schedule-select/schedule-select';
import { ClearScheduleButton } from '$components/buttons/Clear';
import { CopyScheduleButton } from '$components/buttons/Copy';
import DownloadButton from '$components/buttons/Download';
import ScreenshotButton from '$components/buttons/Screenshot';
import AddScheduleDialog from '$components/dialogs/AddSchedule';
import DeleteScheduleDialog from '$components/dialogs/DeleteSchedule';
import RenameScheduleDialog from '$components/dialogs/RenameSchedule';
import analyticsEnum, { logAnalytics } from '$lib/analytics';
import AppStore from '$stores/AppStore';

function handleScheduleChange(index: number) {
logAnalytics({
category: analyticsEnum.calendar.title,
action: analyticsEnum.calendar.actions.CHANGE_SCHEDULE,
});
changeCurrentSchedule(index);
}

/**
* Creates an event handler callback that will change the current schedule to the one at a specified index.
*/
function createScheduleSelector(index: number) {
return () => {
handleScheduleChange(index);
};
}

function handleUndo() {
logAnalytics({
category: analyticsEnum.calendar.title,
Expand All @@ -46,222 +20,6 @@ function handleUndo() {
undoDelete(null);
}

interface RenameScheduleButtonProps {
index: number;
disabled?: boolean;
}

function RenameScheduleButton({ index, disabled }: RenameScheduleButtonProps) {
const [open, setOpen] = useState(false);

const handleOpen = useCallback(() => {
setOpen(true);
}, []);

const handleClose = useCallback(() => {
setOpen(false);
}, []);

return (
<Box>
<Tooltip title="Rename Schedule">
<span>
<IconButton onClick={handleOpen} size="small" disabled={disabled}>
<EditIcon />
</IconButton>
</span>
</Tooltip>
<RenameScheduleDialog fullWidth open={open} index={index} onClose={handleClose} />
</Box>
);
}

interface DeleteScheduleButtonProps {
index: number;
disabled?: boolean;
}

function DeleteScheduleButton({ index, disabled }: DeleteScheduleButtonProps) {
const [open, setOpen] = useState(false);

const handleOpen = useCallback(() => {
setOpen(true);
}, []);

const handleClose = useCallback(() => {
setOpen(false);
}, []);

return (
<Box>
<Tooltip title="Delete Schedule">
<span>
<IconButton
onClick={handleOpen}
size="small"
disabled={AppStore.schedule.getNumberOfSchedules() === 1 || disabled}
>
<ClearIcon />
</IconButton>
</span>
</Tooltip>
<DeleteScheduleDialog fullWidth open={open} index={index} onClose={handleClose} />
</Box>
);
}

interface AddScheduleButtonProps {
disabled: boolean;
}

/**
* MenuItem nested in the select menu to add a new schedule through a dialog.
*/
function AddScheduleButton({ disabled }: AddScheduleButtonProps) {
const [open, setOpen] = useState(false);

const handleOpen = useCallback(() => {
setOpen(true);
}, []);

const handleClose = useCallback(() => {
setOpen(false);
}, []);

return (
<>
<Button color="inherit" onClick={handleOpen} sx={{ display: 'flex', gap: 1 }} disabled={disabled}>
<AddIcon />
<Typography whiteSpace="nowrap" textOverflow="ellipsis" overflow="hidden" textTransform="none">
Add Schedule
</Typography>
</Button>
<AddScheduleDialog fullWidth open={open} onClose={handleClose} />
</>
);
}

/**
* Simulates an HTML select element using a popover.
*
* Can select a schedule, and also control schedule settings with buttons.
*/
function SelectSchedulePopover(props: { scheduleNames: string[] }) {
const [currentScheduleIndex, setCurrentScheduleIndex] = useState(() => AppStore.getCurrentScheduleIndex());
const [skeletonMode, setSkeletonMode] = useState(() => AppStore.getSkeletonMode());

const [anchorEl, setAnchorEl] = useState<HTMLElement>();

const theme = useTheme();

// TODO: maybe these widths should be dynamic based on i.e. the viewport width?

const minWidth = useMemo(() => 100, []);
const maxWidth = useMemo(() => 150, []);

const open = useMemo(() => Boolean(anchorEl), [anchorEl]);

const currentScheduleName = useMemo(() => {
return props.scheduleNames[currentScheduleIndex];
}, [props.scheduleNames, currentScheduleIndex]);

const handleClick = useCallback((event: React.MouseEvent<HTMLButtonElement>) => {
setAnchorEl(event.currentTarget);
}, []);

const handleClose = useCallback(() => {
setAnchorEl(undefined);
}, []);

const handleScheduleIndexChange = useCallback(() => {
setCurrentScheduleIndex(AppStore.getCurrentScheduleIndex());
}, []);

const handleSkeletonModeChange = () => {
setSkeletonMode(AppStore.getSkeletonMode());
};

useEffect(() => {
AppStore.on('addedCoursesChange', handleScheduleIndexChange);
AppStore.on('customEventsChange', handleScheduleIndexChange);
AppStore.on('colorChange', handleScheduleIndexChange);
AppStore.on('currentScheduleIndexChange', handleScheduleIndexChange);
AppStore.on('skeletonModeChange', handleSkeletonModeChange);

return () => {
AppStore.off('addedCoursesChange', handleScheduleIndexChange);
AppStore.off('customEventsChange', handleScheduleIndexChange);
AppStore.off('colorChange', handleScheduleIndexChange);
AppStore.off('currentScheduleIndexChange', handleScheduleIndexChange);
AppStore.off('skeletonModeChange', handleSkeletonModeChange);
};
}, [handleScheduleIndexChange]);

return (
<Box>
<Button
size="small"
color="inherit"
variant="outlined"
onClick={handleClick}
sx={{ minWidth, maxWidth, justifyContent: 'space-between' }}
>
<Typography whiteSpace="nowrap" textOverflow="ellipsis" overflow="hidden" textTransform="none">
{currentScheduleName}
</Typography>
<ArrowDropDownIcon />
</Button>

<Popover
open={open}
anchorEl={anchorEl}
onClose={handleClose}
anchorOrigin={{ vertical: 'bottom', horizontal: 'left' }}
>
<Box padding={1}>
{props.scheduleNames.map((name, index) => (
<Box key={index} display="flex" alignItems="center" gap={1}>
<Box flexGrow={1}>
<Button
color="inherit"
sx={{
minWidth,
maxWidth,
width: '100%',
display: 'flex',
justifyContent: 'flex-start',
background:
index === currentScheduleIndex ? theme.palette.action.selected : undefined,
}}
onClick={createScheduleSelector(index)}
>
<Typography
overflow="hidden"
whiteSpace="nowrap"
textTransform="none"
textOverflow="ellipsis"
>
{name}
</Typography>
</Button>
</Box>
<Box display="flex" alignItems="center" gap={0.5}>
<CopyScheduleButton index={index} disabled={skeletonMode} />
<RenameScheduleButton index={index} disabled={skeletonMode} />
<DeleteScheduleButton index={index} disabled={skeletonMode} />
</Box>
</Box>
))}

<Box marginY={1} />

<AddScheduleButton disabled={skeletonMode} />
</Box>
</Popover>
</Box>
);
}

export interface CalendarPaneToolbarProps {
scheduleNames: string[];
currentScheduleIndex: number;
Expand All @@ -272,7 +30,7 @@ export interface CalendarPaneToolbarProps {
/**
* The root toolbar will pass down the schedule names to its children.
*/
function CalendarPaneToolbar(props: CalendarPaneToolbarProps) {
export const CalendarToolbar = memo((props: CalendarPaneToolbarProps) => {
const { showFinalsSchedule, toggleDisplayFinalsSchedule } = props;
const [scheduleNames, setScheduleNames] = useState(AppStore.getScheduleNames());
const [skeletonMode, setSkeletonMode] = useState(AppStore.getSkeletonMode());
Expand Down Expand Up @@ -359,6 +117,6 @@ function CalendarPaneToolbar(props: CalendarPaneToolbarProps) {
</Box>
</Paper>
);
}
});

export default CalendarPaneToolbar;
CalendarToolbar.displayName = 'CalendarToolbar';
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { Add as AddIcon } from '@mui/icons-material';
import { Box, Button, Typography } from '@mui/material';
import { useCallback, useState } from 'react';

import AddScheduleDialog from '$components/dialogs/AddSchedule';

interface AddScheduleButtonProps {
disabled: boolean;
}

/**
* MenuItem nested in the select menu to add a new schedule through a dialog.
*/
export function AddScheduleButton({ disabled }: AddScheduleButtonProps) {
const [open, setOpen] = useState(false);

const handleOpen = useCallback(() => {
setOpen(true);
}, []);

const handleClose = useCallback(() => {
setOpen(false);
}, []);

return (
<Box>
<Button color="inherit" onClick={handleOpen} sx={{ display: 'flex', gap: 1 }} disabled={disabled}>
<AddIcon />
<Typography whiteSpace="nowrap" textOverflow="ellipsis" overflow="hidden" textTransform="none">
Add Schedule
</Typography>
</Button>
<AddScheduleDialog fullWidth open={open} onClose={handleClose} />
</Box>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { Clear as ClearIcon } from '@mui/icons-material';
import { Box, IconButton, Tooltip } from '@mui/material';
import { useCallback, useState } from 'react';

import DeleteScheduleDialog from '$components/dialogs/DeleteSchedule';
import AppStore from '$stores/AppStore';

interface DeleteScheduleButtonProps {
index: number;
disabled?: boolean;
}

export function DeleteScheduleButton({ index, disabled }: DeleteScheduleButtonProps) {
const [open, setOpen] = useState(false);

const handleOpen = useCallback(() => {
setOpen(true);
}, []);

const handleClose = useCallback(() => {
setOpen(false);
}, []);

return (
<Box>
<Tooltip title="Delete Schedule">
<span>
<IconButton
onClick={handleOpen}
size="small"
disabled={AppStore.schedule.getNumberOfSchedules() === 1 || disabled}
>
<ClearIcon />
</IconButton>
</span>
</Tooltip>
<DeleteScheduleDialog fullWidth open={open} index={index} onClose={handleClose} />
</Box>
);
}
Loading

0 comments on commit b8dd5f6

Please sign in to comment.