From b45e9700d4ab2c314dcaf9494bf149f29d04a544 Mon Sep 17 00:00:00 2001 From: Dorian Koch <doriank@fsmpi.rwth-aachen.de> Date: Thu, 4 Jul 2024 19:07:57 +0200 Subject: [PATCH] Show a list of media sources in edit mode, Add file size to media sources, Closes #42 --- src/components/CourseListing.tsx | 25 +++++++----- src/components/DownloadManager.tsx | 18 ++------- src/components/Player.tsx | 65 ++++++++++++++++++++++++++---- src/misc/Formatting.tsx | 15 +++++++ 4 files changed, 92 insertions(+), 31 deletions(-) diff --git a/src/components/CourseListing.tsx b/src/components/CourseListing.tsx index 5a30790..2c8d00a 100644 --- a/src/components/CourseListing.tsx +++ b/src/components/CourseListing.tsx @@ -8,7 +8,7 @@ import { EmbeddedOMFieldComponent, } from "./OMConfigComponent"; import { useEditMode } from "./EditModeProvider"; -import { DownloadSources } from "./Player"; +import { DownloadSources, EditSources } from "./Player"; import Title from "./TitleComponent"; import DownloadAllModal from "./DownloadManager"; @@ -126,10 +126,15 @@ export function LectureListItem({ }) { const api = useBackendContext(); const { editMode } = useEditMode(); + let content = <></>; if (lecture.media_sources && lecture.media_sources.length > 0) { let thumbUrl = lecture.thumbnail_url ?? `${api.assetUrl()}/thumbnail/l_${lecture.id}.jpg`; + const showDownloadSourcesButton = + editMode === false && + lecture.media_sources && + lecture.media_sources.some((ele) => ele.download_url !== undefined); content = ( <> @@ -233,14 +238,16 @@ export function LectureListItem({ {% endif %*/} </ul> <ul className="col-sm-4 col-12 list-unstyled d-flex"> - {lecture.media_sources && - lecture.media_sources.some((ele) => ele.download_url !== undefined) && ( - <DownloadSources - media_sources={lecture.media_sources} - direction="down" - tabIndex="-1" - /> - )} + {showDownloadSourcesButton && ( + <DownloadSources + media_sources={lecture.media_sources} + direction="down" + tabIndex="-1" + /> + )} + {editMode && lecture.media_sources && ( + <EditSources media_sources={lecture.media_sources} /> + )} <li className="flex-fill" /> <li> diff --git a/src/components/DownloadManager.tsx b/src/components/DownloadManager.tsx index 3e215ca..f7489d1 100644 --- a/src/components/DownloadManager.tsx +++ b/src/components/DownloadManager.tsx @@ -1,6 +1,6 @@ import { useEffect, useRef, useState } from "react"; import Modal from "react-bootstrap/Modal"; -import { sourceToName } from "./Player"; +import { sortSources, sourceToName } from "./Player"; import { showWarningToast } from "@/misc/ErrorHandlers"; import type { GetCourseResponse, media_source } from "@/api/api_v1_types"; @@ -311,13 +311,7 @@ export default function DownloadAllModal({ course }: { course: GetCourseResponse chosenQualities.current = new Map<number, string>(); chosenLectures.current = new Map<number, boolean>(); for (let lecture of course.lectures!) { - let sortedsources = Array.from(lecture.media_sources ?? []) - .filter((q) => q.download_url) - .sort((a, b) => { - let aq = parseInt(a.quality.name.split("p")[0]); - let bq = parseInt(b.quality.name.split("p")[0]); - return bq - aq; - }); + let sortedsources = sortSources(lecture.media_sources).filter((q) => q.download_url); chosenLectures.current.set(lecture.id, sortedsources.length > 0); if (sortedsources.length > 0) chosenQualities.current.set(lecture.id, sourceToName(sortedsources[0])); @@ -328,13 +322,7 @@ export default function DownloadAllModal({ course }: { course: GetCourseResponse let lectures = []; for (let lecture of course.lectures!) { - let sortedsources = Array.from(lecture.media_sources ?? []) - .filter((q) => q.download_url) - .sort((a, b) => { - let aq = parseInt(a.quality.name.split("p")[0]); - let bq = parseInt(b.quality.name.split("p")[0]); - return bq - aq; - }); + let sortedsources = sortSources(lecture.media_sources).filter((q) => q.download_url); let selectedQuality: media_source | undefined = undefined; if (chosenQualities.current.get(lecture.id)) { diff --git a/src/components/Player.tsx b/src/components/Player.tsx index 341f532..37f582c 100644 --- a/src/components/Player.tsx +++ b/src/components/Player.tsx @@ -3,7 +3,7 @@ import "video.js/dist/video-js.min.css"; import "@silvermine/videojs-quality-selector/dist/css/quality-selector.css"; import Link from "next/link"; import { useBackendContext } from "./BackendProvider"; -import { timestampToString } from "@/misc/Formatting"; +import { filesizeToHuman, timestampToString } from "@/misc/Formatting"; import OverlayTrigger from "react-bootstrap/OverlayTrigger"; import Popover from "react-bootstrap/Popover"; @@ -518,11 +518,66 @@ function Chapters({ chapters, seekTo }: { chapters?: chapter[]; seekTo: (time: n } export function sourceToName(source: media_source) { - if (source.comment.length == 0) return source.quality.name; + if (source.comment.length == 0) + return `${source.quality.name} (${filesizeToHuman(source.size)})`; return `${source.quality.name} (${source.comment})`; } +export function sortSources(sources?: media_source[]) { + return Array.from(sources ?? []).sort((a, b) => { + let aq = parseInt(a.quality.name.split("p")[0]); + let bq = parseInt(b.quality.name.split("p")[0]); + if (aq === bq) { + return (a.id ?? 0) - (b.id ?? 0); + } + return bq - aq; + }); +} + +export function EditSources({ media_sources }: { media_sources: media_source[] }) { + const { editMode } = useEditMode(); + + let sortedsources = sortSources(media_sources); + + return ( + <ul> + {sortedsources.map((source) => ( + <li + key={source.id} + className={ + "d-flex border align-items-center px-2 py-1 rounded " + + (source.is_visible === false ? "bg-danger-subtle" : "") + } + > + <span className="bi bi-download" /> + <a href={source.download_url ?? source.player_url} className="mx-1"> + {sourceToName(source)} + </a> + {editMode && ( + <> + <div className="flex-fill" /> + <EmbeddedOMFieldComponent + object_type="media_source" + object_id={source.id!} + field_id="is_visible" + field_type="boolean" + initialValue={source.is_visible} + className="me-2 align-self-center py-0" + /> + <OMDelete + object_type="media_source" + object_id={source.id!} + className="py-0" + /> + </> + )} + </li> + ))} + </ul> + ); +} + export function DownloadSources({ media_sources, className, @@ -537,11 +592,7 @@ export function DownloadSources({ const { editMode } = useEditMode(); const { language } = useLanguage(); - let sortedsources = Array.from(media_sources ?? []).sort((a, b) => { - let aq = parseInt(a.quality.name.split("p")[0]); - let bq = parseInt(b.quality.name.split("p")[0]); - return bq - aq; - }); + let sortedsources = sortSources(media_sources); return ( <Dropdown drop={direction}> diff --git a/src/misc/Formatting.tsx b/src/misc/Formatting.tsx index 239b945..0657733 100644 --- a/src/misc/Formatting.tsx +++ b/src/misc/Formatting.tsx @@ -31,3 +31,18 @@ export function datetimeToString(datetime: string) { let date_parsed = new Date(datetime); return `${date_parsed.toLocaleDateString([], { weekday: "short", year: "numeric", month: "2-digit", day: "2-digit" })}, ${date_parsed.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })}`; } +export function filesizeToHuman(size: number) { + if (size < 1024) { + return size + " B"; + } + size /= 1024; + if (size < 1024) { + return size.toFixed(1) + " KiB"; + } + size /= 1024; + if (size < 1024) { + return size.toFixed(1) + " MiB"; + } + size /= 1024; + return size.toFixed(1) + " GiB"; +} -- GitLab