Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Francy modal menus are not language-agnostic #43

Open
zerline opened this issue May 29, 2019 · 18 comments
Open

Francy modal menus are not language-agnostic #43

zerline opened this issue May 29, 2019 · 18 comments

Comments

@zerline
Copy link

zerline commented May 29, 2019

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?

@mcmartins
Copy link
Member

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

@zerline
Copy link
Author

zerline commented May 29, 2019

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.

@mcmartins
Copy link
Member

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 ;)

@zerline
Copy link
Author

zerline commented May 29, 2019

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.

@zerline
Copy link
Author

zerline commented May 29, 2019

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.

@zerline
Copy link
Author

zerline commented May 30, 2019

How do you run your JS test files please?

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

@mcmartins
Copy link
Member

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

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

@mcmartins
Copy link
Member

mcmartins commented Jun 3, 2019

@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...

@zerline
Copy link
Author

zerline commented Jun 4, 2019

@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": {}}}'

@zerline
Copy link
Author

zerline commented Jun 5, 2019

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?

@mcmartins
Copy link
Member

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...

@zerline
Copy link
Author

zerline commented Jun 11, 2019

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.

@mcmartins
Copy link
Member

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.

@zerline
Copy link
Author

zerline commented Jun 12, 2019

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

@mcmartins
Copy link
Member

mcmartins commented Jun 13, 2019

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.

@mcmartins
Copy link
Member

mcmartins commented Jun 16, 2019

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.

@zerline
Copy link
Author

zerline commented Jun 17, 2019

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.

@mcmartins
Copy link
Member

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants