Skip to content
Snippets Groups Projects
Commit 86a3da61 authored by baletiballo's avatar baletiballo
Browse files

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.
parent f11a11a4
No related branches found
No related tags found
No related merge requests found
......@@ -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)
......
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
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
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment