Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added apps/docs/public/assets/lessons/scorm/create.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/docs/public/assets/lessons/scorm/upload.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 7 additions & 1 deletion apps/docs/src/pages/en/courses/add-content.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ Sections are used to group lessons.

## Lessons

A lesson is a container for the actual learning material. CourseLit supports seven types of lessons, which are as follows.
A lesson is a container for the actual learning material. CourseLit supports multiple types of lessons, which are as follows.

1. Text

Expand Down Expand Up @@ -48,6 +48,12 @@ A lesson is a container for the actual learning material. CourseLit supports sev

See the [guide to add a quiz](/en/lessons/add-quiz).

8. SCORM

For sharing SCORM packages.

See the [guide to add a SCORM package](/en/lessons/scorm).

## Steps to add a new lesson

1. From the `Products` section in the dashboard, select your product to open its dashboard.
Expand Down
137 changes: 137 additions & 0 deletions apps/docs/src/pages/en/lessons/scorm.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
---
title: Add a SCORM package to a course
description: Add a SCORM package to a course
layout: ../../../layouts/MainLayout.astro
---

You can add SCORM packages to your courses in CourseLit. This allows you to import interactive e-learning content created with tools like Articulate Storyline, Rise, Adobe Captivate, iSpring, and more.

> The feature is currently in alpha, which means you may encounter bugs. Please report them in our <a href="https://discord.com/invite/GR4bQsN" target="_blank">Discord</a> group if you run into any.

## What is SCORM?

SCORM (Sharable Content Object Reference Model) is an industry standard for e-learning content. It allows content created in one tool to be used in any SCORM-compliant LMS.

CourseLit supports both **SCORM 1.2** and **SCORM 2004** packages.

## Add a SCORM lesson

1. Go to the `Products` page and click on the course you want to add SCORM content to. Click on `Edit content`.

2. Click on `Add lesson` in any section.

3. On the New Lesson screen, you'll see a row of lesson type cards. Click on the `SCORM` card to select it.

4. Enter a title for your lesson and hit `Save`.

![create SCORM lesson](/assets/lessons/scorm/create.png)

> **Note:** SCORM lessons cannot be previewed. The `Preview` switch will have no effect.

5. A SCORM upload area will appear. Click `Choose File` and select your SCORM package (ZIP file). The maximum file size is **300MB**.

![upload SCORM package](/assets/lessons/scorm/upload.png)

6. Wait for the upload to complete. CourseLit will automatically validate the package and extract the course structure.

![uploaded SCORM package](/assets/lessons/scorm/uploaded.png)

## Replacing a SCORM package

To update an existing SCORM lesson with a new version of the package:

1. Open the SCORM lesson for editing
2. Click the `Replace` button
3. Select the new ZIP file
4. Wait for the upload to complete

## Supported SCORM features

| Feature | SCORM 1.2 | SCORM 2004 |
| --------------------- | --------- | ---------- |
| Progress tracking | ✅ | ✅ |
| Completion status | ✅ | ✅ |
| Resume (suspend data) | ✅ | ✅ |
| Session time | ✅ | ✅ |
| Score reporting | ✅ | ✅ |

## How course completion is calculated

CourseLit uses the data reported by the SCORM package to determine completion. When a learner clicks **Complete and Continue**, CourseLit checks the SCORM status stored in the database.

A lesson is considered complete if **ANY** of the following conditions are met:

1. **Explicit Completion:** The package reports a status of `completed` or `passed`.

- For SCORM 1.2: `cmi.core.lesson_status` is `completed` or `passed`.
- For SCORM 2004: `cmi.completion_status` is `completed` or `cmi.success_status` is `passed`.

2. **Participation Fallback:** If the package does not report a completion status, CourseLit checks for evidence of participation. The lesson will be marked as complete if any of the following fields are present:
- `cmi.suspend_data` (User made progress)
- `cmi.core.session_time` (Time spent is recorded)
- `cmi.core.exit` (Clean exit occurred)

> **Note:** If none of these conditions are met, the learner will see an error message asking them to complete the content first.

## Learner experience

When a learner opens a SCORM lesson:

1. An **Enter** button is displayed

![enter SCORM lesson](/assets/lessons/scorm/learner-enter.png)

2. Clicking the button opens the SCORM content in a popup window

![Popup SCORM lesson](/assets/lessons/scorm/learner-popup.png)

3. Progress is automatically saved as the learner interacts with the content
4. When the learner closes the popup and returns, they can click **Complete and Continue** to proceed

> **Note:** Progress is preserved even if the browser is closed unexpectedly. When the learner returns, they will resume from where they left off.

## Technical notes

### For self-hosted setups

#### Enabling SCORM

SCORM requires disk-based caching to be enabled. Set the `CACHE_DIR` environment variable to enable SCORM support:

| Variable | Description | Required |
| -------------------------- | ------------------------------------------------------- | ------------------- |
| `CACHE_DIR` | Directory path for cache (SCORM uses `CACHE_DIR/scorm`) | **Yes** |
| `SCORM_PACKAGE_SIZE_LIMIT` | Maximum upload size for SCORM packages (in bytes) | No (default: 300MB) |

> **Note:** If `CACHE_DIR` is not set, SCORM uploads will be disabled and the SCORM lesson type will appear grayed out in the lesson creator.

#### Docker Compose Example

```yaml
services:
web:
image: your-app
deploy:
replicas: 3
volumes:
- cache-data:/app/cache
environment:
- CACHE_DIR=/app/cache

volumes:
cache-data:
```

#### Serverless environments

For serverless environments (Vercel, AWS Lambda), you can use `/tmp` as the cache directory:

```
CACHE_DIR=/tmp
```

Note that `/tmp` is ephemeral in serverless - extracted files will be re-extracted on cold starts, but this still works correctly.

## Stuck somewhere?

We are always here for you. Come chat with us in our <a href="https://discord.com/invite/GR4bQsN" target="_blank">Discord</a> channel or send a tweet at <a href="https://twitter.com/courselit" target="_blank">@CourseLit</a>.
5 changes: 4 additions & 1 deletion apps/web/.env
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,7 @@
# SUPER_ADMIN_EMAIL=your@email.com

# Sequence settings
# SEQUENCE_DELAY_BETWEEN_MAILS = 86400000 # 1 day in milliseconds
# SEQUENCE_DELAY_BETWEEN_MAILS = 86400000 # 1 day in milliseconds

# Cache directory
# CACHE_DIR=/tmp
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import {
Video,
HelpCircle,
ChevronDown,
Droplets,
} from "lucide-react";
import Link from "next/link";
import {
Expand All @@ -52,7 +53,6 @@ import {
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
import { Droplets } from "lucide-react";
const { permissions } = UIConstants;

export default function ContentPage() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,12 @@ jest.mock("@courselit/components-library", () => ({
useToast: () => ({
toast: jest.fn(),
}),
Chip: ({ children }: any) => <div data-testid="chip">{children}</div>,
useMediaLit: () => ({
uploadFile: jest.fn(),
isUploading: false,
uploadProgress: 0,
}),
}));

jest.mock("../lesson-content-renderer", () => ({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,45 @@ export function LessonContentRenderer({
)}
</div>
);
case Constants.LessonType.SCORM:
return (
<div className="space-y-4">
<div className="p-4 rounded-lg border bg-muted/50">
{(lesson.content as any)?.mediaId ? (
<div className="space-y-2">
<div className="flex items-center gap-2 text-sm">
<span className="font-medium">
SCORM Package:
</span>
<span className="text-muted-foreground">
{(lesson.content as any)?.title ||
"Uploaded"}
</span>
</div>
<div className="text-xs text-muted-foreground">
Version:{" "}
{(lesson.content as any)?.version || "1.2"}
{(lesson.content as any)?.fileCount &&
` • ${(lesson.content as any)?.fileCount} files`}
</div>
</div>
) : (
<div className="text-center py-4">
<p className="text-sm text-muted-foreground mb-2">
Save the lesson first, then upload the SCORM
package.
</p>
</div>
)}
</div>
{!lesson?.lessonId && (
<p className="text-xs text-muted-foreground flex items-center gap-2">
<Info className="w-4 h-4" />
Save the lesson to enable SCORM package upload
</p>
)}
</div>
);
default:
return null;
}
Expand Down
Loading
Loading