From c9d817b9c572792a8b8cf4547f7feea599f55c60 Mon Sep 17 00:00:00 2001 From: aniruddhzaveri Date: Sat, 25 May 2024 16:57:04 +0200 Subject: [PATCH 01/42] added first draft of course switching --- package-lock.json | 60 +++++++++++++++++++ package.json | 5 +- .../overview/course-overview.component.html | 21 +++++++ .../overview/course-overview.component.scss | 52 ++++++++++++++++ .../app/overview/course-overview.component.ts | 46 ++++++++++++++ .../course/course-overview.component.spec.ts | 4 ++ 6 files changed, 186 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 742d12a5354a..800c346a133d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -88,6 +88,7 @@ "@angular/cli": "17.3.7", "@angular/compiler-cli": "17.3.9", "@angular/language-service": "17.3.9", + "@playwright/test": "^1.44.1", "@sentry/types": "7.114.0", "@types/crypto-js": "4.2.2", "@types/d3-shape": "3.1.6", @@ -5683,6 +5684,21 @@ "url": "https://opencollective.com/unts" } }, + "node_modules/@playwright/test": { + "version": "1.44.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.44.1.tgz", + "integrity": "sha512-1hZ4TNvD5z9VuhNJ/walIjvMVvYkZKf71axoF/uiAqpntQJXpG64dlXhoDXE3OczPuTuvjf/M5KWFg5VAVUS3Q==", + "dev": true, + "dependencies": { + "playwright": "1.44.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=16" + } + }, "node_modules/@popperjs/core": { "version": "2.11.8", "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", @@ -17399,6 +17415,50 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/playwright": { + "version": "1.44.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.44.1.tgz", + "integrity": "sha512-qr/0UJ5CFAtloI3avF95Y0L1xQo6r3LQArLIg/z/PoGJ6xa+EwzrwO5lpNr/09STxdHuUoP2mvuELJS+hLdtgg==", + "dev": true, + "dependencies": { + "playwright-core": "1.44.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=16" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.44.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.44.1.tgz", + "integrity": "sha512-wh0JWtYTrhv1+OSsLPgFzGzt67Y7BE/ZS3jEqgGBlp2ppp1ZDj8c+9IARNW4dwf1poq5MgHreEM2KV/GuR4cFA==", + "dev": true, + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/playwright/node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/postcss": { "version": "8.4.38", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", diff --git a/package.json b/package.json index 25da94fad760..9c995779a51f 100644 --- a/package.json +++ b/package.json @@ -21,8 +21,8 @@ "@angular/forms": "17.3.9", "@angular/localize": "17.3.9", "@angular/material": "17.3.9", - "@angular/platform-browser-dynamic": "17.3.9", "@angular/platform-browser": "17.3.9", + "@angular/platform-browser-dynamic": "17.3.9", "@angular/router": "17.3.9", "@angular/service-worker": "17.3.9", "@ctrl/ngx-emoji-mart": "9.2.0", @@ -50,9 +50,9 @@ "crypto-js": "4.2.0", "dayjs": "1.11.11", "diff-match-patch-typescript": "1.0.8", - "fast-json-patch": "3.1.1", "dompurify": "3.1.3", "export-to-csv": "1.3.0", + "fast-json-patch": "3.1.1", "franc-min": "6.2.0", "interactjs": "1.10.27", "ismobilejs-es5": "0.0.1", @@ -118,6 +118,7 @@ "@angular/cli": "17.3.7", "@angular/compiler-cli": "17.3.9", "@angular/language-service": "17.3.9", + "@playwright/test": "^1.44.1", "@sentry/types": "7.114.0", "@types/crypto-js": "4.2.2", "@types/d3-shape": "3.1.6", diff --git a/src/main/webapp/app/overview/course-overview.component.html b/src/main/webapp/app/overview/course-overview.component.html index dc8f0793eef5..211de62dc0b4 100644 --- a/src/main/webapp/app/overview/course-overview.component.html +++ b/src/main/webapp/app/overview/course-overview.component.html @@ -13,6 +13,27 @@
+
+
+ + +
+
@if (course && course.courseIcon) {
diff --git a/src/main/webapp/app/overview/course-overview.component.scss b/src/main/webapp/app/overview/course-overview.component.scss index 8715d526342c..a8c2480583cd 100644 --- a/src/main/webapp/app/overview/course-overview.component.scss +++ b/src/main/webapp/app/overview/course-overview.component.scss @@ -253,3 +253,55 @@ jhi-secured-image { max-height: 91px; // To avoid cut offs in the dropdown menu content } } + +#container { + display: flex; + position: absolute; + #courseDropdownButton { + cursor: pointer; + position: relative; + padding-top: 30px; + padding-left: 20px; + padding-right: 30px; + top: 10px; + right: 10px; + #dropdownCourses { + display: none; + position: absolute; + width: 200px; + overflow: auto; + background-color: var(--dropdown-bg); + border-radius: 10px; + top: 100%; + left: 0; + border: 1px solid var(--border-color); + z-index: 9999; + ul { + list-style: none; + margin: 0px; + display: flex; + flex-direction: column; + padding: 0px; + } + li { + padding: 0; /* Adjust padding */ + border-bottom: 1px solid #ccc; /* Add border bottom */ + &:last-child { + border-bottom: none; /* Remove border for last item */ + } + } + a { + text-decoration: none; + color: var(--bs-body-color); + padding: 15px 20px; + display: block; + &:hover { + color: var(--link-item-color); + } + } + } + #dropdownCourses.active { + display: block; + } + } +} diff --git a/src/main/webapp/app/overview/course-overview.component.ts b/src/main/webapp/app/overview/course-overview.component.ts index 76d95264aa67..ddef46b474f1 100644 --- a/src/main/webapp/app/overview/course-overview.component.ts +++ b/src/main/webapp/app/overview/course-overview.component.ts @@ -29,6 +29,7 @@ import { IconDefinition, faChalkboardUser, faChartBar, + faChevronDown, faChevronRight, faCircleNotch, faClipboard, @@ -62,6 +63,8 @@ import { CourseExercisesComponent } from './course-exercises/course-exercises.co import { CourseLecturesComponent } from './course-lectures/course-lectures.component'; import { facSidebar } from '../../content/icons/icons'; import { CourseTutorialGroupsComponent } from './course-tutorial-groups/course-tutorial-groups.component'; +import { CoursesForDashboardDTO } from 'app/course/manage/courses-for-dashboard-dto'; +import { CourseForDashboardDTO } from 'app/course/manage/course-for-dashboard-dto'; interface CourseActionItem { title: string; @@ -94,6 +97,7 @@ export class CourseOverviewComponent implements OnInit, OnDestroy, AfterViewInit private courseId: number; private subscription: Subscription; course?: Course; + courses?: Course[]; refreshingCourse = false; private teamAssignmentUpdateListener: Subscription; private quizExercisesChannel: string; @@ -114,11 +118,13 @@ export class CourseOverviewComponent implements OnInit, OnDestroy, AfterViewInit // Properties to track hidden items for dropdown menu dropdownOpen: boolean = false; + dropdownCourses: boolean = false; anyItemHidden: boolean = false; hiddenItems: SidebarItem[] = []; thresholdsForEachSidebarItem: number[] = []; dropdownOffset: number; dropdownClickNumber: number = 0; + dropdownCoursesClickNumber: number = 0; readonly WINDOW_OFFSET: number = 300; readonly ITEM_HEIGHT: number = 38; readonly BREADCRUMB_AND_NAVBAR_HEIGHT: number = 88; @@ -163,6 +169,7 @@ export class CourseOverviewComponent implements OnInit, OnDestroy, AfterViewInit faNetworkWired = faNetworkWired; faChalkboardUser = faChalkboardUser; faChevronRight = faChevronRight; + faChevronDown = faChevronDown; faListCheck = faListCheck; faDoorOpen = faDoorOpen; facSidebar = facSidebar; @@ -201,6 +208,7 @@ export class CourseOverviewComponent implements OnInit, OnDestroy, AfterViewInit }); this.getCollapseStateFromStorage(); this.course = this.courseStorageService.getCourse(this.courseId); + this.getCourses(); this.isNotManagementView = !this.router.url.startsWith('/course-management'); // Notify the course access storage service that the course has been accessed this.courseAccessStorageService.onCourseAccessed(this.courseId); @@ -217,6 +225,7 @@ export class CourseOverviewComponent implements OnInit, OnDestroy, AfterViewInit @HostListener('window: resize', ['$event']) onResize() { this.dropdownOpen = false; + this.dropdownCourses = false; this.dropdownClickNumber = 0; this.updateVisibility(window.innerHeight); this.updateMenuPosition(); @@ -234,6 +243,17 @@ export class CourseOverviewComponent implements OnInit, OnDestroy, AfterViewInit } } + @HostListener('document: click', ['$event']) + onClickCloseDropdownCourses() { + if (this.dropdownCourses) { + this.dropdownCoursesClickNumber += 1; + if (this.dropdownCoursesClickNumber === 2) { + this.dropdownCourses = false; + this.dropdownCoursesClickNumber = 0; + } + } + } + /** Update sidebar item's hidden property based on the window height to display three-dots */ updateVisibility(height: number) { let thresholdLevelForCurrentSidebar = this.calculateThreshold(); @@ -275,6 +295,30 @@ export class CourseOverviewComponent implements OnInit, OnDestroy, AfterViewInit } } + getCourses() { + this.courseService.findAllForDashboard().subscribe({ + next: (res: HttpResponse) => { + if (res.body) { + const courses: Course[] = []; + res.body.courses.forEach((courseDto: CourseForDashboardDTO) => { + courses.push(courseDto.course); + }); + this.courses = courses.sort((a, b) => (a.title ?? '').localeCompare(b.title ?? '')); + if (this.courses.length > 3) { + const lastAccessedCourseIds = this.courseAccessStorageService.getLastAccessedCourses(); + this.courses = this.courses.filter((course) => lastAccessedCourseIds.includes(course.id!)); + } + } + }, + }); + } + + switchCourse(course: Course) { + this.router.navigate(['courses', course.id]).then(() => { + window.location.reload(); + }); + } + getCourseActionItems(): CourseActionItem[] { const courseActionItems = []; this.canUnenroll = this.canStudentUnenroll() && !this.course?.isAtLeastTutor; @@ -751,4 +795,6 @@ export class CourseOverviewComponent implements OnInit, OnDestroy, AfterViewInit this.isNavbarCollapsed = !this.isNavbarCollapsed; localStorage.setItem('navbar.collapseState', JSON.stringify(this.isNavbarCollapsed)); } + + protected readonly String = String; } diff --git a/src/test/javascript/spec/component/course/course-overview.component.spec.ts b/src/test/javascript/spec/component/course/course-overview.component.spec.ts index 23b6f7664b92..0cec13ba1a67 100644 --- a/src/test/javascript/spec/component/course/course-overview.component.spec.ts +++ b/src/test/javascript/spec/component/course/course-overview.component.spec.ts @@ -647,4 +647,8 @@ describe('CourseOverviewComponent', () => { expect(component.dropdownOpen).toBeTrue(); }); + + it('should initialize courses attribute when page is loaded', () => { + expect(component.courses?.length).toBe(2); + }); }); From 031ff3e3454f5ed2adf466aa62b615297d28c683 Mon Sep 17 00:00:00 2001 From: aniruddhzaveri Date: Sat, 25 May 2024 20:55:30 +0200 Subject: [PATCH 02/42] fixed navbar behaviour --- .../overview/course-overview.component.html | 69 ++++++++++++------ .../overview/course-overview.component.scss | 72 +++++++++++++++++-- .../course/course-overview.component.spec.ts | 34 +++++++++ 3 files changed, 147 insertions(+), 28 deletions(-) diff --git a/src/main/webapp/app/overview/course-overview.component.html b/src/main/webapp/app/overview/course-overview.component.html index 211de62dc0b4..7b1d2d62b052 100644 --- a/src/main/webapp/app/overview/course-overview.component.html +++ b/src/main/webapp/app/overview/course-overview.component.html @@ -9,31 +9,58 @@ opened="true" mode="side" > + @if (!isNavbarCollapsed) { +
+
+ + +
+
+ } @else { +
+
+ + +
+
+ }