feat: course colors (#175)

* feat: course colors

* docs: fix typo in jsdoc
This commit is contained in:
Samuel Gunter
2024-03-17 02:05:59 -05:00
committed by GitHub
parent afa634f085
commit dc77cc27da
11 changed files with 32 additions and 29 deletions

View File

@@ -1,3 +1,4 @@
import { type CourseColors, getCourseColors } from '@shared/util/colors';
import type { Serialized } from 'chrome-extension-toolkit'; import type { Serialized } from 'chrome-extension-toolkit';
import type { CourseMeeting } from './CourseMeeting'; import type { CourseMeeting } from './CourseMeeting';
@@ -75,6 +76,8 @@ export class Course {
semester: Semester; semester: Semester;
/** Unix timestamp of when the course was last scraped */ /** Unix timestamp of when the course was last scraped */
scrapedAt: number; scrapedAt: number;
/** The colors of the course when displayed */
colors: CourseColors;
constructor(course: Serialized<Course>) { constructor(course: Serialized<Course>) {
Object.assign(this, course); Object.assign(this, course);
@@ -83,6 +86,7 @@ export class Course {
if (!course.scrapedAt) { if (!course.scrapedAt) {
this.scrapedAt = Date.now(); this.scrapedAt = Date.now();
} }
this.colors = course.colors ? structuredClone(course.colors) : getCourseColors('emerald', 500);
} }
/** /**

View File

@@ -1,6 +1,7 @@
import { Course, Status } from '@shared/types/Course'; import { Course, Status } from '@shared/types/Course';
import { CourseMeeting } from '@shared/types/CourseMeeting'; import { CourseMeeting } from '@shared/types/CourseMeeting';
import Instructor from '@shared/types/Instructor'; import Instructor from '@shared/types/Instructor';
import { getCourseColors } from '@shared/util/colors';
import type { Meta, StoryObj } from '@storybook/react'; import type { Meta, StoryObj } from '@storybook/react';
import ConflictsWithWarning from '@views/components/common/ConflictsWithWarning/ConflictsWithWarning'; import ConflictsWithWarning from '@views/components/common/ConflictsWithWarning/ConflictsWithWarning';
@@ -45,6 +46,7 @@ export const ExampleCourse: Course = new Course({
uniqueId: 12345, uniqueId: 12345,
url: 'https://utdirect.utexas.edu/apps/registrar/course_schedule/20242/12345/', url: 'https://utdirect.utexas.edu/apps/registrar/course_schedule/20242/12345/',
scrapedAt: Date.now(), scrapedAt: Date.now(),
colors: getCourseColors('blue', 500),
}); });
export const ExampleCourse2: Course = new Course({ export const ExampleCourse2: Course = new Course({
courseName: 'PRINCIPLES OF COMPUTER SYSTEMS', courseName: 'PRINCIPLES OF COMPUTER SYSTEMS',
@@ -92,6 +94,7 @@ export const ExampleCourse2: Course = new Course({
uniqueId: 67890, uniqueId: 67890,
url: 'https://utdirect.utexas.edu/apps/registrar/course_schedule/20242/12345/', url: 'https://utdirect.utexas.edu/apps/registrar/course_schedule/20242/12345/',
scrapedAt: Date.now(), scrapedAt: Date.now(),
colors: getCourseColors('yellow', 500),
}); });
const meta = { const meta = {

View File

@@ -2,7 +2,6 @@ import type { DraggableProvidedDragHandleProps } from '@hello-pangea/dnd';
import { Course, Status } from '@shared/types/Course'; import { Course, Status } from '@shared/types/Course';
import { CourseMeeting } from '@shared/types/CourseMeeting'; import { CourseMeeting } from '@shared/types/CourseMeeting';
import Instructor from '@shared/types/Instructor'; import Instructor from '@shared/types/Instructor';
import type { CourseColors } from '@shared/util/colors';
import { tailwindColorways } from '@shared/util/storybook'; import { tailwindColorways } from '@shared/util/storybook';
import type { Meta, StoryObj } from '@storybook/react'; import type { Meta, StoryObj } from '@storybook/react';
import List from '@views/components/common/List/List'; import List from '@views/components/common/List/List';
@@ -63,6 +62,7 @@ const generateCourses = (count: number): Course[] => {
status: Status.WAITLISTED, status: Status.WAITLISTED,
uniqueId: 12345 + i, // Make uniqueId different for each course uniqueId: 12345 + i, // Make uniqueId different for each course
url: 'https://utdirect.utexas.edu/apps/registrar/course_schedule/20242/12345/', url: 'https://utdirect.utexas.edu/apps/registrar/course_schedule/20242/12345/',
colors: tailwindColorways[i],
}); });
courses.push(course); courses.push(course);
@@ -72,10 +72,9 @@ const generateCourses = (count: number): Course[] => {
}; };
const exampleCourses = generateCourses(numberOfCourses); const exampleCourses = generateCourses(numberOfCourses);
const generateCourseBlocks = ( const generateCourseBlocks = (course: Course, dragHandleProps: DraggableProvidedDragHandleProps) => (
{ course, colors }: { course: Course; colors: CourseColors }, <PopupCourseBlock key={course.uniqueId} course={course} colors={course.colors} dragHandleProps={dragHandleProps} />
dragHandleProps: DraggableProvidedDragHandleProps );
) => <PopupCourseBlock key={course.uniqueId} course={course} colors={colors} dragHandleProps={dragHandleProps} />;
const meta = { const meta = {
title: 'Components/Common/List', title: 'Components/Common/List',
@@ -94,9 +93,9 @@ type Story = StoryObj<typeof meta>;
export const Default: Story = { export const Default: Story = {
args: { args: {
draggables: exampleCourses.map((course, i) => ({ course, colors: tailwindColorways[i] })), draggables: exampleCourses,
children: generateCourseBlocks, children: generateCourseBlocks,
itemKey: (item: { course: Course }) => item.course.uniqueId, itemKey: (item: Course) => item.uniqueId,
gap: 12, gap: 12,
}, },
render: args => ( render: args => (

View File

@@ -54,6 +54,7 @@ export const ExampleCourse: Course = new Course({
status: Status.WAITLISTED, status: Status.WAITLISTED,
uniqueId: 12345, uniqueId: 12345,
url: 'https://utdirect.utexas.edu/apps/registrar/course_schedule/20242/12345/', url: 'https://utdirect.utexas.edu/apps/registrar/course_schedule/20242/12345/',
colors: getCourseColors('cyan', 500),
}); });
// More on how to set up stories at: https://storybook.js.org/docs/writing-stories#default-export // More on how to set up stories at: https://storybook.js.org/docs/writing-stories#default-export

View File

@@ -34,6 +34,7 @@ const exampleGovCourse: Course = new Course({
status: Status.OPEN, status: Status.OPEN,
uniqueId: 12345, uniqueId: 12345,
url: 'https://utdirect.utexas.edu/apps/registrar/course_schedule/20242/12345/', url: 'https://utdirect.utexas.edu/apps/registrar/course_schedule/20242/12345/',
colors: getCourseColors('red', 500),
}); });
const examplePsyCourse: Course = new Course({ const examplePsyCourse: Course = new Course({
@@ -65,6 +66,7 @@ const examplePsyCourse: Course = new Course({
status: Status.CLOSED, status: Status.CLOSED,
uniqueId: 12346, uniqueId: 12346,
url: 'https://utdirect.utexas.edu/apps/registrar/course_schedule/20242/12345/', url: 'https://utdirect.utexas.edu/apps/registrar/course_schedule/20242/12345/',
colors: getCourseColors('blue', 500),
}); });
const meta = { const meta = {

View File

@@ -2,6 +2,7 @@ import { Course, Status } from '@shared/types/Course';
import { CourseMeeting, DAY_MAP } from '@shared/types/CourseMeeting'; import { CourseMeeting, DAY_MAP } from '@shared/types/CourseMeeting';
import { CourseSchedule } from '@shared/types/CourseSchedule'; import { CourseSchedule } from '@shared/types/CourseSchedule';
import Instructor from '@shared/types/Instructor'; import Instructor from '@shared/types/Instructor';
import { getCourseColors } from '@shared/util/colors';
import type { Meta, StoryObj } from '@storybook/react'; import type { Meta, StoryObj } from '@storybook/react';
import CalendarCourse from '@views/components/calendar/CalendarCourseBlock/CalendarCourseMeeting'; import CalendarCourse from '@views/components/calendar/CalendarCourseBlock/CalendarCourseMeeting';
@@ -15,7 +16,6 @@ const meta = {
argTypes: { argTypes: {
course: { control: 'object' }, course: { control: 'object' },
meetingIdx: { control: 'number' }, meetingIdx: { control: 'number' },
color: { control: 'color' },
rightIcon: { control: 'object' }, rightIcon: { control: 'object' },
}, },
} satisfies Meta<typeof CalendarCourse>; } satisfies Meta<typeof CalendarCourse>;
@@ -56,8 +56,8 @@ export const Default: Story = {
season: 'Spring', season: 'Spring',
}, },
scrapedAt: Date.now(), scrapedAt: Date.now(),
colors: getCourseColors('emerald', 500),
}), }),
meetingIdx: 0, meetingIdx: 0,
color: 'red',
}, },
}; };

View File

@@ -2,6 +2,7 @@ import { Course, Status } from '@shared/types/Course';
import { CourseMeeting, DAY_MAP } from '@shared/types/CourseMeeting'; import { CourseMeeting, DAY_MAP } from '@shared/types/CourseMeeting';
import Instructor from '@shared/types/Instructor'; import Instructor from '@shared/types/Instructor';
import { UserSchedule } from '@shared/types/UserSchedule'; import { UserSchedule } from '@shared/types/UserSchedule';
import { getCourseColors } from '@shared/util/colors';
export const exampleCourse: Course = new Course({ export const exampleCourse: Course = new Course({
courseName: 'ELEMS OF COMPTRS/PROGRAMMNG-WB', courseName: 'ELEMS OF COMPTRS/PROGRAMMNG-WB',
@@ -51,6 +52,7 @@ export const exampleCourse: Course = new Course({
status: Status.OPEN, status: Status.OPEN,
uniqueId: 12345, uniqueId: 12345,
url: 'https://utdirect.utexas.edu/apps/registrar/course_schedule/20242/12345/', url: 'https://utdirect.utexas.edu/apps/registrar/course_schedule/20242/12345/',
colors: getCourseColors('blue', 500),
}); });
export const exampleSchedule: UserSchedule = new UserSchedule({ export const exampleSchedule: UserSchedule = new UserSchedule({
@@ -104,6 +106,7 @@ export const bevoCourse: Course = new Course({
season: 'Spring', season: 'Spring',
}, },
scrapedAt: Date.now(), scrapedAt: Date.now(),
colors: getCourseColors('green', 500),
}); });
export const bevoSchedule: UserSchedule = new UserSchedule({ export const bevoSchedule: UserSchedule = new UserSchedule({
@@ -158,6 +161,7 @@ export const mikeScottCS314Course: Course = new Course({
season: 'Spring', season: 'Spring',
}, },
scrapedAt: Date.now(), scrapedAt: Date.now(),
colors: getCourseColors('orange', 500),
}); });
export const mikeScottCS314Schedule: UserSchedule = new UserSchedule({ export const mikeScottCS314Schedule: UserSchedule = new UserSchedule({

View File

@@ -1,6 +1,5 @@
import { background } from '@shared/messages'; import { background } from '@shared/messages';
import { UserScheduleStore } from '@shared/storage/UserScheduleStore'; import { UserScheduleStore } from '@shared/storage/UserScheduleStore';
import { tailwindColorways } from '@shared/util/storybook';
import Divider from '@views/components/common/Divider/Divider'; import Divider from '@views/components/common/Divider/Divider';
import ExtensionRoot from '@views/components/common/ExtensionRoot/ExtensionRoot'; import ExtensionRoot from '@views/components/common/ExtensionRoot/ExtensionRoot';
import List from '@views/components/common/List/List'; import List from '@views/components/common/List/List';
@@ -86,22 +85,19 @@ export default function PopupMain(): JSX.Element {
<div className='flex-1 self-stretch overflow-y-auto px-5'> <div className='flex-1 self-stretch overflow-y-auto px-5'>
{activeSchedule?.courses?.length > 0 && ( {activeSchedule?.courses?.length > 0 && (
<List <List
draggables={activeSchedule.courses.map((course, i) => ({ draggables={activeSchedule.courses}
course,
colors: tailwindColorways[i],
}))}
onReordered={reordered => { onReordered={reordered => {
activeSchedule.courses = reordered.map(c => c.course); activeSchedule.courses = reordered;
replaceSchedule(getActiveSchedule(), activeSchedule); replaceSchedule(getActiveSchedule(), activeSchedule);
}} }}
itemKey={e => e.course.uniqueId} itemKey={e => e.uniqueId}
gap={10} gap={10}
> >
{({ course, colors }, handleProps) => ( {(course, handleProps) => (
<PopupCourseBlock <PopupCourseBlock
key={course.uniqueId} key={course.uniqueId}
course={course} course={course}
colors={colors} colors={course.colors}
dragHandleProps={handleProps} dragHandleProps={handleProps}
/> />
)} )}

View File

@@ -12,8 +12,6 @@ export interface CalendarCourseMeetingProps {
course: Course; course: Course;
/* index into course meeting array to display */ /* index into course meeting array to display */
meetingIdx?: number; meetingIdx?: number;
/** The background color for the course. */
color: string;
/** The icon to display on the right side of the course. This is optional. */ /** The icon to display on the right side of the course. This is optional. */
rightIcon?: React.ReactNode; rightIcon?: React.ReactNode;
} }
@@ -22,12 +20,11 @@ export interface CalendarCourseMeetingProps {
* `CalendarCourseMeeting` is a functional component that displays a course meeting. * `CalendarCourseMeeting` is a functional component that displays a course meeting.
* *
* @example * @example
* <CalendarCourseMeeting course={course} meeting={meeting} color="red" rightIcon={<Icon />} /> * <CalendarCourseMeeting course={course} meeting={meeting} rightIcon={<Icon />} />
*/ */
export default function CalendarCourseMeeting({ export default function CalendarCourseMeeting({
course, course,
meetingIdx, meetingIdx,
color,
rightIcon, rightIcon,
}: CalendarCourseMeetingProps): JSX.Element { }: CalendarCourseMeetingProps): JSX.Element {
let meeting: CourseMeeting | null = meetingIdx !== undefined ? course.schedule.meetings[meetingIdx] : null; let meeting: CourseMeeting | null = meetingIdx !== undefined ? course.schedule.meetings[meetingIdx] : null;

View File

@@ -1,5 +1,4 @@
import type { Course } from '@shared/types/Course'; import type { Course } from '@shared/types/Course';
import { getCourseColors } from '@shared/util/colors';
import CalendarCourseCell from '@views/components/calendar/CalendarCourseCell/CalendarCourseCell'; import CalendarCourseCell from '@views/components/calendar/CalendarCourseCell/CalendarCourseCell';
import Text from '@views/components/common/Text/Text'; import Text from '@views/components/common/Text/Text';
import type { CalendarGridCourse } from '@views/hooks/useFlattenedCourseSchedule'; import type { CalendarGridCourse } from '@views/hooks/useFlattenedCourseSchedule';
@@ -120,11 +119,8 @@ function AccountForCourseConflicts({ courseCells, setCourse }: AccountForCourseC
}); });
}); });
// Part of TODO: block.course is definitely a course object
// console.log(courseCells);
return courseCells.map((block, i) => { return courseCells.map((block, i) => {
const { courseDeptAndInstr, timeAndLocation, status, colors } = courseCells[i].componentProps; const { courseDeptAndInstr, timeAndLocation, status } = courseCells[i].componentProps;
return ( return (
<div <div
@@ -141,8 +137,7 @@ function AccountForCourseConflicts({ courseCells, setCourse }: AccountForCourseC
courseDeptAndInstr={courseDeptAndInstr} courseDeptAndInstr={courseDeptAndInstr}
timeAndLocation={timeAndLocation} timeAndLocation={timeAndLocation}
status={status} status={status}
// TODO: Change to block.componentProps.colors when colors are integrated to the rest of the project colors={block.course.colors}
colors={getCourseColors('emerald', 500) /* block.componentProps.colors */}
onClick={() => setCourse(block.course)} onClick={() => setCourse(block.course)}
/> />
</div> </div>

View File

@@ -2,6 +2,7 @@ import type { InstructionMode, ScrapedRow, Semester, StatusType } from '@shared/
import { Course, Status } from '@shared/types/Course'; import { Course, Status } from '@shared/types/Course';
import { CourseSchedule } from '@shared/types/CourseSchedule'; import { CourseSchedule } from '@shared/types/CourseSchedule';
import Instructor from '@shared/types/Instructor'; import Instructor from '@shared/types/Instructor';
import { getCourseColors } from '@shared/util/colors';
import type { SiteSupportType } from '@views/lib/getSiteSupport'; import type { SiteSupportType } from '@views/lib/getSiteSupport';
/** /**
@@ -93,6 +94,7 @@ export class CourseCatalogScraper {
description: this.getDescription(document), description: this.getDescription(document),
semester: this.getSemester(), semester: this.getSemester(),
scrapedAt: Date.now(), scrapedAt: Date.now(),
colors: getCourseColors('emerald', 500),
}); });
courses.push({ courses.push({
element: row, element: row,