diff --git a/prisma/migrations/20251008112310_update_content_duration/migration.sql b/prisma/migrations/20251008112310_update_content_duration/migration.sql new file mode 100644 index 00000000..eb19e5bc --- /dev/null +++ b/prisma/migrations/20251008112310_update_content_duration/migration.sql @@ -0,0 +1,8 @@ +/* + Warnings: + + - Added the required column `content_duration` to the `learning_units` table without a default value. This is not possible if the table is not empty. + +*/ +-- AlterTable +ALTER TABLE "public"."learning_units" ADD COLUMN "content_duration" DECIMAL(65,30) NOT NULL; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 241fb920..01158af3 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -55,11 +55,12 @@ model LearningUnit { updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz(3) // Domain-Specific Fields. - title String @map("title") - contentType ContentType @map("content_type") - contentURL String @map("content_url") - summary String @map("summary") - createdBy String @map("created_by") + title String @map("title") + contentType ContentType @map("content_type") + contentURL String @map("content_url") + contentDuration Decimal @map("content_duration") + summary String @map("summary") + createdBy String @map("created_by") // Relations. collectionId BigInt @map("collection_id") diff --git a/prisma/seed.ts b/prisma/seed.ts index c0d684b6..634cd913 100644 --- a/prisma/seed.ts +++ b/prisma/seed.ts @@ -130,6 +130,7 @@ const learningUnits: Prisma.LearningUnitCreateInput[] = [ questionAnswers: { create: questionAnswers, }, + contentDuration: 300, }, { id: 2, @@ -157,6 +158,7 @@ const learningUnits: Prisma.LearningUnitCreateInput[] = [ questionAnswers: { create: questionAnswers, }, + contentDuration: 300, }, { id: 3, @@ -184,6 +186,7 @@ const learningUnits: Prisma.LearningUnitCreateInput[] = [ questionAnswers: { create: questionAnswers, }, + contentDuration: 300, }, { id: 4, @@ -211,6 +214,7 @@ const learningUnits: Prisma.LearningUnitCreateInput[] = [ questionAnswers: { create: questionAnswers, }, + contentDuration: 300, }, ]; diff --git a/src/lib/components/LearningUnit/LearningUnit.svelte b/src/lib/components/LearningUnit/LearningUnit.svelte index 03200f50..fb7ec499 100644 --- a/src/lib/components/LearningUnit/LearningUnit.svelte +++ b/src/lib/components/LearningUnit/LearningUnit.svelte @@ -5,6 +5,7 @@ import { Badge, type BadgeProps } from '$lib/components/Badge/index.js'; import { Button } from '$lib/components/Button/index.js'; + import { ProgressSpinner } from '$lib/components/ProgressSpinner/index.js'; export interface Props { /** @@ -52,6 +53,14 @@ * A callback to resume playback. */ onresume: () => void; + /** + * The time remaining in the playback. + */ + timeremaining: number; + /** + * The duration of the playback. + */ + duration: number; } | null; } @@ -77,6 +86,14 @@ player?.onresume(); }; + + const formatTimeRemaining = (timeInSeconds: number): string => { + if (timeInSeconds <= 0) return '0 mins left'; + if (timeInSeconds < 60) return '< 1 min left'; + const minutes = Math.ceil(timeInSeconds / 60); + const unit = minutes === 1 ? 'min' : 'mins'; + return `${minutes} ${unit} left`; + }; - - - - - - 23m left + + + {formatTimeRemaining(player.timeremaining ?? 0)} + {/if} diff --git a/src/lib/components/ProgressSpinner/ProgressSpinner.svelte b/src/lib/components/ProgressSpinner/ProgressSpinner.svelte new file mode 100644 index 00000000..83599438 --- /dev/null +++ b/src/lib/components/ProgressSpinner/ProgressSpinner.svelte @@ -0,0 +1,51 @@ + + + + + + + + diff --git a/src/lib/components/ProgressSpinner/index.ts b/src/lib/components/ProgressSpinner/index.ts new file mode 100644 index 00000000..1bc79c35 --- /dev/null +++ b/src/lib/components/ProgressSpinner/index.ts @@ -0,0 +1,4 @@ +export { + default as ProgressSpinner, + type Props as ProgressSpinnerProps, +} from './ProgressSpinner.svelte'; diff --git a/src/lib/states/player.svelte.ts b/src/lib/states/player.svelte.ts index c682293b..9f777931 100644 --- a/src/lib/states/player.svelte.ts +++ b/src/lib/states/player.svelte.ts @@ -62,6 +62,13 @@ export class Player extends EventTarget { }; this.#audio.ontimeupdate = () => { this.#progress = this.#audio?.currentTime || 0; + this.dispatchEvent( + new CustomEvent('progress', { + detail: { + currentTime: this.#audio?.currentTime, + }, + }), + ); }; this.#audio.onended = () => { this.#isPlaying = false; diff --git a/src/routes/(protected)/(core)/+page.server.ts b/src/routes/(protected)/(core)/+page.server.ts index 3d496469..7f742518 100644 --- a/src/routes/(protected)/(core)/+page.server.ts +++ b/src/routes/(protected)/(core)/+page.server.ts @@ -24,6 +24,7 @@ export const load: PageServerLoad = async (event) => { select: { id: true, title: true, + contentDuration: true, contentURL: true, createdAt: true, createdBy: true, @@ -58,8 +59,16 @@ export const load: PageServerLoad = async (event) => { throw error(500); } + const journeys = learningJourneys.map((journey) => ({ + ...journey, + learningUnit: { + ...journey.learningUnit, + contentDuration: journey.learningUnit.contentDuration.toNumber(), + }, + })); + return { - learningJourneys, + learningJourneys: journeys, username: user.name, }; }; diff --git a/src/routes/(protected)/(core)/+page.svelte b/src/routes/(protected)/(core)/+page.svelte index 156d2435..3bb3cb6f 100644 --- a/src/routes/(protected)/(core)/+page.svelte +++ b/src/routes/(protected)/(core)/+page.svelte @@ -1,4 +1,6 @@
@@ -49,9 +91,20 @@ player={{ isactive: player.currentTrack?.id === learningJourney.learningUnit.id, isplaying: player.isPlaying, - onplay: () => handleLearningUnitPlay(learningJourney), + onplay: () => + handleLearningUnitPlay( + learningJourney, + progressState.find((ps) => ps.id === learningJourney.learningUnit.id)?.progress, + ), onpause: handleLearningUnitPause, onresume: handleLearningUnitResume, + duration: + progressState.find((ps) => ps.id === learningJourney.learningUnit.id) + ?.contentDuration ?? 0, + timeremaining: (() => { + const ps = progressState.find((ps) => ps.id === learningJourney.learningUnit.id); + return Math.max((ps?.contentDuration ?? 0) - (ps?.progress ?? 0), 0); + })(), }} /> {/each}