diff --git a/src/lib/assignments.ts b/src/lib/assignments.ts index 8d2c73f..3f46f1f 100644 --- a/src/lib/assignments.ts +++ b/src/lib/assignments.ts @@ -11,6 +11,7 @@ export interface Category { interface Assignment { name: string; + id: string | undefined; pointsEarned: number | undefined; pointsPossible: number | undefined; unscaledPoints: { pointsEarned: number; pointsPossible: number } | undefined; @@ -24,6 +25,7 @@ interface Assignment { } export interface RealAssignment extends Assignment { + id: string; hidden: false; category: string; date: Date; @@ -31,6 +33,7 @@ export interface RealAssignment extends Assignment { } export interface HiddenAssignment extends Assignment { + id: undefined; pointsEarned: number; pointsPossible: number; unscaledPoints: undefined; @@ -47,6 +50,7 @@ export interface ReactiveAssignment extends Assignment { } export interface NewHypotheticalAssignment extends ReactiveAssignment { + id: undefined; newHypothetical: true; unscaledPoints: undefined; extraCredit: false; @@ -286,6 +290,7 @@ export function getHiddenAssignmentsFromCategories( const hiddenAssignment: Flowed = { name: `Hidden ${category.name} Assignments`, + id: undefined, pointsEarned: hiddenPointsEarned, pointsPossible: hiddenPointsPossible, unscaledPoints: undefined, @@ -515,6 +520,7 @@ export function parseSynergyAssignment(synergyAssignment: AssignmentEntity) { const assignment: RealAssignment = { name: _Measure, + id: synergyAssignment._GradebookID, pointsEarned, pointsPossible, unscaledPoints, diff --git a/src/lib/index.ts b/src/lib/index.ts index 69a2933..3dc4206 100644 --- a/src/lib/index.ts +++ b/src/lib/index.ts @@ -67,7 +67,7 @@ export async function getBlobURLFromBase64String(base64: string) { export enum LocalStorageKey { token = 'token', gradebook = 'gradebook2', - periodOverrideState = 'periodOverrideState', + seenAssignmentIDs = 'seenAssignmentIDs', attendance = 'attendance', documents = 'documents', mailData = 'mailData', @@ -115,15 +115,19 @@ export const loadRecord = async ( } if (refresh || forceRefresh) { - recordState.data = await loadFunc(); - recordState.lastRefresh = Date.now(); + try { + recordState.data = await loadFunc(); + recordState.lastRefresh = Date.now(); - const newCache: LocalStorageCache = { - data: recordState.data, - lastRefresh: recordState.lastRefresh - }; + const newCache: LocalStorageCache = { + data: recordState.data, + lastRefresh: recordState.lastRefresh + }; - localStorage.setItem(localStorageKey, JSON.stringify(newCache)); + localStorage.setItem(localStorageKey, JSON.stringify(newCache)); + } catch (err) { + console.error(err); + } } recordState.loaded = true; diff --git a/src/lib/types/Gradebook.ts b/src/lib/types/Gradebook.ts index e1c82c2..8864836 100644 --- a/src/lib/types/Gradebook.ts +++ b/src/lib/types/Gradebook.ts @@ -49,7 +49,7 @@ export interface Mark { } export interface Assignments { - Assignment: AssignmentEntity[]; + Assignment?: AssignmentEntity[]; } export interface AssignmentEntity { diff --git a/src/routes/(authed)/+layout.svelte b/src/routes/(authed)/+layout.svelte index 21e889b..ce4dfc4 100644 --- a/src/routes/(authed)/+layout.svelte +++ b/src/routes/(authed)/+layout.svelte @@ -46,7 +46,7 @@ class="ml-0 text-white" /> GradeVue -
{studentInfoState.studentInfo?.FormattedName}
+
{studentInfoState.data?.FormattedName}
diff --git a/src/routes/(authed)/documents/documents.svelte.ts b/src/routes/(authed)/documents/documents.svelte.ts index cb48372..c2eface 100644 --- a/src/routes/(authed)/documents/documents.svelte.ts +++ b/src/routes/(authed)/documents/documents.svelte.ts @@ -15,4 +15,4 @@ export const loadDocuments = async (forceRefresh = false) => { 1000 * 60 * 60 * 24, forceRefresh ); -} +}; diff --git a/src/routes/(authed)/grades/+layout.svelte b/src/routes/(authed)/grades/+layout.svelte index e4f2cae..1d15906 100644 --- a/src/routes/(authed)/grades/+layout.svelte +++ b/src/routes/(authed)/grades/+layout.svelte @@ -39,7 +39,9 @@ Viewing reporting period {currentGradebookState.data.ReportingPeriod._GradePeriod} - + {/if} diff --git a/src/routes/(authed)/grades/+page.svelte b/src/routes/(authed)/grades/+page.svelte index de638f2..23eb189 100644 --- a/src/routes/(authed)/grades/+page.svelte +++ b/src/routes/(authed)/grades/+page.svelte @@ -1,7 +1,9 @@ @@ -82,20 +88,25 @@ {/if}
    - {#each currentGradebookState.data.Courses.Course ?? [] as { _Title: title, Marks: { Mark: { _CalculatedScoreString: grade, _CalculatedScoreRaw: percent } } }, index} + {#each currentGradebookState.data.Courses.Course ?? [] as { _Title: title, Marks: { Mark: { _CalculatedScoreString: grade, _CalculatedScoreRaw: percent, Assignments } } }, index}
  1. - {removeClassID(title)} - - - + {removeClassID(title)} + + {#if Assignments.Assignment && getUnseenAssignmentsCount(Assignments.Assignment) > 0} + + {getUnseenAssignmentsCount(Assignments.Assignment)} new + + {/if} + + !seenAssignmentIDs.has(id)) + ); @@ -297,6 +307,24 @@ {/if} + {#if unseenAssignments.length > 0 && !hypotheticalMode} +
    + + {unseenAssignments.length} new assignments + + +
    + {/if} +
    @@ -345,7 +373,7 @@
  2. {/each} {:else} - {#each assignments as { name, pointsEarned, pointsPossible, unscaledPoints, extraCredit, gradePercentageChange, notForGrade, hidden, category, date }} + {#each assignments as { name, id, pointsEarned, pointsPossible, unscaledPoints, extraCredit, gradePercentageChange, notForGrade, hidden, category, date }}
  3. {/each} @@ -395,7 +424,7 @@ {/if} {/each} {:else} - {#each assignments as { name, pointsEarned, pointsPossible, unscaledPoints, extraCredit, gradePercentageChange, notForGrade, hidden, category, date }} + {#each assignments as { name, id, pointsEarned, pointsPossible, unscaledPoints, extraCredit, gradePercentageChange, notForGrade, hidden, category, date }} {#if category === categoryName}
  4. {/if} diff --git a/src/routes/(authed)/grades/[index]/AssignmentCard.svelte b/src/routes/(authed)/grades/[index]/AssignmentCard.svelte index d422f67..fb7799c 100644 --- a/src/routes/(authed)/grades/[index]/AssignmentCard.svelte +++ b/src/routes/(authed)/grades/[index]/AssignmentCard.svelte @@ -31,6 +31,7 @@ categoryDropdownOptions?: string[]; date?: Date | undefined; editable?: boolean; + unseen?: boolean; recalculateGradePercentage?: () => void; } @@ -48,6 +49,7 @@ categoryDropdownOptions = [], date = undefined, editable = false, + unseen = false, recalculateGradePercentage = () => {} }: Props = $props(); @@ -66,9 +68,13 @@ ); let percentageChange = $derived(Math.round((gradePercentageChange ?? 0) * 100) / 100); + + const border = $derived(unseen ? 'dark:border-t-green-600 border-t-4' : ''); - +
    {#if editable} diff --git a/src/routes/(authed)/grades/gradebook.svelte.ts b/src/routes/(authed)/grades/gradebook.svelte.ts index 59d7b76..cbeb61f 100644 --- a/src/routes/(authed)/grades/gradebook.svelte.ts +++ b/src/routes/(authed)/grades/gradebook.svelte.ts @@ -1,6 +1,7 @@ import { LocalStorageKey, type LocalStorageCache, type RecordState } from '$lib'; import { acc } from '$lib/account.svelte'; import type { Gradebook, ReportPeriod } from '$lib/types/Gradebook'; +import { SvelteSet } from 'svelte/reactivity'; interface GradebooksLocalStorageCache { records: (undefined | LocalStorageCache)[]; @@ -21,6 +22,8 @@ export const getCurrentGradebookState = (gradebooksState: GradebooksState) => ? gradebooksState.records[gradebooksState.overrideIndex ?? gradebooksState.activeIndex] : undefined; +export const seenAssignmentIDs = new SvelteSet(); + const cacheExpirationTime = 1000 * 60; export const getPeriodIndex = (period: ReportPeriod, periods: ReportPeriod[]) => @@ -45,7 +48,8 @@ const saveGradebooksState = () => { export const loadGradebooks = async () => { const { studentAccount } = acc; - if (!studentAccount || gradebooksState.records || gradebooksState.activeIndex !== undefined) return; + if (!studentAccount || gradebooksState.records || gradebooksState.activeIndex !== undefined) + return; // Try to load the state from the localStorage cache const cacheStr = localStorage.getItem(LocalStorageKey.gradebook); @@ -99,6 +103,18 @@ export const loadGradebooks = async () => { // Save the state to localStorage saveGradebooksState(); + + // Load seen assignment ids from localStorage + const seenIDsStr = localStorage.getItem(LocalStorageKey.seenAssignmentIDs); + if (seenIDsStr) { + try { + const seenIDs: string[] = JSON.parse(seenIDsStr); + seenIDs.forEach((id) => seenAssignmentIDs.add(id)); + } catch (e) { + console.error(e); + localStorage.removeItem(LocalStorageKey.seenAssignmentIDs); + } + } }; export const showGradebook = async (overrideIndex?: number, forceRefresh = false) => { @@ -131,26 +147,32 @@ export const showGradebook = async (overrideIndex?: number, forceRefresh = false // If expired or refreshing manually, refresh if (refresh) { - const newGradebook = await studentAccount.gradebook(overrideIndex); - - gradebooksState.records[index].data = newGradebook; - gradebooksState.records[index].lastRefresh = Date.now(); - - // If it retrieved the active gradebook - if (overrideIndex === undefined) { - // Check if the active index has changed since the last time it was set - const newIndex = getPeriodIndex( - newGradebook.ReportingPeriod, - newGradebook.ReportingPeriods.ReportPeriod - ); - - // If it has, update the active index - if (newIndex !== index) gradebooksState.activeIndex = newIndex; + try { + const newGradebook = await studentAccount.gradebook(overrideIndex); + + gradebooksState.records[index].data = newGradebook; + gradebooksState.records[index].lastRefresh = Date.now(); + + // If it retrieved the active gradebook + if (overrideIndex === undefined) { + // Check if the active index has changed since the last time it was set + const newIndex = getPeriodIndex( + newGradebook.ReportingPeriod, + newGradebook.ReportingPeriods.ReportPeriod + ); + + // If it has, update the active index + if (newIndex !== index) gradebooksState.activeIndex = newIndex; + } + } catch (err) { + console.error(err); } - - // Save refreshed data to localStorage - saveGradebooksState(); } gradebooksState.records[index].loaded = true; + + saveGradebooksState(); }; + +export const saveSeenAssignments = () => + localStorage.setItem(LocalStorageKey.seenAssignmentIDs, JSON.stringify([...seenAssignmentIDs])); diff --git a/src/routes/(authed)/mail/+page.svelte b/src/routes/(authed)/mail/+page.svelte index 2ef8c53..a5f00c9 100644 --- a/src/routes/(authed)/mail/+page.svelte +++ b/src/routes/(authed)/mail/+page.svelte @@ -55,9 +55,9 @@ /> {/if} -

    Inbox

    - {#if mailDataState.data} +

    Inbox

    +
      {#each mailDataState.data.InboxItemListings.MessageXML as message}
    1. { 1000 * 60 * 60, forceRefresh ); -} \ No newline at end of file +}; diff --git a/src/routes/(authed)/studentinfo/studentInfo.svelte.ts b/src/routes/(authed)/studentinfo/studentInfo.svelte.ts index bbf8cf4..9b7872d 100644 --- a/src/routes/(authed)/studentinfo/studentInfo.svelte.ts +++ b/src/routes/(authed)/studentinfo/studentInfo.svelte.ts @@ -16,4 +16,3 @@ export const loadStudentInfo = async (forceRefresh = false) => { forceRefresh ); }; - diff --git a/src/tests/gradebook.test.ts b/src/tests/gradebook.test.ts index 6a9ce91..61a4a2d 100644 --- a/src/tests/gradebook.test.ts +++ b/src/tests/gradebook.test.ts @@ -31,6 +31,7 @@ test('Normal Assignment', () => { expect(parseSynergyAssignment(synergyAssignment)).toEqual({ name: 'Ch. 3 Quiz', + id: '123456', pointsEarned: 3, pointsPossible: 4, unscaledPoints: undefined, @@ -73,6 +74,7 @@ test('Normal Assignment - _Point is empty string', () => { expect(parseSynergyAssignment(synergyAssignment)).toEqual({ name: 'Ch. 3 Quiz', + id: '123456', pointsEarned: 0, pointsPossible: 4, unscaledPoints: undefined, @@ -112,6 +114,7 @@ test('Not Graded Assignment - Points Possible', () => { const match: RealAssignment = { name: 'Ch. 3 Quiz', + id: '123456', pointsEarned: undefined, pointsPossible: 4, unscaledPoints: undefined, @@ -156,6 +159,7 @@ test('Not Graded Assignment - No Points Possible', () => { expect(parseSynergyAssignment(synergyAssignment)).toEqual({ name: 'Ch. 3 Quiz', + id: '123456', pointsEarned: undefined, pointsPossible: undefined, unscaledPoints: undefined, @@ -198,6 +202,7 @@ test('Extra Credit Assignment', () => { expect(parseSynergyAssignment(synergyAssignment)).toEqual({ name: 'Ch. 3 Quiz', + id: '123456', pointsEarned: 3, pointsPossible: 4, unscaledPoints: undefined, @@ -240,6 +245,7 @@ test('Not For Grading Assignment', () => { expect(parseSynergyAssignment(synergyAssignment)).toEqual({ name: 'Ch. 3 Quiz', + id: '123456', pointsEarned: 3, pointsPossible: 4, unscaledPoints: undefined, @@ -282,6 +288,7 @@ test('Scaled Assignment', () => { expect(parseSynergyAssignment(synergyAssignment)).toEqual({ name: 'Ch. 3 Quiz', + id: '123456', pointsEarned: 6, pointsPossible: 8, unscaledPoints: {