diff --git a/src/contentScripts/other/selma/layout.ts b/src/contentScripts/other/selma/layout.ts index 0f0a03b..5c54250 100644 --- a/src/contentScripts/other/selma/layout.ts +++ b/src/contentScripts/other/selma/layout.ts @@ -2,7 +2,7 @@ const currentView = document.location.pathname // Regex for extracting Programm name and arguments from a popup Script // This is used to get the URL which would be opened in a popup const popupScriptsRegex = - /dl_popUp\("\/scripts\/mgrqispi\.dll\?APPNAME=CampusNet&PRGNAME=(\w+)&ARGUMENTS=([^"]+)"/ + /dl_popUp\("\/scripts\/mgrqispi\.dll\?APPNAME=CampusNet&PRGNAME=(\w+)&ARGUMENTS=([^"]+)"/ function scriptToURL (script: string): string { const matches = script.match(popupScriptsRegex)! @@ -85,6 +85,7 @@ namespace Graphing { export function createSVGGradeDistributionGraph ( values: GradeStat[], url: string, + ownGrade: number, width = 200, height = 100 ): string { @@ -104,7 +105,22 @@ namespace Graphing { // Allows styling the failed sections differently let className = 'passed' - if (grade >= 5.0) className = 'failed' + if (grade >= 5.0) { + if (ownGrade === grade) { + className = 'failedgrade' + } else { + className = 'failed' + } + } else if (grade === ownGrade) { + className = 'grade' + } + if (ownGrade === -1) { + className = 'nograde' + } else if (ownGrade === -10) { + className = 'animate-loading' + } + // 99 are any unhandled grade variants + // add handling as needed barsSvg += ` TUfast - by AKORA + by AKORA, Tom ` const disableButton = document.createElement('button') @@ -261,11 +277,12 @@ async function createCreditsBanner() { // Add Credit banner with toggle button const creditElm = await createCreditsBanner() document.querySelector('.semesterChoice')!.appendChild(creditElm) - + if (!improveSelma) return - - eventListener(); + + eventListener() }) + document.addEventListener('DOMContentLoaded', eventListener, false) })() async function eventListener () { @@ -286,18 +303,66 @@ async function eventListener () { applyChanges() } +// default values for Notenübersicht, when table is null +const NULL_TABLE = [ + { + grade: 1, + count: 1 + }, + { + grade: 1.3, + count: 1 + }, + { + grade: 1.7, + count: 1 + }, + { + grade: 2, + count: 1 + }, + { + grade: 2.3, + count: 1 + }, + { + grade: 2.7, + count: 1 + }, + { + grade: 3, + count: 1 + }, + { + grade: 3.3, + count: 1 + }, + { + grade: 3.7, + count: 1 + }, + { + grade: 4, + count: 1 + }, + { + grade: 5, + count: 1 + } +] + function applyChanges () { if (currentView.startsWith('/APP/EXAMRESULTS/')) { // Prüfungen > Ergebnisse // Remove the "gut/befriedigend" section const headRow = document.querySelector('thead>tr')! - headRow.removeChild(headRow.children.item(3)!) headRow.children.item(3)!.textContent = 'Notenverteilung' + headRow.removeChild(headRow.children.item(4)!) const body = document.querySelector('tbody')! - const promises: Promise<{ doc: Document; elm: Element; url: string }>[] = - [] + const promises: Promise<{ doc: Document; elm: Element; url: string; ownGrade: number}>[] = + [] for (const row of body.children) { // Remove useless inline styles which set the vertical alignment for (const col of row.children) col.removeAttribute('style') @@ -313,38 +378,70 @@ function applyChanges () { const url = scriptToURL(scriptContent) + // loading animation + lastCol.innerHTML = Graphing.createSVGGradeDistributionGraph(NULL_TABLE, url, -10) + + let ownGradeNum = 0 + const gradeElm = row.children.item(2)?.innerHTML.trim() + if (gradeElm?.includes(',')) { + let ownGrade: string + const grade = (gradeElm + '').split(',') + let first = Number(grade[0]) + const second = Number(grade[1]) + if (second > 70) { + ownGrade = (first++).toString() + '.0' + } else if (second > 30) { + ownGrade = first.toString() + '.7' + } else if (second > 0) { + ownGrade = first.toString() + '.3' + } else { + ownGrade = first.toString() + '.0' + } + ownGradeNum = Number(ownGrade) + } else if (gradeElm?.includes('be')) { + ownGradeNum = 1 + } else { + // default exception case, maybe expand as needed + ownGradeNum = -99 + } + promises.push( fetch(url).then(async (s) => { const parser = new DOMParser() const doc = parser.parseFromString(await s.text(), 'text/html') - return { doc, elm: lastCol, url } + return { doc, elm: lastCol, url, ownGrade: ownGradeNum } }) ) } promises.forEach((p) => - p.then(({ doc, elm, url }) => { + p.then(({ doc, elm, url, ownGrade }) => { const tableBody = doc.querySelector('tbody')! - const values = [...tableBody.children].map((tr) => { - const gradeText = tr.children.item(0)!.textContent!.replace(',', '.') - const grade = parseFloat(gradeText) - - const countText = tr.children.item(1)!.textContent! - let count: number - if (countText === '---') count = 0 - else count = parseInt(countText) - - return { - grade, - count - } - }) - // .slice(0, -2); // Remove the 5.0 from all lists - - // Present the bar chart - const graphSVG = Graphing.createSVGGradeDistributionGraph(values, url) + let graphSVG + if (tableBody === null) { + // No grades available + graphSVG = Graphing.createSVGGradeDistributionGraph(NULL_TABLE, url, -1) + } else { + const values = [...tableBody.children].map((tr) => { + const gradeText = tr.children.item(0)!.textContent!.replace(',', '.') + const grade = parseFloat(gradeText) + + const countText = tr.children.item(1)!.textContent! + let count: number + if (countText === '---') count = 0 + else count = parseInt(countText) + + return { + grade, + count + } + }) + graphSVG = Graphing.createSVGGradeDistributionGraph(values, url, ownGrade) + } elm.innerHTML = graphSVG + }).catch(({ elm, url }) => { + elm.innerHTML = Graphing.createSVGGradeDistributionGraph(NULL_TABLE, url, -1) }) ) @@ -360,6 +457,7 @@ function applyChanges () { // Remove the "bestanden" section const headRow = document.querySelector('thead>tr')! headRow.removeChild(headRow.children.item(3)!) + headRow.children.item(3)!.textContent = 'Versuche' // Add "Notenverteilung" header { @@ -371,8 +469,9 @@ function applyChanges () { // Create the grade distribution graph const body = document.querySelector('tbody')! - const promises: Promise<{ doc: Document; elm: Element; url: string }>[] = - [] + const promises: Promise<{ doc: Document; elm: Element; url: string, ownGrade:number }>[] = + [] + for (const row of body.children) { // Remove useless inline styles which set the vertical alignment for (const col of row.children) col.removeAttribute('style') @@ -392,43 +491,77 @@ function applyChanges () { // Skip courses wihtout grades if (scriptElm === null) continue + let ownGradeNum = 0 + const gradeElm = row.children.item(2)?.innerHTML.trim() + if (gradeElm?.includes(',')) { + let ownGrade: string + const grade = (gradeElm + '').split(',') + let first = Number(grade[0]) + const second = Number(grade[1]) + if (second > 7) { + ownGrade = (first++).toString() + '.0' + } else if (second > 3) { + ownGrade = first.toString() + '.7' + } else if (second > 0) { + ownGrade = first.toString() + '.3' + } else { + ownGrade = first.toString() + '.0' + } + ownGradeNum = Number(ownGrade) + } else if (gradeElm?.includes('be')) { + ownGradeNum = 1 + } else { + // default exception case, maybe expand as needed + ownGradeNum = -99 + } + const scriptContent = scriptElm!.innerHTML const url = scriptToURL(scriptContent) + // loading animation + lastCol.innerHTML = Graphing.createSVGGradeDistributionGraph(NULL_TABLE, url, -10) promises.push( fetch(url).then(async (s) => { const parser = new DOMParser() const doc = parser.parseFromString(await s.text(), 'text/html') - return { doc, elm: lastCol, url } + return { doc, elm: lastCol, url, ownGrade: ownGradeNum } }) ) } promises.forEach((p) => - p.then(({ doc, elm, url }) => { + p.then(({ doc, elm, url, ownGrade }) => { // Parse the grade distributions const tableBody = doc.querySelector('tbody')! - const values = [...tableBody.children].map((tr) => { - const gradeText = tr.children.item(0)!.textContent!.replace(',', '.') - const grade = parseFloat(gradeText) - - const countText = tr.children.item(1)!.textContent! - let count: number - if (countText === '---') count = 0 - else count = parseInt(countText) - - return { - grade, - count - } - }) + let graphSVG + if (tableBody === null) { + // No grades available + graphSVG = Graphing.createSVGGradeDistributionGraph(NULL_TABLE, url, -1) + } else { + const values = [...tableBody.children].map((tr) => { + const gradeText = tr.children.item(0)!.textContent!.replace(',', '.') + const grade = parseFloat(gradeText) + + const countText = tr.children.item(1)!.textContent! + let count: number + if (countText === '---') count = 0 + else count = parseInt(countText) + + return { + grade, + count + } + }) + graphSVG = Graphing.createSVGGradeDistributionGraph(values, url, ownGrade) + } // .slice(0, -2); // Remove the 5.0 from all lists // Present the bar chart - const graphSVG = Graphing.createSVGGradeDistributionGraph(values, url) elm.innerHTML = graphSVG + }).catch(({ elm, url }) => { + elm.innerHTML = Graphing.createSVGGradeDistributionGraph(NULL_TABLE, url, -1) }) ) @@ -479,7 +612,6 @@ function applyChanges () { tries.push({ date, grade }) i += 2 - continue } } @@ -539,12 +671,14 @@ function applyChanges () { '' ) } - - // Table head "Prüfungsleistung" - document.querySelector('thead > tr > th#Name')!.textContent = '' - // Table head "Termin" - document.querySelector('thead > tr > th#Date')!.textContent = - 'Prüfungsleistung/Termin' } + // Table head "Prüfungsleistung" + document.querySelector('thead > tr > th#Name')!.textContent = '' + // Table head "Termin" + document.querySelector('thead > tr > th#Date')!.textContent = + 'Prüfungsleistung/Termin' + // Append extra bar to head + const headRow = document.querySelector('thead>tr')! + headRow.removeChild(headRow.children.item(3)!) } } diff --git a/src/freshContent/settings/settingPages/SelmajExamTheme.vue b/src/freshContent/settings/settingPages/SelmajExamTheme.vue new file mode 100644 index 0000000..4c2da19 --- /dev/null +++ b/src/freshContent/settings/settingPages/SelmajExamTheme.vue @@ -0,0 +1,54 @@ + + + + + diff --git a/src/styles/components/gradeTable.scss b/src/styles/components/gradeTable.scss index 55d25aa..b2cc802 100644 --- a/src/styles/components/gradeTable.scss +++ b/src/styles/components/gradeTable.scss @@ -1,3 +1,4 @@ +@use 'sass:color'; $clr-blue-dark: rgba(11, 42, 81); $clr-blue-light: rgba(0, 86, 127); $clr-green: rgba(56, 199, 158); @@ -49,7 +50,7 @@ $clr-grey: rgba(231, 220, 220); color: white; } .module td { - background-color: lighten($clr-blue-light, 10%); + background-color: color.adjust($clr-blue-light, $lightness: 10%); color: white; } .exam td { diff --git a/src/styles/contentScripts/selma/exam_results.scss b/src/styles/contentScripts/selma/exam_results.scss index 2c03dd0..d5fcc4f 100644 --- a/src/styles/contentScripts/selma/exam_results.scss +++ b/src/styles/contentScripts/selma/exam_results.scss @@ -47,6 +47,18 @@ tbody > tr > td > svg { fill: #dd2727aa; } + .grade { + fill: rgb(95, 231, 36); + } + + .failedgrade { + fill: rgb(111, 2, 2); + } + + .nograde { + fill: rgb(147, 147, 147); + } + .used { fill: #315584; } @@ -63,6 +75,70 @@ tbody > tr > td > svg { &.tries-counter { height: 1lh; } + + @keyframes flickerAnimation { + 0% { + opacity: 1; + } + + 50% { + opacity: 0.1; + } + + 100% { + opacity: 1; + } + } + + @-o-keyframes flickerAnimation { + 0% { + opacity: 1; + } + + 50% { + opacity: 0.1; + } + + 100% { + opacity: 1; + } + } + + @-moz-keyframes flickerAnimation { + 0% { + opacity: 1; + } + + 50% { + opacity: 0.1; + } + + 100% { + opacity: 1; + } + } + + @-webkit-keyframes flickerAnimation { + 0% { + opacity: 1; + } + + 50% { + opacity: 0.1; + } + + 100% { + opacity: 1; + } + } + + .animate-loading { + fill: #315584; + -webkit-animation: flickerAnimation 1s infinite; + -moz-animation: flickerAnimation 1s infinite; + -o-animation: flickerAnimation 1s infinite; + animation: flickerAnimation 1s infinite; + } } // Not sure if this helps