diff --git a/src/pages/faq.tsx b/src/pages/faq.tsx index 109b74319cf7f56b59b1720892edc00c40c807c4..ece8bc78a9d3904dee4b563651992a9bbecc3886 100644 --- a/src/pages/faq.tsx +++ b/src/pages/faq.tsx @@ -211,13 +211,12 @@ export default function FAQ() { > <p> Auf vielfachen Wunsch unserer Dozenten haben wir die Möglichkeit umgesetzt, - Videos nur für Teilnehmer eines bestimmten Moodle-Lernraums - zugänglich zu machen. Um dies umzusetzen benötigen wir die Liste der - Lernräume eines Nutzers, und damit Zugriff auf den Moodle-Account. - Leider erlaubt uns das Moodle nur entweder keinen oder vollen - (also lesenden und schreibenden) Zugriff, sodass du uns zur - Authentifizierung für Lernraum-interne Videos mehr Zugriffsrechte geben - musst, als theoretisch nötig. + Videos nur für Teilnehmer eines bestimmten Moodle-Lernraums zugänglich zu + machen. Um dies umzusetzen benötigen wir die Liste der Lernräume eines + Nutzers, und damit Zugriff auf den Moodle-Account. Leider erlaubt uns das + Moodle nur entweder keinen oder vollen (also lesenden und schreibenden) + Zugriff, sodass du uns zur Authentifizierung für Lernraum-interne Videos + mehr Zugriffsrechte geben musst, als theoretisch nötig. </p> <p> Um die Missbrauchsgefahr zu minimieren, widerrufen wir die Zugriffsrechte @@ -236,32 +235,46 @@ export default function FAQ() { </FaqEntry> <FaqEntry id="cookies" title="Welche Cookies werden gesetzt und wofür?"> <p> - Hier ist zu unterscheiden zwischen Cookies und Local Storage (Lokaler Speicher). - Cookies werden vom Server gesetzt und vom Browser gespeichert und mit jeder Webanfrage wieder an - den Server geschickt. Local Storage wird auch im Browser gespeichert aber nur vom Java Script im - Browser abgerufen und nicht an den Server geschickt. + Hier ist zu unterscheiden zwischen Cookies und Local Storage (Lokaler + Speicher). Cookies werden vom Server gesetzt und vom Browser gespeichert und + mit jeder Webanfrage wieder an den Server geschickt. Local Storage wird auch + im Browser gespeichert aber nur vom Java Script im Browser abgerufen und + nicht an den Server geschickt. </p> <p> - Aktuell verwenden wir den Local Storage um folgendes zu speichern (Technisch Notwendig) + Aktuell verwenden wir den Local Storage um folgendes zu speichern (Technisch + Notwendig) <ul> - <li><code>language</code>: Welche Sprache du gewählt hast (Deutsch/Englisch)</li> - <li><code>theme</code>: Welches Design du gewählt hast (Dark/Light)</li> - <li><code>hidden_announcements</code>: Welches Ankündungen du bereits gesehen hast und mit dem x weggeklickt hast</li> + <li> + <code>language</code>: Welche Sprache du gewählt hast + (Deutsch/Englisch) + </li> + <li> + <code>theme</code>: Welches Design du gewählt hast (Dark/Light) + </li> + <li> + <code>hidden_announcements</code>: Welches Ankündungen du bereits + gesehen hast und mit dem x weggeklickt hast + </li> </ul> </p> <p> - Cookies werden beim reinen Betrachten der Seite setzen überhaupt keine gesetzt. Einzig - bei der Authentifizierung für RWTH- oder Lernraum-interne Videos werden Cookies benötigt. Dafür - wird der <code>session</code>-Cookie gesetzt welcher OAuth-Tokens und Zugriffsrechte speichert. - Das Cookie ist kryptographisch gesichert und enthält nur die zum jeweiligen Zeitpunkt nötigen Daten. + Cookies werden beim reinen Betrachten der Seite setzen überhaupt keine + gesetzt. Einzig bei der Authentifizierung für RWTH- oder Lernraum-interne + Videos werden Cookies benötigt. Dafür wird der <code>session</code>-Cookie + gesetzt welcher OAuth-Tokens und Zugriffsrechte speichert. Das Cookie ist + kryptographisch gesichert und enthält nur die zum jeweiligen Zeitpunkt + nötigen Daten. </p> <p> - Für unsere Statistiken verwenden wir keine Cookies oder Local Storage! Wenn du ein Video guckst - wird eine komplett zufällige <code>watch_id</code> generiert die notwendig ist um Anfragen des - selben Videoabrufs zusammenfassen zu können. Sobald du aber die Seite neulädst oder ein anderes - Video anguckst wird eine neue <code>watch_id</code> generiert welche nicht zur vorherigen ID - oder Dir zuzuordnen ist. Zusätzlich werden nach einigen Stunden, wenn die Views aggregiert sind, - alle <code>watch_id</code>s auf unseren Servern gelöscht. + Für unsere Statistiken verwenden wir keine Cookies oder Local Storage! Wenn + du ein Video guckst wird eine komplett zufällige <code>watch_id</code>{" "} + generiert die notwendig ist um Anfragen des selben Videoabrufs + zusammenfassen zu können. Sobald du aber die Seite neulädst oder ein anderes + Video anguckst wird eine neue <code>watch_id</code> generiert welche nicht + zur vorherigen ID oder Dir zuzuordnen ist. Zusätzlich werden nach einigen + Stunden, wenn die Views aggregiert sind, alle <code>watch_id</code>s auf + unseren Servern gelöscht. </p> <p> In keinem Fall erstellen wir auf Basis der gespeicherten Daten diff --git a/src/pages/index.tsx b/src/pages/index.tsx index d788f8f0bd0e12458499ed4b084017f0c7bee6ff..e9d4fc4a1cea4780514c329c5e8ae49ccf7c8635 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -11,7 +11,10 @@ import { LectureLiveLabel } from "@/videoag/course/LiveLabel"; import { ErrorComponent, showError, showWarningToast } from "@/videoag/error/ErrorDisplay"; import { FallbackErrorBoundary } from "@/videoag/error/FallbackErrorBoundary"; import { useLanguage } from "@/videoag/localization/LanguageProvider"; -import { datetimeToStringOnlyDate, datetimeToStringOnlyTime } from "@/videoag/miscellaneous/Formatting"; +import { + datetimeToStringOnlyDate, + datetimeToStringOnlyTime, +} from "@/videoag/miscellaneous/Formatting"; import { ReloadBoundary, useReloadBoundary } from "@/videoag/miscellaneous/ReloadBoundary"; import { ResourceType, diff --git a/src/videoag/course/Player.tsx b/src/videoag/course/Player.tsx index f82a2bde216151d20d632baee9215d98b431b796..6d20efaf341046d532ac951e64aa942de98cede1 100644 --- a/src/videoag/course/Player.tsx +++ b/src/videoag/course/Player.tsx @@ -109,11 +109,11 @@ function initPlayer( (player as any).markers.reset(markers); } -function generateWatchId() { - const chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" +function generateWatchId(): string { + const chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; let res = ""; for (let i = 0; i < 64; i++) { - res += chars.charAt(Math.floor(Math.random() * chars.length)) + res += chars.charAt(Math.floor(Math.random() * chars.length)); } return res; } @@ -130,7 +130,7 @@ function VideoPlayer({ const api = useApi(); const videoRef = useRef<HTMLVideoElement>(null); const statTracker = useRef<StatTracker | undefined>(undefined); - const watchIdRef = useRef<string>(generateWatchId); + const watchIdRef = useRef<string>(generateWatchId()); useEffect(() => { return () => { diff --git a/src/videoag/course/Stats.tsx b/src/videoag/course/Stats.tsx index c51a5fa12fda9d227bc0219e814b80bee615dc87..4710f3e6b1929f2001079c3b500a49eb33f427fc 100644 --- a/src/videoag/course/Stats.tsx +++ b/src/videoag/course/Stats.tsx @@ -336,11 +336,20 @@ export class StatTracker { const currentTime = this.getExpectedCurrentTime()!; // Not null as we are playing - if ((currentTime - this.currentSegment.getStartTime()) / this.currentSegment.getPlaybackRate() >= MAX_SEGMENT_REALTIME_DURATION_SEC) { + if ( + (currentTime - this.currentSegment.getStartTime()) / + this.currentSegment.getPlaybackRate() >= + MAX_SEGMENT_REALTIME_DURATION_SEC + ) { this.logDebug("Starting new segment after current one is getting too long"); - const cutTime = this.currentSegment.getStartTime() + MAX_SEGMENT_REALTIME_DURATION_SEC * this.currentSegment.getPlaybackRate(); + const cutTime = + this.currentSegment.getStartTime() + + MAX_SEGMENT_REALTIME_DURATION_SEC * this.currentSegment.getPlaybackRate(); let newStartTime = cutTime; - if ((currentTime - newStartTime) / this.currentSegment.getPlaybackRate() >= MAX_SEGMENT_REALTIME_DURATION_SEC) { + if ( + (currentTime - newStartTime) / this.currentSegment.getPlaybackRate() >= + MAX_SEGMENT_REALTIME_DURATION_SEC + ) { this.logDebug( `Periodic check is falling behind. Dropping segments for ${newStartTime} - ${currentTime} (${currentTime - newStartTime} seconds)`, ); @@ -379,7 +388,9 @@ export class StatTracker { MIN_SEGMENT_DURATION_SEC ) { const segment = { - timestamp: formatApiDatetime(DateTime.fromMillis(this.currentSegment.getPauseRealtimeMs()!)), + timestamp: formatApiDatetime( + DateTime.fromMillis(this.currentSegment.getPauseRealtimeMs()!), + ), watch_start_time_sec: Math.round(this.currentSegment.getStartTime()), // Use difference of rounded times, so that continuous segments stay continuous watch_duration_sec: diff --git a/src/videoag/form/TypeEditor.tsx b/src/videoag/form/TypeEditor.tsx index ed4e9139868fd59c814d9d683bb2f28c6809d1e4..0f9b9aadce6b89c453c69fd211d267b376003d2b 100644 --- a/src/videoag/form/TypeEditor.tsx +++ b/src/videoag/form/TypeEditor.tsx @@ -1,4 +1,5 @@ import React, { useEffect, useRef, useState } from "react"; +import { DateTime } from "luxon"; import type { int } from "@/videoag/api/types"; import { useLanguage } from "@/videoag/localization/LanguageProvider"; @@ -232,11 +233,13 @@ export function DatetimeEditor({ }, [knownValue, value, setForceInputUpdate]); const isValidCheck = (val: string | null | undefined) => { + if (val === null || val === undefined) { + return mayBeNull === true; + } let parsedVal = null; try { parsedVal = parseApiDatetime(val); - } catch {} - if (val === null || val === undefined || parsedVal === null) { + } catch { return mayBeNull === true; } return true; @@ -250,7 +253,7 @@ export function DatetimeEditor({ isValid = false; } } else { - newValue = formatApiDatetime(new Date(newValue)); + newValue = formatApiDatetime(DateTime.fromJSDate(new Date(newValue))); } knownValue.current = newValue; updateValue(newValue, isValid, false); diff --git a/src/videoag/miscellaneous/Formatting.tsx b/src/videoag/miscellaneous/Formatting.tsx index c877dc220b18e8cb1ac70709271ae187a085a3ae..c27d8bf7cb809638649646bf4ed4025116ab2e89 100644 --- a/src/videoag/miscellaneous/Formatting.tsx +++ b/src/videoag/miscellaneous/Formatting.tsx @@ -1,3 +1,5 @@ +import { DateTime } from "luxon"; + import { zeropad, parseApiDatetime } from "./Util"; export function semesterToHuman(semester?: string, long_str?: boolean): string { @@ -30,17 +32,22 @@ export function datetimeToStringOnlyDate(datetime: DateTime | string): string { if (typeof datetime === "string") { datetime = parseApiDatetime(datetime); } - return datetime.toLocaleString({ weekday: "short", year: "numeric", month: "2-digit", day: "2-digit" }); + return datetime.toLocaleString({ + weekday: "short", + year: "numeric", + month: "2-digit", + day: "2-digit", + }); } -export function datetimeToStringOnlyTime(datetime: DateTime): string { +export function datetimeToStringOnlyTime(datetime: DateTime | string): string { if (typeof datetime === "string") { datetime = parseApiDatetime(datetime); } return datetime.toLocaleString({ hour: "2-digit", minute: "2-digit" }); } -export function datetimeToString(datetime: DateTime): string { +export function datetimeToString(datetime: DateTime | string): string { return `${datetimeToStringOnlyDate(datetime)}, ${datetimeToStringOnlyTime(datetime)}`; } diff --git a/src/videoag/miscellaneous/Util.tsx b/src/videoag/miscellaneous/Util.tsx index 8aad538a2491f352e571f7f7059567cd4a108477..bfb869ed49138bedad6112edeac4271762e62456 100644 --- a/src/videoag/miscellaneous/Util.tsx +++ b/src/videoag/miscellaneous/Util.tsx @@ -11,7 +11,12 @@ export function zeropad(num: number, places: number) { } export function formatApiDatetime(datetime: DateTime): string { - return datetime.toUTC().toISO({ format: "extended", suppressSeconds: false, suppressMilliseconds: false, includeOffset: true }); + return datetime.toUTC().toISO({ + format: "extended", + suppressSeconds: false, + suppressMilliseconds: false, + includeOffset: true, + })!; } export function parseApiDatetime(datetimeString: datetime): DateTime {