diff --git a/src/lib/runes.svelte.ts b/src/lib/runes.svelte.ts new file mode 100644 index 000000000..b74ec1ac7 --- /dev/null +++ b/src/lib/runes.svelte.ts @@ -0,0 +1,23 @@ +import type { TutorsId } from "$lib/services/connect"; +import type { Course, Lo } from "./services/base/lo-types"; + +export const rune = (initialValue: T) => { + let _rune = $state(initialValue); + return { + get value() { + return _rune; + }, + set value(v: T) { + _rune = v; + } + }; +}; + +export const currentLabStepIndex = rune(0); +export const adobeLoaded = rune(false); +export const animationDelay = rune(200); + +export const currentLo = rune(null); +export const currentCourse = rune(null); + +export const tutorsId = rune(null); diff --git a/src/lib/runes.ts b/src/lib/runes.ts deleted file mode 100644 index fc1cc6a3e..000000000 --- a/src/lib/runes.ts +++ /dev/null @@ -1,12 +0,0 @@ -import type { Course, Lo } from "./services/models/lo-types"; -import type { TutorsId } from "./services/types.svelte"; -import { rune } from "./services/utils/runes.svelte"; - -export const currentLabStepIndex = rune(0); -export const adobeLoaded = rune(false); -export const animationDelay = rune(200); - -export const currentLo = rune(null); -export const currentCourse = rune(null); - -export const tutorsId = rune(null); \ No newline at end of file diff --git a/src/lib/services/base/index.ts b/src/lib/services/base/index.ts new file mode 100644 index 000000000..6e4ce04cb --- /dev/null +++ b/src/lib/services/base/index.ts @@ -0,0 +1,58 @@ +/** + * Re-exports base types, utilities and constants for easier imports + * @module + */ + +// Type exports from lo-types +export type { + Lo, + Course, + Topic, + Lab, + LabStep, + Talk, + Note, + Web, + Github, + Archive, + PanelNote, + PanelTalk, + PanelVideo, + Panels, + Units, + Composite, + Unit, + Side, + Calendar, + WeekType, + VideoIdentifier, + VideoIdentifiers, + LearningResource, + Properties, + IconType, + LearningRecord, + LoType +} from "./lo-types"; + +// Utility functions from lo-types +export { + imageTypes, + assetTypes, + isCompositeLo +} from "./lo-types"; + +// Supabase client and utilities +export { + supabase, + getNumOfLearningRecordsIncrements, + getCalendarDuration, + getCalendarCount, + getDurationTotal, + insertOrUpdateCalendar, + handleInteractionData, + storeStudentCourseLearningObjectInSupabase, + updateLearningRecordsDuration, + addOrUpdateStudent, + formatDate, + updateCalendarDuration +} from "./utils/supabase-client"; diff --git a/src/lib/services/base/lo-types.ts b/src/lib/services/base/lo-types.ts new file mode 100644 index 000000000..4f42ea066 --- /dev/null +++ b/src/lib/services/base/lo-types.ts @@ -0,0 +1,352 @@ +/** + * @module LoTypes + * Core type definitions for the Tutors learning object system + * Defines the structure and relationships between different types of learning objects (Los) + */ + +import type { IconNavBar } from "$lib/services/themes"; + +/** + * Supported image file extensions for learning objects + */ +export const imageTypes = ["png", "jpg", "jpeg", "gif", "PNG", "JPG", "JPEG", "GIF"]; + +/** + * Supported asset file types, including images and documents + */ +export const assetTypes = imageTypes.concat(["pdf", "zip", "html", "htm", "yaml", "xls", "xlsx", "xlsm", "csv"]); + +/** + * Represents a week in the course calendar + */ +export type WeekType = { + title: string; // Week title/name + type: string; // Type identifier + date: string; // Date string + dateObj: Date; // JavaScript Date object +}; + +/** + * Course calendar structure + */ +export type Calendar = { + title: string; // Calendar title + weeks: WeekType[]; // Array of course weeks + currentWeek: WeekType; // Current active week +}; + +/** + * Video service identifier + */ +export type VideoIdentifier = { + service: string; // Video platform (e.g., "youtube", "vimeo") + id: string; // Video ID on the platform +}; + +/** + * Collection of video identifiers + */ +export type VideoIdentifiers = { + videoid: string; // Primary video ID + videoIds: VideoIdentifier[]; // All video sources +}; + +/** + * Base structure for learning resources + */ +export type LearningResource = { + courseRoot: string; // Root path of the course + route: string; // URL route to the resource + id: string; // Unique identifier + lrs: LearningResource[]; // Nested learning resources + files: Array; // Associated files + type: string; // Resource type +}; + +/** + * Dynamic property collection for learning objects + */ +export class Properties { + [key: string]: string; +} + +/** + * Icon definition with type and color + */ +export type IconType = { + type: string; // Icon identifier + color: string; // Icon color +}; + +/** + * Student interaction tracking for learning objects + */ +export interface LearningRecord { + date: Date; // Interaction date + pageLoads: number; // Page view count + timeActive: number; // Active time in seconds +} + +/** + * Core learning object type + * Represents any content unit in the Tutors system + */ +export type Lo = { + type: string; // Lo type identifier + id: string; // Unique identifier (folder name) + title: string; // Primary title + summary: string; // Brief description + contentMd: string; // Markdown content + frontMatter: Properties; // YAML frontmatter properties + contentHtml?: string; // Rendered HTML content + route: string; // URL path + authLevel: number; // Access control level + + img: string; // Image URL + imgFile: string; // Image filename + icon?: IconType; // Fallback icon + + video: string; // Primary video ID + videoids: VideoIdentifiers; // All video sources + + hide: boolean; // Visibility flag + + parentLo?: Lo; // Parent learning object + parentTopic?: Topic; // Parent topic + parentCourse?: Course; // Parent course + breadCrumbs?: Lo[]; // Navigation hierarchy + + learningRecords?: Map; // Student interaction data +}; + +/** + * Lab step definition + * Represents a single step in a lab + */ +export type LabStep = { + title: string; // Step title + shortTitle: string; // Short title for display + contentMd: string; // Markdown content + contentHtml?: string; // Rendered HTML content + route: string; // URL path + id: string; // Unique identifier + parentLo?: Lab; // Parent lab + type: string; // Step type +}; + +/** + * Lab learning object + * Contains multiple ordered steps + */ +export type Lab = Lo & { + type: "lab"; + los: LabStep[]; + pdf: string; // Route to PDF for the lo + pdfFile: string; // PDF file name +}; + +/** + * Talk learning object + * Represents a presentation or lecture + */ +export type Talk = Lo & { + type: "talk"; + pdf: string; // Route to PDF for the lo + pdfFile: string; // PDF file name +}; + +/** + * Archive learning object + * Represents downloadable content + */ +export type Archive = Lo & { + type: "archive"; + archiveFile?: string; // Archive file in the lo +}; + +/** + * Web resource learning object + */ +export type Web = Lo & { + type: "web"; +}; + +/** + * GitHub repository learning object + */ +export type Github = Lo & { + type: "github"; +}; + +/** + * Note learning object + * Simple text content + */ +export type Note = Lo & { + type: "note"; +}; + +/** + * Panel-style note learning object + */ +export type PanelNote = Lo & { + type: "panelnote"; +}; + +/** + * Panel-style talk learning object + */ +export type PanelTalk = Talk & { + type: "paneltalk"; +}; + +/** + * Panel-style video learning object + */ +export type PanelVideo = Lo & { + type: "panelvideo"; +}; + +/** + * Collection of panel-style learning objects + */ +export type Panels = { + panelVideos: PanelVideo[]; // Collection of panel videos + panelTalks: PanelTalk[]; // Collection of panel talks + panelNotes: PanelNote[]; // Collection of panel notes +}; + +/** + * Collection of course units and sides + */ +export type Units = { + units: Unit[]; // Main course units + sides: Side[]; // Supplementary units + standardLos: Lo[]; // Standard learning objects +}; + +/** + * Composite learning object structure + */ +export type Composite = Lo & { + toc: Lo[]; // Table of contents + los: Lo[]; // Child los + panels: Panels; // Child panel los + units: Units; // Child units and sides +}; + +/** + * Topic learning object + * Groups related content + */ +export type Topic = Composite & { + type: "topic"; +}; + +/** + * Unit learning object + * Major course section + */ +export type Unit = Composite & { + type: "unit"; +}; + +/** + * Side learning object + * Supplementary content + */ +export type Side = Composite & { + type: "side"; +}; + +/** + * Course learning object + * Top-level container for all course content + */ +export type Course = Composite & { + type: "course"; + courseId: string; // Unique course identifier + courseUrl: string; // Course URL + topicIndex: Map; // Index of all topics + loIndex: Map; // Index of all los + walls?: Lo[][]; // 2D array of learning objects + wallMap?: Map; // Map of learning objects + properties: Properties; // Contents of properties.yaml + calendar?: Properties; // Contents of calendar.yaml + enrollment?: string[]; // Contents of enrollment.yaml + courseCalendar?: Calendar; // Course calendar + authLevel: number; // Access control level + isPortfolio: boolean; // Portfolio flag + isPrivate: boolean; // Privacy flag + areVideosHidden: boolean; // Video visibility flag + areLabStepsAutoNumbered: boolean; // Lab step numbering flag + hasEnrollment: boolean; // Enrollment flag + hasCalendar: boolean; // Calendar flag + hasWhiteList: boolean; // Whitelist flag + defaultPdfReader: string; // PDF reader setting + footer: string; // Footer content + ignorePin: string; // Ignore pin + companions: IconNavBar; // Companion navigation + wallBar: IconNavBar; // Wall navigation +}; + +/** + * Simple learning object types + * Used for type checking and filtering + */ +export const simpleTypes = [ + "note", + "archive", + "web", + "github", + "panelnote", + "paneltalk", + "panelvideo", + "talk", + "book", + "lab" +]; + +/** + * Composite learning object types + * Used for type checking and filtering + */ +export const loCompositeTypes = ["unit", "side", "topic", "course"]; + +/** + * All learning object types + * Used for type checking and filtering + */ +export const loTypes = simpleTypes.concat(loCompositeTypes); + +/** + * Type alias for learning object types + */ +export type LoType = (typeof loTypes)[number]; + +/** + * Checks if a learning object is composite (contains other Los) + * @param lo Learning object to check + * @returns boolean indicating if Lo is composite + */ +export function isCompositeLo(lo: Lo) { + return loCompositeTypes.includes(lo.type); +} + +/** + * Learning object type ordering + * Used for sorting and display + */ +export const preOrder = new Map([ + ["unit", 1], + ["side", 2], + ["talk", 3], + ["book", 4], + ["archive", 5], + ["github", 6], + ["web", 7], + ["note", 8], + ["panelnote", 9], + ["paneltalk", 10], + ["panelvideo", 11] +]); diff --git a/src/lib/services/profiles/supabase-client.ts b/src/lib/services/base/utils/supabase-client.ts similarity index 100% rename from src/lib/services/profiles/supabase-client.ts rename to src/lib/services/base/utils/supabase-client.ts diff --git a/src/lib/services/community/index.ts b/src/lib/services/community/index.ts new file mode 100644 index 000000000..8c9baa11a --- /dev/null +++ b/src/lib/services/community/index.ts @@ -0,0 +1,19 @@ +/** + * Re-exports community services and types for easier imports + * @module + */ + +// Service exports +export { catalogueService } from "./services/catalogue"; +export { liveService } from "./services/live.svelte"; +export { presenceService } from "./services/presence.svelte"; +export { analyticsService } from "./services/analytics.svelte"; + +// Type exports +export type { + LoUser, + PresenceService, + CatalogueService, + CatalogueEntry +} from "./types.svelte"; +export { LoRecord } from "./types.svelte"; diff --git a/src/lib/services/analytics.svelte.ts b/src/lib/services/community/services/analytics.svelte.ts similarity index 94% rename from src/lib/services/analytics.svelte.ts rename to src/lib/services/community/services/analytics.svelte.ts index f5ea70e5b..4ccdc895f 100644 --- a/src/lib/services/analytics.svelte.ts +++ b/src/lib/services/community/services/analytics.svelte.ts @@ -4,16 +4,15 @@ * Handles learning events, page loads, and duration tracking. */ -import type { Course, Lo } from "$lib/services/models/lo-types"; -import type { AnalyticsService, TutorsId } from "./types.svelte"; - +import type { TutorsId } from "$lib/services/connect"; +import type { Course, Lo } from "$lib/services/base"; import { storeStudentCourseLearningObjectInSupabase, updateLearningRecordsDuration, updateCalendarDuration, addOrUpdateStudent, formatDate -} from "./profiles/supabase-client"; +} from "$lib/services/base"; export const analyticsService: AnalyticsService = { /** Current learning object route being tracked */ diff --git a/src/lib/services/catalogue.ts b/src/lib/services/community/services/catalogue.ts similarity index 90% rename from src/lib/services/catalogue.ts rename to src/lib/services/community/services/catalogue.ts index 94b1f4d70..f5b9dc9a9 100644 --- a/src/lib/services/catalogue.ts +++ b/src/lib/services/community/services/catalogue.ts @@ -1,5 +1,5 @@ -import { supabase } from "./profiles/supabase-client"; -import type { CatalogueEntry, CatalogueService } from "./types.svelte"; +import type { CatalogueEntry, CatalogueService } from "$lib/services/community"; +import { supabase } from "$lib/services/base"; export const catalogueService: CatalogueService = { async getCatalogue() { diff --git a/src/lib/services/live.svelte.ts b/src/lib/services/community/services/live.svelte.ts similarity index 95% rename from src/lib/services/live.svelte.ts rename to src/lib/services/community/services/live.svelte.ts index 0c65c37df..585a6f105 100644 --- a/src/lib/services/live.svelte.ts +++ b/src/lib/services/community/services/live.svelte.ts @@ -1,9 +1,8 @@ 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"; +import { rune } from "$lib/runes.svelte"; +import { LoRecord, type LiveService } from "../types.svelte"; /** PartyKit server URL from environment */ const partyKitServer = PUBLIC_party_kit_main_room; @@ -17,7 +16,7 @@ if (PUBLIC_party_kit_main_room !== "XXX") { }); } -export const liveService = { +export const liveService: LiveService = { listeningForCourse: rune(""), coursesOnline: rune([]), studentsOnline: rune([]), diff --git a/src/lib/services/presence.svelte.ts b/src/lib/services/community/services/presence.svelte.ts similarity index 95% rename from src/lib/services/presence.svelte.ts rename to src/lib/services/community/services/presence.svelte.ts index 60b56e458..47a4c030b 100644 --- a/src/lib/services/presence.svelte.ts +++ b/src/lib/services/community/services/presence.svelte.ts @@ -5,10 +5,11 @@ import PartySocket from "partysocket"; 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 { tutorsId } from "$lib/runes"; + +import type { Course, Lo } from "$lib/services/base"; +import { rune, tutorsId } from "$lib/runes.svelte"; +import { LoRecord, type LoUser, type PresenceService } from "../types.svelte"; +import type { TutorsId } from "$lib/services/connect"; /** PartyKit server URL from environment */ const partyKitServer = PUBLIC_party_kit_main_room; diff --git a/src/lib/services/community/types.svelte.ts b/src/lib/services/community/types.svelte.ts new file mode 100644 index 000000000..8a6bede56 --- /dev/null +++ b/src/lib/services/community/types.svelte.ts @@ -0,0 +1,102 @@ +import type { TutorsId } from "$lib/services/connect"; +import type { Course, IconType, Lo } from "$lib/services/base/lo-types"; + +/** + * Minimal user information for learning object interactions + */ +export interface LoUser { + fullName: string; + avatar: string; + id: string; +} + +/** + * Record of user interaction with a learning object + * Uses Svelte's state management for reactivity + */ +export class LoRecord { + courseId: string = $state(""); + courseUrl: string = $state(""); + courseTitle: string = $state(""); + loRoute: string = $state(""); + title: string = $state(""); + img?: string = $state(""); + icon?: IconType = $state(); + isPrivate: boolean = $state(false); + user?: LoUser = $state(); + type: string = $state(""); + + constructor(data: any) { + Object.assign(this, data); + } +} + +/** + * Service for managing real-time user presence and interactions + */ +export interface PresenceService { + /** Currently online students */ + studentsOnline: any; + /** Global PartyKit connection */ + partyKitAll: any; + /** Course-specific PartyKit connection */ + partyKitCourse: any; + /** Map of student events */ + studentEventMap: Map; + /** Currently monitored course ID */ + listeningTo: string; + + studentListener(event: any): void; + sendLoEvent(course: Course, lo: Lo, student: TutorsId): void; + connectToAllCourseAccess(): void; + startPresenceListener(courseId: string): void; +} + +/** + * Service for managing real-time course interactions and student presence + */ +export interface LiveService { + listeningForCourse: { value: string }; + coursesOnline: { value: LoRecord[] }; + studentsOnline: { value: LoRecord[] }; + studentsOnlineByCourse: { value: LoRecord[][] }; + studentEventMap: Map; + courseEventMap: Map; + partyKitCourse: any; + listeningAll: boolean; + + groupedStudentListener(event: any): void; + studentListener(event: any): void; + courseListener(event: any): void; + partyKitListener(event: any): void; + startGlobalPresenceService(): void; + startCoursePresenceListener(courseId: string): void; +} + +/** + * Service for tracking user interactions and learning analytics + */ +export interface AnalyticsService { + loRoute: string; + + learningEvent(course: Course, params: Record, lo: Lo, student: TutorsId): void; + reportPageLoad(course: Course, lo: Lo, student: TutorsId): void; + updatePageCount(course: Course, lo: Lo, student: TutorsId): void; + updateLogin(courseId: string, session: any): void; +} + +export interface CatalogueEntry { + course_id: string; + visited_at: Date; + visit_count: number; + course_record: any; +} + +/** + * Service for managing course catalogue data + */ +export interface CatalogueService { + getCatalogue(): Promise; + getCatalogueCount(): Promise; + getStudentCount(): Promise; +} diff --git a/src/lib/services/connect/index.ts b/src/lib/services/connect/index.ts new file mode 100644 index 000000000..aa383e408 --- /dev/null +++ b/src/lib/services/connect/index.ts @@ -0,0 +1,12 @@ +/** + * Re-exports connect service and types for easier imports + * @module + */ + +export { tutorsConnectService } from "./services/connect.svelte"; +export type { + TutorsConnectService, + TutorsId, + ProfileStore, + CourseVisit +} from "./types"; diff --git a/src/lib/services/connect.svelte.ts b/src/lib/services/connect/services/connect.svelte.ts similarity index 91% rename from src/lib/services/connect.svelte.ts rename to src/lib/services/connect/services/connect.svelte.ts index be1fa20d2..d9804859d 100644 --- a/src/lib/services/connect.svelte.ts +++ b/src/lib/services/connect/services/connect.svelte.ts @@ -6,18 +6,19 @@ import { signOut } from "@auth/sveltekit/client"; import { signIn } from "@auth/sveltekit/client"; -import { rune } from "./utils/runes.svelte"; import { browser } from "$app/environment"; -import type { CourseVisit, TutorsConnectService, TutorsId } from "./types.svelte"; import { goto } from "$app/navigation"; -import type { Course } from "./models/lo-types"; -import { localStorageProfile } from "./profiles/localStorageProfile"; -import { supabaseProfile } from "./profiles/supabaseProfile.svelte"; -import { analyticsService } from "./analytics.svelte"; -import { presenceService } from "./presence.svelte"; +import type { Course } from "$lib/services/base"; + +import { analyticsService, presenceService } from "$lib/services/community"; import { PUBLIC_ANON_MODE } from "$env/static/public"; -import { updateCourseList } from "./profiles/allCourseAccess"; -import { currentCourse, currentLo, tutorsId } from "$lib/runes"; + +import { currentCourse, currentLo, tutorsId } from "$lib/runes.svelte"; +import { localStorageProfile } from "./localStorageProfile"; + +import { updateCourseList } from "../utils/allCourseAccess"; +import type { CourseVisit, TutorsConnectService, TutorsId } from "../types"; +import { supabaseProfile } from "./supabaseProfile.svelte"; /** Global anonymous mode flag, controlled by environment variable */ let anonMode = false; diff --git a/src/lib/services/profiles/localStorageProfile.ts b/src/lib/services/connect/services/localStorageProfile.ts similarity index 95% rename from src/lib/services/profiles/localStorageProfile.ts rename to src/lib/services/connect/services/localStorageProfile.ts index 20c049bd6..2fc659b4f 100644 --- a/src/lib/services/profiles/localStorageProfile.ts +++ b/src/lib/services/connect/services/localStorageProfile.ts @@ -5,8 +5,8 @@ */ import { browser } from "$app/environment"; -import type { Course, IconType } from "../models/lo-types"; -import type { CourseVisit, ProfileStore } from "../types.svelte"; +import type { Course, IconType } from "$lib/services/base"; +import type { CourseVisit, ProfileStore } from "../types"; export const localStorageProfile: ProfileStore = { courseVisits: [] as CourseVisit[], diff --git a/src/lib/services/profiles/supabaseProfile.svelte.ts b/src/lib/services/connect/services/supabaseProfile.svelte.ts similarity index 92% rename from src/lib/services/profiles/supabaseProfile.svelte.ts rename to src/lib/services/connect/services/supabaseProfile.svelte.ts index b137b4e36..21420be07 100644 --- a/src/lib/services/profiles/supabaseProfile.svelte.ts +++ b/src/lib/services/connect/services/supabaseProfile.svelte.ts @@ -5,11 +5,11 @@ * Requires authenticated user context from tutorsConnectService */ -import type { Course, IconType } from "../models/lo-types"; -import { tutorsConnectService } from "$lib/services/connect.svelte.js"; -import type { CourseVisit, ProfileStore } from "../types.svelte"; -import { supabase } from "./supabase-client"; -import { tutorsId } from "$lib/runes"; +import { supabase } from "$lib/services/base"; +import { tutorsId } from "$lib/runes.svelte"; + +import type { Course, IconType } from "$lib/services/base"; +import type { CourseVisit, ProfileStore } from "../types"; export const supabaseProfile: ProfileStore = { courseVisits: [] as CourseVisit[], diff --git a/src/lib/services/connect/types.ts b/src/lib/services/connect/types.ts new file mode 100644 index 000000000..8464027c7 --- /dev/null +++ b/src/lib/services/connect/types.ts @@ -0,0 +1,67 @@ +import type { Course, IconType } from "$lib/services/base/lo-types"; + +/** + * Record of a user's interaction with a course + */ +export type CourseVisit = { + id: string; + title: string; + img?: string; + icon?: IconType; + lastVisit: Date; + credits: string; + visits?: number; + private: boolean; + favourite: boolean; +}; + +/** + * Service for managing user profile data and course interactions + */ +export interface ProfileStore { + /** List of courses visited by user */ + courseVisits: CourseVisit[]; + + reload(): void; + save(): void; + logCourseVisit(course: Course): void; + favouriteCourse(courseId: string): void; + unfavouriteCourse(courseId: string): void; + deleteCourseVisit(courseId: string): void; + getCourseVisits(): Promise; +} + +/** + * User identity information from authentication provider + */ +export type TutorsId = { + name: string; + login: string; + email: string; + image: string; + share: string; +}; + +/** + * Service for managing user authentication and course access + */ +export interface TutorsConnectService { + profile: ProfileStore; + intervalId: any; + anonMode: boolean; + + connect(redirectStr: string): void; + reconnect(user: TutorsId): void; + disconnect(redirectStr: string): void; + toggleShare(): void; + + courseVisit(course: Course): void; + deleteCourseVisit(courseId: string): void; + getCourseVisits(): Promise; + favouriteCourse(courseId: string): void; + unfavouriteCourse(courseId: string): void; + + learningEvent(params: Record): void; + startTimer(): void; + stopTimer(): void; +} diff --git a/src/lib/services/profiles/allCourseAccess.ts b/src/lib/services/connect/utils/allCourseAccess.ts similarity index 91% rename from src/lib/services/profiles/allCourseAccess.ts rename to src/lib/services/connect/utils/allCourseAccess.ts index cfdee323e..9cbf5f1fc 100644 --- a/src/lib/services/profiles/allCourseAccess.ts +++ b/src/lib/services/connect/utils/allCourseAccess.ts @@ -3,9 +3,9 @@ * Service for tracking and managing course access statistics across all users */ -import type { Course, IconType } from "$lib/services/models/lo-types"; -import type { CourseVisit } from "../types.svelte"; -import { supabase } from "./supabase-client"; +import type { Course, IconType } from "$lib/services/base/lo-types"; +import { supabase } from "$lib/services/base/utils/supabase-client"; +import type { CourseVisit } from "../types"; /** * Updates the course access statistics in the database diff --git a/src/lib/services/course/index.ts b/src/lib/services/course/index.ts new file mode 100644 index 000000000..fdc10bdeb --- /dev/null +++ b/src/lib/services/course/index.ts @@ -0,0 +1,27 @@ +/** + * Re-exports course service, utilities and types for easier imports + * @module + */ + +export { courseService } from "./services/course.svelte"; +export { LiveLab } from "./services/live-lab"; + +// Utils exports +export { + filterByType, + setShowHide, + removeLeadingHashes +} from "./utils/lo-utils"; +export { + searchHits, + isValid +} from "./utils/search"; + +// Type exports +export type { + CourseService, + LabService +} from "./types"; +export type { + ResultType +} from "./utils/search"; diff --git a/src/lib/services/course.svelte.ts b/src/lib/services/course/services/course.svelte.ts similarity index 93% rename from src/lib/services/course.svelte.ts rename to src/lib/services/course/services/course.svelte.ts index 60021bbf8..b37cfe879 100644 --- a/src/lib/services/course.svelte.ts +++ b/src/lib/services/course/services/course.svelte.ts @@ -3,13 +3,13 @@ * Handles course loading, caching, and content transformation. */ -import type { Lo, Course, Lab, Note } from "$lib/services/models/lo-types"; -import { decorateCourseTree } from "./models/lo-tree"; -import { LiveLab } from "./models/live-lab"; -import type { CourseService } from "./types.svelte"; -import { markdownService } from "./markdown.svelte"; -import { rune } from "./utils/runes.svelte"; -import { currentCourse, currentLo } from "$lib/runes"; +import type { Lo, Course, Lab, Note } from "$lib/services/base"; +import { decorateCourseTree } from "../utils/lo-tree"; +import { LiveLab } from "./live-lab"; + +import { markdownService } from "$lib/services/markdown"; +import { currentCourse, currentLo, rune } from "$lib/runes.svelte"; +import type { CourseService, LabService } from "../types"; export const courseService: CourseService = { /** Cache of loaded courses indexed by courseId */ @@ -105,7 +105,7 @@ export const courseService: CourseService = { * @param fetchFunction - Fetch implementation * @returns Promise resolving to LiveLab instance */ - async readLab(courseId: string, labId: string, fetchFunction: typeof fetch): Promise { + async readLab(courseId: string, labId: string, fetchFunction: typeof fetch): Promise { const course = await this.readCourse(courseId, fetchFunction); const lastSegment = labId.substring(labId.lastIndexOf("/") + 1); diff --git a/src/lib/services/models/live-lab.ts b/src/lib/services/course/services/live-lab.ts similarity index 95% rename from src/lib/services/models/live-lab.ts rename to src/lib/services/course/services/live-lab.ts index 74a41cdd8..0dcc682d9 100644 --- a/src/lib/services/models/live-lab.ts +++ b/src/lib/services/course/services/live-lab.ts @@ -1,6 +1,6 @@ -import type { Lab } from "./lo-types"; -import type { Course } from "./lo-types"; -import { removeLeadingHashes } from "./lo-utils"; +import type { Lab, Course } from "$lib/services/base"; +import type { LabService } from "../types"; +import { removeLeadingHashes } from "../utils/lo-utils"; function getKeyIndex(map: Map, targetKey: string) { const keysArray = [...map.keys()]; @@ -15,7 +15,7 @@ function truncate(input: string) { return input; } -export class LiveLab { +export class LiveLab implements LabService { course: Course; lab: Lab; url = ""; diff --git a/src/lib/services/course/types.ts b/src/lib/services/course/types.ts new file mode 100644 index 000000000..249b25917 --- /dev/null +++ b/src/lib/services/course/types.ts @@ -0,0 +1,54 @@ +import type { Course, Lab, Lo, Note } from "$lib/services/base/lo-types"; + +/** + * Interface representing a live interactive lab session + * Manages lab content, navigation, and state + */ +export interface LabService { + course: Course; + lab: Lab; + url: string; + objectivesHtml: string; + currentChapterShortTitle: string; + currentChapterTitle: string; + navbarHtml: string; + horizontalNavbarHtml: string; + content: string; + chaptersHtml: Map; + chaptersTitles: Map; + steps: string[]; + index: number; + autoNumber: boolean; + vertical: boolean; + + convertMdToHtml(): void; + refreshStep(): void; + refreshNav(): void; + setCurrentChapter(step: string): void; + setFirstPageActive(): void; + setActivePage(step: string): void; + nextStep(): string; + prevStep(): string; +} + +/** + * Service for managing course content and navigation + */ +export interface CourseService { + /** Cache of loaded courses */ + courses: Map; + /** Cache of live lab instances */ + labs: Map; + /** Cache of processed notes */ + notes: Map; + /** Current course URL */ + courseUrl: any; + + getOrLoadCourse(courseId: string, fetchFunction: typeof fetch): Promise; + readCourse(courseId: string, fetchFunction: typeof fetch): Promise; + readTopic(courseId: string, topicId: string, fetchFunction: typeof fetch): Promise; + readLab(courseId: string, labId: string, fetchFunction: typeof fetch): Promise; + readWall(courseId: string, type: string, fetchFunction: typeof fetch): Promise; + readLo(courseId: string, loId: string, fetchFunction: typeof fetch): Promise; + refreshAllLabs(codeTheme: string): void; +} diff --git a/src/lib/services/models/course-utils.ts b/src/lib/services/course/utils/course-utils.ts similarity index 96% rename from src/lib/services/models/course-utils.ts rename to src/lib/services/course/utils/course-utils.ts index 32b730cf6..5d394b69e 100644 --- a/src/lib/services/models/course-utils.ts +++ b/src/lib/services/course/utils/course-utils.ts @@ -1,5 +1,6 @@ -import { themeService } from "$lib/services/themes.svelte"; -import type { Composite, Course, IconNav, Lo, LoType, Topic } from "./lo-types"; +import { themeService } from "$lib/services/themes"; +import type { IconNav } from "$lib/services/themes"; +import type { Composite, Course, Lo, LoType, Topic } from "$lib/services/base/lo-types"; import { filterByType, setShowHide } from "./lo-utils"; export function createToc(course: Course) { diff --git a/src/lib/services/models/lo-tree.ts b/src/lib/services/course/utils/lo-tree.ts similarity index 96% rename from src/lib/services/models/lo-tree.ts rename to src/lib/services/course/utils/lo-tree.ts index bef8e8ddb..13d7c335b 100644 --- a/src/lib/services/models/lo-tree.ts +++ b/src/lib/services/course/utils/lo-tree.ts @@ -1,4 +1,4 @@ -import { isCompositeLo, type Course, type Lo, type Composite, type LoType, type Topic } from "./lo-types"; +import { isCompositeLo, type Course, type Lo, type Composite, type Topic } from "$lib/services/base/lo-types"; import { allVideoLos, crumbs, @@ -11,7 +11,7 @@ import { filterByType } from "./lo-utils"; import { createCompanions, createWalls, initCalendar, loadPropertyFlags } from "./course-utils"; -import { markdownService } from "../markdown.svelte"; +import { markdownService } from "$lib/services/markdown"; export function decorateCourseTree(course: Course, courseId: string = "", courseUrl = "") { // define course properties diff --git a/src/lib/services/models/lo-utils.ts b/src/lib/services/course/utils/lo-utils.ts similarity index 99% rename from src/lib/services/models/lo-utils.ts rename to src/lib/services/course/utils/lo-utils.ts index e686eafa7..ce8a83595 100644 --- a/src/lib/services/models/lo-utils.ts +++ b/src/lib/services/course/utils/lo-utils.ts @@ -14,7 +14,7 @@ import { type PanelVideo, type PanelNote, type Lab -} from "./lo-types"; +} from "$lib/services/base/lo-types"; export function flattenLos(los: Lo[]): Lo[] { let result: Lo[] = []; diff --git a/src/lib/services/utils/search.ts b/src/lib/services/course/utils/search.ts similarity index 98% rename from src/lib/services/utils/search.ts rename to src/lib/services/course/utils/search.ts index 3d3c58b62..be155e680 100644 --- a/src/lib/services/utils/search.ts +++ b/src/lib/services/course/utils/search.ts @@ -1,5 +1,5 @@ -import type { Lo } from "$lib/services/models/lo-types"; -import { removeLeadingHashes } from "$lib/services/models/lo-utils"; +import type { Lo } from "$lib/services/base/lo-types"; +import { removeLeadingHashes } from "./lo-utils"; const maxNumberHits = 100; const fenceTick = "```"; diff --git a/src/lib/services/markdown/index.ts b/src/lib/services/markdown/index.ts new file mode 100644 index 000000000..a9bfbf72a --- /dev/null +++ b/src/lib/services/markdown/index.ts @@ -0,0 +1,8 @@ +/** + * Re-exports markdown service, utilities and types for easier imports + * @module + */ + +export { markdownService, currentCodeTheme } from "./services/markdown.svelte"; +export { convertMdToHtml } from "./utils/markdown-utils"; +export type { MarkdownService } from "./types"; diff --git a/src/lib/services/markdown.svelte.ts b/src/lib/services/markdown/services/markdown.svelte.ts similarity index 96% rename from src/lib/services/markdown.svelte.ts rename to src/lib/services/markdown/services/markdown.svelte.ts index e454ed5f5..f9d799d93 100644 --- a/src/lib/services/markdown.svelte.ts +++ b/src/lib/services/markdown/services/markdown.svelte.ts @@ -4,8 +4,8 @@ * Handles markdown conversion for labs, notes, and learning objects. */ -import type { Course, Lab, Note, Lo } from "./models/lo-types"; -import { convertMdToHtml, initHighlighter } from "./models/markdown-utils"; +import type { Course, Lab, Note, Lo } from "$lib/services/base"; +import { convertMdToHtml, initHighlighter } from "../utils/markdown-utils"; // Import Shiki themes import ayuDark from "shiki/themes/ayu-dark.mjs"; @@ -51,8 +51,8 @@ import shell from "shiki/langs/shell.mjs"; import xml from "shiki/langs/xml.mjs"; import vue from "shiki/langs/vue.mjs"; import { browser } from "$app/environment"; -import type { MarkdownService } from "./types.svelte"; -import { rune } from "./utils/runes.svelte"; +import type { MarkdownService } from "../types"; +import { rune } from "$lib/runes.svelte"; /** Supported programming languages for syntax highlighting */ const languages = [ diff --git a/src/lib/services/markdown/types.ts b/src/lib/services/markdown/types.ts new file mode 100644 index 000000000..c03b679ed --- /dev/null +++ b/src/lib/services/markdown/types.ts @@ -0,0 +1,16 @@ +import type { Course, Lab, Lo, Note } from "$lib/services/base/lo-types"; + +/** + * Service for processing and rendering markdown content + */ +export interface MarkdownService { + /** Available syntax highlighting themes */ + codeThemes: any; + + setCodeTheme(theme: string): void; + convertLabToHtml(course: Course, lab: Lab, refreshOnly?: boolean): void; + convertNoteToHtml(course: Course, note: Note, refreshOnly?: boolean): void; + convertLoToHtml(course: Course, lo: Lo): void; + replaceAll(str: string, find: string, replace: string): string; + filter(src: string, url: string): string; +} diff --git a/src/lib/services/models/markdown-utils.ts b/src/lib/services/markdown/utils/markdown-utils.ts similarity index 100% rename from src/lib/services/models/markdown-utils.ts rename to src/lib/services/markdown/utils/markdown-utils.ts diff --git a/src/lib/services/models/lo-types.ts b/src/lib/services/models/lo-types.ts deleted file mode 100644 index 76aefbd8f..000000000 --- a/src/lib/services/models/lo-types.ts +++ /dev/null @@ -1,381 +0,0 @@ -/** - * @module LoTypes - * Core type definitions for the Tutors learning object system - * Defines the structure and relationships between different types of learning objects (Los) - */ - -/** - * Supported image file extensions for learning objects - */ -export const imageTypes = ["png", "jpg", "jpeg", "gif", "PNG", "JPG", "JPEG", "GIF"]; - -/** - * Supported asset file types, including images and documents - */ -export const assetTypes = imageTypes.concat(["pdf", "zip", "html", "htm", "yaml", "xls", "xlsx", "xlsm", "csv"]); - -/** - * Represents a week in the course calendar - */ -export type WeekType = { - title: string; // Week title/name - type: string; // Type identifier - date: string; // Date string - dateObj: Date; // JavaScript Date object -}; - -/** - * Course calendar structure - */ -export type Calendar = { - title: string; // Calendar title - weeks: WeekType[]; // Array of course weeks - currentWeek: WeekType; // Current active week -}; - -/** - * Video service identifier - */ -export type VideoIdentifier = { - service: string; // Video platform (e.g., "youtube", "vimeo") - id: string; // Video ID on the platform -}; - -/** - * Collection of video identifiers - */ -export type VideoIdentifiers = { - videoid: string; // Primary video ID - videoIds: VideoIdentifier[]; // All video sources -}; - -/** - * Base structure for learning resources - */ -export type LearningResource = { - courseRoot: string; // Root path of the course - route: string; // URL route to the resource - id: string; // Unique identifier - lrs: LearningResource[]; // Nested learning resources - files: Array; // Associated files - type: string; // Resource type -}; - -/** - * Dynamic property collection for learning objects - */ -export class Properties { - [key: string]: string; -} - -/** - * Icon definition with type and color - */ -export type IconType = { - type: string; // Icon identifier - color: string; // Icon color -}; - -/** - * Navigation icon with link and tooltip - */ -export type IconNav = { - link: string; // Target URL - type: string; // Icon type - tip: string; // Tooltip text - target: string; // Link target -}; - -/** - * Collection of navigation icons - */ -export type IconNavBar = { - show: boolean; // Visibility flag - bar: IconNav[]; // Navigation items -}; - -/** - * Collection of icon definitions - */ -export type IconLib = Record; - -/** - * Theme definition with icons - */ -export type Theme = { - name: string; // Theme name - icons: IconLib; // Theme icons -}; - -/** - * Student interaction tracking for learning objects - */ -export interface LearningRecord { - date: Date; // Interaction date - pageLoads: number; // Page view count - timeActive: number; // Active time in seconds -} - -/** - * Core learning object type - * Represents any content unit in the Tutors system - */ -export type Lo = { - type: string; // Lo type identifier - id: string; // Unique identifier (folder name) - title: string; // Primary title - summary: string; // Brief description - contentMd: string; // Markdown content - frontMatter: Properties; // YAML frontmatter properties - contentHtml?: string; // Rendered HTML content - route: string; // URL path - authLevel: number; // Access control level - - img: string; // Image URL - imgFile: string; // Image filename - icon?: IconType; // Fallback icon - - video: string; // Primary video ID - videoids: VideoIdentifiers; // All video sources - - hide: boolean; // Visibility flag - - parentLo?: Lo; // Parent learning object - parentTopic?: Topic; // Parent topic - parentCourse?: Course; // Parent course - breadCrumbs?: Lo[]; // Navigation hierarchy - - learningRecords?: Map; // Student interaction data -}; - -/** - * Lab step definition - * Represents a single step in a lab - */ -export type LabStep = { - title: string; // Step title - shortTitle: string; // Short title for display - contentMd: string; // Markdown content - contentHtml?: string; // Rendered HTML content - route: string; // URL path - id: string; // Unique identifier - parentLo?: Lab; // Parent lab - type: string; // Step type -}; - -/** - * Lab learning object - * Contains multiple ordered steps - */ -export type Lab = Lo & { - type: "lab"; - los: LabStep[]; - pdf: string; // Route to PDF for the lo - pdfFile: string; // PDF file name -}; - -/** - * Talk learning object - * Represents a presentation or lecture - */ -export type Talk = Lo & { - type: "talk"; - pdf: string; // Route to PDF for the lo - pdfFile: string; // PDF file name -}; - -/** - * Archive learning object - * Represents downloadable content - */ -export type Archive = Lo & { - type: "archive"; - archiveFile?: string; // Archive file in the lo -}; - -/** - * Web resource learning object - */ -export type Web = Lo & { - type: "web"; -}; - -/** - * GitHub repository learning object - */ -export type Github = Lo & { - type: "github"; -}; - -/** - * Note learning object - * Simple text content - */ -export type Note = Lo & { - type: "note"; -}; - -/** - * Panel-style note learning object - */ -export type PanelNote = Lo & { - type: "panelnote"; -}; - -/** - * Panel-style talk learning object - */ -export type PanelTalk = Talk & { - type: "paneltalk"; -}; - -/** - * Panel-style video learning object - */ -export type PanelVideo = Lo & { - type: "panelvideo"; -}; - -/** - * Collection of panel-style learning objects - */ -export type Panels = { - panelVideos: PanelVideo[]; // Collection of panel videos - panelTalks: PanelTalk[]; // Collection of panel talks - panelNotes: PanelNote[]; // Collection of panel notes -}; - -/** - * Collection of course units and sides - */ -export type Units = { - units: Unit[]; // Main course units - sides: Side[]; // Supplementary units - standardLos: Lo[]; // Standard learning objects -}; - -/** - * Composite learning object structure - */ -export type Composite = Lo & { - toc: Lo[]; // Table of contents - los: Lo[]; // Child los - panels: Panels; // Child panel los - units: Units; // Child units and sides -}; - -/** - * Topic learning object - * Groups related content - */ -export type Topic = Composite & { - type: "topic"; -}; - -/** - * Unit learning object - * Major course section - */ -export type Unit = Composite & { - type: "unit"; -}; - -/** - * Side learning object - * Supplementary content - */ -export type Side = Composite & { - type: "side"; -}; - -/** - * Course learning object - * Top-level container for all course content - */ -export type Course = Composite & { - type: "course"; - courseId: string; // Unique course identifier - courseUrl: string; // Course URL - topicIndex: Map;// Index of all topics - loIndex: Map; // Index of all los - walls?: Lo[][]; // 2D array of learning objects - wallMap?: Map; // Map of learning objects - properties: Properties; // Contents of properties.yaml - calendar?: Properties; // Contents of calendar.yaml - enrollment?: string[]; // Contents of enrollment.yaml - courseCalendar?: Calendar; // Course calendar - authLevel: number; // Access control level - isPortfolio: boolean; // Portfolio flag - isPrivate: boolean; // Privacy flag - areVideosHidden: boolean; // Video visibility flag - areLabStepsAutoNumbered: boolean; // Lab step numbering flag - hasEnrollment: boolean; // Enrollment flag - hasCalendar: boolean; // Calendar flag - hasWhiteList: boolean; // Whitelist flag - defaultPdfReader: string; // PDF reader setting - footer: string; // Footer content - ignorePin: string; // Ignore pin - companions: IconNavBar; // Companion navigation - wallBar: IconNavBar; // Wall navigation -}; - -/** - * Simple learning object types - * Used for type checking and filtering - */ -export const simpleTypes = [ - "note", - "archive", - "web", - "github", - "panelnote", - "paneltalk", - "panelvideo", - "talk", - "book", - "lab" -]; - -/** - * Composite learning object types - * Used for type checking and filtering - */ -export const loCompositeTypes = ["unit", "side", "topic", "course"]; - -/** - * All learning object types - * Used for type checking and filtering - */ -export const loTypes = simpleTypes.concat(loCompositeTypes); - -/** - * Type alias for learning object types - */ -export type LoType = (typeof loTypes)[number]; - -/** - * Checks if a learning object is composite (contains other Los) - * @param lo Learning object to check - * @returns boolean indicating if Lo is composite - */ -export function isCompositeLo(lo: Lo) { - return loCompositeTypes.includes(lo.type); -} - -/** - * Learning object type ordering - * Used for sorting and display - */ -export const preOrder = new Map([ - ["unit", 1], - ["side", 2], - ["talk", 3], - ["book", 4], - ["archive", 5], - ["github", 6], - ["web", 7], - ["note", 8], - ["panelnote", 9], - ["paneltalk", 10], - ["panelvideo", 11] -]); diff --git a/src/lib/ui/themes/events/festive.svelte.ts b/src/lib/services/themes/events/festive.svelte.ts similarity index 91% rename from src/lib/ui/themes/events/festive.svelte.ts rename to src/lib/services/themes/events/festive.svelte.ts index 8d269c093..55c37c6bb 100644 --- a/src/lib/ui/themes/events/festive.svelte.ts +++ b/src/lib/services/themes/events/festive.svelte.ts @@ -1,5 +1,6 @@ /* eslint-disable @typescript-eslint/ban-ts-comment */ -import { themeService } from "$lib/services/themes.svelte"; +import { browser } from "$app/environment"; +import { themeService } from "$lib/services/themes"; import { snow } from "./snow"; export async function makeItSnow() { diff --git a/src/lib/ui/themes/events/festive.ts b/src/lib/services/themes/events/festive.ts similarity index 100% rename from src/lib/ui/themes/events/festive.ts rename to src/lib/services/themes/events/festive.ts diff --git a/src/lib/ui/themes/events/snow.ts b/src/lib/services/themes/events/snow.ts similarity index 100% rename from src/lib/ui/themes/events/snow.ts rename to src/lib/services/themes/events/snow.ts diff --git a/src/lib/ui/themes/icons/festive-icons.ts b/src/lib/services/themes/icons/festive-icons.ts similarity index 98% rename from src/lib/ui/themes/icons/festive-icons.ts rename to src/lib/services/themes/icons/festive-icons.ts index b19674867..573bce936 100644 --- a/src/lib/ui/themes/icons/festive-icons.ts +++ b/src/lib/services/themes/icons/festive-icons.ts @@ -1,4 +1,4 @@ -import type { IconLib } from "$lib/services/models/lo-types"; +import type { IconLib } from "../types"; export const FestiveIcons: IconLib = { // Home Icon diff --git a/src/lib/ui/themes/icons/fluent-icons.ts b/src/lib/services/themes/icons/fluent-icons.ts similarity index 98% rename from src/lib/ui/themes/icons/fluent-icons.ts rename to src/lib/services/themes/icons/fluent-icons.ts index b3a20d4f4..c9b0f3ad9 100644 --- a/src/lib/ui/themes/icons/fluent-icons.ts +++ b/src/lib/services/themes/icons/fluent-icons.ts @@ -1,4 +1,4 @@ -import type { IconLib } from "$lib/services/models/lo-types"; +import type { IconLib } from "../types"; export const FluentIconLib: IconLib = { // Home Icon diff --git a/src/lib/ui/themes/icons/halloween-icons.ts b/src/lib/services/themes/icons/halloween-icons.ts similarity index 97% rename from src/lib/ui/themes/icons/halloween-icons.ts rename to src/lib/services/themes/icons/halloween-icons.ts index 88d2383a7..7d6b39585 100644 --- a/src/lib/ui/themes/icons/halloween-icons.ts +++ b/src/lib/services/themes/icons/halloween-icons.ts @@ -1,4 +1,4 @@ -import type { IconLib } from "$lib/services/models/lo-types"; +import type { IconLib } from "../types"; export const HalloweenIconLib: IconLib = { // Home type diff --git a/src/lib/ui/themes/icons/hero-icons.ts b/src/lib/services/themes/icons/hero-icons.ts similarity index 98% rename from src/lib/ui/themes/icons/hero-icons.ts rename to src/lib/services/themes/icons/hero-icons.ts index 9ffa87c09..9689d5ed2 100644 --- a/src/lib/ui/themes/icons/hero-icons.ts +++ b/src/lib/services/themes/icons/hero-icons.ts @@ -1,4 +1,4 @@ -import type { IconLib } from "$lib/services/models/lo-types"; +import type { IconLib } from "../types"; export const HeroIconLib: IconLib = { // Home Icon diff --git a/src/lib/services/themes/index.ts b/src/lib/services/themes/index.ts new file mode 100644 index 000000000..bf50d023d --- /dev/null +++ b/src/lib/services/themes/index.ts @@ -0,0 +1,16 @@ +/** + * Re-exports theme service and types for easier imports + * @module + */ + +export { themeService } from "./services/themes.svelte"; +export type { + ThemeService, + IconNav, + IconNavBar, + IconLib, + Theme, + LayoutType, + CardStyleType, + CardDetails +} from "./types"; diff --git a/src/lib/services/themes.svelte.ts b/src/lib/services/themes/services/themes.svelte.ts similarity index 91% rename from src/lib/services/themes.svelte.ts rename to src/lib/services/themes/services/themes.svelte.ts index b351ce670..e5d1dcab9 100644 --- a/src/lib/services/themes.svelte.ts +++ b/src/lib/services/themes/services/themes.svelte.ts @@ -4,13 +4,13 @@ * Supports multiple icon sets and persists user preferences. */ -import type { IconType, Theme } from "$lib/services/models/lo-types"; -import { FluentIconLib } from "../ui/themes/icons/fluent-icons"; -import { HeroIconLib } from "../ui/themes/icons/hero-icons"; -import { FestiveIcons } from "../ui/themes/icons/festive-icons"; -import { makeItSnow, makeItStopSnowing } from "../ui/themes/events/festive.svelte"; -import type { CardStyleType, LayoutType, ThemeService } from "$lib/services/types.svelte"; -import { rune } from "./utils/runes.svelte"; +import type { IconType, CardStyleType, LayoutType, Theme, ThemeService } from "$lib/services/base"; +import { FluentIconLib } from "../icons/fluent-icons"; +import { HeroIconLib } from "../icons/hero-icons"; +import { FestiveIcons } from "../icons/festive-icons"; +import { makeItSnow, makeItStopSnowing } from "../events/festive.svelte"; + +import { rune } from "$lib/runes.svelte"; /** * Implementation of the ThemeService interface. diff --git a/src/lib/ui/themes/styles/classic.ts b/src/lib/services/themes/styles/classic.ts similarity index 100% rename from src/lib/ui/themes/styles/classic.ts rename to src/lib/services/themes/styles/classic.ts diff --git a/src/lib/ui/themes/styles/dyslexia.ts b/src/lib/services/themes/styles/dyslexia.ts similarity index 100% rename from src/lib/ui/themes/styles/dyslexia.ts rename to src/lib/services/themes/styles/dyslexia.ts diff --git a/src/lib/ui/themes/styles/tutors.ts b/src/lib/services/themes/styles/tutors.ts similarity index 100% rename from src/lib/ui/themes/styles/tutors.ts rename to src/lib/services/themes/styles/tutors.ts diff --git a/src/lib/services/themes/types.ts b/src/lib/services/themes/types.ts new file mode 100644 index 000000000..9fdbb45b7 --- /dev/null +++ b/src/lib/services/themes/types.ts @@ -0,0 +1,86 @@ +/** + * Core type definitions for the Tutors application. + * Defines interfaces for services, data models, and user interactions. + */ + +import type { IconType } from "$lib/services/base/lo-types"; +import type { LoUser } from "$lib/services/community"; + +/** + * Navigation icon with link and tooltip + */ +export type IconNav = { + link: string; // Target URL + type: string; // Icon type + tip: string; // Tooltip text + target: string; // Link target +}; + +/** + * Collection of navigation icons + */ +export type IconNavBar = { + show: boolean; // Visibility flag + bar: IconNav[]; // Navigation items +}; + +/** + * Collection of icon definitions + */ +export type IconLib = Record; + +/** + * Theme definition with icons + */ +export type Theme = { + name: string; // Theme name + icons: IconLib; // Theme icons +}; + +/** Layout type for card display */ +export type LayoutType = "expanded" | "compacted"; + +/** Card style type for display options */ +export type CardStyleType = "portrait" | "landscape" | "circular"; + +/** + * Display information for course/topic/lab cards + */ +export interface CardDetails { + route: string; + title?: string; + type: string; + summary?: string; + summaryEx?: string; + icon?: IconType; + img?: string; + student?: LoUser; + video?: string; +} + +/** + * Service for theme management and icon customization + */ +export interface ThemeService { + /** Available themes with their icon libraries */ + themes: Theme[]; + /** current theme */ + currentTheme: any; + layout: { value: LayoutType }; + lightMode: any; + cardStyle: { value: CardStyleType }; + /** Tracks if festive snow animation is active */ + isSnowing: boolean; + + initDisplay(forceTheme?: string, forceMode?: string): void; + setDisplayMode(mode: string): void; + toggleDisplayMode(): void; + setTheme(theme: string): void; + setLayout(layout: string): void; + toggleLayout(): void; + setCardStyle(style: string): void; + getIcon(type: string): IconType; + addIcon(type: string, icon: IconType): void; + getTypeColour(type: string): string; + eventTrigger(): void; +} diff --git a/src/lib/services/types.svelte.ts b/src/lib/services/types.svelte.ts deleted file mode 100644 index fbe0260e1..000000000 --- a/src/lib/services/types.svelte.ts +++ /dev/null @@ -1,238 +0,0 @@ -/** - * Core type definitions for the Tutors application. - * Defines interfaces for services, data models, and user interactions. - */ - -import type { LiveLab } from "./models/live-lab"; -import type { Course, IconType, Lab, Lo, Note, Theme } from "./models/lo-types"; - -/** - * User identity information from authentication provider - */ -export type TutorsId = { - name: string; - login: string; - email: string; - image: string; - share: string; -}; - -/** - * Record of a user's interaction with a course - */ -export type CourseVisit = { - id: string; - title: string; - img?: string; - icon?: IconType; - lastVisit: Date; - credits: string; - visits?: number; - private: boolean; - favourite: boolean; -}; - -export interface CatalogueEntry { - course_id: string; - visited_at: Date; - visit_count: number; - course_record: any; -} - -/** - * Minimal user information for learning object interactions - */ -export interface LoUser { - fullName: string; - avatar: string; - id: string; -} - -/** - * Record of user interaction with a learning object - * Uses Svelte's state management for reactivity - */ -export class LoRecord { - courseId: string = $state(""); - courseUrl: string = $state(""); - courseTitle: string = $state(""); - loRoute: string = $state(""); - title: string = $state(""); - img?: string = $state(""); - icon?: IconType = $state(); - isPrivate: boolean = $state(false); - user?: LoUser = $state(); - type: string = $state(""); - - constructor(data: any) { - Object.assign(this, data); - } -} - -/** Layout type for card display */ -export type LayoutType = "expanded" | "compacted"; - -/** Card style type for display options */ -export type CardStyleType = "portrait" | "landscape" | "circular"; - -/** - * Display information for course/topic/lab cards - */ -export interface CardDetails { - route: string; - title?: string; - type: string; - summary?: string; - summaryEx?: string; - icon?: IconType; - img?: string; - student?: LoUser; - video?: string; -} - -/** - * Service for managing course content and navigation - */ -export interface CourseService { - /** Cache of loaded courses */ - courses: Map; - /** Cache of live lab instances */ - labs: Map; - /** Cache of processed notes */ - notes: Map; - /** Current course URL */ - courseUrl: any; - - getOrLoadCourse(courseId: string, fetchFunction: typeof fetch): Promise; - readCourse(courseId: string, fetchFunction: typeof fetch): Promise; - readTopic(courseId: string, topicId: string, fetchFunction: typeof fetch): Promise; - readLab(courseId: string, labId: string, fetchFunction: typeof fetch): Promise; - readWall(courseId: string, type: string, fetchFunction: typeof fetch): Promise; - readLo(courseId: string, loId: string, fetchFunction: typeof fetch): Promise; - refreshAllLabs(codeTheme: string): void; -} - -/** - * Service for managing user profile data and course interactions - */ -export interface ProfileStore { - /** List of courses visited by user */ - courseVisits: CourseVisit[]; - - reload(): void; - save(): void; - logCourseVisit(course: Course): void; - favouriteCourse(courseId: string): void; - unfavouriteCourse(courseId: string): void; - deleteCourseVisit(courseId: string): void; - getCourseVisits(): Promise; -} - -/** - * Service for managing user authentication and course access - */ -export interface TutorsConnectService { - profile: ProfileStore; - intervalId: any; - anonMode: boolean; - - connect(redirectStr: string): void; - reconnect(user: TutorsId): void; - disconnect(redirectStr: string): void; - toggleShare(): void; - - courseVisit(course: Course): void; - deleteCourseVisit(courseId: string): void; - getCourseVisits(): Promise; - favouriteCourse(courseId: string): void; - unfavouriteCourse(courseId: string): void; - - learningEvent(params: Record): void; - startTimer(): void; - stopTimer(): void; -} - -/** - * Service for tracking user interactions and learning analytics - */ -export interface AnalyticsService { - /** Current learning object route being tracked */ - loRoute: string; - - learningEvent(course: Course, params: Record, lo: Lo, student: TutorsId): void; - reportPageLoad(course: Course, lo: Lo, student: TutorsId): void; - updatePageCount(course: Course, lo: Lo, student: TutorsId): void; - updateLogin(courseId: string, session: any): void; -} - -/** - * Service for managing real-time user presence and interactions - */ -export interface PresenceService { - /** Currently online students */ - studentsOnline: any; - /** Global PartyKit connection */ - partyKitAll: any; - /** Course-specific PartyKit connection */ - partyKitCourse: any; - /** Map of student events */ - studentEventMap: Map; - /** Currently monitored course ID */ - listeningTo: string; - - studentListener(event: any): void; - sendLoEvent(course: Course, lo: Lo, student: TutorsId): void; - connectToAllCourseAccess(): void; - startPresenceListener(courseId: string): void; -} - -/** - * Service for processing and rendering markdown content - */ -export interface MarkdownService { - /** Available syntax highlighting themes */ - codeThemes: any; - - setCodeTheme(theme: string): void; - convertLabToHtml(course: Course, lab: Lab, refreshOnly?: boolean): void; - convertNoteToHtml(course: Course, note: Note, refreshOnly?: boolean): void; - convertLoToHtml(course: Course, lo: Lo): void; - replaceAll(str: string, find: string, replace: string): string; - filter(src: string, url: string): string; -} - -/** - * Service for theme management and icon customization - */ -export interface ThemeService { - /** Available themes with their icon libraries */ - themes: Theme[]; - /** current theme */ - currentTheme: any; - layout: { value: LayoutType }; - lightMode: any; - cardStyle: { value: CardStyleType }; - /** Tracks if festive snow animation is active */ - isSnowing: boolean; - - initDisplay(forceTheme?: string, forceMode?: string): void; - setDisplayMode(mode: string): void; - toggleDisplayMode(): void; - setTheme(theme: string): void; - setLayout(layout: string): void; - toggleLayout(): void; - setCardStyle(style: string): void; - getIcon(type: string): IconType; - addIcon(type: string, icon: IconType): void; - getTypeColour(type: string): string; - eventTrigger(): void; -} - -/** - * Service for managing course catalogue data - */ -export interface CatalogueService { - getCatalogue(): Promise; - getCatalogueCount(): Promise; - getStudentCount(): Promise; -} diff --git a/src/lib/services/utils/runes.svelte.ts b/src/lib/services/utils/runes.svelte.ts deleted file mode 100644 index 0632aa509..000000000 --- a/src/lib/services/utils/runes.svelte.ts +++ /dev/null @@ -1,11 +0,0 @@ -export const rune = (initialValue: T) => { - let _rune = $state(initialValue); - return { - get value() { - return _rune; - }, - set value(v: T) { - _rune = v; - } - }; -}; diff --git a/src/lib/ui/TutorsShell.svelte b/src/lib/ui/TutorsShell.svelte index 16d6596bc..4a9a7f734 100644 --- a/src/lib/ui/TutorsShell.svelte +++ b/src/lib/ui/TutorsShell.svelte @@ -4,7 +4,7 @@ import { onMount, type Snippet } from "svelte"; import MainNavigator from "./navigators/MainNavigator.svelte"; import { slide } from "svelte/transition"; - import { currentCourse } from "$lib/runes"; + import { currentCourse } from "$lib/runes.svelte"; type Props = { children: Snippet }; let { children }: Props = $props(); diff --git a/src/lib/ui/components/Icon.svelte b/src/lib/ui/components/Icon.svelte index 9670b203e..a115f4b14 100644 --- a/src/lib/ui/components/Icon.svelte +++ b/src/lib/ui/components/Icon.svelte @@ -2,7 +2,7 @@ import Icon from "@iconify/svelte"; import { Tooltip } from "@skeletonlabs/skeleton-svelte"; - import { themeService } from "../../services/themes.svelte"; + import { themeService } from "$lib/services/themes"; interface Props { type?: string; diff --git a/src/lib/ui/components/IconBar.svelte b/src/lib/ui/components/IconBar.svelte index c285a3df6..02507b1b5 100644 --- a/src/lib/ui/components/IconBar.svelte +++ b/src/lib/ui/components/IconBar.svelte @@ -1,6 +1,6 @@ diff --git a/src/lib/ui/learning-objects/structure/LoContext.svelte b/src/lib/ui/learning-objects/structure/LoContext.svelte index ba19dd697..fc42b1fb1 100644 --- a/src/lib/ui/learning-objects/structure/LoContext.svelte +++ b/src/lib/ui/learning-objects/structure/LoContext.svelte @@ -1,5 +1,5 @@ - import type { Lo } from "$lib/services/models/lo-types"; + import type { Lo } from "$lib/services/base/lo-types"; import Icon from "$lib/ui/components/Icon.svelte"; - import { currentCourse, currentLo } from "$lib/runes"; + import { currentCourse, currentLo } from "$lib/runes.svelte"; let truncated = [true, true, true, true, true, true, true]; @@ -35,8 +35,8 @@ }); -
-
    +
    +
    1. {#if currentCourse?.value?.courseCalendar?.currentWeek} diff --git a/src/lib/ui/navigators/buttons/EditCoursButton.svelte b/src/lib/ui/navigators/buttons/EditCoursButton.svelte index 869b4df12..07dce804f 100644 --- a/src/lib/ui/navigators/buttons/EditCoursButton.svelte +++ b/src/lib/ui/navigators/buttons/EditCoursButton.svelte @@ -1,6 +1,6 @@ diff --git a/src/lib/ui/navigators/buttons/OnlineButton.svelte b/src/lib/ui/navigators/buttons/OnlineButton.svelte index 26e55eea7..5567cf458 100644 --- a/src/lib/ui/navigators/buttons/OnlineButton.svelte +++ b/src/lib/ui/navigators/buttons/OnlineButton.svelte @@ -1,5 +1,5 @@ diff --git a/src/lib/ui/navigators/buttons/SearchButton.svelte b/src/lib/ui/navigators/buttons/SearchButton.svelte index 51c2c7969..1808c4591 100644 --- a/src/lib/ui/navigators/buttons/SearchButton.svelte +++ b/src/lib/ui/navigators/buttons/SearchButton.svelte @@ -1,6 +1,6 @@ diff --git a/src/lib/ui/navigators/footers/Footer.svelte b/src/lib/ui/navigators/footers/Footer.svelte index 9f87232a8..e65146fbc 100644 --- a/src/lib/ui/navigators/footers/Footer.svelte +++ b/src/lib/ui/navigators/footers/Footer.svelte @@ -1,5 +1,5 @@ diff --git a/src/lib/ui/navigators/tutors-connect/ConnectedProfile.svelte b/src/lib/ui/navigators/tutors-connect/ConnectedProfile.svelte index 0900d01cf..ca2fc83a1 100644 --- a/src/lib/ui/navigators/tutors-connect/ConnectedProfile.svelte +++ b/src/lib/ui/navigators/tutors-connect/ConnectedProfile.svelte @@ -1,11 +1,11 @@ diff --git a/src/lib/ui/time/CourseGroup.svelte b/src/lib/ui/time/CourseGroup.svelte index b09932e91..be1b48953 100644 --- a/src/lib/ui/time/CourseGroup.svelte +++ b/src/lib/ui/time/CourseGroup.svelte @@ -1,5 +1,5 @@ diff --git a/src/lib/ui/time/Courses.svelte b/src/lib/ui/time/Courses.svelte index f5b1c1307..9a704d760 100644 --- a/src/lib/ui/time/Courses.svelte +++ b/src/lib/ui/time/Courses.svelte @@ -1,5 +1,5 @@ diff --git a/src/lib/ui/time/CoursesGroup.svelte b/src/lib/ui/time/CoursesGroup.svelte index 31c8aa399..b7afe5fa6 100644 --- a/src/lib/ui/time/CoursesGroup.svelte +++ b/src/lib/ui/time/CoursesGroup.svelte @@ -1,5 +1,5 @@ diff --git a/src/lib/ui/time/Students.svelte b/src/lib/ui/time/Students.svelte index aa4f652f0..5d1f626d9 100644 --- a/src/lib/ui/time/Students.svelte +++ b/src/lib/ui/time/Students.svelte @@ -1,5 +1,5 @@ diff --git a/src/routes/(auth)/auth/SigninWithGithub.svelte b/src/routes/(auth)/auth/SigninWithGithub.svelte index 5174308dd..037156875 100644 --- a/src/routes/(auth)/auth/SigninWithGithub.svelte +++ b/src/routes/(auth)/auth/SigninWithGithub.svelte @@ -1,9 +1,9 @@ {#if tutorsId.value?.name} diff --git a/src/routes/(live)/+layout.svelte b/src/routes/(live)/+layout.svelte index 5e6a05a31..74128ae2f 100644 --- a/src/routes/(live)/+layout.svelte +++ b/src/routes/(live)/+layout.svelte @@ -1,7 +1,6 @@