From d04d7b679bb84157c0ad4034855b0dba8575dbc7 Mon Sep 17 00:00:00 2001 From: Grace Tian <66385449+snowmonkey18@users.noreply.github.com> Date: Mon, 30 Dec 2024 23:21:04 -0500 Subject: [PATCH 01/11] show latest term if the url term is invalid (instead of loading forever) --- src/components/App.tsx | 65 ++++++++++++++++++++++++++++++------------ 1 file changed, 47 insertions(+), 18 deletions(-) diff --git a/src/components/App.tsx b/src/components/App.tsx index 7f940f86..60a455c2 100644 --- a/src/components/App.tsx +++ b/src/components/App.tsx @@ -55,24 +55,53 @@ function useHydrant(): { }; useEffect(() => { - const params = new URLSearchParams(document.location.search); - const term = params.get("t") ?? "latest"; - Promise.all([ - fetchNoCache("latestTerm.json"), - fetchNoCache(`${term}.json`), - ]).then(([latestTerm, { classes, lastUpdated, termInfo }]) => { - const classesMap = new Map(Object.entries(classes)); - const hydrantObj = new State( - classesMap, - new Term(termInfo), - lastUpdated, - latestTerm.semester.urlName, - ); - hydrantRef.current = hydrantObj; - setLoading(false); - window.hydrant = hydrantObj; - }); - }, []); + fetchNoCache("latestTerm.json") + .then( + (latestTerm) => { + const params = new URLSearchParams(document.location.search); + const term = params.get("t") ?? latestTerm.semester.urlName; + fetchNoCache(`${term}.json`) + .then( + ({ classes, lastUpdated, termInfo }) => { + const classesMap = new Map(Object.entries(classes)); + const hydrantObj = new State( + classesMap, + new Term(termInfo), + lastUpdated, + latestTerm.semester.urlName, + ); + hydrantRef.current = hydrantObj; + setLoading(false); + window.hydrant = hydrantObj; + } + ) + // TODO: - make this nicer, without the try/catch + // - actually redirect to a valid URL + // - redirect 's'->latestSpring, 'f'->latestFall, etc. + // - have a visual cue if the term in the url is invalid + .catch((error) => { + if (error instanceof SyntaxError) { + console.log(`Invalid term ${term}, showing latest term`); + fetchNoCache("latest.json") + .then( + ({ classes, lastUpdated, termInfo }) => { + const classesMap = new Map(Object.entries(classes)); + const hydrantObj = new State( + classesMap, + new Term(termInfo), + lastUpdated, + latestTerm.semester.urlName, + ); + hydrantRef.current = hydrantObj; + setLoading(false); + window.hydrant = hydrantObj; + } + ); + } + }); + } + ); + }, []); const { colorMode, toggleColorMode } = useColorMode(); From 8d8401aaddfb250ad83a82a1b72725663ae28745 Mon Sep 17 00:00:00 2001 From: Grace Tian <66385449+snowmonkey18@users.noreply.github.com> Date: Mon, 30 Dec 2024 23:25:55 -0500 Subject: [PATCH 02/11] Trigger Build From 6ef092b27707766c9e4c840fb31e84f60e49e36b Mon Sep 17 00:00:00 2001 From: Grace Tian <66385449+snowmonkey18@users.noreply.github.com> Date: Mon, 30 Dec 2024 23:28:55 -0500 Subject: [PATCH 03/11] App.tsx prettier --- src/components/App.tsx | 46 ++++++++++++++++++------------------------ 1 file changed, 20 insertions(+), 26 deletions(-) diff --git a/src/components/App.tsx b/src/components/App.tsx index 60a455c2..f10e9749 100644 --- a/src/components/App.tsx +++ b/src/components/App.tsx @@ -55,26 +55,22 @@ function useHydrant(): { }; useEffect(() => { - fetchNoCache("latestTerm.json") - .then( - (latestTerm) => { - const params = new URLSearchParams(document.location.search); - const term = params.get("t") ?? latestTerm.semester.urlName; - fetchNoCache(`${term}.json`) - .then( - ({ classes, lastUpdated, termInfo }) => { - const classesMap = new Map(Object.entries(classes)); - const hydrantObj = new State( - classesMap, - new Term(termInfo), - lastUpdated, - latestTerm.semester.urlName, - ); - hydrantRef.current = hydrantObj; - setLoading(false); - window.hydrant = hydrantObj; - } - ) + fetchNoCache("latestTerm.json").then((latestTerm) => { + const params = new URLSearchParams(document.location.search); + const term = params.get("t") ?? latestTerm.semester.urlName; + fetchNoCache(`${term}.json`) + .then(({ classes, lastUpdated, termInfo }) => { + const classesMap = new Map(Object.entries(classes)); + const hydrantObj = new State( + classesMap, + new Term(termInfo), + lastUpdated, + latestTerm.semester.urlName, + ); + hydrantRef.current = hydrantObj; + setLoading(false); + window.hydrant = hydrantObj; + }) // TODO: - make this nicer, without the try/catch // - actually redirect to a valid URL // - redirect 's'->latestSpring, 'f'->latestFall, etc. @@ -82,8 +78,7 @@ function useHydrant(): { .catch((error) => { if (error instanceof SyntaxError) { console.log(`Invalid term ${term}, showing latest term`); - fetchNoCache("latest.json") - .then( + fetchNoCache("latest.json").then( ({ classes, lastUpdated, termInfo }) => { const classesMap = new Map(Object.entries(classes)); const hydrantObj = new State( @@ -95,13 +90,12 @@ function useHydrant(): { hydrantRef.current = hydrantObj; setLoading(false); window.hydrant = hydrantObj; - } + }, ); } }); - } - ); - }, []); + }); + }, []); const { colorMode, toggleColorMode } = useColorMode(); From b37ebede5c92b9662e6b60197e26456038db1fe0 Mon Sep 17 00:00:00 2001 From: Diego Temkin <65834932+dtemkin1@users.noreply.github.com> Date: Wed, 1 Jan 2025 20:35:49 -0600 Subject: [PATCH 04/11] url name isnt same as json name (for some reason...), change to request correct file --- src/components/App.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/App.tsx b/src/components/App.tsx index f10e9749..c66a66e5 100644 --- a/src/components/App.tsx +++ b/src/components/App.tsx @@ -57,7 +57,7 @@ function useHydrant(): { useEffect(() => { fetchNoCache("latestTerm.json").then((latestTerm) => { const params = new URLSearchParams(document.location.search); - const term = params.get("t") ?? latestTerm.semester.urlName; + const term = params.get("t") ?? "latest"; fetchNoCache(`${term}.json`) .then(({ classes, lastUpdated, termInfo }) => { const classesMap = new Map(Object.entries(classes)); From 2a70ee668fb5d260c00b970bb9a75cac635b61b4 Mon Sep 17 00:00:00 2001 From: Pratyush Venkatakrishnan Date: Mon, 6 Jan 2025 17:29:51 -0500 Subject: [PATCH 05/11] Use async/await syntax This avoids the nesting indentation that comes from multiple sequential requests. --- src/components/App.tsx | 58 ++++++++++++++++++++++-------------------- 1 file changed, 30 insertions(+), 28 deletions(-) diff --git a/src/components/App.tsx b/src/components/App.tsx index c66a66e5..150a1a01 100644 --- a/src/components/App.tsx +++ b/src/components/App.tsx @@ -55,11 +55,33 @@ function useHydrant(): { }; useEffect(() => { - fetchNoCache("latestTerm.json").then((latestTerm) => { + const fetchData = async () => { + const latestTerm = await fetchNoCache("latestTerm.json"); const params = new URLSearchParams(document.location.search); const term = params.get("t") ?? "latest"; - fetchNoCache(`${term}.json`) - .then(({ classes, lastUpdated, termInfo }) => { + + try { + const { classes, lastUpdated, termInfo } = + await fetchNoCache(`${term}.json`); + const classesMap = new Map(Object.entries(classes)); + const hydrantObj = new State( + classesMap, + new Term(termInfo), + lastUpdated, + latestTerm.semester.urlName, + ); + hydrantRef.current = hydrantObj; + setLoading(false); + window.hydrant = hydrantObj; + } catch (error) { + // TODO: - make this nicer, without the try/catch + // - actually redirect to a valid URL + // - redirect 's'->latestSpring, 'f'->latestFall, etc. + // - have a visual cue if the term in the url is invalid + if (error instanceof SyntaxError) { + console.log(`Invalid term ${term}, showing latest term`); + const { classes, lastUpdated, termInfo } = + await fetchNoCache("latest.json"); const classesMap = new Map(Object.entries(classes)); const hydrantObj = new State( classesMap, @@ -70,31 +92,11 @@ function useHydrant(): { hydrantRef.current = hydrantObj; setLoading(false); window.hydrant = hydrantObj; - }) - // TODO: - make this nicer, without the try/catch - // - actually redirect to a valid URL - // - redirect 's'->latestSpring, 'f'->latestFall, etc. - // - have a visual cue if the term in the url is invalid - .catch((error) => { - if (error instanceof SyntaxError) { - console.log(`Invalid term ${term}, showing latest term`); - fetchNoCache("latest.json").then( - ({ classes, lastUpdated, termInfo }) => { - const classesMap = new Map(Object.entries(classes)); - const hydrantObj = new State( - classesMap, - new Term(termInfo), - lastUpdated, - latestTerm.semester.urlName, - ); - hydrantRef.current = hydrantObj; - setLoading(false); - window.hydrant = hydrantObj; - }, - ); - } - }); - }); + } + } + }; + + fetchData(); }, []); const { colorMode, toggleColorMode } = useColorMode(); From 83726daec3ae4eda7db2b223d04bc7e5da374251 Mon Sep 17 00:00:00 2001 From: Pratyush Venkatakrishnan Date: Mon, 6 Jan 2025 17:38:18 -0500 Subject: [PATCH 06/11] Redirect instead of silently loading a valid term This resolves one of the TODO comments, but it is useful since it means that the URL in the address bar matches what is shown on the page. --- src/components/App.tsx | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/src/components/App.tsx b/src/components/App.tsx index 150a1a01..807977e3 100644 --- a/src/components/App.tsx +++ b/src/components/App.tsx @@ -75,23 +75,14 @@ function useHydrant(): { window.hydrant = hydrantObj; } catch (error) { // TODO: - make this nicer, without the try/catch - // - actually redirect to a valid URL // - redirect 's'->latestSpring, 'f'->latestFall, etc. // - have a visual cue if the term in the url is invalid if (error instanceof SyntaxError) { - console.log(`Invalid term ${term}, showing latest term`); - const { classes, lastUpdated, termInfo } = - await fetchNoCache("latest.json"); - const classesMap = new Map(Object.entries(classes)); - const hydrantObj = new State( - classesMap, - new Term(termInfo), - lastUpdated, - latestTerm.semester.urlName, - ); - hydrantRef.current = hydrantObj; - setLoading(false); - window.hydrant = hydrantObj; + // Redirect to the latest term, while storing the initially requested + // term in the "ti" parameter so that the user can be notified + params.delete("t"); + params.set("ti", term); + window.location.search = params.toString(); } } }; From 2157ebf101939507eb3c8be31002672bc5522e57 Mon Sep 17 00:00:00 2001 From: Pratyush Venkatakrishnan Date: Mon, 6 Jan 2025 18:10:25 -0500 Subject: [PATCH 07/11] Explicitly check against urlNames To avoid try/catch. --- src/components/App.tsx | 23 ++++++++-------- src/components/TermSwitcher.tsx | 49 +-------------------------------- src/lib/dates.ts | 47 +++++++++++++++++++++++++++++++ 3 files changed, 59 insertions(+), 60 deletions(-) diff --git a/src/components/App.tsx b/src/components/App.tsx index 807977e3..d1ae1a04 100644 --- a/src/components/App.tsx +++ b/src/components/App.tsx @@ -7,7 +7,7 @@ import { Tooltip } from "./ui/tooltip"; import { Provider } from "./ui/provider"; import { useColorMode } from "./ui/color-mode"; -import { LatestTermInfo, Term, TermInfo } from "../lib/dates"; +import { LatestTermInfo, Term, TermInfo, getUrlNames } from "../lib/dates"; import { State } from "../lib/state"; import { RawClass } from "../lib/rawClass"; import { Class } from "../lib/class"; @@ -58,9 +58,10 @@ function useHydrant(): { const fetchData = async () => { const latestTerm = await fetchNoCache("latestTerm.json"); const params = new URLSearchParams(document.location.search); + const urlNames = getUrlNames(latestTerm.semester.urlName); const term = params.get("t") ?? "latest"; - try { + if (urlNames.includes(term) || term === "latest") { const { classes, lastUpdated, termInfo } = await fetchNoCache(`${term}.json`); const classesMap = new Map(Object.entries(classes)); @@ -73,17 +74,15 @@ function useHydrant(): { hydrantRef.current = hydrantObj; setLoading(false); window.hydrant = hydrantObj; - } catch (error) { - // TODO: - make this nicer, without the try/catch - // - redirect 's'->latestSpring, 'f'->latestFall, etc. + } else { + // TODO: - redirect 's'->latestSpring, 'f'->latestFall, etc. // - have a visual cue if the term in the url is invalid - if (error instanceof SyntaxError) { - // Redirect to the latest term, while storing the initially requested - // term in the "ti" parameter so that the user can be notified - params.delete("t"); - params.set("ti", term); - window.location.search = params.toString(); - } + + // Redirect to the latest term, while storing the initially requested + // term in the "ti" parameter so that the user can be notified + params.delete("t"); + params.set("ti", term); + window.location.search = params.toString(); } }; diff --git a/src/components/TermSwitcher.tsx b/src/components/TermSwitcher.tsx index 3add3eb4..3eede480 100644 --- a/src/components/TermSwitcher.tsx +++ b/src/components/TermSwitcher.tsx @@ -10,54 +10,7 @@ import { import { createListCollection } from "@chakra-ui/react"; import { State } from "../lib/state"; -import { Term } from "../lib/dates"; - -/** Given a urlName like i22, return its corresponding URL. */ -function toFullUrl(urlName: string, latestUrlName: string): string { - const url = new URL(window.location.href); - Array.from(url.searchParams.keys()).forEach((key) => { - url.searchParams.delete(key); - }); - if (urlName !== latestUrlName) { - url.searchParams.set("t", urlName); - } - return url.href; -} - -/** Given a urlName like "i22", return the previous one, "f21". */ -function getLastUrlName(urlName: string): string { - const { semester, year } = new Term({ urlName }); - switch (semester) { - case "f": - return `m${year}`; - case "m": - return `s${year}`; - case "s": - return `i${year}`; - case "i": - return `f${parseInt(year, 10) - 1}`; - } -} - -/** urlNames that don't have a State */ -const EXCLUDED_URLS = ["i23", "m23", "i24", "m24"]; - -/** Earliest urlName we have a State for. */ -const EARLIEST_URL = "f22"; - -/** Return all urlNames before the given one. */ -function getUrlNames(latestUrlName: string): Array { - let urlName = latestUrlName; - const res = []; - while (urlName !== EARLIEST_URL) { - res.push(urlName); - do { - urlName = getLastUrlName(urlName); - } while (EXCLUDED_URLS.includes(urlName)); - } - res.push(EARLIEST_URL); - return res; -} +import { Term, toFullUrl, getUrlNames } from "../lib/dates"; export function TermSwitcher(props: { state: State }) { const { state } = props; diff --git a/src/lib/dates.ts b/src/lib/dates.ts index 06c652f1..e28ebbb0 100644 --- a/src/lib/dates.ts +++ b/src/lib/dates.ts @@ -146,6 +146,53 @@ export function parseUrlName(urlName: string): { }; } +/** Given a urlName like i22, return its corresponding URL. */ +export function toFullUrl(urlName: string, latestUrlName: string): string { + const url = new URL(window.location.href); + Array.from(url.searchParams.keys()).forEach((key) => { + url.searchParams.delete(key); + }); + if (urlName !== latestUrlName) { + url.searchParams.set("t", urlName); + } + return url.href; +} + +/** Given a urlName like "i22", return the previous one, "f21". */ +function getLastUrlName(urlName: string): string { + const { semester, year } = new Term({ urlName }); + switch (semester) { + case "f": + return `m${year}`; + case "m": + return `s${year}`; + case "s": + return `i${year}`; + case "i": + return `f${parseInt(year, 10) - 1}`; + } +} + +/** urlNames that don't have a State */ +const EXCLUDED_URLS = ["i23", "m23", "i24", "m24"]; + +/** Earliest urlName we have a State for. */ +const EARLIEST_URL = "f22"; + +/** Return all urlNames before the given one. */ +export function getUrlNames(latestUrlName: string): Array { + let urlName = latestUrlName; + const res = []; + while (urlName !== EARLIEST_URL) { + res.push(urlName); + do { + urlName = getLastUrlName(urlName); + } while (EXCLUDED_URLS.includes(urlName)); + } + res.push(EARLIEST_URL); + return res; +} + /** Type of object passed to Term constructor. */ export type TermInfo = { urlName: string; From 6f60470de2b4eeafae667b08efc1f2a26be80d64 Mon Sep 17 00:00:00 2001 From: Pratyush Venkatakrishnan Date: Mon, 6 Jan 2025 22:35:35 -0500 Subject: [PATCH 08/11] Redirect to the "closest" urlName, not necc latest IAP or summer of a year where those terms were not scraped separately should redirect silently to spring or fall of that year, and otherwise we try to redirect (eventually with some sort of notice to the user) to the same semester of the current year. --- src/components/App.tsx | 37 +++++++++++++++++++-------- src/lib/dates.ts | 57 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+), 10 deletions(-) diff --git a/src/components/App.tsx b/src/components/App.tsx index d1ae1a04..79630661 100644 --- a/src/components/App.tsx +++ b/src/components/App.tsx @@ -7,7 +7,12 @@ import { Tooltip } from "./ui/tooltip"; import { Provider } from "./ui/provider"; import { useColorMode } from "./ui/color-mode"; -import { LatestTermInfo, Term, TermInfo, getUrlNames } from "../lib/dates"; +import { + LatestTermInfo, + Term, + TermInfo, + getClosestUrlName, +} from "../lib/dates"; import { State } from "../lib/state"; import { RawClass } from "../lib/rawClass"; import { Class } from "../lib/class"; @@ -58,10 +63,16 @@ function useHydrant(): { const fetchData = async () => { const latestTerm = await fetchNoCache("latestTerm.json"); const params = new URLSearchParams(document.location.search); - const urlNames = getUrlNames(latestTerm.semester.urlName); - const term = params.get("t") ?? "latest"; - if (urlNames.includes(term) || term === "latest") { + const urlNameOrig = params.get("t"); + const { urlName, shouldWarn } = getClosestUrlName( + urlNameOrig, + latestTerm.semester.urlName, + ); + + if (urlName === urlNameOrig || urlNameOrig === null) { + const term = + urlName === latestTerm.semester.urlName ? "latest" : urlName; const { classes, lastUpdated, termInfo } = await fetchNoCache(`${term}.json`); const classesMap = new Map(Object.entries(classes)); @@ -75,13 +86,19 @@ function useHydrant(): { setLoading(false); window.hydrant = hydrantObj; } else { - // TODO: - redirect 's'->latestSpring, 'f'->latestFall, etc. - // - have a visual cue if the term in the url is invalid + // TODO: have a visual cue if the term in the url is invalid - // Redirect to the latest term, while storing the initially requested - // term in the "ti" parameter so that the user can be notified - params.delete("t"); - params.set("ti", term); + // Redirect to the indicated term, while storing the initially requested + // term in the "ti" parameter (if necessary) so that the user can be + // notified + if (urlName === latestTerm.semester.urlName) { + params.delete("t"); + } else { + params.set("t", urlName); + } + if (shouldWarn) { + params.set("ti", urlNameOrig!); + } window.location.search = params.toString(); } }; diff --git a/src/lib/dates.ts b/src/lib/dates.ts index e28ebbb0..e365efe9 100644 --- a/src/lib/dates.ts +++ b/src/lib/dates.ts @@ -173,6 +173,21 @@ function getLastUrlName(urlName: string): string { } } +/** Given a urlName like "i22", return the next one, "s22". */ +function getNextUrlName(urlName: string): string { + const { semester, year } = new Term({ urlName }); + switch (semester) { + case "i": + return `s${year}`; + case "s": + return `m${year}`; + case "m": + return `f${year}`; + case "f": + return `i${parseInt(year, 10) + 1}`; + } +} + /** urlNames that don't have a State */ const EXCLUDED_URLS = ["i23", "m23", "i24", "m24"]; @@ -193,6 +208,48 @@ export function getUrlNames(latestUrlName: string): Array { return res; } +/** + * Return the "closest" urlName to the one provided, as well as whether or not + * the user should be shown a warning that this does not match the term + * requested. + */ +export function getClosestUrlName( + urlName: string | null, + latestUrlName: string, +): { + urlName: string; + shouldWarn: boolean; +} { + if (urlName === null || urlName === "" || urlName === "latest") { + return { urlName: latestUrlName, shouldWarn: false }; + } + + const urlNames = getUrlNames(latestUrlName); + if (urlNames.includes(urlName)) { + return { urlName: urlName, shouldWarn: false }; + } + + // IAP or summer for a year where those were folded into spring/fall + if (EXCLUDED_URLS.includes(urlName)) { + const nextUrlName = getNextUrlName(urlName); + if (urlNames.includes(nextUrlName)) { + // modified: false because in these cases, e.g. s24 includes the data + // corresponding to i24 + return { urlName: nextUrlName, shouldWarn: false }; + } + } + + const urlNamesSameSem = urlNames.filter((u) => u[0] === urlName[0]); + if (urlNamesSameSem.length > 0) { + // Unrecognized term, but we can return the latest term of the same type of + // semester (fall, spring, etc.) + return { urlName: urlNamesSameSem[0], shouldWarn: true }; + } + + // Fallback: return latest term + return { urlName: latestUrlName, shouldWarn: true }; +} + /** Type of object passed to Term constructor. */ export type TermInfo = { urlName: string; From f8c77a658efc83750d61d5d2a1c52d6f69211c87 Mon Sep 17 00:00:00 2001 From: Pratyush Venkatakrishnan Date: Mon, 6 Jan 2025 23:24:02 -0500 Subject: [PATCH 09/11] Add warning after redirecting from invalid term --- src/components/App.tsx | 2 -- src/components/Header.tsx | 35 ++++++++++++++++++++++++++++++++++- 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/src/components/App.tsx b/src/components/App.tsx index 79630661..f32c86dc 100644 --- a/src/components/App.tsx +++ b/src/components/App.tsx @@ -86,8 +86,6 @@ function useHydrant(): { setLoading(false); window.hydrant = hydrantObj; } else { - // TODO: have a visual cue if the term in the url is invalid - // Redirect to the indicated term, while storing the initially requested // term in the "ti" parameter (if necessary) so that the user can be // notified diff --git a/src/components/Header.tsx b/src/components/Header.tsx index 1a7694b2..e23d558b 100644 --- a/src/components/Header.tsx +++ b/src/components/Header.tsx @@ -1,4 +1,5 @@ -import { Flex, Image } from "@chakra-ui/react"; +import { Card, IconButton, Flex, Image, Text } from "@chakra-ui/react"; +import { LuX } from "react-icons/lu"; import { DialogRoot, @@ -128,6 +129,19 @@ export function Header(props: { state: State; preferences: Preferences }) { const { state, preferences } = props; const logoSrc = useColorModeValue(logo, logoDark); + const params = new URLSearchParams(document.location.search); + const urlNameOrig = params.get("ti"); + const urlName = params.get("t") ?? state.latestUrlName; + + const [show, setShow] = useState(urlNameOrig !== null); + + const onClose = () => { + const url = new URL(window.location.href); + url.searchParams.delete("ti"); + window.history.pushState({}, "", url); + setShow(false); + }; + return ( @@ -143,6 +157,25 @@ export function Header(props: { state: State; preferences: Preferences }) { + {show && ( + + + + + Term {urlNameOrig} not found; loaded term {urlName} instead. + + + + + + + + )} ); } From 73ce42bd6dd84df9fcf029f13e5cdd8334573e4a Mon Sep 17 00:00:00 2001 From: Grace Tian <66385449+snowmonkey18@users.noreply.github.com> Date: Wed, 29 Jan 2025 18:31:07 -0500 Subject: [PATCH 10/11] Add star button to ActivityDescription --- src/components/ActivityButtons.tsx | 9 +++++++++ src/components/ClassTable.tsx | 13 ++++++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/components/ActivityButtons.tsx b/src/components/ActivityButtons.tsx index d1e579dc..a30fa113 100644 --- a/src/components/ActivityButtons.tsx +++ b/src/components/ActivityButtons.tsx @@ -39,6 +39,7 @@ import { Class, LockOption, SectionLockOption, Sections } from "../lib/class"; import { WEEKDAY_STRINGS, TIMESLOT_STRINGS, Slot } from "../lib/dates"; import { State } from "../lib/state"; import { LuCheck as CheckIcon, LuX as CloseIcon } from "react-icons/lu"; +import { StarButton } from "./ClassTable"; /** * A button that toggles the active value, and is outlined if active, solid @@ -240,6 +241,14 @@ export function ClassButtons(props: { cls: Class; state: State }) { return ( + { + const event = new CustomEvent("refreshStarCells"); + window.dispatchEvent(event); + }} + /> diff --git a/src/components/ClassTable.tsx b/src/components/ClassTable.tsx index 200a19d1..3b6efaf9 100644 --- a/src/components/ClassTable.tsx +++ b/src/components/ClassTable.tsx @@ -382,7 +382,7 @@ function ClassFlags(props: { ); } -const StarButton = ({ +export const StarButton = ({ cls, state, onStarToggle, @@ -416,6 +416,17 @@ export function ClassTable(props: { const { classes, state } = props; const gridRef = useRef(null); + useEffect(() => { + const refreshCells = () => { + gridRef.current?.api?.refreshCells({ + force: true, + columns: ["number"], + }); + }; + window.addEventListener('refreshStarCells', refreshCells); + return () => window.removeEventListener('refreshStarCells', refreshCells); + }, []); + // Setup table columns const columnDefs: ColDef[] = useMemo(() => { const initialSort = "asc" as const; From 88060ae1c935f5b5a88a28e9ce8da68e1e541280 Mon Sep 17 00:00:00 2001 From: Grace Tian <66385449+snowmonkey18@users.noreply.github.com> Date: Wed, 29 Jan 2025 18:40:07 -0500 Subject: [PATCH 11/11] formatting --- src/components/ClassTable.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/ClassTable.tsx b/src/components/ClassTable.tsx index 3b6efaf9..9d703650 100644 --- a/src/components/ClassTable.tsx +++ b/src/components/ClassTable.tsx @@ -423,8 +423,8 @@ export function ClassTable(props: { columns: ["number"], }); }; - window.addEventListener('refreshStarCells', refreshCells); - return () => window.removeEventListener('refreshStarCells', refreshCells); + window.addEventListener("refreshStarCells", refreshCells); + return () => window.removeEventListener("refreshStarCells", refreshCells); }, []); // Setup table columns