Skip to content

Commit 8aea9eb

Browse files
authored
Merge pull request #698 from UTDNebula/develop
Sync with develop
2 parents c80a150 + 23acd81 commit 8aea9eb

23 files changed

+425
-229
lines changed

.github/workflows/versioning.yml

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
name: Scheduled Versioning
2+
3+
on:
4+
workflow_dispatch:
5+
schedule:
6+
- cron: '0 0 15 8 *'
7+
8+
jobs:
9+
versioning:
10+
name: Versioning System
11+
runs-on: ubuntu-latest
12+
13+
steps:
14+
- name: Checkout
15+
uses: actions/checkout@v3
16+
17+
- uses: actions/setup-python@v4
18+
with:
19+
python-version: '3.10'
20+
21+
- name: Install dependencies
22+
working-directory: validator
23+
run: |
24+
pip3 install -r requirements.txt
25+
26+
- name: Execute versioning script
27+
env:
28+
JIRA_API_KEY: ${{ secrets.JIRA_API_KEY }}
29+
working-directory: ./validator/scripts
30+
run: python diff.py

public/index.css

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -76,13 +76,13 @@ header a:hover {
7676
*/
7777

7878
.login {
79-
background-color: #3772ff;
80-
border-color: #3772ff;
79+
background-color: #6266F9;
80+
border-color: #6266F9;
8181
color: #fff !important;
8282
}
8383

8484
.login:hover {
85-
background-color: #2956c5;
85+
background-color: #474bb6;
8686
color: #fff;
8787
}
8888

src/components/planner/CourseInfoHoverCard.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@ export const CourseInfoHoverCard: FC<CourseInfoHoverCardProps> = ({
4444
</Link>
4545
</h3>
4646
<CourseDescription description={description} />
47-
4847
<HoverCard.Arrow className="fill-primary" />
4948
</HoverCard.Content>
5049
</HoverCard.Portal>
@@ -60,10 +59,15 @@ export default CourseInfoHoverCard;
6059
*/
6160
const CourseDescription = ({ description }: { description: string }) => {
6261
const [showMore, setShowMore] = useState(false);
62+
const boldDescription = description.replaceAll(/(\b[A-Z]{2,4} \d{4}\b)/gi, '<b>$1</b>');
6363
return (
6464
<span>
6565
<p className="text-xs">
66-
{!showMore ? `${description.substring(0, 200)}... ` : `${description} `}
66+
<span
67+
dangerouslySetInnerHTML={{
68+
__html: !showMore ? boldDescription.substring(0, 200) + '...' : boldDescription,
69+
}}
70+
/>{' '}
6771
<button
6872
className={`${showMore ? '' : 'inline'} font-medium text-primary`}
6973
onClick={(e) => {

src/components/planner/Planner.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,9 @@ export default function Planner({
9393
// Course that is currently being dragged
9494
const [activeCourse, setActiveCourse] = useState<ActiveDragData | null>(null);
9595

96+
// Controls drag locking for the sidebar
97+
const [isCourseDragging, setIsCourseDragging] = useState(false);
98+
9699
// Delay necessary so events inside draggables propagate
97100
// valid sensors: https://github.com/clauderic/dnd-kit/discussions/82#discussioncomment-347608
98101
const sensors = useSensors(
@@ -116,6 +119,7 @@ export default function Planner({
116119
const handleOnDragStart = ({ active }: { active: Active }) => {
117120
const originData = active.data.current as DragEventOriginData;
118121
setActiveCourse({ from: originData.from, course: originData.course });
122+
setIsCourseDragging(true);
119123
};
120124

121125
const handleOnDragEnd = ({ active, over }: { active: Active; over: Over | null }) => {
@@ -143,15 +147,16 @@ export default function Planner({
143147
);
144148
}
145149
}
150+
151+
setIsCourseDragging(false);
146152
};
147153

148154
const ref = useRef<HTMLDivElement>(null);
149155
// TODO: Use resizeobserver to change column count based on screen size
150156

151157
return (
152158
<DndContext
153-
// Enabling autoScroll causes odd behavior when dragging outside of a scrollable container (eg. Sidebar)
154-
autoScroll={false}
159+
autoScroll={true}
155160
sensors={sensors}
156161
collisionDetection={pointerWithin}
157162
onDragStart={handleOnDragStart}
@@ -218,6 +223,7 @@ export default function Planner({
218223
transferCredits={transferCredits}
219224
getSearchedDragId={(course) => `course-list-searched-${course.id}`}
220225
getRequirementDragId={(course) => `course-list-requirement-${course.id}`}
226+
courseDragged={isCourseDragging}
221227
/>
222228
</div>
223229
</DndContext>

src/components/planner/Sidebar/Sidebar.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export interface CourseSelectorContainerProps {
2525
transferCredits: string[];
2626
getSearchedDragId: GetDragIdByCourse;
2727
getRequirementDragId: GetDragIdByCourse;
28+
courseDragged: boolean;
2829
}
2930

3031
function CourseSelectorContainer({
@@ -33,6 +34,7 @@ function CourseSelectorContainer({
3334
transferCredits,
3435
getSearchedDragId,
3536
getRequirementDragId,
37+
courseDragged,
3638
}: CourseSelectorContainerProps) {
3739
const {
3840
data: validationData,
@@ -119,7 +121,9 @@ function CourseSelectorContainer({
119121
{open ? (
120122
<div
121123
id="tutorial-editor-1"
122-
className="z-0 h-screen w-[30%] min-w-[30%] overflow-x-hidden overflow-y-scroll"
124+
className={`z-0 h-screen w-[30%] min-w-[30%] overflow-x-hidden ${
125+
courseDragged ? 'overflow-y-hidden' : 'overflow-y-scroll'
126+
}`}
123127
>
124128
<div className="flex h-fit min-h-screen w-full flex-col gap-y-4 bg-white p-4">
125129
<div className="flex flex-col">

src/components/planner/Tiles/SemesterTile.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,7 @@ const DroppableSemesterTile: FC<DroppableSemesterTileProps> = ({
208208
const { setNodeRef } = useDroppable({
209209
id: dropId,
210210
data: { to: 'semester-tile', semester } as DragDataToSemesterTile,
211+
disabled: semester.locked,
211212
});
212213

213214
return (

validator/degree_solver.py

Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
11
from __future__ import annotations
22
from enum import Enum
33
from glob import glob
4+
from collections import Counter
45

56
from pydantic import Json
67

78
from typing import Any
89

910
import core
10-
from major.requirements import AbstractRequirement
11+
from major.requirements import AbstractRequirement, FreeElectiveRequirement
1112
from dataclasses import dataclass
1213

13-
from major.requirements.map import REQUIREMENTS_MAP
14+
from major.requirements import loader
1415
import json
1516

1617
from major.requirements.shared import (
@@ -19,6 +20,7 @@
1920
)
2021
from course import Course
2122

23+
LOADER = loader.Loader()
2224

2325
# Read all degree plan JSON files and store their contents in a hashmap
2426
# This is so that we can avoid reading all the files each time we want to get the data for a certain course
@@ -144,7 +146,7 @@ def __init__(
144146
requirements: DegreeRequirementsInput,
145147
bypasses: BypassInput,
146148
) -> None:
147-
self.courses = set(courses)
149+
self.courses = set([Course.from_name(course) for course in courses])
148150
self.degree_requirements = self.load_requirements(requirements)
149151
self.solved_core: core.store.AssignmentStore | None = None
150152
self.bypasses = bypasses
@@ -187,9 +189,7 @@ def load_requirements(
187189

188190
# Add requirements
189191
for req_data in requirements_data:
190-
major_req.requirements.append(
191-
REQUIREMENTS_MAP[req_data["matcher"]].from_json(req_data)
192-
)
192+
major_req.requirements.append(LOADER.requirement_from_json(req_data))
193193
degree_requirements.append(major_req)
194194
# We don't need to check the other JSON files
195195
break
@@ -201,23 +201,28 @@ def load_requirements(
201201
def solve(self) -> DegreeRequirementsSolver:
202202
# Run for core
203203
core_solver = self.load_core()
204-
self.solved_core = core_solver.solve(
205-
[Course.from_name(course) for course in self.courses], []
206-
)
207-
# Set of the core courses that are fulfilled, so they won't be considered as free electives
208-
used_core_courses = set()
204+
self.solved_core = core_solver.solve(list(self.courses), [])
205+
206+
# Counter of the core courses and their used hours, so they won't be considered as free electives.
207+
used_core_courses: Counter[Course] = Counter()
209208
if self.solved_core is not None:
210209
for req_fill in self.solved_core.reqs_to_courses.values():
211-
used_core_courses.update([course.name for course in req_fill.keys()])
210+
used_core_courses.update(req_fill)
212211

213212
# Run for major
214213
for degree_req in self.degree_requirements:
215214
for course in self.courses:
216-
# Make sure it's not a core course
217-
if course in used_core_courses:
218-
continue
219215
for requirement in degree_req.requirements:
220-
if requirement.attempt_fulfill(course):
216+
# Free elective requirements are special, since they can take left over hours from core courses.
217+
if type(requirement) == FreeElectiveRequirement:
218+
if requirement.attempt_fulfill(
219+
course.name,
220+
available_hours=(
221+
int(course.hours) - used_core_courses[course]
222+
),
223+
):
224+
break
225+
elif requirement.attempt_fulfill(course.name):
221226
break
222227

223228
# Handle requirements bypasses for major

validator/gen_schema.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from types import GenericAlias
77
from typing import Any, ForwardRef
88
from jsonschema import Draft7Validator
9-
from major.requirements import REQUIREMENTS_MAP
9+
from major.requirements import loader
1010

1111
schema: dict[str, Any] = {
1212
"$schema": Draft7Validator.META_SCHEMA["$id"],
@@ -89,8 +89,9 @@ def forward_ref_to_schema(ref: ForwardRef) -> dict[str, Any]:
8989
raise Exception("Expected type, got", type(ref_type), ref_type)
9090

9191

92-
for req_name in REQUIREMENTS_MAP:
93-
req = REQUIREMENTS_MAP[req_name]
92+
req_loader = loader.Loader()
93+
for req_name in req_loader.REQUIREMENTS_MAP:
94+
req = req_loader.REQUIREMENTS_MAP[req_name]
9495
requirement_schema_props: dict[str, Any] = {"matcher": {"const": req_name}}
9596

9697
for prop_name, prop_type in req.JSON.__annotations__.items():
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
from .base import AbstractRequirement
22
from .shared import *
3-
from .map import *
3+
from . import loader

validator/major/requirements/base.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
from __future__ import annotations
44
from abc import abstractmethod, ABC
55
from dataclasses import dataclass
6-
76
from typing import Any
87

98
from pydantic import Json
@@ -19,6 +18,7 @@ class AbstractRequirement(ABC):
1918
def attempt_fulfill(
2019
self,
2120
course: str,
21+
available_hours: int = 0,
2222
) -> bool:
2323
pass
2424

0 commit comments

Comments
 (0)