Skip to content

Commit

Permalink
feat: Drag and drop schedules
Browse files Browse the repository at this point in the history
  • Loading branch information
mathhulk committed Oct 19, 2024
1 parent 20d12a3 commit 1611806
Show file tree
Hide file tree
Showing 20 changed files with 282 additions and 60 deletions.
6 changes: 3 additions & 3 deletions apps/backend/src/modules/schedule/typedefs/schedule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { gql } from "graphql-tag";
const typedef = gql`
type SelectedClass {
class: Class!
selectedSections: [String!]
selectedSections: [Int!]
}
type Event {
Expand All @@ -16,7 +16,7 @@ const typedef = gql`
}
type Schedule {
_id: ID
_id: ID!
name: String!
createdBy: String!
year: Int!
Expand Down Expand Up @@ -45,7 +45,7 @@ const typedef = gql`
subject: String!
courseNumber: String!
number: String!
sections: [String!]!
sections: [Int!]!
}
input UpdateScheduleInput {
Expand Down
13 changes: 10 additions & 3 deletions apps/frontend/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,13 @@ type Query {
classNumber: String!
number: String!
): Section
enrollment(
year: Int!
semester: Semester!
subject: String!
courseNumber: String!
number: String!
): Section!
}

input BookmarkedCourseInput {
Expand Down Expand Up @@ -131,7 +138,7 @@ enum Semester {

type SelectedClass {
class: Class!
selectedSections: [String!]
selectedSections: [Int!]
}

type Event {
Expand All @@ -144,7 +151,7 @@ type Event {
}

type Schedule {
_id: ID
_id: ID!
name: String!
createdBy: String!
year: Int!
Expand All @@ -168,7 +175,7 @@ input SelectedClassInput {
subject: String!
courseNumber: String!
number: String!
sections: [String!]!
sections: [Int!]!
}

input UpdateScheduleInput {
Expand Down
2 changes: 0 additions & 2 deletions apps/frontend/src/app/Plan/Term/Catalog/Catalog.module.scss
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
.content {
height: 100dvh;
position: fixed;
left: 0;
top: 0;
z-index: 989;
display: flex;
flex-direction: column;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
.content {
height: 100dvh;
position: fixed;
left: 0;
top: 0;
z-index: 989;
display: flex;
flex-direction: column;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,27 @@
.root {
display: flex;

&:global(.draggable-mirror) {
border-color: var(--blue-500);
z-index: 999;
box-shadow: 0 4px 16px rgb(0 0 0 / 10%);
}

&:global(.draggable-source--is-dragging) {
background-color: transparent;
box-shadow: unset;
border-style: dashed;

.body {
visibility: hidden;
}
}

&:not(:global(.draggable-source--is-dragging)) {
box-shadow: 0 1px 2px rgb(0 0 0 / 2.5%);
background-color: var(--foreground-color);
}

.border {
width: 8px;
background-color: var(--purple-500);
Expand Down Expand Up @@ -100,4 +121,4 @@
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ export default function Class({
}, [_class]);

return (
<div className={styles.root}>
<div className={styles.root} data-draggable>
<div
className={styles.border}
style={{
Expand Down
36 changes: 34 additions & 2 deletions apps/frontend/src/app/Schedule/Editor/SideBar/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useMemo } from "react";
import { useEffect, useMemo, useRef } from "react";

import { Plugins, Sortable } from "@shopify/draggable";
import { Plus } from "iconoir-react";

import { Button } from "@repo/theme";
Expand All @@ -15,6 +16,7 @@ import styles from "./SideBar.module.scss";
interface SideBarProps {
schedule: ISchedule;
expanded: boolean[];
onSortEnd: (previousIndex: number, currentIndex: number) => void;
onClassSelect: (
subject: string,
courseNumber: string,
Expand Down Expand Up @@ -44,9 +46,39 @@ export default function SideBar({
onSectionMouseOver,
onSectionMouseOut,
onExpandedChange,
onSortEnd,
}: SideBarProps) {
const bodyRef = useRef<HTMLDivElement>(null);

const [minimum, maximum] = useMemo(() => getUnits(schedule), [schedule]);

useEffect(() => {
if (!bodyRef.current) return;

const sortable = new Sortable(bodyRef.current, {
draggable: `[data-draggable]`,
distance: 8,
mirror: {
constrainDimensions: true,
},
plugins: [Plugins.ResizeMirror],
});

sortable.on("drag:stop", (event) => {
event.cancel();
});

sortable.on("sortable:stop", (event) => {
const { oldIndex, newIndex } = event;

onSortEnd(oldIndex, newIndex);
});

return () => {
sortable.destroy();
};
}, [onSortEnd]);

return (
<div className={styles.root}>
<div className={styles.header}>
Expand All @@ -73,7 +105,7 @@ export default function SideBar({
</Button>
</Catalog>
</div>
<div className={styles.body}>
<div className={styles.body} ref={bodyRef}>
{schedule.classes.map((selectedClass, index) => {
return (
<Class
Expand Down
40 changes: 39 additions & 1 deletion apps/frontend/src/app/Schedule/Editor/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,41 @@ export default function Editor() {
[setCurrentSection, tab]
);

const handleSortEnd = useCallback(
(previousIndex: number, currentIndex: number) => {
// Clone the schedule for immutability
const _schedule = structuredClone(schedule);

const [removed] = _schedule.classes.splice(previousIndex, 1);

_schedule.classes.splice(currentIndex, 0, removed);

// Update the schedule
updateSchedule(
schedule._id,
{
classes: _schedule.classes.map(
({
selectedSections,
class: { number, subject, courseNumber },
}) => ({
subject,
courseNumber,
number,
sections: selectedSections,
})
),
},
{
optimisticResponse: {
updateSchedule: _schedule,
},
}
);
},
[schedule]
);

const handleClassSelect = useCallback(
async (subject: string, courseNumber: string, number: string) => {
// Clone the schedule for immutability
Expand All @@ -178,6 +213,8 @@ export default function Editor() {

// Move existing classes to the top rather than duplicating them
if (existingClass) {
console.log("existingClass", existingClass);

const index = _schedule.classes.findIndex(
(selectedClass) =>
selectedClass.class.subject === subject &&
Expand Down Expand Up @@ -282,7 +319,7 @@ export default function Editor() {
}
);
},
[apolloClient, setExpanded]
[apolloClient, setExpanded, schedule]
);

const handleExpandedChange = (index: number, expanded: boolean) => {
Expand Down Expand Up @@ -347,6 +384,7 @@ export default function Editor() {
onExpandedChange={handleExpandedChange}
onSectionMouseOver={handleSectionMouseOver}
onSectionMouseOut={() => setCurrentSection(null)}
onSortEnd={handleSortEnd}
/>
<div className={styles.view} ref={bodyRef} id="boundary">
{tab === 0 ? (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
.content {
height: 100dvh;
position: fixed;
right: 0;
top: 0;
background-color: var(--background-color);
width: 640px;
border-left: 1px solid var(--border-color);
z-index: 989;
display: flex;
flex-direction: column;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
.content {
height: 100dvh;
position: fixed;
right: 0;
top: 0;
background-color: var(--background-color);
width: 640px;
border-left: 1px solid var(--border-color);
z-index: 989;
display: flex;
flex-direction: column;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,11 @@
height: 48px;
transform: translateX(calc(100% - 48px));
pointer-events: auto;
transition: transform 150ms ease-out;

&:hover,
&:focus {
animation: slideOut 250ms ease-in-out forwards;
transform: translateX(0);
}
}
}

@keyframes slideOut {
to {
transform: translateY(0);
}
}
26 changes: 0 additions & 26 deletions apps/frontend/src/components/Layout/Pins/Pins.module.scss

This file was deleted.

Loading

0 comments on commit 1611806

Please sign in to comment.