From 86a3da614929d59db1680d5640a669a3011cad0a Mon Sep 17 00:00:00 2001
From: baletiballo <75846481+baletiballo@users.noreply.github.com>
Date: Thu, 23 Jan 2025 17:42:21 +0100
Subject: [PATCH] Have JS extract all data from the rendered scroll

This made a lot of C# Code obsolete. Moved it into individual obsolete functions and created a region for them.
---
 Assets/Scripts/UI/InGame/WebViewController.cs | 211 +++++++++---------
 .../scroll_interaction/.SetScrollContent.js   |  33 ---
 .../scroll_interaction/SetScrollContent.js    |  45 ++--
 3 files changed, 136 insertions(+), 153 deletions(-)
 delete mode 100644 Assets/StreamingAssets/ScrollView_Server/scroll_interaction/.SetScrollContent.js

diff --git a/Assets/Scripts/UI/InGame/WebViewController.cs b/Assets/Scripts/UI/InGame/WebViewController.cs
index 103d3606..5b5a7b12 100644
--- a/Assets/Scripts/UI/InGame/WebViewController.cs
+++ b/Assets/Scripts/UI/InGame/WebViewController.cs
@@ -12,7 +12,6 @@
 using System.Linq;
 using System.Text.RegularExpressions;
 using UnityEngine;
-using UnityEngine.UIElements;
 
 public class WebViewController : ScrollView
 {
@@ -64,7 +63,7 @@ private async void RegisterBrowserEventhandlers()
     {
         // register js event handlers in the browser
         _ = webViewComponent.tab.Evaluate("addDropZoneEventListeners()");
-        
+
         // register c# event handlers
         // get the dropzones from the scroll and add the event handler to track when something is dropped
         DomNodeWrapper document = await webViewComponent.tab.Document;
@@ -79,94 +78,48 @@ private void DeRegisterBrowserEventhandlers()
 
     /// <summary>
     /// <para>
-    /// Hand the ScrollView the labels (and potentially assignments) of all facts, so it can render the scroll content. (And then tell it to do so)
-    /// </para><para>
-    /// More precisely: Put a stringified and Json serialized Dictionary 
-    /// <see cref="OMS.uri"/> slotID  -> (<see cref="string"/> label, <see cref="Fact.Id"/> factId) <br/>
-    /// into the "data-assignments" attribute of the "assignments" node
+    /// Hand the ScrollView the (partially applied) scroll, and request it to rerender the description. 
     /// </para>
     /// </summary>
-    /// <param name="scroll"></param>
-    private async void SetScrollContent(Scroll scroll)
+    /// <param name="scroll">The (partially applied) scroll to be rendered</param>
+    private void SetScrollContent(Scroll scroll)
     {
-        // update scroll container content
-        DomNodeWrapper document = await webViewComponent.tab.Document;
-        DomNodeWrapper scrollContainer = await document.querySelectorAsync("#scrollContainer"); 
-
-        var description = scroll.description;
-        if (!Regex.IsMatch(description, ".*<scroll-description.*"))
+        // If the description is plaintext, create a generic MathML version
+        if (!Regex.IsMatch(scroll.description, ".*<scroll-description.*"))
         {
-            // scroll is a legacy plain text scroll, generate html with slots for the required facts
-            var factSlots = SwitchScrollUI.activeScrollData.Assignments
-                .Where(pair => pair.Value.IsVisible)
-                .Select(pair =>
-                    @$"<span class='legacySlot' dropzone='copy' data-slot-id='{pair.Key}' {(pair.Value.IsSet ? $"data-fact-id='{pair.Value.fact.Id}'" : "")}>
-                        {(pair.Value.IsSet ?
-                            pair.Value.fact.GetLabel(StageStatic.stage.factState) :
-                            scroll.requiredFacts.Find(fact => fact.@ref.uri == pair.Key).label)}
-                    </span>");
-
-            description = $"<scroll-description><p>{scroll.description}</p><div id='legacySlots'>{String.Join("", factSlots)}</div></scroll-description>";
-            
+            scroll.description = CreateGenericScrollDescription(scroll);
         }
-        //else // Is done by JS. How to render the scroll is the scrolls buisness
-        //{
-        //    // replace slot templates
-        //    description = Regex.Replace(description, "<scroll-slot([^>]*)>([^<]*)</scroll-slot>", match =>
-        //    {
-        //        var extraAttributes = match.Groups[1].Value;
-        //        var slotId = match.Groups[2].Value;
-        //        var assignment = SwitchScrollUI.activeScrollData.Assignments[slotId];
-        //        /** label of the assigned fact, or the slot label if nothing assigned */
-        //        var label = assignment.IsSet    ? assignment.fact.GetLabel(StageStatic.stage.factState) 
-        //                                        : scroll.requiredFacts.Find(fact => fact.@ref.uri == slotId).label;
-        //        /** id of the assigned fact. If nothing is assigned don't add the attribute */
-        //        var fact_id = assignment.IsSet  ? $"data-fact-id='{assignment.fact.Id}'" 
-        //                                        : "";
-        //        return $"<mi dropzone='copy' data-slot-id='{slotId}' {fact_id} {extraAttributes}>{label}</mi>";
-        //    });
-        //    // replace solution templates
-        //    description = Regex.Replace(description, "<scroll-solution([^>]*)>([^<]*)</scroll-solution>", match =>
-        //    {
-        //        var extraAttributes = match.Groups[1].Value;
-        //        var solutionId = match.Groups[2].Value;
-        //        var label = scroll.acquiredFacts.Find(fact => fact.@ref.uri == solutionId).label;
-        //        return $"<mi data-solution-id='{solutionId}' {extraAttributes}>{label}</mi>";
-        //    });
-        //}
-
-        // Display the scroll description.
-        await scrollContainer.setOuterHtmlAsync($"<div id='scrollContainer'>{description}</div>");
 
-
-        DomNodeWrapper assignmentsNode = await document.querySelectorAsync("#assignments");
-        Dictionary<string, (string, string)> assignments = new();
-
-        foreach (MMTFact fact in scroll.requiredFacts)
-        {
-            string slotId = fact.@ref.uri;
-            ActiveScroll.SlotAssignment assignment = SwitchScrollUI.activeScrollData.Assignments[slotId];
-            if (assignment.IsSet)
-            {
-                assignments.Add(slotId, (assignment.fact.GetLabel(StageStatic.stage.factState), assignment.fact.Id));
-            }
-            else
-            {
-                assignments.Add(slotId, (fact.label, ""));
-            }
-        }
-        foreach (MMTFact fact in scroll.acquiredFacts)
-        {
-            assignments.Add(fact.@ref.uri, (fact.label, ""));
-        }
-        await assignmentsNode.setAttributeValue("data-assignments", JsonConvert.SerializeObject(assignments));
+        ForwardDataToScrollView("scrollDynamic", scroll);
         _ = webViewComponent.tab.Evaluate("RenderScroll()");
+        Debug.Log("Called for scrollView to rerender");
 
         // Update the interactive components
         dropzones = null;
         RegisterBrowserEventhandlers();
     }
 
+    /// <summary>
+    /// Create a MathML description for a scroll without one.
+    /// Obviously cannot be very fancy. 
+    /// </summary>
+    /// <param name="scroll">The rendered scroll to create a description for</param>
+    /// <returns>A fitting scroll-description element as a string</returns>
+    private string CreateGenericScrollDescription(Scroll scroll)
+    {
+        // scroll is a legacy plain text scroll, generate html with slots for the required facts
+        var factSlots = SwitchScrollUI.activeScrollData.Assignments
+            .Where(pair => pair.Value.IsVisible)
+            .Select(pair =>
+                @$"<span class='legacySlot' dropzone='copy' data-slot-id='{pair.Key}' {(pair.Value.IsSet ? $"data-fact-id='{pair.Value.fact.Id}'" : "")}>
+                        {(pair.Value.IsSet ?
+                        pair.Value.fact.GetLabel(StageStatic.stage.factState) :
+                        scroll.requiredFacts.Find(fact => fact.@ref.uri == pair.Key).label)}
+                    </span>");
+
+        return $"<scroll-description><p>{scroll.description}</p><div id='legacySlots'>{String.Join("", factSlots)}</div></scroll-description>";
+    }
+
     private async void DropzoneAttributeModifiedHandler(attributeModifiedEvent attributeModifiedEvent)
     {
         //Debug.Log($"dropzoneAttributeModifiedHandler: '{attributeModifiedEvent.name}'");
@@ -176,7 +129,7 @@ private async void DropzoneAttributeModifiedHandler(attributeModifiedEvent attri
         {
             // get the slot id
             var node = await webViewComponent.tab.GetNode(attributeModifiedEvent.nodeId);
-            if (! (node.Node.attributes.TryGetValue("data-slot-id", out string slot)
+            if (!(node.Node.attributes.TryGetValue("data-slot-id", out string slot)
                 || (await node.getAttributesAsync()).TryGetValue("data-slot-id", out slot)))
             {
                 Debug.LogError($"dropzoneAttributeModifiedHandler: data-slot-id attribute not found on dropzone");
@@ -229,6 +182,33 @@ private void GetHintHandler(string url)
         SwitchScrollUI.activeScrollData.ButtonClicked(new HintScrollButton(url));
     }
 
+    /// <summary>
+    /// Send data to the ScrollView.
+    /// <para>JSON serialize the <paramref name="dataValue"/> and add it to the dataset of the Unity-Data-Interface Node. JS can then retrieve it by calling
+    /// <code>const unityData = JSON.parse(document.querySelector("#Unity-Data-Interface").dataset.<paramref name="dataID"/>);</code> 
+    /// </para>
+    /// </summary>
+    /// <param name="dataID">The name of the dataset-member <paramref name="dataValue"/> will be assigned to</param>
+    /// <param name="dataValue">The data to transmit</param>
+    public async void ForwardDataToScrollView(string dataID, object dataValue)
+    {
+        string payload = JsonConvert.SerializeObject(dataValue);
+        //payload = WebUtility.HtmlEncode(payload);
+        // Using the DomNodeWrapper methods requires 3 requests, and since those go to an altogether
+        // different prozess, I would like to avoid that.
+        await webViewComponent.tab.Evaluate(
+            $"document.querySelector('#Unity-Data-Interface').dataset.{dataID} = '{payload}'"
+            );
+    }
+
+    public string[] GetFactAssignments()
+    {
+        return dropzones.Select(dropzone => dropzone.Node.attributes.GetValueOrDefault("data-fact-id", null)).ToArray();
+    }
+
+    #region ObsoleteFunctions : Functions that are no longer used. Since similar functionality may be of use sometime later, they are here for reference.
+
+
     /// <summary>
     /// <para>
     /// Hand the ScrollView the labels (and potentially assignments) of all facts, so it can render the scroll.
@@ -239,36 +219,62 @@ private void GetHintHandler(string url)
     /// </para>
     /// </summary>
     /// <param name="youHappyNowJS">Is ignored. JS bindings need to take a string argument, so here it is.</param>
-    [Obsolete("This data can be aquired directly from the MMTServer")]
-    private async void UpdateAssignmentsHandler(string youHappyNowJS = "")
+    [Obsolete("This data needs to be updated iff the assignment is changed in Unity, JS doesn't need a way to to request it independently.")]
+    private void UpdateAssignmentsHandler(string youHappyNowJS = "")
     {
-        DomNodeWrapper document = await webViewComponent.tab.Document;
-        DomNodeWrapper assignmentsNode = await document.querySelectorAsync("#assignments");
         Scroll scroll = SwitchScrollUI.activeScrollData.RenderedScroll;
-        Dictionary<string, (string,string)> assignments = new();
-        foreach (var (slotId,assignment) in SwitchScrollUI.activeScrollData.Assignments)
+        Dictionary<string, (string, string)> assignments = new();
+
+        foreach (MMTFact slot in scroll.requiredFacts)
         {
-            string label = assignment.IsSet ? assignment.fact.GetLabel(StageStatic.stage.factState)
-                                                : scroll.requiredFacts.Find(fact => fact.@ref.uri == slotId).label;
-            /** id of the assigned fact. If nothing is assigned don't add the attribute */
-            string fact_id = assignment.IsSet ? assignment.fact.Id : "";
-            assignments.Add(slotId,(label,fact_id));
+            string slotId = slot.@ref.uri;
+            ActiveScroll.SlotAssignment assignment = SwitchScrollUI.activeScrollData.Assignments[slotId];
+            if (assignment.IsSet)
+            {
+                Fact fact = assignment.fact;
+                assignments.Add(slotId, (fact.GetLabel(StageStatic.stage.factState), fact.Id));
+            }
+            else
+            {
+                assignments.Add(slotId, (slot.label, ""));
+            }
         }
-        foreach(MMTFact fact in scroll.acquiredFacts)
+        foreach (MMTFact fact in scroll.acquiredFacts)
         {
-            string label = scroll.acquiredFacts.Find(f => fact.@ref.uri == f.@ref.uri).label;
-            Debug.Log(label);
-            Debug.Log(fact.label);
             assignments.Add(fact.@ref.uri, (fact.label, ""));
         }
-    
-
-        await assignmentsNode.setAttributeValue("data-assignments", JsonConvert.SerializeObject(assignments));
+        ForwardDataToScrollView("assignments", JsonConvert.SerializeObject(assignments));
     }
 
-    public string[] GetFactAssignments()
+    [Obsolete("Is done by JS. How to render the scroll is the scrolls buisness.")]
+    private string createCustomScrollDescription(Scroll scroll)
     {
-        return dropzones.Select(dropzone => dropzone.Node.attributes.GetValueOrDefault("data-fact-id", null)).ToArray();
+        string description = scroll.description;
+        // replace slot templates
+        //the scroll-slot syntax has changed by now, so the match would have to be adapted.
+        description = Regex.Replace(description, "<scroll-slot([^>]*)>([^<]*)</scroll-slot>", match =>
+        {
+            var extraAttributes = match.Groups[1].Value;
+            var slotId = match.Groups[2].Value;
+            var assignment = SwitchScrollUI.activeScrollData.Assignments[slotId];
+            /** label of the assigned fact, or the slot label if nothing assigned */
+            var label = assignment.IsSet ? assignment.fact.GetLabel(StageStatic.stage.factState)
+                                            : scroll.requiredFacts.Find(fact => fact.@ref.uri == slotId).label;
+            /** id of the assigned fact. If nothing is assigned don't add the attribute */
+            var fact_id = assignment.IsSet ? $"data-fact-id='{assignment.fact.Id}'"
+                                            : "";
+            return $"<mi dropzone='copy' data-slot-id='{slotId}' {fact_id} {extraAttributes}>{label}</mi>";
+        });
+        // replace solution templates
+        description = Regex.Replace(description, "<scroll-solution([^>]*)>([^<]*)</scroll-solution>", match =>
+        {
+            var extraAttributes = match.Groups[1].Value;
+            var solutionId = match.Groups[2].Value;
+            var label = scroll.acquiredFacts.Find(fact => fact.@ref.uri == solutionId).label;
+            return $"<mi data-solution-id='{solutionId}' {extraAttributes}>{label}</mi>";
+        });
+
+        return description;
     }
 
     [Obsolete()]
@@ -303,11 +309,13 @@ private IEnumerator GetDropzoneState()
         Debug.LogWarning($"dropzone pre");
 
         DomNodeWrapper doc = null;
-        yield return DomNodeWrapper.getDocument(webViewComponent.tab, (document) => {
+        yield return DomNodeWrapper.getDocument(webViewComponent.tab, (document) =>
+        {
             Debug.LogWarning($"dropzone 1: '{document}'");
             doc = document;
 
-            StartCoroutine(document.querySelectorAll("[dropzone='copy']", (dropzones) => {
+            StartCoroutine(document.querySelectorAll("[dropzone='copy']", (dropzones) =>
+            {
                 foreach (var dropzone in dropzones)
                 {
                     Debug.LogWarning($"dropzone 2: Node is Null?: '{dropzone.Node == null}'");
@@ -320,8 +328,11 @@ private IEnumerator GetDropzoneState()
         });
         Debug.LogWarning($"dropzone post: '{doc}'");
     }
+    #endregion ObsoleteFunctions
 }
 
+
+
 public class FactObjectUIConverter : JsonConverter<FactObjectUI>
 {
     public override void WriteJson(JsonWriter writer, FactObjectUI value, JsonSerializer serializer)
diff --git a/Assets/StreamingAssets/ScrollView_Server/scroll_interaction/.SetScrollContent.js b/Assets/StreamingAssets/ScrollView_Server/scroll_interaction/.SetScrollContent.js
deleted file mode 100644
index 7fcf8b06..00000000
--- a/Assets/StreamingAssets/ScrollView_Server/scroll_interaction/.SetScrollContent.js
+++ /dev/null
@@ -1,33 +0,0 @@
-let currentScrollRef = "" // ref of the scroll currently displayed 
-
-function RenderScroll() {
-    const scroll = JSON.parse(document.querySelector("#Unity-Data-Interface").dataset.scrollDynamic);
-    console.log(scroll.requiredFacts);
-    const scrollContainer = document.querySelector("#scrollContainer");
-
-    // replace the description if the scroll changed, otherwise only its content needs update
-    if (scroll.ref != currentScrollRef) {
-        currentScrollRef = scroll.ref;
-        scrollContainer.innerHTML = scroll.description;
-    }
-
-    // go through the facts in the scroll, show their labels and add dropzones
-    scroll.requiredFacts.forEach(fact => {
-        $("[data-slot-id='" + fact.ref.uri + "']")
-            .text(fact.label)
-            .attr("dropzone", "copy")
-            //.attr("data-fact-id", fact.df == null ? "" : fact.df.ref.uri)
-    })
-
-    // acquired facts only need updated labels
-    scroll.acquiredFacts.forEach(fact => {
-        $("[data-solution-id='" + fact.ref.uri + "']")
-            .text(fact.label)
-    })
-    console.log(scroll.label + 'Scroll rendered')
-}
-
-
-$(function () {
-    RenderScroll()
-});
\ No newline at end of file
diff --git a/Assets/StreamingAssets/ScrollView_Server/scroll_interaction/SetScrollContent.js b/Assets/StreamingAssets/ScrollView_Server/scroll_interaction/SetScrollContent.js
index b91bd379..7fcf8b06 100644
--- a/Assets/StreamingAssets/ScrollView_Server/scroll_interaction/SetScrollContent.js
+++ b/Assets/StreamingAssets/ScrollView_Server/scroll_interaction/SetScrollContent.js
@@ -1,28 +1,33 @@
+let currentScrollRef = "" // ref of the scroll currently displayed 
 
 function RenderScroll() {
+    const scroll = JSON.parse(document.querySelector("#Unity-Data-Interface").dataset.scrollDynamic);
+    console.log(scroll.requiredFacts);
     const scrollContainer = document.querySelector("#scrollContainer");
-    const description = scrollContainer.textContent;
-    const assignments = JSON.parse(document.querySelector("#assignments").dataset.assignments)
-    console.log(assignments);
 
-    //everything that has a slotID is a scroll slot
-    scrollContainer.querySelectorAll("[data-slot-id]").forEach(element => {
-        //console.log(element);
-        let slotId = element.dataset.slotId;
-        let fact_assignment = assignments[slotId];
+    // replace the description if the scroll changed, otherwise only its content needs update
+    if (scroll.ref != currentScrollRef) {
+        currentScrollRef = scroll.ref;
+        scrollContainer.innerHTML = scroll.description;
+    }
 
-        element.setAttribute("dropzone", "copy");
-        element.textContent = fact_assignment.Item1;
-
-        if (fact_assignment.Item2 != "") {
-            element.dataset.factId = fact_assignment.Item2;
-        }
-    });
+    // go through the facts in the scroll, show their labels and add dropzones
+    scroll.requiredFacts.forEach(fact => {
+        $("[data-slot-id='" + fact.ref.uri + "']")
+            .text(fact.label)
+            .attr("dropzone", "copy")
+            //.attr("data-fact-id", fact.df == null ? "" : fact.df.ref.uri)
+    })
 
-    //everything that has a solutionID is a scroll solution
-    scrollContainer.querySelectorAll("[data-solution-id]").forEach(element => {
-        element.textContent = assignments[element.dataset.solutionId].Item1;
+    // acquired facts only need updated labels
+    scroll.acquiredFacts.forEach(fact => {
+        $("[data-solution-id='" + fact.ref.uri + "']")
+            .text(fact.label)
     })
-    console.log('Scroll rendered')
+    console.log(scroll.label + 'Scroll rendered')
 }
-RenderScroll()
\ No newline at end of file
+
+
+$(function () {
+    RenderScroll()
+});
\ No newline at end of file
-- 
GitLab