From 592b60445815ea6a7170a44da0ded838e75cd393 Mon Sep 17 00:00:00 2001
From: PurelyAnecdotal <58897346+PurelyAnecdotal@users.noreply.github.com>
Date: Mon, 30 Dec 2024 15:04:21 -1000
Subject: [PATCH] Add number-flow component for grade percentage display (#113)

---
 bun.lock                                      |  5 +++
 package.json                                  |  1 +
 src/routes/(authed)/grades/+page.svelte       |  8 +++--
 .../(authed)/grades/[index]/+page.svelte      | 35 ++++++++++++++-----
 4 files changed, 39 insertions(+), 10 deletions(-)

diff --git a/bun.lock b/bun.lock
index af3bf9e..ce28430 100755
--- a/bun.lock
+++ b/bun.lock
@@ -9,6 +9,7 @@
       },
       "devDependencies": {
         "@eslint/compat": "^1.2.4",
+        "@number-flow/svelte": "^0.2.3",
         "@sveltejs/adapter-vercel": "^5.5.2",
         "@sveltejs/kit": "^2.15.0",
         "@sveltejs/vite-plugin-svelte": "^5.0.3",
@@ -148,6 +149,8 @@
 
     "@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="],
 
+    "@number-flow/svelte": ["@number-flow/svelte@0.2.3", "", { "dependencies": { "esm-env": "^1.1.4", "number-flow": "0.4.2" }, "peerDependencies": { "svelte": "^4 || ^5" } }, "sha512-19jagH1P9Oagh5vckNDENVekqP1cnjarhi9Yaf2YdktbIzc1FeyiHeyRbyLdsuJiYLlk0KHdFP/WKE+MSod1Mg=="],
+
     "@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="],
 
     "@polka/url": ["@polka/url@1.0.0-next.28", "", {}, "sha512-8LduaNlMZGwdZ6qWrKlfa+2M4gahzFkprZiAt2TF8uS0qQgBizKXpXURqvTJ4WtmupWxaLqjRb2UCTe72mu+Aw=="],
@@ -614,6 +617,8 @@
 
     "normalize-range": ["normalize-range@0.1.2", "", {}, "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA=="],
 
+    "number-flow": ["number-flow@0.4.2", "", { "dependencies": { "esm-env": "^1.1.4" } }, "sha512-YLN73/m8BUU4r/6mq9zqLdpFKt3LSPPRectOECheA9jtNWF4PP8EIz0+Z1giqu/x9nS86KnKwwouLXXiqnjhQQ=="],
+
     "object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="],
 
     "object-hash": ["object-hash@3.0.0", "", {}, "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw=="],
diff --git a/package.json b/package.json
index 3efe43e..07f6c9b 100644
--- a/package.json
+++ b/package.json
@@ -14,6 +14,7 @@
 	},
 	"devDependencies": {
 		"@eslint/compat": "^1.2.4",
+		"@number-flow/svelte": "^0.2.3",
 		"@sveltejs/adapter-vercel": "^5.5.2",
 		"@sveltejs/kit": "^2.15.0",
 		"@sveltejs/vite-plugin-svelte": "^5.0.3",
diff --git a/src/routes/(authed)/grades/+page.svelte b/src/routes/(authed)/grades/+page.svelte
index 8502183..de638f2 100644
--- a/src/routes/(authed)/grades/+page.svelte
+++ b/src/routes/(authed)/grades/+page.svelte
@@ -1,5 +1,6 @@
 <script lang="ts">
 	import { getColorForGrade, removeClassID } from '$lib';
+	import NumberFlow from '@number-flow/svelte';
 	import { Alert, Button, Card, Dropdown, DropdownItem, Progressbar } from 'flowbite-svelte';
 	import ChevronDownOutline from 'flowbite-svelte-icons/ChevronDownOutline.svelte';
 	import ChevronUpOutline from 'flowbite-svelte-icons/ChevronUpOutline.svelte';
@@ -89,8 +90,11 @@
 					>
 						<span class="mr-2 line-clamp-1">{removeClassID(title)}</span>
 						<span class="ml-auto mr-2 shrink-0">
-							{grade}
-							{parseFloat(percent)}%
+							<NumberFlow
+								prefix={grade + ' '}
+								value={parseFloat(percent) / 100}
+								format={{ style: 'percent', maximumFractionDigits: 3 }}
+							/>
 						</span>
 
 						<Progressbar
diff --git a/src/routes/(authed)/grades/[index]/+page.svelte b/src/routes/(authed)/grades/[index]/+page.svelte
index fa72d9e..062bf3c 100644
--- a/src/routes/(authed)/grades/[index]/+page.svelte
+++ b/src/routes/(authed)/grades/[index]/+page.svelte
@@ -21,6 +21,7 @@
 		type ReactiveAssignment,
 		type RealAssignment
 	} from '$lib/assignments';
+	import NumberFlow from '@number-flow/svelte';
 	import {
 		Alert,
 		Button,
@@ -164,6 +165,22 @@
 	}
 
 	let calcWarningOpen = $state(false);
+
+	const prefix = $derived(
+		hypotheticalMode ? '' : synergyCourse?.Marks.Mark._CalculatedScoreString + ' '
+	);
+
+	const value = $derived(
+		hypotheticalMode
+			? hypotheticalGrade / 100
+			: synergyCourse
+				? parseFloat(synergyCourse.Marks.Mark._CalculatedScoreRaw) / 100
+				: undefined
+	);
+
+	// https://github.com/barvian/number-flow/blob/e9fc6999417df7cb7e7b290f7f2019f570c18cc7/packages/number-flow/src/index.ts#L73
+	const easing =
+		'linear(0,.005,.019,.039,.066,.096,.129,.165,.202,.24,.278,.316,.354,.39,.426,.461,.494,.526,.557,.586,.614,.64,.665,.689,.711,.731,.751,.769,.786,.802,.817,.831,.844,.856,.867,.877,.887,.896,.904,.912,.919,.925,.931,.937,.942,.947,.951,.955,.959,.962,.965,.968,.971,.973,.976,.978,.98,.981,.983,.984,.986,.987,.988,.989,.99,.991,.992,.992,.993,.994,.994,.995,.995,.996,.996,.9963,.9967,.9969,.9972,.9975,.9977,.9979,.9981,.9982,.9984,.9985,.9987,.9988,.9989,1)';
 </script>
 
 <svelte:head>
@@ -176,14 +193,16 @@
 			{courseName}
 		</span>
 		<span class="flex shrink-0 items-center text-2xl">
-			{#if hypotheticalMode}
-				{#if !categories && !rawGradeCalcMatches}
-					<ExclamationCircleSolid class="mr-2 focus:outline-none" />
-				{/if}
-				{Math.round(hypotheticalGrade * 1000) / 1000}%
-			{:else}
-				{synergyCourse.Marks.Mark._CalculatedScoreString}
-				{synergyCourse.Marks.Mark._CalculatedScoreRaw}%
+			{#if hypotheticalMode && !categories && !rawGradeCalcMatches}
+				<ExclamationCircleSolid class="mr-2 focus:outline-none" />
+			{/if}
+			{#if value}
+				<NumberFlow
+					{prefix}
+					{value}
+					format={{ style: 'percent', maximumFractionDigits: 3 }}
+					spinTiming={{ duration: 400, easing }}
+				/>
 			{/if}
 		</span>
 	</div>