From e913079c54cb1ffca0522ce84be89f4e7fe87cf7 Mon Sep 17 00:00:00 2001 From: Fabio Gaspar Date: Sun, 31 Oct 2021 00:24:17 +0100 Subject: [PATCH 1/2] chore: Watch for changes in manifest.json in gulp --- gulpfile.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gulpfile.js b/gulpfile.js index 7a0fcc9..e1f08fa 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -137,7 +137,7 @@ function zip() { /** Task: Watch for any changes in the source folder */ function startWatching() { $.livereload.listen() - watch(['./src/*', './src/**/*', './src/**/**/*']).on("change", () => { + watch(['manifest.json','./src/*', './src/**/*', './src/**/**/*']).on("change", () => { exports.build() $.livereload.reload() }); From e32de294ba06e1e935484f61a9c7fb468a3a4f5d Mon Sep 17 00:00:00 2001 From: Fabio Gaspar Date: Sun, 31 Oct 2021 00:26:22 +0100 Subject: [PATCH 2/2] feat: Initial work in a grades simulator - Parses the data from the 'percurso academico' table - Constructs the necessary DOM elements: a button near the table to launch the modal, and the modal itself. The modal shows a table listing all courses, the achieved grade (if any), and then an input to simulate a grade - Auxiliar methods to calculate the average grade based on user input --- manifest.json | 4 + src/icons/slider-48.png | Bin 0 -> 396 bytes src/js/extractors/gradesim.js | 206 ++++++++++++++++++++++++++++++++++ 3 files changed, 210 insertions(+) create mode 100644 src/icons/slider-48.png create mode 100644 src/js/extractors/gradesim.js diff --git a/manifest.json b/manifest.json index 62ce852..ff0d235 100644 --- a/manifest.json +++ b/manifest.json @@ -55,6 +55,10 @@ "matches": ["https://*.up.pt/*gpag_ccorrente_geral.conta_corrente_view*", "https://*.up.pt/*GPAG_CCORRENTE_GERAL.CONTA_CORRENTE_VIEW*"], "js": ["js/extractors/bills.js"], "run_at": "document_end" + }, { + "matches": ["https://*.up.pt/*/fest_geral.curso_percurso_academico_view*"], + "js": ["js/extractors/gradesim.js"], + "run_at": "document_end" }], "permissions": [ "https://*.up.pt/*", diff --git a/src/icons/slider-48.png b/src/icons/slider-48.png new file mode 100644 index 0000000000000000000000000000000000000000..c90a70fb7923eb48823d90656d21f9dd6a41e062 GIT binary patch literal 396 zcmV;70dxL|P)(>4fcROId~VihjKIn3^Y%vWY%W`39>9|Ml#I5{bzRK0pyrz*~YquCGbh_X3l z7{=DE>;ue(e*jkJUf!wO!14C0WxIs|?w?;;O~I4NH=~cb^X+iC&H!+>K0g;dznzxI~;^J%XaoF zGz??vHUO8v6qr)97cqu+9LLE|92@O}k+J3pU}&u7HrfY`u_m+8K3E!SmH-leHP$Qv zER8kKM{H=Uc~;1c)p4AEgZ_UY;r&xW7a;NdQ$l5 simGrade !== null) // skip unfinished courses + .reduce((acc, { ects }) => acc + ects, 0.0); + } + + avgGrade() { + const weightedGrades = Object.values(this.grades) + .filter(({ simGrade }) => simGrade !== null) // skip unfinished courses + .reduce((acc, { ects, simGrade }) => acc + simGrade * ects, 0.0); + + const ects = this.totalEcts(); + + return weightedGrades / ects; + } + + /** + * + */ + parseGradesTable() { + const $t = document.querySelector('#tabelapercurso'); + const $tbody = $t.querySelector("tbody"); + // iterates over the table rows, skipping empty/seperator kind rows + for (const $tr of $tbody.querySelectorAll('tr:not(tr.separador)')) { + // collumns + // 1 - year + // 2 - semester, e.g. "1S" + // 3 - code e.g EIC0016 + // 4 - name + // 5 - minor ( not relevant ) + // 6 - number of credits + try { + // if fails in first columns, then likely is a row that does not + // represent a course + const year = $tr.querySelector('td:nth-child(1)').innerText; + const semester = $tr.querySelector('td:nth-child(2)').innerText; + const id = $tr.querySelector('td:nth-child(3)').innerText; + const name = $tr.querySelector('.unidade-curricular').innerText; + const ects = $tr.querySelector('td:nth-child(6)').innerText; + + // The number of subsequent columns varies, according to number of school years + // if the course is completed (grade >= 10), there should be a cell + // with class 'n aprovado' containing the grade + // If it does not exist, then the course is not finished + const tryGrade = $tr.querySelector('.n.aprovado'); + const grade = tryGrade ? tryGrade.innerText : null; + + this.grades[id] = { + name, + ects: Number.parseFloat(ects.replace(',', '.')), + year, + semester, + grade: grade && Number.parseInt(grade), + simGrade: grade && Number.parseInt(grade) + } + + } catch (e) { + continue; + } + } + } + + /** + * + */ + uiInit() { + // prepare modal + const $modal = this.uiCreateModal(); + $("head").before($modal); + + // prepare button to open modal + document + .querySelector('.caixa table:first-child') + .insertAdjacentElement('afterend', this.uiCreateOpenModalBtn()); + } + + /** + * Creates clickable button to launch the model where users can start + * simulating their grades + */ + uiCreateOpenModalBtn() { + const $btn = document.createElement('button'); + $btn.innerHTML = ` + + `; + return $btn; + } + + uiCreateModalTableRow(id, name, ects, grade) { + const html = ` + + ${name} + ${ects} + ${grade} + + + `; + // create table row DOM + const $tmp = document.createElement('template'); + $tmp.innerHTML = html.trim(); + + // attach event listeners to input + const that = this; + $tmp.content.querySelector('input').addEventListener('input', function (e) { + const num = Number.parseInt(e.target.value); + + if (num < 10) + num = 10; + else if (num > 20) + num = 20; + + e.target.value = num; + that.grades[id].simGrade = num; + }); + + return $tmp.content.firstElementChild; + } + + /** + * + */ + uiCreateModal() { + const modalHtml = + `
+
+

SigTools

+ + + + + + + + +
Name (UC)CreditsGradeSimulate
+
+
+
`; + + const $tmp = document.createElement('template'); + $tmp.innerHTML = modalHtml.trim(); + const $modal = $tmp.content.firstElementChild; + + // add rows to the table, one per course (UC) + const $ucTable = $modal.querySelector('tbody'); + for (const { id, name, ects, grade } of this.grades) { + $ucTable.append(this.uiCreateModalTableRow(id, name, ects, grade)); + } + + return $modal; + } +} + +// add an instance to the EXTRACTORS variable, and also trigger attachIfPossible due to constructor +EXTRACTORS.push(new GradeSim()); \ No newline at end of file