varkor / quiver

A modern commutative diagram editor for the web.
https://q.uiver.app
MIT License
2.4k stars 80 forks source link

When Save is clicked, does it wait for URL to populate before returning (from click())? Also share of Django + Quiver project. #245

Closed FruitfulApproach closed 1 month ago

FruitfulApproach commented 1 month ago

Here's how I've included Quiver into a Django site. Quiver code gets copied (entire folder) into Project/static/quiver. I also download and copy the KaTeX distro into Project/static/quiver/src/KaTeX. Previous times I did this there were quite a few code changes to Quiver itself. This new method only has two lines of Quiver changed and they're both display:none for the ".toolbar" class in main.ss and a display:none for the logo (so far). If you try to hide these using programmatic JavaScript after the iframe loads, then its too late - they've displayed.

Anyway, I accomplish this by looping through all buttons in the iframe and grabbing each's data-name attribute. We can then click the buttons with btn.click(); however:

Question.: I'm wondering if quiver_save_btn.click() will completely save the diagram to URL before exiting. Because if it doesn't, it's just by diagram size/chance that my current code is working. Besides a timer based delay scaled by the size of the diagram, how else could we remedy this? Does Quiver have a "done-saving-to-URL" indicator that I can hack into?

quiver_editor_include.html:

{% load static %}

<iframe sandbox="allow-same-origin allow-scripts allow-popups allow-forms allow-modals"
    src='{% static "quiver/src/index.html" %}' 
    style="height:96vh;width:100%;border:none;overflow:hidden;"
    id="quiver-frame">
</iframe>

<script type="text/javascript">
    var prop_id = "{{ prop_id }}";

      //find iframe
    var quiver_frame = null; 
    var quiver_save_btn = null;
    var quiver_undo_btn = null;
    var quiver_redo_btn = null;

    document.addEventListener("DOMContentLoaded", function(event) { 
        quiver_frame = document.getElementById("quiver-frame");

        quiver_frame.addEventListener("load", function() {      
            var buttons = quiver_frame.contentWindow.document.getElementsByTagName("button");             

            for (var i = 0; i < buttons.length; i++)
            {   
                var button = buttons[i];

                if (button.hasAttribute('data-name'))
                {
                    var data_name = button.getAttribute('data-name');

                    switch (data_name)
                    {
                        case 'Save':
                            quiver_save_btn = button;
                            break;
                        case 'Undo':
                            quiver_undo_btn = button;
                            break;
                        case 'Redo':
                            quiver_redo_btn = button;
                            break;
                        default:
                            break;
                    }
                }
            }
        });       

    });

    function undo() {
        quiver_undo_btn.click();
    }

    function redo() {
        quiver_redo_btn.click();
    }

    function save_prop() {
        quiver_save_btn.click();

        var save_data = quiver_frame.contentWindow.location.href;
        var json_data = null;

        if (save_data.indexOf("#q=") != -1)
        {
            json_data = save_data.split("#q=")[1];

            json_data = {
                'prop_id' : prop_id,
                'json_data' : json_data,
            }            
        }
        else {
            json_data = {
                'prop_id': prop_id,
                'json_data' : '',
            }
        }    

        $.ajax({
            type: "POST",
            url: "{% url 'save_prop' %}",

            data: {
                 csrfmiddlewaretoken: "{{ csrf_token }}",
                 json: JSON.stringify(json_data)
            },

            success: function (response) {
                // save_prop service's response:
                console.log(response);
            }
        });
    }
</script>

Additional shares: In my appliction a Diagram + Title + a few extra fields such as diagram_commutes and logic_negated make up a Prop class in my Neo4j models. All objects / arrows are an instance of an ArrowNode class. It has to be a node in Neo4j because Quiver supports Arrow-to-Arrow/Node connections as well. These "Diagram sketch props" can be strung together into right-associative => (implies) chains, which recursively form a tree of Props. In other words we had to dual/triply/multiply purpose a Prop class so that this will work out as Neomodel / Neo4j don't like model polymorphism, or there's practically no way to make use of it in a mixed setting of many subclasses from a base class; i.e. in the way that you'd expect Python polymorphism to work. This is okay for us as our models.py in Django is not going to be too extensive. Mainly we're encoding what needs to be done in order to present this site correctly. As far as extending the Visual language goes, this is done in the language itself and not by having to necessarily make patch of the code. Though some things will be built-in such as simple functoring of a CD (commutative diagram). And CD gluing along a common subdiagram.

FruitfulApproach commented 1 month ago

image

This might solve it: repeatedly checking with a do-while loop for changes in the iframe's href.