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

Widget with toolbar and sidepanel in cell output #419

Merged
merged 22 commits into from
Feb 6, 2025

Conversation

brichet
Copy link
Collaborator

@brichet brichet commented Jan 31, 2025

Fixes #397

Peek 2025-01-31 17-47

This PR:

  • creates a new widget (JupyterGISOutputWidget) extending main area widget, to be used in the cell output. It extends the main area widget to ease the integration of the toolbar, but it could a simplest widget (to be discussed ?)
  • allows the tracker to track these new widget, which enables the toolbar buttons and the side panel
  • the document context is often used to get the document path and model. This PR adds a simplified context interface to share the same interface between document widget (main area) and JupyterGISOutputWidget, and allows the commands to work with both widgets.
  • uses the localPath property of the context instead of path, to avoid bringing the RTC: prefix.

EDIT

  • the document context was often used to get the model (IJupyterGISModel) or the document path (also contained in the model). The new JupyterGISOutputWidget is not a document widget and do not have a proper Context. This PR remove the use of the context and use directly the model instead, which can easily be retrieved from JupyterGISOutputWidget or JupyterGISWidget, the 2 widget tracked by the WidgetTracker.

Checklist

  • PR has a descriptive title and content.
  • PR description contains references to any issues the PR resolves, e.g. Resolves #XXX.
  • PR has one of the labels: documentation, bug, enhancement, feature, maintenance
  • Checks are passing.
    Failing lint checks can be resolved with:
    • pre-commit run --all-files
    • jlpm run lint

📚 Documentation preview: https://jupytergis--419.org.readthedocs.build/en/419/
💡 JupyterLite preview: https://jupytergis--419.org.readthedocs.build/en/419/lite

Copy link
Contributor

Binder 👈 Launch a Binder on branch brichet/jupytergis/cell_output_document_widget

@brichet brichet added the enhancement New feature or request label Jan 31, 2025
@brichet brichet force-pushed the cell_output_document_widget branch from 60ef40e to fe38083 Compare January 31, 2025 13:19
Copy link
Contributor

github-actions bot commented Jan 31, 2025

Integration tests report: appsharing.space

@brichet
Copy link
Collaborator Author

brichet commented Jan 31, 2025

I keep it as draft as I can't find a way to handle the resize event, which leads to toolbar elements hidden.

@brichet brichet marked this pull request as ready for review February 2, 2025 21:50
@brichet brichet force-pushed the cell_output_document_widget branch from 937f7a7 to 68d24a2 Compare February 2, 2025 21:51
@brichet
Copy link
Collaborator Author

brichet commented Feb 2, 2025

I keep it as draft as I can't find a way to handle the resize event, which leads to toolbar elements hidden.

It should work now.

@brichet brichet marked this pull request as draft February 3, 2025 07:48
@brichet
Copy link
Collaborator Author

brichet commented Feb 3, 2025

Please update snapshots

@brichet brichet marked this pull request as ready for review February 3, 2025 13:55
Copy link
Collaborator Author

@brichet brichet left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here are the 2 main changes that could be ported to JupyterCAD.

Other changes may be related to JupyterGIS only.

Comment on lines 48 to 76
/**
* A main area widget designed to be used as Notebook cell output widget, to ease the
* integration of toolbar and tracking.
*/
export class JupyterGISOutputWidget
extends MainAreaWidget<JupyterGISPanel>
implements IJupyterGISOutputWidget
{
constructor(options: JupyterGISOutputWidget.IOptions) {
super(options);
this.addClass(CELL_OUTPUT_WIDGET_CLASS);
this.model = options.model;

const resizeObserver = new ResizeObserver(() => {
// Send a resize message to the widget, to update the child size.
MessageLoop.sendMessage(this, Widget.ResizeMessage.UnknownSize);
});
resizeObserver.observe(this.node);
}

readonly model: IJupyterGISModel;
}

export namespace JupyterGISOutputWidget {
export interface IOptions extends MainAreaWidget.IOptions<JupyterGISPanel> {
model: IJupyterGISModel;
}
}

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The widget displayed in cell output.

Comment on lines 55 to 66
const content = new JupyterGISPanel({ model });
const toolbar = new ToolbarWidget({
commands,
model,
externalCommands: externalCommands.getCommands()
});
this._jgisWidget = new JupyterGISOutputWidget({
model,
content,
toolbar
});

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where it is created in the notebook renderer.

@brichet brichet force-pushed the cell_output_document_widget branch from a795d35 to 7f18130 Compare February 3, 2025 14:19
@@ -72,7 +72,9 @@ def __init__(
ydoc = Doc()

super().__init__(
comm_metadata=dict(ymodel_name="@jupytergis:widget", **comm_metadata),
comm_metadata=dict(
ymodel_name="@jupytergis:widget", cwd=os.getcwd(), **comm_metadata
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we really need this? cc. @davidbrochart for awareness

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is required to be able to add local layer from the widget buttons.
It could probably be removed if we don't use file path relative to the JGIS project file.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is required to be able to add local layer from the widget buttons

Once the JGIS file is created, we know its "cwd" is the path to the file, right? Since we know that path in the front-end already, we may not need to pass this info from Python?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it is not used if a path is provided.
But we want to create a GISDocument without file, no ?

doc = GISDocument()

Copy link
Member

@martinRenou martinRenou Feb 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In that case, we can assume the "cwd" is the Notebook path, and since we have the notebook tracker handy, we can get its path?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

arf

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All this situation is not great. We need a better approach but I don't have a good quick solution to suggest.

See #426 (comment)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are the widgets supported in console ? I don't know how to display it in console.
If not, we don't need the path, it is useful for the buttons only, not for the API I think.

Copy link
Collaborator Author

@brichet brichet Feb 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Otherwise something like the following should work in the front-end :

const currentWidget = app.shell.currentWidget;
let currentPath: string | undefined = undefined;
if (currentWidget instanceof NotebookPanel && notebookTracker) {
  currentPath = notebookTracker.currentWidget?.context.localPath;
} else if (currentWidget instanceof ConsolePanel && consoleTracker) {
  currentPath = consoleTracker.currentWidget?.sessionContext.path;
}
if (currentPath) {
  this.jupyterGISModel.filePath = PathExt.join(
    PathExt.dirname(currentPath),
    'unsaved_project'
  );
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That should work in most cases. It's only when people changed did os.chdir that it will break this. But I guess we can live with this.

Copy link
Member

@mfisher87 mfisher87 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Beautiful 🤩

header.title.label = this._currentModel.filePath;
this._annotationModel.model =
options.tracker.currentWidget?.model || undefined;
// await changed.context.ready;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// await changed.context.ready;

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, thanks for remind me this.
It seems to work without, but I don't know if it was necessary and should be kept somehow.

@@ -22,6 +28,10 @@ export class JupyterGISWidget
super(options);
}

get model(): IJupyterGISModel {
return this.context.model;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤩

This refactor to pass around model instead of context feels so much simpler!!

@mfisher87
Copy link
Member

@brichet it would be amazing to release this feature before the hackathon on Wednesday. How do you feel about that?

I didn't approve because although I read the entire PR, I don't feel I have the expertise to "approve" on this project yet. I have a lot to learn from you all! :)

if (currentWidgetPath) {
this.jupyterGISModel.filePath = PathExt.join(
PathExt.dirname(currentWidgetPath),
'unsaved_project'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of giving this file path, what would it take to create the file from the front-end? If no file name is provided, we create an "untitled" file, if a new file path is provided (something we don't know) we create the file.

In replacement of what is removed in https://github.com/geojupyter/jupytergis/pull/426/files

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it is also nice to be able to debug without creating a file. And to add a save() method to the API, to be able to save the document.
But indeed, if the path is provided and does not exist, we could create it from the frontend.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it is also nice to be able to debug without creating a file. And to add a save() method to the API, to be able to save the document.

💯 💯 💯

@brichet
Copy link
Collaborator Author

brichet commented Feb 6, 2025

Some more information about the changes in this PR.

There are still issues with the widgets when deleting a cell output.
We have to choose the least worst solution between:

  • doing nothing, the tracker still think this widget is the current and does not update side bar
  • informing the model that the cell output has been deleted and dispose of the model and widget (so the widget is removed from the tracker). But it will need to be instantiated again in the notebook to be displayed properly.
  • only dispose of the widget, which is then removed from the tracker. Since the model is kept in yjs-widgets registry, it can be displayed again. The side effect is that disposing of the widget remove it from all cell output where it is displayed.

The last solution I can think of (my preference) is to use some kind of isVisible on the widget, and display the side bar only if the widget is the current one and is visible. We can also use it to empty the side bar when the main area widget is not the current widget, it can be less confusing for users.

@brichet
Copy link
Collaborator Author

brichet commented Feb 6, 2025

After some discussion with @martinRenou and @trungleduc the best is probably to go the first option temporary.

doing nothing, the tracker still think this widget is the current and does not update side bar

The idea is to fix this behavior upstream in yjs-widget, by properly dispose of the actual widget in the concerned cell output.

Copy link
Collaborator Author

@brichet brichet left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@martinRenou for me it is ready.
My only concern is commented below, and I don't know if this code is tested somewhere.

this._annotationModel.contextChanged.connect(async () => {
await this._annotationModel?.context?.ready;
this._annotationModel.modelChanged.connect(async () => {
// await this._annotationModel?.context?.ready;
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gjmooney do you remember if this await was required ? Should we restore something similar, now we removed the context ?

@trungleduc
Copy link
Collaborator

The idea is to fix this behavior upstream in yjs-widget, by properly dispose of the actual widget in the concerned cell output.

you are trackingthis._jgisWidget and not the widget disposed by yjs-widget, so you might need to dispose this._jgisWidget in YJupyterGISWidget.dispose method

@brichet
Copy link
Collaborator Author

brichet commented Feb 6, 2025

The idea is to fix this behavior upstream in yjs-widget, by properly dispose of the actual widget in the concerned cell output.

Should be fixed with QuantStack/yjs-widgets#20

Copy link
Member

@martinRenou martinRenou left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great!

Let's merge :D

@martinRenou martinRenou merged commit 2669fad into geojupyter:main Feb 6, 2025
14 checks passed
@brichet brichet deleted the cell_output_document_widget branch February 6, 2025 13:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

earthquakes.jGIS fails to load points when opened in widget Display toolbar in widget view
4 participants