feat: enable TS strict mode (#168)
* feat: enable TS strict mode * fix: strict TS errors * fix: strict TS errors * fix: strict TS errors * fix: strict TS errors * fix: strict TS errors * fix: strict TS errors * fix: strict TS errors * fix: strict TS errors * fix: colors bug with default * fix: strict TS errors * fix: strict TS errors * fix: strict TS errors * fix: strict TS errors * fix: strict TS errors * fix: strict TS errors * fix: strict TS errors * fix: strict TS errors * fix: strict TS errors * fix: strict TS errors * fix: strict TS errors * fix: text type errors * fix: strict TS errors * fix: strict TS errors * fix: strict TS errors - add definite assignment assertion * fix: strict TS errors - add definite assignment assertion * fix: strict TS errors * fix: strict TS errors * fix: strict TS errors * fix: strict TS errors * fix: strict TS errors * fix: strict TS errors * fix(ESLint): error on no-explicit-any * fix: type annotations for any types * fix: strict TS errors * fix: strict TS errors * fix: strict TS errors * fix: strict TS errors (and remove packages) * fix: strict TS errors * fix: strict TS errors * fix: strict TS errors * fix: strict TS errors * fix: strict TS errors * fix: strict TS errors * fix: strict TS errors * feat: enable React.StrictMode * fix: strict TS errors (done!) * fix: build error * fix: replace no-explicit-any assertions * refactor: cleanup * refactor: more cleanup * style: prettier --------- Co-authored-by: Lukas Zenick <lukas@utexas.edu> Co-authored-by: Razboy20 <razboy20@gmail.com>
This commit is contained in:
@@ -43,40 +43,40 @@ export type Semester = {
|
||||
*/
|
||||
export class Course {
|
||||
/** Every course has a uniqueId within UT's registrar system corresponding to each course section */
|
||||
uniqueId: number;
|
||||
uniqueId!: number;
|
||||
/** This is the course number for a course, i.e CS 314 would be 314, MAL 306H would be 306H */
|
||||
number: string;
|
||||
number!: string;
|
||||
/** The full name of the course, i.e. CS 314 Data Structures and Algorithms */
|
||||
fullName: string;
|
||||
fullName!: string;
|
||||
/** Just the english name for a course, without the number and department */
|
||||
courseName: string;
|
||||
courseName!: string;
|
||||
/** The unique identifier for which department that a course belongs to, i.e. CS, MAL, etc. */
|
||||
department: string;
|
||||
department!: string;
|
||||
|
||||
/** The number of credits that a course is worth */
|
||||
creditHours: number;
|
||||
creditHours!: number;
|
||||
/** Is the course open, closed, waitlisted, or cancelled? */
|
||||
status: StatusType;
|
||||
status!: StatusType;
|
||||
/** all the people that are teaching this course, and some metadata about their names */
|
||||
instructors: Instructor[];
|
||||
/** Some courses at UT are reserved for certain groups of people or people within a certain major, which makes it difficult for people outside of that group to register for the course. */
|
||||
isReserved: boolean;
|
||||
isReserved!: boolean;
|
||||
/** The description of the course as an array of "lines". This will include important information as well as a short summary of the topics covered */
|
||||
description?: string[];
|
||||
/** The schedule for the course, which includes the days of the week that the course is taught, the time that the course is taught, and the location that the course is taught */
|
||||
schedule: CourseSchedule;
|
||||
/** the link to the course details page for this course */
|
||||
url: string;
|
||||
url!: string;
|
||||
/** the link to the registration page for this course, for easy access when registering */
|
||||
registerURL?: string;
|
||||
/** At UT, some courses have certain "flags" which aid in graduation */
|
||||
flags: string[];
|
||||
flags!: string[];
|
||||
/** How is the class being taught (online, hybrid, in person, etc) */
|
||||
instructionMode: InstructionMode;
|
||||
instructionMode!: InstructionMode;
|
||||
/** Which semester is the course from */
|
||||
semester: Semester;
|
||||
semester!: Semester;
|
||||
/** Unix timestamp of when the course was last scraped */
|
||||
scrapedAt: number;
|
||||
scrapedAt!: number;
|
||||
/** The colors of the course when displayed */
|
||||
colors: CourseColors;
|
||||
|
||||
|
||||
@@ -30,15 +30,15 @@ export type Location = {
|
||||
*/
|
||||
export class CourseMeeting {
|
||||
/** The day of the week that the course is taught */
|
||||
days: Day[];
|
||||
days!: Day[];
|
||||
/** NOTE: Times starting and after 12 PM have an additional 720 minutes (12 hrs) added to them
|
||||
* The start time of the course, in minutes since midnight
|
||||
* */
|
||||
startTime: number;
|
||||
startTime!: number;
|
||||
/** NOTE: Times starting and after 12 PM have an additional 720 minutes (12 hrs) added to them
|
||||
* The end time of the course, in minutes since midnight
|
||||
* */
|
||||
endTime: number;
|
||||
endTime!: number;
|
||||
/** The location that the course is taught */
|
||||
location?: Location;
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ export class CourseSchedule {
|
||||
if (char === 'S' && nextChar === 'U') {
|
||||
day += nextChar;
|
||||
}
|
||||
return DAY_MAP[day];
|
||||
return DAY_MAP[day as keyof typeof DAY_MAP];
|
||||
})
|
||||
.filter(Boolean) as Day[];
|
||||
|
||||
@@ -47,7 +47,7 @@ export class CourseSchedule {
|
||||
.split('-')
|
||||
.map(time => {
|
||||
const [rawHour, rest] = time.split(':');
|
||||
const [rawMinute, ampm] = rest.split(' ');
|
||||
const [rawMinute, ampm] = rest?.split(' ') ?? ['', ''];
|
||||
const hour = (rawHour === '12' ? 0 : Number(rawHour)) + (ampm === 'pm' ? 12 : 0);
|
||||
const minute = Number(rawMinute);
|
||||
|
||||
@@ -56,17 +56,27 @@ export class CourseSchedule {
|
||||
|
||||
const location = locLine.split(' ').filter(Boolean);
|
||||
|
||||
if (startTime === undefined || endTime === undefined) {
|
||||
throw new Error('Failed to parse time');
|
||||
}
|
||||
|
||||
if (startTime >= endTime) {
|
||||
throw new Error('Start time must be before end time');
|
||||
}
|
||||
|
||||
if (location === undefined) {
|
||||
throw new Error('Failed to parse location');
|
||||
}
|
||||
|
||||
return new CourseMeeting({
|
||||
days,
|
||||
startTime,
|
||||
endTime,
|
||||
location: location.length
|
||||
? {
|
||||
building: location[0],
|
||||
room: location[1],
|
||||
}
|
||||
: undefined,
|
||||
});
|
||||
location: {
|
||||
building: location[0] ?? '',
|
||||
room: location[1] ?? '',
|
||||
},
|
||||
} satisfies Serialized<CourseMeeting>);
|
||||
} catch (e) {
|
||||
throw new Error(`Failed to parse schedule: ${dayLine} ${timeLine} ${locLine}`);
|
||||
}
|
||||
|
||||
@@ -5,9 +5,9 @@ import type { Serialized } from 'chrome-extension-toolkit';
|
||||
* A type representing an instructor for a course (who teaches it)
|
||||
*/
|
||||
export default class Instructor {
|
||||
fullName: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
fullName?: string;
|
||||
firstName?: string;
|
||||
lastName?: string;
|
||||
middleInitial?: string;
|
||||
|
||||
constructor(instructor: Serialized<Instructor>) {
|
||||
@@ -53,16 +53,16 @@ export default class Instructor {
|
||||
return capitalize(str);
|
||||
};
|
||||
|
||||
if (format === 'abbr') {
|
||||
if (format === 'abbr' && firstName && lastName && firstName[0]) {
|
||||
return `${process(firstName[0])}. ${process(lastName)}`;
|
||||
}
|
||||
if (format === 'full_name') {
|
||||
if (format === 'full_name' && fullName) {
|
||||
return process(fullName);
|
||||
}
|
||||
if (format === 'first_last') {
|
||||
if (format === 'first_last' && firstName && lastName) {
|
||||
return `${process(firstName)} ${process(lastName)}`;
|
||||
}
|
||||
if (format === 'last') {
|
||||
if (format === 'last' && lastName) {
|
||||
return process(lastName);
|
||||
}
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ export const extendedColors = {
|
||||
} as const;
|
||||
|
||||
type NestedKeys<T> = {
|
||||
[K in keyof T]: T[K] extends Record<string, any> ? `${string & K}-${string & keyof T[K]}` : never;
|
||||
[K in keyof T]: T[K] extends Record<string, unknown> ? `${string & K}-${string & keyof T[K]}` : never;
|
||||
}[keyof T];
|
||||
|
||||
/**
|
||||
@@ -56,6 +56,7 @@ export type ThemeColor = NestedKeys<typeof colors>;
|
||||
export type TWColorway = {
|
||||
[K in keyof typeof theme.colors]: (typeof theme.colors)[K] extends Record<string, unknown> ? K : never;
|
||||
}[keyof typeof theme.colors];
|
||||
export type TWIndex = keyof (typeof theme.colors)[TWColorway];
|
||||
|
||||
/**
|
||||
* Represents the colors for a course.
|
||||
|
||||
@@ -4,7 +4,7 @@ import { theme } from 'unocss/preset-mini';
|
||||
import type { HexColor, Lab, RGB, sRGB } from '../types/Color';
|
||||
import { isHexColor } from '../types/Color';
|
||||
import type { Course } from '../types/Course';
|
||||
import type { CourseColors, TWColorway } from '../types/ThemeColors';
|
||||
import type { CourseColors, TWColorway, TWIndex } from '../types/ThemeColors';
|
||||
import { colorwayIndexes } from '../types/ThemeColors';
|
||||
import type { UserSchedule } from '../types/UserSchedule';
|
||||
|
||||
@@ -14,18 +14,21 @@ import type { UserSchedule } from '../types/UserSchedule';
|
||||
* @param hex - The hexadecimal color value.
|
||||
* @returns An array containing the RGB values.
|
||||
*/
|
||||
export function hexToRGB(hex: HexColor): RGB {
|
||||
export function hexToRGB(hex: HexColor): RGB | undefined {
|
||||
// Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
|
||||
let shorthandRegex: RegExp = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
|
||||
const parsedHex: string = hex.replace(shorthandRegex, (m, r, g, b) => r + r + g + g + b + b);
|
||||
let shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
|
||||
const parsedHex = hex.replace(shorthandRegex, (m, r, g, b) => r + r + g + g + b + b);
|
||||
|
||||
let result: RegExpExecArray = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(parsedHex);
|
||||
return result ? [parseInt(result[1], 16), parseInt(result[2], 16), parseInt(result[3], 16)] : null;
|
||||
let result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(parsedHex);
|
||||
|
||||
if (!result || !(result.length > 3)) return undefined;
|
||||
|
||||
return [parseInt(result[1]!, 16), parseInt(result[2]!, 16), parseInt(result[3]!, 16)];
|
||||
}
|
||||
|
||||
export const useableColorways = Object.keys(theme.colors)
|
||||
// check that the color is a colorway (is an object)
|
||||
.filter(color => typeof theme.colors[color] === 'object')
|
||||
.filter(color => typeof theme.colors[color as keyof typeof theme.colors] === 'object')
|
||||
.slice(0, 17) as TWColorway[];
|
||||
|
||||
/**
|
||||
@@ -33,12 +36,16 @@ export const useableColorways = Object.keys(theme.colors)
|
||||
* @param bgColor the hex color of the background
|
||||
*/
|
||||
export function pickFontColor(bgColor: HexColor): 'text-white' | 'text-black' | 'text-theme-black' {
|
||||
const coefficients = [0.2126729, 0.7151522, 0.072175];
|
||||
const coefficients = [0.2126729, 0.7151522, 0.072175] as const;
|
||||
|
||||
const flipYs = 0.342; // based on APCA™ 0.98G middle contrast BG color
|
||||
|
||||
const trc = 2.4; // 2.4 exponent for emulating actual monitor perception
|
||||
let Ys = hexToRGB(bgColor).reduce((acc, c, i) => acc + (c / 255.0) ** trc * coefficients[i], 0);
|
||||
const rgb = hexToRGB(bgColor);
|
||||
if (!rgb) throw new Error('bgColor: Invalid hex.');
|
||||
|
||||
// coefficients and rgb are both 3 elements long, so this is safe
|
||||
let Ys = rgb.reduce((acc, c, i) => acc + (c / 255.0) ** trc * coefficients[i]!, 0);
|
||||
|
||||
if (Ys < flipYs) {
|
||||
return 'text-white';
|
||||
@@ -54,13 +61,13 @@ export function pickFontColor(bgColor: HexColor): 'text-white' | 'text-black' |
|
||||
export function getCourseColors(colorway: TWColorway, index?: number, offset: number = 300): CourseColors {
|
||||
if (index === undefined) {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
index = colorway in colorwayIndexes ? colorwayIndexes[colorway] : 500;
|
||||
index = colorway in colorwayIndexes ? colorwayIndexes[colorway as keyof typeof colorwayIndexes] : 500;
|
||||
}
|
||||
|
||||
return {
|
||||
primaryColor: theme.colors[colorway][index],
|
||||
secondaryColor: theme.colors[colorway][index + offset],
|
||||
} satisfies CourseColors;
|
||||
primaryColor: theme.colors[colorway][index as TWIndex] as HexColor,
|
||||
secondaryColor: theme.colors[colorway][(index + offset) as TWIndex] as HexColor,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -87,7 +94,12 @@ export function getColorwayFromColor(color: HexColor): TWColorway {
|
||||
continue;
|
||||
}
|
||||
|
||||
const distance = oklabDistance(rgbToOKlab(hexToRGB(shadeColor)), rgbToOKlab(hexToRGB(color)));
|
||||
const shadeColorRGB = hexToRGB(shadeColor);
|
||||
if (!shadeColorRGB) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const distance = oklabDistance(rgbToOKlab(shadeColorRGB), rgbToOKlab(shadeColorRGB));
|
||||
if (distance < closestDistance) {
|
||||
closestDistance = distance;
|
||||
closestColor = shade;
|
||||
@@ -148,7 +160,8 @@ export function getUnusedColor(
|
||||
|
||||
if (sameDepartment.length > 0) {
|
||||
// check to see if any adjacent colorways are available
|
||||
const centerCourse = sameDepartment[Math.floor(Math.random() * sameDepartment.length)];
|
||||
const centerCourse = sameDepartment[Math.floor(Math.random() * sameDepartment.length)]!;
|
||||
|
||||
let nextColorway = getNextColorway(centerCourse.colorway);
|
||||
let prevColorway = getPreviousColorway(centerCourse.colorway);
|
||||
|
||||
@@ -175,11 +188,18 @@ export function getUnusedColor(
|
||||
|
||||
if (shortenedColorways.size > 0) {
|
||||
// TODO: make this go by 3's to leave future spaces open
|
||||
const randomColorway = Array.from(shortenedColorways)[Math.floor(Math.random() * shortenedColorways.size)];
|
||||
const randomColorway = Array.from(shortenedColorways)[Math.floor(Math.random() * shortenedColorways.size)]!;
|
||||
|
||||
return getCourseColors(randomColorway, index, offset);
|
||||
}
|
||||
// no colorways are at least 2 indexes away from any used colors, just get a random colorway
|
||||
const randomColorway = Array.from(availableColorways)[Math.floor(Math.random() * availableColorways.size)];
|
||||
const randomColorway: TWColorway | undefined =
|
||||
Array.from(availableColorways)[Math.floor(Math.random() * availableColorways.size)];
|
||||
|
||||
if (!randomColorway) {
|
||||
throw new Error('randomColorway is undefined');
|
||||
}
|
||||
|
||||
return getCourseColors(randomColorway, index, offset);
|
||||
}
|
||||
// TODO: get just a random color idk
|
||||
|
||||
@@ -10,9 +10,9 @@ import CancelledIcon from '~icons/material-symbols/warning';
|
||||
/**
|
||||
* Get Icon component based on status
|
||||
* @param props.status status
|
||||
* @returns React.ReactElement - the icon component
|
||||
* @returns the icon component
|
||||
*/
|
||||
export function StatusIcon(props: SVGProps<SVGSVGElement> & { status: StatusType }): React.ReactElement {
|
||||
export function StatusIcon(props: SVGProps<SVGSVGElement> & { status: StatusType }): JSX.Element | null {
|
||||
const { status, ...rest } = props;
|
||||
|
||||
switch (props.status) {
|
||||
@@ -23,5 +23,6 @@ export function StatusIcon(props: SVGProps<SVGSVGElement> & { status: StatusType
|
||||
case Status.CANCELLED:
|
||||
return <CancelledIcon {...rest} />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,12 +5,11 @@ import { hexToRGB } from './colors';
|
||||
|
||||
/**
|
||||
* Flattened colors object.
|
||||
* @type {Record<ThemeColor, string>}
|
||||
*/
|
||||
export const colorsFlattened = Object.entries(colors).reduce(
|
||||
(acc, [prefix, group]) => {
|
||||
export const colorsFlattened: Record<ThemeColor, string> = Object.entries(colors).reduce(
|
||||
(acc: Record<ThemeColor, string>, [prefix, group]) => {
|
||||
for (const [name, hex] of Object.entries(group)) {
|
||||
acc[`${prefix}-${name}`] = hex;
|
||||
acc[`${prefix}-${name}` as ThemeColor] = hex;
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
@@ -19,9 +18,8 @@ export const colorsFlattened = Object.entries(colors).reduce(
|
||||
|
||||
/**
|
||||
* Represents the flattened RGB values of the colors.
|
||||
* @type {Record<ThemeColor, ReturnType<typeof hexToRgb>>}
|
||||
*/
|
||||
const colorsFlattenedRgb = Object.fromEntries(
|
||||
const colorsFlattenedRgb: Record<ThemeColor, ReturnType<typeof hexToRGB>> = Object.fromEntries(
|
||||
Object.entries(colorsFlattened).map(([name, hex]) => [name, hexToRGB(hex as HexColor)])
|
||||
) as Record<ThemeColor, ReturnType<typeof hexToRGB>>;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user