feat: add media resolution updates & API endpoints

Introduced a new API endpoint to retrieve media for a given category,
enhancing the user experience by dynamically updating thumbnails
post-upload based on available resolutions. This connects the frontend
to the backend efficiently, ensuring users can see the immediate impact
of their uploads. Additionally, updated serializers and views in the
backend to support this functionality, alongside improving code comments
for future development clarity.

- Implemented `getCategoryMedia` in the JS API for fetching media
resolutions.
- Enabled dynamic thumbnail updates in the user area post file upload,
improving visual feedback.
- Extended the Django REST framework routers to include the new media
endpoint, facilitating easier access to media items.
- Updated the `OriginalMediaSerializer` to include `category` for more
detailed media information.
- Enriched task and views files with necessary TODO comments to guide
future enhancements and exception handling.

This change significantly improves the content management workflow,
making it more interactive and user-friendly.
This commit is contained in:
Kumi 2024-03-17 08:35:40 +01:00
parent 6dc56e02bd
commit d921c2dcad
Signed by: kumi
GPG key ID: ECBCC9082395383F
6 changed files with 56 additions and 12 deletions

View file

@ -50,6 +50,22 @@ function getCategory(category) {
);
}
function getCategoryMedia(category_uuid, uuid) {
return api
.then(
(client) =>
client.apis.tours.tours_api_category_media_retrieve({
category: category_uuid,
id: uuid,
}),
(reason) => console.error("Failed to load OpenAPI spec: " + reason)
)
.then(
(result) => result,
(reason) => console.error("Failed to execute API call: " + reason)
);
}
// Function to get the CSRF token cookie. Not exactly "API", but fits here best.
function getCookie(name) {
let cookieValue = null;
@ -66,4 +82,4 @@ function getCookie(name) {
return cookieValue;
}
export { getScene, getSceneElement, getCategory, getCookie };
export { getScene, getSceneElement, getCategory, getCategoryMedia, getCookie };

View file

@ -1,7 +1,7 @@
import "../scss/frontend.scss";
import "../css/userarea.css";
import { getCookie, getElementById } from "./api";
import { getCategoryMedia, getCookie, getElementById } from "./api";
import { Tab } from "bootstrap";
import DataTable from "datatables.net-dt";
@ -61,7 +61,7 @@ function handleFiles(files) {
thumbnailCol.classList.add("col-2");
const thumbnail = document.createElement("img");
thumbnail.classList.add("img-fluid", "thumbnail");
thumbnail.src = ""; // Placeholder until upload completes
thumbnail.src = ""; // TODO: Add a loading spinner or something here
thumbnailCol.appendChild(thumbnail);
const fileNameCol = document.createElement("div");
@ -129,11 +129,19 @@ function uploadFile(file, progressBar, thumbnail) {
xhr.onload = () => {
if (xhr.status === 201) {
const response = JSON.parse(xhr.responseText);
progressBar.classList.add("bg-success");
progressBar.textContent = "Success!";
progressBar.textContent = "Converting...";
// TODO: Check API until the first resolution is available
let thumbnailCheck = setInterval(() => {
getCategoryMedia(response.category, response.id).then((media) => {
if (media.obj.resolutions.length > 0) {
clearInterval(thumbnailCheck);
thumbnail.src = media.obj.resolutions[0].file;
progressBar.classList.add("bg-success");
progressBar.textContent = "Done!";
}
});
}, 2000);
} else {
progressBar.classList.add("bg-danger");
progressBar.textContent = "Error!";

View file

@ -76,7 +76,15 @@ class OriginalMediaSerializer(serializers.ModelSerializer):
class Meta:
model = OriginalMedia
fields = ["id", "title", "media_type", "width", "height", "resolutions"]
fields = [
"id",
"category",
"title",
"media_type",
"width",
"height",
"resolutions",
]
def to_representation(self, instance):
request = self.context.get("request")
@ -138,6 +146,6 @@ class CategorySerializer(serializers.ModelSerializer):
def to_representation(self, instance):
ret = super().to_representation(instance)
ret['media'] = [m for m in ret['media'] if m is not None]
ret['scenes'] = [s for s in ret['scenes'] if s is not None]
return ret
ret["media"] = [m for m in ret["media"] if m is not None]
ret["scenes"] = [s for s in ret["scenes"] if s is not None]
return ret

View file

@ -22,6 +22,8 @@ def create_image_resolutions(image: "OriginalImage"):
if isinstance(image, (str, uuid.UUID)):
image = OriginalImage.objects.get(id=image)
# TODO: Exception handling
with Image.open(image.file) as img:
resolutions = settings.QUACKSCAPE_CONTENT_RESOLUTIONS
@ -51,6 +53,9 @@ def create_video_resolutions(video: "OriginalVideo"):
if isinstance(video, (str, uuid.UUID)):
video = OriginalVideo.objects.get(id=video)
# TODO: Testing
# TODO: Exception handling
resolutions = settings.QUACKSCAPE_CONTENT_RESOLUTIONS
ffmpeg_options = settings.FFMPEG_OPTIONS[settings.FFMPEG_DEFAULT_OPTION]

View file

@ -2,13 +2,14 @@ from django.urls import path, include
from rest_framework.routers import DefaultRouter
from .views import SceneAPIViewSet, SceneView, SceneEditView, SceneEmbedView, ElementAPIViewSet, CategoryAPIViewSet
from .views import SceneAPIViewSet, SceneView, SceneEditView, SceneEmbedView, ElementAPIViewSet, CategoryAPIViewSet, MediaAPIViewSet
router = DefaultRouter()
router.register(r'scenes', SceneAPIViewSet, "scene")
router.register(r'scene/(?P<scene>[^/.]+)/elements', ElementAPIViewSet, "element")
router.register(r'categories', CategoryAPIViewSet, "category")
router.register(r'category/(?P<category>[^/.]+)/scenes', SceneAPIViewSet, "category-scene")
router.register(r'category/(?P<category>[^/.]+)/media', MediaAPIViewSet, "category-media")
urlpatterns = [
path('api/', include(router.urls)),

View file

@ -5,11 +5,12 @@ from django.core.exceptions import PermissionDenied
from rest_framework import viewsets
from .models import Scene, Element, Category
from .models import Scene, Element, Category, OriginalMedia
from .serializers import (
SceneSerializer,
ElementPolymorphicSerializer,
CategorySerializer,
OriginalMediaSerializer,
)
@ -63,3 +64,8 @@ class SceneEmbedView(SceneView):
class CategoryAPIViewSet(viewsets.ModelViewSet):
serializer_class = CategorySerializer
queryset = Category.objects.all()
class MediaAPIViewSet(viewsets.GenericViewSet, viewsets.mixins.RetrieveModelMixin):
serializer_class = OriginalMediaSerializer
queryset = OriginalMedia.objects.all()