varkor / quiver

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

Exposing Quiver as an API #97

Open emilypi opened 3 years ago

emilypi commented 3 years ago

Hi @varkor!

I was wondering if there were plans to have quiver's diagram construction and extraction to various formats as part of an externally query-able API? I'd love to write bindings for one.

Cheers!

varkor commented 3 years ago

At the moment, it is unfortunately quite difficult to set this up, because everything must be static to be hosted on GitHub Pages. I don't see a way to support this without changing how quiver is hosted.

enjoysmath commented 3 years ago

@varkor what if all we need is a javascript call into Quiver for getting the base64 encoding and another call for setting, or we could set by directing the iframe to the new URL. But if we host Quiver ourselves in a Django template, then this API via javascript call will work.

enjoysmath commented 3 years ago

@emilypi I need this feature too! It is in fact critical to my application that I be able to get and set the graph externally.

varkor commented 3 years ago

If you're willing to self-host quiver, then this would be possible, yes (most likely you could use the existing internal APIs without much hassle). I agree that it would be nice to have this feature without self-hosting, but at the moment, I don't see how to achieve this the current way quiver is set up.

enjoysmath commented 2 years ago

I can share some code of how I accomplished the Javascript API for my personal project "Abstract Spacecraft". I even did it with Quiver completely copied into a folder under Django project's static/ directory. I then have a Django template with an iframe call to load quiver in it. The Django template in turn is then included (with special #IncludeDjangoTemplate-diagram_editor HTML tag id). The export script of Bootstrap Studio then includes the template using a Jinja2 include statement. And it works! I then hid all of the default Quiver buttons / panes using CSS display:none. If you had to completely remove them it would require a lot of JS editing of ui.js or else things would be broken. Anyway, I have bootstrap buttons and a connect_quiver_gui.js included into my BSS design. That file does JQuery statements to call functions that I coded inside of the UI class (ui.js of Quiver). There's things you have to do such as load the CSRF token into the iframe's parent window as a global JS variable, as well as the URL to the Django/Neo4j database save/load code. Those functions are essentially like an API. That is the way forward to solve your issues I think. Self-host Quiver (easy to do, download, run make, and copy into your project), and start hacking the ui.js / quiver.js code to create your API. I am available if you need help with this. I was also new to JS, but somehow managed to get things to work just by googling around and the SO stack exchange. @emilypi

enjoysmath commented 2 years ago

@varkor All we need are some instructions on how to get the URL from the iframe and how to set it and reload Quiver if needed. We don't need much in the way of an API in other words. So please help us do this :)

We would like to use your version of Quiver, either hosted by us or by your web host, so that we can always keep it up-to-date with what you're developing and pushing to github.

varkor commented 2 years ago

@enjoysmath: I don't think you will be able to get the URL of an <iframe> if quiver is hosted externally due to security constraints in JavaScript. However, if you are self-hosting quiver, then you should be able to query and set the URL with:

your_iframe.contentWindow.location.href
varkor commented 2 years ago

@emilypi: could you say a few more words about the use case you have in mind?

Quelklef commented 3 months ago

I'd like to chime in because I have what sounds like a similar use-case in mind

I want embed q.uiver inside a web application of my own. My app would have a "create commutative diagram" button. When clicked, it would open q.uiver in an <iframe>. The user could create a diagram using q.uiver. When complete, they would click a "done" button in in my application; this would read the output of q.uiver (ie, the generated embed code or LaTeX) and return it to the rest of my application code.

My code would use that returned value to display the crafted diagram. The diagram would include an "edit" button which would open up q.uiver in an <iframe> like as before, using the "callback" URL that q.uiver provides on export (ie, the one with #q=).

If this were possible, it would mean that q.uiver can be embedded into any website as as a diagram editor component using a little javascript

I believe that there would need to be two changes for this to be possible:

  1. q.uiver.app would need to emit a sufficiently-lenient CORS header, such as Access-Control-Allow-Origin: *, so that application code can reach inside the <iframe>. Since q.uiver does not store any sensitive user-data, I think this is safe to do

  2. q.uiver.app would need to expose export options programmatically, for instance by managing a document.quiver.exports object so that document.quiver.exports.latex({ cramped: true }) does what it sounds like

Quelklef commented 3 months ago

to add: I'm open to trying to make a PR to add this, if you like, since I'm asking for it. I might not easily be able to if NixOS gets in the way of make, but I can try :)

varkor commented 2 months ago

@Quelklef: sorry for the delay in responding. My impression was that GitHub Pages already sets Access-Control-Allow-Origin: *. I'd be happy for the exports to be refactored to make it more convenient for using quiver as an embedded component, though.

Quelklef commented 1 month ago

You're right, q.uiver.app does already reply with access-control-allow-origin: *. My mistake! Seems like the actual barrier is the same-origin policy, which applies to <iframe>. It looks like window.postMessage() (and related functions) would be an appropriate API to use?

Quelklef commented 1 month ago

I set up a little test and it looks like this should work :-)

I created https://maynards.site/pm-sample containing the following code:

if (window.parent) {
  window.addEventListener('message', ev => {
    if (typeof ev.data === 'string')
      window.parent.postMessage(reverseString(ev.data), ev.origin);
  });
}
function reverseString(str) { return [...str].reverse().join(''); }

And I created a codepen (here) with an <iframe src="https://maynards.site">. It sends a message to the iframe with

theIframe.postMessage('test string', 'https://maynards.site')`

and listens for a response with

window.addEventListener('message', ev => {
  if (ev.origin === 'https://maynards.site')
    const response = ev.data;
    ...
  }
});

Altogether it works as expected: when the pen performs postMessage() with some string, it later receives a response containing the string reversed.