gap-packages / francy

An Interactive Discrete Mathematics Framework for GAP

Home Page:https://gap-packages.github.io/francy/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Francy modal menus are not language-agnostic

zerline opened this issue · comments

Hi again!

I'm trying to get a modal menu on graph nodes, on a python kernel.

Francy JS code (in francy-core/src/render/callback.js) calls a Trigger function, which does not exist in Python.

Did you think of any more general message handling feature?

Hi Odile,

This was a hack to make it work with JupyterKernel that implements a native GAP kernel.

// oh well, Trigger(<json>); is the entrypoint back to GAP
// while we don't support comms on the kernel:
return this.callback(`Trigger(${JSON.stringify(JSON.stringify(object))});`);

In order to make it work with the python Kernel we need to change a bit this _execute function to make it talk to the kernel using the comm_manager:

https://jupyter-notebook.readthedocs.io/en/stable/comms.html

I might be able to spend some time in a near future and try to sort this out...

Manuel

Yes, comms would be great :)

I've been told, though, that comms are difficult to handle with GAP.

So I have been trying to implement a switch in _execute function. We can easily send some additional parameters with the callback (like the programming language...), and use them to decide what to do.

Of course that would be another hack, but would be a great satisfaction for the users.

If you don't agree, I would be happy to help on the comms' strategy, though.

I still remember the day I wrote the first version of this hack by the end of January 2017 in Edinburgh at the ICMS... and it managed to live for so long...

I would give a try with comms or instead abstract the callback a bit more by excluding this Trigger() string somehow... in the end we only send back the callback JSON itself...

I'm open for suggestions ;)

The callback string (the command line) to be passed is language dependent. Therefore, we need to first parse the callback that comes from the interface, then construct a language-specific string.

Alternatively: make this language-specific string already at modal menu build time. But this would make a more language-specific JSON. Don't know if that's any better.

As for passing the language parameter, an easy solution is to pass it as knownArgs first element. Another one would be to have language a parameter in its own.

How do you run your JS test files please?

For instance: ./js/packages/francy-core/src/__test__/modal.test.js

You have to run all the tests for all the packages:

user@local $> cd francy/js
user@local $> npm run test

@zerline if you proceed with a solution for this issue, I'll be happy to evaluate the pull request
I would rather include a new property language in the JSON spec and not use the knownArgs for that, but my preference would be the comms implementation although I'm not sure if it is feasible...

@mcmartins would you want to have a look at this commit ? This code i's actually using the callback object language parameter. I still don't see any result, though.
Here is an example JSON. It's an actual menu (not a modal menu).

'{"version": "1.1.3", "mime": "application/vnd.francy+json", "canvas": {"id": "mycanvas", "title": "A menu", "width": 800.0, "height": 100.0, "zoomToFit": true, "texTypesetting": false, "graph": {}, "menus": {"mycanvas_menu1": {"id": "mycanvas_menu1", "title": "My menu", "callback": {}, "menus": {"mycanvas_menu2": {"id": "mycanvas_menu2", "title": "My function call", "callback": {"id": "mycanvas_callback2", "language": "python", "funcname": "len", "trigger": "click", "knownArgs": ["a string"], "requiredArgs": {}}, "menus": {}, "messages": {}}, "mycanvas_menu3": {"id": "mycanvas_menu3", "title": "My method call", "callback": {"id": "mycanvas_callback3", "language": "python", "funcname": "pop", "trigger": "click", "knownArgs": ["[42,42,42]"], "funcscope": "object", "requiredArgs": {}}, "menus": {}, "messages": {}}}, "messages": {}}}, "messages": {}}}'

NB: defining a Trigger function also works. One has to make sure to have this function in the global namespace. In any case, I'm unable to get anything as a result, on the interface. I wonder if somebody could tell me what I'm supposed to see: a popup?

The callback modal calls this Trigger function that must exist in the global space (this in gap is normally true on each session) with a JSON string:

{
"callback": {
  "id": "callback_1",
  "funcname": "showfacts",
  "trigger": "click",
  "knownArgs": [],
   "requiredArgs": {}
  },
}

This JSON contains the metadata of what is expected to be executed by the kernel. The Trigger function should parse this metadata and create the function call syntax and pass it to eval. This means that the function that is being passed in this JSON will be executed by the eval and will return something to the user. The catch here is that, if that function does not return and does a print or returns a String, for instance, Francy won't display it on the screen (I think this is what you're experiencing). It can be improved and display these messages somehow, but at the moment is not the case.

So the function being evaluated should return a canvas object, usually the same canvas with the changes introduced by this function (it could be a new element or even a FrancyMessage).

In GAP the callback defined before would execute a function as follows:

showfacts:=function()
  message := FrancyMessage("Some Message");
  Add(canvas, message);
  return Draw(canvas);
end;

The result is then re-drawn on the screen...

Thanks very much!
I must say, I fear it will be a lot of work, and to re-display all the canvas does not seem ideal.
Where do you print your outputs, typically? In a canvas message, I guess?
I'm exploring something different: an Output widget, outside of the canvas.
With ipywidgets (ie the python kernel), we can print outputs to a dedicated widget. This way, there is no need to compute new JSON and let the browser display everything again. So I find it better. No idea whether you could do that in GAP, though.

Well, the outputs in Francy are usually added to FrancyMessages and then re-drawn with the whole canvas object. I do agree, and re-drawing the whole canvas is not ideal... I did struggle a bit to make things differently but without much success... There is something important missing in Francy, that is the synchronization between GAP and Javascript object model (on the other hand, IPywidgets are fully synchronized with their correspondent objects in Python and Javascript). The GAP Kernel is not totally finish and the lack of a comms implementation, makes this a bit difficult, I would say. Nonetheless, I think theOutputWidget is a good idea.
Please let me know when you have something that you can show me, I will try to integrate / replicate it with GAP version.

Of course, being able to deal with comms would be a great step forward!

As for my example with ipywidgets.Output, here is a small test notebook at

https://gke.mybinder.org/v2/gh/zerline/francy-widget/develop?filepath=test/MenuWithOutput.ipynb

Ok, I’ve implemented an outofthebox solution so you won’t need to do any hack on the Trigger function.
It will display everything that is not a application/Francy mime type in an output below Francy working area.
I’ll push this to develop soonish and share a notebook with you.

You can give it a try here
Just invoke the callback by clicking the circle, for instance, and after submitting the form a new are will appear in the output.

It looks good, and quite nice. And useful.
Yet we plan to integrate Francy object into dashboard-like complex layouts (like sage-explorer for example), so dealing with a real ipytwidgets Output would be even better.

I'm not planning to develop further functionality, but would be nice if someone could spend sometime investigating how to implement this, perhaps improving the JupyterGapKernel as well.
I'll leave it open for now.