Skip to content
Snippets Groups Projects
WebViewController.cs 11.4 KiB
Newer Older
  • Learn to ignore specific revisions
  • 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.Linq;
    
    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);
    
            SwitchScrollUI.activeScrollData.HintAvailableEvent.AddListener(OnHintAvailable);
    
            webViewComponent.tab.AddJSBinding("applyScroll", ApplyScrollHandler);
            webViewComponent.tab.AddJSBinding("getHint", GetHintHandler);
    
            if (SwitchScrollUI.activeScrollData.Scroll is not null)
            {
                SetScrollContent(SwitchScrollUI.activeScrollData.Scroll);
            }
    
            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("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;
    
            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.Document;
    
            DomNodeWrapper scrollContainer = await document.querySelectorAsync("#scrollContainer");
    
            var description = scroll.description;
    
            if (Regex.IsMatch(description, ".*<scroll-description.*"))
    
                // 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;
                    return $"<mi dropzone='copy' data-slot-id='{slotId}' {(assignment.IsSet ? $"data-fact-id='{assignment.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;
                    return $"<mi data-solution-id='{solutionId}' {extraAttributes}>{scroll.acquiredFacts.Find(fact => fact.@ref.uri == solutionId).label}</mi>";
                });
    
            }
            else
            {
                // scroll is a lagacy 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='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
    
            dropzones = null;
    
            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");
                }
    
    
                // if fact has been unasigned from the scroll
                if (attributeModifiedEvent.value == "null")
                {
                    // remove fact from slot
                    SwitchScrollUI.activeScrollData.AssignFact(slot, null);
                    return;
                }
    
    
                // 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 async void OnHintAvailable(IReadOnlyList<string> url)
        {
            dropzones = await (await webViewComponent.tab.Document).querySelectorAllAsync("[dropzone='copy']");
            foreach (var dropzone in dropzones)
            {
                if (url.Contains(dropzone.Node.attributes["data-slot-id"]))
                {
                    _ = dropzone.setAttributeValue("data-hint-available", "true");
                }
                else if (dropzone.Node.attributes.ContainsKey("data-hint-available"))
                {
                    _ = dropzone.removeAttributeAsync("data-hint-available");
                }
            }
        }
    
    
        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();
        }
    
    
        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;
        }
    }