From 6c0ba3c3819c5a004ed05630f447997de06a0703 Mon Sep 17 00:00:00 2001 From: Dorian Koch <doriank@fsmpi.rwth-aachen.de> Date: Wed, 11 Sep 2024 14:32:46 +0200 Subject: [PATCH] Fix Player title not updating after navigating to next lecture --- src/components/OMConfigComponent.tsx | 55 ++++++++++++++++------------ src/components/StopNavigation.tsx | 24 ++++++++++++ 2 files changed, 55 insertions(+), 24 deletions(-) create mode 100644 src/components/StopNavigation.tsx diff --git a/src/components/OMConfigComponent.tsx b/src/components/OMConfigComponent.tsx index 45a3e1e..451cc9f 100644 --- a/src/components/OMConfigComponent.tsx +++ b/src/components/OMConfigComponent.tsx @@ -20,6 +20,7 @@ import type { int, } from "@/api/api_v1_types"; import { StylizedText } from "./StylizedText"; +import StopNavigation from "./StopNavigation"; const markdownSupportingFields = [ "announcement.text", @@ -132,29 +133,31 @@ export function EmbeddedOMFieldComponent({ const [isValid, setIsValid] = useState(true); const [isEditing, setIsEditing] = useState(false); const [field, setField] = useState<field_value>(); - //We need an additional references since a state is not updated immediately and sometimes the API call would fail + // We need an additional references since a state is not updated immediately and sometimes the API call would fail // due to an unexpected current value. (Only using a ref does not work, since the rendering needs this to indicate // if changes have been made) - const fieldValueRef = useRef<field_value>(); + const serverSideFieldValueRef = useRef<field_value>(); // This is what we expect the value to be on the server const updatedSinceLastReloadRef = useRef<boolean>(false); - const beforeEditingValue = useRef(initialValue); + const beforeEditingValue = useRef(); const { editMode } = useEditMode(); - //These types can be edited without loading the configuration or starting editing (e.g. isEditing is ignored) - //The description of these types may have no additional fields (only id, type, default_value) + // These types can be edited without loading the configuration or starting editing (e.g. isEditing is ignored) + // The description of these types may have no additional fields (only id, type, default_value) const isInstantEdit = ["boolean"].includes(field_type); useEffect(() => { - if (!isInstantEdit) return; - setField({ - description: { - id: field_id, - type: field_type, - //Default value does not matter - }, - value: initialValue, - }); - fieldValueRef.current = initialValue; + if (isInstantEdit) { + setField({ + description: { + id: field_id, + type: field_type, + //Default value does not matter + }, + value: initialValue, + }); + } + + serverSideFieldValueRef.current = initialValue; setValue(initialValue); // eslint-disable-next-line react-hooks/exhaustive-deps }, [isInstantEdit, field_id, field_type, initialValue]); @@ -162,6 +165,7 @@ export function EmbeddedOMFieldComponent({ useEffect(() => { if (!isEditing) return; + // TODO: disable editing while config is still loading, this is especially annoying with high latency api.getOMConfiguration(object_type, object_id) .then((config) => { const field = config.fields?.find((field) => field.description.id === field_id); @@ -171,7 +175,7 @@ export function EmbeddedOMFieldComponent({ return; } setField(field); - fieldValueRef.current = field.value; + serverSideFieldValueRef.current = field.value; setValue(field.value); }) .catch((error) => { @@ -180,18 +184,18 @@ export function EmbeddedOMFieldComponent({ }); }, [api, isEditing, object_type, object_id, field_id]); - const [deferred, now] = useDebounceUpdateData<any, void>( + const [deferredSendValueUpdate, nowSendValueUpdate] = useDebounceUpdateData<any, void>( async (newValue: any) => { if (field === undefined) return; - if (deepEquals(newValue, fieldValueRef.current)) return; + if (deepEquals(newValue, serverSideFieldValueRef.current)) return; return api .updateOMObject(object_type, object_id, { updates: { [field_id]: newValue }, - expected_current_values: { [field_id]: fieldValueRef.current }, + expected_current_values: { [field_id]: serverSideFieldValueRef.current }, }) .then(() => { - fieldValueRef.current = newValue; + serverSideFieldValueRef.current = newValue; setField(field === undefined ? undefined : { ...field, value: newValue }); if (isInstantEdit) { reloadFunc(); @@ -209,7 +213,7 @@ export function EmbeddedOMFieldComponent({ const disableEditing = () => { if (!isValid) return; if (!isEditing) return; - now(value).then(() => { + nowSendValueUpdate(value).then(() => { setIsEditing(false); if (updatedSinceLastReloadRef.current) { reloadFunc(); @@ -222,7 +226,7 @@ export function EmbeddedOMFieldComponent({ if (!isEditing) return; setValue(beforeEditingValue.current); setIsValid(true); - deferred(beforeEditingValue.current); + deferredSendValueUpdate(beforeEditingValue.current); }; useEffect(() => { @@ -276,9 +280,9 @@ export function EmbeddedOMFieldComponent({ setIsValid(isValid); if (isValid) { if (affectNow) { - now(newValue); + nowSendValueUpdate(newValue); } else { - deferred(newValue); + deferredSendValueUpdate(newValue); } } }, @@ -301,6 +305,9 @@ export function EmbeddedOMFieldComponent({ onBlur={disableEditing} onSubmit={disableEditing} > + {isEditing && hasChanged && ( + <StopNavigation warningText="You have unsaved changes. Are you sure you want to leave?" /> + )} <div className={"vr mx-1 " + (hasChanged ? "opacity-25" : "opacity-0")} style={{ color: "orange", width: "4px", verticalAlign: "baseline" }} diff --git a/src/components/StopNavigation.tsx b/src/components/StopNavigation.tsx new file mode 100644 index 0000000..47c92f0 --- /dev/null +++ b/src/components/StopNavigation.tsx @@ -0,0 +1,24 @@ +import { useEffect } from "react"; +import { useRouter } from "next/router"; + +export default function StopNavigation({ warningText }: { warningText: string }) { + const router = useRouter(); + useEffect(() => { + const handleWindowClose = (e: BeforeUnloadEvent) => { + e.preventDefault(); + return warningText; + }; + const handleBrowseAway = () => { + if (window.confirm(warningText)) return; + router.events.emit("routeChangeError"); + throw "routeChange aborted."; + }; + window.addEventListener("beforeunload", handleWindowClose); + router.events.on("routeChangeStart", handleBrowseAway); + return () => { + window.removeEventListener("beforeunload", handleWindowClose); + router.events.off("routeChangeStart", handleBrowseAway); + }; + }, [router, warningText]); + return null; +} -- GitLab