Canvas Dashboard Grades

Modern alternative to the Canvas Dashboard Grades extension.

  1. // ==UserScript==
  2. // @name Canvas Dashboard Grades
  3. // @namespace https://xela.codes
  4. // @version 2025-01-07
  5. // @description Modern alternative to the Canvas Dashboard Grades extension.
  6. // @author Alex
  7. // @match https://*.instructure.com/
  8. // @icon https://lh3.googleusercontent.com/kA8gaN8ouFmWN-A224OZoB7mR_YpqQuWqeMUcuATLT1DBVecjfD5arRhIvl0rQ17-LoeVump_yWWVmjKKmjc1kQDKbE=s60
  9. // @grant none
  10. // @run-at document-end
  11. // ==/UserScript==
  12.  
  13. (async function () {
  14. "use strict";
  15.  
  16. const enrolledCourses = await fetch(
  17. location.protocol +
  18. "//" +
  19. location.host +
  20. location.pathname +
  21. "api/v1/courses?include[]=total_scores&per_page=100&enrollment_state=active",
  22. {
  23. method: "GET",
  24. credentials: "include",
  25. headers: {
  26. Accept: "application/json",
  27. },
  28. }
  29. ).then((res) => res.json());
  30.  
  31. function doGrades() {
  32. enrolledCourses.forEach((course) => {
  33. /** course card header element */
  34. const courseCard = document.querySelector(
  35. `.ic-DashboardCard a[href="/courses/${course.id}"]`
  36. )?.parentElement,
  37. hero = courseCard?.querySelector(".ic-DashboardCard__header_hero"),
  38. /** course enrollment information (contains grade) */
  39. enrollment = course.enrollments?.slice(-1)[0];
  40. if (!courseCard || !hero || !enrollment)
  41. return console.error(`Failed to add grade for course ${course.name}.`);
  42.  
  43. // use the course color for text
  44. const color = hero.style?.backgroundColor || "inherit";
  45.  
  46. const title = document.createElement("span");
  47. title.classList.add("cdg-grade");
  48. title.style.color = color;
  49. title.style.background = "white";
  50. title.style.borderRadius = "99999px";
  51. title.style.opacity = "1";
  52. title.style.padding = "0.2rem 0.5rem";
  53. title.style.fontSize = title.style.lineHeight = "14px";
  54. title.style.display = "inline-block";
  55. title.style.position = "absolute";
  56. title.style.top = title.style.left = "0.35rem";
  57. title.style.fontWeight = "bold";
  58. title.innerText =
  59. // the enrollment contains the current grade
  60. enrollment.computed_current_score == null ? "N/A" : `${enrollment.computed_current_score}%`;
  61. courseCard.appendChild(title);
  62.  
  63. // update the color if changed
  64. const observer = new MutationObserver((list) => {
  65. if (!title.parentElement) return;
  66. list.forEach((mut) => {
  67. if (mut.type == "attributes" && mut.attributeName == "style")
  68. title.style.color = hero.style.backgroundColor || "inherit";
  69. });
  70. });
  71.  
  72. observer.observe(hero, { attributes: true });
  73. });
  74. }
  75. doGrades();
  76.  
  77. // sometimes the page gets re-rendered, removing the grades
  78. // so we check every so often to make sure they're still there
  79. if (enrolledCourses?.length)
  80. setInterval(() => {
  81. if (!document.querySelector(".cdg-grade")) doGrades();
  82. }, 66);
  83. })();