import { Check, Copy, DotsSixVertical } from '@phosphor-icons/react'; import { background } from '@shared/messages'; import { initSettings, OptionsStore } from '@shared/storage/OptionsStore'; import type { Course } from '@shared/types/Course'; import { Status } from '@shared/types/Course'; import type { CourseColors } from '@shared/types/ThemeColors'; import { pickFontColor } from '@shared/util/colors'; import { StatusIcon } from '@shared/util/icons'; import Text from '@views/components/common/Text/Text'; import clsx from 'clsx'; import React, { memo, useEffect, useMemo, useRef, useState } from 'react'; import { Button } from './Button'; import { SortableListDragHandle } from './SortableListDragHandle'; /** * Props for PopupCourseBlock */ export interface PopupCourseBlockProps { className?: string; course: Course; colors: CourseColors; } const IS_STORYBOOK = import.meta.env.STORYBOOK; const CourseMeeting = memo( ({ meeting, fontColor }: { meeting: Course['schedule']['meetings'][0]; fontColor: string }) => { const dateString = meeting.getDaysString({ format: 'short' }); return ( {`${dateString} ${meeting.getTimeString({ separator: '-' })}${ meeting.location ? `, ${meeting.location.building} ${meeting.location.room}` : '' }`} ); } ); /** * The "course block" to be used in the extension popup. * * @param className - The class name to apply to the component. * @param course - The course object to display. * @param colors - The colors to use for the course block. * @param dragHandleProps - The drag handle props for the course block. * @returns The rendered PopupCourseBlock component. */ export default function PopupCourseBlock({ className, course, colors }: PopupCourseBlockProps): JSX.Element { const [enableCourseStatusChips, setEnableCourseStatusChips] = useState(false); const [isCopied, setIsCopied] = useState(false); const lastCopyTime = useRef(0); const ref = useRef(null); useEffect(() => { const initAllSettings = async () => { const { enableCourseStatusChips } = await initSettings(); setEnableCourseStatusChips(enableCourseStatusChips); }; initAllSettings(); const l1 = OptionsStore.listen('enableCourseStatusChips', async ({ newValue }) => { setEnableCourseStatusChips(newValue); }); // adds transition for shadow hover after three frames requestAnimationFrame(() => { requestAnimationFrame(() => { requestAnimationFrame(() => { if (ref.current) { ref.current.classList.add('transition-shadow-100'); } }); }); }); return () => { OptionsStore.removeListener(l1); }; }, []); // text-white or text-black based on secondaryColor const fontColor = pickFontColor(colors.primaryColor); const formattedUniqueId = course.uniqueId.toString().padStart(5, '0'); const handleClick = async () => { await background.switchToCalendarTab({ uniqueId: course.uniqueId }); window.close(); }; const handleCopy = async (e: React.MouseEvent) => { e.stopPropagation(); const now = Date.now(); if (now - lastCopyTime.current < 500) { return; } lastCopyTime.current = now; await navigator.clipboard.writeText(formattedUniqueId); setIsCopied(true); setTimeout(() => setIsCopied(false), 500); }; const meetings = useMemo( () => course.schedule.meetings.map(meeting => ( )), [course.schedule.meetings, fontColor] ); return (
{IS_STORYBOOK ? (
) : ( )}
0 ? 'mb-auto' : 'my-auto'}`, fontColor )} variant='h1-course' > {course.department} {course.number} {course.instructors.length > 0 ? <> – : ''} {course.instructors.map(v => v.toString({ format: 'last' })).join('; ')}
{meetings}
{enableCourseStatusChips && course.status !== Status.OPEN && (
)}
); }