diff --git a/src/app/degree-planner/saved-for-later-container/saved-for-later-container.component.html b/src/app/degree-planner/saved-for-later-container/saved-for-later-container.component.html index e6da7ebb29d767255aa7c5e1d8da0d677243444e..dc037d569d93bdb55346d77cf1bf0010f98024eb 100644 --- a/src/app/degree-planner/saved-for-later-container/saved-for-later-container.component.html +++ b/src/app/degree-planner/saved-for-later-container/saved-for-later-container.component.html @@ -3,6 +3,7 @@ id="saved-courses" class="course-list" cdkDropList + [cdkDropListData]="courses$ | async" [cdkDropListConnectedTo]="dropZoneIds$ | async" (cdkDropListDropped)="drop($event)"> <div class="course-wrapper" *ngFor="let course of courses$ | async" cdkDrag [cdkDragData]="course"> diff --git a/src/app/degree-planner/saved-for-later-container/saved-for-later-container.component.scss b/src/app/degree-planner/saved-for-later-container/saved-for-later-container.component.scss index 866d06058fb45b76ff3269f36b8ab23391d69f09..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 100644 --- a/src/app/degree-planner/saved-for-later-container/saved-for-later-container.component.scss +++ b/src/app/degree-planner/saved-for-later-container/saved-for-later-container.component.scss @@ -1,26 +0,0 @@ -#saved-for-later-container { - padding: 20px 0 0px 0; - margin-top: 0px; - border-top: 1px solid rgba(0, 0, 0, 0.54); -} - -.cdk-drop-list-dragging { - position: relative; - &:after { - content: 'Save course for later'; - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - background-color: white; - border: dashed 2px #2879a8; - color: #2879a8; - font-weight: bold; - text-transform: uppercase; - border-radius: 5px; - display: flex; - align-items: center; - justify-content: center; - } -} diff --git a/src/app/degree-planner/saved-for-later-container/saved-for-later-container.component.ts b/src/app/degree-planner/saved-for-later-container/saved-for-later-container.component.ts index 4caea152121129bccf3594214d97e61ad72106ff..8eff182fd59aeb0b46703cb0374664359e1df833 100644 --- a/src/app/degree-planner/saved-for-later-container/saved-for-later-container.component.ts +++ b/src/app/degree-planner/saved-for-later-container/saved-for-later-container.component.ts @@ -51,6 +51,7 @@ export class SavedForLaterContainerComponent implements OnInit { subjectCode: course.subjectCode, title: course.title, catalogNumber: course.catalogNumber, + newIndex: event.currentIndex, }), ); diff --git a/src/app/degree-planner/shared/course-item/course-item.component.ts b/src/app/degree-planner/shared/course-item/course-item.component.ts index cdad1ab602900ebc0262ddb487589e7b9514bd5a..3183bb840d6301b718eb5c2cd4b9f49cd2cbb46d 100644 --- a/src/app/degree-planner/shared/course-item/course-item.component.ts +++ b/src/app/degree-planner/shared/course-item/course-item.component.ts @@ -77,6 +77,7 @@ export class CourseItemComponent implements OnInit { subjectCode: course.subjectCode, title: course.title, catalogNumber: course.catalogNumber, + newIndex: 0, }), ); } @@ -94,7 +95,7 @@ export class CourseItemComponent implements OnInit { termCode: string; }; this.store.dispatch( - new MoveCourseBetweenTerms({ to: termCode, from, id }), + new MoveCourseBetweenTerms({ to: termCode, from, id, newIndex: 0 }), ); break; @@ -131,6 +132,7 @@ export class CourseItemComponent implements OnInit { subjectCode: subjectCode, title: title, catalogNumber: catalogNumber, + newIndex: 0, }), ); @@ -202,7 +204,17 @@ export class CourseItemComponent implements OnInit { const { subjectCode, courseId } = course; const termCode = term; - this.store.dispatch(new AddCourse({ subjectCode, courseId, termCode })); + const newIndex = 0; + this.store.dispatch( + new AddCourse({ subjectCode, courseId, termCode, newIndex }), + ); + } + + switchTerm(course, term) { + const { id, termCode: from } = course; + const to = term; + const newIndex = 0; + this.store.dispatch(new MoveCourseBetweenTerms({ to, from, id, newIndex })); } openCourseDetailsDialog(course) { diff --git a/src/app/degree-planner/store/actions/course.actions.ts b/src/app/degree-planner/store/actions/course.actions.ts index 24839bc4d309f9730a20991da89b8d7e4ad2b7c5..28f22b69508ae35c3cf96440cd271f2feb0c27d6 100644 --- a/src/app/degree-planner/store/actions/course.actions.ts +++ b/src/app/degree-planner/store/actions/course.actions.ts @@ -25,24 +25,33 @@ export enum CourseActionTypes { export class MoveCourseBetweenTerms implements Action { public readonly type = CourseActionTypes.MoveCourseBetweenTerms; - constructor(public payload: { to: string; from: string; id: number }) {} + constructor( + public payload: { to: string; from: string; id: number; newIndex: number }, + ) {} } export class MoveCourseBetweenTermsSuccess implements Action { public readonly type = CourseActionTypes.MoveCourseBetweenTermsSuccess; - constructor(public payload: { to: string; from: string; id: number }) {} + constructor( + public payload: { to: string; from: string; id: number; newIndex: number }, + ) {} } export class AddCourse implements Action { public readonly type = CourseActionTypes.AddCourse; constructor( - public payload: { subjectCode: string; courseId: string; termCode: string }, + public payload: { + subjectCode: string; + courseId: string; + termCode: string; + newIndex: number; + }, ) {} } export class AddCourseSuccess implements Action { public readonly type = CourseActionTypes.AddCourseSuccess; - constructor(public payload: { course: Course }) {} + constructor(public payload: { course: Course; newIndex: number }) {} } export class RemoveCourse implements Action { @@ -63,6 +72,7 @@ export class AddSaveForLater implements Action { courseId: string; title: string; catalogNumber: string; + newIndex: number; }, ) {} } @@ -75,6 +85,7 @@ export class AddSaveForLaterSuccess implements Action { courseId: string; title: string; catalogNumber: string; + newIndex: number; }, ) {} } diff --git a/src/app/degree-planner/store/effects/course.effects.ts b/src/app/degree-planner/store/effects/course.effects.ts index 2517e9a27850ee1aaf36011403d25d7a1411502c..4c380e68c5c70efbff3d8c3649f5462a93e1f977 100644 --- a/src/app/degree-planner/store/effects/course.effects.ts +++ b/src/app/degree-planner/store/effects/course.effects.ts @@ -102,7 +102,7 @@ export class CourseEffects { flatMap(([[action, visibleDegreePlan], subjects]) => { // TODO error handle the API calls const roadmapId = (visibleDegreePlan as DegreePlan).roadmapId; - const { subjectCode, termCode, courseId } = action.payload; + const { subjectCode, termCode, courseId, newIndex } = action.payload; const addCourse$ = this.api.addCourse( roadmapId, @@ -118,9 +118,8 @@ export class CourseEffects { subject: subjects[courseBase.subjectCode], })), ); - const toSuccessAction$ = courseBaseToCourse$.pipe( - map(course => new AddCourseSuccess({ course })), + map(course => new AddCourseSuccess({ course, newIndex })), ); return toSuccessAction$; diff --git a/src/app/degree-planner/store/reducer.ts b/src/app/degree-planner/store/reducer.ts index 54a039d555b49d4517301c1285b440117b7fa029..7b45f5bbd5edb152d8a8c101a01923241c7d688d 100644 --- a/src/app/degree-planner/store/reducer.ts +++ b/src/app/degree-planner/store/reducer.ts @@ -208,7 +208,12 @@ export function degreePlannerReducer( } case CourseActionTypes.MoveCourseBetweenTerms: { - const { to: toTermCode, from: fromTermCode, id } = action.payload; + const { + to: toTermCode, + from: fromTermCode, + id, + newIndex, + } = action.payload; const { yearCode: fromYearCode } = parseTermCode(fromTermCode); const { yearCode: toYearCode } = parseTermCode(toTermCode); const course = findCourse(state.visibleYears, fromTermCode, id); @@ -224,6 +229,7 @@ export function degreePlannerReducer( const toYear = createYearWithCourse( toTermCode, course, + newIndex, state.activeTermCodes, fromYearCode === toYearCode ? fromYear @@ -243,13 +249,14 @@ export function degreePlannerReducer( } case CourseActionTypes.AddCourseSuccess: { - const { course } = action.payload; + const { course, newIndex } = action.payload; const { termCode } = course; const { yearCode } = parseTermCode(termCode); const year: Year = createYearWithCourse( termCode, course, + newIndex, state.activeTermCodes, state.visibleYears[yearCode], ); @@ -294,21 +301,22 @@ export function degreePlannerReducer( } case CourseActionTypes.AddSaveForLater: { - const newSavedForLater: SavedForLaterCourse[] = [ - ...state.savedForLaterCourses, - { - id: null, - courseId: action.payload.courseId, - termCode: '0000', - topicId: 0, - subjectCode: action.payload.subjectCode, - title: action.payload.title, - catalogNumber: action.payload.catalogNumber, - courseOrder: 0, - subject: state.subjects[action.payload.subjectCode], - }, - ]; - return { ...state, savedForLaterCourses: newSavedForLater }; + const { newIndex } = action.payload; + const newSavedCourse: SavedForLaterCourse = { + id: null, + courseId: action.payload.courseId, + termCode: '0000', + topicId: 0, + subjectCode: action.payload.subjectCode, + title: action.payload.title, + catalogNumber: action.payload.catalogNumber, + courseOrder: 0, + subject: state.subjects[action.payload.subjectCode], + }; + const savedForLaterCoursesArr = state.savedForLaterCourses.slice(); + + savedForLaterCoursesArr.splice(newIndex, 0, newSavedCourse); + return { ...state, savedForLaterCourses: savedForLaterCoursesArr }; } case PlanActionTypes.CreatePlanSuccess: { @@ -469,12 +477,14 @@ const findCourse = (years: YearMapping, termCode: string, recordId: number) => { const createYearWithCourse = ( termCode: string, course: Course, + newIndex: number, activeTermCodes: string[], year = generateYearForTermCode(termCode, activeTermCodes), ): Year => { const { termName } = parseTermCode(termCode); const term = year[termName]; - const courses = term.courses.concat({ ...course, termCode }); + const courses = term.courses.slice(); + courses.splice(newIndex, 0, course); return { ...year, [termName]: { ...term, courses } }; }; diff --git a/src/app/degree-planner/term-container/term-container.component.scss b/src/app/degree-planner/term-container/term-container.component.scss index 7caae52a5bcd041fb6a47297091f41db96776e2b..c721ae2ed034afe0f641b654c54efe1c1df2ef43 100644 --- a/src/app/degree-planner/term-container/term-container.component.scss +++ b/src/app/degree-planner/term-container/term-container.component.scss @@ -45,27 +45,6 @@ font-weight: 400; } -.cdk-drop-list-dragging { - position: relative; - &:after { - content: 'Add course to term'; - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - background-color: #eef1f3; - border: dashed 2px #2879a8; - color: #2879a8; - font-weight: bold; - text-transform: uppercase; - border-radius: 5px; - display: flex; - align-items: center; - justify-content: center; - } -} - .add-new-wrapper { border-top: solid #e0e4e7 1px; padding: 8px; diff --git a/src/app/degree-planner/term-container/term-container.component.ts b/src/app/degree-planner/term-container/term-container.component.ts index d407da3da0f1704104a7dfb9de86b3c69ba83812..0fb5bcfc10bbb3a4c2a240c3c9142af64328cc6b 100644 --- a/src/app/degree-planner/term-container/term-container.component.ts +++ b/src/app/degree-planner/term-container/term-container.component.ts @@ -1,6 +1,10 @@ // Libraries import { Component, Input, OnInit } from '@angular/core'; -import { CdkDragDrop } from '@angular/cdk/drag-drop'; +import { + CdkDragDrop, + moveItemInArray, + transferArrayItem, +} from '@angular/cdk/drag-drop'; import { MatDialog } from '@angular/material'; import { Observable } from 'rxjs'; import { filter, map, distinctUntilChanged } from 'rxjs/operators'; @@ -129,6 +133,11 @@ export class TermContainerComponent implements OnInit { if (newContainer === previousContainer) { // If the user dropped a course into the same container do nothing + moveItemInArray( + event.container.data, + event.previousIndex, + event.currentIndex, + ); return; } else if (previousContainer.indexOf('term-') === 0) { // If moving from term to term @@ -136,15 +145,18 @@ export class TermContainerComponent implements OnInit { // Get the pervious and new term code, and the record ID const to = newContainer.substr(5); const { termCode: from, id } = event.item.data; + const newIndex = event.currentIndex; // Dispatch a new change request - this.store.dispatch(new actions.MoveCourseBetweenTerms({ to, from, id })); + this.store.dispatch( + new actions.MoveCourseBetweenTerms({ to, from, id, newIndex }), + ); } else if (previousContainer === 'saved-courses') { // If moving from saved courses to term // Get the term code from the new term dropzone's ID const termCode = newContainer.substr(5); - + const newIndex = event.currentIndex; // Pull the course data from the moved item const { subjectCode, courseId } = event.item.data; @@ -153,6 +165,7 @@ export class TermContainerComponent implements OnInit { subjectCode, courseId, termCode, + newIndex, }), ); this.store.dispatch( @@ -164,8 +177,9 @@ export class TermContainerComponent implements OnInit { ) { const termCode = newContainer.substr(5); const { subjectCode, courseId } = event.item.data; + const newIndex = event.currentIndex; this.store.dispatch( - new actions.AddCourse({ subjectCode, courseId, termCode }), + new actions.AddCourse({ subjectCode, courseId, termCode, newIndex }), ); } } diff --git a/src/assets/sass/general.scss b/src/assets/sass/general.scss index 8bc0f35bc176d52e334644ccce12527548be36ff..593a7048083756bc8cfe5de8e39b12e610eab566 100644 --- a/src/assets/sass/general.scss +++ b/src/assets/sass/general.scss @@ -94,32 +94,22 @@ body { } } -#favoriteCourse-dropZone { - .course-favorite { - .course-item { - border: 1px solid #b7b7b7; - border-radius: 4px; - padding: 5px 10px; - } - } -} - -.cdk-drag-placeholder { +.cdk-drop-list-dragging { position: relative; - opacity: 0; - height: 0; + .cdk-drag-placeholder { + opacity: 0.4; + min-height: 100% !important; + height: 100% !important; + transition: transform 200ms cubic-bezier(0, 0, 0.2, 1); + border-radius: 5px; + } } -.course-wrapper:not(.cdk-drag-preview) { - transform: translate(0) !important; +.cdk-drag-animating { + transition: transform 200ms cubic-bezier(0, 0, 0.2, 1); } - -.cdk-drag-preview { - .course-wrapper-inner { - transform: rotate(-3deg); - border-radius: 5px; - box-shadow: -3px 3px 3px 2px rgba(0, 0, 0, 0.26); - } +.course-list.cdk-drop-list-dragging .course-wrapper:not(.cdk-drag-placeholder) { + transition: transform 250ms cubic-bezier(0, 0, 0.2, 1) !important; } .no-courses {