· 6 years ago · Sep 20, 2019, 05:24 PM
1/* Power+ v2.0.0 (beta)
2(c) 2018 - 2019 jottocraft
3https://github.com/jottocraft/dtps */
4
5//Basic global Power+ configuration. All global Power+ variables go under dtps
6var dtps = {
7 ver: 200,
8 readableVer: "v2.0.0 (beta)",
9 trackSuffix: " (beta)",
10 trackColor: "#ec9b06",
11 showLetters: false,
12 fullNames: false,
13 classes: [],
14 latestStream: [],
15 explorer: [],
16 embedded: window.location.href.includes("instructure.com"),
17 auth: {
18 accessToken: window.localStorage.accessToken ? window.localStorage.accessToken : undefined,
19 canvasURL: "https://dtechhs.instructure.com",
20 client_id: "146080000000000005",
21 uri: "https://powerplus.app/app",
22 scope: [
23 "url:GET|/api/v1/courses/:course_id/outcome_rollups",
24 "url:GET|/api/v1/users/:id",
25 "url:GET|/api/v1/users/:id/colors",
26 "url:GET|/api/v1/users/:id/dashboard_positions",
27 "url:GET|/api/v1/courses",
28 "url:GET|/api/v1/courses/:course_id/pages",
29 "url:GET|/api/v1/courses/:course_id/discussion_topics",
30 "url:GET|/api/v1/courses/:course_id/discussion_topics/:topic_id/view",
31 "url:GET|/api/v1/courses/:course_id/outcome_results",
32 "url:GET|/api/v1/courses/:course_id/assignment_groups",
33 "url:GET|/api/v1/courses/:course_id/modules",
34 "url:GET|/api/v1/courses/:course_id/front_page",
35 "url:GET|/api/v1/courses/:course_id/pages/:url",
36 "url:GET|/api/v1/outcomes/:id",
37 "url:GET|/api/v1/courses/:course_id/outcome_alignments",
38 "url:GET|/api/v1/announcements",
39 //not currently used in dtps, might be used later on
40 "url:GET|/api/v1/courses/:course_id/assignments",
41 "url:GET|/api/v1/courses/:course_id/assignment_groups/:assignment_group_id/assignments"
42 ]
43 },
44 chromaProfile: {
45 title: "Power+",
46 description: "Razer Chroma effects for Power+ (beta)",
47 author: "jottocraft",
48 domain: "dtps.js.org"
49 }
50};
51
52//alert all errors if dtpsDebug enabled (for chromebooks w/o devtools)
53window.onerror = function myErrorHandler(errorMsg, url, lineNumber) {
54 if (window.localStorage.dtpsDebug == "true") alert("[DTPS] ERROR: " + errorMsg + "\n\n" + url + ":" + lineNumber);
55 return false;
56}
57
58//Load a better version of jQuery as soon as possible because of Canvas's weird included version of jQuery that breaks a lot of important things
59if (dtps.embedded) jQuery.getScript("https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js")
60
61//Embedded Status Updates (load this before AND after rendering Power+ just in case something breaks in dtps.render)
62//REMOVED 8.13.2019
63//jQuery.getScript("https://fnxldqd4m5fr.statuspage.io/embed/script.js")
64
65//Shows the Power+ changelog modal
66dtps.changelog = function () {
67 fluid.cards.close(".card.focus")
68 fluid.modal(".card.changelog");
69};
70
71//get url paramaters
72dtps.getParams = function () {
73 var vars = {};
74 var parts = window.location.href.replace(/[?&]+([^=&]+)=([^&]*)/gi, function (m, key, value) {
75 vars[key] = value;
76 });
77 return vars;
78}
79
80//dtps.authenticate and dtps.webReq work together to ensure the user is authenticated
81//the dtps client must not be embedded for dtps.authenticate to work
82dtps.authenticate = function (cb) {
83 if (window.localStorage.accessToken) {
84 //manually defined access token
85 cb();
86 } else {
87 var params = dtps.getParams();
88 window.history.replaceState(null, null, window.location.pathname);
89 if (params.code && (params.state == window.localStorage.authState)) {
90 //get tokens
91 dtps.webReq("backend", "/login/oauth2/token?client_id=" + dtps.auth.client_id + "&redirect_uri=" + dtps.auth.uri + "&grant_type=authorization_code&code=" + params.code, function (res) {
92 var data = JSON.parse(res);
93 window.localStorage.setItem("access_token", data.access_token);
94 window.localStorage.setItem("refresh_token", data.refresh_token);
95 window.localStorage.setItem("expires_at", new Date(new Date().getTime() + (1000 * data.expires_in)));
96 dtps.accessToken = data.access_token;
97 cb();
98 });
99 } else {
100 if (window.localStorage.refresh_token) {
101 //refresh token exists
102 if (window.localStorage.expires_at > new Date().getTime()) {
103 //access token not expired
104 dtps.auth.accessToken = window.localStorage.access_token;
105 } else {
106 //get new access token
107 //COMING SOON
108 }
109 } else {
110 //show login screen
111 var state = "WinterCreek" + Math.floor(1000 + Math.random() * 9000);
112 window.localStorage.authState = state;
113 window.location.href = dtps.auth.canvasURL + "/login/oauth2/auth?client_id=" + dtps.auth.client_id + "&scope=" + dtps.auth.scope.join(" ") + "&purpose=" + encodeURI(navigator.platform) + "&response_type=code&state=" + state + "&redirect_uri=" + dtps.auth.uri
114
115 }
116 }
117 }
118}
119
120//Logs debugging messags to both the normal JS console and also Power+'s included debugging log
121dtps.log = function (msg) {
122 console.log("[DTPS" + dtps.trackSuffix + "] ", msg);
123 if (typeof msg !== "object") { try { jQuery("span.log").html(`<p>[DTPS` + dtps.trackSuffix + `] ` + msg + `</p>` + jQuery("span.log").html()); } catch (e) { } }
124}
125
126//Renders Power+ first run stuff
127dtps.firstrun = function () {
128 if (dtps.embedded) {
129 jQuery("body").append(`<div id="dtpsNativeAlert" class="ui-dialog ui-widget ui-widget-content ui-corner-all ui-dialog-buttons" tabindex="-1" aria-hidden="false" style="outline: 0px; z-index: 5000; height: auto; width: 500px; margin-top: 100px; top: 0; margin-left: calc(50% - 250px); display: block;">
130<div class="ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix"><span id="ui-id-1" class="ui-dialog-title" role="heading">Welcome to Power+` + dtps.trackSuffix + `</span><button onclick="jQuery('#dtpsNativeAlert').remove();jQuery('#dtpsNativeOverlay').remove();" class="ui-dialog-titlebar-close ui-corner-all"><span class="ui-icon ui-icon-closethick">close</span></button></div>
131<form id="new_course_form" class="bootstrap-form form-horizontal ui-dialog-content ui-widget-content" data-turn-into-dialog="{"width":500,"resizable":false}" style="width: auto; min-height: 0px; height: auto; display: block;" action="/courses" accept-charset="UTF-8" method="post" aria-expanded="true" scrolltop="0" scrollleft="0">
132<h5>` + dtps.readableVer + `</h5>
133<p>Things to keep in mind when testing Power+` + dtps.trackSuffix + `</p>
134<li>Power+` + dtps.trackSuffix + ` can't fully replace Canvas yet. Many Canvas features are not included in Power+` + dtps.trackSuffix + `.</li>
135<li>The Power+ gradebook is being temporarily disabled and will return later this year.</li>
136<li style="color: red;"><b>Power+` + dtps.trackSuffix + ` is still in development. There will be a lot of bugs and missing features, especially in the first month of the school year.</b></li>
137<li><b>Power+` + dtps.trackSuffix + ` may have bugs that cause it to display inaccurate information. Use Power+` + dtps.trackSuffix + ` at your own risk.</b></li>
138</form><div class="ui-dialog-buttonpane ui-widget-content ui-helper-clearfix"><div class="ui-dialog-buttonset"><button onclick="jQuery('#dtpsNativeAlert').remove();jQuery('#dtpsNativeOverlay').remove();" type="button" data-text-while-loading="Cancel" class="btn dialog_closer ui-button ui-widget ui-state-default ui-corner-all ui-button-text-only" role="button" aria-disabled="false"><span class="ui-button-text">Cancel</span></button><button onclick="localStorage.setItem('dtpsInstalled', 'true'); dtps.render();" type="button" data-text-while-loading="Loading Power+..." class="btn btn-primary button_type_submit ui-button ui-widget ui-state-default ui-corner-all ui-button-text-only" role="button" aria-disabled="false"><span class="ui-button-text">Continue</span></button></div></div></div>
139<div id="dtpsNativeOverlay" class="ui-widget-overlay" style="width: 100%; height: 100%; z-index: 500;"></div>`)
140 } else {
141 $("#welcomeVer").html(dtps.readableVer)
142 fluid.splash("#welcomeToDtps")
143 }
144};
145
146//Displays a native Canvas alert (cannot be used after Power+ is rendered / dtps.render)
147//On non-embedded clients, this displays a Fluid UI Alert
148dtps.nativeAlert = function (text, sub, loadingSplash) {
149 if (text == undefined) var text = "";
150 if (sub == undefined) var sub = "";
151 if (loadingSplash) {
152 if (dtps.embedded) {
153 jQuery("body").append(`<div id="dtpsNativeOverlay" class="ui-widget-overlay" style="width: 100%;height: 100%;z-index: 500;background: rgba(31, 31, 31, 0.89);"> <h1 style="position: fixed;font-size: 125px;background: -webkit-linear-gradient(rgb(255, 167, 0), rgb(255, 244, 0));-webkit-background-clip: text;-webkit-text-fill-color: transparent;font-weight: bolder;font-family: Product sans;text-align: center;top: 200px;width: 100%;">Power+</h1><h5 style="font-family: Product sans;font-size: 30px;color: gray;width: 100%;text-align: center;position: fixed;top: 375px;">` + sub + `</h5><div class="spinner" style="margin-top: 500px;"></div>
154<style>@font-face{font-family: 'Product sans'; font-display: auto; font-style: normal; font-weight: 400; src: url(https://fluid.js.org/product-sans.ttf) format('truetype');}.spinner { width: 40px; height: 40px; margin: 100px auto; background-color: gray; border-radius: 100%; -webkit-animation: sk-scaleout 1.0s infinite ease-in-out; animation: sk-scaleout 1.0s infinite ease-in-out; } @-webkit-keyframes sk-scaleout { 0% { -webkit-transform: scale(0) } 100% { -webkit-transform: scale(1.0); opacity: 0; } } @keyframes sk-scaleout { 0% { -webkit-transform: scale(0); transform: scale(0); } 100% { -webkit-transform: scale(1.0); transform: scale(1.0); opacity: 0; } }</style></div>`)
155 }
156 } else {
157 jQuery("body").append(`<div id="dtpsNativeAlert" class="ui-dialog ui-widget ui-widget-content ui-corner-all ui-dialog-buttons" tabindex="-1" aria-hidden="false" style="outline: 0px; z-index: 5000; height: auto; width: 500px; margin-top: 100px; top: 0; margin-left: calc(50% - 250px); display: block;">
158<div class="ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix"><span id="ui-id-1" class="ui-dialog-title" role="heading">Power+</span></div>
159<form id="new_course_form" class="bootstrap-form form-horizontal ui-dialog-content ui-widget-content" data-turn-into-dialog="{"width":500,"resizable":false}" style="width: auto; min-height: 0px; height: auto; display: block;" action="/courses" accept-charset="UTF-8" method="post" aria-expanded="true" scrolltop="0" scrollleft="0">
160<h4>` + text + `</h4>
161<p>` + sub + `</p>
162</form></div>
163<div id="dtpsNativeOverlay" class="ui-widget-overlay" style="width: 100%; height: 100%; z-index: 500;"></div>`)
164 }
165};
166
167//All Canvas & LMS data is sent through dtps.webReq
168dtps.requests = {};
169dtps.http = {};
170dtps.webReq = function (req, url, callback, q) {
171 if ((dtps.requests[url] == undefined) || url.includes("|")) {
172 //Use backend request instead of canvas request for standalone Power+
173 if ((req == "canvas") && !dtps.embedded) req = "backend"
174 //"Canvas" request type for making a GET request to the Canvas API
175 if (req == "canvas") {
176 dtps.log("Making DTPS Canvas web request")
177 dtps.http[url] = new XMLHttpRequest();
178 dtps.http[url].onreadystatechange = function () {
179 if (this.readyState == 4) {
180 if (this.status == 200) {
181 if (callback) callback(this.responseText, q);
182 dtps.requests[url] = this.responseText;
183 dtps.log("Returning DTPS data")
184 } else {
185 if (callback) callback(JSON.stringify({ error: this.status }), q);
186 dtps.requests[url] = JSON.stringify({ error: this.status });
187 dtps.log("DTPS webReq error" + this.status)
188 }
189 }
190 };
191 dtps.http[url].open("GET", url, true);
192 dtps.http[url].setRequestHeader("Accept", "application/json+canvas-string-ids")
193 dtps.http[url].send();
194 }
195 //"backend" request type for making a GET request to the Canvas API w/ jottocraft.com backend
196 if (req == "backend") {
197 dtps.http[url] = new XMLHttpRequest();
198 dtps.http[url].onreadystatechange = function () {
199 if (this.readyState == 4) {
200 if (this.status == 200) {
201 if (callback) callback(this.responseText, q);
202 dtps.requests[url] = this.responseText;
203 dtps.log("Returning DTPS data")
204 } else {
205 if (callback) callback(JSON.stringify({ error: this.status }), q);
206 dtps.requests[url] = JSON.stringify({ error: this.status });
207 dtps.log("DTPS webReq error" + this.status)
208 }
209 }
210 };
211 dtps.http[url].open("GET", "https://lms.jottocraft.com:2755" + url, true);
212 dtps.http[url].setRequestHeader("Accept", "application/json+canvas-string-ids");
213 dtps.http[url].setRequestHeader("dtps", "WinterCreek/" + dtps.ver);
214 dtps.http[url].setRequestHeader("Authorization", "Bearer " + dtps.auth.accessToken);
215 dtps.http[url].send();
216 }
217 //"canSUBMIT" request type for submitting assignments using the Canvas API (POST request)
218 if (req == "canSUBMIT") {
219 console.log("canSUBMIT")
220 dtps.http[url] = new XMLHttpRequest();
221 dtps.http[url].onreadystatechange = function () {
222 if (this.readyState == 4) {
223 if (this.status == 200) {
224 if (callback) callback(this.responseText, q);
225 dtps.requests[url] = this.responseText;
226 dtps.log("Returning DTPS data")
227 } else {
228 if (callback) callback(JSON.stringify({ error: this.status }), q);
229 dtps.requests[url] = JSON.stringify({ error: this.status });
230 dtps.log("DTPS webReq error" + this.status)
231 }
232 }
233 };
234 dtps.http[url].open("POST", url.split("|")[0], true);
235 dtps.http[url].setRequestHeader("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3")
236 dtps.http[url].setRequestHeader("Content-Type", "application/x-www-form-urlencoded")
237 dtps.http[url].setRequestHeader("Cache-Control", "max-age=0")
238 dtps.http[url].send(url.split("|")[1]);
239 }
240 } else {
241 if (callback) callback(dtps.requests[url], q);
242 }
243}
244
245//Calculates the class grade based on Outcomes results from Canvas
246dtps.computeClassGrade = function (num, renderSidebar) {
247 dtps.webReq("canvas", "/api/v1/courses/" + dtps.classes[num].id + "/outcome_rollups?user_ids[]=" + dtps.user.id + "&include[]=outcomes", function (resp, classNum) {
248 var data = JSON.parse(resp);
249 console.log(data);
250 var rollups = data.rollups[0];
251
252 //array of outcome averages sorted from highest to lowest
253 var rollupScores = rollups.scores.map(function (score) { return score.score; }).sort((a, b) => b - a);
254
255 //the highest value 75% of outcomes are greater than or equal to
256 //number75thresh is the percentage of assignments >= number75. number75thresh is always >= 0.75 && <= 1
257 var number75 = null;
258 var number75thresh = null;
259
260 //test values
261 var testValues = [3.5, 3, 2.5]
262
263 for (var i = 0; i < testValues.length; i++) {
264 //make sure a higher number for number75 isn't already found
265 if (number75 == null) {
266 //array of numbers equal to or greater than the testing value
267 var equalOrGreater = [];
268
269 //check every score to see if it is >= the test value
270 for (var ii = 0; ii < rollupScores.length; ii++) {
271 if (rollupScores[ii] >= testValues[i]) equalOrGreater.push(rollupScores[ii]);
272 }
273
274 //if at least 75% of the outcomes are >= test value, number75 is the test value
275 if ((equalOrGreater.length / rollupScores.length) >= 0.75) {
276 number75 = testValues[i];
277 number75thresh = (equalOrGreater.length / rollupScores.length);
278 }
279 }
280 }
281
282 //since rollupScores is sorted greatest to least, the last value is the smallest
283 var lowestValue = rollupScores[rollupScores.length - 1];
284
285 //get letter grade
286 var letter = "I";
287 if (rollupScores.length == 0) letter = "--";
288 if ((number75 >= 2.5) && (lowestValue >= 2)) letter = "C";
289 if ((number75 >= 3) && (lowestValue >= 2)) letter = "B-";
290 if ((number75 >= 3) && (lowestValue >= 2.25)) letter = "B";
291 if ((number75 >= 3) && (lowestValue >= 2.5)) letter = "B+";
292 if ((number75 >= 3.5) && (lowestValue >= 2.5)) letter = "A-";
293 if ((number75 >= 3.5) && (lowestValue >= 3)) letter = "A";
294
295 if (fluid.get('pref-calcGrades') !== "false") dtps.classes[classNum].letter = letter;
296
297 //store grade calculation variables to show them in the gradebook
298 dtps.classes[classNum].gradeCalc = {
299 lowestValue: lowestValue,
300 number75: (number75 !== null ? number75 : ""),
301 number75thresh: number75thresh,
302 //number75percent: (number75thresh-0.75)/(1-0.75),
303 //lowestValuePercent: (),
304 }
305
306 //if there are no outcomes, remember this so the grades tab can be hidden
307 if (!data.linked.outcomes.length) dtps.classes[classNum].noOutcomes = true;
308
309 dtps.computedClassGrades++;
310 if (renderSidebar) {
311 if (letter !== "--") {
312 if (letter.includes("A")) gpa.push(4)
313 if (letter.includes("B")) gpa.push(3)
314 if (letter.includes("C")) gpa.push(2)
315 if (letter.includes("I")) gpa.push(0)
316 if (letter == "I") score = 0;
317 if (letter == "C") score = 75;
318 if (letter == "B-") score = 80;
319 if (letter == "B") score = 85;
320 if (letter == "B+") score = 88;
321 if (letter == "A-") score = 90;
322 if (letter == "A") score = 95;
323 dtps.gradeHTML.push(`<div style="cursor: auto; background-color: var(--norm);" class="progressBar big ` + dtps.classes[classNum].col + `">
324 <div style="color: var(--dark);" class="progressLabel">` + dtps.classes[classNum].subject + `</div>
325 <div class="progress" style="background-color: var(--light); width: calc(` + score + `% - 300px);"></div></div>`)
326 }
327 }
328 if ((dtps.computedClassGrades == dtps.classes.length) && renderSidebar) {
329 dtps.showClasses(true);
330 var total = 0;
331 for (var i = 0; i < gpa.length; i++) {
332 total += gpa[i];
333 }
334 dtps.gpa = (total / gpa.length).toFixed(2);
335 }
336 }, num);
337}
338
339dtps.explore = function (path) {
340 dtps.webReq("canvas", path, function (resp) {
341 var data = JSON.parse(resp);
342 $("#explorerData").html(JSON.stringify(data, null, "\t"))
343 });
344}
345
346//Convert default Canvas colors to optimized Power+ filters based on hex values for default colors
347//Not all Canvas colors are listed here. Only existing Power+ colors and colors that look bad have optimized filters.
348dtps.filter = function (color) {
349 var filter = undefined;
350 if (color == "#009606") filter = "filter_3"
351 if (color == "#8D9900") filter = "filter_2"
352 if ((color == "#D97900") || (color == "#FD5D10")) filter = "filter_1"
353 if (color == "#F06291") filter = "filter_6"
354 if (color == "#FF2717") filter = "filter_0"
355 if (color == "#BD3C14") filter = "filter_7"
356 if (color == "#8F3E97") filter = "filter_5"
357 return filter;
358}
359
360//Get all JavaScript libraries
361dtps.JS = function (cb) {
362 if (dtps.embedded) {
363 jQuery.getScript('https://dtps.js.org/fluid.js');
364 jQuery.getScript("https://unpkg.com/sweetalert/dist/sweetalert.min.js")
365 jQuery.getScript("https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.22.2/moment.min.js", function () {
366 jQuery.getScript("https://cdnjs.cloudflare.com/ajax/libs/fullcalendar/3.9.0/fullcalendar.min.js")
367 })
368 jQuery.getScript('https://cdnjs.cloudflare.com/ajax/libs/fuse.js/3.3.0/fuse.min.js');
369
370 jQuery.getScript("https://www.googletagmanager.com/gtag/js?id=UA-105685403-3");
371
372 jQuery.getScript("https://cdn.jottocraft.com/tinycolor.js", cb);
373 } else {
374 cb();
375 }
376}
377
378//Starts Power+, and renders if running a non-embedded client
379dtps.init = function () {
380 dtps.log("Starting DTPS " + dtps.readableVer + "...");
381
382 //add basic explorer items
383 dtps.explorer.push({ name: "/users/self", path: "/api/v1/users/self" });
384 dtps.explorer.push({ name: "/users/self/dashboard_positions", path: "/api/v1/users/self/dashboard_positions" });
385 dtps.explorer.push({ name: "/users/self/colors", path: "/api/v1/users/self/colors" });
386 dtps.explorer.push({ name: "/courses", path: "/api/v1/courses?include[]=total_scores&include[]=public_description&include[]=favorites&include[]=total_students&include[]=account&include[]=teachers&include[]=course_image&include[]=syllabus_body&include[]=tabs" });
387
388 dtps.webReq("canvas", "/api/v1/users/self", function (user) {
389 fluidThemes = [["rainbow"]];
390 fluidAutoLoad = false;
391
392 document.addEventListener("fluidTheme", function (data) {
393 if (dtps.oldTheme !== data.detail) {
394 //theme change
395 console.log("[DTPS] Fluid UI Theme Change")
396 dtps.oldTheme = data.detail;
397 var next = window.getComputedStyle(document.getElementsByClassName("background")[0]).getPropertyValue("--grad")
398 if (dtps.selectedClass !== "dash") next = "linear-gradient(to bottom right, " + window.getComputedStyle(document.getElementsByClassName("background")[0]).getPropertyValue($("body").hasClass("midnight") ? "--dark" : "--light") + ", " + ($("body").hasClass("dark") ? "var(--background, #252525)" : "var(--background, white)") + ")"
399 if (dtps.selectedClass !== "dash") $('body').removeClass('dashboard');
400 $(".background").css("background", next)
401 dtps.chroma();
402 }
403 })
404
405 dtps.user = JSON.parse(user);
406 sudoers = ["669", "672", "209"]
407 if (sudoers.includes(dtps.user.id)) { jQuery("body").addClass("sudo"); dtps.log("Sudo mode enabled"); }
408 marketers = ["669", "672", "209"]
409 if (marketers.includes(dtps.user.id)) { jQuery("body").addClass("marketer"); dtps.log("Promotional marketing mode enabled"); }
410 contributors = ["669"]
411 if (contributors.includes(dtps.user.id)) { jQuery("body").addClass("contributor"); }
412 if (dtps.user.id == "669") { jQuery("body").addClass("dev"); dtps.log("Dev mode enabled"); }
413 if ((dtps.trackSuffix !== "") && (dtps.trackSuffix !== "GM")) jQuery("body").addClass("prerelease");
414 if (sudoers.includes(dtps.user.id)) jQuery("body").addClass("prerelease");
415 $ = jQuery;
416 var min = new Date().getMinutes();
417 if (String(min).length < 2) min = Number("0" + String(min))
418 var time = Number(String(new Date().getHours()) + String(min))
419 dtps.period = null;
420 if ((new Date().getDay() > 0) && (new Date().getDay() < 6)) {
421 //Weekday
422 if (new Date().getDay() == 3) {
423 //Wednesday
424 if ((time > 0844) && (time < 0911)) dtps.period = 7;
425 if ((time > 0912) && (time < 1003)) dtps.period = 1;
426 if ((time > 1004) && (time < 1056)) dtps.period = 2;
427 if ((time > 1057) && (time < 1149)) dtps.period = 3;
428 if ((time > 1220) && (time < 1312)) dtps.period = 4;
429 if ((time > 1313) && (time < 1405)) dtps.period = 5;
430 if ((time > 1406) && (time < 1458)) dtps.period = 6;
431 } else {
432 if (new Date().getDay() !== 4) {
433 //M, TU, F
434 if ((time > 0844) && (time < 0916)) dtps.period = 7;
435 if ((time > 0917) && (time < 1013)) dtps.period = 1;
436 if ((time > 1022) && (time < 1118)) dtps.period = 2;
437 if ((time > 1119) && (time < 1215)) dtps.period = 3;
438 if ((time > 1246) && (time < 1342)) dtps.period = 4;
439 if ((time > 1343) && (time < 1439)) dtps.period = 5;
440 if ((time > 1440) && (time < 1536)) dtps.period = 6;
441 }
442 }
443 }
444 if (dtps.period && (String(localStorage.dtpsSchedule).startsWith("{"))) {
445 dtps.currentClass = JSON.parse(localStorage.dtpsSchedule)[dtps.period];
446 if (dtps.currentClass) {
447 $("#headText").html(("Period " + dtps.period).replace("Period 7", "@d.tech"));
448 }
449 }
450 if (!dtps.currentClass) {
451 dtps.selectedClass = "dash";
452 dtps.selectedContent = "stream";
453 }
454 dtps.masterContent = "assignments";
455 if (!dtps.embedded) { dtps.renderLite(); dtps.showClasses(); }
456
457 dtps.shouldRender = true;
458 dtps.showChangelog = false;
459 dtps.first = false;
460 if (window.localStorage.dtpsInstalled !== "true") {
461 if (!dtps.embedded) dtps.shouldRender = false;
462 dtps.first = true;
463 }
464 if (dtps.first && !dtps.embedded) dtps.firstrun();
465 if (Number(window.localStorage.dtps) < dtps.ver) {
466 dtps.log("New release")
467 dtps.showChangelog = true;
468 if (dtps.shouldRender) dtps.nativeAlert("Loading...", "Updating to Power+ " + dtps.readableVer, true);
469 }
470 if (dtps.shouldRender && !dtps.showChangelog) {
471 dtps.nativeAlert("Loading...", undefined, true);
472 }
473 dtps.JS(function () {
474
475 window.dataLayer = window.dataLayer || [];
476 function gtag() { dataLayer.push(arguments); }
477 var configTmp = {
478 'page_title': 'portal',
479 'page_path': '/portal',
480 'anonymize_ip': true
481 }
482 if (dtps.trackSuffix !== "") {
483 configTmp = {
484 //'page_title' : 'prerelease',
485 //'page_path': '/prerelease',
486 'page_title': 'canvasdp',
487 'page_path': '/canvasdp',
488 'anonymize_ip': true
489 }
490 }
491 gtag('config', 'UA-105685403-3', configTmp);
492
493 dtps.webReq("canvas", "/api/v1/users/self/colors", function (colorsResp) {
494 dtps.webReq("canvas", "/api/v1/users/self/dashboard_positions", function (dashboardResp) {
495 dtps.webReq("canvas", "/api/v1/courses?include[]=total_scores&include[]=public_description&include[]=favorites&include[]=total_students&include[]=account&include[]=teachers&include[]=course_image&include[]=syllabus_body&include[]=tabs", function (resp) {
496 dtps.classesReady = 0;
497 dtps.colorCSS = [];
498 dtps.gpa = null;
499 gpa = [];
500 var colors = JSON.parse(colorsResp);
501 var dashboard = JSON.parse(dashboardResp);
502 var data = JSON.parse(resp);
503 dtps.gradeHTML = [];
504 data.sort(function (a, b) {
505 var keyA = dashboard.dashboard_positions["course_" + a.id],
506 keyB = dashboard.dashboard_positions["course_" + b.id];
507 if (keyA < keyB) return -1;
508 if (keyA > keyB) return 1;
509 return 0;
510 });
511 for (var i = 0; i < data.length; i++) {
512 var name = data[i].name;
513 var subject = null;
514 var icon = null;
515 if (name.includes("Physics")) { var subject = "Physics"; var icon = "experiment"; }; if (name.includes("English")) { var subject = "English"; var icon = "library_books" }; if (name.includes("Physical Education")) { var subject = "PE"; var icon = "directions_run"; };
516 if (name.includes("Prototyping")) { var subject = "Prototyping"; var icon = "drive_file_rename_outline"; }; if (name.includes("Algebra")) { var subject = "Algebra"; }; if (name.includes("Algebra 2")) { var subject = "Algebra 2"; };
517 if (name.includes("Spanish")) { var subject = "Spanish" }; if (name.includes("@") || name.includes("dtech")) { var subject = "@d.tech" }; if (name.includes("Environmental")) { var subject = "Environmental Science" };
518 if (name.includes("Robotics")) { var subject = "Robotics" }; if (name.includes("Chemistry")) { var subject = "Chemistry" }; if (name.includes("Biology")) { var subject = "Biology" }; if (name.includes("Engineering")) { var subject = "Engineering" }; if (name.includes("Geometry")) { var subject = "Geometry" };
519 if (name.includes("Photography")) { var subject = "Photography" }; if (name.includes("World History")) { var subject = "World History" }; if (name.includes("U.S. History")) { var subject = "US History" };
520 if (name.includes("Calculus")) { var subject = "Calculus" }; if (name.toUpperCase().includes("CALCULUS") && name.toUpperCase().includes("PRE")) { var subject = "Precalculus" }; if (name.includes("Statistics")) { var subject = "Advanced Statistics" };
521 if (name.includes("Model United Nations")) { var subject = "Model UN" }; if (name.includes("Government")) { var subject = "Government" }; if (name.includes("Economics")) { var subject = "Economics" };
522 if (data[i].name !== data[i].course_code) {
523 //Canvas class manually renamed
524 subject = null;
525 }
526 if (subject == null) var subject = name;
527 var filter = "filter_" + colors.custom_colors["course_" + data[i].id].toLowerCase().replace("#", "");
528 //Suport Power+ v1.x.x Colors by detecting if the user selects a native canvas color
529 if (dtps.filter(colors.custom_colors["course_" + data[i].id])) filter = dtps.filter(colors.custom_colors["course_" + data[i].id]);
530 pagesTab = false;
531 for (var ii = 0; ii < data[i].tabs.length; ii++) {
532 if (data[i].tabs[ii].id == "pages") pagesTab = true;
533 }
534 dtps.classes.push({
535 name: name,
536 subject: subject,
537 description: data[i].public_description,
538 totalStudents: data[i].total_students,
539 favorite: data[i].is_favorite,
540 defaultView: data[i].default_view,
541 icon: icon,
542 col: filter,
543 pagesTab: pagesTab,
544 syllabus: data[i].syllabus_body,
545 norm: colors.custom_colors["course_" + data[i].id],
546 light: tinycolor(colors.custom_colors["course_" + data[i].id]).brighten(20).toHexString(),
547 dark: tinycolor(colors.custom_colors["course_" + data[i].id]).darken(20).toHexString(),
548 isBright: (dtps.filter(colors.custom_colors["course_" + data[i].id]) ? false : !tinycolor(colors.custom_colors["course_" + data[i].id]).isDark()),
549 id: data[i].id,
550 //collection of data used to calculate a letter grade
551 grades: {},
552 grade: (data[i].enrollments[0].computed_current_score ? data[i].enrollments[0].computed_current_score : "--"),
553 letter: (data[i].enrollments[0].computed_current_grade ? data[i].enrollments[0].computed_current_grade : (fluid.get("pref-calcGrades") !== "false" ? false : "--")),
554 num: i,
555 tmp: {},
556 image: data[i].image_download_url,
557 teacher: {
558 name: data[i].teachers[0].display_name,
559 prof: data[i].teachers[0].avatar_image_url
560 }
561 })
562 if (!dtps.filter(colors.custom_colors["course_" + data[i].id])) {
563 dtps.colorCSS.push(`\n.` + dtps.classes[i].col + ` {
564 --light: ` + dtps.classes[i].light + `;
565 --norm: ` + dtps.classes[i].norm + `;
566 --dark: ` + dtps.classes[i].dark + `;
567 --filterText: ` + (dtps.classes[i].isBright ? dtps.classes[i].dark : "white") + `;
568 --grad: linear-gradient(to bottom right, ` + dtps.classes[i].light + `, ` + dtps.classes[i].dark + `);;
569}`);
570 }
571 dtps.computedClassGrades = 0;
572 if (fluid.get("pref-calcGrades") !== "false") dtps.computeClassGrade(i, true);
573 dtps.classStream(i, true);
574 if (dtps.currentClass == data[i].id) {
575 dtps.selectedClass = i;
576 dtps.selectedContent = "stream";
577 dtps.chroma();
578 dtps.classStream(i);
579 }
580 }
581 dtps.log("Grades loaded: ", dtps.classes);
582
583 if (dtps.shouldRender) dtps.render();
584 if (dtps.first && dtps.embedded) dtps.firstrun();
585 });
586 });
587 });
588 });
589 });
590
591}
592
593//Checks if all classes have loaded to determine if the dashboard is still loading
594dtps.readyInterval = "n/a";
595dtps.checkReady = function (num) {
596 dtps.log(num + " reporting as READY total of " + dtps.classesReady);
597 if ((dtps.selectedClass == "dash") && (dtps.classesReady == dtps.classes.length)) {
598 dtps.log("All classes ready, loading master stream");
599 dtps.masterStream(true);
600 } else {
601 if ((dtps.selectedClass == "dash") && (dtps.classesReady < dtps.classes.length)) {
602 dtps.masterStream();
603 }
604 }
605}
606
607//Loads the list of pages for a class
608dtps.loadPages = function (num) {
609 if ((dtps.selectedClass == num) && (dtps.selectedContent == "pages")) {
610 jQuery(".sidebar").html(`
611<div class="classDivider"></div>
612<div class="spinner"></div>
613`);
614 jQuery(".classContent").html("");
615 }
616 dtps.webReq("canvas", "/api/v1/courses/" + dtps.classes[num].id + "/pages", function (resp) {
617 var data = JSON.parse(resp);
618 if (data.error) {
619 jQuery(".sidebar").html(`<div onclick="dtps.selectedContent = 'stream'; dtps.chroma(); dtps.classStream(dtps.selectedClass);" class="class back">
620 <div class="name">Classes</div>
621 <div class="grade"><i class="material-icons">keyboard_arrow_left</i></div>
622 </div>
623 <div class="classDivider"></div>
624 <h5 style="text-align: center; font-weight: bold;margin-left: -10px;">No pages found</h5><p style="text-align: center;margin-left: -10px;">This class doesn't have any pages</p>`)
625 } else {
626 dtps.classes[num].pages = [];
627 dtps.classes[num].pagelist = [];
628 for (var i = 0; i < data.length; i++) {
629 dtps.classes[num].pages.push({
630 id: data[i].page_id,
631 title: data[i].title,
632 content: "",
633 num: i
634 });
635 dtps.classes[num].pagelist.push(`
636 <div onclick="dtps.selectedPage = ` + data[i].page_id + `" class="class ` + data[i].page_id + `">
637 <div class="name">` + data[i].title + `</div>
638 <div class="grade"><i class="material-icons">notes</i></div>
639 </div>
640 `);
641 }
642 if ((dtps.selectedClass == num) && (dtps.selectedContent == "pages")) {
643 jQuery(".sidebar").html(`<div onclick="dtps.selectedContent = 'stream'; dtps.chroma(); dtps.classStream(dtps.selectedClass);" class="class back">
644 <div class="name">Classes</div>
645 <div class="grade"><i class="material-icons">keyboard_arrow_left</i></div>
646 </div>
647 <div class="classDivider"></div>
648 ` + dtps.classes[num].pagelist.join(""))
649 }
650
651 $(".class:not(.back)").click(function (event) {
652 $(this).siblings().removeClass("active")
653 $(this).addClass("active")
654 dtps.getPage(dtps.classes[dtps.selectedClass].id, dtps.selectedPage);
655 });
656 }
657 });
658}
659
660//Loads the list of discussion topics for a class
661dtps.loadTopics = function (num) {
662 if ((dtps.selectedClass == num) && (dtps.selectedContent == "discuss")) {
663 jQuery(".sidebar").html(`
664<div class="classDivider"></div>
665<div class="spinner"></div>
666`);
667 jQuery(".classContent").html("");
668 }
669 dtps.webReq("canvas", "/api/v1/courses/" + dtps.classes[num].id + "/discussion_topics", function (resp) {
670 var data = JSON.parse(resp);
671 if (data.error || (data.length == 0)) {
672 jQuery(".sidebar").html(`<div onclick="dtps.selectedContent = 'stream'; dtps.chroma(); dtps.classStream(dtps.selectedClass);" class="class back">
673 <div class="name">Classes</div>
674 <div class="grade"><i class="material-icons">keyboard_arrow_left</i></div>
675 </div>
676 <div onclick="window.open('/courses/` + dtps.classes[num].id + `/discussion_topics/new')" class="class back">
677 <div class="name">New discussion</div>
678 <div class="grade"><i class="material-icons">add</i></div>
679 </div>
680 <div class="classDivider"></div>
681 <h5 style="text-align: center; font-weight: bold; margin-left: -10px;">No discussions found</h5><p style="text-align: center;margin-left: -10px;">There aren't any discussion topics</p>`)
682 } else {
683 dtps.classes[num].topics = [];
684 dtps.classes[num].topicList = [];
685 for (var i = 0; i < data.length; i++) {
686 dtps.classes[num].topics[data[i].id] = {
687 id: data[i].id,
688 title: data[i].title,
689 content: data[i].message,
690 author: {
691 name: data[i].author.display_name,
692 prof: data[i].author.avatar_image_url
693 },
694 locked: data[i].locked_for_user,
695 requirePost: data[i].require_initial_post,
696 num: i
697 };
698 dtps.classes[num].topicList.push(`
699 <div onclick="dtps.selectedPage = ` + data[i].id + `" class="class ` + data[i].id + `">
700 <div class="name">` + data[i].title + `</div>
701 <div class="grade"><i style="font-family: 'Material Icons Extended';" class="material-icons">` + (data[i].locked_for_user ? "lock_outline" : "chat_bubble_outline") + `</i></div>
702 </div>
703 `);
704 }
705 if ((dtps.selectedClass == num) && (dtps.selectedContent == "discuss")) {
706 jQuery(".sidebar").html(`<div onclick="dtps.selectedContent = 'stream'; dtps.chroma(); dtps.classStream(dtps.selectedClass);" class="class back">
707 <div class="name">Classes</div>
708 <div class="grade"><i class="material-icons">keyboard_arrow_left</i></div>
709 </div>
710 <div onclick="window.open('/courses/` + dtps.classes[num].id + `/discussion_topics/new')" class="class back">
711 <div class="name">New discussion</div>
712 <div class="grade"><i class="material-icons">add</i></div>
713 </div>
714 <div class="classDivider"></div>
715 ` + dtps.classes[num].topicList.join(""))
716 }
717
718 $(".class:not(.back)").click(function (event) {
719 $(this).siblings().removeClass("active")
720 $(this).addClass("active")
721 dtps.getTopic(dtps.selectedClass, dtps.selectedPage);
722 });
723 }
724 });
725}
726
727//Gets and displays a discussion
728dtps.getTopic = function (num, id, fromModuleStream) {
729 var classID = dtps.classes[num].id
730 if (id == undefined) var id = dtps.selectedPage;
731 if ((dtps.classes[dtps.selectedClass].id == classID) && ((dtps.selectedContent == "discuss") || fromModuleStream)) {
732 jQuery(".classContent").html(`<div class="spinner"></div>`);
733 }
734 if (dtps.classes[num].topics[id].locked) {
735 alert(dtps.classes[num].topics[id].content);
736 jQuery(".classContent").html("");
737 } else {
738 var spinnerTmp = true;
739 dtps.webReq("canvas", "/api/v1/courses/" + classID + "/discussion_topics/" + id + "/view", function (resp) {
740 var data = JSON.parse(resp);
741 if ((dtps.classes[dtps.selectedClass].id == classID) && ((dtps.selectedContent == "discuss") || fromModuleStream)) {
742 $(".cacaoBar .tab.active span").html(dtps.classes[num].topics[id].title)
743 var blob = new Blob([`<base target="_blank" /> <link type="text/css" rel="stylesheet" href="https://cdn.jottocraft.com/CanvasCSS.css" media="screen,projection"/>
744 <style>body {background-color: ` + getComputedStyle($(".card.details")[0]).getPropertyValue("--cards") + `; color: ` + getComputedStyle($(".card.details")[0]).getPropertyValue("--text") + `}</style>` + dtps.classes[num].topics[id].content], { type: 'text/html' });
745 var newurl = window.URL.createObjectURL(blob);
746 var people = {};
747 (data.participants ? data.participants.forEach((person) => people[person.id] = person) : "");
748 jQuery(".classContent").html((fromModuleStream ? `<div class="acrylicMaterial" onclick="dtps.moduleStream(dtps.selectedClass)" style="line-height: 40px;display: inline-block;border-radius: 20px;margin: 82px 0px 0px 82px; cursor: pointer;">
749 <div style="font-size: 16px;display: inline-block;vertical-align: middle;margin: 0px 20px;"><i style="vertical-align: middle;" class="material-icons">keyboard_arrow_left</i> Back</div></div>` : "") + `
750
751 ` + (dtps.classes[num].topics[id].requirePost && !data.view ? `<div class="acrylicMaterial" style="border-radius: 20px;display: inline-block;margin: 10px 82px;margin-top: 25px;height: 40px;line-height: 40px;padding: 0px 20px; margin-right: -70px;">
752 Replies are only visible to those who have posted at least one reply</div>` : "") + `
753
754 <div class="acrylicMaterial" style="line-height: 40px;display: inline-block;border-radius: 20px;margin: 82px 0px 0px 82px;">
755 <div style="font-size: 16px;display: inline-block;vertical-align: middle;margin: 0px 20px;">Discussions (beta)</div></div>
756
757 <div class="card" style="margin-top: 20px;">
758 <h4 style="font-weight: bold; margin: 0px; margin-top: 10px;">` + dtps.classes[num].topics[id].title + `</h4>
759 <img style="width: 25px; vertical-align: middle; border-radius: 50%;" src="` + dtps.classes[num].topics[id].author.prof + `" />
760 <h5 style="display: inline-block; vertical-align: middle;color: var(--lightText); font-size: 22px;">` + dtps.classes[num].topics[id].author.name + `</h5>
761
762 <iframe id="classPageIframe" onload="dtps.iframeLoad('classPageIframe')" style="margin: 10px 0px; width: 100%; border: none; outline: none;" src="` + newurl + `" />
763 </div>
764
765 ` + (data.view ? data.view.map(function (comment) {
766 return `<div class="card" style="padding: 20px;">
767 <img style="width: 25px; vertical-align: middle; border-radius: 50%;" src="` + people[comment.user_id].avatar_image_url + `" />
768 <h5 style="display: inline-block; vertical-align: middle;">` + people[comment.user_id].display_name + `
769 ` + (comment.rating_sum ? `<div style="color: var(--secText);margin: 2px 0px;display: inline-block;font-size: 18px;line-height: 18px; position: absolute; top: 30px; right: 20px;"><i class="material-icons" style=" margin-right: 0px; vertical-align: middle; ">thumb_up_alt</i> ` + comment.rating_sum + `</div>` : "") + `</h5>
770 ` + comment.message + `
771 ` + (comment.replies ? comment.replies.map(function (reply) {
772 return `<div style="padding: 10px 20px;background-color: var(--elements);border-radius: 10px; margin: 20px 0px;"><h6>` + people[reply.user_id].display_name + `
773 ` + (reply.rating_sum ? `<div style="color: var(--secText);margin: 2px 0px;display: inline-block;font-size: 18px;line-height: 18px; float:right;margin-top: -5px;"><i class="material-icons" style=" margin-right: 0px; vertical-align: middle; ">thumb_up_alt</i> ` + reply.rating_sum + `</div>` : "") + `</h6>
774 <p>` + reply.message + `</p>
775 ` + (reply.replies ? reply.replies.map(function (replyLayer) {
776 return `<div style="padding: 10px 20px;background-color: var(--darker);border-radius: 10px; margin: 20px 0px; margin-left: 20px;"><h6>` + people[replyLayer.user_id].display_name + `
777 ` + (replyLayer.rating_sum ? `<div style="color: var(--secText);margin: 2px 0px;display: inline-block;font-size: 18px;line-height: 18px; float:right;margin-top: -5px;"><i class="material-icons" style=" margin-right: 0px; vertical-align: middle; ">thumb_up_alt</i> ` + replyLayer.rating_sum + `</div>` : "") + `</h6>
778 <p>` + replyLayer.message + `</p>
779 ` + (replyLayer.replies ? replyLayer.replies.map(function (replyLayerLayer) {
780 return `<div style="padding: 10px 20px;background-color: var(--darkest);border-radius: 10px; margin: 20px 0px; margin-left: 20px;"><h6>` + people[replyLayerLayer.user_id].display_name + `
781 ` + (replyLayerLayer.rating_sum ? `<div style="color: var(--secText);margin: 2px 0px;display: inline-block;font-size: 18px;line-height: 18px; float:right;margin-top: -5px;"><i class="material-icons" style=" margin-right: 0px; vertical-align: middle; ">thumb_up_alt</i> ` + replyLayerLayer.rating_sum + `</div>` : "") + `</h6>
782 <p>` + replyLayerLayer.message + `</p></div>`
783 }).join("") : "") + `</div>`
784 }).join("") : "") + `
785 </div>`
786 }).join("") : "") + `
787 </div>`
788 }).join("") : "") + `
789 `);
790 }
791 });
792 }
793}
794
795//12 hour time formatter from stackoverflow
796dtps.ampm = function (date) {
797 var hours = date.getHours();
798 var minutes = date.getMinutes();
799 var ampm = hours >= 12 ? 'pm' : 'am';
800 hours = hours % 12;
801 hours = hours ? hours : 12; // the hour '0' should be '12'
802 minutes = minutes < 10 ? '0' + minutes : minutes;
803 var strTime = hours + ':' + minutes + ' ' + ampm;
804 return strTime;
805}
806
807//Fetches assignment data for a class
808dtps.classStream = function (num, renderOv) {
809 dtps.log("Fetching assignments for class " + num)
810 if (!renderOv) dtps.showClasses();
811 if ((dtps.selectedClass == num) && (dtps.selectedContent == "stream")) {
812 if (!renderOv) {
813 jQuery(".classContent").html(`
814 <div class="spinner"></div>
815 `);
816 }
817 }
818 var allData = [];
819 var total = null;
820 dtps.webReq("canvas", "/api/v1/courses/" + dtps.classes[num].id + "/outcome_results?user_ids[]=" + dtps.user.id, function (respp) {
821 dtps.webReq("canvas", "/api/v1/courses/" + dtps.classes[num].id + "/assignment_groups?include[]=assignments&include[]=submissions&include[]=submission", function (resp) {
822 var data = JSON.parse(resp);
823 var outcomes = JSON.parse(respp).outcome_results;
824 dtps.classes[num].stream = [];
825 dtps.classes[num].rawStream = data;
826 dtps.classes[num].streamitems = [];
827 dtps.classes[num].weights = [];
828
829 for (var i = 0; i < data.length; i++) {
830 dtps.classes[num].weights.push({ weight: data[i].name + " (" + ((data.length == 1) && (data[i].group_weight == 0) ? 100 : data[i].group_weight) + "%)", assignments: [], possiblePoints: 0, earnedPoints: 0, icon: `<i class="material-icons">category</i> ` });
831 if (dtps.classes[num].weights[i].weight.toUpperCase().includes("SUCCESS") || dtps.classes[num].weights[i].weight.includes("SS")) { dtps.classes[num].weights[i].icon = `<i class="material-icons">star_border</i> `; dtps.classes[num].weights[i].weight = "Success Skills (" + dtps.classes[num].weights[i].weight.match(/\(([^)]+)\)/)[1] + ")"; }
832 if (dtps.classes[num].weights[i].weight.toUpperCase().includes("COMPREHENSION") || dtps.classes[num].weights[i].weight.includes("CC")) { dtps.classes[num].weights[i].icon = `<i class="material-icons">done</i> `; dtps.classes[num].weights[i].weight = "Comprehension Checks (" + dtps.classes[num].weights[i].weight.match(/\(([^)]+)\)/)[1] + ")"; }
833 if (dtps.classes[num].weights[i].weight.toUpperCase().includes("PERFORMANCE") || dtps.classes[num].weights[i].weight.includes("PT") || dtps.classes[num].weights[i].weight.includes("UE") || dtps.classes[num].weights[i].weight.includes("Exam")) { dtps.classes[num].weights[i].icon = `<i class="material-icons">assessment</i> `; dtps.classes[num].weights[i].weight = "Performance Tasks (" + dtps.classes[num].weights[i].weight.match(/\(([^)]+)\)/)[1] + ")"; }
834 for (var ii = 0; ii < data[i].assignments.length; ii++) {
835 dtps.classes[num].stream.push({
836 id: data[i].assignments[ii].id,
837 title: data[i].assignments[ii].name,
838 due: (data[i].assignments[ii].due_at ? new Date(data[i].assignments[ii].due_at).toDateString().slice(0, -5) + ", " + dtps.ampm(new Date(data[i].assignments[ii].due_at)) : ""),
839 dueDate: data[i].assignments[ii].due_at,
840 url: data[i].assignments[ii].html_url,
841 types: data[i].assignments[ii].submission_types,
842 col: dtps.classes[num].col,
843 turnedIn: (data[i].assignments[ii].submission !== undefined ? (data[i].assignments[ii].submission.submission_type !== null) : false),
844 hasSubmissions: data[i].assignments[ii].has_submitted_submissions,
845 class: num,
846 subject: dtps.classes[num].subject,
847 streamItem: dtps.classes[num].stream.length - 1,
848 weight: dtps.classes[num].weights[i].weight.replace(/ *\([^)]*\) */g, ""),
849 weightIcon: dtps.classes[num].weights[i].icon,
850 uniqueWeight: data.length > 1,
851 published: new Date(data[i].assignments[ii].created_at).toDateString(),
852 outcomes: (data[i].assignments[ii].rubric ? data[i].assignments[ii].rubric.map(function (key) { return key.description }) : undefined),
853 outcomeIDs: (data[i].assignments[ii].rubric ? data[i].assignments[ii].rubric.map(function (key) { return key.outcome_id }) : undefined),
854 locksAt: data[i].assignments[ii].lock_at,
855 unlocksAt: data[i].assignments[ii].unlock_at,
856 locked: data[i].assignments[ii].locked_for_user,
857 lockedReason: data[i].assignments[ii].lock_explanation,
858 submissions: data[i].assignments[ii].submission.preview_url,
859 body: data[i].assignments[ii].description,
860 rubric: data[i].assignments[ii].rubric,
861 rubricItems: [],
862 submissionTypes: data[i].assignments[ii].submission_types,
863 worth: data[i].assignments[ii].points_possible
864 });
865 if (data[i].assignments[ii].rubric) {
866 for (var iii = 0; iii < data[i].assignments[ii].rubric.length; iii++) {
867 dtps.classes[num].stream[dtps.classes[num].stream.length - 1].rubricItems.push(data[i].assignments[ii].rubric[iii].outcome_id);
868 if (data[i].assignments[ii].rubric[iii].ratings) {
869 data[i].assignments[ii].rubric[iii].ratingItems = [];
870 for (var iiii = 0; iiii < data[i].assignments[ii].rubric[iii].ratings.length; iiii++) {
871 if (data[i].assignments[ii].rubric[iii].ratings[iiii].description.toUpperCase().includes("EMERGING")) data[i].assignments[ii].rubric[iii].ratings[iiii].name = "Emerging";
872 if (data[i].assignments[ii].rubric[iii].ratings[iiii].description.toUpperCase().includes("DEVELOPING")) data[i].assignments[ii].rubric[iii].ratings[iiii].name = "Developing";
873 if (data[i].assignments[ii].rubric[iii].ratings[iiii].description.toUpperCase().includes("PROFICIENT")) data[i].assignments[ii].rubric[iii].ratings[iiii].name = "Proficient";
874 if (data[i].assignments[ii].rubric[iii].ratings[iiii].description.toUpperCase().includes("PIONEERING")) data[i].assignments[ii].rubric[iii].ratings[iiii].name = "Pioneering";
875 if (data[i].assignments[ii].rubric[iii].ratings[iiii].points == 1) data[i].assignments[ii].rubric[iii].ratings[iiii].color = "#b90000";
876 if (data[i].assignments[ii].rubric[iii].ratings[iiii].points == 2) data[i].assignments[ii].rubric[iii].ratings[iiii].color = "#cc8400";
877 if (data[i].assignments[ii].rubric[iii].ratings[iiii].points == 3) data[i].assignments[ii].rubric[iii].ratings[iiii].color = "#b5b500";
878 if (data[i].assignments[ii].rubric[iii].ratings[iiii].points == 4) data[i].assignments[ii].rubric[iii].ratings[iiii].color = "#007700";
879 data[i].assignments[ii].rubric[iii].ratingItems.push(data[i].assignments[ii].rubric[iii].ratings[iiii].points);
880 if (!data[i].assignments[ii].rubric[iii].ratings[iiii].name) data[i].assignments[ii].rubric[iii].ratings[iiii].name = "";
881 if (!data[i].assignments[ii].rubric[iii].ratings[iiii].color) data[i].assignments[ii].rubric[iii].ratings[iiii].color = "gray";
882 }
883 }
884 }
885 }
886 dtps.classes[num].streamitems.push(String(data[i].assignments[ii].id));
887 if ((data[i].assignments[ii].submission.score !== null) && (data[i].assignments[ii].submission.score !== undefined)) {
888 dtps.classes[num].stream[dtps.classes[num].stream.length - 1].grade = data[i].assignments[ii].submission.score + "/" + data[i].assignments[ii].points_possible;
889 dtps.classes[num].stream[dtps.classes[num].stream.length - 1].status = data[i].assignments[ii].submission.workflow_state;
890 dtps.classes[num].stream[dtps.classes[num].stream.length - 1].late = data[i].assignments[ii].submission.late;
891 dtps.classes[num].stream[dtps.classes[num].stream.length - 1].letter = (data[i].assignments[ii].submission.grade.match(/[a-z]/i) ? data[i].assignments[ii].submission.grade : data[i].assignments[ii].submission.score);
892 //Only treat assignment as graded in the gradebook if the assignment status says the grades are published. Scores are still shown with a pending review icon. This is to match native Canvas behavior.
893 if (data[i].assignments[ii].submission.workflow_state == "graded") {
894 dtps.classes[num].weights[i].possiblePoints = dtps.classes[num].weights[i].possiblePoints + data[i].assignments[ii].points_possible;
895 dtps.classes[num].weights[i].earnedPoints = dtps.classes[num].weights[i].earnedPoints + data[i].assignments[ii].submission.score;
896 dtps.classes[num].weights[i].assignments.push({ id: data[i].assignments[ii].id, disp: data[i].assignments[ii].name + ": " + data[i].assignments[ii].submission.score + "/" + data[i].assignments[ii].points_possible, percentage: (data[i].assignments[ii].submission.score / data[i].assignments[ii].points_possible).toFixed(2), possible: data[i].assignments[ii].points_possible, earned: data[i].assignments[ii].submission.score });
897 }
898 }
899 if (data[i].assignments[ii].submission !== undefined) {
900 dtps.classes[num].stream[dtps.classes[num].stream.length - 1].missing = data[i].assignments[ii].submission.missing;
901 }
902 }
903 if (dtps.classes[num].weights[i].possiblePoints !== 0) { dtps.classes[num].weights[i].grade = ((dtps.classes[num].weights[i].earnedPoints / dtps.classes[num].weights[i].possiblePoints) * 100).toFixed(2) + "%" } else { dtps.classes[num].weights[i].grade = "" }
904 }
905
906 for (var i = 0; i < outcomes.length; i++) {
907 var streamItem = dtps.classes[num].streamitems.indexOf(outcomes[i].links.assignment.match(/\d+/)[0]);
908 console.log("streamItem ", streamItem)
909 if (streamItem !== -1) {
910 var rubricItem = dtps.classes[num].stream[streamItem].rubricItems.indexOf(outcomes[i].links.learning_outcome);
911 console.log("rubricItem ", rubricItem)
912 if (rubricItem !== -1) {
913 dtps.classes[num].stream[streamItem].rubric[rubricItem].score = outcomes[i].score
914 dtps.classes[num].stream[streamItem].rubric[rubricItem].scoreName = outcomes[i].score
915 if (dtps.classes[num].grades[outcomes[i].links.learning_outcome] == undefined) dtps.classes[num].grades[outcomes[i].links.learning_outcome] = [];
916 dtps.classes[num].grades[outcomes[i].links.learning_outcome].push({ score: outcomes[i].score, possible: outcomes[i].possible })
917 }
918 }
919 }
920 if ((dtps.selectedClass == num) && (dtps.selectedContent == "stream")) {
921 if (!renderOv) {
922 jQuery(".classContent").html(dtps.renderStream(dtps.classes[num].stream.slice().sort(function (a, b) {
923 var keyA = new Date(a.dueDate).getTime(),
924 keyB = new Date(b.dueDate).getTime();
925 var now = new Date().getTime();
926 if (a.dueDate == null) { keyA = Infinity; a.old = true; }
927 if (b.dueDate == null) { keyB = Infinity; b.old = true; }
928 if (keyA < now) { keyA += 9999999999999; a.old = true; }
929 if (keyB < now) { keyB += 9999999999999; b.old = true; }
930 // Compare the 2 dates
931 if (keyA > keyB) return 1;
932 if (keyA < keyB) return -1;
933 return 0;
934 })));
935 }
936 }
937 dtps.classesReady++;
938 dtps.checkReady(num);
939 });
940 });
941}
942
943//Fetches module stream for a class
944dtps.moduleStream = function (num) {
945 var moduleRootHTML = `
946<div class="acrylicMaterial" style="position: absolute;display: inline-block;border-radius: 20px;margin: 82px;">
947<img src="` + dtps.classes[dtps.selectedClass].teacher.prof + `" style="width: 40px; height: 40px; border-radius: 50%;vertical-align: middle;"> <div style="font-size: 16px;display: inline-block;vertical-align: middle;margin: 0px 10px;">` + dtps.classes[dtps.selectedClass].teacher.name + `</div></div>
948<div style="text-align: right;">
949<br />
950<div class="btns row small acrylicMaterial assignmentPicker" style="margin: 63px 80px 20px 0px !important;">
951 <button class="btn" onclick="dtps.classStream(dtps.selectedClass);"><i class="material-icons">assignment</i>Assignments</button>
952 <button class="btn active" onclick="dtps.moduleStream(dtps.selectedClass);"><i class="material-icons">view_module</i>Modules</button>
953</div><script>fluid.init();</script>
954</div>
955</div>`
956 jQuery(".classContent").html(moduleRootHTML + `<div class="spinner"></div>`);
957 streamData = [];
958 dtps.webReq("canvas", "/api/v1/courses/" + dtps.classes[num].id + "/modules?include[]=items&include[]=content_details", function (resp) {
959 var data = JSON.parse(resp);
960 for (var i = 0; i < data.length; i++) {
961 var subsetData = [];
962 for (var ii = 0; ii < data[i].items.length; ii++) {
963 var icon = "star_border";
964 if (data[i].items[ii].type == "ExternalTool") icon = "insert_link";
965 if (data[i].items[ii].type == "ExternalUrl") icon = "open_in_new";
966 if (data[i].items[ii].type == "Assignment") icon = "assignment";
967 if (data[i].items[ii].type == "Page") icon = "insert_drive_file";
968 if (data[i].items[ii].type == "Discussion") icon = "forum";
969 if (data[i].items[ii].type == "Quiz") icon = "assessment";
970 if (data[i].items[ii].type == "SubHeader") icon = "format_size";
971 var open = `window.open('` + data[i].items[ii].html_url + `')`;
972 if (data[i].items[ii].type == "ExternalTool") open = `$('#moduleIFrame').attr('src', ''); fluid.cards('.card.moduleURL'); $.getJSON('` + data[i].items[ii].url + `', function (data) { $('#moduleIFrame').attr('src', data.url); });`
973 if (data[i].items[ii].type == "Assignment") open = `dtps.assignment(` + data[i].items[ii].content_id + `, dtps.selectedClass);`
974 if (data[i].items[ii].type == "Page") open = `dtps.getPage(dtps.classes[dtps.selectedClass].id, '` + data[i].items[ii].page_url + `', true)`
975 subsetData.push(`<div onclick="` + open + `" style="background-color:var(--elements);padding:20px;font-size:17px;border-radius:15px;margin:15px 0; cursor: pointer;">
976<i class="material-icons" style="vertical-align: middle; margin-right: 10px;">` + icon + `</i>` + data[i].items[ii].title + `</div>`);
977 }
978 streamData.push(`<div class="card">
979<h4 style="margin-top: 5px;">` + data[i].name + `</h4>
980` + subsetData.join("") + `
981</div>`)
982 }
983 jQuery(".classContent").html(moduleRootHTML + streamData.join(""));
984
985 });
986}
987
988//Asks the user when they have each class to load the class automatically
989dtps.schedule = function () {
990 var schedule = {}
991 if (confirm("Type in which period you have each class as a number (1-6, type 7 for @d.tech). If the class is from a different semester or you don't have that class for a class period, leave the box blank")) {
992 for (var i = 0; i < dtps.classes.length; i++) {
993 var num = prompt("Which class period do you have '" + dtps.classes[i].name + "'? (Number 1-6, type 7 for @d.tech, or leave blank)");
994 if ((Number(num) > 0) && (Number(num) < 8)) schedule[num] = dtps.classes[i].id;
995 }
996 localStorage.setItem("dtpsSchedule", JSON.stringify(schedule));
997 alert("Your schedule has been saved. When loading Power+, Power+ will try to load the class that you are in instead of the dashboard, if you are using Power+ during school hours.")
998 }
999}
1000
1001//Displays class info & syllabus
1002dtps.classInfo = function (num) {
1003 if ((dtps.classes[num].syllabus !== "") && dtps.classes[num].syllabus !== null) {
1004 var blob = new Blob([`<base target="_blank" /> <link type="text/css" rel="stylesheet" href="https://cdn.jottocraft.com/CanvasCSS.css" media="screen,projection"/>
1005 <style>body {background-color: ` + getComputedStyle($(".card.details")[0]).getPropertyValue("--cards") + `; color: ` + getComputedStyle($(".card.details")[0]).getPropertyValue("--text") + `}</style>` + dtps.classes[num].syllabus], { type: 'text/html' });
1006 var newurl = window.URL.createObjectURL(blob);
1007 }
1008 $(".card.classInfoCard").html(`<i onclick="fluid.cards.close('.card.classInfoCard')" class="material-icons close">close</i>
1009 <h4 style="font-weight: bold;">` + dtps.classes[num].name + `</h4>
1010 <p style="color: var(--secText)">` + (dtps.classes[num].description ? dtps.classes[num].description : "") + `</p>
1011 <div class="assignmentChip"><i class="material-icons">group</i>` + dtps.classes[num].totalStudents + ` students</div>
1012 ` + (dtps.classes[num].favorite ? `<div title="Favorited class" class="assignmentChip" style="background-color: #daa520"><i style="color:white;font-family: 'Material Icons Outline'" class="material-icons">star_border</i></div>` : "") + `
1013 <br />
1014 <div style="margin-top: 20px;" class="syllabusBody">` + ((dtps.classes[num].syllabus !== "") && dtps.classes[num].syllabus !== null ? `<iframe id="syllabusIframe" onload="dtps.iframeLoad('syllabusIframe')" style="margin: 10px 0px; width: 100%; border: none; outline: none;" src="` + newurl + `" />` : "") + `</div>
1015 `)
1016 fluid.modal(".card.classInfoCard")
1017}
1018
1019//Displays class homepage
1020dtps.classHome = function (num) {
1021 $(".card.classInfoCard").html(`<i onclick="fluid.cards.close('.card.classInfoCard')" class="material-icons close">close</i>
1022 <h4 style="font-weight: bold;">` + dtps.classes[num].subject + ` Homepage</h4>
1023 <br />
1024 <p>Loading...</p>`)
1025 dtps.webReq("canvas", "/api/v1/courses/" + dtps.classes[num].id + "/front_page", function (resp) {
1026 var data = JSON.parse(resp);
1027 if (!data.message) {
1028 var blob = new Blob([`<base target="_blank" /> <link type="text/css" rel="stylesheet" href="https://cdn.jottocraft.com/CanvasCSS.css" media="screen,projection"/>
1029 <style>body {background-color: ` + getComputedStyle($(".card.details")[0]).getPropertyValue("--cards") + `; color: ` + getComputedStyle($(".card.details")[0]).getPropertyValue("--text") + `}</style>` + data.body], { type: 'text/html' });
1030 var newurl = window.URL.createObjectURL(blob);
1031 $(".card.classInfoCard").html(`<i onclick="fluid.cards.close('.card.classInfoCard')" class="material-icons close">close</i>
1032 <h4 style="font-weight: bold;">` + data.title + `</h4>
1033 <br />
1034 <div style="margin-top: 20px;" class="homepageBody"><iframe id="homepageIframe" onload="dtps.iframeLoad('homepageIframe')" style="margin: 10px 0px; width: 100%; border: none; outline: none;" src="` + newurl + `" /></div>
1035 `)
1036 fluid.modal(".card.classInfoCard")
1037 } else {
1038 alert("Error: No homepage found for this class")
1039 fluid.cards.close('.card.classInfoCard')
1040 }
1041 })
1042}
1043
1044//Converts a Power+ stream array into HTML for displaying the assignment list
1045dtps.renderStream = function (stream, searchRes) {
1046 var streamlist = [];
1047 var oldDiv = false;
1048 for (var i = 0; i < stream.length; i++) {
1049 var outcomeDom = [];
1050 if (stream[i].rubric) {
1051 for (var ii = 0; ii < stream[i].rubric.length; ii++) {
1052 if (stream[i].rubric[ii].score) {
1053 outcomeDom.push(`<div class="outcome score` + stream[i].rubric[ii].score + `"></div>`)
1054 }
1055 }
1056 }
1057 streamlist.push((stream[i].old && !oldDiv ? `<h5 style="margin: 75px 75px 10px 75px;` + (dtps.selectedClass == "dash" ? `text-align: center;margin: 75px 25px 10px 75px;` : ``) + ` font-weight: bold;">Old/Undated Assignments</h5>` : "") + `
1058 <div onclick="` + (stream[i].google ? `window.open('` + stream[i].url + `')` : `dtps.assignment('` + stream[i].id + `', ` + stream[i].class + `)`) + `" class="card graded assignment ` + stream[i].col + `">
1059 ` + (stream[i].turnedIn && (stream[i].status !== "unsubmitted") ? `<i title="Assignment submitted" class="material-icons floatingIcon" style="color: #0bb75b;">assignment_turned_in</i>` : ``) + `
1060 ` + (stream[i].status == "unsubmitted" ? `<i title="Assignment unsubmitted" class="material-icons floatingIcon" style="color: #b3b70b;">warning</i>` : ``) + `
1061 ` + (stream[i].missing ? `<i title="Assignment is missing" class="material-icons floatingIcon" style="color: #c44848;">remove_circle_outline</i>` : ``) + `
1062 ` + (stream[i].late ? `<i title="Assignment is late" class="material-icons floatingIcon" style="color: #c44848;">assignment_late</i>` : ``) + `
1063 ` + (stream[i].locked ? `<i title="Assignment submissions are locked" class="material-icons floatingIcon" style="font-family: 'Material Icons Extended'; color: var(--secText, gray);">lock_outline</i>` : ``) + `
1064 ` + (stream[i].status == "pending_review" ? `<i title="Grade is pending review" class="material-icons floatingIcon" style="color: #b3b70b;">rate_review</i>` : ``) + `
1065 <div class="points">
1066 ` + (outcomeDom.length ? `<div class="earned outcomes">` + outcomeDom.join("") + `</div>` : `<div class="earned numbers">` + (stream[i].letter ? stream[i].grade.split("/")[0] : "") + `</div>`) + `
1067 ` + (stream[i].grade ? (stream[i].grade.split("/")[1] !== undefined ? `<div class="total possible">/` + stream[i].grade.split("/")[1] + `</div>` : "") : "") + `
1068 </div>
1069 <h4>` + stream[i].title + `</h4>
1070 <h5>
1071 ` + (stream[i].due ? `<div class="infoChip"><i style="margin-top: -4px;" class="material-icons">alarm</i> Due ` + stream[i].due + `</div>` : "") + `
1072 ` + ((stream[i].weight !== undefined) && stream[i].uniqueWeight ? `<div class="infoChip weighted">` + stream[i].weightIcon + stream[i].weight.replace("Comprehension Checks", "CC").replace("Success Skills", "SS").replace("Performance Tasks", "PT") + `</div>` : "") + `
1073 ` + (stream[i].outcomes !== undefined ? `<div class="infoChip weighted"><i class="material-icons">adjust</i>` + stream[i].outcomes.length + `</div>` : "") + `
1074 </h5>
1075 </div>
1076 `);
1077 if (stream[i].old) oldDiv = true;
1078 }
1079 if (typeof Fuse !== "undefined") {
1080 if (searchRes == undefined) {
1081 dtps.latestStream = stream;
1082 dtps.fuse = new Fuse(stream, {
1083 shouldSort: true,
1084 threshold: 0.6,
1085 keys: ["title", "id", "due", "subject"]
1086 });
1087 searchRes = "";
1088 }
1089 }
1090 return ((streamlist.length == 0) && (dtps.selectedClass !== "dash")) ?
1091 (searchRes !== "" ? `<div style="text-align: right;"><i class="inputIcon material-icons">search</i><input value="` + searchRes + `" onchange="dtps.search()" class="search inputIcon shadow" placeholder="Search assignments" type="search" /></div>` : "") + `<div style="cursor: auto;" class="card assignment"><h4>No ` + (searchRes == "" ? "assignments" : "results found") + `</h4><p>` + (searchRes == "" ? "There aren't any assignments in this class yet" : "There aren't any search results") + `</p></div>`
1092 : ((typeof Fuse !== "undefined" ? `
1093
1094` + ((dtps.selectedClass !== "dash") && (searchRes == "") ? `
1095
1096<div style="position: absolute;display: inline-block;margin: 82px;">
1097<div class="acrylicMaterial" style="border-radius: 20px; display: inline-block; margin-right: 5px;">
1098<img src="` + dtps.classes[dtps.selectedClass].teacher.prof + `" style="width: 40px; height: 40px; border-radius: 50%;vertical-align: middle;"> <div style="font-size: 16px;display: inline-block;vertical-align: middle;margin: 0px 10px;">` + dtps.classes[dtps.selectedClass].teacher.name + `</div></div>
1099
1100<div onclick="dtps.classInfo(` + dtps.selectedClass + `)" class="acrylicMaterial" style="border-radius: 50%; height: 40px; width: 40px; text-align: center; display: inline-block; vertical-align: middle; cursor: pointer; margin-right: 3px;">
1101<i style="line-height: 40px;" class="material-icons">info</i>
1102</div>
1103
1104` + (dtps.classes[dtps.selectedClass].defaultView == "wiki" ? `<div onclick="dtps.classHome(` + dtps.selectedClass + `)" class="acrylicMaterial" style="border-radius: 50%; height: 40px; width: 40px; text-align: center; display: inline-block; vertical-align: middle; cursor: pointer;">
1105<i style="line-height: 40px;" class="material-icons">home</i>
1106</div>` : "") + `
1107
1108</div>
1109` : "") + `
1110
1111<div style="text-align: right;"><i class="inputIcon material-icons">search</i><input value="` + searchRes + `" onchange="dtps.search()" class="search inputIcon shadow" placeholder="Search assignments" type="search" />
1112` + ((dtps.selectedClass !== "dash") && (searchRes == "") ? `<br />
1113<div class="btns row small acrylicMaterial assignmentPicker" style="margin: 20px 80px 20px 0px !important;">
1114 <button class="btn active" onclick="dtps.classStream(dtps.selectedClass);"><i class="material-icons">assignment</i>Assignments</button>
1115 <button class="btn" onclick="dtps.moduleStream(dtps.selectedClass);"><i class="material-icons">view_module</i>Modules</button>
1116</div><script>fluid.init();</script>` : "") + `
1117</div>` : "") + streamlist.join(""));
1118 //return streamlist.join("");
1119}
1120
1121//Searches the assignment stream for a keyword using Fuse.js
1122dtps.search = function () {
1123 if (dtps.selectedClass == "dash") {
1124 if ($("input.search").val() == "") {
1125 jQuery(".classContent .stream").html(dtps.renderStream(dtps.latestStream, ""))
1126 } else {
1127 jQuery(".classContent .stream").html(dtps.renderStream(dtps.fuse.search($("input.search").val()), $("input.search").val()))
1128 }
1129 $(".card.assignment").addClass("color");
1130 } else {
1131 if ($("input.search").val() == "") {
1132 jQuery(".classContent").html(dtps.renderStream(dtps.latestStream, ""))
1133 } else {
1134 jQuery(".classContent").html(dtps.renderStream(dtps.fuse.search($("input.search").val()), $("input.search").val()))
1135 }
1136 }
1137}
1138
1139//Renders the Power+ master stream / dashboard showing an overview of all classes
1140dtps.masterStream = function (doneLoading, omitOldAssignments) {
1141 dtps.log("RENDERING DASHBOARD")
1142 dtps.showClasses();
1143 if ((dtps.selectedClass == "dash") && (dtps.masterContent == "assignments")) {
1144 jQuery(".classContent").html(`
1145 <div class="spinner"></div>
1146 `);
1147 }
1148 var buffer = [];
1149 if (dtps.classes) {
1150 for (var i = 0; i < dtps.classes.length; i++) {
1151 if (dtps.classes[i].stream) {
1152 buffer = buffer.concat(dtps.classes[i].stream)
1153 }
1154 }
1155 }
1156 var loadingDom = "";
1157 if (!doneLoading) {
1158 loadingDom = `<div class="spinner"></div>`;
1159 } else {
1160 dtps.logGrades();
1161 }
1162 if ((dtps.selectedClass == "dash") && (dtps.masterContent == "assignments")) {
1163 jQuery(".classContent").html(`
1164<div class="dash cal" style="width: 40%;display: inline-block; vertical-align: top;">
1165` + ($.fullCalendar !== undefined ? `<div id="calendar" class="card" style="padding: 20px;"></div>` : ``) + `
1166<div class="announcements"></div>
1167</div>
1168<div style="width: 59%; display: inline-block;" class="dash stream">
1169` + loadingDom + `
1170<div class="assignmentStream"></div>
1171</div>
1172`)
1173 }
1174
1175 dtps.announcements();
1176 jQuery(".classContent .dash .assignmentStream").html(dtps.renderStream(buffer.sort(function (a, b) {
1177 var keyA = new Date(a.dueDate).getTime(),
1178 keyB = new Date(b.dueDate).getTime();
1179 var now = new Date().getTime();
1180 if (a.dueDate == null) { keyA = Infinity; a.old = true; }
1181 if (b.dueDate == null) { keyB = Infinity; b.old = true; }
1182 if (keyA < now) { keyA += 9999999999999; a.old = true; }
1183 if (keyB < now) { keyB += 9999999999999; b.old = true; }
1184 // Compare the 2 dates
1185 if (keyA > keyB) return 1;
1186 if (keyA < keyB) return -1;
1187 return 0;
1188 })));
1189 $(".card.assignment").addClass("color");
1190 dtps.calendar(doneLoading);
1191}
1192
1193//Loads Google APIs for Classroom integration
1194dtps.gapis = function () {
1195 //these 2 lines are temporary. remove them when uncommenting the rest of dtps.gapis
1196 jQuery(".googleClassroom").hide();
1197 jQuery(".googleSetup").show();
1198 /*jQuery.getScript("https://apis.google.com/js/api.js", function() {
1199 gapi.load('client:auth2', function() {
1200gapi.client.init({
1201 apiKey: 'AIzaSyB3l_RWC3UMgNDAjZ4wD_HD2NyrneL9H9g',
1202 clientId: '117676227556-lrt444o80hgrli1nlcl4ij6cm2dbop8v.apps.googleusercontent.com',
1203 discoveryDocs: ["https://www.googleapis.com/discovery/v1/apis/classroom/v1/rest"],
1204 scope: "https://www.googleapis.com/auth/classroom.courses.readonly https://www.googleapis.com/auth/classroom.coursework.me.readonly"
1205 }).then(function () {
1206 gapi.auth2.getAuthInstance().isSignedIn.listen(updateSigninStatus);
1207 updateSigninStatus(gapi.auth2.getAuthInstance().isSignedIn.get());
1208 }, function(error) {
1209 dtps.log(JSON.stringify(error));
1210 console.error(error);
1211 });
1212 function updateSigninStatus(isSignedIn) {
1213 if (isSignedIn) {
1214 jQuery(".googleClassroom").show();
1215 jQuery(".googleSetup").hide();
1216 if (dtps.googleSetup !== undefined) {
1217 window.alert("Google account linked. You have to sign in to Canvas again to finish setup.")
1218 window.location.reload();
1219 } else {
1220 dtps.googleAuth();
1221 }
1222 } else {
1223 jQuery(".googleClassroom").hide();
1224 jQuery(".googleSetup").show();
1225 }
1226 }
1227 });
1228});*/
1229}
1230
1231//Loads a page
1232dtps.getPage = function (classID, id, fromModuleStream) {
1233 if (id == undefined) var id = dtps.selectedPage;
1234 if ((dtps.classes[dtps.selectedClass].id == classID) && ((dtps.selectedContent == "pages") || fromModuleStream)) {
1235 jQuery(".classContent").html(`<div class="spinner"></div>`);
1236 }
1237 var spinnerTmp = true;
1238 dtps.webReq("canvas", "/api/v1/courses/" + classID + "/pages/" + id, function (resp) {
1239 var data = JSON.parse(resp);
1240 if ((dtps.classes[dtps.selectedClass].id == classID) && ((dtps.selectedContent == "pages") || fromModuleStream)) {
1241 $(".cacaoBar .tab.active span").html(data.title)
1242 var blob = new Blob([`<base target="_blank" /> <link type="text/css" rel="stylesheet" href="https://cdn.jottocraft.com/CanvasCSS.css" media="screen,projection"/>
1243 <style>body {background-color: ` + getComputedStyle($(".card.details")[0]).getPropertyValue("--cards") + `; color: ` + getComputedStyle($(".card.details")[0]).getPropertyValue("--text") + `}</style>` + data.body], { type: 'text/html' });
1244 var newurl = window.URL.createObjectURL(blob);
1245 jQuery(".classContent").html((fromModuleStream ? `<div class="acrylicMaterial" onclick="dtps.moduleStream(dtps.selectedClass)" style="line-height: 40px;display: inline-block;border-radius: 20px;margin: 82px 0px 0px 82px; cursor: pointer;">
1246 <div style="font-size: 16px;display: inline-block;vertical-align: middle;margin: 0px 20px;"><i style="vertical-align: middle;" class="material-icons">keyboard_arrow_left</i> Back</div></div>` : "") + `
1247 <div class="card">
1248 <h4 style="font-weight: bold;">` + data.title + `</h4>
1249 <br />
1250 <iframe id="classPageIframe" onload="dtps.iframeLoad('classPageIframe')" style="margin: 10px 0px; width: 100%; border: none; outline: none;" src="` + newurl + `" />
1251 </div>
1252 `);
1253 }
1254 });
1255}
1256
1257dtps.outcome = function (num, id) {
1258 dtps.webReq("canvas", "/api/v1/outcomes/" + id, function (resp) {
1259 var data = JSON.parse(resp);
1260
1261 if (data.description) {
1262 var blob = new Blob([`<base target="_blank" /> <link type="text/css" rel="stylesheet" href="https://cdn.jottocraft.com/CanvasCSS.css" media="screen,projection"/>
1263 <style>body {background-color: ` + getComputedStyle($(".card.details")[0]).getPropertyValue("--cards") + `; color: ` + getComputedStyle($(".card.details")[0]).getPropertyValue("--text") + `}</style>` + data.description], { type: 'text/html' });
1264 var newurl = window.URL.createObjectURL(blob);
1265 }
1266
1267 $(".card.outcomeCard").html(`<i onclick="fluid.cards.close('.card.outcomeCard')" class="material-icons close">close</i>
1268 <h4 style="font-weight: bold;">` + data.title + `</h4>
1269 ` + data.ratings.map(function (rating) {
1270 return `<h5>` + rating.points + ": " + rating.description + `</h5>`
1271 }).join("") + `
1272 <div style="margin-top: 20px;" class="syllabusBody">` + (data.description ? `<iframe id="syllabusIframe" onload="dtps.iframeLoad('syllabusIframe')" style="margin: 10px 0px; width: 100%; border: none; outline: none;" src="` + newurl + `" />` : "") + `</div>
1273 `)
1274 fluid.modal(".card.outcomeCard")
1275 })
1276}
1277
1278//Loads the gradebook for a class. The type paramater specifies if it should load the mastery gradebook or not
1279dtps.gradebook = function (num) {
1280 dtps.showClasses();
1281 if (dtps.classes[num].noOutcomes) {
1282 $(".btns .btn.grades").hide();
1283 $(".btns .btn").removeClass("active");
1284 $(".btns .btn.stream").addClass("active");
1285 dtps.selectedContent = "stream";
1286 dtps.classStream(num);
1287 } else {
1288 headsUp = `<div class="acrylicMaterial" style="line-height: 40px;display: inline-block;border-radius: 20px;margin: 82px 0px 0px 82px;">
1289 <div style="font-size: 16px;display: inline-block;vertical-align: middle;margin: 0px 20px;">Gradebook (beta)</div></div>`
1290 $(".classContent").html(headsUp + `<div class="spinner"></div>`)
1291
1292 dtps.webReq("canvas", "/api/v1/courses/" + dtps.classes[num].id + "/outcome_alignments?student_id=" + dtps.user.id, function (resp) {
1293 dtps.webReq("canvas", "/api/v1/courses/" + dtps.classes[num].id + "/outcome_rollups?user_ids[]=" + dtps.user.id + "&include[]=outcomes", function (respp) {
1294 dtps.webReq("canvas", "/api/v1/courses/" + dtps.classes[num].id + "/outcome_results?user_ids[]=" + dtps.user.id, function (resppp) {
1295 var alignmentData = JSON.parse(resp);
1296 var rollupData = JSON.parse(respp);
1297 var resultsData = JSON.parse(resppp).outcome_results;
1298 dtps.classes[num].outcomes = {};
1299
1300 console.log(resultsData)
1301
1302 for (var i = 0; i < rollupData.linked.outcomes.length; i++) {
1303 dtps.classes[num].outcomes[rollupData.linked.outcomes[i].id] = rollupData.linked.outcomes[i]
1304 dtps.classes[num].outcomes[rollupData.linked.outcomes[i].id].alignments = []
1305 dtps.classes[num].outcomes[rollupData.linked.outcomes[i].id].gradedAlignments = []
1306 dtps.classes[num].outcomes[rollupData.linked.outcomes[i].id].gradedAlignmentIDs = []
1307 }
1308
1309 for (var i = 0; i < alignmentData.length; i++) {
1310 if (dtps.classes[num].outcomes[alignmentData[i].learning_outcome_id] !== undefined) {
1311 dtps.classes[num].outcomes[alignmentData[i].learning_outcome_id].alignments.push(alignmentData[i]);
1312 for (var ii = 0; ii < resultsData.length; ii++) {
1313 if (String(resultsData[ii].links.assignment).replace("assignment_", "") == alignmentData[i].assignment_id) {
1314 if (resultsData[ii].score && !dtps.classes[num].outcomes[alignmentData[i].learning_outcome_id].gradedAlignmentIDs.includes(resultsData[ii].links.assignment)) {
1315 dtps.classes[num].outcomes[alignmentData[i].learning_outcome_id].gradedAlignments.push(alignmentData[i]);
1316 dtps.classes[num].outcomes[alignmentData[i].learning_outcome_id].gradedAlignmentIDs.push(resultsData[ii].links.assignment);
1317 }
1318 }
1319 }
1320 } else {
1321 dtps.log("WARNING: GRADEBOOK FOUND AN OUTCOME ALIGNMENT THAT DOES NOT MATCH ANY ROLLUP (dtps.gradebook)")
1322 }
1323 }
1324
1325 for (var i = 0; i < rollupData.rollups[0].scores.length; i++) {
1326 var outcome = dtps.classes[num].outcomes[rollupData.rollups[0].scores[i].links.outcome];
1327 outcome.score = rollupData.rollups[0].scores[i].score
1328 for (var ii = 0; ii < dtps.classes[num].stream.length; ii++) {
1329 if (dtps.classes[num].stream[ii].title == rollupData.rollups[0].scores[i].title) {
1330 outcome.lastAssignment = {
1331 id: dtps.classes[num].stream[ii].id,
1332 name: rollupData.rollups[0].scores[i].title,
1333 score: dtps.classes[num].stream[ii].rubric[dtps.classes[num].stream[ii].rubricItems.indexOf(rollupData.rollups[0].scores[i].links.outcome)].score
1334 }
1335 }
1336 }
1337 if (outcome.lastAssignment) {
1338 //straight outcome average (w/o decaying average thing)
1339 outcome.straightAverage = ((((outcome.score - (outcome.lastAssignment.score * (outcome.calculation_int / 100))) / (1 - (outcome.calculation_int / 100))) * (outcome.gradedAlignments.length - 1)) + outcome.lastAssignment.score) / outcome.gradedAlignments.length;
1340 }
1341 }
1342
1343 if (dtps.classes[num].computedOutcomeScores !== undefined) {
1344 Object.keys(dtps.classes[num].computedOutcomeScores).forEach((k) => {
1345 if (dtps.classes[num].outcomes[k]) {
1346 dtps.classes[num].outcomes[k].score = dtps.classes[num].computedOutcomeScores[k]
1347 }
1348 })
1349 }
1350
1351 //temporary variable for if outcome divider is added yet
1352 var dividerAdded = false;
1353
1354 $(".classContent").html(headsUp + `
1355
1356 ` + (dtps.classes[num].letter !== "--" ? `<div class="card">
1357 <h4 style="margin-bottom: 40px; height: 80px; line-height: 80px; margin-top: 0px; font-weight: bold; -webkit-text-fill-color: transparent; background: -webkit-linear-gradient(var(--light), var(--norm)); -webkit-background-clip: text;">` + dtps.classes[num].name + `
1358 <div class="classGradeCircle" style="display: inline-block;width: 80px;height: 80px;text-align: center;line-height: 80px;border-radius: 50%;float: right;vertical-align: middle;color: var(--light);">` + dtps.classes[num].letter + `</div></h4>
1359 <h5 style="height: 60px; line-height: 60px;">75% of outcome scores are ≥
1360 <div style=" display: inline-block; background-color: var(--elements); width: 60px; height: 60px; text-align: center; line-height: 60px; border-radius: 50%; float: right; vertical-align: middle; font-size: 22px;">` + (dtps.classes[num].gradeCalc.number75 ? dtps.classes[num].gradeCalc.number75 : "--") + `</div></h5>
1361 <h5 style="height: 60px; line-height: 60px;">No outcome scores are lower than
1362 <div style=" display: inline-block; background-color: var(--elements); width: 60px; height: 60px; text-align: center; line-height: 60px; border-radius: 50%; float: right; vertical-align: middle; font-size: 22px;">` + dtps.classes[num].gradeCalc.lowestValue + `</div></h5>
1363 <br />
1364 <a onclick="$('#classGradeMore').show(); $(this).hide();" style="color: var(--secText, gray); cursor: pointer;">Show More</a>
1365 <div style="display: none;" id="classGradeMore">
1366 <table class="u-full-width">
1367 <thead>
1368 <tr>
1369 <th>Final Letter</th>
1370 <th>75% of outcome scores ≥</th>
1371 <th>No outcome scores below</th>
1372 </tr>
1373 </thead>
1374 <tbody>
1375 <tr ` + (dtps.classes[num].letter == "A" ? `style="color: var(--norm);font-size:20px;"` : ``) + `>
1376 <td>A</td>
1377 <td>3.5</td>
1378 <td>3.0</td>
1379 </tr>
1380 <tr ` + (dtps.classes[num].letter == "A-" ? `style="color: var(--norm);font-size:20px;"` : ``) + `>
1381 <td>A-</td>
1382 <td>3.5</td>
1383 <td>2.5</td>
1384 </tr>
1385 <tr ` + (dtps.classes[num].letter == "B+" ? `style="color: var(--norm);font-size:20px;"` : ``) + `>
1386 <td>B+</td>
1387 <td>3</td>
1388 <td>2.5</td>
1389 </tr>
1390 <tr ` + (dtps.classes[num].letter == "B" ? `style="color: var(--norm);font-size:20px;"` : ``) + `>
1391 <td>B</td>
1392 <td>3</td>
1393 <td>2.25</td>
1394 </tr>
1395 <tr ` + (dtps.classes[num].letter == "B-" ? `style="color: var(--norm);font-size:20px;"` : ``) + `>
1396 <td>B-</td>
1397 <td>3</td>
1398 <td>2.0</td>
1399 </tr>
1400 <tr ` + (dtps.classes[num].letter == "C" ? `style="color: var(--norm);font-size:20px;"` : ``) + `>
1401 <td>C</td>
1402 <td>2.5</td>
1403 <td>2.0</td>
1404 </tr>
1405 <tr ` + (dtps.classes[num].letter == "I" ? `style="color: var(--norm);font-size:20px;"` : ``) + `>
1406 <td>I</td>
1407 <td>Anything else</td>
1408 <td>--</td>
1409 </tr>
1410 </tbody>
1411</table>
1412</div>
1413</div>` : "") + `
1414` + Object.keys(dtps.classes[num].outcomes).sort(function (a, b) {
1415 var keyA = dtps.classes[num].outcomes[a].score,
1416 keyB = dtps.classes[num].outcomes[b].score;
1417 if (keyA == undefined) { keyA = 999999 - dtps.classes[num].outcomes[a].alignments.length; }
1418 if (keyB == undefined) { keyB = 999999 - dtps.classes[num].outcomes[b].alignments.length; }
1419 // Compare the 2 scores
1420 if (keyA > keyB) return 1;
1421 if (keyA < keyB) return -1;
1422 return 0;
1423 }).map(function (i) {
1424 var divider = !dividerAdded && !dtps.classes[num].outcomes[i].score;
1425 if (divider) dividerAdded = true;
1426 return (divider ? `<h5 style="font-weight: bold;margin: 75px 75px 10px 75px;">Unassesed outcomes</h5>` : "") + `
1427<div onclick="dtps.outcome(` + num + `, '` + dtps.classes[num].outcomes[i].id + `')" style="border-radius: 20px;padding: 10px 20px;height: 105px;cursor: pointer;" class="card">
1428 <h5 style="font-size: 1.5rem; white-space: nowrap;overflow: hidden;text-overflow: ellipsis;">` + dtps.classes[num].outcomes[i].title + `</h5>
1429 <div title="Number of assignments that assess this outcome" style="color: var(--secText); display: inline-block; margin-right: 5px;"><i class="material-icons" style=" vertical-align: middle; ">assignment</i> ` + dtps.classes[num].outcomes[i].alignments.length + `</div>
1430 ` + (dtps.classes[num].outcomes[i].calculation_method == "decaying_average" ? `<div title="Calculation ratio (last assignment / everything else)" style="color: var(--secText); display: inline-block; margin: 0px 5px;"><i class="material-icons" style=" vertical-align: middle; ">functions</i> ` + dtps.classes[num].outcomes[i].calculation_int + "/" + (100 - dtps.classes[num].outcomes[i].calculation_int) + `</div>` : "") + `
1431 ` + (dtps.classes[num].outcomes[i].score ? `<div title="Outcome score" style="color: var(--secText); display: inline-block; margin: 0px 5px;"><i class="material-icons" style=" vertical-align: middle; ">assessment</i> ` + dtps.classes[num].outcomes[i].score + `</div>` : "") + `
1432 ` + (dtps.classes[num].outcomes[i].score >= dtps.classes[num].outcomes[i].mastery_points ? `<div title="Outcome has been mastered" style="display: inline-block;margin: 0px 5px;color: #5d985d;">MASTERED</div>` : "") + `
1433</div>`
1434 }).join("") + `
1435</div>`)
1436 });
1437 });
1438 })
1439 }
1440}
1441
1442//grade boost is its own thing now because of CBL
1443dtps.gradeBoost = function () {
1444
1445}
1446
1447//Shows details for an assignment given the assignment ID and class number
1448dtps.assignment = function (id, classNum, submissions) {
1449 var streamNum = dtps.classes[classNum].streamitems.indexOf(String(id));
1450 var assignment = dtps.classes[classNum].stream[streamNum];
1451 console.log(classNum, streamNum);
1452 if (submissions == "handIN") {
1453 $(".card.details").html(`<i onclick="fluid.cards.close('.card.details')" class="material-icons close">close</i>
1454<i style="left: 0; right: auto;" onclick="dtps.assignment(` + id + `, ` + classNum + `)" class="material-icons close">arrow_back</i>
1455<br /><h4>Loading...</h4>`);
1456 $.get("https://canvas.instructure.com/courses/" + dtps.classes[classNum].id + "/assignments/" + id, function (data) {
1457 dtps.submissionToken = $(data).find("input[name=authenticity_token]").attr("value");
1458 $(".card.details").html(`<i onclick="fluid.cards.close('.card.details')" class="material-icons close">close</i>
1459<i style="left: 0; right: auto;" onclick="dtps.assignment(` + id + `, ` + classNum + `)" class="material-icons close">arrow_back</i>
1460<br /><h4>Submit assignment (beta)</h4>
1461<p>Submitting assignments in Power+ is a beta feature. Use at your own risk and double check the Canvas submission to ensure it worked properly.
1462Power+ currently only supports assignments that use online text entry. Other assignment types will be added in the future.</p>
1463<br />
1464<textarea class="submissionText" placeholder="Submission"></textarea>
1465<br /><br />
1466<button class="btn" onclick="dtps.assignment(` + id + `, ` + classNum + `, 'submitText')"><i class="material-icons">public</i>Submit</button>`);
1467 });
1468 } else {
1469 if (String(submissions).startsWith("submit")) {
1470 if (submissions == "submitText") {
1471 dtps.webReq('canSUBMIT', "/api/v1/courses/" + dtps.classes[classNum].id + "/assignments/" + id + "/submissions?submission[submission_type]=online_text_entry&submission[body]=" + $('.submissionText').val() + "&authenticity_token=" + dtps.submissionToken, function () {
1472 window.alert('Assignment submitted')
1473 })
1474 }
1475 } else {
1476 if (submissions) {
1477 $(".card.details").css("background-color", "white");
1478 $(".card.details").css("color", "black")
1479 $(".card.details").html(`<i style="color: black !important;" onclick="fluid.cards.close('.card.details')" class="material-icons close">close</i>
1480<i style="left: 0; right: auto; color: black !important;" onclick="dtps.assignment(` + id + `, ` + classNum + `)" class="material-icons close">arrow_back</i>
1481<br /><br />
1482<iframe style="width: 100%; height: calc(100vh - 175px); border: none;" src="` + assignment.submissions + `"></iframe>`);
1483 } else {
1484 $(".card.details").css("background-color", "")
1485 $(".card.details").css("color", "")
1486
1487 if (assignment.body) {
1488 var blob = new Blob([`<base target="_blank" /> <link type="text/css" rel="stylesheet" href="https://cdn.jottocraft.com/CanvasCSS.css" media="screen,projection"/>
1489 <style>body {background-color: ` + getComputedStyle($(".card.details")[0]).getPropertyValue("--cards") + `; color: ` + getComputedStyle($(".card.details")[0]).getPropertyValue("--text") + `}</style>` + assignment.body], { type: 'text/html' });
1490 var newurl = window.URL.createObjectURL(blob);
1491 }
1492
1493 $(".card.details").html(`<i onclick="fluid.cards.close('.card.details'); $('.card.details').html('');" class="material-icons close">close</i>
1494<h4 style="font-weight: bold;">` + assignment.title + `</h4>
1495
1496<div>
1497` + (assignment.due ? `<div class="assignmentChip"><i class="material-icons">alarm</i>Due ` + assignment.due + `</div>` : "") + `
1498` + (assignment.outcomes ? `<div class="assignmentChip"><i class="material-icons">adjust</i> ` + assignment.outcomes.length + `</div>` : "") + `
1499` + (assignment.turnedIn && (assignment.status !== "unsubmitted") ? `<div title="Assignment submitted" class="assignmentChip" style="background-color: #0bb75b"><i style="color:white;" class="material-icons">assignment_turned_in</i></div>` : "") + `
1500` + (assignment.status == "unsubmitted" ? `<div title="Assignment unsubmitted" class="assignmentChip" style="background-color: #b3b70b"><i style="color:white;" class="material-icons">warning</i></div>` : "") + `
1501` + (assignment.missing ? `<div title="Assignment is missing" class="assignmentChip" style="background-color: #c44848"><i style="color:white;" class="material-icons">remove_circle_outline</i></div>` : "") + `
1502` + (assignment.late ? `<div title="Assignment is late" class="assignmentChip" style="background-color: #c44848"><i style="color:white;" class="material-icons">assignment_late</i></div>` : "") + `
1503` + (assignment.locked ? `<div title="Assignment submissions are locked" class="assignmentChip" style="background-color: var(--secText, gray);"><i style="color:white;font-family: 'Material Icons Extended';" class="material-icons">lock_outline</i></div>` : "") + `
1504` + (assignment.status == "pending_review" ? `<div title="Grade is pending review" class="assignmentChip" style="background-color: #b3b70b"><i style="color:white;" class="material-icons">rate_review</i></div>` : "") + `
1505</div>
1506
1507<div style="margin-top: 20px;" class="assignmentBody">` + (assignment.body ? `<iframe id="assignmentIframe" onload="dtps.iframeLoad('assignmentIframe')" style="margin: 10px 0px; width: 100%; border: none; outline: none;" src="` + newurl + `" />` : "") + `</div>
1508
1509<div style="margin: 5px 0px; background-color: var(--secText); height: 1px; width: 100%;" class="divider"></div>
1510<div style="width: calc(40% - 2px); margin-top: 20px; display: inline-block; overflow: hidden; vertical-align: middle;">
1511<p style="color: var(--secText); margin: 5px 0px;"><i style="vertical-align: middle;" class="material-icons">add_box</i> Posted: ` + assignment.published + `</p>
1512` + (assignment.due ? `<p style="color: var(--secText); margin: 5px 0px;"><i style="vertical-align: middle;" class="material-icons">alarm</i> Due: ` + assignment.due + `</p>` : "") + `
1513` + (assignment.locksAt ? `<p style="color: var(--secText); margin: 5px 0px;"><i style="vertical-align: middle;font-family: 'Material Icons Extended'" class="material-icons">lock_outline</i> Locks: ` + new Date(assignment.locksAt).toDateString().slice(0, -5) + ", " + dtps.ampm(new Date(assignment.locksAt)) + `</p>` : "") + `
1514` + (assignment.unlocksAt ? `<p style="color: var(--secText); margin: 5px 0px;"><i style="vertical-align: middle;" class="material-icons">lock_open</i> Unlocks: ` + new Date(assignment.unlocksAt).toDateString().slice(0, -5) + ", " + dtps.ampm(new Date(assignment.unlocksAt)) + `</p>` : "") + `
1515` + (assignment.status ? `<p style="color: var(--secText); margin: 5px 0px;"><i style="vertical-align: middle;" class="material-icons">assignment_return</i> Status: ` + (assignment.status == "submitted" ? "Submitted" : (assignment.status == "unsubmitted" ? "Unsubmitted" : (assignment.status == "graded" ? "Graded" : (assignment.status == "pending_review" ? "Pending Review" : assignment.status)))) + `</p>` : "") + `
1516` + ((assignment.worth !== undefined) && (assignment.worth !== null) ? `<p style="color: var(--secText); margin: 5px 0px;"><i style="vertical-align: middle;" class="material-icons">bar_chart</i> Total Points: ` + assignment.worth + `</p>` : "") + `
1517` + (assignment.grade ? `<p style="color: var(--secText); margin: 5px 0px;"><i style="vertical-align: middle;" class="material-icons">assessment</i> Points Earned: ` + assignment.grade + ` (` + assignment.letter + `)</p>` : "") + `
1518` + (assignment.submissionTypes ? `<p style="color: var(--secText); margin: 5px 0px;"><i style="vertical-align: middle;" class="material-icons">assignment</i> Submission Types: ` + assignment.submissionTypes.join(", ").replace(/online_/g, '').replace(/_/g, ' ') + `</p>` : "") + `
1519<p style="color: var(--secText); margin: 5px 0px;"><i style="vertical-align: middle;" class="material-icons">category</i> Group: ` + assignment.weight + `</p>
1520` + (assignment.outcomes ? assignment.outcomes.map(function (key) { return `<p style="color: var(--secText); margin: 5px 0px;"><i style="vertical-align: middle;" class="material-icons">adjust</i> ` + key + `</p>`; }).join("") : "") + `
1521` + (assignment.locked && assignment.lockedReason ? `<p style="color: var(--secText); margin: 5px 0px;"><i style="vertical-align: middle;font-family: 'Material Icons Extended'" class="material-icons">lock_outline</i> ` + assignment.lockedReason + `</p>` : "") + `
1522<p style="color: var(--secText); margin: 5px 0px;"><i style="vertical-align: middle;" class="material-icons">class</i> Class: ` + assignment.subject + `</p>
1523<br />
1524<div class="btn small outline" onclick="dtps.assignment(` + id + `, ` + classNum + `, true)"><i class="material-icons">assignment</i> Submissions</div>
1525<div class="btn small outline" onclick="window.open('` + assignment.url + `')"><i class="material-icons">open_in_new</i> Open in Canvas</div>
1526</div>
1527<div style="width: calc(60% - 7px); margin-top: 20px; margin-left: 5px; display: inline-block; overflow: hidden; vertical-align: middle;">
1528` + (assignment.rubric ? assignment.rubric.map(function (rubric) {
1529 if (rubric.ratings) {
1530 dtps.classes[classNum].tmp[rubric.id] = rubric.long_description
1531 return `
1532 <div style="margin: 20px 0px;">
1533 <h6 style="white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">` + rubric.description + `</h6>
1534 <div style="display: inline-block; margin-right: 20px; vertical-align: middle;">
1535<p style="color: var(--secText);" class="` + rubric.id + `"><a onclick="$('p.` + rubric.id + `').html(dtps.classes[` + classNum + `].tmp['` + rubric.id + `'])" href="#">Rubric details</a></p>
1536</div>
1537
1538<div style="display: inline-block; border-radius: 10px; overflow: hidden; font-size: 0; vertical-align: middle; background: ` + (rubric.score ? rubric.ratings[rubric.ratingItems.indexOf(rubric.score)].color : `linear-gradient(90deg, ` + rubric.ratings.map(function (rating) { return rating.color + ","; }).join("").slice(0, -1) + `)`) + `;">
1539` + (rubric.score !== undefined ? `
1540<div style="padding: 5px 10px; font-size: 18px; background-color: transparent; color: white; font-weight: bold; width: 200px; text-align: center; display: inline-block;">
1541` + rubric.score + "/" + rubric.points + " " + rubric.ratings[rubric.ratingItems.indexOf(rubric.score)].name + `
1542</div>` : rubric.ratings.map(function (rating) {
1543 return `
1544<div style="padding: 5px 10px; font-size: 18px; background-color: transparent; color: white; font-weight: bold; width: 120px; text-align: center; display: inline-block;">
1545` + rating.name + `
1546</div>`
1547 }).join("")) + `</div></div>`
1548 } else { return ""; }
1549 }).join("") : "") + `
1550</div>
1551`)
1552 }
1553 }
1554 }
1555 fluid.cards.close(".card.focus");
1556 fluid.modal(".card.details");
1557}
1558
1559dtps.iframeLoad = function (iframe) {
1560 var iFrameID = document.getElementById(iframe);
1561 if (iFrameID) {
1562 iFrameID.height = "";
1563 iFrameID.height = iFrameID.contentWindow.document.body.scrollHeight + "px";
1564 }
1565}
1566
1567//Fetches and displays announcements
1568dtps.announcements = function () {
1569 var context = [];
1570 for (var i = 0; i < dtps.classes.length; i++) {
1571 if (i == 0) { context.push("?context_codes[]=course_" + dtps.classes[i].id) } else { context.push("&context_codes[]=course_" + dtps.classes[i].id) }
1572 }
1573 dtps.webReq("canvas", "/api/v1/announcements" + context.join(""), function (resp) {
1574 var ann = JSON.parse(resp);
1575 var announcements = [];
1576 for (var i = 0; i < ann.length; i++) {
1577 var dtpsClass = null;
1578 for (var ii = 0; ii < dtps.classes.length; ii++) {
1579 if (dtps.classes[ii].id == ann[i].context_code.split("_")[1]) {
1580 dtpsClass = ii;
1581 }
1582 }
1583 announcements.push(`<div onclick="$(this).toggleClass('open');" style="cursor: pointer;" class="announcement card color ` + dtps.classes[dtpsClass].col + `">
1584<div class="label">` + dtps.classes[dtpsClass].subject + `</div>` + ann[i].message + `
1585</div>
1586`);
1587 }
1588 if ((dtps.selectedClass == "dash") && (dtps.masterContent == "assignments")) {
1589 jQuery(".dash .announcements").html(announcements.join(""));
1590 }
1591 });
1592};
1593
1594//Compiles and displays assignment due dates in the calendar
1595dtps.calendar = function (doneLoading) {
1596 dtps.log("BUILDING CAL")
1597 if ((dtps.selectedClass == "dash") && (dtps.masterContent == "assignments")) {
1598 calEvents = [];
1599 for (var i = 0; i < dtps.classes.length; i++) {
1600 if (dtps.classes[i].stream) {
1601 for (var ii = 0; ii < dtps.classes[i].stream.length; ii++) {
1602 if (dtps.classes[i].stream[ii].dueDate) {
1603 var styles = window.getComputedStyle($(".class." + i)[0]);
1604 calEvents.push({
1605 title: dtps.classes[i].stream[ii].title,
1606 start: moment(new Date(dtps.classes[i].stream[ii].dueDate)).toISOString(true),
1607 allDay: false,
1608 color: styles.getPropertyValue('--norm'),
1609 classNum: i,
1610 assignmentID: dtps.classes[i].stream[ii].id
1611 })
1612 }
1613 }
1614 }
1615 }
1616 if ($.fullCalendar !== undefined) {
1617 $('#calendar').fullCalendar({
1618 events: calEvents,
1619 header: {
1620 left: 'title',
1621 right: 'prev,next'
1622 },
1623 eventClick: function (calEvent, jsEvent, view) {
1624 dtps.assignment(calEvent.assignmentID, calEvent.classNum);
1625 },
1626 eventAfterAllRender: function () {
1627 $(".fc-prev-button").html(`<i class="material-icons">keyboard_arrow_left</i>`);
1628 $(".fc-next-button").html(`<i class="material-icons">keyboard_arrow_right</i>`);
1629 }
1630 });
1631 }
1632 $(".fc-prev-button").html(`<i class="material-icons">keyboard_arrow_left</i>`);
1633 $(".fc-next-button").html(`<i class="material-icons">keyboard_arrow_right</i>`);
1634
1635 }
1636}
1637
1638//Clears all Power+ data
1639dtps.clearData = function () {
1640 if (window.confirm("Clearing Power+ data will clear all local user data stored by Power+. This should be done if it is a new semester / school year or if you are having issues with Power+. Are you sure you want to clear all your Power+ data?")) {
1641 window.localStorage.clear()
1642 window.alert("Power+ data cleared. Reload the page to begin repopulating your userdata.")
1643 }
1644}
1645
1646//Renders chroma effect for selected class
1647dtps.chroma = function () {
1648 if (fluid.chroma) {
1649 if (fluid.chroma.on) {
1650 var classVar = "--light"
1651 if ($("body").hasClass("dark")) classVar = "--norm"
1652 if ($("body").hasClass("midnight")) classVar = "--dark"
1653 if (dtps.selectedClass !== "dash") {
1654 var dark = "white";
1655 var lighting = JSON.parse(`[
1656 [null,null,null,` + (dtps.selectedContent == "stream" ? '"white","white","white","white"' : "null,null,null,null") + `,` + (dtps.selectedContent == "pages" ? '"white","white","white","white"' : "null,null,null,null") + `,` + (dtps.selectedContent == "grades" ? '"white","white","white","white"' : "null,null,null,null") + `,null,null,null,null,null,null,null],
1657 [null,null,` + (dtps.classes[dtps.selectedClass].grade >= 10 ? `"` + dark + `"` : 0) + `,` + (dtps.classes[dtps.selectedClass].grade >= 20 ? `"` + dark + `"` : 0) + `,` + (dtps.classes[dtps.selectedClass].grade >= 30 ? `"` + dark + `"` : 0) + `,` + (dtps.classes[dtps.selectedClass].grade >= 40 ? `"` + dark + `"` : 0) + `,` + (dtps.classes[dtps.selectedClass].grade >= 50 ? `"` + dark + `"` : 0) + `,
1658 ` + (dtps.classes[dtps.selectedClass].grade >= 60 ? `"` + dark + `"` : 0) + `,` + (dtps.classes[dtps.selectedClass].grade >= 70 ? `"` + dark + `"` : 0) + `,` + (dtps.classes[dtps.selectedClass].grade >= 80 ? `"` + dark + `"` : 0) + `,` + (dtps.classes[dtps.selectedClass].grade >= 90 ? `"` + dark + `"` : 0) + `,` + (dtps.classes[dtps.selectedClass].grade >= 100 ? `"` + dark + `"` : 0) + `,null,null,null,null,null,null,null,null,null,null],
1659 [null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],
1660 [null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],
1661 [null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],
1662 [null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null]]`)
1663 var ele = $(".background")[0];
1664 if ($(".class." + dtps.selectedClass)[0] !== undefined) ele = $(".class." + dtps.selectedClass)[0];
1665 fluid.chroma.effect(tinycolor(getComputedStyle(ele).getPropertyValue(classVar)).saturate(70).toHexString(), lighting);
1666 } else {
1667 var ele = $(".background")[0];
1668 if ($(".class.masterStream")[0] !== undefined) ele = $(".class.masterStream")[0];
1669 fluid.chroma.static(getComputedStyle(ele).getPropertyValue(classVar));
1670 }
1671 }
1672 }
1673}
1674
1675//Renders the class list in the sidebar
1676dtps.showClasses = function (override) {
1677 var streamClass = "active"
1678 if (dtps.selectedClass !== "dash") var streamClass = "";
1679 dtps.classlist = [];
1680 for (var i = 0; i < dtps.classes.length; i++) {
1681 var name = dtps.classes[i].subject
1682 if (dtps.fullNames) name = dtps.classes[i].name
1683 dtps.classlist.push(`
1684 <div onclick="dtps.selectedClass = ` + i + `" class="class ` + i + ` ` + dtps.classes[i].col + `">
1685 <div class="name">` + name + `</div>
1686 <div class="grade val">` + (dtps.classes[i].letter !== false ? `<span style="display: block !important;" class="letter">` + dtps.classes[i].letter + `</span><span style="display: none !important;" class="points">` + dtps.classes[i].grade + `%</span>` : `
1687 <div class="spinner" style=" background-color: var(--norm); margin: 5px; "></div>`) + `</div>
1688 </div>
1689 `);
1690 }
1691 var googleClassDom = ""
1692 if (dtps.isolatedGoogleClasses) {
1693 dtps.classlist.push(`<div class="classDivider"></div>`)
1694 for (var i = 0; i < dtps.isolatedGoogleClasses.length; i++) {
1695 dtps.classlist.push(`<div style="display: none;" onclick="$('.sidebar .class').removeClass('active'); $(this).addClass('active'); $('body').addClass('isolatedGoogleClass'); dtps.selectedClass = 'isolatedGoogleClass'; $('.classContent').html(dtps.renderStream(dtps.googleClasses[` + dtps.isolatedGoogleClasses[i] + `].stream)); $('#headText').html('` + dtps.googleClasses[dtps.isolatedGoogleClasses[i]].name + `')" class="class isolated google ` + dtps.googleClasses[dtps.isolatedGoogleClasses[i]].id + `">
1696 <div style="width: 100%; padding-right: 10px;" class="name">` + dtps.googleClasses[dtps.isolatedGoogleClasses[i]].name + `</div>
1697 </div>`)
1698 }
1699 }
1700 if ((!Boolean(jQuery(".sidebar .class.masterStream")[0])) || override) {
1701 jQuery(".sidebar").html(`<h5 style="margin: 10px 0px 25px 0px; font-weight: 600; font-size: 27px; text-align: center;">Power+</h5>
1702<div onclick="dtps.selectedClass = 'dash';" class="class masterStream ` + streamClass + `">
1703 <div class="name">Dashboard</div>
1704 <div class="grade"><i class="material-icons">dashboard</i></div>
1705 </div>
1706 <div class="classDivider"></div>
1707 ` + dtps.classlist.join(""));
1708 if (dtps.selectedClass !== "dash") $(".class." + dtps.selectedClass).addClass("active");
1709 if ($(".btn.pages").hasClass("active")) { $(".btn.pages").removeClass("active"); $(".btn.stream").addClass("active"); dtps.classStream(dtps.selectedClass); dtps.selectedContent = "stream"; }
1710 if ($(".btn.discuss").hasClass("active")) { $(".btn.discuss").removeClass("active"); $(".btn.stream").addClass("active"); dtps.classStream(dtps.selectedClass); dtps.selectedContent = "stream"; }
1711 $(".class:not(.google)").click(function (event) {
1712 if (dtps.selectedClass == "dash") $('body').addClass('dashboard');
1713 if (dtps.selectedClass !== "dash") $('body').removeClass('dashboard');
1714 dtps.chroma();
1715 $('body').removeClass('isolatedGoogleClass');
1716 $(".btn.google").hide();
1717 $(".background").addClass("trans");
1718 if (dtps.classes[dtps.selectedClass]) {
1719 if (dtps.classes[dtps.selectedClass].image && (fluid.get("pref-classImages") !== "true")) {
1720 $(".cover.image").css("background-image", 'url("' + dtps.classes[dtps.selectedClass].image + '")');
1721 $(".background").css("opacity", '0.90');
1722 $(".background").css("filter", 'none');
1723 } else {
1724 $(".cover.image").css("background-image", 'none');
1725 $(".background").css("opacity", '1');
1726 $(".background").css("filter", 'blur(10px)');
1727 }
1728 } else {
1729 $(".cover.image").css("background-image", 'none');
1730 $(".background").css("opacity", '1');
1731 $(".background").css("filter", 'blur(10px)');
1732 }
1733 clearTimeout(dtps.bgTimeout);
1734 dtps.bgTimeout = setTimeout(function () {
1735 dtps.oldTheme = "squidward theme park";
1736 document.dispatchEvent(new CustomEvent('fluidTheme'))
1737 $(".background").removeClass("trans");
1738 }, 500);
1739 $(".background").removeClass(jQuery.grep($(".background").attr("class").split(" "), function (item, index) {
1740 return item.trim().match(/^filter_/);
1741 })[0]);
1742 $(".cacaoBar .tab.active").removeClass(jQuery.grep($(".cacaoBar .tab.active").attr("class").split(" "), function (item, index) {
1743 return item.trim().match(/^filter_/);
1744 })[0]);
1745 $(".header").removeClass(jQuery.grep($(".header").attr("class").split(" "), function (item, index) {
1746 return item.trim().match(/^filter_/);
1747 })[0]);
1748 $(".classContent").removeClass(jQuery.grep($(".classContent").attr("class").split(" "), function (item, index) {
1749 return item.trim().match(/^filter_/);
1750 })[0]);
1751 if (dtps.classes[dtps.selectedClass]) {
1752 if (dtps.classes[dtps.selectedClass].google) {
1753 $(".btn.google").show();
1754 };
1755 $(".background").addClass(dtps.classes[dtps.selectedClass].col);
1756 $(".cacaoBar .tab.active").addClass(dtps.classes[dtps.selectedClass].col);
1757 $(".header").addClass(dtps.classes[dtps.selectedClass].col);
1758 $(".classContent").addClass(dtps.classes[dtps.selectedClass].col);
1759 }
1760 $(this).siblings().removeClass("active")
1761 $(this).addClass("active")
1762 $(".header h1").html($(this).children(".name").text())
1763 $(".cacaoBar .tab.active span").html($(this).children(".name").text())
1764 if (dtps.selectedClass == "dash") {
1765 $(".cacaoBar .tab.active i").html("dashboard")
1766 } else {
1767 $(".cacaoBar .tab.active i").html(($(".btn." + dtps.selectedContent + " i").html() ? $(".btn." + dtps.selectedContent + " i").html() : "dashboard"))
1768 }
1769 if (!dtps.classes[dtps.selectedClass]) {
1770 $(".header .btns").hide();
1771 } else {
1772 $(".header .btns:not(.master)").show();
1773 }
1774 if ((dtps.selectedContent == "stream") && (dtps.classes[dtps.selectedClass])) dtps.classStream(dtps.selectedClass)
1775 if ((dtps.selectedContent == "grades") && (dtps.classes[dtps.selectedClass])) dtps.gradebook(dtps.selectedClass)
1776 if (dtps.selectedClass == "dash") dtps.masterStream(true);
1777 if (dtps.selectedClass == "announcements") dtps.announcements();
1778 if (dtps.classes[dtps.selectedClass]) { if (dtps.classes[dtps.selectedClass].pagesTab) { $(".btns .btn.pages").show(); } else { $(".btns .btn.pages").hide(); } }
1779 if (dtps.classes[dtps.selectedClass]) { if (dtps.classes[dtps.selectedClass].noOutcomes) { $(".btns .btn.grades").hide(); } else { $(".btns .btn.grades").show(); } }
1780 });
1781 }
1782 if (override == "first") {
1783 if (dtps.currentClass) {
1784 $(".class." + dtps.selectedClass).click();
1785 }
1786 }
1787}
1788
1789//Fetches Google Classroom assignment data for all Google Classes
1790dtps.googleStream = function () {
1791 dtps.log("FETCHING GOOGLE ASSIGNMENTS")
1792 function googleStream(i) {
1793 if (dtps.googleClasses[i]) {
1794 gapi.client.classroom.courses.courseWork.list({ courseId: dtps.googleClasses[i].id }).then(function (resp) {
1795 dtps.googleClasses[i].rawData = resp.result;
1796 dtps.googleClasses[i].stream = [];
1797 for (var ii = 0; ii < resp.result.courseWork.length; ii++) {
1798 if (resp.result.courseWork[ii].dueDate) {
1799 var due = new Date(resp.result.courseWork[ii].dueDate.year, resp.result.courseWork[ii].dueDate.month - 1, resp.result.courseWork[ii].dueDate.day - 1);
1800 } else {
1801 var due = new Date();
1802 }
1803 dtps.googleClasses[i].stream.push({
1804 title: resp.result.courseWork[ii].title,
1805 due: due.toHumanString(),
1806 dueDate: due.toISOString(),
1807 turnedIn: false,
1808 google: true,
1809 url: resp.result.courseWork[ii].alternateLink + "?authuser=" + dtps.user.google.getEmail(),
1810 letter: "--",
1811 grade: "/" + resp.result.courseWork[ii].maxPoints
1812 })
1813 if (dtps.googleClasses[i].psClass !== undefined) {
1814 dtps.googleClasses[i].stream[ii].class = dtps.googleClasses[i].psClass;
1815 dtps.googleClasses[i].stream[ii].subject = dtps.classes[dtps.googleClasses[i].psClass].subject;
1816 }
1817 }
1818 if (i < (dtps.googleClasses.length - 1)) googleStream(i + 1);
1819 });
1820 } else {
1821 if (i < (dtps.googleClasses.length - 1)) googleStream(i + 1);
1822 }
1823 }
1824 googleStream(0);
1825}
1826
1827//Authenticates the user for Google Classroom integration
1828dtps.googleAuth = function () {
1829 dtps.log("GOOGLE AUTH")
1830 dtps.user.google = gapi.auth2.getAuthInstance().currentUser.get().getBasicProfile();
1831 $(".items img").attr("src", dtps.user.google.getImageUrl())
1832 gapi.client.classroom.courses.list({ pageSize: 40 }).then(function (resp) {
1833 dtps.googleClasses = resp.result.courses;
1834 if (dtps.googleClasses == undefined) {
1835 dtps.gapis();
1836 } else {
1837 for (var i = 0; i < dtps.googleClasses.length; i++) {
1838 if (dtps.googleClasses[i].courseState == "ACTIVE") {
1839 var match = null;
1840 for (var ii = 0; ii < dtps.classes.length; ii++) {
1841 if (dtps.googleClasses[i].name.includes(dtps.classes[ii].subject)) match = ii;
1842 }
1843 if (match !== null) {
1844 if (dtps.classes[match].google == undefined) {
1845 dtps.classes[match].google = dtps.googleClasses[i]
1846 dtps.googleClasses[i].psClass = match
1847 }
1848 }
1849 }
1850 }
1851 dtps.isolatedGoogleClasses = [];
1852 var isolatedDom = [];
1853 for (var i = 0; i < dtps.googleClasses.length; i++) {
1854 if ((dtps.googleClasses[i].psClass == undefined) && (dtps.googleClasses[i].courseState == "ACTIVE")) {
1855 dtps.isolatedGoogleClasses.push(i)
1856 isolatedDom.push(`<br /><br />
1857 <div onclick="jQuery('.google.isolated.class.` + dtps.googleClasses[i].id + `').toggle()" class="switch"><span class="head"></span></div>
1858 <div class="label">` + dtps.googleClasses[i].name + `</div>`)
1859 }
1860 }
1861 $(".isolatedGClassList").html(isolatedDom.join("").slice(12));
1862 dtps.showClasses(true);
1863 dtps.googleStream();
1864 fluid.init();
1865 }
1866 });
1867}
1868
1869//Saves grade data locally for grade trend
1870dtps.logGrades = function () {
1871 if ((window.localStorage.dtpsGradeTrend !== "false") && (window.localStorage.dtpsGradeTrend !== undefined)) {
1872 if (window.localStorage.dtpsGradeTrend.startsWith("{")) {
1873 dtps.log("LOGGING GRADES")
1874 var now = new Date();
1875 var start = new Date(now.getFullYear(), 0, 0);
1876 var diff = (now - start) + ((start.getTimezoneOffset() - now.getTimezoneOffset()) * 60 * 1000);
1877 var oneDay = 1000 * 60 * 60 * 24;
1878 var day = Math.floor(diff / oneDay);
1879 var gradeData = JSON.parse(window.localStorage.dtpsGradeTrend);
1880 for (var i = 0; i < dtps.classes.length; i++) {
1881 if (dtps.classes[i].grade !== "--") {
1882 if (!gradeData[dtps.classes[i].id]) gradeData[dtps.classes[i].id] = { oldGrade: dtps.classes[i].grade, lastUpdated: new Date(), currentGrade: dtps.classes[i].grade }
1883 if (dtps.classes[i].grade !== gradeData[dtps.classes[i].id].currentGrade) {
1884 gradeData[dtps.classes[i].id].oldGrade = gradeData[dtps.classes[i].id].currentGrade
1885 gradeData[dtps.classes[i].id].currentGrade = dtps.classes[i].grade
1886 gradeData[dtps.classes[i].id].lastUpdated = new Date();
1887 }
1888 }
1889 }
1890 window.localStorage.setItem("dtpsGradeTrend", JSON.stringify(gradeData));
1891 }
1892 }
1893}
1894
1895//Enables/Disables grade trend
1896dtps.gradeTrend = function (ele) {
1897 var temp = ele;
1898 if ($(temp).hasClass('head')) { temp = $(ele).parent()[0] };
1899 if (!$(temp).hasClass('active')) {
1900 window.localStorage.setItem('dtpsGradeTrend', 'false');
1901 swal('Grade trend is disabled. All data stored on your computer by grade trend has been deleted.', { icon: 'success', });
1902 } else {
1903 swal({ title: 'Enable grade trend', text: 'By enabling grade trend, Power+ will store a copy of your grades locally on your computer every time you use Power+. When a grade for one of your classes changes, Power+ will tell you how much it changed in the grades tab of the class. The grade trend setting applies to all classes.', buttons: true }).then((enable) => {
1904 if (enable) {
1905 window.localStorage.setItem('dtpsGradeTrend', '{}'); swal('Grade trend is enabled', { icon: 'success', });
1906 } else {
1907 $(temp).removeClass('active')
1908 }
1909 });
1910 }
1911}
1912
1913//cacao variables
1914dtps.states = {};
1915letters = 'abcdefghijklmnopqrstuvwxyz'.split('')
1916letter = 1;
1917
1918//Tab click handler (cacao)
1919dtps.cacao = function (state) {
1920 if (String(fluid.get("pref-cacao")) == "true") {
1921 if (state == "new") {
1922 $(".cacaoBar .tab.new").before(`<div onclick="dtps.cacao('` + letters[letter] + `');" state="` + letters[letter] + `" class="tab ` + letters[letter] + `"><i class="material-icons">dashboard</i><span>Dashboard</span></div>`)
1923 letter++;
1924 } else {
1925 dtps.saveState($(".cacaoBar .tab.active").attr("state"));
1926 $(".cacaoBar .tab." + state).siblings().removeClass("active")
1927 $(".cacaoBar .tab." + state).addClass("active")
1928 dtps.loadState(state);
1929 }
1930 }
1931}
1932
1933//Save state (cacao)
1934dtps.saveState = function (state) {
1935 if (String(fluid.get("pref-cacao")) == "true") {
1936 dtps.states[state] = {
1937 class: dtps.selectedClass,
1938 content: dtps.selectedContent,
1939 page: dtps.selectedPage,
1940 classContent: $(".classContent").html(),
1941 scrollTop: document.documentElement.scrollTop,
1942 scrollLeft: document.documentElement.scrollLeft
1943 }
1944 }
1945}
1946
1947//Load state (cacao)
1948dtps.loadState = function (stateKey) {
1949 if (String(fluid.get("pref-cacao")) == "true") {
1950 var state = dtps.states[stateKey]
1951 if (state) {
1952 dtps.selectedClass = state.class;
1953 dtps.selectedContent = state.content;
1954 dtps.selectedPage = state.page;
1955 dtps.showClasses(true);
1956 $(".class." + (state.class == "dash" ? "masterStream" : state.class)).click();
1957 if (state.content == "pages") {
1958 $(".header .btn.pages").click();
1959 $(".sidebar .btn." + dtps.selectedPage).click();
1960 }
1961 $(".classContent").html(state.classContent);
1962 document.documentElement.scrollTop = state.scrollTop;
1963 document.documentElement.scrollLeft = state.scrollLeft;
1964 } else {
1965 //no state present, load default dashboard state
1966 dtps.selectedClass = "dash"
1967 dtps.selectedContent = "stream"
1968 document.documentElement.scrollTop = 0;
1969 document.documentElement.scrollLeft = 0;
1970 dtps.showClasses(true);
1971 $(".class.masterStream").click();
1972 dtps.saveState(stateKey)
1973 }
1974 }
1975}
1976
1977//Renders Power+ and removes all Canvas HTML
1978//Seperated into static HTML and javascript-based things
1979dtps.render = function () {
1980 if (dtps.embedded) {
1981 jQuery("head").html("");
1982 $("body").addClass("dashboard");
1983 }
1984 document.title = "Power+" + dtps.trackSuffix;
1985
1986 //Cacao pref
1987 if (fluid.get("pref-cacao") == "true") { $("body").addClass("cacao"); $('.sidebar').addClass("acrylicMaterial"); }
1988 document.addEventListener("pref-cacao", function (e) {
1989 if (String(e.detail) == "true") {
1990 $("body").addClass("cacao");
1991 $('.sidebar').addClass("acrylicMaterial");
1992 } else {
1993 $("body").removeClass("cacao");
1994 $('.sidebar').removeClass("acrylicMaterial");
1995 }
1996 })
1997
1998 //Full names pref
1999 if (fluid.get("pref-fullNames") == "true") { dtps.fullNames = true; }
2000 document.addEventListener("pref-fullNames", function (e) {
2001 if (String(e.detail) == "true") { dtps.fullNames = true; } else { dtps.fullNames = false; }
2002 dtps.showClasses(true);
2003 })
2004
2005 //Hide grades pref
2006 if (fluid.get("pref-hideGrades") == "true") { jQuery('body').addClass('hidegrades'); }
2007 document.addEventListener("pref-hideGrades", function (e) {
2008 if (String(e.detail) == "true") { jQuery('body').addClass('hidegrades'); } else { jQuery('body').removeClass('hidegrades'); }
2009 dtps.showClasses(true);
2010 })
2011
2012 if (dtps.embedded) {
2013 jQuery("body").html(`
2014 <div style="line-height: 0;" class="sidebar">
2015 </div>
2016 <div class="cover image"></div>
2017 <div class="background trans"></div>
2018<div class="header">
2019 <h1 id="headText">Dashboard</h1>
2020 <div style="display: none;" class="btns row tabs">
2021 <button onclick="dtps.selectedContent = 'stream'; dtps.chroma(); $('.cacaoBar .tab.active i').html('assignment'); dtps.classStream(dtps.selectedClass);" class="btn active stream">
2022 <i class="material-icons">library_books</i>
2023 Coursework
2024 </button>
2025 <button onclick="dtps.selectedContent = 'google'; dtps.chroma(); $('.cacaoBar .tab.active i').html('class'); $('.classContent').html(dtps.renderStream(dtps.classes[dtps.selectedClass].google.stream))" class="btn google">
2026 <i class="material-icons">class</i>
2027 google_logo Classroom
2028 </button>
2029 <button onclick="dtps.selectedContent = 'discuss'; dtps.chroma(); $('.cacaoBar .tab.active i').html('group'); dtps.loadTopics(dtps.selectedClass);" class="btn discuss">
2030 <i class="material-icons">forum</i>
2031 Discussions
2032 </button>
2033 <button onclick="dtps.selectedContent = 'pages'; dtps.chroma(); $('.cacaoBar .tab.active i').html('insert_drive_file'); dtps.loadPages(dtps.selectedClass);" class="btn pages">
2034 <i class="material-icons">insert_drive_file</i>
2035 Pages
2036 </button>
2037 <button onclick="dtps.selectedContent = 'grades'; dtps.chroma(); $('.cacaoBar .tab.active i').html('assessment'); dtps.gradebook(dtps.selectedClass);" class="btn grades">
2038 <i class="material-icons">assessment</i>
2039 Grades
2040 </button>
2041 </div>
2042 </div>
2043 <div class="classContent">
2044 <div class="spinner"></div>
2045 </div>
2046<div style="height: calc(100vh - 50px); overflow: auto !important;" class="card withnav focus close container abt-new"></div>
2047
2048<div style="display: none;" class="cacaoBar acrylicMaterial">
2049 <div onclick="dtps.cacao('a')" state="a" class="tab a active"><i class="material-icons">dashboard</i><span>Dashboard</span></div>
2050 <div onclick="dtps.cacao('new')" class="tab icon new"><i class="material-icons">add</i></div>
2051</div>
2052 <div class="items">
2053 </div>
2054<div style="border-radius: 30px;" class="card focus changelog close container">
2055<i onclick="fluid.cards.close('.card.changelog')" class="material-icons close">close</i>
2056<h3>What's new in Power+</h3>
2057<h5>There was an error loading the changelog. Try again later.</h5>
2058</div>
2059<div style="border-radius: 30px;" class="card focus details close container">
2060<i onclick="fluid.cards.close('.card.details')" class="material-icons close">close</i>
2061<p>An error occured</p>
2062</div>
2063
2064<div style="border-radius: 30px; top: 50px; background-color: white; color: black;" class="card focus close moduleURL container">
2065<i style="color: black !important;" onclick="fluid.cards.close('.card.moduleURL'); $('#moduleIFrame').attr('src', '');" class="material-icons close">close</i>
2066<br /><br />
2067<iframe style="width: 100%; height: calc(100vh - 175px); border: none;" id="moduleIFrame"></iframe>
2068</div>
2069
2070<div style="border-radius: 30px; top: 50px;" class="card focus close classInfoCard container">
2071<i onclick="fluid.cards.close('.card.classInfoCard')" class="material-icons close">close</i>
2072<h4>An error occured</h4>
2073</div>
2074
2075<div style="border-radius: 30px; top: 50px;" class="card focus close outcomeCard container">
2076<i onclick="fluid.cards.close('.card.outcomeCard')" class="material-icons close">close</i>
2077<h4>An error occured</h4>
2078</div>
2079
2080<style id="colorCSS"></style>
2081<script>fluid.init();</script>
2082 `);
2083 }
2084
2085
2086 jQuery("#colorCSS").html(dtps.colorCSS ? dtps.colorCSS.join("") : "")
2087 if (dtps.embedded) dtps.renderLite();
2088
2089
2090
2091 var idleTime = 0;
2092 $(document).ready(function () {
2093 //Increment the idle time counter every minute.
2094 var idleInterval = setInterval(timerIncrement, 60000); // 1 minute
2095
2096 //Zero the idle timer on mouse movement.
2097 $(this).mousemove(function (e) {
2098 idleTime = 0;
2099 });
2100 $(this).keypress(function (e) {
2101 idleTime = 0;
2102 });
2103 });
2104
2105 function timerIncrement() {
2106 idleTime = idleTime + 1;
2107 if (idleTime > 60) { // Clear all saved assignment data to get the latest info
2108 dtps.requests = {};
2109 dtps.http = {};
2110 dtps.classesReady = 0;
2111 for (var i = 0; i < dtps.classes.length; i++) {
2112 dtps.classStream(i, true);
2113 }
2114 }
2115 }
2116
2117 var getURL = "https://api.github.com/repos/jottocraft/dtps/commits?path=init.js";
2118 //if (dtps.trackSuffix !== "") var getURL = "https://api.github.com/repos/jottocraft/dtps/commits?path=dev.js";
2119 jQuery.getJSON(getURL, function (data) {
2120 jQuery(".buildInfo").html("build " + data[0].sha.substring(7, 0));
2121 jQuery(".buildInfo").click(function () {
2122 window.open("https://github.com/jottocraft/dtps/commit/" + data[0].sha)
2123 });
2124 })
2125 jQuery.getScript("https://cdnjs.cloudflare.com/ajax/libs/showdown/1.8.6/showdown.min.js", function () {
2126 markdown = new showdown.Converter();
2127 jQuery.getJSON("https://api.github.com/repos/jottocraft/dtps/releases", function (data) {
2128 jQuery(".card.changelog").html(`<i onclick="fluid.cards.close('.card.changelog')" class="material-icons close">close</i>` + markdown.makeHtml(data[0].body));
2129 if (data[0].tag_name == dtps.readableVer.replace(dtps.trackSuffix, "")) {
2130 localStorage.setItem('dtps', dtps.ver);
2131 if (dtps.showChangelog) dtps.changelog();
2132 }
2133 $(".btn.changelog").show();
2134 });
2135 });
2136
2137 fluid.theme();
2138 dtps.showClasses("first");
2139 //dtps.gapis();
2140
2141 if (dtps.embedded) {
2142 $("link").remove();
2143 jQuery("<link/>", {
2144 rel: "shortcut icon",
2145 type: "image/png",
2146 href: "https://dtps.js.org/favicon.png"
2147 }).appendTo("head");
2148 jQuery("<link/>", {
2149 rel: "stylesheet",
2150 type: "text/css",
2151 href: "https://dtps.js.org/fluid.css"
2152 }).appendTo("head");
2153 jQuery("<link/>", {
2154 rel: "stylesheet",
2155 type: "text/css",
2156 href: "https://cdnjs.cloudflare.com/ajax/libs/fullcalendar/3.9.0/fullcalendar.min.css"
2157 }).appendTo("head");
2158
2159 if (dtps.trackSuffix !== "") {
2160 jQuery("<link/>", {
2161 rel: "stylesheet",
2162 type: "text/css",
2163 href: "https://dtps.js.org/dev.css"
2164 }).appendTo("head");
2165 } else {
2166 jQuery("<link/>", {
2167 rel: "stylesheet",
2168 type: "text/css",
2169 href: "https://dtps.js.org/dtps.css"
2170 }).appendTo("head");
2171 }
2172
2173 jQuery("<link/>", {
2174 rel: "stylesheet",
2175 type: "text/css",
2176 href: "https://fonts.googleapis.com/icon?family=Material+Icons+Extended"
2177 }).appendTo("head");
2178
2179 }
2180
2181 jQuery('.classContent').bind('heightChange', function () {
2182 jQuery(".sidebar").css("height", Number(jQuery(".classContent").css("height").slice(0, -2)))
2183 });
2184
2185 if (dtps.embedded) fluid.onLoad();
2186}
2187
2188//Render function that can ran before classes are ready
2189dtps.renderLite = function () {
2190 if (fluid.chroma) {
2191 fluid.chroma.supported(function (res) {
2192 if (res) {
2193 //Razer Synapse installed
2194 $(".razerChroma").show();
2195
2196 //Razer Chroma pref
2197 if (fluid.get("pref-chromaEffects") == "true") { if (!fluid.chroma.on) { fluid.chroma.init(dtps.chromaProfile, () => fluid.chroma.static(getComputedStyle($(".background")[0]).getPropertyValue("--dark"))); } }
2198 document.addEventListener("pref-chromaEffects", function (e) {
2199 if (String(e.detail) == "true") { if (!fluid.chroma.on) { fluid.chroma.init(dtps.chromaProfile, () => fluid.chroma.static(getComputedStyle($(".background")[0]).getPropertyValue("--dark"))); } } else { fluid.chroma.disable(); }
2200 })
2201 }
2202 })
2203 }
2204 var trackDom = "";
2205 if (dtps.trackSuffix !== "") {
2206 trackDom = `<div style="display:inline-block;font-size: 16px; padding: 3px 4px;background-color: ` + dtps.trackColor + `" class="beta badge notice">` + dtps.trackSuffix.replace(" (", "").replace(")", "") + `</div>`
2207 } else {
2208 trackDom = ``;
2209 }
2210 var verDom = dtps.readableVer.replace(dtps.trackSuffix, "");
2211 if (dtps.trackSuffix !== "") {
2212 verDom = `<div class="buildInfo" style="display: inline-block;font-size: 12px;cursor: pointer;"></div>`
2213 } else {
2214 verDom = dtps.readableVer.replace(dtps.trackSuffix, "");
2215 }
2216 jQuery(".card.abt-new").html(`<i onclick="fluid.cards.close('.card.abt-new')" class="material-icons close">close</i>
2217 <div class="sidenav" style="position: fixed; height: calc(100% - 50px); border-radius: 20px 0px 0px 20px;">
2218 <div class="title">
2219 <img src="https://dtps.js.org/outline.png" style="width: 50px;vertical-align: middle;padding: 7px; padding-top: 14px;" />
2220 <div style="vertical-align: middle; display: inline-block;">
2221 <h5 style="font-weight: bold;display: inline-block;vertical-align: middle;">Power+</h5>` + trackDom + `
2222 <p style="font-weight: bold;">` + verDom + `</p>
2223 </div>
2224 </div>
2225 <div onclick="$('.abtpage').hide();$('.abtpage.settings').show();" class="item active">
2226 <i class="material-icons">settings</i> Settings
2227 </div>
2228 <div onclick="$('.abtpage').hide();$('.abtpage.classes').show();" class="item">
2229 <i class="material-icons">book</i> Classes
2230 </div>
2231 <div onclick="$('.abtpage').hide();$('.abtpage.experiments').show();" style="/*display: none !important;*/" class="item sudo">
2232 <i class="material-icons" style="font-family: 'Material Icons Extended'">experiment</i> Experiments
2233 </div>
2234 <div onclick="$('.abtpage').hide();$('.abtpage.debug').show();" class="item dev">
2235 <i class="material-icons">bug_report</i> Debugging
2236 </div>
2237 <div onclick="$('.abtpage').hide();$('.abtpage.about').show(); if ($('body').hasClass('sudo')) { $('.advancedOptions').show(); $('.advOp').hide(); } else { $('.advancedOptions').hide(); $('.advOp').show(); }" class="item">
2238 <i class="material-icons">info</i> About
2239 </div>
2240 </div>
2241 <div style="min-height: 100%" class="content">
2242<div class="abtpage settings">
2243 <h5>Settings</h5>
2244 <br />
2245 <p>Theme</p>
2246 <div class="btns row themeSelector"></div>
2247 <br />
2248 <p>Grades</p>
2249 <div onclick="fluid.set('pref-calcGrades')" class="switch pref-calcGrades active"><span class="head"></span></div>
2250 <div class="label"><i class="material-icons">functions</i> Calculate class grades (beta)</div>
2251 <br /><br />
2252 <div onclick="fluid.set('pref-hideGrades')" class="switch pref-hideGrades"><span class="head"></span></div>
2253 <div class="label"><i class="material-icons">visibility_off</i> Hide class grades</div>
2254 <!-- <br /><br />
2255 <div onclick="dtps.gradeTrend(this);" class="switch` + (String(window.localStorage.dtpsGradeTrend).startsWith("{") ? " active" : "") + `"><span class="head"></span></div>
2256 <div class="label"><i class="material-icons">timeline</i> Show grade trend</div> -->
2257 <br /><br />
2258 <p>Classes</p>
2259 <div onclick="fluid.set('pref-fullNames')" class="switch pref-fullNames"><span class="head"></span></div>
2260 <div class="label"><i class="material-icons">title</i> Show full class names</div>
2261 <br /><br />
2262 <div onclick="fluid.set('pref-classImages')" class="switch pref-classImages"><span class="head"></span></div>
2263 <div class="label"><i class="material-icons">image</i> Hide class images</div>
2264 <br style="display: none;" class="razerChroma" /><br style="display: none;" class="razerChroma" />
2265 <div style="display: none" onclick="fluid.set('pref-chromaEffects')" class="switch pref-chromaEffects razerChroma"><span class="head"></span></div>
2266 <div class="label razerChroma" style="display: none;"><img style="width: 26px;vertical-align: middle;margin-right: 2px;" src="https://i.imgur.com/FLwviAM.png" class="material-icons" /img> Razer Chroma Effects (beta)</div>
2267 <div class="embeddedOptions">
2268 <br /><br />
2269 <p>Power+</p>
2270 <div onclick="fluid.set('pref-autoLoad')" class="switch pref-autoLoad"><span class="head"></span></div>
2271 <div class="label"><i class="material-icons">code</i> Automatically load Power+</div>
2272 <!-- <br /><br />
2273 <div onclick="fluid.set('pref-devChannel')" class="switch pref-devChannel"><span class="head"></span></div>
2274 <div class="label"><i class="material-icons">bug_report</i> Use the unstable (dev) version of Power+</div> -->
2275 </div>
2276</div>
2277<div style="display: none;" class="abtpage classes">
2278<h5>Classes</h5>
2279<button onclick="dtps.schedule()" class="btn"><i class="material-icons">access_time</i>Schedule classes</button>
2280<br /><br />
2281<div id="classGrades">
2282<h5>Grades</h5>
2283<div class="gradeDom">
2284<p>Loading...</p>
2285</div>
2286</div>
2287<!--
2288<br /><br />
2289<div class="googleClassroom sudo">
2290 <h5>google_logo Classes</h5>
2291 <button class="btn" onclick="window.alert('On the page that opens, select Project DTPS, and click Remove Access.'); window.open('https://myaccount.google.com/permissions?authuser=' + dtps.user.google.getEmail());"><i class="material-icons">link_off</i>Unlink Google Classroom</button>
2292 <br /><br />
2293 <p>Classes listed below could not be associated with a Canvas class. You can choose which classes to show in the sidebar.</p>
2294 <div class="isolatedGClassList"><p>Loading...</p></div>
2295</div>
2296<div class="googleSetup sudo">
2297 <h5>google_logo Classroom <div class="badge">beta</div></h5>
2298 <p>Link google_logo Classroom to see assignments and classes from both Canvas and Google.</p>
2299 <p>If Power+ thinks one of your Canvas classes also has a Google Classroom, it'll add a Google Classroom tab to that class. You can choose which extra classes to show in the sidebar.</p>
2300 <button onclick="if (window.confirm('EXPERIMENTAL FEATURE: Google Classroom features are still in development. Continue at your own risk. Please leave feedback by clicking the feedback button at the top right corner of Power+.')) { dtps.googleSetup = true; dtps.webReq('psGET', 'https://dtechhs.learning.powerschool.com/do/account/logout', function() { gapi.auth2.getAuthInstance().signIn().catch(function(err) { /*window.location.reload()*/ console.warn(err); }); })}" class="btn sudo"><i class="material-icons">link</i>Link Google Classroom</button>
2301</div>
2302-->
2303</div>
2304<div style="display: none;" class="abtpage extension">
2305 <h5>Extension</h5>
2306 <div class="extensionDom" ></div>
2307</div>
2308<div style="display: none;" class="abtpage apiExplorer">
2309 <h5>API Explorer</h5>
2310 <ul>` + dtps.explorer.map(function (item) {
2311 return `<li style="cursor: pointer;" onclick="dtps.explore('` + item.path + `')">` + item.name + `</li>`
2312 }).join("") + `</ul>
2313<br />
2314<pre><code id="explorerData">Select an item
2315</code></pre>
2316</div>
2317<div style="display: none;" class="abtpage experiments">
2318<div class="sudo">
2319 <h5>Experiments</h5>
2320 <p>WARNING: Features listed below are not officially supported and can break Power+. Use at your own risk.</p>
2321 <p>Want to test out new features as they are developed? <a href="https://dtps.js.org/devbookmark.txt">Try the dev version of Power+</a>.</p>
2322<br />
2323<div onclick="fluid.set('pref-cacao')" class="switch pref-cacao"><span class="head"></span></div>
2324<div class="label"><i class="material-icons">view_carousel</i> Project Cacao</div>
2325<br /><br />
2326</div>
2327</div>
2328<div style="display: none;" class="abtpage debug">
2329<div class="dev">
2330 <h5>Debugging</h5>
2331 <br>
2332 <div id="dtpsLocal" onclick="fluid.set('pref-localDtps')" class="switch pref-localDtps"><span class="head"></span></div>
2333 <div class="label"><i class="material-icons">public</i> Use local copy of Project DTPS</div>
2334<br /><br>
2335<button onclick="$('body').removeClass('sudo');$('body').removeClass('contributor');$('body').removeClass('dev');">Remove badges</button>
2336 <br /><br>
2337<span class="log">
2338</span>
2339</div>
2340</div>
2341<div style="display: none;" class="abtpage about">
2342<h5>About</h5>
2343<div class="card" style="padding: 10px 20px; box-shadow: none !important; border: 2px solid var(--elements); margin-top: 20px;">
2344<img src="https://dtps.js.org/outline.png" style="height: 50px; margin-right: 10px; vertical-align: middle; margin-top: 20px;" />
2345<div style="display: inline-block; vertical-align: middle;">
2346<h4 style="font-weight: bold; font-size: 32px; margin-bottom: 0px;">Power+</h4>
2347<div style="font-size: 16px; margin-top: 5px;">` + dtps.readableVer + ` <div class="buildInfo" style="display: inline-block;margin: 0px 5px;font-size: 12px;cursor: pointer;"></div></div>
2348</div>
2349<div style="margin-top: 15px; margin-bottom: 7px;"><a onclick="dtps.changelog();" style="color: var(--lightText); margin: 0px 5px;" href="#"><i class="material-icons" style="vertical-align: middle">update</i> Changelog</a>
2350<a onclick="if (window.confirm('Are you sure you want to uninstall Power+? The extension will be removed and all of your Power+ data will be erased.')) { document.dispatchEvent(new CustomEvent('extensionData', { detail: 'extensionUninstall' })); window.localStorage.clear(); window.alert('Power+ has been uninstalled. Reload the page to go back to Canvas.') }" style="color: var(--lightText); margin: 0px 5px;" href="#"><i class="material-icons" style="vertical-align: middle">delete_outline</i> Uninstall</a>
2351<a style="color: var(--lightText); margin: 0px 5px;" href="https://github.com/jottocraft/dtps"><i class="material-icons" style="vertical-align: middle">code</i> GitHub</a></div>
2352</div>
2353 <div class="card" style="padding: 10px 20px; box-shadow: none !important; border: 2px solid var(--elements); margin-top: 20px;">
2354<img src="` + dtps.user.avatar_url + `" style="height: 50px; margin-right: 10px; vertical-align: middle; margin-top: 20px; border-radius: 50%;" />
2355<div style="display: inline-block; vertical-align: middle;">
2356<h4 style="font-weight: bold; font-size: 32px; margin-bottom: 0px;">` + dtps.user.name + ` <span style="font-size: 12px;">` + dtps.user.id + `</span></h4>
2357
2358
2359<div style="display:inline-block;" class="badge marketer">marketer<i style="vertical-align: middle;" class="material-icons marketer">work_outline</i></div>
2360<div style="display:inline-block;" class="badge sudo">tester<i style="vertical-align: middle;" class="material-icons sudo">bug_report</i></div>
2361<div style="display:inline-block;" class="badge contributor">contributor<i style="vertical-align: middle;" class="material-icons contributor">group</i></div>
2362<div style="display:inline-block;" class="badge dev">developer<i style="vertical-align: middle;" class="material-icons dev">code</i></div>
2363</div>
2364<div style="margin-top: 15px; margin-bottom: 7px;"><a style="color: var(--lightText); margin: 0px 5px;" href="/logout"><i class="material-icons" style="vertical-align: middle">exit_to_app</i> Logout</a></div>
2365</div>
2366<div class="card advancedOptions" style="padding: 8px 16px; box-shadow: none !important; border: 2px solid var(--elements); margin-top: 20px; display: none;">
2367<div style="display: inline-block; vertical-align: middle;">
2368<h4 style="font-weight: bold; font-size: 28px; margin-bottom: 0px;">Advanced Options</h4>
2369</div>
2370<div style="margin-top: 15px; margin-bottom: 7px;">
2371<a style="color: var(--lightText); margin: 0px 5px;" onclick="dtps.clearData();" href="#"><i class="material-icons" style="vertical-align: middle">refresh</i> Reset Power+</a>
2372<a style="color: var(--lightText); margin: 0px 5px;" onclick="$('.abtpage').hide();$('.abtpage.apiExplorer').show();" href="#"><i class="material-icons" style="vertical-align: middle">folder_shared</i> API Explorer</a>
2373<a style="color: var(--lightText); margin: 0px 5px;" href="https://github.com/jottocraft/dtps/issues/new/choose"><i class="material-icons" style="vertical-align: middle">feedback</i> Send feedback</a></div>
2374</div>
2375<br />
2376<p style="cursor: pointer; color: var(--secText, gray)" onclick="$('.advancedOptions').toggle(); $(this).hide();" class="advOp">Show advanced options</p>
2377<p>(c) 2018-2019 jottocraft (<a href="https://github.com/jottocraft/dtps/blob/master/LICENSE">license</a>)</p>
2378</div>
2379 </div>`)
2380 jQuery(".items").html(`<h4>` + dtps.user.name + `</h4>
2381 <img src="` + dtps.user.avatar_url + `" style="width: 50px; height: 50px; margin: 0px 5px; border-radius: 50%; vertical-align: middle;box-shadow: 0 5px 5px rgba(0, 0, 0, 0.17);" />
2382 <i onclick="window.open('https://github.com/jottocraft/dtps/issues/new/choose')" class="material-icons prerelease">feedback</i>
2383 <i onclick="if (dtps.gradeHTML) { $('.gradeDom').html((dtps.gpa ? '<p>Estimated GPA (beta): ' + dtps.gpa + '</p>' : '') + dtps.gradeHTML.join('')); if (dtps.gradeHTML.length == 0) { $('#classGrades').hide(); } else { $('#classGrades').show(); }; } else {$('#classGrades').hide();}; fluid.modal('.abt-new')" class="material-icons">settings</i>`);
2384 if (!dtps.embedded) $(".embeddedOptions").hide();
2385 if (!dtps.embedded) fluid.onLoad();
2386}
2387
2388dtps.init();