Skip to content

Commit

Permalink
feat: CF-356: unlocked course nodes added to graphical selector (#1035)
Browse files Browse the repository at this point in the history
Different course node stylings added for the following states:
- course node is added to the planner
- course node is unlocked (i.e. meets the prereqs for that course)
- course node that is locked
https://csesoc.atlassian.net/browse/CF-356
---------

Co-authored-by: Daysure <[email protected]>
Co-authored-by: Leonardo Fan <[email protected]>
  • Loading branch information
3 people authored Jul 3, 2023
1 parent ed997de commit 453ffb0
Show file tree
Hide file tree
Showing 11 changed files with 78 additions and 40 deletions.
Binary file modified frontend/src/assets/GraphicalSelectorHelp/step2-dark.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified frontend/src/assets/GraphicalSelectorHelp/step2-light.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified frontend/src/assets/GraphicalSelectorHelp/step3-dark.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified frontend/src/assets/GraphicalSelectorHelp/step3-light.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified frontend/src/assets/GraphicalSelectorHelp/step4-dark.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified frontend/src/assets/GraphicalSelectorHelp/step4-light.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
26 changes: 16 additions & 10 deletions frontend/src/pages/GraphicalSelector/CourseGraph/CourseGraph.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ const CourseGraph = ({ onNodeClick, handleToggleFullscreen, fullscreen, focused
const previousTheme = useRef<typeof theme>(theme);
const { programCode, specs } = useSelector((state: RootState) => state.degree);
const { courses: plannedCourses } = useSelector((state: RootState) => state.planner);
const { degree, planner } = useSelector((state: RootState) => state);
const { degree, planner, courses } = useSelector((state: RootState) => state);
const windowSize = useAppWindowSize();

const graphRef = useRef<Graph | null>(null);
Expand Down Expand Up @@ -114,7 +114,10 @@ const CourseGraph = ({ onNodeClick, handleToggleFullscreen, fullscreen, focused
});
graphRef.current?.getNodes().forEach((n) => {
const courseId = n.getID();
graphRef.current?.updateItem(n as Item, mapNodeRestore(courseId, plannedCourses, theme));
graphRef.current?.updateItem(
n as Item,
mapNodeRestore(courseId, plannedCourses, courses.courses, theme)
);
graphRef.current?.updateItem(n as Item, mapNodeOpacity(courseId, 1));
n.toFront();
});
Expand Down Expand Up @@ -144,8 +147,7 @@ const CourseGraph = ({ onNodeClick, handleToggleFullscreen, fullscreen, focused
graphRef.current?.paint();
};

// courses is a list of course codes
const initialiseGraph = async (courses: string[], courseEdges: CourseEdge[]) => {
const initialiseGraph = async (courseCodes: string[], courseEdges: CourseEdge[]) => {
const container = containerRef.current;
if (!container) return;

Expand Down Expand Up @@ -180,9 +182,8 @@ const CourseGraph = ({ onNodeClick, handleToggleFullscreen, fullscreen, focused
};

graphRef.current = new Graph(graphArgs);

const data = {
nodes: courses.map((c) => mapNodeStyle(c, plannedCourses, theme)),
nodes: courseCodes.map((c) => mapNodeStyle(c, plannedCourses, courses.courses, theme)),
edges: courseEdges
};

Expand Down Expand Up @@ -221,7 +222,10 @@ const CourseGraph = ({ onNodeClick, handleToggleFullscreen, fullscreen, focused
const repaintCanvas = async () => {
const nodes = graphRef.current?.getNodes();
nodes?.map((n) =>
graphRef.current?.updateItem(n, mapNodeStyle(n.getID(), plannedCourses, theme))
graphRef.current?.updateItem(
n,
mapNodeStyle(n.getID(), plannedCourses, courses.courses, theme)
)
);

graphRef.current?.off('node:mouseenter');
Expand All @@ -245,9 +249,11 @@ const CourseGraph = ({ onNodeClick, handleToggleFullscreen, fullscreen, focused
const res = await axios.get<GraphPayload>(
`/programs/graph/${programCode}/${specs.join('+')}`
);
const { edges, courses } = res.data;
const { edges } = res.data;
makePrerequisitesMap(edges);
if (courses.length !== 0 && edges.length !== 0) initialiseGraph(courses, edges);
if (res.data.courses.length !== 0 && edges.length !== 0) {
initialiseGraph(res.data.courses, edges);
}
} catch (e) {
// eslint-disable-next-line no-console
console.error('Error at setupGraph', e);
Expand All @@ -260,7 +266,7 @@ const CourseGraph = ({ onNodeClick, handleToggleFullscreen, fullscreen, focused
previousTheme.current = theme;
repaintCanvas();
}
}, [onNodeClick, plannedCourses, programCode, specs, theme, prerequisites]);
}, [onNodeClick, plannedCourses, programCode, specs, theme, prerequisites, courses]);

const showAllCourses = () => {
if (!graphRef.current) return;
Expand Down
63 changes: 45 additions & 18 deletions frontend/src/pages/GraphicalSelector/CourseGraph/graph.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { Arrow } from '@antv/g6';
import { CourseValidation } from 'types/courses';
import { PlannerCourse } from 'types/planner';

const plannedNode = {
Expand Down Expand Up @@ -31,6 +32,19 @@ const lockedNode = (theme: string) => ({
}
});

const unlockedNode = (theme: string) => ({
style: {
fill: theme === 'light' ? '#fbefff' : '#381f56',
stroke: theme === 'light' ? '#9254de' : '#d7b7fd',
lineWidth: 1
},
labelCfg: {
style: {
fill: theme === 'light' ? '#9254de' : '#d7b7fd'
}
}
});

const prereqNode = (theme: string) => ({
style: {
stroke: theme === 'light' ? '#000' : '#fff',
Expand All @@ -43,6 +57,22 @@ const sameNode = (courseCode: string) => ({
label: courseCode
});

const plannedLabel = {
labelCfg: {
style: {
fill: '#fff'
}
}
};

const lockedLabel = (theme: string) => ({
labelCfg: {
style: {
fill: theme === 'light' ? '#9254de' : '#d7b7fd'
}
}
});

const nodeStateStyles = {
hover: {
fill: '#b37feb',
Expand Down Expand Up @@ -99,10 +129,14 @@ const edgeUnhoverStyle = (arrow: typeof Arrow, theme: string, id: string) => {
const mapNodeStyle = (
courseCode: string,
plannedCourses: Record<string, PlannerCourse>,
courses: Record<string, CourseValidation>,
theme: string
) => {
// If planned, keep default styling. Otherwise apply lockedNode styling.
if (plannedCourses[courseCode]) return sameNode(courseCode);
const isPlanned = plannedCourses[courseCode];
const isUnlocked = courses[courseCode]?.unlocked;

if (isPlanned) return sameNode(courseCode);
if (isUnlocked) return { ...sameNode(courseCode), ...unlockedNode(theme) };
return { ...sameNode(courseCode), ...lockedNode(theme) };
};

Expand All @@ -116,9 +150,14 @@ const mapNodePrereq = (courseCode: string, theme: string) => {
const mapNodeRestore = (
courseCode: string,
plannedCourses: Record<string, PlannerCourse>,
courses: Record<string, CourseValidation>,
theme: string
) => {
if (plannedCourses[courseCode]) return { ...sameNode(courseCode), ...plannedNode };
const isPlanned = plannedCourses[courseCode];
const isUnlocked = courses[courseCode]?.unlocked;

if (isPlanned) return { ...sameNode(courseCode), ...plannedNode };
if (isUnlocked) return { ...sameNode(courseCode), ...unlockedNode(theme) };
return { ...sameNode(courseCode), ...lockedNode(theme) };
};

Expand All @@ -145,11 +184,7 @@ const edgeOpacity = (id: string, opacity: number) => ({

const nodeLabelHoverStyle = (courseCode: string) => ({
...sameNode(courseCode),
labelCfg: {
style: {
fill: '#fff'
}
}
...plannedLabel
});

const nodeLabelUnhoverStyle = (
Expand All @@ -161,20 +196,12 @@ const nodeLabelUnhoverStyle = (
// uses default node style with label color changed
return {
...sameNode(courseCode),
labelCfg: {
style: {
fill: '#fff'
}
}
...plannedLabel
};
}
return {
...sameNode(courseCode),
labelCfg: {
style: {
fill: theme === 'light' ? '#9254de' : '#d7b7fd'
}
}
...lockedLabel(theme)
};
};

Expand Down
29 changes: 17 additions & 12 deletions frontend/src/pages/GraphicalSelector/HowToUse/HowToUse.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import step3Dark from 'assets/GraphicalSelectorHelp/step3-dark.jpg';
import step3Light from 'assets/GraphicalSelectorHelp/step3-light.jpg';
import step4Dark from 'assets/GraphicalSelectorHelp/step4-dark.jpg';
import step4Light from 'assets/GraphicalSelectorHelp/step4-light.jpg';
import step5Dark from 'assets/GraphicalSelectorHelp/step5-dark.jpg';
import step5Light from 'assets/GraphicalSelectorHelp/step5-light.jpg';
import type { RootState } from 'config/store';
import CS from '../common/styles';
import S from './styles';
Expand All @@ -19,12 +21,11 @@ const HowToUse = () => {
const { theme } = useSelector((state: RootState) => state.settings);

const step = (num: number) => {
let url = '';
if (num === 1) url = theme === 'light' ? step1Light : step1Dark;
if (num === 2) url = theme === 'light' ? step2Light : step2Dark;
if (num === 3) url = theme === 'light' ? step3Light : step3Dark;
if (num === 4) url = theme === 'light' ? step4Light : step4Dark;
return url;
const pics = {
light: [step1Light, step2Light, step3Light, step4Light, step5Light],
dark: [step1Dark, step2Dark, step3Dark, step4Dark, step5Dark]
};
return theme === 'light' ? pics.light[num - 1] : pics.dark[num - 1];
};

return (
Expand All @@ -41,17 +42,21 @@ const HowToUse = () => {
</div>
<S.ImageStep src={step(1)} alt="How to find a course infographic." />
<div>
<CS.TextWrapper> 2. Use the search bar to bring up the courses. </CS.TextWrapper>
<CS.TextWrapper> 2. Courses can be planned, unlocked or locked. </CS.TextWrapper>
</div>
<S.ImageStep src={step(2)} alt="How to use the search bar infographic." />
<S.ImageStep src={step(2)} alt="Icons for planned, unlocked or locked courses." />
<div>
<CS.TextWrapper> 3. Hover over a course to see related courses. </CS.TextWrapper>
<CS.TextWrapper> 3. Use the search bar to bring up the courses. </CS.TextWrapper>
</div>
<S.ImageStep src={step(3)} alt="How to quickly view related courses." />
<S.ImageStep src={step(3)} alt="How to use the search bar infographic." />
<div>
<CS.TextWrapper> 4. Click the course to view the course information! </CS.TextWrapper>
<CS.TextWrapper> 4. Hover over a course to see related courses. </CS.TextWrapper>
</div>
<S.ImageStep src={step(4)} alt="Where to click to view course information infographic." />
<S.ImageStep src={step(4)} alt="How to quickly view related courses." />
<div>
<CS.TextWrapper> 5. Click the course to view the course information! </CS.TextWrapper>
</div>
<S.ImageStep src={step(5)} alt="Where to click to view course information infographic." />
</S.ContentsWrapper>
</S.Wrapper>
);
Expand Down

0 comments on commit 453ffb0

Please sign in to comment.