Power+ Userscript Loader

An alternate, customizable loader for Power+ (powerplus.app)

  1. // ==UserScript==
  2. // @name Power+ Userscript Loader
  3. // @namespace https://powerplus.app/
  4. // @version 1.2
  5. // @description An alternate, customizable loader for Power+ (powerplus.app)
  6. // @author jottocraft
  7. // @license MIT
  8. // @icon https://powerplus.app/favicon.png
  9. // @grant none
  10. // @run-at document-start
  11. // @match https://*.instructure.com/*
  12. // ==/UserScript==
  13.  
  14. (function () {
  15. 'use strict';
  16.  
  17. if ((window.location.pathname === "/power+/") || (window.location.pathname === "/power+")) {
  18. var s;
  19.  
  20. //Check for light or dark theme
  21. var light = false;
  22. if (window.localStorage.fluidTheme == "light") {
  23. light = true;
  24. } else if (((window.localStorage.fluidTheme == "system") || (window.localStorage.fluidTheme == "auto")) && !window.matchMedia("(prefers-color-scheme: dark)").matches) {
  25. light = true;
  26. }
  27.  
  28. //Replace canvas 404 page with Power+
  29. const observer = new MutationObserver(mutations => {
  30. mutations.forEach(({ addedNodes }) => {
  31. addedNodes.forEach(node => {
  32. //Power+ preloader
  33. if (node.nodeType === 1 && node.tagName === 'BODY') {
  34. node.innerHTML = /*html*/`
  35. <div dtps="true" id="dtpsNativeOverlay" style="background-color: inherit; position: fixed; top: 0px; left: 0px; width: 100%; height: 100vh; z-index: 99;text-align: center;z-index: 999;transition: opacity 0.2s;">
  36. <img dtps="true" style="height: 100px; margin-top: 132px;" src="${light ? "https://i.imgur.com/0WzWwb1.png" : "https://i.imgur.com/7dDUVh2.png"}" />
  37. <br dtps="true" />
  38. <div dtps="true" class="progress"><div id="dtpsLoadingScreenBar" dtps="true" class="indeterminate"></div></div>
  39. <style dtps="true">body {background-color: ${light ? "white" : "#151515"}; --crxElements: ${light ? "#ececec" : "#2b2b2b"}; --crxText: ${light ? "#333" : "#efefef"}; overflow: hidden;}*,:after,:before{box-sizing:border-box}.progress{background:var(--crxElements);position:relative;width:600px;height:5px;overflow:hidden;border-radius:12px;backdrop-filter:opacity(.4);display:inline-block;margin-top:75px}.progress .indeterminate{position:absolute;background:#e3ba4b;height:5px;animation:indeterminate 1.4s infinite;animation-timing-function:linear}@keyframes indeterminate{0%{width:5%;left:-15%}to{width:100%;left:110%}}p{font-family:BlinkMacSystemFont,-apple-system,"Segoe UI",Roboto,Oxygen,Ubuntu,Cantarell,"Fira Sans","Droid Sans","Helvetica Neue",Helvetica,Arial,sans-serif;color: var(--crxText);margin-top: 24px;}</style>
  40. </div>
  41. `;
  42. } else if (node.nodeType === 1 && node.tagName === 'HEAD') {
  43. node.innerHTML = /*html*/`
  44. <link dtps="true" rel="shortcut icon" href="https://powerplus.app/favicon.png" type="image/png">
  45. <meta dtps="true" name="viewport" content="width=device-width, initial-scale=1">
  46. <meta dtps="true" charset="utf-8">
  47. <title dtps="true">Power+</title>
  48. <meta dtps="true" name="description" content="A better UI for Canvas LMS">
  49. <meta dtps="true" name="author" content="jottocraft">
  50. `;
  51. } else if (node.nodeType === 1 && node.tagName === 'SCRIPT' && (node.textContent && node.textContent.includes('"current_user"'))) {
  52. //Do nothing for node containing enviornment data for faster load times
  53. } else if (node.nodeType === 1 && node.getAttribute("dtps") != "true") {
  54. //Node is not added by dtps
  55. node.remove();
  56. }
  57. })
  58. })
  59. })
  60.  
  61. //Start MutationObserver
  62. observer.observe(document.documentElement, {
  63. childList: true,
  64. subtree: true
  65. });
  66.  
  67. //Get Power+ base URL
  68. var baseURL = "https://powerplus.app";
  69. if (window.localStorage.dtpsLoaderPref === "local") {
  70. baseURL = "http://localhost:2750";
  71. }
  72.  
  73. //Set DTPS loader parameters
  74. s = document.createElement("script");
  75. s.textContent = "window.dtpsPreLoader = true;window.dtpsBaseURL = '" + baseURL + "'";
  76. s.async = false;
  77. s.setAttribute("dtps", "true");
  78. document.documentElement.appendChild(s);
  79.  
  80. //Load jQuery
  81. s = document.createElement("script");
  82. s.src = "https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js";
  83. s.async = false;
  84. s.setAttribute("dtps", "true");
  85. document.documentElement.appendChild(s);
  86.  
  87. //Wait for page to load
  88. window.onload = function () {
  89. //Stop observer
  90. observer.disconnect();
  91.  
  92. //Determine LMS script to load
  93. var lmsScript = null;
  94. if (window.location.hostname.startsWith("dtechhs")) {
  95. lmsScript = "dtech";
  96. } else {
  97. lmsScript = "canvas";
  98. }
  99.  
  100. //Check for debugging LMS overrides
  101. if (window.localStorage.dtpsLMSOverride) lmsScript = window.localStorage.dtpsLMSOverride;
  102.  
  103. //Add script to DOM
  104. s = document.createElement("script");
  105. s.src = baseURL + "/scripts/lms/" + lmsScript + ".js";
  106. s.async = false;
  107. s.setAttribute("dtps", "true");
  108. document.documentElement.appendChild(s);
  109. s.onerror = function () {
  110. //Couldn't load debugging script, fallback to production
  111. if (window.localStorage.dtpsLoaderPref && (window.localStorage.dtpsLoaderPref !== "prod")) {
  112. console.log("[DTPS CHROME] Failed to load debugging script. Falling back to production.");
  113. window.localStorage.dtpsLoaderPref = "prod";
  114. window.location.reload();
  115. } else {
  116. document.getElementById("dtpsLoadingScreenBar").style.animationPlayState = "paused";
  117. document.getElementById("dtpsLoadingScreenStatus").innerText = "Could not load Power+. Please try again later.";
  118. }
  119. };
  120. }
  121. } else {
  122. var releaseType = null;
  123. if (window.localStorage.dtpsLoaderPref == "local") releaseType = "Power+ (local)";
  124.  
  125. const observer = new MutationObserver(mutations => {
  126. mutations.forEach(({ addedNodes }) => {
  127. addedNodes.forEach(node => {
  128. //Power+ button
  129. if (node.nodeType === 1 && node.id == "menu") {
  130. node.insertAdjacentHTML("beforeend", /*html*/`
  131. <li class="menu-item ic-app-header__menu-list-item ">
  132. <a id="global_nav_dtps_link" role="button" href="/power+/" class="ic-app-header__menu-list-link">
  133. <div class="menu-item-icon-container" aria-hidden="true">
  134. <svg xmlns="http://www.w3.org/2000/svg" class="ic-icon-svg ic-icon-svg--dtps" version="1.1" viewBox="0 0 48 63.999"><path d="m5.333 0c-2.946 0-5.333 2.4-5.333 5.333v48c0 2.933 2.388 5.333 5.333 5.333h5.333v5.333l6.667-5.333 6.667 5.333v-5.333h18.667c2.947 0 5.333-2.4 5.333-5.333v-2.667c0 2.933-2.387 5.333-5.333 5.333h-18.667v-5.333h-13.333v5.333h-4c-2.209 0-4-1.867-4-4 0-2.4 1.791-4 4-4h36c2.947 0 5.333-2.4 5.333-5.333v-37.333c0-2.933-2.387-5.333-5.333-5.333h-37.333z"/></svg>
  135. </div>
  136. <div class="menu-item__text">${releaseType || "Power+"}</div>
  137. </a>
  138. </li>
  139. `);
  140. }
  141. })
  142. })
  143. })
  144.  
  145. // Starts the monitoring
  146. observer.observe(document.documentElement, {
  147. childList: true,
  148. subtree: true
  149. })
  150. }
  151. })();