From da69eff9058647bcfc90fe95b1eea76a582ddf51 Mon Sep 17 00:00:00 2001 From: Grace Tian <66385449+snowmonkey18@users.noreply.github.com> Date: Wed, 22 Jan 2025 03:05:10 -0500 Subject: [PATCH 1/4] Add my starred classes --- package-lock.json | 1 - src/components/ClassTable.tsx | 69 ++++++++++++++++++++++++++--------- src/lib/state.ts | 31 ++++++++++++++++ src/lib/store.ts | 1 + 4 files changed, 84 insertions(+), 18 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0838286..f3a1d00 100644 --- a/package-lock.json +++ b/package-lock.json @@ -530,7 +530,6 @@ }, "node_modules/@clack/prompts/node_modules/is-unicode-supported": { "version": "1.3.0", - "extraneous": true, "inBundle": true, "license": "MIT", "engines": { diff --git a/src/components/ClassTable.tsx b/src/components/ClassTable.tsx index 0414442..39c2eb9 100644 --- a/src/components/ClassTable.tsx +++ b/src/components/ClassTable.tsx @@ -9,7 +9,7 @@ import { import { Box, Group, Flex, Image, Input } from "@chakra-ui/react"; import React, { useEffect, useMemo, useRef, useState } from "react"; -import { LuPlus, LuMinus, LuSearch } from "react-icons/lu"; +import { LuPlus, LuMinus, LuSearch, LuStar } from "react-icons/lu"; import { InputGroup } from "./ui/input-group"; import { Button, LabelledButton } from "./ui/button"; @@ -201,10 +201,11 @@ function ClassInput(props: { ); } -type FilterGroup = Array<[keyof Flags | "fits", string, string?]>; +type FilterGroup = Array<[keyof Flags | "fits" | "starred", string, React.ReactNode?]>; /** List of top filter IDs and their displayed names. */ const CLASS_FLAGS_1: FilterGroup = [ + ["starred", "My starred", ], ["hass", "HASS"], ["cih", "CI-H"], ["fits", "Fits schedule"], @@ -245,7 +246,7 @@ function ClassFlags(props: { const { setFlagsFilter, state, updateFilter } = props; // Map from flag to whether it's on. - const [flags, setFlags] = useState>(() => { + const [flags, setFlags] = useState>(() => { const result = new Map(); for (const flag of CLASS_FLAGS) { result.set(flag, false); @@ -262,7 +263,7 @@ function ClassFlags(props: { state.fitsScheduleCallback = () => flags.get("fits") && updateFilter(); }, [state, flags, updateFilter]); - const onChange = (flag: keyof Flags | "fits", value: boolean) => { + const onChange = (flag: keyof Flags | "fits" | "starred", value: boolean) => { const newFlags = new Map(flags); newFlags.set(flag, value); setFlags(newFlags); @@ -275,7 +276,9 @@ function ClassFlags(props: { newFlags.forEach((value, flag) => { if (value && flag === "fits" && !state.fitsSchedule(cls)) { result = false; - } else if (value && flag !== "fits" && !cls.flags[flag]) { + } else if (value && flag === "starred" && !state.isClassStarred(cls.number)) { + result = false; + } else if (value && flag !== "fits" && flag !== "starred" && !cls.flags[flag]) { result = false; } }); @@ -291,23 +294,14 @@ function ClassFlags(props: { {group.map(([flag, label, image]) => { const checked = flags.get(flag); return image ? ( - onChange(flag, !checked)} title={label} variant={checked ? "solid" : "outline"} - portalled > - {label} - + {image} + ) : ( + ); +}; + /** The table of all classes, along with searching and filtering with flags. */ export function ClassTable(props: { classes: Map; @@ -373,6 +389,25 @@ export function ClassTable(props: { ...sortProps, }; return [ + { + headerName: "", + field: "number", + maxWidth: 49, + cellRenderer: (params: any) => ( + { + gridRef.current?.api?.refreshCells({ + force: true, + columns: ["number"], + }); + }} + /> + ), + sortable: false, + cellStyle: { padding: 0 }, + }, { field: "number", headerName: "Class", diff --git a/src/lib/state.ts b/src/lib/state.ts index 9761eb6..50d70d7 100644 --- a/src/lib/state.ts +++ b/src/lib/state.ts @@ -23,6 +23,8 @@ export class State { conflicts: number = 0; /** Browser-specific saved state. */ store: Store; + /** Set of starred class numbers */ + private starredClasses: Set = new Set(); // The following are React state, so should be private. Even if we pass the // State object to React components, they shouldn't be looking at these @@ -68,6 +70,12 @@ export class State { this.classes.set(number, new Class(cls, this.colorScheme)); }); this.initState(); + + // Load starred classes from storage + const storedStarred = this.store.get("starredClasses"); + if (storedStarred) { + this.starredClasses = new Set(storedStarred); + } } /** All activities. */ @@ -401,4 +409,27 @@ export class State { this.loadSave(this.saves[0]!.id); } } + + /** Star or unstar a class */ + toggleStarClass(classNumber: string): void { + if (this.starredClasses.has(classNumber)) { + this.starredClasses.delete(classNumber); + } else { + this.starredClasses.add(classNumber); + } + this.store.set("starredClasses", Array.from(this.starredClasses)); + this.updateState(); + } + + /** Check if a class is starred */ + isClassStarred(classNumber: string): boolean { + return this.starredClasses.has(classNumber); + } + + /** Get all starred classes */ + getStarredClasses(): Class[] { + return Array.from(this.starredClasses) + .map(number => this.classes.get(number)) + .filter((cls): cls is Class => cls !== undefined); + } } diff --git a/src/lib/store.ts b/src/lib/store.ts index 9c1a5ae..c208ff5 100644 --- a/src/lib/store.ts +++ b/src/lib/store.ts @@ -2,6 +2,7 @@ import { Preferences, Save } from "./schema"; export type TermStore = { saves: Save[]; + starredClasses: string[]; // Array of class numbers that are starred [saveId: string]: unknown[]; }; From e03ceb3d4c1f42872f97f0d99ba3a09dc1049d6f Mon Sep 17 00:00:00 2001 From: Grace Tian <66385449+snowmonkey18@users.noreply.github.com> Date: Wed, 22 Jan 2025 03:08:28 -0500 Subject: [PATCH 2/4] formatting --- src/components/ClassTable.tsx | 33 +++++++++++++++++++++++++-------- src/lib/state.ts | 4 ++-- src/lib/store.ts | 2 +- 3 files changed, 28 insertions(+), 11 deletions(-) diff --git a/src/components/ClassTable.tsx b/src/components/ClassTable.tsx index 39c2eb9..17e0a43 100644 --- a/src/components/ClassTable.tsx +++ b/src/components/ClassTable.tsx @@ -201,7 +201,9 @@ function ClassInput(props: { ); } -type FilterGroup = Array<[keyof Flags | "fits" | "starred", string, React.ReactNode?]>; +type FilterGroup = Array< + [keyof Flags | "fits" | "starred", string, React.ReactNode?] +>; /** List of top filter IDs and their displayed names. */ const CLASS_FLAGS_1: FilterGroup = [ @@ -246,7 +248,9 @@ function ClassFlags(props: { const { setFlagsFilter, state, updateFilter } = props; // Map from flag to whether it's on. - const [flags, setFlags] = useState>(() => { + const [flags, setFlags] = useState< + Map + >(() => { const result = new Map(); for (const flag of CLASS_FLAGS) { result.set(flag, false); @@ -276,9 +280,18 @@ function ClassFlags(props: { newFlags.forEach((value, flag) => { if (value && flag === "fits" && !state.fitsSchedule(cls)) { result = false; - } else if (value && flag === "starred" && !state.isClassStarred(cls.number)) { + } else if ( + value && + flag === "starred" && + !state.isClassStarred(cls.number) + ) { result = false; - } else if (value && flag !== "fits" && flag !== "starred" && !cls.flags[flag]) { + } else if ( + value && + flag !== "fits" && + flag !== "starred" && + !cls.flags[flag] + ) { result = false; } }); @@ -336,8 +349,12 @@ function ClassFlags(props: { ); } -const StarButton = ({ classNumber, state, onStarToggle }: { - classNumber: string; +const StarButton = ({ + classNumber, + state, + onStarToggle, +}: { + classNumber: string; state: State; onStarToggle?: () => void; }) => { @@ -394,8 +411,8 @@ export function ClassTable(props: { field: "number", maxWidth: 49, cellRenderer: (params: any) => ( - { gridRef.current?.api?.refreshCells({ diff --git a/src/lib/state.ts b/src/lib/state.ts index 50d70d7..646061f 100644 --- a/src/lib/state.ts +++ b/src/lib/state.ts @@ -70,7 +70,7 @@ export class State { this.classes.set(number, new Class(cls, this.colorScheme)); }); this.initState(); - + // Load starred classes from storage const storedStarred = this.store.get("starredClasses"); if (storedStarred) { @@ -429,7 +429,7 @@ export class State { /** Get all starred classes */ getStarredClasses(): Class[] { return Array.from(this.starredClasses) - .map(number => this.classes.get(number)) + .map((number) => this.classes.get(number)) .filter((cls): cls is Class => cls !== undefined); } } diff --git a/src/lib/store.ts b/src/lib/store.ts index c208ff5..a4f7604 100644 --- a/src/lib/store.ts +++ b/src/lib/store.ts @@ -2,7 +2,7 @@ import { Preferences, Save } from "./schema"; export type TermStore = { saves: Save[]; - starredClasses: string[]; // Array of class numbers that are starred + starredClasses: string[]; // Array of class numbers that are starred [saveId: string]: unknown[]; }; From ef8901d0b7bacbb02e97b73c4fd58820e6642a88 Mon Sep 17 00:00:00 2001 From: Grace Tian <66385449+snowmonkey18@users.noreply.github.com> Date: Wed, 22 Jan 2025 03:20:29 -0500 Subject: [PATCH 3/4] eslint --- src/components/ClassTable.tsx | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/components/ClassTable.tsx b/src/components/ClassTable.tsx index 17e0a43..0546967 100644 --- a/src/components/ClassTable.tsx +++ b/src/components/ClassTable.tsx @@ -6,16 +6,15 @@ import { type IRowNode, type ColDef, } from "ag-grid-community"; -import { Box, Group, Flex, Image, Input } from "@chakra-ui/react"; +import { Box, Group, Flex, Input } from "@chakra-ui/react"; import React, { useEffect, useMemo, useRef, useState } from "react"; import { LuPlus, LuMinus, LuSearch, LuStar } from "react-icons/lu"; import { InputGroup } from "./ui/input-group"; -import { Button, LabelledButton } from "./ui/button"; -import { useColorMode } from "./ui/color-mode"; +import { Button } from "./ui/button"; -import { Class, DARK_IMAGES, Flags, getFlagImg } from "../lib/class"; +import { Class, Flags, getFlagImg } from "../lib/class"; import { classNumberMatch, classSort, simplifyString } from "../lib/utils"; import { State } from "../lib/state"; import { TSemester } from "../lib/dates"; @@ -299,8 +298,6 @@ function ClassFlags(props: { }); }; - const { colorMode } = useColorMode(); - const renderGroup = (group: FilterGroup) => { return ( @@ -410,7 +407,7 @@ export function ClassTable(props: { headerName: "", field: "number", maxWidth: 49, - cellRenderer: (params: any) => ( + cellRenderer: (params: { value: string; data: ClassTableRow }) => ( Date: Wed, 22 Jan 2025 12:17:19 -0500 Subject: [PATCH 4/4] undo unnecessary package-lock change --- package-lock.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package-lock.json b/package-lock.json index f3a1d00..0838286 100644 --- a/package-lock.json +++ b/package-lock.json @@ -530,6 +530,7 @@ }, "node_modules/@clack/prompts/node_modules/is-unicode-supported": { "version": "1.3.0", + "extraneous": true, "inBundle": true, "license": "MIT", "engines": {