Compare commits

...

391 Commits

Author SHA1 Message Date
Hykilpikonna 97e6b9e42b Merge remote-tracking branch 'upstream/master' 2020-08-02 12:45:01 -04:00
Hykilpikonna 1e2da25d58 [O] Allow cookies to determine demo mode 2020-08-02 12:43:43 -04:00
Hykilpikonna 66ba3430fd [U] Release v0.5.6.1761 2020-08-01 20:25:07 -04:00
Hykilpikonna 1abbce4daa [O] Lock submission button when in demo mode 2020-08-01 20:23:14 -04:00
Hykilpikonna d8430a0885 [F] Fix buttons locked when rating submission failed 2020-08-01 20:22:48 -04:00
Hykilpikonna c6054648d3 [S] Style page not available message 2020-08-01 20:22:18 -04:00
Hykilpikonna 74d3183da3 [+] Display page not available for course-selection in demo mode 2020-08-01 20:22:09 -04:00
Hykilpikonna cc9a35355d [S] Fix all unassigned font issues 2020-08-01 20:21:37 -04:00
Hykilpikonna 90e4e334c4 [S] Fix the align of demo text 2020-08-01 20:21:19 -04:00
Hykilpikonna b51ec78ff5 Merge remote-tracking branch 'upstream/master' 2020-08-01 19:47:30 -04:00
Hykilpikonna 8ef9b152f0 [+] Add demo notification 2020-08-01 19:45:47 -04:00
Hykilpikonna 08b8b31e47 [F] Deploy under personal account rather than organization 2020-08-01 19:24:12 -04:00
Hykilpikonna 1bec167be6 [+] Separate demo cname record 2020-08-01 17:57:35 -04:00
Hykilpikonna 641778b169 [O] Optimize code length 2020-08-01 16:19:17 -04:00
Hykilpikonna 73a651f3aa [F] Let bubble chart start from the selected term if a specific term is selected 2020-08-01 16:17:35 -04:00
Hykilpikonna e84911c25d [F] Only display one quarter if a specific quarter is selected 2020-08-01 16:14:57 -04:00
Hykilpikonna 467e036411 [O] Let bubble chart start at when the first quarter starts 2020-08-01 16:14:02 -04:00
Hykilpikonna c643eedbc8 [F] Let bubble chart stop at when the last quarter ends 2020-08-01 16:13:33 -04:00
Hykilpikonna 542ab0a286 [+] Show ratings by default 2020-08-01 16:05:20 -04:00
Hykilpikonna d31173fb9d [F] Fix gradedCourses null 2020-08-01 16:00:22 -04:00
Hykilpikonna a8b49f713b Revert "[O] Make gradedCourses a getter instead of a field"
This reverts commit cc046010ff.
2020-08-01 15:58:49 -04:00
Hykilpikonna c65a6fb7da [+] Load demo 2020-08-01 15:58:40 -04:00
Hykilpikonna 17808a3c1a [+] Create demo loader 2020-08-01 15:58:21 -04:00
Hykilpikonna cc046010ff [O] Make gradedCourses a getter instead of a field 2020-08-01 15:49:27 -04:00
Hykilpikonna 0c5b993f20 [+] Encapsulate method for http get 2020-08-01 15:48:58 -04:00
Hykilpikonna 1c63f795f4 [+] Show login only if not in demo mode 2020-08-01 14:25:31 -04:00
Hykilpikonna ba14fc80a9 [+] Detect demo mode 2020-08-01 14:23:14 -04:00
Hykilpikonna 6015c27e1b [-] Remove windows run script 2020-08-01 14:02:50 -04:00
Hykilpikonna f4ae9f4a8c [-] Remove gitattributes 2020-08-01 14:02:03 -04:00
Hykilpikonna dad1cb0c3c [O] Convert line endings again 2020-08-01 13:59:17 -04:00
Hykilpikonna 608c7f4613 Merge branch 'master' of https://github.com/HyDevelop/VeracrossAnalyzer.Client 2020-08-01 13:48:41 -04:00
Hykilpikonna 991153997b [U] npm update 2020-08-01 13:48:39 -04:00
Hykilpikonna e8bc3e695b [O] Force LF line ending 2020-08-01 13:46:49 -04:00
Hykilpikonna 98fabf382c [+] Add token data to demo-data 2020-08-01 13:42:24 -04:00
Hykilpikonna 833eec4833 [+] Move backup data to demo-data 2020-08-01 13:40:44 -04:00
Hykilpikonna 7aa258bc97 [PR] Merge pull request #6 from HyDevelop/dependabot/npm_and_yarn/elliptic-6.5.3
Bump elliptic from 6.5.2 to 6.5.3
2020-07-31 16:52:33 -04:00
dependabot[bot] a4b6c50c0e Bump elliptic from 6.5.2 to 6.5.3
Bumps [elliptic](https://github.com/indutny/elliptic) from 6.5.2 to 6.5.3.
- [Release notes](https://github.com/indutny/elliptic/releases)
- [Commits](https://github.com/indutny/elliptic/compare/v6.5.2...v6.5.3)

Signed-off-by: dependabot[bot] <support@github.com>
2020-07-31 20:51:33 +00:00
Vanilla 47deca8859 Merge pull request #5 from HyDevelop/dependabot/npm_and_yarn/lodash-4.17.19
Bump lodash from 4.17.15 to 4.17.19
2020-07-18 19:09:02 +08:00
dependabot[bot] ca01ee743b Bump lodash from 4.17.15 to 4.17.19
Bumps [lodash](https://github.com/lodash/lodash) from 4.17.15 to 4.17.19.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.15...4.17.19)

Signed-off-by: dependabot[bot] <support@github.com>
2020-07-18 08:46:31 +00:00
Vanilla 27ca1d8bfe Merge pull request #4 from HyDevelop/dependabot/npm_and_yarn/websocket-extensions-0.1.4
Bump websocket-extensions from 0.1.3 to 0.1.4
2020-06-07 19:41:42 +08:00
dependabot[bot] 05a6abc1ba Bump websocket-extensions from 0.1.3 to 0.1.4
Bumps [websocket-extensions](https://github.com/faye/websocket-extensions-node) from 0.1.3 to 0.1.4.
- [Release notes](https://github.com/faye/websocket-extensions-node/releases)
- [Changelog](https://github.com/faye/websocket-extensions-node/blob/master/CHANGELOG.md)
- [Commits](https://github.com/faye/websocket-extensions-node/compare/0.1.3...0.1.4)

Signed-off-by: dependabot[bot] <support@github.com>
2020-06-07 07:51:46 +00:00
Hykilpikonna 29ba8509bc [+] Upload backup data 2020-06-06 13:38:28 -04:00
Hykilpikonna a290a3d93e [F] Fix final assignment compatibility 2020-06-06 13:37:30 -04:00
Hykilpikonna 4fe83d732a [F] Fix a login bug 2020-05-26 09:18:25 -04:00
Hykilpikonna 5f7a564b33 [+] Add check for blank usernames 2020-05-25 21:33:15 -04:00
Hykilpikonna 0de22d4f0d [O] Unify show loading 2020-05-25 21:32:05 -04:00
Hykilpikonna 15149d1d07 [O] Fix field name mismatch 2020-05-25 21:28:41 -04:00
Hykilpikonna 5b54fc3b27 [F] Use first name instead of nickname 2020-05-25 21:24:28 -04:00
Hykilpikonna beed3883a8 [-] Remove courses request in app.ts 2020-05-25 21:23:28 -04:00
Hykilpikonna 254c2690e5 [+] Implement new user model 2020-05-25 21:21:58 -04:00
Hykilpikonna 2839304dc5 [F] Fix cookie keep duration 2020-05-25 21:15:40 -04:00
Hykilpikonna 48b4b989b1 [O] Combine onEnter with loginClick 2020-05-25 21:15:17 -04:00
Hykilpikonna 96306afecf [-] Remove maintenance check since it's badly written 2020-05-25 21:11:51 -04:00
Hykilpikonna d267988b55 [O] Combine login into one function 2020-05-25 21:11:27 -04:00
Hykilpikonna 5780a6fc25 [S] Add margin for multiple comments 2020-05-08 20:08:47 -04:00
Hykilpikonna 28eddf4aea [+] Display the rating that the commentor gave 2020-05-08 20:08:35 -04:00
Hykilpikonna d8a7a700c4 [S] Add comments header 2020-05-08 20:07:47 -04:00
Hykilpikonna fc6b3331ac [S] Add a icon for user 2020-05-08 20:07:29 -04:00
Hykilpikonna 2210e6389f [S] Make blolckquote look like a md blockquote 2020-05-08 20:07:13 -04:00
Hykilpikonna ebf692e6a3 [+] Display comment text in blockquote form 2020-05-08 20:04:33 -04:00
Hykilpikonna 5422c1c45f [+] Display comments users 2020-05-08 20:04:12 -04:00
Hykilpikonna f5c63aa958 [O] Obtain comments 2020-05-08 20:00:54 -04:00
Hykilpikonna 4b0bf16916 [+] Create detailsComments field 2020-05-08 20:00:42 -04:00
Hykilpikonna b8a5172d70 [+] Calculate average rating in CourseInfoRating 2020-05-08 20:00:19 -04:00
Hykilpikonna c4735e5603 [+] Split username 2020-05-08 19:59:58 -04:00
Hykilpikonna e601f6b57a [F] Fix rating display rounding issue 2020-05-08 18:20:11 -04:00
Hykilpikonna 272e7e5b98 [+] Add comment to scroll down to find submit button 2020-04-29 11:38:15 -04:00
Hykilpikonna 5304c77548 [O] Don't hide the course-selection tab for seniors 2020-04-29 11:37:43 -04:00
Hykilpikonna 2e57869c5f [F] Fix problem where the ratings are not loading when a course details page is opened after another 2020-04-29 11:37:26 -04:00
Hykilpikonna cde51445a1 [O] Don't block seniors from seeing course ratings 2020-04-29 11:36:48 -04:00
Hykilpikonna 9b144b58bb [O] Enable show all courses by default for seniors 2020-04-29 11:36:00 -04:00
Hykilpikonna 1796bf8fb8 [+] Implement show updates command 2020-04-29 11:35:14 -04:00
Hykilpikonna c587cf7953 [+] Auto detect update notification on load 2020-04-29 11:34:56 -04:00
Hykilpikonna 922e3911d8 [+] Add updates button on avatar menu 2020-04-29 11:34:27 -04:00
Hykilpikonna aadbaf0f75 [+] Create method to show update message 2020-04-29 11:33:55 -04:00
Hykilpikonna 4b95053651 [S] Create comic sans class 2020-04-29 11:33:47 -04:00
Hykilpikonna f66aaa09b0 [F] Forgot to import 2020-04-27 21:57:47 -04:00
Hykilpikonna f7b352f595 [+] Add notification for the last rating 2020-04-27 21:57:25 -04:00
Hykilpikonna 3cfb888a5f [F] Fix error message when closing course details 2020-04-27 21:09:17 -04:00
Hykilpikonna d9d0ea3168 [+] Add todo for comments display 2020-04-27 18:42:14 -04:00
Hykilpikonna 408d62c09d [S] Unify numeric rating looks 2020-04-27 18:25:17 -04:00
Hykilpikonna a4c61f3dd9 [+] Display numeric ratings too 2020-04-27 18:25:05 -04:00
Hykilpikonna f3321ff267 [S] Make criteria title font size larger 2020-04-27 18:18:37 -04:00
Hykilpikonna 2ef242099b [S] Reduce margin from subtitle to ratings 2020-04-27 18:18:22 -04:00
Hykilpikonna 3497c277e3 [S] Specify height for the float boxes 2020-04-27 18:17:57 -04:00
Hykilpikonna 74c47b8dd9 [S] Change "rating details" to "ratings" 2020-04-27 18:17:16 -04:00
Hykilpikonna c860ffd315 [S] Adjust margin under title 2020-04-27 18:16:37 -04:00
Hykilpikonna 1b13f4abb2 [S] Copy style from course-head 2020-04-27 18:16:07 -04:00
Hykilpikonna a8c882e343 [+] Display rating stars 2020-04-27 18:15:22 -04:00
Hykilpikonna 99c51bb205 [+] Open details on click 2020-04-27 18:15:13 -04:00
Hykilpikonna 142e0da7c0 [+] Encapsulate openDetails() 2020-04-27 18:14:58 -04:00
Hykilpikonna 226297c810 [+] Add details/comments popup for course-detail 2020-04-27 18:14:29 -04:00
Hykilpikonna 13167fe050 [O] Separate rating criteria 2020-04-27 18:13:56 -04:00
Hykilpikonna 9665b3a793 [S] Adjust numeric rating margins 2020-04-27 17:24:17 -04:00
Hykilpikonna 95111476f5 [+] Display rating count 2020-04-27 17:23:50 -04:00
Hykilpikonna fcfdd248b6 [+] Display numeric rating out of 5 2020-04-27 17:21:30 -04:00
Hykilpikonna df792a57c8 [+] Display star rating in course-detail 2020-04-27 17:17:21 -04:00
Hykilpikonna 41e68d070d [S] Fix loading bar positioning on full screen 2020-04-27 17:16:53 -04:00
Hykilpikonna 42231c79d9 [+] Calclate ratingAverages and overallRating 2020-04-27 17:16:27 -04:00
Hykilpikonna 41cc66a9f4 [O] Add comments to fields 2020-04-27 17:16:14 -04:00
Hykilpikonna 71dcf9e8c1 [O] Make AnalyzedRating a class 2020-04-27 17:16:01 -04:00
Hykilpikonna 38232c3182 [+] Create star rating component 2020-04-27 16:09:08 -04:00
Hykilpikonna 13288c6620 [+] Import star rating sprite 2020-04-27 16:08:48 -04:00
Hykilpikonna 11b855109a [O] Fix tslint quotation marks 2020-04-27 15:55:00 -04:00
Hykilpikonna 4110b91b45 [M] Move loading-spinner from pages to components 2020-04-27 15:46:43 -04:00
Hykilpikonna c90c50a4b6 [F] Forgot to import component 2020-04-27 05:12:53 -04:00
Hykilpikonna ef5322ebd5 [S] Vertically center loading spinner 2020-04-27 05:12:38 -04:00
Hykilpikonna e82ae7cb88 [+] Show loading spinner when loading ratings 2020-04-27 05:12:17 -04:00
Hykilpikonna 0726783465 [+] Split course-detail items into float-left and float-right 2020-04-27 05:11:28 -04:00
Hykilpikonna 8a7007c7b7 [U] Use LoadingSpinner 2020-04-27 05:10:52 -04:00
Hykilpikonna 606732c987 [+] Add centered property to loading spinner 2020-04-27 05:08:45 -04:00
Hykilpikonna b664674a06 [+] Create a loading spinner component 2020-04-27 05:04:10 -04:00
Hykilpikonna 9b201d8475 [+] Add error message on fail 2020-04-27 04:45:09 -04:00
Hykilpikonna 66d553f6a7 [+] Load rating 2020-04-27 04:43:21 -04:00
Hykilpikonna 94a1a0f108 [+] Use AnalyzedRating for courseInfo 2020-04-27 04:43:03 -04:00
Hykilpikonna e3af901caf [+] Create AnalyzedRating class 2020-04-27 04:42:41 -04:00
Hykilpikonna ad5f5c3e39 [+] Update loading screen when finished loading 2020-04-27 04:26:46 -04:00
Hykilpikonna ddc6aacaf3 [+] Create loading screen for course-selection 2020-04-27 04:26:17 -04:00
Hykilpikonna a88164319b [O] Fix grammar issue 2020-04-27 04:14:51 -04:00
Hykilpikonna 93aeab2c01 [+] Display error message if not all criterias are rated 2020-04-27 04:14:26 -04:00
Hykilpikonna 0be4bc6cb4 [F] Fix issue where rating wouldn't reset 2020-04-27 04:14:09 -04:00
Hykilpikonna d12984cb3a [O] Disable button when rating is posting 2020-04-27 04:13:53 -04:00
Hykilpikonna 2fc8986bd8 [O] Optimize language 2020-04-27 03:45:54 -04:00
Hykilpikonna f8b8588413 [O] Shorten the message 2020-04-27 03:45:09 -04:00
Hykilpikonna 3519ca6018 [F] Fix execution order 2020-04-27 03:44:18 -04:00
Hykilpikonna cffc39dbfd [O] Use filter().length instead of map().reduce() 2020-04-27 03:42:41 -04:00
Hykilpikonna c75dc76526 [U] Add a comment about that ↓ 2020-04-27 03:40:49 -04:00
Hykilpikonna e6d5e35ff0 [F] Updating the first review shouldn't count as first review 2020-04-27 03:40:24 -04:00
Hykilpikonna cb05b8be96 [+] Use showRating in course-head 2020-04-27 03:26:01 -04:00
Hykilpikonna 8735237626 [+] Add default case 2020-04-27 03:25:42 -04:00
Hykilpikonna 67a9dcc37f [+] Implement switch rating 2020-04-27 03:25:06 -04:00
Hykilpikonna 18825670e4 [+] Add showRating field in App 2020-04-27 03:24:51 -04:00
Hykilpikonna 17dc6045b0 [+] Add rating buttons toggler in dropdown 2020-04-27 03:24:27 -04:00
Hykilpikonna c8ec7893e1 [-] Remove unnecessary isDark() 2020-04-27 03:24:08 -04:00
Hykilpikonna 837fda814b [O] Remove unnecessary line of initialization 2020-04-27 03:21:25 -04:00
Hykilpikonna e306b91e37 [S] Add darkmode colors to rating blocks 2020-04-27 03:20:51 -04:00
Hykilpikonna 50377cd9ee [+] Add message for first rating 2020-04-27 03:01:00 -04:00
Hykilpikonna 969336f203 [+] Change "Submit" to "Update" if rated 2020-04-27 02:53:41 -04:00
Hykilpikonna ae14eae711 [S] Specfy rated button color 2020-04-27 02:53:26 -04:00
Hykilpikonna 5603831f5e [+] Allow changing rating 2020-04-27 02:52:45 -04:00
Hykilpikonna 760cfe2463 [S] Make navbar margins look nicer 2020-04-27 02:52:18 -04:00
Hykilpikonna 509243f874 [O] Use CourseInfoRating in course-head 2020-04-27 02:23:55 -04:00
Hykilpikonna 311544b5aa [F] Fix anonymous default open 2020-04-27 02:23:33 -04:00
Hykilpikonna 1cf9ff157a [+] Parse rating when parsing course 2020-04-27 02:19:55 -04:00
Hykilpikonna 8e54749e2e [+] Create alternative constructor for posting 2020-04-27 02:19:33 -04:00
Hykilpikonna 9adc78f287 [+] Create constructor for CourseInfoRating 2020-04-27 02:19:20 -04:00
Hykilpikonna 6309a77b63 [+] Create CourseInfoRating class 2020-04-27 02:19:06 -04:00
Hykilpikonna 1eb8b5c137 [-] Remove annoying width calculations 2020-04-27 02:05:31 -04:00
Hykilpikonna e78c5c8cef [+] Implement submitRating 2020-04-27 02:02:54 -04:00
Hykilpikonna 01662cfa8d [S] Adjust popup top position 2020-04-27 01:59:49 -04:00
Hykilpikonna 1bda3ccd53 [S] Adjsut textarea margins 2020-04-27 01:59:19 -04:00
Hykilpikonna d9896f0b48 [+] Add checkbox for anonymous 2020-04-27 01:59:07 -04:00
Hykilpikonna ac13da1dc6 [+] Add textarea for comment 2020-04-27 01:58:46 -04:00
Hykilpikonna 20988fb35e [S] Fix rated course left padding 2020-04-27 01:58:04 -04:00
Hykilpikonna 5e6c0853c1 [S] Further reduce margins 2020-04-26 20:16:03 -04:00
Hykilpikonna 7a467d6346 [S] Add some margins to subtitle 2020-04-26 20:15:32 -04:00
Hykilpikonna 505953753c [S] Fix header overflow 2020-04-26 20:15:15 -04:00
Hykilpikonna e4ddd12b7b [S] Make subtitle italic 2020-04-26 20:15:01 -04:00
Hykilpikonna e1decbf7c2 [O] Separate popup header to a separate div 2020-04-26 20:14:13 -04:00
Hykilpikonna cdd1712b85 [S] Colorize title too 2020-04-26 20:13:43 -04:00
Hykilpikonna 356259f0ec [S] Give stars a golden color 2020-04-26 20:13:28 -04:00
Hykilpikonna 7928fe6598 [S] Make stars larger 2020-04-26 20:13:09 -04:00
Hykilpikonna bcc8c8492f [S] Set font size for description 2020-04-26 20:12:56 -04:00
Hykilpikonna d26f6618ec [S] Make title larger 2020-04-26 20:12:30 -04:00
Hykilpikonna af919e77e7 [S] Auto wrap text 2020-04-26 20:12:19 -04:00
Hykilpikonna c0aa07f736 [S] Reduce top margin 2020-04-26 20:12:05 -04:00
Hykilpikonna ae3e5c5092 [S] Separate items with margin 2020-04-26 20:03:28 -04:00
Hykilpikonna a2665d12f6 [S] Left-align text 2020-04-26 20:01:45 -04:00
Hykilpikonna 00edf57ebc [O] Give the rating popup an id 2020-04-26 18:57:10 -04:00
Hykilpikonna f1bd6799a7 [O] Change "Confirm" to "Submit" 2020-04-26 18:46:11 -04:00
Hykilpikonna 4b12b44f8b [F] Fix UI upate issue after changing stars 2020-04-26 18:45:58 -04:00
Hykilpikonna f9fc840c3c [+] Implement change stars on click 2020-04-26 18:45:43 -04:00
Hykilpikonna bfe3c62db7 [O] Prevent accidental closing 2020-04-26 18:39:55 -04:00
Hykilpikonna aa7cc12876 [S] Make popup window not clickable 2020-04-26 18:38:28 -04:00
Hykilpikonna 9a2476b095 [+] Display stars 2020-04-26 18:36:57 -04:00
Hykilpikonna 297fa9a4c6 [+] Display rating criteria 2020-04-26 18:36:48 -04:00
Hykilpikonna 6fbe0b2d40 [+] Create rating data 2020-04-26 18:36:33 -04:00
Hykilpikonna cae401ce55 [+] Create rating criteria 2020-04-26 18:36:24 -04:00
Hykilpikonna 8874888846 [S] Optimize padding box 2020-04-26 16:19:03 -04:00
Hykilpikonna 1f5a27a425 [S] Width calculation 2020-04-26 16:18:36 -04:00
Hykilpikonna 81a686b2ec [S] Specify width for rating button 2020-04-26 16:14:00 -04:00
Hykilpikonna 19dd5b8d00 [S] Align content box 2020-04-26 16:13:48 -04:00
Hykilpikonna 351b6da4d1 [+] Separate content with rate button for @click 2020-04-26 16:13:38 -04:00
Hykilpikonna 078b36eea6 [+] Create rating dialog 2020-04-26 16:13:20 -04:00
Hykilpikonna f003f0d48c [O] Don't display rate button on course detail page 2020-04-26 15:55:58 -04:00
Hykilpikonna 616f0a7420 [S] Add left shadow (inset) 2020-04-26 15:52:22 -04:00
Hykilpikonna 208d816c64 [S] Add paddings 2020-04-26 15:52:06 -04:00
Hykilpikonna 43cb9e384f [S] Align block height and relative position to match course-head 2020-04-26 15:51:55 -04:00
Hykilpikonna f28bd1851a [S] Add left margins 2020-04-26 15:51:21 -04:00
Hykilpikonna 4807a6d503 [S] Round corners 2020-04-26 15:50:29 -04:00
Hykilpikonna 2e8e4b330f [S] Add background color to the button 2020-04-26 15:50:22 -04:00
Hykilpikonna ff433f2fec [S] Align rating button to the right 2020-04-26 15:50:02 -04:00
Hykilpikonna 0f92254402 [+] Create rating button 2020-04-26 15:49:16 -04:00
Hykilpikonna 230b69356c [+] Encapsulate displayRate() 2020-04-26 15:45:35 -04:00
Hykilpikonna 30f1600063 [+] Display classes in course-detail 2020-04-26 15:03:57 -04:00
Hykilpikonna f5beddd68e [+] Add rated field to course 2020-04-26 15:03:40 -04:00
Hykilpikonna c66a3b5b11 [+] Move level detection to server side 2020-04-26 14:46:48 -04:00
Hykilpikonna 0fd4d31d6b [+] Encapsulate getLevel() 2020-04-26 14:46:28 -04:00
Hykilpikonna 86f9008804 [+] Add some more level constants 2020-04-26 14:46:18 -04:00
Hykilpikonna 380fe19a20 [-] Remove detectLevel() 2020-04-26 14:46:05 -04:00
Hykilpikonna 9e6eb3c71e [+] Display course enrollments 2020-04-20 18:20:59 -04:00
Hykilpikonna 5ea2fcf592 [+] Implement level filtering 2020-04-20 18:16:08 -04:00
Hykilpikonna 7a5bf96916 [S] Apply margins to the checkboxes 2020-04-20 18:15:55 -04:00
Hykilpikonna cb45759d5e [+] Add level selector checkbox 2020-04-20 18:15:41 -04:00
Hykilpikonna 2ce6cb9d0b [O] Sort by levelID instead of name 2020-04-20 18:02:58 -04:00
Hykilpikonna b6ddfba700 [+] Add levelID field to CourseInfo 2020-04-20 18:02:43 -04:00
Hykilpikonna 22d9e278c5 [O] Sort courses by name 2020-04-20 18:01:41 -04:00
Hykilpikonna ee06f557dc [S] Make course info items look nicer 2020-04-20 18:01:19 -04:00
Hykilpikonna 11b6455c00 [+] Display course info items 2020-04-20 18:01:01 -04:00
Hykilpikonna 6cbbfd9b46 [F] Null cases 2020-04-20 18:00:15 -04:00
Hykilpikonna 1d31bfb5ae [+] Add levelFull field to CourseInfo 2020-04-20 18:00:09 -04:00
Hykilpikonna 1d6db35c5c [+] Encapsulate getLevelFullName() 2020-04-20 17:59:42 -04:00
Hykilpikonna 6412482c06 [+] Encapsulate getLevelID() 2020-04-20 17:59:24 -04:00
Hykilpikonna cc039655ae [+] Encapsulate isNumeric() 2020-04-20 17:59:09 -04:00
Hykilpikonna fef4612122 [F] Fix cannot switch back to course if closed 2020-04-20 17:58:50 -04:00
Hykilpikonna 4e7c458b7f [S] Make course item clickable and unselectable 2020-04-20 17:58:24 -04:00
Hykilpikonna dbb51e50bd [S] Use ellipsis if header is too long 2020-04-20 17:57:41 -04:00
Hykilpikonna ab5caf9701 [+] Show course name 2020-04-20 17:57:16 -04:00
Hykilpikonna 0aeeb2a933 [+] Implement openCourse on click 2020-04-20 17:08:12 -04:00
Hykilpikonna 4c8b7e6ad0 [O] Pass in unique course instead of course info 2020-04-20 17:07:54 -04:00
Hykilpikonna c3c9e2c175 [+] Import course-detail page into course-selection 2020-04-20 16:57:41 -04:00
Hykilpikonna d821be41df [+] Add courseInfo property to course-detail page 2020-04-20 16:57:05 -04:00
Hykilpikonna 103a7a2fa4 [+] Create course detail component 2020-04-20 16:53:32 -04:00
Hykilpikonna f17122d1ca [S] Justify text 2020-04-20 16:51:04 -04:00
Hykilpikonna 131027d1d9 [+] Add notation documentation in welcome screen 2020-04-20 16:50:56 -04:00
Hykilpikonna be0851cdd1 [+] Write better welcome message 2020-04-20 16:50:30 -04:00
Hykilpikonna 8a430dccc4 [S] Make header margin look more aligned with the search bar on the right 2020-04-20 15:31:39 -04:00
Hykilpikonna 79208f811a [+] Display welcome if no page is open 2020-04-20 15:30:59 -04:00
Hykilpikonna 14884d0111 [M] Move implementations too 2020-04-20 15:30:45 -04:00
Hykilpikonna ed727a7f26 [M] Move welcome message to welcome component 2020-04-20 15:25:01 -04:00
Hykilpikonna 10d25ff091 [O] Separate a universal css for course selection pages 2020-04-20 15:21:27 -04:00
Hykilpikonna 587f1d4a93 [+] Create welcome component for course-selection 2020-04-20 15:19:13 -04:00
Hykilpikonna 63612f4474 [S] Reduce radio margins 2020-04-20 15:17:29 -04:00
Hykilpikonna 5e67b1a08f [S] Make radio buttons look nicer 2020-04-20 15:17:18 -04:00
Hykilpikonna 8e4557af18 [+] Implement sort by popularity 2020-04-20 14:19:00 -04:00
Hykilpikonna cc4cbe0b28 [S] Make margins look better 2020-04-20 14:18:35 -04:00
Hykilpikonna f6f009083f [+] Add sort by settings 2020-04-20 14:18:23 -04:00
Hykilpikonna 2744eb97b6 [+] Implement showAllCourses in filtering 2020-04-20 13:23:34 -04:00
Hykilpikonna 3691f16d8c [+] Sync search settings as a param 2020-04-20 13:22:54 -04:00
Hykilpikonna c0d23770b5 [+] Create separate class for SearchSettings 2020-04-20 13:22:21 -04:00
Hykilpikonna 2fb69a5992 [+] Add getter to get setting as object 2020-04-20 13:12:23 -04:00
Hykilpikonna 52e664dd18 [+] Implement openSettings() 2020-04-20 13:12:10 -04:00
Hykilpikonna d923b6ea02 [S] Left align settings 2020-04-20 13:07:38 -04:00
Hykilpikonna f153cb3fff [S] Unify the margins of the divider 2020-04-20 13:07:25 -04:00
Hykilpikonna 2cff73862d [+] Add divider between settingsheader and content 2020-04-20 13:07:08 -04:00
Hykilpikonna 316ea5428e [+] Add showAllCourses option 2020-04-20 13:06:43 -04:00
Hykilpikonna 45e397b6c4 [+] Display teacher count in course data 2020-04-19 19:53:43 -04:00
Hykilpikonna 74148e3782 [+] Import SearchSettings 2020-04-19 19:53:27 -04:00
Hykilpikonna 91f50946b5 [S] Style the header to match the course list header 2020-04-19 19:52:27 -04:00
Hykilpikonna 83fc54fcda [+] Add settings header 2020-04-19 19:52:13 -04:00
Hykilpikonna d1cfc69b5c [+] Create SearchSettings vue 2020-04-19 19:52:01 -04:00
Hykilpikonna 012e2d5aea [F] Fix upper case searching issue 2020-04-19 19:39:47 -04:00
Hykilpikonna 0a5f7d3be2 [+] Encapsulate getter classes() 2020-04-19 19:34:19 -04:00
Hykilpikonna eff37aac03 [O] Make UniqueCourse a class 2020-04-19 19:34:03 -04:00
Hykilpikonna 4e23bfb507 [F] Fix null pointer caused by initialization 2020-04-19 19:29:57 -04:00
Hykilpikonna d1b167c002 [+] Map classinfo to courseinfo 2020-04-19 19:27:55 -04:00
Hykilpikonna 0b51713d95 [+] Parse classInfos 2020-04-19 19:27:38 -04:00
Hykilpikonna 0306585cf8 [+] Create courseInfo class 2020-04-19 19:18:30 -04:00
Hykilpikonna fce32881e3 [M] Separate ts file for course-selection 2020-04-19 18:48:57 -04:00
Hykilpikonna bb79ed6e92 [S] Add space between icon and text 2020-04-19 16:56:18 -04:00
Hykilpikonna 4f44423239 [M] Rename 'Descriptions' to 'Data' 2020-04-19 16:56:01 -04:00
Hykilpikonna d5f01c8ab0 [S] Reduce font size for description 2020-04-19 16:55:19 -04:00
Hykilpikonna 276f9651a1 [S] Switch to left align 2020-04-19 16:55:08 -04:00
Hykilpikonna 39b1fad44e [S] Make items' buttom margin larger 2020-04-19 16:53:19 -04:00
Hykilpikonna 237dd45cde [S] Align the icons by using left align 2020-04-19 16:50:18 -04:00
Hykilpikonna e4b6494046 [S] Align the widths 2020-04-19 16:50:06 -04:00
Hykilpikonna 2300cf6bf0 [+] Add search settings button 2020-04-19 16:49:51 -04:00
Hykilpikonna 6fb2d6a5f5 [+] Display how many class a course opens 2020-04-19 16:48:31 -04:00
Hykilpikonna 0a5cd155e2 [S] Further dim the color 2020-04-19 16:47:52 -04:00
Hykilpikonna 9b3d30959d [F] Fix enrollments not counting correctly 2020-04-19 16:28:37 -04:00
Hykilpikonna b8339a4e4c [O] Wrap enrollement info in a span 2020-04-19 16:25:15 -04:00
Hykilpikonna ebc3dfb75b [O] Right align discriptions 2020-04-19 16:24:11 -04:00
Hykilpikonna 1293967512 [S] Add icon to enrollments 2020-04-19 16:20:05 -04:00
Hykilpikonna f0abfa16ed [+] Display enrollments 2020-04-19 16:19:41 -04:00
Hykilpikonna f1885509d8 [S] Replace definite height with padding 2020-04-19 16:17:07 -04:00
Hykilpikonna cbfad69d22 [O] Fix jslint quotation marks 2020-04-19 16:14:44 -04:00
Hykilpikonna 533d788b6a [M] Move course-selection css to new file 2020-04-19 16:14:20 -04:00
Hykilpikonna 48d1931368 [+] Sum up total enrollments when counting course 2020-04-14 23:19:10 -04:00
Hykilpikonna 808e8a5708 [+] Add enrollments field to UniqueCourse 2020-04-14 23:18:50 -04:00
Hykilpikonna 396edfef41 [+] Count enrollments when parsing data 2020-04-14 23:18:32 -04:00
Hykilpikonna 8cad524217 [+] Add enrollment field to CourseInfo 2020-04-14 23:18:17 -04:00
Hykilpikonna e377f678c8 [-] Remove debug logging 2020-04-12 16:30:05 -04:00
Hykilpikonna bdaca5aeda [+] Sort courses by name 2020-04-12 16:29:54 -04:00
Hykilpikonna 7ca8554d7e [F] Not unescape url but unescape html 2020-04-12 16:24:48 -04:00
Hykilpikonna fda91fbfe3 Revert "[O] Unescape course name"
This reverts commit 2a4a54d60b.
2020-04-12 16:23:33 -04:00
Hykilpikonna 259d4c3474 [O] Replace more in course names 2020-04-12 16:21:50 -04:00
Hykilpikonna 2a4a54d60b [O] Unescape course name 2020-04-12 16:21:29 -04:00
Hykilpikonna 6d55ae8180 [O] Filter out courses that are unavailable for a grade level 2020-04-12 16:21:15 -04:00
Hykilpikonna deda2a194e [+] Map grade levels to course infos 2020-04-12 16:20:58 -04:00
Hykilpikonna 7b1078dad2 [+] Index courseIds 2020-04-12 16:20:20 -04:00
Hykilpikonna 70a46c0782 [O] Parse courseIDs 2020-04-12 16:19:52 -04:00
Hykilpikonna 3f48a97668 [+] Create gradeLevels field in CourseInfo 2020-04-12 15:54:24 -04:00
Hykilpikonna 5391ca7137 [-] Remove debug logging 2020-04-12 15:54:00 -04:00
Hykilpikonna 80d463aba8 [O] Specify data type for directory 2020-04-12 15:53:52 -04:00
Hykilpikonna 3e3ab4fdab [O] Combine course info and directory 2020-04-12 15:53:37 -04:00
Hykilpikonna 97ca5c5443 [O] Provide a more customized welcome message 2020-04-12 15:44:47 -04:00
Hykilpikonna 9cd3bbbd48 [+] Add gradeLevelName field to user 2020-04-12 15:40:20 -04:00
Hykilpikonna d092ef2972 [U] Use uniqueName in course filtering 2020-04-12 15:38:41 -04:00
Hykilpikonna b6182c3466 [+] Parse unique-name by replacing level 2020-04-12 15:38:19 -04:00
Hykilpikonna 902cb66cc4 [+] Add unique name to course-info 2020-04-12 15:38:02 -04:00
Hykilpikonna bf690a941c [+] Create method to get grade level's name 2020-04-12 15:36:58 -04:00
Hykilpikonna bc5a679bb6 [+] Calculate height for cards 2020-04-12 15:26:37 -04:00
Hykilpikonna 2a3109577c [S] Dynamically update height 2020-04-12 15:17:40 -04:00
Hykilpikonna 5631ea2ac3 [O] Filter more levels 2020-04-12 15:05:09 -04:00
Hykilpikonna b84dd14ced [+] Create filter for unique courses 2020-04-12 15:04:59 -04:00
Hykilpikonna 2899f6836b [+] Create UniqueCourse interface 2020-04-12 15:04:31 -04:00
Hykilpikonna 24c89ac381 [O] Trim course names 2020-04-12 15:04:10 -04:00
Hykilpikonna 87a3aed16d [O] Filter out courses not from this year 2020-04-12 14:50:21 -04:00
Hykilpikonna cd14b2a768 [+] Encapsulate getSchoolYear() 2020-04-12 14:49:02 -04:00
Hykilpikonna df79998990 [S] Cut long course names 2020-04-12 14:44:30 -04:00
Hykilpikonna 25d0965696 [S] Remove scrollbar 2020-04-12 14:44:14 -04:00
Hykilpikonna e886f981a0 [O] Remove dynamic width, use fixed height instead 2020-04-12 14:44:00 -04:00
Hykilpikonna 81f66b65de [S] Separate course items to a separate div 2020-04-12 14:01:14 -04:00
Hykilpikonna 61c934c360 [O] Update width on resize 2020-04-12 14:00:31 -04:00
Hykilpikonna ed1a667e09 [S] Fix header and search bar width 2020-04-12 13:55:55 -04:00
Hykilpikonna 3f27db8c77 [S] Remove card body padding 2020-04-12 13:55:31 -04:00
Hykilpikonna b975c0332e [F] Dynamically update width with js 2020-04-12 13:52:31 -04:00
Hykilpikonna a0ae839dd3 [S] Make header fixed from scrolling 2020-04-12 13:52:11 -04:00
Hykilpikonna 43c7918854 [+] Create container for header 2020-04-12 13:17:57 -04:00
Hykilpikonna e16a95837c Revert "[O] Separate course list card from actual course list"
This reverts commit 3d6254f2cc.
2020-04-12 13:16:06 -04:00
Hykilpikonna 3d6254f2cc [O] Separate course list card from actual course list 2020-04-12 13:08:03 -04:00
Hykilpikonna 367dccfe95 [S] Adjust search margin 2020-04-12 12:51:27 -04:00
Hykilpikonna 7c564f9497 [S] Round corners 2020-04-12 12:50:39 -04:00
Hykilpikonna 546ba16bb2 [S] Add left padding 2020-04-12 12:49:57 -04:00
Hykilpikonna a958fc6d9e [S] Add background dim to items 2020-04-12 12:49:45 -04:00
Hykilpikonna 3f7433f4da [S] Add bottom margins 2020-04-12 12:49:29 -04:00
Hykilpikonna 37ff3f5dbf [S] Align names to the left 2020-04-12 12:49:17 -04:00
Hykilpikonna d20479e88d [O] Unify margins for search 2020-04-12 12:45:06 -04:00
Hykilpikonna f9bf5b81c9 [O] Filter out clubs from course selection 2020-04-12 12:43:49 -04:00
Hykilpikonna 43e1e9f0ed [F] Fix undefined case for search text 2020-04-12 12:41:48 -04:00
Hykilpikonna e4357abcfd [+] Implement search 2020-04-12 12:41:27 -04:00
Hykilpikonna 3e26965ad0 [O] Compact search into one line 2020-04-12 12:40:10 -04:00
Hykilpikonna 6035136358 [+] Add search bar component 2020-04-12 12:39:46 -04:00
Hykilpikonna 1472bf4dbe [O] Specify data type when getting course info 2020-04-12 12:36:41 -04:00
Hykilpikonna 2bae41eed1 [+] Create courseinfo class 2020-04-12 12:35:10 -04:00
Hykilpikonna ad4bb875ae [S] Specify item height 2020-04-12 12:28:02 -04:00
Hykilpikonna 0a145ecdb5 [+] Create course info item list 2020-04-12 12:27:47 -04:00
Hykilpikonna 95a7907d72 [S] Specify header margins and font size 2020-04-12 12:27:22 -04:00
Hykilpikonna f019d31f99 [+] Create course selection card header 2020-04-12 12:27:03 -04:00
Hykilpikonna 3d7fa6cdae [S] Add inner scrollbar to card 2020-04-12 12:26:50 -04:00
Hykilpikonna 7e5302c913 [+] Load directory 2020-04-12 12:25:15 -04:00
Hykilpikonna 4cf64a0418 [+] Load course info 2020-04-12 12:25:02 -04:00
Hykilpikonna c8289f5278 [O] Fix typo in comment 2020-04-12 00:14:29 -04:00
Hykilpikonna ea93bc8d1a [S] Only enable bottom padding for overall and course 2020-04-11 20:01:03 -04:00
Hykilpikonna b1df999efb [O] CSS indent 2020-04-11 19:49:37 -04:00
Hykilpikonna 320c7ab8ff [S] Unify left and right margins 2020-04-11 19:49:24 -04:00
Hykilpikonna b706c439c8 [S] Unify top and bottom margins 2020-04-11 19:48:52 -04:00
Hykilpikonna f4e11fcbcc [+] Add cards below info card 2020-04-11 19:47:37 -04:00
Hykilpikonna 3dea971055 [O] Hide course selection tab for seniors 2020-04-11 19:21:05 -04:00
Hykilpikonna 252be18c2f [S] Use red for error notice 2020-04-11 19:20:01 -04:00
Hykilpikonna ba84861628 [+] Add a friendly note to seniors in the course selection page 2020-04-11 19:18:43 -04:00
Hykilpikonna d974fb24a0 [+] Add gradeLevel field to user 2020-04-11 19:16:38 -04:00
Hykilpikonna b05b145911 [+] Create method to getGradeLevel from graduation year 2020-04-11 19:16:23 -04:00
Hykilpikonna 06bffb905e [O] Make user's graduation year a number instead of a string 2020-04-11 19:15:58 -04:00
Hykilpikonna 8ea8a3474f [+] Pass in app as a property to course selection page 2020-04-11 19:02:27 -04:00
Hykilpikonna 7609513a07 [+] Import course selection element in app 2020-04-11 19:00:07 -04:00
Hykilpikonna 8b1ffb412c [+] Create course selection tab on navbar 2020-04-11 18:59:44 -04:00
Hykilpikonna a137466ed1 [+] Create course selection page 2020-04-11 18:59:29 -04:00
Hykilpikonna 852d016203 [O] Turn on deployment warnings by default 2020-04-11 18:59:05 -04:00
Hykilpikonna 49765498e7 [U] Only update safe dependencies 2020-04-03 17:25:14 -04:00
Hykilpikonna 02c2ddd7d8 Revert "[U] Further update packages with ncu"
This reverts commit 522f2f803b.
2020-04-03 17:13:18 -04:00
Hykilpikonna e643b65d98 [O] Remove unnecessary courses prop 2020-04-03 17:05:41 -04:00
Hykilpikonna e3b011003d [F] Remove problematic App.instance usage and pass in app 2020-04-03 17:05:14 -04:00
Hykilpikonna b6b1a9362c [F] Fix newer vue import issues 2020-04-03 17:03:50 -04:00
Hykilpikonna 522f2f803b [U] Further update packages with ncu 2020-04-03 15:16:56 -04:00
Hykilpikonna 4d7109fa6b [U] Update packages 2020-04-03 15:08:26 -04:00
Hykilpikonna c31043a760 [U] Open term 4 2020-03-23 18:22:02 -04:00
Hykilpikonna fed0634a51 [O] Only show graded types on graphs 2020-03-22 18:43:53 -04:00
Hykilpikonna d4f643b3f4 [O] Only display percent average if it's not NaN 2020-03-22 18:43:35 -04:00
Hykilpikonna 18b17cdd67 [+] Separate graded assignments from regular when calculating weighting 2020-03-22 18:43:15 -04:00
Hykilpikonna 74dab07d3f [U] Update Dependencies 2020-03-15 23:39:22 +00:00
Hykilpikonna 91e52d2fc0 [+] Add graded field to AssignmentType 2020-03-14 21:52:51 -04:00
Hykilpikonna 4ba6f4a613 [S] Make the color of late seem less important 2020-03-07 23:57:39 -05:00
Hykilpikonna df68cc5927 [O] Add default case 2020-03-07 23:57:19 -05:00
Hykilpikonna 4201d9aa4e [F] Support "not turned in" as a completion status 2020-03-07 23:56:49 -05:00
Hykilpikonna 7c8bde24b3 [U] Release v0.5.4.1391 2020-03-07 16:55:27 -05:00
Hykilpikonna 158344f24f [F] Only average graded assignments in type average 2020-03-07 16:15:59 -05:00
Hykilpikonna 78159fda3f [S] Reverse alternating shading order 2020-03-07 16:13:03 -05:00
Hykilpikonna 8988e02793 [S] Fix assignment alignment issues on smaller screens 2020-03-07 16:12:24 -05:00
Hykilpikonna 6e7642e126 [S] Make min-width look better 2020-03-07 16:12:02 -05:00
Hykilpikonna 47bea6197a [S] Make bottom margin larger 2020-03-07 16:11:35 -05:00
Hykilpikonna ea17b3e9ea [S] Use alternating shading for gades 2020-03-07 16:11:23 -05:00
Hykilpikonna da719a0a6b [O] Treat close as clearUnread(false) 2020-03-07 16:10:57 -05:00
Hykilpikonna f654527311 [O] Only display grade if graded 2020-03-07 16:10:14 -05:00
Hykilpikonna 880dea1bef [F] Should display un-graded assignments too 2020-03-07 16:09:40 -05:00
Hykilpikonna 021915d9d4 [F] Include "incomplete" in GPA calculation 2020-03-07 16:08:53 -05:00
Hykilpikonna 1fb0102328 [F] NREQ shouldn't be graded 2020-03-07 16:08:19 -05:00
Hykilpikonna 1fc2a2adda [+] Support "incomplete" as a completion status 2020-03-07 16:08:15 -05:00
Hykilpikonna ddc1024611 [O] Support "pending" as a completion status 2020-03-07 16:06:56 -05:00
Hykilpikonna 1de041351c [O] Order completion status by id 2020-03-07 16:06:23 -05:00
Hykilpikonna f64cf0ad02 [F] Fix unread clearing issue 2020-03-05 18:25:42 -05:00
55 changed files with 3745 additions and 1643 deletions
+4 -4
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env bash
#echo "Switch to production database settings"
#exit
echo "Switch to production database settings"
exit
# abort on errors
set -e
@@ -13,13 +13,13 @@ npm run build
cd dist
# if you are deploying to a custom domain
echo 'vera.hydev.org' > CNAME
echo 'demo.vera.hydev.org' > CNAME
git init
git add -A
git commit -m 'deploy'
# if you are deploying to https://<USERNAME>.github.io/<REPO>
git push -f git@github.com:HyDevelop/VeracrossAnalyzer.Client.git master:gh-pages
git push -f git@github.com:Hykilpikonna/VeracrossAnalyzerDemo.git master:gh-pages
cd -
+1761 -1376
View File
File diff suppressed because it is too large Load Diff
+14 -14
View File
@@ -8,29 +8,29 @@
"lint": "vue-cli-service lint"
},
"dependencies": {
"@types/chroma-js": "^1.4.3",
"@types/md5": "^2.1.33",
"@types/chroma-js": "^2.0.0",
"@types/md5": "^2.2.0",
"chroma-js": "^2.1.0",
"core-js": "^2.6.10",
"echarts": "^4.5.0",
"element-ui": "^2.13.0",
"echarts": "^4.8.0",
"element-ui": "^2.13.2",
"md5": "^2.2.1",
"moment": "^2.24.0",
"moment": "^2.27.0",
"p-wait-for": "^3.1.0",
"v-charts": "^1.19.0",
"vue": "^2.6.10",
"vue-class-component": "^7.0.2",
"vue-cookies": "^1.5.13",
"vue-property-decorator": "^8.3.0"
"vue": "^2.6.11",
"vue-class-component": "^7.2.5",
"vue-cookies": "^1.7.3",
"vue-property-decorator": "^8.5.1"
},
"devDependencies": {
"@vue/cli-plugin-babel": "^3.12.1",
"@vue/cli-plugin-typescript": "^3.12.1",
"@vue/cli-service": "^4.1.1",
"node-sass": "^4.13.0",
"sass-loader": "^7.3.1",
"typescript": "^3.7.3",
"vue-template-compiler": "^2.6.10"
"@vue/cli-service": "^4.4.6",
"node-sass": "^4.14.1",
"sass-loader": "^8.0.2",
"typescript": "^3.9.7",
"vue-template-compiler": "^2.6.11"
},
"postcss": {
"plugins": {
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
{"success":true,"data":{"assignments":[{"score_id":5593330,"id":322150,"assignment_id":322150,"assignment_type_id":3,"assignment_type":"Quiz","assignment_type_sort_key":3,"assignment_description":"2.3 Open Notes","grading_period":"Quarter 1","assignment_date_long":"Wed, Sep 11","due_date_long":"Wed, Sep 11","due_date":"Sep 11","due_day":"Wed","_date":"09/11/2019","include_in_calculated_grade":1,"num_attachments":0,"num_criteria":0,"num_feedback":0,"maximum_score":16,"points_possible":16,"raw_score":"14","percent_grade":"8750%","completion_status_id":3,"completion_status":"Complete","is_unread":1.0,"is_notification":0,"is_problem":0,"display_grade":1,"display_score":1,"display_maximum_score":1,"display_percent_grade":1,"display_points_possible":1,"allow_student_feedback":0},{"score_id":5584935,"id":321649,"assignment_id":321649,"assignment_type_id":3,"assignment_type":"Quiz","assignment_type_sort_key":3,"assignment_description":"2.2 Open Notes","grading_period":"Quarter 1","assignment_date_long":"Mon, Sep 09","due_date_long":"Mon, Sep 09","due_date":"Sep 09","due_day":"Mon","_date":"09/09/2019","include_in_calculated_grade":1,"num_attachments":0,"num_criteria":0,"num_feedback":0,"maximum_score":10,"points_possible":10,"raw_score":"6","percent_grade":"6000%","completion_status_id":3,"completion_status":"Complete","is_notification":0,"is_problem":0,"display_grade":1,"display_score":1,"display_maximum_score":1,"display_percent_grade":1,"display_points_possible":1,"allow_student_feedback":0},{"score_id":5602940,"id":322723,"assignment_id":322723,"assignment_type_id":2,"assignment_type":"Homework","assignment_type_sort_key":2,"assignment_description":"Autobiography","grading_period":"Quarter 1","assignment_date_long":"Wed, Sep 04","due_date_long":"Thu, Sep 05","due_date":"Sep 05","due_day":"Thu","_date":"09/05/2019","include_in_calculated_grade":1,"num_attachments":0,"num_criteria":0,"num_feedback":0,"maximum_score":10,"points_possible":10,"raw_score":"","percent_grade":"0%","completion_status_id":0,"completion_status":"Pending","is_notification":0,"is_problem":0,"display_grade":1,"display_score":1,"display_maximum_score":1,"display_percent_grade":1,"display_points_possible":1,"allow_student_feedback":0}],"attachments":[],"criteria":[],"criteria_grade_scale_levels":[]}}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
{"success":true,"data":{"assignments":[],"attachments":[],"criteria":[],"criteria_grade_scale_levels":[]}}
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
{"success":true,"data":{"assignments":[],"attachments":[],"criteria":[],"criteria_grade_scale_levels":[]}}
@@ -0,0 +1 @@
{"success":true,"data":{"assignments":[],"attachments":[],"criteria":[],"criteria_grade_scale_levels":[]}}
@@ -0,0 +1 @@
{"success":true,"data":{"assignments":[],"attachments":[],"criteria":[],"criteria_grade_scale_levels":[]}}
@@ -0,0 +1 @@
{"success":true,"data":{"assignments":[],"attachments":[],"criteria":[],"criteria_grade_scale_levels":[]}}
+250
View File
@@ -0,0 +1,250 @@
{
"success": true,
"data": {
"user": {
"id": 1,
"schoolPersonPk": 109467,
"username": "ygui21",
"lastLogin": "Jun 5, 2020 1:28:51 PM",
"firstLogin": "Dec 2, 2019 7:31:15 PM",
"firstName": "Yijie",
"lastName": "Gui",
"graduationYear": 2021,
"emails": "ygui21@stjohnsprep.org",
"classes": "32451|32453|32458|32856|32872|32874|32878|32880|32882|32890|33070|33093|33121|33173|33337|33464|34174|34197|34199|34209"
},
"token": "Removed",
"courses": [
{
"level": "H",
"id_ci": 196,
"rating": {
"id_ci": 196,
"id_user": 1,
"userFullName": "Yijie]\u003d[Gui",
"ratings": [
5,
5,
3,
5,
4
],
"comment": "Mr. Crowell\u0027s selected books for this course are very interesting, and the essay topics are generally unique too, allowing us to express our creativity. His lectures are also very in-depth, strengthening our understanding of the ideas that the authors wanted to express. However, the style of the classroom might be boring for some people. For grading fairness, I think it is very fair, but his standards are a little bit too high because achieving a perfect score for an essay is very much impossible."
},
"name": "English 3 H",
"teacherName": "Mr. Crowell",
"id": 33337,
"assignmentsId": 10934147,
"letterGrade": "A+",
"numericGrade": 97.14,
"status": "active"
},
{
"level": "AP",
"id_ci": 19,
"rating": {
"id_ci": 19,
"id_user": -1,
"userFullName": "Anonymous]\u003d[Student",
"ratings": [
5,
5,
5,
5,
5
],
"comment": "Calculus made so much easier"
},
"name": "AP Calculus AB (Juniors)",
"teacherName": "Ms. Dobrowolski",
"id": 32453,
"assignmentsId": 10934142,
"letterGrade": "A+",
"numericGrade": 100.0,
"status": "active"
},
{
"level": "AP",
"id_ci": 251,
"rating": {
"id_ci": 251,
"id_user": 1,
"userFullName": "Yijie]\u003d[Gui",
"ratings": [
5,
5,
5,
5,
5
],
"comment": "Cations go meow"
},
"name": "AP Chemistry",
"teacherName": "Ms. Stone",
"id": 33464,
"assignmentsId": 10934148,
"letterGrade": "A+",
"numericGrade": 100.0,
"status": "active"
},
{
"level": "AP",
"id_ci": 22,
"rating": {
"id_ci": 22,
"id_user": 1,
"userFullName": "Yijie]\u003d[Gui",
"ratings": [
5,
5,
5,
3,
4
],
"comment": "Mr. Dankert can\u0027t really systematically teach, often he forgets to tell us something very important and then forgets that he forgot. The labs explained a lot of them very well."
},
"name": "AP Physics 1",
"teacherName": "Mr. Dankert",
"id": 32458,
"assignmentsId": 10934143,
"letterGrade": "A",
"numericGrade": 95.67,
"status": "active"
},
{
"level": "AP",
"id_ci": 18,
"rating": {
"id_ci": 18,
"id_user": 1,
"userFullName": "Yijie]\u003d[Gui",
"ratings": [
5,
5,
5,
5,
5
],
"comment": "Psychology is the best, and most valuable class I\u0027ve ever taken! Everything is just so relatable to my life!"
},
"name": "AP Psychology",
"teacherName": "Mr. Emerson",
"id": 32451,
"assignmentsId": 10934141,
"letterGrade": "A+",
"numericGrade": 100.0,
"status": "active"
},
{
"level": "A",
"id_ci": 98,
"rating": {
"id_ci": 98,
"id_user": 1,
"userFullName": "Yijie]\u003d[Gui",
"ratings": [
5,
5,
5,
5,
5
],
"comment": "Mr. Pynchon is very nice and encouraging."
},
"name": "US History A",
"teacherName": "Mr. Pynchon",
"id": 33093,
"assignmentsId": 10941280,
"letterGrade": "A+",
"numericGrade": 99.39,
"status": "active"
},
{
"level": "H",
"id_ci": 100,
"name": "US History H",
"teacherName": "Ms. Heath",
"id": 33096,
"assignmentsId": 10934144,
"status": "past"
},
{
"level": "A",
"id_ci": 134,
"name": "Relational Dynamics A",
"teacherName": "Mr. Reinbold",
"id": 33173,
"assignmentsId": 10934146,
"status": "past"
},
{
"level": "A",
"id_ci": 77,
"rating": {
"id_ci": 77,
"id_user": -1,
"userFullName": "Anonymous]\u003d[Student",
"ratings": [
5,
4,
5,
5,
3
],
"comment": "Honestly, everything is great except that the grading is way too harsh for an Accelerated course."
},
"name": "Social Justice A",
"teacherName": "Mr. Reinbold",
"id": 33121,
"assignmentsId": 10934145,
"letterGrade": "A+",
"numericGrade": 97.0,
"status": "active"
},
{
"level": "Sport",
"name": "Yoga AS",
"teacherName": "Ms. Fanikos",
"id": 33070,
"assignmentsId": 10935189,
"status": "past"
},
{
"level": "Club",
"id_ci": 316,
"name": "HS Magic Trick Club 2019",
"teacherName": "Mr. Reinbold",
"id": 34174,
"assignmentsId": 10949139,
"status": "active"
},
{
"level": "Club",
"id_ci": 331,
"name": "HS Science \u0026amp; Technology 2019",
"teacherName": "Ms. Erwin",
"id": 34197,
"assignmentsId": 10952100,
"status": "active"
},
{
"level": "Club",
"id_ci": 333,
"name": "HS Computer Club 2019",
"teacherName": "Mr. Gilmore",
"id": 34199,
"assignmentsId": 10951448,
"status": "active"
},
{
"level": "Club",
"id_ci": 336,
"name": "HS Chinese Ambassadors Club 2019",
"teacherName": "Mrs. Mills",
"id": 34209,
"assignmentsId": 10953979,
"status": "active"
}
]
}
}
-2
View File
@@ -1,2 +0,0 @@
cd ../
npm run serve
+45
View File
@@ -0,0 +1,45 @@
import LoginUser from '@/logic/login-user';
import App from '@/components/app/app';
import pWaitFor from 'p-wait-for';
export default class AppDemo
{
static loadDemo(app: App)
{
app.logLoading('1. Logging in...')
App.http.get('./demo-data/token.json').then(response =>
{
app.user = new LoginUser(response.data)
app.courses = app.user.courses
app.logLoading('1. Loading assignments...')
app.courses.forEach(course =>
{
App.http.get(`./demo-data/assignments-${course.assignmentsId}.json`).then(response =>
{
course.loadAssignments(response.data);
})
})
pWaitFor(() => app.courses.every(c => c.rawAssignments)).then(() =>
{
app.gradedCourses = app.courses.filter(c => c.isGraded)
app.gradedCourses.forEach(c => [0, 1, 2, 3].forEach(i => c.termGrading[i] = {method: 'TOTAL_MEAN', weightingMap: {}}))
app.logLoading('');
app.assignmentsReady = true
app.showRating = true
app.$alert(
'This demo analyzes an offline snapshot of my data from Jun 6, 2020, ' +
'which displays my academic results from Junior year.<br/>' +
'<br/>' +
'Feel free to click around! 😇<br/>' +
'<br/>' +
'-- The Veracross Analyzer Team (YGui)<br/>' +
'-- Made with 🧡 in SJP',
'🥳 Welcome to VeracrossAnalyzer Demo!',
{dangerouslyUseHTMLString: true, confirmButtonText: 'OK'});
})
})
}
}
+20 -7
View File
@@ -1,11 +1,16 @@
div
{
font-family: -apple-system, Nunito Sans, Avenir, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue,
Hiragino Sans GB, Microsoft YaHei, WenQuanYi Micro Hei, Helvetica, Arial, sans-serif;
}
#app
{
font-family: var(--font);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
padding-bottom: 100px;
}
#app-content
@@ -24,11 +29,6 @@
--assignment-type-2: #3f991e;
--assignment-type-3: #ff9900;
--assignment-type-4: #b02b02;
//--font: 'Avenir', Helvetica, Arial, sans-serif;
--font: -apple-system, Nunito Sans, Avenir, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue,
Hiragino Sans GB, Microsoft YaHei, WenQuanYi Micro Hei, Helvetica, Arial, sans-serif;
}
.dark
@@ -145,3 +145,16 @@ div.el-card.course-card > div.el-card__body
{
word-break: unset !important;
}
.comic
{
font-family: "Comic Sans MS", Nunito Sans, Helvetica Neue, Microsoft YaHei, "微软雅黑", Arial, sans-serif;
}
#demo-not-available
{
padding-top: 40vh;
font-size: 2em;
color: #bbbbbb;
margin: 0 50px;
}
+57 -39
View File
@@ -1,6 +1,6 @@
import {Component, Vue} from 'vue-property-decorator';
import Login from '@/components/login/login';
import Navigation from '@/components/navigation/navigation';
import Login from '@/components/login/login.vue';
import Navigation from '@/components/navigation/navigation.vue';
import Overall from '@/pages/overall/overall.vue';
import Constants from '@/constants';
import pWaitFor from 'p-wait-for';
@@ -11,15 +11,14 @@ import Course from '@/logic/course';
import LoginUser from '@/logic/login-user';
import NavController from '@/logic/nav-controller';
import Info from '@/statics/Info.vue';
import CourseSelection from '@/pages/course-selection/course-selection.vue';
import AppDemo from '@/components/app/app-demo';
@Component({
components: {Login, Navigation, Overall, Loading, CoursePage, Info},
components: {Login, Navigation, Overall, Loading, CoursePage, Info, CourseSelection},
})
export default class App extends Vue
{
// Is the login panel shown
showLogin: boolean = true;
// List of course that the student takes
courses: Course[] = [];
gradedCourses: Course[] = [];
@@ -49,7 +48,16 @@ export default class App extends Vue
staticPage: string = '';
// Dark mode
darkMode: boolean = false;
darkMode: boolean = this.$cookies.isKey('dark');
// Show rating
showRating: boolean = this.$cookies.get('show-rating') == 'set=yes';
// Demo mode
demoMode: boolean = window.location.hostname == 'demo.vera.hydev.org' || this.$cookies.isKey('demo-mode')
// Is the login panel shown
showLogin: boolean = !this.demoMode
/**
* This is called when the instance is created.
@@ -68,8 +76,17 @@ export default class App extends Vue
this.staticPage = 'info';
}
// Dark mode
this.darkMode = this.$cookies.isKey('dark');
// Default config
if (!this.$cookies.isKey('show-rating'))
{
this.showRating = Constants.CURRENT_TERM == 3;
}
// Demo
if (this.demoMode)
{
AppDemo.loadDemo(this);
}
}
/**
@@ -87,37 +104,13 @@ export default class App extends Vue
// Store user
this.user = user;
this.courses = user.courses
// Assign user to http client
App.http.user = user;
// Load data
this.loadCoursesAfterLogin();
}
/**
* Load courses data after logging in.
*/
loadCoursesAfterLogin()
{
// Show loading message
this.logLoading('2. Loading courses...');
// Post request
App.http.post('/courses', {}).then(response =>
{
// Check success
if (response.success)
{
// Save courses
this.courses = response.data.map((courseJson: any) => new Course(courseJson));
// Load assignments
this.loadAssignments();
}
else throw new Error(response.data);
})
.catch(e => this.showError(`Error: Course data failed to load.\n(${e})`));
// Load assignments
this.loadAssignments();
}
/**
@@ -126,7 +119,7 @@ export default class App extends Vue
loadAssignments()
{
// Show loading message
this.logLoading('3. Loading assignments...');
this.logLoading('1. Loading assignments...');
// Get assignments for all the courses
this.courses.forEach(course =>
@@ -161,7 +154,7 @@ export default class App extends Vue
checkGradingAlgorithms()
{
// Show loading message
this.logLoading('4. Checking grading algorithms...');
this.logLoading('2. Checking grading algorithms...');
// Loop through all the courses
for (const course of this.gradedCourses)
@@ -190,7 +183,7 @@ export default class App extends Vue
// This is because only percent_type can update over time
if (course.termGrading[i].method == 'TOTAL_MEAN')
{
this.$cookies.set(cookieIndex, 'TOTAL_MEAN', 'd');
this.$cookies.set(cookieIndex, 'TOTAL_MEAN', '3d');
}
}
else throw new Error(resp.data);
@@ -206,9 +199,34 @@ export default class App extends Vue
// Remove loading
this.logLoading('');
// Check if rating notification should be displayed
if (this.courses.filter(c => c.rated).length == 0 && this.showRating &&
!this.$cookies.isKey('rating-notified'))
{
// Show notification
this.$cookies.set('rating-notified', true);
this.showUpdates()
}
})
}
showUpdates()
{
this.$alert(
'<b>TL;DR:</b><br/>' +
'📅 Added a Course Selection tab to help you schedule for next year!<br/>' +
'🤩 You can now give star ratings to your courses!<br/>' +
'😮 You can also see others\' ratings in the course selection tab!<br/>' +
'<br/>' +
'That\'s it, try things out and have fun! 😇<br/>' +
'<br/>' +
'-- The Veracross Analyzer Team<br/>' +
'-- Made with 🧡 in SJP',
'🥳 Huge updates!',
{dangerouslyUseHTMLString: true, confirmButtonText: 'OK', customClass: 'comic'});
}
/**
* Log a message to loading screen
*
+15 -10
View File
@@ -1,21 +1,19 @@
<template>
<div id="app" class="theme-default">
<div id="app-inner" v-if="staticPage === ''" :class="{dark: darkMode}">
<div id="app-inner" v-if="staticPage === ''" :class="{dark: darkMode, padding: nav.id !== 'course-selection'}">
<login v-if="showLogin" v-on:login:user="onLogin"/>
<navigation v-if="user != null"
:courses="gradedCourses"
:user="user"
:nav="nav"
:app="this" :user="user" :nav="nav"
@sign-out="signOut" @select-time="selectTime">
</navigation>
<div id="app-content" v-if="assignmentsReady && loading === ''">
<overall v-if="nav.id === 'overall'"
:courses="gradedCourses">
</overall>
<course-page v-if="nav.id === 'course'"
:course="gradedCourses.find(c => +c.id === +nav.info.id)">
</course-page>
<overall v-if="nav.id === 'overall'" :courses="gradedCourses"></overall>
<course-page v-if="nav.id === 'course'" :course="gradedCourses.find(c => +c.id === +nav.info.id)"></course-page>
<course-selection v-if="nav.id === 'course-selection' && !demoMode" :app="this"></course-selection>
<div id="demo-not-available" class="unselectable" v-if="nav.id === 'course-selection' && demoMode">
Course selection page is not available in demo mode.
</div>
</div>
<loading v-if="loading !== ''" :text="loading" :error="loadingError"/>
@@ -27,3 +25,10 @@
<script src="./app.ts" lang="ts"></script>
<style src="./app.scss" lang="scss"/>
<style lang="scss" scoped>
.padding
{
padding-bottom: 100px;
}
</style>
+29
View File
@@ -0,0 +1,29 @@
<template>
<div class="el-loading-spinner" :class="{'not-centered': !centered}">
<svg viewBox="25 25 50 50" class="circular" :style="{width: size + 'px', height: size + 'px'}">
<circle cx="50" cy="50" r="20" fill="none" class="path"/>
</svg>
</div>
</template>
<script lang="ts">
import {Component, Prop, Vue} from 'vue-property-decorator'
@Component
export default class LoadingSpinner extends Vue
{
@Prop({default: '42'}) size: string
@Prop({default: true}) centered: string
}
</script>
<style lang="scss" scoped>
.not-centered
{
top: unset;
margin-top: unset;
width: unset;
text-align: unset;
position: unset;
}
</style>
+23 -32
View File
@@ -26,6 +26,8 @@ export default class Login extends Vue
*/
created()
{
// TODO: Check maintenance
// Check login cookies
if (this.$cookies.isKey('va.token'))
{
@@ -33,9 +35,6 @@ export default class Login extends Vue
if (this.needToUpdateCookies()) this.clearCookies();
else
{
// Show loading
this.disableInput = this.loading = true;
// Login with token
this.login('/login/token', {token: this.$cookies.get('va.token')});
}
@@ -52,53 +51,52 @@ export default class Login extends Vue
// Version doesn't exist
if (!this.$cookies.isKey('va.version')) return true;
// Bug
if (this.$cookies.get('va.token') == 'undefined') return true
// If the current version is less than the min supported version
return VersionUtils.compare(this.$cookies.get('va.version'), Constants.MIN_SUPPORTED_VERSION) == -1;
}
/**
* When the user clicks, send the username and password to the server.
* When the user clicks, post the login request and process the response
* This is also called when the user hits enter on the input boxes.
*/
onLoginClick()
loginClick()
{
// Make login button loading
this.loading = true;
// Simple checks
if (this.username == '')
{
this.error = 'Username cannot be blank 🤔';
}
// Format it
this.username = this.username.toLowerCase().replace(/ /g, '').replace(/@.*/g, '');
// Login
this.login('/login', {username: this.username, password: this.password});
// Actually login
this.login('/login', {username: this.username, password: this.password})
}
/**
* Actually post the login request and process the response
*
* @param url Posting url
* @param data Data to be posted
* Actually post the request and process the response
*/
login(url: string, data: any)
{
// Show loading
this.disableInput = this.loading = true;
// Fetch request
App.http.post(url, data)
.then(response =>
App.http.post(url, data).then(response =>
{
// Check success
if (response.success)
{
// Maintenance
if (response.data.maintenance)
{
this.maintenance = response.data.maintenance;
return;
}
// Save token to cookies
this.$cookies.set('va.token', response.data.user.token, '27d');
this.$cookies.set('va.token', response.data.token, '27d');
this.$cookies.set('va.version', Constants.VERSION, '27d');
// Call a custom event with the token
this.$emit('login:user', new LoginUser(response.data.user));
this.$emit('login:user', new LoginUser(response.data));
}
else
{
@@ -109,6 +107,7 @@ export default class Login extends Vue
}
// Show error message & allow user to retry
// TODO: Automatic report error
this.error = response.data;
this.disableInput = this.loading = false;
}
@@ -121,14 +120,6 @@ export default class Login extends Vue
});
}
/**
* This is called when the user hits enter on the input boxes.
*/
onEnter()
{
this.onLoginClick();
}
/**
* Clear cookies
*/
+3 -3
View File
@@ -10,7 +10,7 @@
placeholder="SJP Username (Eg. flast21)"
:class="{'input-error': error !== ''}"
v-if="!disableInput"
@keyup.enter.native="onEnter">
@keyup.enter.native="loginClick">
</el-input>
<el-input v-model="password"
@@ -18,12 +18,12 @@
show-password=""
:class="{'input-error': error !== ''}"
v-if="!disableInput"
@keyup.enter.native="onEnter">
@keyup.enter.native="loginClick">
</el-input>
<div class="el-form-item__error custom">{{error}}</div>
<el-button plain type="primary" @click="onLoginClick" :loading="loading">Login</el-button>
<el-button plain type="primary" @click="loginClick" :loading="loading">Login</el-button>
</form>
</div>
</div>
@@ -147,3 +147,8 @@ footer
// Cursor
cursor: pointer;
}
.el-submenu__title
{
padding-right: 5px !important;
}
+20 -11
View File
@@ -3,7 +3,7 @@ import Course from '@/logic/course';
import Constants from '@/constants';
import LoginUser from '@/logic/login-user';
import NavController from '@/logic/nav-controller';
import App from '@/components/app/app';
import App from '@/components/app/app.ts';
/**
* This component is the top navigation bar
@@ -11,8 +11,8 @@ import App from '@/components/app/app';
@Component
export default class Navigation extends Vue
{
@Prop({required: true}) app: App;
@Prop({required: true}) nav: NavController;
@Prop({required: true}) courses: Course[];
@Prop({required: true}) user: LoginUser;
private gradingPeriod: string = 'All Year';
@@ -92,10 +92,10 @@ export default class Navigation extends Vue
findCourse(courseId: string, indexOffset: number)
{
// Find current course index
let courseIndex = this.courses.findIndex(c => c.id == +courseId);
let courseIndex = this.app.gradedCourses.findIndex(c => c.id == +courseId);
// Find next course
return this.courses[courseIndex + indexOffset];
return this.app.gradedCourses[courseIndex + indexOffset];
}
/**
@@ -137,20 +137,29 @@ export default class Navigation extends Vue
}
case 'switch-dark':
{
App.instance.darkMode = !App.instance.darkMode;
this.app.darkMode = !this.app.darkMode;
if (this.isDark()) this.$cookies.set('dark', true);
if (this.app.darkMode) this.$cookies.set('dark', true);
else this.$cookies.remove('dark');
break
}
case 'switch-rating':
{
this.app.showRating = !this.app.showRating;
if (this.app.showRating) this.$cookies.set('show-rating', 'set=yes', '30d');
else this.$cookies.set('show-rating', 'set=no', '30d');
break
}
case 'updates':
{
this.app.showUpdates()
break
}
}
}
isDark()
{
return App.instance.darkMode;
}
get version() {return Constants.VERSION}
}
+14 -4
View File
@@ -13,11 +13,13 @@
<el-submenu index="">
<template slot="title">Courses</template>
<el-menu-item v-for="course in courses"
<el-menu-item v-for="course in app.gradedCourses"
:index="JSON.stringify(course.urlIndex)"
:key="course.id">{{course.name}}</el-menu-item>
</el-submenu>
<el-menu-item index="course-selection">Course Selection</el-menu-item>
<!-- Grading period selection -->
<el-dropdown id="nav-grading-period" @command="selectGradingPeriod">
<el-button type="primary" size="medium">
@@ -27,7 +29,7 @@
<el-dropdown-item command="Term 1">Term 1</el-dropdown-item>
<el-dropdown-item command="Term 2">Term 2</el-dropdown-item>
<el-dropdown-item command="Term 3">Term 3</el-dropdown-item>
<el-dropdown-item command="Term 4" disabled>Term 4</el-dropdown-item>
<el-dropdown-item command="Term 4">Term 4</el-dropdown-item>
<!-- TODO: Auto enable / disable quarters -->
<el-dropdown-item command="All Year" divided>All Year</el-dropdown-item>
</el-dropdown-menu>
@@ -38,9 +40,17 @@
<el-avatar :src="user.avatarUrl"/>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item style="text-align: center">{{user.nickname}}</el-dropdown-item>
<el-dropdown-item style="text-align: center">{{user.firstName}}</el-dropdown-item>
<el-dropdown-item icon="el-icon-sunrise" command="switch-dark" divided>{{!isDark() ? 'Dark Mode (Unfinished)' : 'Light Mode'}}</el-dropdown-item>
<el-dropdown-item icon="el-icon-sunrise" command="switch-dark" divided>
{{!app.darkMode ? 'Dark Mode (Unfinished)' : 'Light Mode'}}
</el-dropdown-item>
<el-dropdown-item icon="el-icon-edit-outline" command="switch-rating">
{{app.showRating ? 'Hide rating buttons' : 'Show rating button'}}
</el-dropdown-item>
<el-dropdown-item icon="el-icon-cold-drink" command="updates">
Check out the updates
</el-dropdown-item>
<el-dropdown-item icon="el-icon-switch-button" command="sign-out" divided>Sign Out</el-dropdown-item>
</el-dropdown-menu>
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

+36
View File
@@ -0,0 +1,36 @@
<template>
<div class="background">
<span :style="{width: (score / 5 * 100).toFixed(2) + '%'}" class="rating"/>
</div>
</template>
<script lang="ts">
import {Component, Prop, Vue} from 'vue-property-decorator'
@Component
export default class StarRating extends Vue
{
@Prop({required: true}) score: number;
}
</script>
<style lang="scss" scoped>
.background
{
background: url("./star-rating-sprite.png") repeat-x;
font-size: 0;
height: 21px;
line-height: 0;
overflow: hidden;
width: 110px;
margin: 0 auto;
span.rating
{
background: url("./star-rating-sprite.png") repeat-x 0 100%;
float: left;
height: 21px;
display: block;
}
}
</style>
+1 -1
View File
@@ -10,7 +10,7 @@ export default class Constants
// static API_URL: string = 'http://localhost:24021/api';
/** Current version */
static VERSION: string = '0.5.3.1373';
static VERSION: string = '0.5.6.1761';
/** The minimum version that still supports the same cookies */
static MIN_SUPPORTED_VERSION: string = '0.4.6.1087';
+176
View File
@@ -0,0 +1,176 @@
import {CourseUtils} from '@/logic/utils/course-utils';
export default class CourseInfo
{
id_ci: number
year: number
name: string
teacher: string
level: string
courseIds: number[]
uniqueName: string
courseCount: number
gradeLevels: number[]
enrollments: number
classes: ClassInfo[]
levelID: number;
levelFull: string
rating: AnalyzedRating = null as any as AnalyzedRating
/**
* Construct with a json object
*
* @param json
*/
constructor(json: any)
{
this.id_ci = json.id_ci
this.year = json.year
this.name = json.name.trim().replace('&amp;', '&').replace('&quot;', '"')
this.teacher = json.teacher
this.level = json.level
this.courseIds = json.courseIds.split('|').map((id: string) => +id);
this.courseCount = this.courseIds.length;
this.gradeLevels = [];
this.uniqueName = CourseInfo.toUniqueName(this.name);
this.enrollments = 0;
this.classes = []
this.levelID = CourseUtils.getLevelID(this.level);
this.levelFull = CourseUtils.getLevelFullName(this.level);
}
static toUniqueName(name: string)
{
return name
.replace(/( Semester| Full Year|)/g, '')
.replace(/( Accelerated| Honors| College Prep|)/g, '')
.replace(/( A| Acc| CP| H| \(.*\))$/g, '');
}
}
export class UniqueCourse
{
name: string
courses: CourseInfo[]
enrollments: number
constructor(name: string, courses: CourseInfo[], enrollments: number)
{
this.name = name;
this.courses = courses;
this.enrollments = enrollments;
}
get classes()
{
return this.courses.flatMap(c => c.classes);
}
}
export class ClassInfo
{
id: number
name: string
teacher: string
level: string
uniqueName: string
/**
* Construct with a json object
*
* @param json
*/
constructor(json: any)
{
this.id = json.id;
this.name = json.name.trim().replace('&amp;', '&').replace('&quot;', '"')
this.teacher = json.teacher
this.level = json.level;
this.uniqueName = CourseInfo.toUniqueName(this.name);
}
}
export class CourseInfoRating
{
id_ci: number
id_user: number
firstName: string
lastName: string
anonymous: boolean
ratings: number[]
comment: string
averageRating: number = 0
constructor(json: any)
{
this.id_ci = json.id_ci;
this.id_user = json.id_user;
this.anonymous = this.id_user == -1;
this.ratings = json.ratings;
this.comment = json.comment;
if (json.userFullName != null)
{
let nameSplit = json.userFullName.split(']=[');
this.firstName = nameSplit[0];
this.lastName = nameSplit[1];
}
this.ratings.forEach(r => this.averageRating += r);
this.averageRating /= 5;
}
/**
* Create new for posting to the server
* @param id_ci
*/
public static createNew(id_ci: number)
{
return new CourseInfoRating({id_ci: id_ci, id_user: -2, userFullName: null,
anonymous: false, ratings: [0,0,0,0,0], comment: ''})
}
}
export class AnalyzedRating
{
ratingCounts: number[][] // ratingCounts[criteria][stars] = count
ratingSums: number[] // ratingSums[criteria] = total stars
totalCount: number
ratingAverages: number[] = [] // ratingAverages[criteria] = average
overallRating: number = 0
constructor(json: any)
{
this.ratingCounts = json.ratingCounts;
this.ratingSums = json.ratingSums;
this.totalCount = json.totalCount;
// No ratings
if (this.totalCount == 0)
{
this.overallRating = -1;
return;
}
// Calculate overall rating
this.ratingSums.forEach((criteriaScore, i) => this.overallRating += this.ratingAverages[i] = criteriaScore / this.totalCount);
this.overallRating /= this.ratingAverages.length;
}
}
export const RATING_CRITERIA: {title: string, desc: string}[] =
[
{title: 'Enjoyable', desc: 'How enjoyable is the course?'},
{title: 'Knowledge', desc: 'How interesting is the content of the course? ' +
'Is it something you feel worth learning?'},
{title: 'Interactivity', desc: 'How interesting is the teacher? Is the teacher interactive?'},
{title: 'Eloquence', desc: `Are the teacher's lectures easy to understand?`},
{title: 'Fairness', desc: `How fair is the teacher's grading? Is credit given in proportion to effort?`}
];
+48 -30
View File
@@ -6,6 +6,7 @@ import CacheUtils from '@/logic/utils/cache-utils';
import Constants from '@/constants';
import {Index} from '@/logic/nav-controller';
import App from '@/components/app/app';
import {CourseInfoRating} from '@/logic/course-info';
/**
* Objects of this interface represent assignment grades.
@@ -54,7 +55,9 @@ export class Assignment
this.scoreMax = json.maximum_score;
this.score = +json.raw_score;
this.gradingPeriod = +json.grading_period.replace('Quarter ', '') - 1;
// 0, 1, 2, 3 contains quarter assignments, 4 contains final assignments
if (json.grading_period.toLowerCase() == 'all') this.gradingPeriod = 4;
else this.gradingPeriod = +json.grading_period.replace('Quarter ', '') - 1;
}
/**
@@ -63,7 +66,9 @@ export class Assignment
get graded()
{
// TODO: Add more cases
return this.include && (this.complete == 'Complete' || this.complete == 'Late' || this.complete == 'NREQ');
// Incomplete doesn't mean that the teacher didn't grade it yet, which is "Pending".
// NREQ is not graded.
return this.include && (this.complete == 'Complete' || this.complete == 'Late' || this.complete == 'Incomplete' || this.complete == 'Not Turned In');
}
/**
@@ -75,9 +80,13 @@ export class Assignment
{
switch (this.complete)
{
case 'Complete': return '';
case 'Pending': return 'Pending'; // ID: 0
case 'Not Turned In': return 'Not Turned In'; // ID: 1
case 'Incomplete': return 'Incomplete'; // ID: 2
case 'Complete': return ''; // ID: 3
case 'NREQ': return 'Dropped'; // ID: 4
case 'Late': return 'Late';
case 'NREQ': return 'Dropped';
default: return this.complete;
}
}
@@ -88,8 +97,11 @@ export class Assignment
{
switch (this.complete)
{
case 'Late': return '#ff0036';
case 'Pending': return '#b1b1b1';
case 'Not Turned In': return '#ff0036';
case 'Incomplete': return '#ff7a2f';
case 'NREQ': return '#41b141';
case 'Late': return '#ff7a2f';
}
}
@@ -136,6 +148,8 @@ export interface AssignmentType
score: number
percent: number
assignmentCount: number
graded: boolean
}
export interface Grading
@@ -146,21 +160,24 @@ export interface Grading
export default class Course
{
id: number;
assignmentsId: number;
name: string;
teacherName: string;
status: string;
rawAssignments: Assignment[];
id: number
id_ci: number
assignmentsId: number
name: string
teacherName: string
status: string
rawAssignments: Assignment[]
rating: CourseInfoRating
rated: boolean
rawLetterGrade?: string;
rawNumericGrade?: number;
rawLetterGrade?: string
rawNumericGrade?: number
level: string;
scaleUp: number;
level: string
scaleUp: number
termGrading: Grading[];
termAssignments: Assignment[][];
termGrading: Grading[]
termAssignments: Assignment[][]
cache: CacheUtils = new CacheUtils();
@@ -172,10 +189,13 @@ export default class Course
constructor(courseJson: any)
{
this.id = courseJson.id;
this.id_ci = courseJson.id_ci;
this.assignmentsId = courseJson.assignmentsId;
this.name = FormatUtils.parseText(courseJson.name).trim();
this.teacherName = courseJson.teacherName;
this.status = courseJson.status;
this.rated = courseJson.rating != null;
this.rating = this.rated ? new CourseInfoRating(courseJson.rating) : CourseInfoRating.createNew(this.id_ci);
this.rawLetterGrade = courseJson.letterGrade;
this.rawNumericGrade = courseJson.numericGrade;
@@ -187,14 +207,10 @@ export default class Course
this.rawLetterGrade = undefined;
}
// Level and scaleUp
let level = CourseUtils.detectLevel(this.name);
if (level != undefined)
{
this.level = level.level;
this.scaleUp = level.scaleUp;
}
else this.level = 'Unknown';
// Level and scaleUp TODO: Use server course level
let level = CourseUtils.getLevel(courseJson.level);
this.level = level.level;
this.scaleUp = level.scaleUp;
this.termGrading = new Array(4).fill(null);
}
@@ -214,7 +230,7 @@ export default class Course
this.rawAssignments.sort((a, b) => b.time - a.time);
// Filter assignments into terms
this.termAssignments = [[], [], [], []];
this.termAssignments = [[], [], [], [], []];
// Loop through it by time order
this.rawAssignments.forEach(a => this.termAssignments[a.gradingPeriod].push(a));
@@ -271,7 +287,6 @@ export default class Course
{
return this.gradingPeriods
.flatMap(term => this.termAssignments[term])
.filter(a => a.graded)
.sort((a, b) => b.time - a.time);
}
@@ -376,9 +391,12 @@ export default class Course
// Get assignments of the type
let typeAssignments = this.assignments.filter(a => a.type == type);
// Get graded assignments
let gradedAssignments = typeAssignments.filter(a => a.graded);
// Count scores and max scores
let score = typeAssignments.reduce((sum, a) => sum + a.score, 0);
let scoreMax = typeAssignments.reduce((sum, a) => sum + a.scoreMax, 0);
let score = gradedAssignments.reduce((sum, a) => sum + a.score, 0);
let scoreMax = gradedAssignments.reduce((sum, a) => sum + a.scoreMax, 0);
// Calculate weight
let weight = this.termGrading[0].method == 'PERCENT_TYPE'
@@ -387,7 +405,7 @@ export default class Course
// Return
return {name: type, id: typeAssignments[0].typeId, weight: +(weight * 100).toFixed(2),
scoreMax: scoreMax, score: score, percent: +(score / scoreMax * 100).toFixed(2),
assignmentCount: typeAssignments.length}
assignmentCount: typeAssignments.length, graded: gradedAssignments.length > 0}
})
})
}
+31 -21
View File
@@ -1,25 +1,31 @@
import {GPAUtils} from '@/logic/utils/gpa-utils';
import Course from '@/logic/course';
const md5 = require('md5');
export default class LoginUser
{
id: number;
schoolPersonPk: number;
username: string;
lastLogin: Date;
firstLogin: Date;
firstName: string;
lastName: string;
nickname: string;
graduationYear: string;
groups: string;
emails: string[];
classes: string[];
birthday: string;
avatarUrl: string;
token: string;
id: number
schoolPersonPk: number
username: string
lastLogin: Date
firstLogin: Date
firstName: string
lastName: string
graduationYear: number
emails: string[]
classes: string[]
avatarUrl: string
constructor(json: any)
token: string
courses: Course[]
gradeLevel: number
gradeLevelName: string
constructor(jsonData: any)
{
let json = jsonData.user
this.id = json.id;
this.schoolPersonPk = json.schoolPersonPk;
this.username = json.username;
@@ -27,14 +33,18 @@ export default class LoginUser
this.firstLogin = new Date(json.firstLogin);
this.firstName = json.firstName;
this.lastName = json.lastName;
this.nickname = json.nickname;
this.graduationYear = json.graduationYear;
this.groups = json.groups;
this.graduationYear = +json.graduationYear;
this.emails = json.emails.split('|').map((e: any) => e.toLowerCase().trim());
this.classes = json.classes.split('|');
this.birthday = json.birthday;
this.avatarUrl = json.avatarUrl;
this.token = json.token;
// Extracted in newer versions
this.token = jsonData.token;
this.courses = jsonData.courses.map((courseJson: any) => new Course(courseJson));
// Calculated grade level
this.gradeLevel = GPAUtils.getGradeLevel(this.graduationYear);
this.gradeLevelName = GPAUtils.gradeLevelName(this.gradeLevel);
// Generate default avatar
if (this.avatarUrl == null || this.avatarUrl == '')
+65 -46
View File
@@ -1,60 +1,18 @@
import Navigation from '@/components/navigation/navigation';
import Constants from '@/constants';
import {isNumeric} from '@/logic/utils/general-utils';
const LEVEL_AP = {level: 'AP', scaleUp: 1};
const LEVEL_H = {level: 'H', scaleUp: 0.75};
const LEVEL_A = {level: 'A', scaleUp: 0.5};
const LEVEL_CP = {level: 'CP', scaleUp: 0.25};
const LEVEL_CLUB = {level: 'Club', scaleUp: -1};
const UNKNOWN_COURSE_LIST = new Map();
UNKNOWN_COURSE_LIST.set('Piano Masterclass', LEVEL_H);
UNKNOWN_COURSE_LIST.set('Multivariable Calculus with Differential Equations', LEVEL_H);
UNKNOWN_COURSE_LIST.set('Introduction to Algorithmic Thinking and Computational Technologies', LEVEL_A);
UNKNOWN_COURSE_LIST.set('Ceramics 1', LEVEL_CP);
UNKNOWN_COURSE_LIST.set('Ceramics 2', LEVEL_A);
UNKNOWN_COURSE_LIST.set('Sculpture', LEVEL_CP);
UNKNOWN_COURSE_LIST.set('Drawing', LEVEL_CP);
UNKNOWN_COURSE_LIST.set('Painting', LEVEL_CP);
const LEVEL_SPORT = {level: 'Sport', scaleUp: -1};
const LEVEL_NONE = {level: 'None', scaleUp: -1};
const LEVEL_UNKNOWN = {level: 'Unknown', scaleUp: -1};
export class CourseUtils
{
/**
* Detect course level based on course name
*
* @param name Course name
*/
static detectLevel(name: string)
{
// Common ones
if (name.startsWith('AP')) return LEVEL_AP;
if (name.endsWith(' H')) return LEVEL_H;
if (name.endsWith(' A')) return LEVEL_A;
if (name.endsWith(' CP')) return LEVEL_CP;
if (name.startsWith('HS ')) return LEVEL_CLUB;
if (name.startsWith('MS ')) return LEVEL_CLUB;
// Uncommon ones
let lower = name.toLowerCase();
if (name.startsWith('Pre-AP')) return LEVEL_AP;
if (lower.endsWith(' acc')) return LEVEL_A;
if (name.endsWith('H')) return LEVEL_H;
if (name.endsWith('A')) return LEVEL_A;
if (name.endsWith('CP')) return LEVEL_CP;
// Even more uncommon
if (lower.includes('honors')) return LEVEL_H;
if (lower.includes('accelerated')) return LEVEL_A;
if (name.includes('Advanced')) return LEVEL_A;
// Unknown course list
if (UNKNOWN_COURSE_LIST.has(name)) return UNKNOWN_COURSE_LIST.get(name);
// Really unknown
return undefined;
}
/**
* Get the begin date of the selected term
*/
@@ -74,4 +32,65 @@ export class CourseUtils
return selected == -1 ? Constants.TERMS[4] : Constants.TERMS[selected + 1];
}
static getLevelID(level: string)
{
if (level == undefined) return -1;
level = level.toLowerCase();
if (level == 'ap' || level == 'advanced placement') return 1;
if (level == 'h' || level == 'honors') return 2;
if (level == 'a' || level == 'acc' || level == 'accelerated') return 3;
if (level == 'cp' || level == 'college prep') return 4;
if (level == 'club') return 101;
if (level == 'sport') return 102;
if (level == 'none') return 201;
if (isNumeric(level)) return +level;
return -1;
}
/**
* Get full name of a level from short name
*
* @param level Any level name
*/
static getLevelFullName(level: string)
{
switch (this.getLevelID(level))
{
case 1: return 'AP';
case 2: return 'Honors';
case 3: return 'Accelerated';
case 4: return 'CP';
case 101: return 'Club';
case 102: return 'Sport';
case 201: return 'None';
default: return '--';
}
}
/**
* Get full name of a level from short name
*
* @param level Any level name
*/
static getLevel(level: string)
{
switch (this.getLevelID(level))
{
case 1: return LEVEL_AP;
case 2: return LEVEL_H;
case 3: return LEVEL_A;
case 4: return LEVEL_CP;
case 101: return LEVEL_CLUB;
case 102: return LEVEL_SPORT;
case 201: return LEVEL_NONE;
default: return LEVEL_UNKNOWN;
}
}
}
+5
View File
@@ -5,3 +5,8 @@ export function findLastIndex<T>(array: T[], callback: (v: T) => boolean): numbe
let result = arr2.findIndex(callback);
return result == -1 ? -1 : arr2.length - result - 1;
}
export function isNumeric(str: string)
{
return !isNaN(parseFloat(str)) && isFinite(+str);
}
+45 -2
View File
@@ -127,7 +127,7 @@ export class GPAUtils
assignments.forEach(assignment =>
{
// If assignment should be displayed
if (assignment.complete != 'Complete') return;
if (!assignment.graded) return;
// Record scores
score += assignment.score;
@@ -153,7 +153,7 @@ export class GPAUtils
assignments.forEach(assignment =>
{
// If assignment should be displayed
if (assignment.complete != 'Complete') return;
if (!assignment.graded) return;
// Record scores
if (typeScores[assignment.type] == undefined) typeScores[assignment.type] = 0;
@@ -186,4 +186,47 @@ export class GPAUtils
// Add average to the row
return +(score * 100).toFixed(2);
}
/**
* Get current school year
*/
public static getSchoolYear(): number
{
// Get current year
let currentYear = new Date().getFullYear();
// Convert current year to current school year: +1 if it's after August
if (new Date().getMonth() > 7) currentYear ++;
return currentYear;
}
/**
* Get grade level from graduation year
*
* @param graduationYear
*/
public static getGradeLevel(graduationYear: number): number
{
// Calculate grade level
return 12 - (graduationYear - this.getSchoolYear());
}
/**
* Get grade level name from grade level. (Eg. Freshman, Sophomore, etc.)
*
* @param gradeLevel
*/
public static gradeLevelName(gradeLevel: number): string
{
switch (gradeLevel)
{
case 9: return 'Freshman';
case 10: return 'Sophomore';
case 11: return 'Junior';
case 12: return 'Senior';
default: return 'Grade ' + gradeLevel;
}
}
}
+21
View File
@@ -28,4 +28,25 @@ export class HttpUtils
.catch(reject)
});
}
public get(url: string): Promise<any>
{
// Create promise
return new Promise<any>((resolve, reject) =>
{
// Fetch request
fetch(url, {method: 'GET'}).then(res =>
{
// Get response body text
res.text().then(text =>
{
// Parse response
let response = JSON.parse(text);
resolve(response);
})
.catch(reject)
})
.catch(reject)
});
}
}
@@ -0,0 +1,81 @@
#course-list
{
margin-right: 20px;
height: 70vh;
.padding-fix
{
// Fix width issue
padding-left: 20px;
padding-right: 20px;
border-radius: 4px;
}
.header
{
.text
{
margin-top: 15px;
margin-bottom: 10px;
font-size: 24px;
}
.search
{
margin-bottom: 20px;
}
}
.list
{
overflow: scroll;
overflow-x: hidden;
height: 377px;
}
// Remove scrollbar
.list::-webkit-scrollbar
{
width: 0;
}
.item
{
text-align: left;
margin-bottom: 15px;
background: #fbfbfb;
border-radius: 4px;
padding: 5px 10px;
.name
{
// Text too long
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.data
{
// text-align: right;
color: #a5a5a5;
span
{
display: inline-block;
width: 40px;
// text-align: left;
font-size: 12px;
}
}
}
}
// Cards
.left
{
margin-left: 20px;
}
@@ -0,0 +1,197 @@
import {Component, Prop, Vue} from 'vue-property-decorator'
import App from '@/components/app/app';
import CourseInfo, {ClassInfo, UniqueCourse} from '@/logic/course-info';
import {GPAUtils} from '@/logic/utils/gpa-utils';
// @ts-ignore
import SearchSettingsComponent, {SearchSettings} from '@/pages/course-selection/pages/search-settings.vue';
import Welcome from '@/pages/course-selection/pages/welcome.vue';
import CourseDetail from '@/pages/course-selection/pages/course-detail.vue';
import LoadingSpinner from '@/components/loading-spinner.vue';
@Component({components: {SearchSettings: SearchSettingsComponent, Welcome, CourseDetail, LoadingSpinner}})
export default class CourseSelection extends Vue
{
@Prop({required: true}) app: App
search: string = ''
courseInfo: CourseInfo[] = []
courseIdIndex: any = {} // Map<CourseID, index in courseInfo>
directory: {gradeLevel: number, classes: string}[] = []
classes: ClassInfo[] = []
loading = true
courseListHeight: number = 0;
cardsHeight: number = 0;
openedPage: string = '';
settings: SearchSettings = new SearchSettings();
activeCourse: UniqueCourse = new UniqueCourse('', [], -1);
/**
* Called before rendering
*/
created()
{
// Update width dynamically
window.addEventListener('resize', this.updateHeight);
// Get courses
App.http.post('/course-info', {}).then(result =>
{
if (result.success)
{
// Parse data
this.classes = result.data.classes.map((json: any) => new ClassInfo(json));
this.courseInfo = result.data.courseInfos.map((json: any, index: number) =>
{
let info = new CourseInfo(json);
// Index
info.courseIds.forEach(id =>
{
this.courseIdIndex[id] = index;
// Add class info into course
let classInfo = this.classes.find(c => c.id == id)
if (classInfo == null) return;
info.classes.push(classInfo);
});
return info;
});
this.directory = result.data.studentInfos;
// Use directory data
this.directory.forEach(d =>
{
d.classes.split('|').forEach(classId =>
{
// Get info by class id
let info = this.courseInfo[this.courseIdIndex[+classId]];
if (info as any != null)
{
// Add grade level
if (!info.gradeLevels.includes(d.gradeLevel))
{
info.gradeLevels.push(d.gradeLevel);
}
// Count enrollments
info.enrollments ++;
}
})
})
this.loading = false;
}
});
}
/**
* Called on destroy
*/
destroyed()
{
// Remove width updater
window.removeEventListener('resize', this.updateHeight);
}
/**
* Called on vue update
*/
updated()
{
this.updateHeight()
}
/**
* Update header height. (CSS doesn't work)
*/
updateHeight()
{
// Get element
let cl = this.$refs.cl as Vue;
if (cl as any == null) return;
let el = cl.$el;
// Calculate height
this.cardsHeight = window.innerHeight - el.getBoundingClientRect().top - 20;
this.courseListHeight = this.cardsHeight - 15 - 102;
}
/**
* Open the settings page.
*/
openSettings()
{
this.openedPage = this.openedPage == 'settings' ? '' : 'settings';
}
/**
* Open course page.
*/
openCourse(course: UniqueCourse)
{
if (this.activeCourse == course)
{
this.openedPage = '';
this.activeCourse = null as any as UniqueCourse;
}
else
{
this.activeCourse = course;
this.openedPage = 'course'
}
}
get filteredCourses()
{
let year = GPAUtils.getSchoolYear();
return this.courseInfo.filter(c =>
c.uniqueName.toLowerCase().includes(this.search.toLowerCase()) &&
c.level != null && this.settings.levels.includes(c.level) &&
c.year == year &&
(this.settings.showAllCourses || c.gradeLevels.includes(this.app.user.gradeLevel + 1))
)
}
/**
* Gets unique courses by name, even though many different teachers might teach it.
*/
get uniqueCourses(): UniqueCourse[]
{
let names: string[] = [];
let list: UniqueCourse[] = [];
this.filteredCourses.forEach(c =>
{
// Create the course list if doesn't exist
if (!names.includes(c.uniqueName))
{
names.push(c.uniqueName);
list.push(new UniqueCourse(c.uniqueName, [], 0))
}
// Add the course
list[names.indexOf(c.uniqueName)].courses.push(c);
list[names.indexOf(c.uniqueName)].enrollments += c.enrollments;
})
// Sorting
switch (this.settings.sortBy)
{
case 'Popularity':
{
list.sort((a, b) => b.enrollments - a.enrollments);
break
}
default:
{
list.sort((a, b) => a.name.localeCompare(b.name));
break
}
}
return list;
}
}
@@ -0,0 +1,49 @@
<template>
<div id="course-selection">
<div v-if="loading" class="loading vertical-center" style="height: 100%">
<LoadingSpinner style="left: 0"/>
</div>
<el-row v-else>
<el-col :span="16" class="overall-span">
<el-card class="left" :style="{height: cardsHeight + 'px'}">
<SearchSettings v-if="openedPage === 'settings'" ref="settings" :settings="settings"/>
<Welcome v-if="openedPage === ''" :app="app"/>
<CourseDetail v-if="openedPage === 'course'" :unique-course="activeCourse"/>
</el-card>
</el-col>
<!-- Course list card -->
<el-col :span="8" class="overall-span">
<el-card id="course-list" class="right" ref="cl" body-style="padding: 0" :style="{height: cardsHeight + 'px'}">
<div class="header padding-fix">
<div class="text">Course List</div>
<!-- Search -->
<el-input class="search" placeholder="Search..." prefix-icon="el-icon-search" v-model="search">
<el-button slot="append" icon="el-icon-s-tools" @click="openSettings"/>
</el-input>
</div>
<!-- Actual course list -->
<div class="list padding-fix" :style="{height: courseListHeight + 'px'}">
<!-- Every course -->
<div v-for="(course, index) in uniqueCourses" class="item vertical-center clickable unselectable"
@click="openCourse(course)">
<div class="name">{{course.name}}</div>
<div class="data">
<span class="classes"><i class="el-icon-s-home"/> {{course.classes.length}}</span>
<span class="teachers"><i class="el-icon-user-solid"/> {{course.courses.length}}</span>
<span class="enrollments"><i class="el-icon-user"/> {{course.enrollments}}</span>
</div>
</div>
</div>
</el-card>
</el-col>
</el-row>
</div>
</template>
<script src="./course-selection.ts" lang="ts"/>
<style src="./course-selection.scss" lang="scss" scoped/>
@@ -0,0 +1,269 @@
<template>
<div id="course-detail">
<div class="header">Course: <span style="color: #229fff">{{uniqueCourse.name}}</span></div>
<el-divider class="divider"><i class="el-icon-reading"></i></el-divider>
<!-- All course-infos -->
<div class="item clickable unselectable" v-for="c in sortedCourses" @click="openDetails(c)">
<div class="float-left">
<div>{{c.levelFull}} - <i>{{c.teacher}}</i></div>
<div class="info">
<span class="name">{{c.name}} : </span>
<span class="classes"><i class="el-icon-s-home"/> {{c.classes.length}}</span>
<span class="enrollments"><i class="el-icon-user"/> {{c.enrollments}}</span>
</div>
</div>
<div class="float-right">
<LoadingSpinner v-if="c.rating == null" class="loading" size="30" :centered="false"/>
<div v-else class="rating">
<span v-if="c.rating.totalCount === 0" class="text">No ratings yet...</span>
<span v-else class="stars">
<StarRating :score="c.rating.overallRating"></StarRating>
<span class="info">
<span class="numeric-rating">{{c.rating.overallRating.toFixed(2)}} / 5</span>
<span>({{c.rating.totalCount}} rating{{c.rating.totalCount > 1?'s':''}})</span>
</span>
</span>
</div>
</div>
</div>
<!-- Detail / Comments Popup -->
<el-dialog id="detail-popup" v-if="detailsCourse" :visible="detailsCourse != null" width="50%" top="10vh"
:before-close="closeDetails">
<span slot="title" class="header">
<div class="title">Ratings for {{detailsCourse.name}}</div>
<span class="subtitle">And for {{detailsCourse.teacher}}</span>
</span>
<div class="rating-item" v-for="(criteria, index) of ratingCriteria">
<div class="title float-left">{{criteria.title}}</div>
<div class="stars float-right">
<span class="info numeric-rating">{{rating.ratingAverages[index].toFixed(2)}} / 5</span>
<StarRating :score="rating.ratingAverages[index]" style="display: inline-block"></StarRating>
</div>
</div>
<div class="comments">
<div class="header">
Comments
</div>
<LoadingSpinner v-if="detailsComments == null"/>
<div class="comment" v-else v-for="comment of detailsComments">
<div class="user">
<i class="el-icon-user-solid"/>
{{comment.firstName}} {{comment.lastName}}:
<span class="info numeric-rating" style="margin-left: 5px">{{comment.averageRating.toFixed(2)}} / 5</span>
</div>
<div class="text">
<blockquote>{{comment.comment}}</blockquote>
</div>
</div>
</div>
</el-dialog>
</div>
</template>
<script lang="ts">
import {Component, Prop, Vue} from 'vue-property-decorator'
import CourseInfo, {AnalyzedRating, CourseInfoRating, RATING_CRITERIA, UniqueCourse} from '@/logic/course-info';
import App from '@/components/app/app';
import course from '@/logic/course';
import LoadingSpinner from '@/components/loading-spinner.vue';
import loading from '@/components/overlays/loading.vue';
import StarRating from '@/components/star-rating.vue';
@Component({components: {StarRating, LoadingSpinner}})
export default class CourseDetail extends Vue
{
@Prop({required: true}) uniqueCourse: UniqueCourse;
detailsCourse: CourseInfo = null as any as CourseInfo
detailsComments: CourseInfoRating[] = null as any as []
get ratingCriteria() {return RATING_CRITERIA}
get rating() {return this.detailsCourse.rating}
mounted()
{
this.checkRatings()
}
updated()
{
this.checkRatings()
}
checkRatings()
{
// Load ratings
this.sortedCourses.forEach(c =>
{
// Already has rating
if (c.rating as any != null) return;
// Get rating
App.http.post('/course-info/rating/get', {condition: 'course', value: c.id_ci}).then(result =>
{
if (result.success)
{
// Assign rating
c.rating = new AnalyzedRating(result.data);
}
else
{
this.$message.error(`Rating data for ${c.name} / ${c.teacher} failed to load.`)
console.log(result.data);
}
})
})
}
get sortedCourses(): CourseInfo[]
{
return this.uniqueCourse.courses.sort((a, b) => a.levelID - b.levelID);
}
openDetails(course: CourseInfo)
{
let c = this.detailsCourse = this.detailsCourse == course ? null as any as CourseInfo : course;
// Load comments
App.http.post('/course-info/rating/get', {condition: 'course-comments', value: c.id_ci}).then(result =>
{
if (result.success)
{
this.detailsComments = result.data.map((r:any) => new CourseInfoRating(r));
}
else
{
this.$message.error(`Rating data for ${c.name} / ${c.teacher} failed to load.`)
console.log(result.data);
}
})
// TODO: Finish comment section
}
closeDetails()
{
this.detailsCourse = null as any as CourseInfo;
this.detailsComments = null as any as []
}
}
</script>
<style src="./pages.scss" lang="scss" scoped/>
<style lang="scss" scoped>
.item
{
text-align: left;
margin-bottom: 15px;
background: #f8fdff;
border-radius: 4px;
padding: 5px 10px;
height: 40px;
}
.info
{
font-size: 12px;
color: gray;
.classes
{
display: inline-block;
margin-right: 10px;
}
}
.numeric-rating
{
margin-right: 10px;
}
.float-left
{
text-align: left;
float: left;
}
.float-right
{
text-align: right;
float: right;
}
.loading
{
margin-top: 5px !important;
}
.rating
{
.text
{
font-size: 14px;
}
}
#detail-popup
{
text-align: left;
.header
{
.title
{
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
margin-bottom: -10px;
}
.subtitle
{
margin-left: 10px;
font-size: 12px;
font-style: italic;
color: #999999;
}
}
.rating-item
{
height: 30px;
.title
{
font-size: 18px;
}
}
.rating-item:first-child
{
margin-top: -15px;
}
.comments
{
margin-top: 40px;
.comment
{
margin-bottom: 20px;
}
blockquote
{
padding: 0 1em;
/* color: #6a737d; */
border-left: .25em solid #dfe2e5;
margin: 5px 0 0 0;
}
}
}
</style>
@@ -0,0 +1,16 @@
.header
{
margin-top: 20px;
margin-bottom: 10px;
font-size: 24px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.divider
{
margin-top: 20px !important;
}
@@ -0,0 +1,70 @@
<template>
<div id="settings">
<div class="header">Settings</div>
<el-divider class="divider"><i class="el-icon-s-tools"></i></el-divider>
<div class="content">
<el-switch v-model="settings.showAllCourses" class="item"
active-text="Show all courses (including the ones not listed on your grade level)"/>
<div class="item">
<span class="item-label">Sort by:</span>
<el-radio-group v-model="settings.sortBy">
<el-radio label="Name"></el-radio>
<el-radio label="Popularity"></el-radio>
<el-radio label="Classes"></el-radio>
<el-radio label="Level"></el-radio>
<el-radio disabled label="Peer Rating (Coming soon)"></el-radio>
</el-radio-group>
</div>
<div class="item">
<span class="item-label">Levels:</span>
<el-checkbox-group v-model="settings.levels" style="display: inline-block;">
<el-checkbox label="AP">AP</el-checkbox>
<el-checkbox label="H">Honors</el-checkbox>
<el-checkbox label="A">Accelerated</el-checkbox>
<el-checkbox label="CP">CP</el-checkbox>
</el-checkbox-group>
</div>
</div>
</div>
</template>
<script lang="ts">
import {Component, Prop, Vue} from 'vue-property-decorator'
import App from '@/components/app/app';
@Component
export default class SearchSettingsComponent extends Vue
{
@Prop({required: true}) settings: SearchSettings;
// TODO: Show all courses option
}
export class SearchSettings
{
showAllCourses: boolean = App.instance.user.gradeLevel == 12;
sortBy: string = 'Name'
levels: string[] = ['AP','H','A','CP']
}
</script>
<style src="./pages.scss" lang="scss" scoped/>
<style lang="scss" scoped>
.content
{
text-align: left;
.item
{
margin-bottom: 20px;
font-size: 14px;
.item-label,.el-radio,.el-checkbox
{
margin-right: 15px;
}
}
}
</style>
@@ -0,0 +1,63 @@
<template>
<div id="welcome">
<div class="header">Welcome</div>
<el-divider class="divider"><i class="el-icon-cold-drink"></i></el-divider>
<div class="content" style="color: #ff3d3d" v-if="app.user.gradeLevel >= 12">
You are a senior, what are you doing over here lol. <br>
Unfortunately I can't help you with college course selection.<br>
(But you can still view course ratings)<br><br>
</div>
<div class="content">
<span style="color:#409EFF">
This new page is designed to help you with your course selection for your {{nextGrade}} year,
providing more information such as how many people are currently enrolled in a course.
</span>
<br><br>
The courses displayed are from the current year,
but since they are unlikely to change,
they can provide a good view for the courses next year.
However, this also means that the new courses
and courses that didn't open this year are not going to be displayed here.
For 2020, the new courses are Financial Algebra and Acc Psychology.
Also, by default, only the courses that current {{nextGrade.toLowerCase()}}s take are displayed,
and you can enable "show all courses" in settings if you want to see all courses.
<br><br>
<b>Notations:</b><br>
<i class="el-icon-s-home"/>: How many classes (blocks) did the course open this year.<br>
<i class="el-icon-user-solid"/>: How many teachers are teaching this course.<br>
<i class="el-icon-user"/> How many students are enrolled.<br>
<br>
<b>Sorting:</b><br>
By default the courses are sorted by name,
but you can change the settings to sort by popularity, by classes, or by level.
</div>
</div>
</template>
<script lang="ts">
import {Component, Prop, Vue} from 'vue-property-decorator'
import App from '@/components/app/app';
import {GPAUtils} from '@/logic/utils/gpa-utils';
@Component
export default class Welcome extends Vue
{
@Prop({required: true}) app: App
get nextGrade()
{
return GPAUtils.gradeLevelName(this.app.user.gradeLevel + 1)
}
}
</script>
<style src="./pages.scss" lang="scss" scoped/>
<style lang="scss" scoped>
.content
{
text-align: justify;
color: #585858;
}
</style>
@@ -3,12 +3,13 @@
<el-card :body-style="{padding: '0px'}">
<div id="type-info-card">
<span id="type-name">{{type.name}}</span>
<span id="type-average">Average: {{type.percent}}%</span>
<span class="type-average" v-if="type.graded">Average: {{type.percent}}%</span>
<span class="type-average" v-if="!type.graded">No grades yet!</span>
</div>
<AssignmentEntry v-for="assignment of filteredAssignments" :key="assignment.id"
<AssignmentEntry v-for="(assignment, index) of filteredAssignments" :key="assignment.id"
:assignment="assignment" :unread="false"
backgroundColor="#ffffff" narrow="true">
:backgroundColor="index % 2 === 1 ? '#ffffff' : '#f7f7f7'" narrow="true">
</AssignmentEntry>
</el-card>
</div>
@@ -36,6 +37,11 @@
</script>
<style lang="scss" scoped>
#assignment-type-head
{
margin-bottom: 20px;
}
#type-info-card
{
height: 60px;
@@ -56,7 +62,7 @@
float: left;
}
#type-average
.type-average
{
// Font
font-size: 14px;
@@ -1,10 +1,11 @@
import {Component, Prop, Vue} from 'vue-property-decorator';
import Constants from '@/constants';
import {FormatUtils} from '@/logic/utils/format-utils';
import moment from 'moment';
import moment, {min, Moment} from 'moment';
import Course, {Assignment} from '@/logic/course';
import GraphUtils from '@/logic/utils/graph-utils';
import chroma from 'chroma-js';
import Navigation from '@/components/navigation/navigation';
@Component
export default class CourseScatter extends Vue
@@ -26,6 +27,8 @@ export default class CourseScatter extends Vue
*/
get chartSettings()
{
let term = Navigation.instance.getSelectedTerm()
// Create settings
let settings =
{
@@ -40,7 +43,9 @@ export default class CourseScatter extends Vue
{
formatter: (name: any) => moment(name).format('MMM DD')
},
max: new Date().getTime()
min: Constants.TERMS[term == -1 ? 0 : term].getTime(),
max: term == -1 ? moment.min(moment(), moment(Constants.TERMS[4])).toDate().getTime() :
Constants.TERMS[term + 1].getTime()
},
// Y axis represents GPAs and MaxGPAs
@@ -98,7 +103,7 @@ export default class CourseScatter extends Vue
private series()
{
// Create scatter plots
let series: any[] = this.course.assignmentTypes.map((type, i) =>
let series: any[] = this.course.assignmentTypes.filter(t => t.graded).map((type, i) =>
{
return {
type: 'scatter',
+1 -1
View File
@@ -37,7 +37,7 @@ export default class TypePie extends Vue
radius: ['40%', '60%'],
center: ['50%', '55%'],
label: GraphUtils.pieTextStyle(),
data: this.course.assignmentTypes.map((t, i) => {return {
data: this.course.assignmentTypes.filter(t => t.graded).map((t, i) => {return {
value: t.weight,
name: `${t.name}\n${t.weight}%`,
itemStyle:
+3 -3
View File
@@ -23,7 +23,7 @@ export default class TypeRadar extends Vue
*/
get chartSettings()
{
let min = this.course.assignmentTypes.reduce((min, t) => Math.min(min, t.percent), 100);
let min = this.course.assignmentTypes.filter(t => t.graded).reduce((min, t) => Math.min(min, t.percent), 100);
// Create settings
let settings =
@@ -54,7 +54,7 @@ export default class TypeRadar extends Vue
opacity: 0.4
}
},
indicator: this.course.assignmentTypes.map((t, i) => {return {
indicator: this.course.assignmentTypes.filter(t => t.graded).map((t, i) => {return {
name: `${t.name}\n${t.percent}%`,
max: 100,
min: min - 30,
@@ -89,7 +89,7 @@ export default class TypeRadar extends Vue
},
opacity: 0.2
},
value: this.course.assignmentTypes.map(t => t.percent)
value: this.course.assignmentTypes.filter(t => t.graded).map(t => t.percent)
}
]
},
@@ -11,7 +11,7 @@
// Date
.el-col.date
{
min-width: 130px;
min-width: 150px;
span.month
{
@@ -57,6 +57,9 @@
text-align: right;
float: right;
// Fix smaller screen display issues.
width: unset;
// Status / Problems
span.status
{
@@ -21,12 +21,12 @@
<span v-if="assignment.problem" class="status entry-box" :style="{color: assignment.problemColor}">
{{assignment.problem}}
</span>
<span class="percent entry-box">
<span v-if="assignment.graded" class="percent entry-box">
{{(assignment.score / assignment.scoreMax * 100).toFixed(1)}}
<span class="symbol">%</span>
</span>
<span class="score entry-box">{{assignment.score}}</span>
<span class="max entry-box">{{assignment.scoreMax}}</span>
<span v-if="assignment.graded" class="score entry-box">{{assignment.score}}</span>
<span v-if="assignment.graded" class="max entry-box">{{assignment.scoreMax}}</span>
<el-button class="mark-as-read" :class="unread ? 'unread' : 'no-unread'"
size="mini" type="text" icon="el-icon-close"
@@ -94,6 +94,106 @@
}
}
#content
{
margin-top: -20px;
padding: 20px 0 20px 20px;
height: 50px;
margin-left: -20px;
}
#block-rate
{
// Align right
width: 55px;
float: right;
margin-left: 20px;
padding: 0 10px;
color: white;
background: #84c0ff;
border-radius: 0 4px 4px 0;
height: 90px;
margin-top: -20px;
margin-right: -20px;
box-shadow: inset 8px 0 11px -4px rgba(0,0,0,.1);
}
#block-rate.rated
{
background: #66eaad;
}
.dark #block-rate > span
{
color: #84c0ff !important;
}
.dark #block-rate.rated > span
{
color: #66eaad !important;
}
#rating-popup
{
text-align: left;
.header
{
.title
{
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.subtitle
{
margin-left: 10px;
font-size: 12px;
font-style: italic;
color: #999999;
}
}
.item
{
margin-bottom: 10px;
white-space: normal;
.title
{
font-size: 18px;
color: var(--main);
}
.description
{
font-size: 14px;
}
.stars
{
font-size: 20px;
color: #FFB300;
}
.el-textarea
{
margin-top: 5px;
margin-bottom: 5px;
}
}
.item:first-child
{
margin-top: -15px;
}
}
#block-term-grades
{
// Align right
@@ -1,27 +1,82 @@
<template>
<div id="course-head" class="course-card-content main vertical-center"
:class="clickable ? 'clickable' : ''" @click="redirect">
<div id="block-info">
<div id="name">{{course.name}}</div>
<div id="teacher">{{course.teacherName}}</div>
<div id="course-head" class="course-card-content main vertical-center">
<!-- Rating button -->
<div id="block-rate" v-if="displayRate" class="vertical-center clickable"
@click="ratingDialog = true" :class="{rated: course.rated}">
<span v-if="!course.rated">Give a<br>Rating!</span>
<span v-else>Rating<br>Entered</span>
</div>
<div id="block-grade">
<div id="grade">
<span id="letter">{{course.letterGrade}} </span>
<span id="numeric">{{course.numericGrade === undefined ? '--' : course.numericGrade.toFixed(2)}}</span>
<span id="percent" v-if="course.numericGrade !== undefined">%</span>
<!-- Main content -->
<div id="content" @click="redirect" :class="clickable ? 'clickable' : ''">
<!-- Left -->
<div id="block-info">
<div id="name">{{course.name}}</div>
<div id="teacher">{{course.teacherName}}</div>
</div>
<div id="updates" @click="redirect" :class="unread === 0 ? 'none' : 'unread'">
<span id="unread-number">{{unread}}</span>
<span id="unread-text" :class="clickable ? 'clickable' : ''">new update{{unread >= 2 ? 's' : ''}}</span>
<!-- Right -->
<div id="block-grade">
<div id="grade">
<span id="letter">{{course.letterGrade}} </span>
<span id="numeric">{{course.numericGrade === undefined ? '--' : course.numericGrade.toFixed(2)}}</span>
<span id="percent" v-if="course.numericGrade !== undefined">%</span>
</div>
<div id="updates" @click="redirect" :class="unread === 0 ? 'none' : 'unread'">
<span id="unread-number">{{unread}}</span>
<span id="unread-text" :class="clickable ? 'clickable' : ''">new update{{unread >= 2 ? 's' : ''}}</span>
</div>
</div>
<div id="block-term-grades" v-if="course.rawSelectedTerm === -1"
v-for="term in course.allGradingPeriods.slice().reverse()">
<div id="term">Term {{term + 1}}</div>
<div id="term-letter">{{course.letterGradeTerm(term)}}</div>
<div id="term-numeric">{{course.numericGradeTerm(term).toFixed(1)}}%</div>
</div>
</div>
<div id="block-term-grades" v-if="course.rawSelectedTerm === -1"
v-for="term in course.allGradingPeriods.slice().reverse()">
<div id="term">Term {{term + 1}}</div>
<div id="term-letter">{{course.letterGradeTerm(term)}}</div>
<div id="term-numeric">{{course.numericGradeTerm(term).toFixed(1)}}%</div>
</div>
<!-- Rating Popup -->
<el-dialog id="rating-popup" :visible.sync="ratingDialog" width="50%" top="5vh"
:show-close="false" :close-on-click-modal="false" :close-on-press-escape="false">
<span slot="title" class="header">
<div class="title">Give a Rating for {{course.name}}</div>
<span class="subtitle">And for {{course.teacherName}}<br></span>
<span class="subtitle" style="color: #e67b0d;">(might need to scroll down to find the submit button)</span>
</span>
<div class="item" v-for="(criteria, index) of ratingCriteria">
<div class="title">{{criteria.title}}</div>
<div class="description">{{criteria.desc}}</div>
<div class="stars">
<span class="star clickable" v-for="star in [0,1,2,3,4]" @click="changeStars(index, star)">
<i v-if="rating.ratings[index] > star" class="el-icon-star-on"/>
<i v-else class="el-icon-star-off"/>
</span>
</div>
</div>
<div class="item">
<div class="title">Comments</div>
<div class="description">Any additional comments? (this is optional)</div>
<el-input type="textarea" placeholder="Comments... (Optional)"
v-model="rating.comment" maxlength="4500" show-word-limit :autosize="{minRows: 2, maxRows: 4}">
</el-input>
<el-checkbox v-model="rating.anonymous">Anonymous</el-checkbox>
</div>
<span slot="footer" class="dialog-footer">
<el-button @click="ratingDialog = false" :disabled="ratingPosting">Cancel</el-button>
<el-button type="primary" @click="submitRating()" :disabled="canSubmit">
{{course.rated ? 'Update' : 'Submit'}}
</el-button>
</span>
</el-dialog>
</div>
</template>
@@ -29,16 +84,27 @@
import {Component, Prop, Vue} from 'vue-property-decorator';
import Course from '@/logic/course';
import App from '@/components/app/app';
import {GPAUtils} from '@/logic/utils/gpa-utils';
import Constants from '@/constants';
import {RATING_CRITERIA} from '@/logic/course-info';
import Navigation from '@/components/navigation/navigation';
@Component
export default class CourseHead extends Vue
{
@Prop({required: true}) unread: number;
@Prop({required: true}) course: Course;
@Prop({required: true}) clickable: boolean;
ratingDialog = false;
ratingPosting = false;
rating = this.course.rating;
get canSubmit()
{
return this.ratingPosting || App.instance.demoMode
}
/**
* Redirect to the course page
*/
@@ -47,6 +113,85 @@
if (!this.clickable) return;
App.instance.nav.updateIndex(this.course.urlIndex);
}
/**
* Display rate button or not
*/
get displayRate()
{
return this.clickable && App.instance.showRating;
}
get ratingCriteria() {return RATING_CRITERIA}
/**
* Change star rating data
*
* @param index Index of the rating
* @param star Change to how many stars
*/
changeStars(index: number, star: number)
{
this.rating.ratings[index] = star + 1;
this.$forceUpdate();
}
/**
* Submit a rating
*/
submitRating()
{
if (this.rating.ratings.includes(0))
{
this.$message.error('You haven\'t rated all of the criteria yet!');
return;
}
this.ratingPosting = true;
App.http.post('/course-info/rating/set', {rating: this.rating}).then(response =>
{
if (response.success)
{
this.ratingDialog = false;
this.ratingPosting = false;
this.$message.success('Rating successfully posted, thank you!');
// First rating (Updating the first review doesn't count as first review)
if (this.course.rated) return;
this.course.rated = true;
if (App.instance.courses.filter(c => c.rated).length == 1)
{
this.$alert('You can view other courses\'' +
' ratings in the Course Selection tab, or review yours by clicking' +
' the green button that says "Rating Entered." There are also option to turn off the rating buttons ' +
' by clicking on your avatar on the top right corner.',
'Thank you for submitting your fist rating!', {confirmButtonText: 'OK'}
);
}
// Last rating
if (App.instance.courses.filter(c => c.isGraded && !c.rated).length == 0)
{
this.$confirm('You have rated all of your courses! Do you want to turn off the rating buttons?' +
' (there are option to toggle them on again by clicking your avatar on the top right corner.)',
'Thank you for submitting rating!',
{confirmButtonText: 'Sure!', cancelButtonText: 'Nope.'}).then(() =>
{
// Disable rating buttons
Navigation.instance.onAvatarMenu('switch-rating');
this.$message.success('Rating buttons are disabled');
});
}
}
else
{
this.$message.error('Sorry, but rating failed to post, please try again or email me if you continues to have issues. ' +
'But wait! The email system isn\'t created yet... oops!. (Technical error message: ' + response.data + ')');
this.ratingPosting = false;
}
});
}
}
</script>
+3 -5
View File
@@ -3,7 +3,7 @@
<el-progress v-if="started" :text-inside="true" :percentage="progress()"
:stroke-width="20" status="success" style="margin: 0 20px"/>
<el-dialog title="Notice" :visible.sync="clearUnreadPrompt"
<el-dialog title="Notice" :visible.sync="clearUnreadPrompt" @close="clearUnread(false)"
width="30%" style="word-break: unset;">
<span>You have too many new grade notifications. Clear them now?</span>
<img src="./too-many-unread.png" alt=""/>
@@ -45,10 +45,7 @@
</el-card>
</el-row>
<overall-course v-for="course in courses"
:course="course"
:key="course.id">
</overall-course>
<overall-course v-for="course in courses" :course="course" :key="course.id"/>
</div>
</template>
@@ -126,6 +123,7 @@
// Don't ask again
this.$cookies.set('va.ignore-unread', true);
return;
}
// Clear unread