Skip to content

Commit

Permalink
Merge pull request #915 from tutors-sdk/development
Browse files Browse the repository at this point in the history
reintegrate tutors live
  • Loading branch information
edeleastar authored Jan 6, 2025
2 parents 6d230d1 + 6c3c5b3 commit 2b71595
Show file tree
Hide file tree
Showing 21 changed files with 445 additions and 38 deletions.
57 changes: 57 additions & 0 deletions src/lib/services/catalogue.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { supabase } from "./profiles/supabase-client";
import type { CatalogueEntry, CatalogueService } from "./types.svelte";

export const catalogueService: CatalogueService = {
async getCatalogue() {
try {
const { data, error } = await supabase
.from("tutors-connect-courses")
.select("*")
.order("visited_at", { ascending: false });

if (error) {
throw error;
}

const catalogue = data as Array<CatalogueEntry>;
return catalogue;
} catch (error) {
console.error("Error fetching courses:", error);
return [];
}
},

async getCatalogueCount() {
try {
const { count, error } = await supabase
.from("tutors-connect-courses")
.select("*", { count: "exact", head: true });

if (error) {
throw error;
}

return count || 0;
} catch (error) {
console.error("Error fetching course count:", error);
return 0;
}
},

async getStudentCount() {
try {
const { count, error } = await supabase
.from("tutors-connect-profiles")
.select("*", { count: "exact", head: true });

if (error) {
throw error;
}

return count || 0;
} catch (error) {
console.error("Error fetching course count:", error);
return 0;
}
}
};
104 changes: 104 additions & 0 deletions src/lib/services/live.svelte.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import PartySocket from "partysocket";

import { PUBLIC_party_kit_main_room } from "$env/static/public";
import { rune } from "./utils/runes.svelte";
import { LoRecord } from "./types.svelte";
import { refreshLoRecord } from "./presence.svelte";

/** PartyKit server URL from environment */
const partyKitServer = PUBLIC_party_kit_main_room;

let partyKitAll = <PartySocket>{};

if (PUBLIC_party_kit_main_room !== "XXX") {
partyKitAll = new PartySocket({
host: partyKitServer,
room: "tutors-all-course-access"
});
}

export const liveService = {
listeningForCourse: rune<string>(""),
coursesOnline: rune<LoRecord[]>([]),
studentsOnline: rune<LoRecord[]>([]),
studentsOnlineByCourse: rune<LoRecord[][]>([]),

studentEventMap: new Map<string, LoRecord>(),
courseEventMap: new Map<string, LoRecord>(),

partyKitCourse: <PartySocket>{},
listeningAll: false,

groupedStudentListener(event: any) {
const courseArray = this.studentsOnlineByCourse.value.find((lo: LoRecord[]) => lo[0].courseId === event.courseId);
if (!courseArray) {
const studentArray = new Array<LoRecord>();
studentArray.push(new LoRecord(event));
this.studentsOnlineByCourse.value.push(studentArray);
} else {
const loStudent = courseArray.find((lo: LoRecord) => lo.user?.id === event.user.id);
if (!loStudent) {
courseArray.push(new LoRecord(event));
} else {
refreshLoRecord(loStudent, event);
}
}
},

studentListener(event: any) {
const studentEvent = this.studentEventMap.get(event.user.id);
if (!studentEvent) {
const latestLo = new LoRecord(event);
this.studentsOnline.value.push(latestLo);
this.studentEventMap.set(event.user.id, latestLo);
} else {
refreshLoRecord(studentEvent, event);
}
},

courseListener(event: any) {
const courseEvent = this.courseEventMap.get(event.courseId);
if (!courseEvent) {
const latestLo = new LoRecord(event);
this.coursesOnline.value.push(latestLo);
this.courseEventMap.set(event.courseId, latestLo);
} else {
refreshLoRecord(courseEvent, event);
}
},
partyKitListener(event: any) {
try {
const nextCourseEvent = JSON.parse(event.data);
this.courseListener(nextCourseEvent);
this.studentListener(nextCourseEvent);
this.groupedStudentListener(nextCourseEvent);
} catch (e) {
console.log(e);
}
},

startGlobalPresenceService() {
if (!this.listeningAll) {
this.listeningAll = true;
partyKitAll.addEventListener("message", (event) => {
this.partyKitListener(event);
});
}
},

startCoursePresenceListener(courseId: string) {
const partyKitCourse = new PartySocket({
host: partyKitServer,
room: courseId
});
partyKitCourse.addEventListener("message", (event) => {
try {
const nextCourseEvent = JSON.parse(event.data);
this.listeningForCourse.value = nextCourseEvent.courseTitle;
this.groupedStudentListener(nextCourseEvent);
} catch (e) {
console.log(e);
}
});
}
};
6 changes: 1 addition & 5 deletions src/lib/services/presence.svelte.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import { PUBLIC_party_kit_main_room } from "$env/static/public";
import { rune } from "./utils/runes.svelte";
import { LoRecord, type LoUser, type PresenceService, type TutorsId } from "./types.svelte";
import type { Course, Lo } from "./models/lo-types";
import { tutorsConnectService } from "./connect.svelte";
import { tutorsId } from "$lib/runes";

/** PartyKit server URL from environment */
Expand All @@ -33,10 +32,7 @@ export const presenceService: PresenceService = {
*/
studentListener(event: any) {
const nextCourseEvent = JSON.parse(event.data);
if (
nextCourseEvent.courseId === this.listeningTo &&
nextCourseEvent.user.id !== tutorsId.value?.login
) {
if (nextCourseEvent.courseId === this.listeningTo && nextCourseEvent.user.id !== tutorsId.value?.login) {
const studentEvent = this.studentEventMap.get(nextCourseEvent.user.id);
if (!studentEvent) {
const latestLo = new LoRecord(nextCourseEvent);
Expand Down
18 changes: 17 additions & 1 deletion src/lib/services/types.svelte.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,13 @@ export type CourseVisit = {
favourite: boolean;
};

export interface CatalogueEntry {
course_id: string;
visited_at: Date;
visit_count: number;
course_record: any;
}

/**
* Minimal user information for learning object interactions
*/
Expand Down Expand Up @@ -67,7 +74,7 @@ export class LoRecord {
*/
export interface CardDetails {
route: string;
title: string;
title?: string;
type: string;
summary?: string;
summaryEx?: string;
Expand Down Expand Up @@ -215,3 +222,12 @@ export interface ThemeService {
getTypeColour(type: string): string;
eventTrigger(): void;
}

/**
* Service for managing course catalogue data
*/
export interface CatalogueService {
getCatalogue(): Promise<CatalogueEntry[]>;
getCatalogueCount(): Promise<number>;
getStudentCount(): Promise<number>;
}
3 changes: 2 additions & 1 deletion src/lib/ui/components/Card.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
const hideVideoIcon = $derived(currentCourse.value?.areVideosHidden);
const isLandscape = $derived(themeService.cardStyle.value === "landscape");
const isCircular = $derived(themeService.cardStyle.value === "circular");
const hasFullName = $derived(cardDetails?.student?.fullName);
const headingText = $derived(
themeService.layout.value === "compacted"
Expand Down Expand Up @@ -87,7 +88,7 @@
{#if cardDetails.student}
<div class="flex items-center">
<img src={cardDetails.student.avatar} alt={cardDetails.student.fullName} class="rounded-3xl {avatarWidth}" />
<h6 class={textSize}>{cardDetails.student?.fullName}</h6>
<h6 class={textSize}>&nbsp;{hasFullName ? cardDetails.student?.fullName : cardDetails.student?.id}</h6>
</div>
{:else}
<div class="line-clamp-2 pr-10 !text-black dark:!text-white {headingText}">
Expand Down
8 changes: 4 additions & 4 deletions src/lib/ui/components/Icon.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@
{/if}
{/snippet}

{#if tip}
<!-- {#if tip}
<Tooltip
positioning={{ placement: "top" }}
triggerBase="underline"
Expand All @@ -90,6 +90,6 @@
{tip}
{/snippet}
</Tooltip>
{:else}
{@render displayIcon()}
{/if}
{:else} -->
{@render displayIcon()}
<!-- {/if} -->
2 changes: 1 addition & 1 deletion src/lib/ui/navigators/MainNavigator.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
{#snippet trail()}
<span class="hidden md:block">
<TutorsTimeIndicator />
{#if !currentCourse?.value?.isPortfolio}
{#if currentCourse?.value && !currentCourse?.value?.isPortfolio}
<SearchButton />
{/if}
<span class="mx-2 h-10 w-[1px] bg-gray-400 dark:bg-gray-200"></span>
Expand Down
41 changes: 19 additions & 22 deletions src/lib/ui/navigators/tutors-connect/ConnectedProfile.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -38,30 +38,27 @@

{#snippet menuContent()}
<ul class="mt-12 space-y-2">
{#if tutorsId.value?.share === "true"}
<MenuItem text="Share Presence" type="online" onClick={shareStatusChange} />
{:else}
<MenuItem text="Share Presence" type="offline" onClick={shareStatusChange} />
{/if}
{#if tutorsId.value?.share === "true"}
<MenuItem
link="https://time.tutors.dev/{currentCourse.value?.courseId}"
text="Tutors Time"
type="tutorsTime"
targetStr="_blank"
/>
<MenuItem
link="https://live.tutors.dev/course/{currentCourse.value?.courseId}"
text="Tutors Live"
type="live"
targetStr="_blank"
/>
{#if currentCourse.value}
{#if tutorsId.value?.share === "true"}
<MenuItem text="Share Presence" type="online" onClick={shareStatusChange} />
{:else}
<MenuItem text="Share Presence" type="offline" onClick={shareStatusChange} />
{/if}
{#if tutorsId.value?.share === "true"}
<MenuItem
link="https://time.tutors.dev/{currentCourse.value?.courseId}"
text="Tutors Time"
type="tutorsTime"
targetStr="_blank"
/>
<MenuItem link="/live/{currentCourse.value?.courseId}" text="Tutors Live" type="live" />

<li class="option !p-0 hover:preset-tonal">
<OnlineButton />
</li>
<li class="option !p-0 hover:preset-tonal">
<OnlineButton />
</li>

<hr />
<hr />
{/if}
{/if}
<MenuItem link="/" text="Dashboard" type="tutors" />
<MenuItem
Expand Down
25 changes: 25 additions & 0 deletions src/lib/ui/time/Catalogue.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<script lang="ts">
import type { CourseVisit } from "$lib/services/types.svelte";
import Card from "../components/Card.svelte";
interface Props {
courseRecords: CourseVisit[];
}
let { courseRecords = [] }: Props = $props();
// tabSet.value = 4;
</script>

<div class="flex flex-wrap justify-center">
{#each courseRecords as courseRecord}
<Card
cardDetails={{
route: `/course/${courseRecord?.id}`,
title: courseRecord?.title,
type: "course",
summary: courseRecord?.credits,
img: courseRecord?.img,
icon: courseRecord?.icon
}}
/>
{/each}
</div>
20 changes: 20 additions & 0 deletions src/lib/ui/time/Course.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<script lang="ts">
import { presenceService } from "$lib/services/presence.svelte";
import Card from "../components/Card.svelte";
</script>

<div class="flex flex-wrap justify-center">
{#each presenceService.studentsOnline.value as lo}
<Card
cardDetails={{
route: lo.loRoute,
title: lo.courseTitle,
type: lo.type,
summary: lo.title,
summaryEx: "(" + lo.type + ")",
img: lo.img,
icon: lo.icon
}}
/>
{/each}
</div>
30 changes: 30 additions & 0 deletions src/lib/ui/time/CourseGroup.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<script lang="ts">
import { LoRecord } from "$lib/services/types.svelte";
import Card from "../components/Card.svelte";
export let los: LoRecord[];
</script>

<div
class="bg-surface-100-800-token border-surface-200-700-token mx-auto mb-2 w-full place-items-center overflow-hidden rounded-xl border-[1px] p-4"
>
<div class="flex w-full justify-between pb-2">
<h2 class="p-2">
{los[0].courseTitle}
</h2>
</div>
<div class="flex flex-wrap justify-center">
{#each los as lo}
<Card
cardDetails={{
route: lo?.loRoute,
student: lo?.user,
title: lo?.courseTitle,
type: lo?.type,
summary: lo?.title,
img: lo?.img,
icon: lo?.icon
}}
/>
{/each}
</div>
</div>
Loading

0 comments on commit 2b71595

Please sign in to comment.