@@ -19,8 +19,14 @@ import {
1919 type Edge ,
2020} from '@xyflow/react' ;
2121import dagre from '@dagrejs/dagre' ;
22- import { courseList , courses , prereqEdges } from '@/data/loadCourses' ;
22+ import {
23+ courseList ,
24+ coreCourseList ,
25+ courses ,
26+ prereqEdges ,
27+ } from '@/data/loadCourses' ;
2328import { useExplorerStore } from '@/store/explorerStore' ;
29+ import type { Course } from '@/types/course' ;
2430import CourseNode from '@/components/CourseNode' ;
2531import type { CourseNodeData } from '@/components/CourseNode' ;
2632import CourseDetailPanel from '@/components/CourseDetailPanel' ;
@@ -30,22 +36,18 @@ import ExplorerLegend from '@/components/ExplorerLegend';
3036const NODE_W = 180 ;
3137const NODE_H = 60 ;
3238
33- // Only draw edges where both endpoints are in our course set; SYSC courses that
34- // appear in COMP 3004's prereqs are not nodes yet (see TODO #3 above).
35- const knownCodes = new Set ( courseList . map ( ( c ) => c . code ) ) ;
36- const visibleEdges = prereqEdges . filter (
37- ( e ) => knownCodes . has ( e . from ) && knownCodes . has ( e . to ) ,
38- ) ;
39-
40- function computeLayout ( ) : {
39+ function computeLayout (
40+ visibleCourses : Course [ ] ,
41+ visibleEdges : { from : string ; to : string } [ ] ,
42+ ) : {
4143 nodes : Node < CourseNodeData > [ ] ;
4244 edges : Edge [ ] ;
4345} {
4446 const g = new dagre . graphlib . Graph ( ) ;
4547 g . setDefaultEdgeLabel ( ( ) => ( { } ) ) ;
4648 g . setGraph ( { rankdir : 'TB' , ranksep : 80 , nodesep : 40 } ) ;
4749
48- for ( const course of courseList ) {
50+ for ( const course of visibleCourses ) {
4951 g . setNode ( course . code , { width : NODE_W , height : NODE_H } ) ;
5052 }
5153
@@ -57,7 +59,7 @@ function computeLayout(): {
5759
5860 dagre . layout ( g ) ;
5961
60- const nodes : Node < CourseNodeData > [ ] = courseList . map ( ( course ) => {
62+ const nodes : Node < CourseNodeData > [ ] = visibleCourses . map ( ( course ) => {
6163 const pos = g . node ( course . code ) ;
6264 return {
6365 id : course . code ,
@@ -80,11 +82,35 @@ function computeLayout(): {
8082const nodeTypes : NodeTypes = { courseNode : CourseNode } ;
8183
8284export default function Explorer ( ) {
83- const { selectedCourse, highlightedSet, setSelectedCourse } =
84- useExplorerStore ( ) ;
85+ const {
86+ selectedCourse,
87+ highlightedSet,
88+ setSelectedCourse,
89+ showAllCourses,
90+ toggleShowAllCourses,
91+ } = useExplorerStore ( ) ;
92+
93+ // The single source of truth for "what's shown" — later filter modes
94+ // (reachable-on-click, department/year, search) should derive their own
95+ // visible set the same way, upstream of layout.
96+ const visibleCourses = showAllCourses ? courseList : coreCourseList ;
97+ const visibleCodes = useMemo (
98+ ( ) => new Set ( visibleCourses . map ( ( c ) => c . code ) ) ,
99+ [ visibleCourses ] ,
100+ ) ;
101+ const visibleEdges = useMemo (
102+ ( ) =>
103+ prereqEdges . filter (
104+ ( e ) => visibleCodes . has ( e . from ) && visibleCodes . has ( e . to ) ,
105+ ) ,
106+ [ visibleCodes ] ,
107+ ) ;
85108
86- // Layout is derived entirely from static import-time data; deps array is empty.
87- const { nodes, edges : layoutEdges } = useMemo ( ( ) => computeLayout ( ) , [ ] ) ;
109+ // Layout is recomputed whenever the visible course set changes.
110+ const { nodes, edges : layoutEdges } = useMemo (
111+ ( ) => computeLayout ( visibleCourses , visibleEdges ) ,
112+ [ visibleCourses , visibleEdges ] ,
113+ ) ;
88114
89115 // Re-derive edge styles when selection changes.
90116 const edges = useMemo (
@@ -108,9 +134,9 @@ export default function Explorer() {
108134
109135 return (
110136 < div className = "relative h-full w-full" >
111- < ExplorerSearch />
112- < ExplorerLegend />
113137 < ReactFlow
138+ // Remounting on toggle lets React Flow's `fitView` re-fit to the new set.
139+ key = { showAllCourses ? 'all' : 'core' }
114140 nodes = { nodes }
115141 edges = { edges }
116142 nodeTypes = { nodeTypes }
@@ -122,6 +148,20 @@ export default function Explorer() {
122148 < Background />
123149 < Controls />
124150 </ ReactFlow >
151+ { /* Graph controls stack here so they can't collide; the column ignores
152+ pointer events so canvas panning works through the gaps. */ }
153+ < div className = "pointer-events-none absolute top-4 left-4 z-10 flex w-72 max-w-[calc(100%-2rem)] flex-col gap-2" >
154+ < button
155+ type = "button"
156+ onClick = { toggleShowAllCourses }
157+ aria-pressed = { showAllCourses }
158+ className = "pointer-events-auto self-start rounded-md border border-gray-300 bg-white px-3 py-1.5 text-sm font-medium text-gray-700 shadow-sm hover:bg-gray-50"
159+ >
160+ { showAllCourses ? 'Show core only' : 'Show all courses' }
161+ </ button >
162+ < ExplorerSearch />
163+ < ExplorerLegend />
164+ </ div >
125165 < CourseDetailPanel
126166 course = { selected }
127167 onClose = { ( ) => setSelectedCourse ( null ) }
0 commit comments