From 080838f086255ec4b3b5386978b92ea7b7f5b266 Mon Sep 17 00:00:00 2001 From: Bjoern Esswein <692-bessw@users.noreply.gl.kwarc.info> Date: Wed, 17 Jul 2024 18:11:08 +0200 Subject: [PATCH] Attached the WebView scroll UI to the ActiveScroll. --- Assets/Resources/Prefabs/UI/FrameITUI.prefab | 8 +- .../Prefabs/UI/Ingame/HidingCanvas.prefab | 37 ++++- Assets/Scripts/UI/InGame/ScrollDetails.cs | 9 +- Assets/Scripts/UI/InGame/WebViewController.cs | 117 ++++++++++++--- .../scrollView.html | 134 +++++++----------- Packages/bessw.unity.webview | 2 +- 6 files changed, 193 insertions(+), 114 deletions(-) diff --git a/Assets/Resources/Prefabs/UI/FrameITUI.prefab b/Assets/Resources/Prefabs/UI/FrameITUI.prefab index 3b1ab204..d726a687 100644 --- a/Assets/Resources/Prefabs/UI/FrameITUI.prefab +++ b/Assets/Resources/Prefabs/UI/FrameITUI.prefab @@ -406,12 +406,12 @@ PrefabInstance: - target: {fileID: 8553388048532215990, guid: 884ac57de337c364391b247761071fb1, type: 3} propertyPath: m_AnchorMax.y - value: 0 + value: 1 objectReference: {fileID: 0} - target: {fileID: 8553388048532215990, guid: 884ac57de337c364391b247761071fb1, type: 3} propertyPath: m_AnchorMin.y - value: 0 + value: 1 objectReference: {fileID: 0} - target: {fileID: 8553388048532215990, guid: 884ac57de337c364391b247761071fb1, type: 3} @@ -561,7 +561,7 @@ PrefabInstance: - target: {fileID: 6500467619489830996, guid: 292834880e6f0e54186b873acc62d3f2, type: 3} propertyPath: m_AnchoredPosition.y - value: 604800 + value: 605880 objectReference: {fileID: 0} - target: {fileID: 7849991042685492731, guid: 292834880e6f0e54186b873acc62d3f2, type: 3} @@ -611,7 +611,7 @@ PrefabInstance: - target: {fileID: 7989559431199338490, guid: 292834880e6f0e54186b873acc62d3f2, type: 3} propertyPath: m_AnchoredPosition.y - value: -33600 + value: -33660 objectReference: {fileID: 0} - target: {fileID: 8004702056544321748, guid: 292834880e6f0e54186b873acc62d3f2, type: 3} diff --git a/Assets/Resources/Prefabs/UI/Ingame/HidingCanvas.prefab b/Assets/Resources/Prefabs/UI/Ingame/HidingCanvas.prefab index 846b8783..ba7a6d62 100644 --- a/Assets/Resources/Prefabs/UI/Ingame/HidingCanvas.prefab +++ b/Assets/Resources/Prefabs/UI/Ingame/HidingCanvas.prefab @@ -2950,6 +2950,36 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: ae310511ad607a64e891bcb46517277b, type: 3} m_Name: m_EditorClassIdentifier: + OnButtonClick: + m_PersistentCalls: + m_Calls: [] + HintAvailableEvent: + m_PersistentCalls: + m_Calls: [] + OnFactAssignmentUpdated: + m_PersistentCalls: + m_Calls: [] + OnMMTServerComunicationError: + m_PersistentCalls: + m_Calls: [] + OnScrollDynamicInfoError: + m_PersistentCalls: + m_Calls: [] + OnScrollApplicationError: + m_PersistentCalls: + m_Calls: [] + OnCancelErrorState: + m_PersistentCalls: + m_Calls: [] + OnScrollSolved: + m_PersistentCalls: + m_Calls: [] + OnScrollChanged: + m_PersistentCalls: + m_Calls: [] + OnScrollDynamicInfoUpdated: + m_PersistentCalls: + m_Calls: [] DynamicScrollDescriptionsActive: 1 AutomaticHintGenerationActive: 1 --- !u!1 &8004702058016740896 @@ -3475,12 +3505,12 @@ PrefabInstance: - target: {fileID: 2974656142881083530, guid: 1e9d514b702f5784791a4df8d22e1866, type: 3} propertyPath: m_SizeDelta.x - value: 600 + value: 32 objectReference: {fileID: 0} - target: {fileID: 2974656142881083530, guid: 1e9d514b702f5784791a4df8d22e1866, type: 3} propertyPath: m_SizeDelta.y - value: 600 + value: 32 objectReference: {fileID: 0} - target: {fileID: 2974656142881083530, guid: 1e9d514b702f5784791a4df8d22e1866, type: 3} @@ -3573,9 +3603,6 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 534d25977ad35154781c29672ce2bb13, type: 3} m_Name: m_EditorClassIdentifier: - onFactAssignment: - m_PersistentCalls: - m_Calls: [] onHintRequested: m_PersistentCalls: m_Calls: [] diff --git a/Assets/Scripts/UI/InGame/ScrollDetails.cs b/Assets/Scripts/UI/InGame/ScrollDetails.cs index 1302849c..c203d684 100644 --- a/Assets/Scripts/UI/InGame/ScrollDetails.cs +++ b/Assets/Scripts/UI/InGame/ScrollDetails.cs @@ -1,5 +1,6 @@ using REST_JSON_API; using System.Collections.Generic; +using System.Text.RegularExpressions; using TMPro; using UnityEngine; @@ -67,7 +68,13 @@ private void ShowScroll(Scroll newScroll) //Clear all current ScrollFacts originalViewport.GetChild(0).gameObject.DestroyAllChildren(); - originalScroll.GetChild(0).GetComponent<TextMeshProUGUI>().text = newScroll.description; + var description = newScroll.description; + // if description is a html description, extract the alternative text representation + if (newScroll.description.StartsWith("<scroll-description")) + { + description = Regex.Match(newScroll.description, "<scroll-description [^>]* alt=([\"']?)(.*)\\1 [^>]*>").Groups[2].Value; + } + originalScroll.GetChild(0).GetComponent<TextMeshProUGUI>().text = description; //ParameterDisplays.ForEach(gameObj => Destroy(gameObj)); ParameterDisplays = new(); diff --git a/Assets/Scripts/UI/InGame/WebViewController.cs b/Assets/Scripts/UI/InGame/WebViewController.cs index d4c24bb0..a2ca32a3 100644 --- a/Assets/Scripts/UI/InGame/WebViewController.cs +++ b/Assets/Scripts/UI/InGame/WebViewController.cs @@ -4,6 +4,7 @@ using MoreLinq; using Newtonsoft.Json; using Newtonsoft.Json.Linq; +using REST_JSON_API; using System; using System.Collections; using System.Collections.Generic; @@ -14,14 +15,6 @@ public class WebViewController : ScrollView { - - - // TODO: onFactAssignment event shoud hav a dropzone id and a fact id as parameters - public UnityEvent<DomNodeWrapper, string> onFactAssignment; - // TODO: implement onHintRequested event - public UnityEvent<DomNodeWrapper, string> onHintRequested; - // TODO: click events (option for custom buttons for a scroll) - public UnityEvent<DomNodeWrapper> onClick; private WebViewComponent webViewComponent; private DomNodeWrapper[] dropzones; @@ -31,47 +24,129 @@ private void Awake() webViewComponent = GetComponent<WebViewComponent>(); WebViewComponent.serializerSettings.Converters.Add(new FactObjectUIConverter()); webViewComponent.targetUrl = Path.Join(CommunicationEvents.Get_DataPath(), "scrollView.html"); + webViewComponent.OnWebViewComponentReady += OnWebViewComponentReady; // TODO: handle webViewComponent.onDomDocumentUpdated + } + private void OnWebViewComponentReady() + { + webViewComponent.onDomDocumentUpdated += DocumentUpdatedHandler; + SwitchScrollUI.activeScrollData.OnScrollChanged.AddListener(SetScrollContent); + SwitchScrollUI.activeScrollData.OnScrollDynamicInfoUpdated.AddListener(SetScrollContent); + webViewComponent.tab.AddJSBinding("applyScroll", ApplyScrollHandler); + webViewComponent.tab.AddJSBinding("getHint", GetHintHandler); + RegisterBrowserEventhandlers(); + } + private void OnDisable() + { + webViewComponent.onDomDocumentUpdated -= DocumentUpdatedHandler; + SwitchScrollUI.activeScrollData.OnScrollChanged.RemoveListener(SetScrollContent); + SwitchScrollUI.activeScrollData.OnScrollDynamicInfoUpdated.RemoveListener(SetScrollContent); + webViewComponent.tab.RemoveJSBinding("applyScroll"); + webViewComponent.tab.RemoveJSBinding("getHint"); + DeRegisterBrowserEventhandlers(); + } - // TODO: remove this debug code - onFactAssignment.AddListener((DomNodeWrapper node, string factId) => { - Debug.LogWarning($"onFactAssignment: '{string.Join(", ", GetFactAssignments())}'"); - }); + private void DocumentUpdatedHandler(documentUpdatedEvent _) + { + // all old DomNodeWrapper objects are invalid, because the whole document has been updated + dropzones = null; + RegisterBrowserEventhandlers(); + } + + private async void RegisterBrowserEventhandlers() + { + // register js event handlers in the browser + _ = webViewComponent.tab.Evaluate("dropZones = document.querySelectorAll('[dropzone=\"copy\"]'); dropZones.forEach( dropZone => dropZone.addEventListener(\"drop\", ev => dropHandler(ev, \"DropEvent\")) )"); + + // 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.GetDocument(); + dropzones = await document.querySelectorAllAsync("[dropzone='copy']"); + dropzones.ForEach(dropzone => dropzone.OnAttributeModified += DropzoneAttributeModifiedHandler); + } + + private void DeRegisterBrowserEventhandlers() + { + dropzones?.ForEach(dropzone => dropzone.OnAttributeModified -= DropzoneAttributeModifiedHandler); } /// <summary> /// sets or updates the content of the scroll container dom element /// </summary> /// <param name="scrollHTML"></param> - public async void SetScrollContent(string scrollHTML) + private async void SetScrollContent(Scroll scroll) { // update scroll container content - DomNodeWrapper scrollContainer = await DomNodeWrapper.requestNodeAsync(webViewComponent.tab, "scrollContainer"); - await scrollContainer.setNodeValueAsync(scrollHTML); + DomNodeWrapper document = await webViewComponent.tab.GetDocument(); + DomNodeWrapper scrollContainer = await document.querySelectorAsync("#scrollContainer"); - // get the dropzones from the scroll and add the event handler to track when something is dropped - dropzones = await scrollContainer.querySelectorAllAsync("[dropzone='copy']"); - dropzones.ForEach(dropzone => dropzone.OnAttributeModified += DropzoneAttributeModifiedHandler); + var description = scroll.description; + // if scroll is a lagacy plain text scroll, generate html with slots for the required facts + if (!scroll.description.StartsWith("<scroll-description")) + { + // generate slots for a plane text scroll description + var factSlots = SwitchScrollUI.activeScrollData.Assignments + .Where(pair => pair.Value.IsVisible) + .Select(pair => + @$"<span class='lagacySlot' 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='lagacySlots'>{String.Join("", factSlots)}</div></scroll-description>"; + } + // display the scroll description + await scrollContainer.setOuterHtmlAsync($"<div id='scrollContainer'>{description}</div>"); + + RegisterBrowserEventhandlers(); } - private void DropzoneAttributeModifiedHandler(attributeModifiedEvent attributeModifiedEvent) + private async void DropzoneAttributeModifiedHandler(attributeModifiedEvent attributeModifiedEvent) { Debug.LogWarning($"dropzoneAtrributeModifiedHandler: '{attributeModifiedEvent.name}'"); // call the onFactAssignment event if the data-fact-id attribute was modified if (attributeModifiedEvent.name == "data-fact-id") { - var node = webViewComponent.tab.domNodes.GetValueOrDefault(attributeModifiedEvent.nodeId); - onFactAssignment.Invoke(node, attributeModifiedEvent.value); + // get the slot id + var node = await webViewComponent.tab.GetNode(attributeModifiedEvent.nodeId); + if (! (node.Node.attributes.TryGetValue("data-slot-id", out string slot) + || (await node.getAttributesAsync()).TryGetValue("data-slot-id", out slot))) + { + Debug.LogError($"dropzoneAtrributeModifiedHandler: data-slot-id attribute not found on dropzone"); + throw new Exception("data-slot-id attribute not found on dropzone"); + } + + // get the fact from the fact id + if (!FactRecorder.AllFacts.TryGetValue(attributeModifiedEvent.value, out Fact fact)) + { + Debug.LogError($"dropzoneAtrributeModifiedHandler: fact with id '{attributeModifiedEvent.value}' not found"); + throw new Exception($"fact with id '{attributeModifiedEvent.value}' not found"); + } + + // assign fact to slot + SwitchScrollUI.activeScrollData.AssignFact(slot, fact); } } + private void ApplyScrollHandler(string button) + { + SwitchScrollUI.activeScrollData.ButtonClicked(new MagicScrollButton()); + } + + private void GetHintHandler(string url) + { + SwitchScrollUI.activeScrollData.ButtonClicked(new HintScrollButton(url)); + } + public string[] GetFactAssignments() { return dropzones.Select(dropzone => dropzone.Node.attributes.GetValueOrDefault("data-fact-id", null)).ToArray(); } + [Obsolete()] private async void GetDropzoneStateAsync() { // alternative way to get the dropzone state diff --git a/Assets/StreamingAssets/StreamToDataPath_withHandler/scrollView.html b/Assets/StreamingAssets/StreamToDataPath_withHandler/scrollView.html index af335f71..9c1302c9 100644 --- a/Assets/StreamingAssets/StreamToDataPath_withHandler/scrollView.html +++ b/Assets/StreamingAssets/StreamToDataPath_withHandler/scrollView.html @@ -1,5 +1,4 @@ <!DOCTYPE html> -<html lang="en"> <head> <meta charset="utf-8"> <title>Scroll View</title> @@ -9,7 +8,13 @@ font-size: 100px; } [dropzone] { - background-color: yellow; + background-color: grey; + } + .lagacySlot { + width: 100px; + height: 100px; + margin: 50px; + border-width: 1px; } #rectangle { width: 10px; @@ -24,86 +29,51 @@ </style> </head> <body> - <math> - <mi>E</mi> - <mo>=</mo> - <mfenced> - <mtable> - <mtr> - <mtd> - <mi dropzone="copy" data-allowed-types="PointFact,AngleFact" data-slot-id="http://mathhub.info/FrameIT/frameworld?Example?A">a</mi> - </mtd> - <mtd> - <mi dropzone="copy" data-slot-id="http://mathhub.info/FrameIT/frameworld?Example?B">b</mi> - </mtd> - <mtd> - <mn>0</mn> - </mtd> - </mtr> - <mtr> - <mtd> - <mn>0</mn> - </mtd> - <mtd> - <mn>1</mn> - </mtd> - <mtd> - <mn>0</mn> - </mtd> - </mtr> - <mtr> - <mtd> - <mn>0</mn> - </mtd> - <mtd> - <mn>0</mn> - </mtd> - <mtd> - <mn>1</mn> - </mtd> - </mtr> - </mtable> - </math> - <div class="demoFact">{ - "s_type": "PointFact", - "label": "A", - "_CustomLabel": null, - "hasCustomLabel": false, - "labelId": 0, - "point": {"x": -1.66086578, "y": -0.00494432449, "z": -2.17682648}, - "normal": {"x": 0.1, "y": 1.2, "z": 0.3} - }</div> - <div class="demoFact">{ - "pid1": "http://mathhub.info/FrameIT/frameworld?DefaultSituationSpace/SituationTheory1?fact252", - "pid2": "http://mathhub.info/FrameIT/frameworld?DefaultSituationSpace/SituationTheory1?fact254", - "pid3": "http://mathhub.info/FrameIT/frameworld?DefaultSituationSpace/SituationTheory1?fact256", - "s_type": "AngleFact", - "label": "∠BDF", - "_CustomLabel": null, - "is_right_angle": false, - "hasCustomLabel": false, - "labelId": 0 - }</div> - <div class="demoFact">{ - "s_type": "LineFact", - "pid1": "http://mathhub.info/FrameIT/frameworld?DefaultSituationSpace/SituationTheory1?fact255", - "pid2": "http://mathhub.info/FrameIT/frameworld?DefaultSituationSpace/SituationTheory1?fact256", - "dir": {"x": 0.1, "y": 1.2, "z": 0.3}, - "label": "[EF]", - "_CustomLabel": null, - "hasCustomLabel": false, - "labelId": 0 - }</div> - <div class="demoFact">{ - "s_type": "RayFact", - "pid1": "http://mathhub.info/FrameIT/frameworld?DefaultSituationSpace/SituationTheory1?fact256", - "pid2": "http://mathhub.info/FrameIT/frameworld?DefaultSituationSpace/SituationTheory1?fact252", - "dir": {"x": 0.1, "y": 1.2, "z": 0.3}, - "label": "FB", - "_CustomLabel": null, - "hasCustomLabel": false, - "labelId": 0 - }</div> + <div id="scrollContainer"> + <div>Default Scroll</div> + <math> + <mi>E</mi> + <mo>=</mo> + <mfenced> + <mtable> + <mtr> + <mtd> + <mi dropzone="copy" data-allowed-types="PointFact,AngleFact" data-slot-id="http://mathhub.info/FrameIT/frameworld?Example?A">a</mi> + </mtd> + <mtd> + <mi dropzone="copy" data-slot-id="http://mathhub.info/FrameIT/frameworld?Example?B">b</mi> + </mtd> + <mtd> + <mn>0</mn> + </mtd> + </mtr> + <mtr> + <mtd> + <mn>0</mn> + </mtd> + <mtd> + <mn>1</mn> + </mtd> + <mtd> + <mn>0</mn> + </mtd> + </mtr> + <mtr> + <mtd> + <mn>0</mn> + </mtd> + <mtd> + <mn>0</mn> + </mtd> + <mtd> + <mn>1</mn> + </mtd> + </mtr> + </mtable> + </math> + </div> + <button type="button" title="Apply the scroll" onclick="applyScroll()">Magic</button><br> + <button type="button" title="Hint" onclick="getHint('http://mathhub.info/FrameIT/frameworld?TriangleProblem_RightAngleAtC?rightAngleC')">Hint</button> </body> <script> /** diff --git a/Packages/bessw.unity.webview b/Packages/bessw.unity.webview index c49f768b..32bbc9be 160000 --- a/Packages/bessw.unity.webview +++ b/Packages/bessw.unity.webview @@ -1 +1 @@ -Subproject commit c49f768b41a3caf0541fcdbfa0d65b74c7e63a4f +Subproject commit 32bbc9be2e099918d10d0e54e8993cae6e54799a -- GitLab