Created
September 6, 2022 21:11
-
-
Save evaneliasyoung/89ba68585827f8427ef86fdf31951e75 to your computer and use it in GitHub Desktop.
ZyBooks completion tool
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* @file zbc.es6 | |
* @brief ZyBooks completion tool. | |
* | |
* @author Evan Elias Young | |
* @date 2021-09-19 | |
* @date 2022-09-06 | |
* @copyright Copyright 2021-2022 Evan Elias Young. All rights reserved. | |
*/ | |
/** | |
* Represents an enhanced HTML element. | |
*/ | |
class HTMLSuperElement { | |
/** | |
* @brief The underlying element. | |
* @type {HTMLElement} | |
*/ | |
#el; | |
/** | |
* @brief Reference to the base element. | |
* @returns {HTMLElement} The base element. | |
*/ | |
get ref() { return this.#el; } | |
/** | |
* @brief Creates a new HTMLSuperElement with jQuery inspired methods. | |
* @returns {HTMLSuperElement} The enhanced HTML element. | |
*/ | |
constructor(el) { this.#el = el; } | |
/** | |
* @brief Returns all element descendants of node that match selectors. | |
* @param {string} selectors Selectors with which to match elements. | |
* @returns {NodeListOf<HTMLElement>} All element descendants of node that match selectors. | |
*/ | |
$ = (selectors) => this.ref.querySelectorAll(selectors); | |
/** | |
* @brief Returns the first element that is a descendant of node that matches selectors. | |
* @param {string} selectors Selectors with which to match elements | |
* @returns {HTMLElement|null} The first element that is a descendant of node that matches selectors. | |
*/ | |
$$ = (selectors) => this.ref.querySelector(selectors); | |
} | |
/** | |
* Represents a multiple choice question. | |
*/ | |
class MultipleChoice extends HTMLSuperElement { | |
/** | |
* @brief The index of the attempted answers. | |
* @type {number} | |
*/ | |
idx = -1; | |
/** | |
* @brief The answers' buttons for the question. | |
* @returns {NodeListOf<HTMLInputElement>} The answers' buttons. | |
*/ | |
get $answers() { return this.$('input'); } | |
/** | |
* @brief Determines whether or not the question is done. | |
* @returns {bool} Whether or not the question is done. | |
*/ | |
get done() { return !!this.$$('[aria-label="Question completed"]'); } | |
/** | |
* Creates a new multiple choice question. | |
* @param {HTMLDivElement} el The DOM element containing the question. | |
*/ | |
constructor(el) { super(el); this.step(); } | |
answer(idx) { this.$answers[idx].click(); } | |
try(idx) { if (!this.done) this.answer(idx); } | |
step() { if (this.idx++ < this.$answers.length) this.try(this.idx); } | |
} | |
/** | |
* Represents a set of multiple choice questions. | |
*/ | |
class MultipleChoiceSet extends HTMLSuperElement { | |
/** | |
* @brief The multiple choice questions in the set. | |
* @type {MultipleChoice[]} | |
*/ | |
questions; | |
/** | |
* @brief The questions' containers for the set. | |
* @returns {NodeListOf<HTMLDivElement>} The questions' containers. | |
*/ | |
get $questions() { return this.$('.question-set-question.multiple-choice-question'); } | |
/** | |
* @brief Determines whether or not the question set is done. | |
* @returns {bool} Whether or not the question set is done. | |
*/ | |
get done() { return !!this.$$('[aria-label="Activity completed"]'); } | |
/** | |
* Creates a new multiple choice question set. | |
* @param {HTMLDivElement} el The DOM element containing the question set. | |
*/ | |
constructor(el) { super(el); } | |
init() { this.questions = Array.from(this.$questions).map(el => new MultipleChoice(el)); } | |
answer(idx) { this.questions.forEach(que => que.answer(idx)); } | |
try(idx) { this.questions.forEach(que => que.try(idx)); } | |
step() { this.questions.forEach(que => que.step()); } | |
} | |
/** | |
* Represents a walkthrough problem. | |
*/ | |
class Walkthrough extends HTMLSuperElement { | |
/** | |
* @brief The start button for the walkthrough. | |
* @returns {HTMLButtonElement|null} The start button. | |
*/ | |
get $start() { return this.$$('button.start-button'); } | |
/** | |
* @brief The speed controller for the walkthrough. | |
* @returns {HTMLInputElement|null} The speed controller. | |
*/ | |
get $speed() { return this.$$('.speed-control input'); } | |
/** | |
* @brief The play button for the walkthrough. | |
* @returns {HTMLButtonElement|null} The play button. | |
*/ | |
get $play() { return this.$$('button[aria-label="Play"]'); } | |
/** | |
* @brief The pause button for the walkthrough. | |
* @returns {HTMLButtonElement|null} The pause button. | |
*/ | |
get $pause() { return this.$$('button[aria-label="Pause"]'); } | |
/** | |
* @brief The playback speed setting. | |
* @returns {0|1|2} The playback speed settings. | |
*/ | |
get speed() { return this.$speed ? this.$speed.value === 'true' ? 2 : 1 : 0; } | |
/** | |
* @brief Whether or not the walkthrough is done. | |
* @returns {boolean} Whether or not the walkthrough is done. | |
*/ | |
get done() { return !!this.$$('[aria-label="Activity completed"]'); } | |
/** | |
* Creates a new walkthrough. | |
* @param {HTMLDivElement} el The DOM element containing the walkthrough. | |
*/ | |
constructor(el) { super(el); } | |
init() { if (!this.done) { this.double(); this.start(); } } | |
double() { if (this.speed === 1) this.$speed.click(); } | |
start() { if (this.$start) this.$start.click(); } | |
step() { if (this.$play) this.$play.click(); } | |
} | |
/** | |
* Represents a short answer problem. | |
*/ | |
class ShortAnswer extends HTMLSuperElement { | |
/** | |
* @brief The show button for the question. | |
* @returns {HTMLButtonElement|null} The show button. | |
*/ | |
get $show() { return this.$$('button.show-answer-button'); } | |
/** | |
* @brief The user's response for the question. | |
* @returns {HTMLTextAreaElement|null} The user's response. | |
*/ | |
get $response() { return this.$$('textarea'); } | |
/** | |
* @brief The check button for the question. | |
* @returns {HTMLButtonElement|null} The check button. | |
*/ | |
get $check() { return this.$$('button.check-button'); } | |
/** | |
* @brief The solution for the question. | |
* @returns {HTMLSpanElement|null} The solution. | |
*/ | |
get $answer() { return this.$$('span.forfeit-answer'); } | |
/** | |
* @brief The solution for the question. | |
* @returns {string|null} The solution. | |
*/ | |
get answer() { return this.$answer ? this.$answer.textContent : null; } | |
/** | |
* @brief Determines whether or not the question is done. | |
* @returns {bool} Whether or not the question is done. | |
*/ | |
get done() { return !!this.$$('[aria-label="Question completed"]'); } | |
/** | |
* Creates a new short answer question. | |
* @param {HTMLDivElement} el The DOM element containing the question. | |
*/ | |
constructor(el) { super(el); } | |
init() { this.show(); } | |
show() { this.$show.click(); this.$show.click(); } | |
copy() { this.$response.value = this.answer || 'null?'; } | |
} | |
/** | |
* Represents the main solver for ZyBook problems. | |
*/ | |
class ZyBookMan extends HTMLSuperElement { | |
/** | |
* @brief The set of multiple choice questions. | |
* @type {MultipleChoiceSet[]} | |
*/ | |
mc; | |
/** | |
* @brief The set of walkthroughs. | |
* @type {Walkthrough[]} | |
*/ | |
wt; | |
/** | |
* @brief The question sets' containers. | |
* @returns {NodeListOf<HTMLDivElement>} The question sets' containers. | |
*/ | |
get $mc() { return this.$('.interactive-activity-container.multiple-choice-content-resource.participation'); } | |
/** | |
* @brief The walkthrough containers. | |
* @returns {NodeListOf<HTMLDivElement>} The walkthrough containers. | |
*/ | |
get $wt() { return this.$('.interactive-activity-container.animation-player-content-resource.participation'); } | |
/** | |
* @brief Determines whether or not all questions are done. | |
* @returns {bool} Whether or not all questions are done. | |
*/ | |
get doneMC() { return this.mc.reduce((allDone, mc) => allDone && mc.done, true); } | |
/** | |
* @brief Determines whether or not all walkthroughs are done. | |
* @returns {bool} Whether or not all walkthroughs are done. | |
*/ | |
get doneWT() { return this.wt.reduce((allDone, wt) => allDone && wt.done, true); } | |
/** | |
* @brief Determines whether or not all problems are done. | |
* @returns {bool} Whether or not all problems are done. | |
*/ | |
get done() { return this.doneMC && this.doneWT; } | |
/** | |
* @brief Creates a new ZyBookMan to solve questions. | |
*/ | |
constructor() { | |
console.log("%cWelcome to ZyBookMan, please initialize...", "color:#bfd730;") | |
super(document); | |
} | |
init() { | |
console.log("%cInitializing ZyBookMan...", "color:#bfd730;") | |
this.mc = Array.from(this.$mc).map(el => new MultipleChoiceSet(el)); | |
this.wt = Array.from(this.$wt).map(el => new Walkthrough(el)); | |
[this.mc, this.wt].forEach(klasses => klasses.forEach(klass => klass.init())); | |
} | |
step() { | |
console.log("%cStepping ZyBookMan...", "color:#d19d30;") | |
if (!this.doneMC) this.stepMC(); | |
if (!this.doneWT) this.stepWT(); | |
} | |
stepMC() { this.mc.forEach(mc => mc.step()); } | |
stepWT() { this.wt.forEach(wt => wt.step()); } | |
} | |
zbm = new ZyBookMan(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment