-
Notifications
You must be signed in to change notification settings - Fork 25
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add my starred classes #121
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,16 +6,15 @@ | |
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 } 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"; | ||
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"; | ||
|
@@ -201,10 +200,13 @@ | |
); | ||
} | ||
|
||
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", <LuStar fill="currentColor" />], | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. question: is this where we want this to be? like, before even the "HASS" filter? seems too important for a feature that i'm not sure will get much usage i think it should be at least at the end of this row, and possibly even hidden |
||
["hass", "HASS"], | ||
["cih", "CI-H"], | ||
["fits", "Fits schedule"], | ||
|
@@ -245,7 +247,9 @@ | |
const { setFlagsFilter, state, updateFilter } = props; | ||
|
||
// Map from flag to whether it's on. | ||
const [flags, setFlags] = useState<Map<keyof Flags | "fits", boolean>>(() => { | ||
const [flags, setFlags] = useState< | ||
Map<keyof Flags | "fits" | "starred", boolean> | ||
>(() => { | ||
const result = new Map(); | ||
for (const flag of CLASS_FLAGS) { | ||
result.set(flag, false); | ||
|
@@ -262,7 +266,7 @@ | |
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,39 +279,39 @@ | |
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; | ||
} | ||
}); | ||
return result; | ||
}); | ||
}; | ||
|
||
const { colorMode } = useColorMode(); | ||
|
||
const renderGroup = (group: FilterGroup) => { | ||
return ( | ||
<Group attached colorPalette="orange" wrap="wrap"> | ||
{group.map(([flag, label, image]) => { | ||
const checked = flags.get(flag); | ||
return image ? ( | ||
<LabelledButton | ||
<Button | ||
key={flag} | ||
onClick={() => onChange(flag, !checked)} | ||
title={label} | ||
variant={checked ? "solid" : "outline"} | ||
portalled | ||
> | ||
<Image | ||
src={image} | ||
alt={label} | ||
filter={ | ||
colorMode === "dark" && DARK_IMAGES.includes(flag ?? "") | ||
? "invert()" | ||
: "" | ||
} | ||
/> | ||
</LabelledButton> | ||
{image} | ||
</Button> | ||
Comment on lines
+313
to
+314
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. question: why was this removed? what does this look like on dark mode? |
||
) : ( | ||
<Button | ||
key={flag} | ||
|
@@ -342,6 +346,32 @@ | |
); | ||
} | ||
|
||
const StarButton = ({ | ||
classNumber, | ||
state, | ||
onStarToggle, | ||
}: { | ||
classNumber: string; | ||
state: State; | ||
onStarToggle?: () => void; | ||
}) => { | ||
const isStarred = state.isClassStarred(classNumber); | ||
return ( | ||
<Button | ||
onClick={(e) => { | ||
e.stopPropagation(); | ||
state.toggleStarClass(classNumber); | ||
onStarToggle?.(); | ||
}} | ||
variant="ghost" | ||
size="sm" | ||
aria-label={isStarred ? "Unstar class" : "Star class"} | ||
> | ||
<LuStar fill={isStarred ? "currentColor" : "none"} /> | ||
</Button> | ||
); | ||
}; | ||
|
||
/** The table of all classes, along with searching and filtering with flags. */ | ||
export function ClassTable(props: { | ||
classes: Map<string, Class>; | ||
|
@@ -373,6 +403,25 @@ | |
...sortProps, | ||
}; | ||
return [ | ||
{ | ||
headerName: "", | ||
field: "number", | ||
maxWidth: 49, | ||
cellRenderer: (params: { value: string; data: ClassTableRow }) => ( | ||
<StarButton | ||
classNumber={params.value} | ||
state={state} | ||
onStarToggle={() => { | ||
gridRef.current?.api?.refreshCells({ | ||
force: true, | ||
columns: ["number"], | ||
}); | ||
}} | ||
/> | ||
), | ||
sortable: false, | ||
cellStyle: { padding: 0 }, | ||
}, | ||
{ | ||
field: "number", | ||
headerName: "Class", | ||
|
@@ -401,7 +450,7 @@ | |
}, | ||
{ field: "name", sortable: false, flex: 1 }, | ||
]; | ||
}, [state.term.semester]); | ||
Check warning on line 453 in src/components/ClassTable.tsx GitHub Actions / Build
|
||
|
||
const defaultColDef: ColDef<ClassTableRow, string> = useMemo(() => { | ||
return { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -23,6 +23,8 @@ export class State { | |
conflicts: number = 0; | ||
/** Browser-specific saved state. */ | ||
store: Store; | ||
/** Set of starred class numbers */ | ||
private starredClasses: Set<string> = new Set(); | ||
Comment on lines
+26
to
+27
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: move this to below the other private fields |
||
|
||
// 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); | ||
} | ||
Comment on lines
+73
to
+78
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: move this to initState |
||
} | ||
|
||
/** All activities. */ | ||
|
@@ -401,4 +409,27 @@ export class State { | |
this.loadSave(this.saves[0]!.id); | ||
} | ||
} | ||
|
||
/** Star or unstar a class */ | ||
toggleStarClass(classNumber: string): void { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: i'm not sure if we want this method to use |
||
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); | ||
} | ||
Comment on lines
+412
to
+434
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: move these under the state management methods
Comment on lines
+429
to
+434
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: remove unused function |
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,6 +2,7 @@ import { Preferences, Save } from "./schema"; | |
|
||
export type TermStore = { | ||
saves: Save[]; | ||
starredClasses: string[]; // Array of class numbers that are starred | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. question: i kind of wonder whether we should be putting this as under TermStore or GlobalStore, though i think TermStore makes more sense There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: let's use the jsdoc style to comment this |
||
[saveId: string]: unknown[]; | ||
}; | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: "Starred", no my
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
but honestly whatever