Skip to content

Commit bc5c33e

Browse files
committed
feat: #279 support custom component for course page
1 parent 6153f45 commit bc5c33e

8 files changed

Lines changed: 72 additions & 1 deletion

File tree

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
from enum import StrEnum
2+
from pydantic import BaseModel
3+
4+
5+
class Display(StrEnum):
6+
INLINE = "inline"
7+
BLOCK = "block"
8+
INLINE_BLOCK = "inline-block"
9+
NONE = "none"
10+
11+
12+
class WebComponent(BaseModel):
13+
script_url: str | None = None
14+
style_url: str | None = None
15+
html: str
16+
container_display: Display = Display.INLINE_BLOCK

django_email_learning/platform/views.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,7 @@ def get_context_data(self, **kwargs) -> dict: # type: ignore[no-untyped-def]
223223
context["appContext"]["courseId"] = course.id
224224
context["appContext"]["courseTitle"] = course.title
225225
context["appContext"]["courseLanguage"] = course.language
226+
context["appContext"]["customComponent"] = None
226227
context["appContext"]["direction"] = (
227228
"rtl" if get_language_info(course.language)["bidi"] else "ltr"
228229
)
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
{% extends "platform/base.html" %}
22
{% load django_vite %}
33
{% block extra_head %}
4+
{% if appContext.customComponent %}
5+
{% if appContext.customComponent.script_url %}
6+
<script src="{{ appContext.customComponent.script_url }}"></script>
7+
{% endif %}
8+
{% if appContext.customComponent.style_url %}
9+
<link rel="stylesheet" href="{{ appContext.customComponent.style_url }}">
10+
{% endif %}
11+
{% endif %}
412
{% vite_asset 'platform/course/Course.jsx' %}
513
{% endblock %}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
let clickCount = 0;
2+
3+
document.addEventListener("click", (event) => {
4+
const button = event.target.closest("#custom-btn");
5+
if (!button) {
6+
return;
7+
}
8+
9+
clickCount += 1;
10+
button.textContent = `Clicked: ${clickCount} times`;
11+
});
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
.custom-component-wrapper {
2+
float: right;
3+
}
4+
5+
#custom-btn {
6+
background-color: #eeeeee;
7+
border: none;
8+
padding: 10px 20px;
9+
cursor: pointer;
10+
border-radius: 5px;
11+
z-index: 1000;
12+
}

django_service/urls.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
from django.conf import settings
2222
from django.conf.urls.static import static
2323

24+
from django_service.views import CustomComponentCourseView
25+
2426

2527
urlpatterns = [
2628
path(
@@ -58,6 +60,11 @@
5860
),
5961
name="password_reset_complete",
6062
),
63+
path(
64+
"email_learning/platform/courses/<int:course_id>/",
65+
CustomComponentCourseView.as_view(),
66+
name="course",
67+
),
6168
path(
6269
"email_learning/",
6370
include("django_email_learning.urls", namespace="django_email_learning"),

django_service/views.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
from django_email_learning.platform.views import CourseView
2+
from django_email_learning.platform.serializers import WebComponent
3+
4+
5+
class CustomComponentCourseView(CourseView):
6+
def get_context_data(self, **kwargs) -> dict: # type: ignore[no-untyped-def]
7+
context = super().get_context_data(**kwargs)
8+
9+
custom_component = WebComponent(
10+
html="<button id='custom-btn'>Clicked 0 times!</button>",
11+
style_url="/static/styles/custom-component.css",
12+
script_url="/static/scripts/custom-component.js",
13+
)
14+
context["appContext"]["customComponent"] = custom_component.model_dump()
15+
return context

frontend/platform/course/Course.jsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ const DeleteContentForm = lazy(() => import("./components/DeleteContentForm.jsx"
2121

2222

2323
function Course() {
24-
const { courseTitle, courseId, localeMessages, direction, userRole, apiBaseUrl, platformBaseUrl } = useAppContext();
24+
const { courseTitle, courseId, localeMessages, direction, userRole, apiBaseUrl, platformBaseUrl, customComponent } = useAppContext();
2525
const [dialogOpen, setDialogOpen] = useState(false)
2626
const [dialogContent, setDialogContent] = useState(null)
2727
const [contentLoaded, setContentLoaded] = useState(false)
@@ -313,6 +313,7 @@ function Course() {
313313
setDialogOpen(true);}}>{localeMessages["add_quiz"]}</Button>
314314
{userRole === 'admin' && <EnrollMenu successCallback={handleEnrollMenuSuccess} />}
315315
</> }
316+
{customComponent && <Box className="custom-component-wrapper" sx={{ display: customComponent.container_display }} dangerouslySetInnerHTML={{ __html: customComponent.html }}></Box>}
316317
<ContentTable courseId={courseId} loaded={contentLoaded} eventHandler={(event) => tableEventHandler(event)} />
317318
</Box>
318319
<Box sx={{ mt: 3, mb: 3 }}>

0 commit comments

Comments
 (0)