Skip to content

Commit

Permalink
course management data-providers refactor (#1137)
Browse files Browse the repository at this point in the history
* started wit hrefactor of course management

* add dao & route

* update route

* started wit hfetching

* Add changeset abstraction

* cleanup file

* remove get all streams again, as there is AdminJson method

* Its rendering

* further dev :S

* added some directive

* little fixes

* ...

* ... no idea...

* right path i guess

* changeset working except video sections

* fix discard files

* save from laptop

* Add changeset doku

* make private works like charm

* changing works pretty well

* add video upload

* add series update

* ported transcoding

* add readme and lecture hall select

* more description

* simplified lecture hall set

* linter

* more fixes

* :)

* add attachments

* :)

* Impl. Feedback

* fix delete lecture

* reenable page reload on create
  • Loading branch information
mono424 authored and SebiWrn committed May 7, 2024
1 parent b475124 commit ba85813
Show file tree
Hide file tree
Showing 12 changed files with 1,286 additions and 592 deletions.
16 changes: 14 additions & 2 deletions api/courses.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ import (
"errors"
"fmt"
"github.com/RBG-TUM/commons"
"github.com/getsentry/sentry-go"
"github.com/gin-gonic/gin"
"github.com/TUM-Dev/gocast/dao"
"github.com/TUM-Dev/gocast/model"
"github.com/TUM-Dev/gocast/tools"
"github.com/TUM-Dev/gocast/tools/tum"
"github.com/getsentry/sentry-go"
"github.com/gin-gonic/gin"
"github.com/meilisearch/meilisearch-go"
uuid "github.com/satori/go.uuid"
log "github.com/sirupsen/logrus"
Expand Down Expand Up @@ -61,6 +61,7 @@ func configGinCourseRouter(router *gin.Engine, daoWrapper dao.DaoWrapper) {
courses.Use(tools.InitCourse(daoWrapper))
courses.Use(tools.AdminOfCourse)
courses.DELETE("/", routes.deleteCourse)
courses.GET("/lectures", routes.fetchLectures)
courses.POST("/createVOD", routes.createVOD)
courses.POST("/uploadVODMedia", routes.uploadVODMedia)
courses.POST("/copy", routes.copyCourse)
Expand Down Expand Up @@ -1038,6 +1039,17 @@ func (r coursesRoutes) renameLecture(c *gin.Context) {
}
}

func (r coursesRoutes) fetchLectures(c *gin.Context) {
tlctx := c.MustGet("TUMLiveContext").(tools.TUMLiveContext)

lectureHalls := r.LectureHallsDao.GetAllLectureHalls()
streams := tlctx.Course.AdminJson(lectureHalls)

c.JSON(http.StatusOK, gin.H{
"streams": streams,
})
}

func (r coursesRoutes) updateLectureSeries(c *gin.Context) {
stream, err := r.StreamsDao.GetStreamByID(context.Background(), c.Param("streamID"))
if err != nil {
Expand Down
157 changes: 157 additions & 0 deletions web/assets/init-admin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
document.addEventListener("alpine:init", () => {
const textInputTypes = [
'text',
'password',
'email',
'search',
'url',
'tel',
'number',
'datetime-local',
'date',
'month',
'week',
'time',
'color'
];

const convert = (modifiers, value) => {
if (modifiers.includes("int")) {
return parseInt(value);
} else if (modifiers.includes("float")) {
return parseFloat(value);
}
return value;
}

/**
* Alpine.js Directive: `x-bind-change-set`
*
* This directive allows you to synchronize form elements with a given changeSet object.
* It is designed to work with different form input types including text inputs,
* textareas, checkboxes, and file inputs.
*
* ## Parameters
*
* - `el`: The DOM element this directive is attached to.
* - `expression`: The JavaScript expression passed to the directive, evaluated to get the changeSet object.
* - `value`: Optional parameter to specify the field name. Defaults to the `name` attribute of the element.
* - `modifiers`: Array of additional modifiers to customize the behavior. For instance, use 'single' for single-file uploads.
* - `evaluate`: Function to evaluate Alpine.js expressions.
* - `cleanup`: Function to remove event listeners when element is destroyed or directive is unbound.
*
* ## Events
*
* This directive emits a custom event named "csupdate" whenever the changeSet object or the form element is updated.
*
* ## Usage
*
* ### Example in HTML
*
* ```html
* <select name="lectureHallId" x-bind-change-set="changeSet">
* <option value="0">Self streaming</option>
* <!-- ... other options ... -->
* </select>
* ```
*
* - `changeSet`: The changeSet object you want to bind with the form element.
*
* ## Modifiers
*
* - `single`: Use this modifier for file inputs when you want to work with a single file instead of a FileList.
* - `int`: Use this modifier to convert the inserted value to integer.
* - `float`: Use this modifier to convert the inserted value to float.
*
* ```html
* <input type="file" x-bind-change-set.single="changeSet" />
* ```
*
* ## Notes
*
* This directive is intended to be used with the existing `ChangeSet` class.
* Make sure to import and initialize a `ChangeSet` object in your Alpine.js component
* to utilize this directive effectively. The `ChangeSet` class should have implemented
* methods such as `patch`, `listen`, `removeListener`, and `get`,
* and manage a `DirtyState` object for tracking changes.
*/

Alpine.directive("bind-change-set", (el, { expression, value, modifiers }, { evaluate, cleanup }) => {
const changeSet = evaluate(expression);
const fieldName = value || el.name;
const nativeEventName = "csupdate";

if (el.type === "file") {
const isSingle = modifiers.includes("single")

const changeHandler = (e) => {
changeSet.patch(fieldName, isSingle ? e.target.files[0] : e.target.files);
};

const onChangeSetUpdateHandler = (data) => {
if (!data[fieldName]) {
el.value = "";
}
el.dispatchEvent(new CustomEvent(nativeEventName, { detail: data[fieldName] }));
};

changeSet.listen(onChangeSetUpdateHandler);
el.addEventListener('change', changeHandler);

cleanup(() => {
changeSet.removeListener(onChangeSetUpdateHandler);
el.removeEventListener('change', changeHandler)
})
} else if (el.type === "checkbox") {
const changeHandler = (e) => {
changeSet.patch(fieldName, e.target.checked);
};

const onChangeSetUpdateHandler = (data) => {
el.checked = !!data[fieldName];
el.dispatchEvent(new CustomEvent(nativeEventName, { detail: !!data[fieldName] }));
};

changeSet.listen(onChangeSetUpdateHandler);
el.addEventListener('change', changeHandler)
el.checked = changeSet.get()[fieldName];

cleanup(() => {
changeSet.removeListener(onChangeSetUpdateHandler);
el.removeEventListener('change', changeHandler)
})
} else if (el.tagName === "textarea" || textInputTypes.includes(el.type)) {
const keyupHandler = (e) => changeSet.patch(fieldName, convert(modifiers, e.target.value));

const onChangeSetUpdateHandler = (data) => {
el.value = `${data[fieldName]}`;
el.dispatchEvent(new CustomEvent(nativeEventName, { detail: data[fieldName] }));
};

changeSet.listen(onChangeSetUpdateHandler);
el.addEventListener('keyup', keyupHandler)
el.value = `${changeSet.get()[fieldName]}`;

cleanup(() => {
changeSet.removeListener(onChangeSetUpdateHandler);
el.removeEventListener('keyup', keyupHandler)
})
} else {
const changeHandler = (e) => changeSet.patch(fieldName, convert(modifiers, e.target.value));

const onChangeSetUpdateHandler = (data) => {
el.value = `${data[fieldName]}`;
el.dispatchEvent(new CustomEvent(nativeEventName, { detail: data[fieldName] }));
};

changeSet.listen(onChangeSetUpdateHandler);
el.addEventListener('change', changeHandler)
el.value = `${changeSet.get()[fieldName]}`;

cleanup(() => {
changeSet.removeListener(onChangeSetUpdateHandler);
el.removeEventListener('change', changeHandler)
})
}
});
});
1 change: 1 addition & 0 deletions web/template/admin/admin.gohtml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
<meta charset="UTF-8">
<title>{{.IndexData.Branding.Title}} | Administration</title>
{{template "headImports" .IndexData.VersionTag}}
<script src="/static/assets/init-admin.js"></script>
<script src="/static/assets/ts-dist/admin.bundle.js?v={{.IndexData.VersionTag}}"></script>
</head>
<body>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,8 @@
</div>

{{- /* streams: */ -}}
<div x-data="{ lectures: [], marked: [] }"
x-on:newlectures.window="e => {lectures = e.detail.lectures; marked = e.detail.markedIds; }"
x-init="$nextTick(() => admin.LectureList.init({{toJson ($course.AdminJson $lectureHalls)}}))">
<div x-data="{ lectureList: new admin.LectureList({{$course.Model.ID}}), lectures: [], marked: [] }"
x-on:newlectures.window="e => {lectures = e.detail.lectures; marked = e.detail.markedIds; }">
<ul class="flex" :class="sortAsc ? 'flex-col' : 'flex-col-reverse'">
<template x-for="lecture in lectures" :key="lecture.lectureId">
{{template "lecture-management-card" .}}
Expand Down
Loading

0 comments on commit ba85813

Please sign in to comment.