using bessw.Unity.WebView; using bessw.Unity.WebView.ChromeDevTools; using bessw.Unity.WebView.ChromeDevTools.Protocol.DOM; using MoreLinq; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using REST_JSON_API; using System; using System.Collections; using System.Collections.Generic; using System.IO; using System.Linq; using UnityEngine; using UnityEngine.Events; public class WebViewController : ScrollView { private WebViewComponent webViewComponent; private DomNodeWrapper[] dropzones; private void Awake() { Debug.LogWarning("awake Webview Controller"); 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(); } 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> private async void SetScrollContent(Scroll scroll) { // update scroll container content DomNodeWrapper document = await webViewComponent.tab.GetDocument(); DomNodeWrapper scrollContainer = await document.querySelectorAsync("#scrollContainer"); 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 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") { // 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 DomNodeWrapper document = await DomNodeWrapper.getDocumentAsync(webViewComponent.tab); Debug.LogWarning($"dropzone 1: '{document}'"); var dropzones = await document.querySelectorAllAsync("[dropzone='copy']"); Debug.LogWarning($"dropzone 2: '{dropzones.Index().Aggregate("", (currentString, dropzone) => $"{currentString}, {dropzone.Key}: {dropzone.Value.Node.attributes.GetValueOrDefault("data-fact-id")}")}'"); string[] factIDs = new string[dropzones.Length]; // get attributes for each dropzone //for (int i = 0; i < dropzones.Length; i++) //{ // factIDs[i] = ( await dropzones[i].getAttributesAsync() ).GetValueOrDefault("data-fact-id", null); //} //Debug.LogWarning($"dropzone 3: '{string.Join(", ", factIDs)}'"); } /// <summary> /// First try to get the dropzone state using coroutines. /// Doesn't work because coroutines can't access method local temporary variables. /// </summary> /// <returns></returns> [Obsolete("Use getDropzoneStateAsync instead")] private IEnumerator GetDropzoneState() { Debug.LogWarning($"dropzone pre"); DomNodeWrapper doc = null; yield return DomNodeWrapper.getDocument(webViewComponent.tab, (document) => { Debug.LogWarning($"dropzone 1: '{document}'"); doc = document; StartCoroutine(document.querySelectorAll("[dropzone='copy']", (dropzones) => { foreach (var dropzone in dropzones) { Debug.LogWarning($"dropzone 2: Node is Null?: '{dropzone.Node == null}'"); StartCoroutine(dropzone.getAttributes((attributes) => { Debug.LogWarning($"dropzone 3 getAttributes: '{string.Join(", ", attributes.Values)}'"); })); } })); }); Debug.LogWarning($"dropzone post: '{doc}'"); } } public class FactObjectUIConverter : JsonConverter<FactObjectUI> { public override void WriteJson(JsonWriter writer, FactObjectUI value, JsonSerializer serializer) { //serializer.Serialize(writer, value.Fact); JObject o = JObject.FromObject(value.Fact, WebViewComponent.serializer); o.AddFirst(new JProperty("id", value.Fact.Id)); o.WriteTo(writer); } public override FactObjectUI ReadJson(JsonReader reader, Type objectType, FactObjectUI existingValue, bool hasExistingValue, JsonSerializer serializer) { var factObject = new FactObjectUI(); factObject.Fact = serializer.Deserialize<Fact>(reader); return factObject; } }