-
Notifications
You must be signed in to change notification settings - Fork 92
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
Add UI modal dialog for saving plots #2589
Conversation
use a progress bar when rendering move to a grid layout to provide more space to the preview
Use save callback
Fixing this build failure. I messed up something that I didn't catch locally during code cleanup. |
...vs/workbench/browser/positronComponents/positronModalDialog/components/okCancelActionBar.tsx
Outdated
Show resolved
Hide resolved
src/vs/workbench/contrib/positronPlots/browser/components/actionBars.tsx
Outdated
Show resolved
Hide resolved
@@ -117,6 +125,7 @@ export const ActionBars = (props: PropsWithChildren<ActionBarsProps>) => { | |||
<ActionBarButton iconId='positron-right-arrow' disabled={disableRight} tooltip={positronShowNextPlot} ariaLabel={positronShowNextPlot} onPressed={showNextPlotHandler} /> | |||
|
|||
{enableZoomPlot && <ZoomPlotMenuButton actionHandler={zoomPlotHandler} zoomLevel={props.zoomLevel} />} | |||
{enableSavingPlots && <ActionBarButton iconId='positron-save' tooltip={localize('positronSavePlot', "Save plot")} ariaLabel={localize('positronSavePlot', "Save plot")} onPressed={savePlotHandler} />} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit, for consistency, should localize('positronSavePlot', "Save plot")
be saved in a variable, like positronClearAllPlots was on line 37?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We no longer need to preload all our calls to localize in modal dialogs. This was fixed in my modal dialog rework a couple of weeks ago.
So now, this code can be inlined:
/**
* Localized strings.
*/
const positronShowPreviousPlot = localize('positronShowPreviousPlot', "Show previous plot");
const positronShowNextPlot = localize('positronShowNextPlot', "Show next plot");
const positronClearAllPlots = localize('positronClearAllPlots', "Clear all plots");
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's good to know. I thought it was for ease of re-use and clarity.
Nice Tim! Do you want to log a follow-up task to handle if the target file already exists when saving? |
Also, for another follow up issue, do you want to add support for saving SVG based plots (presumably a rendering to PNG as an MVP)? Alternatively, if we don't support this in 1.0, the Save button should be disabled, perhaps with a tool-tip explaining the plot format is not supported for saving. |
@@ -106,6 +111,9 @@ export const ActionBars = (props: PropsWithChildren<ActionBarsProps>) => { | |||
const zoomPlotHandler = (zoomLevel: number) => { | |||
props.zoomHandler(zoomLevel); | |||
}; | |||
const savePlotHandler = async () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One of these should be the implemetation:
const savePlotHandler = async () => positronPlotsContext.positronPlotsService.savePlot();
const savePlotHandler = async () => {
await positronPlotsContext.positronPlotsService.savePlot();
};
@@ -117,6 +125,7 @@ export const ActionBars = (props: PropsWithChildren<ActionBarsProps>) => { | |||
<ActionBarButton iconId='positron-right-arrow' disabled={disableRight} tooltip={positronShowNextPlot} ariaLabel={positronShowNextPlot} onPressed={showNextPlotHandler} /> | |||
|
|||
{enableZoomPlot && <ZoomPlotMenuButton actionHandler={zoomPlotHandler} zoomLevel={props.zoomLevel} />} | |||
{enableSavingPlots && <ActionBarButton iconId='positron-save' tooltip={localize('positronSavePlot', "Save plot")} ariaLabel={localize('positronSavePlot', "Save plot")} onPressed={savePlotHandler} />} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We no longer need to preload all our calls to localize in modal dialogs. This was fixed in my modal dialog rework a couple of weeks ago.
So now, this code can be inlined:
/**
* Localized strings.
*/
const positronShowPreviousPlot = localize('positronShowPreviousPlot', "Show previous plot");
const positronShowNextPlot = localize('positronShowNextPlot', "Show next plot");
const positronClearAllPlots = localize('positronClearAllPlots', "Clear all plots");
|
||
if (plot instanceof StaticPlotClient) { | ||
// if it's a static plot, save the image to disk | ||
const staticPlot = plot as StaticPlotClient; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is unnecessary. plot
is an instance of StaticPlotClient
.
I think it's good now from using the |
Fix focusable elements in modal Inline string localization Fix action bar button order and separator visibility
I think this can be added in without too much more work. Most of the existing code prior to this PR accounts for other formats. |
I also fixed up the button order on the action bar. The save button wasn't in a consistent location due to the zoom button. The tab order in the modal is also better now since the input number elements weren't being included properly in the focusable elements. There still seems to be a bug with the Save and Cancel buttons that don't seem to be included in the tab order. They should work as far as I can tell. |
Fixed up the compile error. The change didn't get staged for commit for some reason. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This could be merged as-is but I think we could make the error & plot state management better! Left a few notes.
@@ -299,13 +299,62 @@ export class PlotClientInstance extends Disposable implements IPositronPlotClien | |||
return deferred.promise; | |||
} | |||
|
|||
/** | |||
* Requests that the plot be rendered at a specific size, but does not | |||
* store the rendered plot to _lastRender. This is useful for previewing |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This duplicates a lot of the code in render()
. Consider consolidating?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One way to avoid this duplication would be to create a way to clone the plot client object so that you have two clients running against the same comm -- one for your preview window, another for the plots pane.
Since the backend is not stateful this is totally legit. It would also fix the other issue re: unnecessary re-rendering since the client for the preview window could also cache previous renders.
showSavePlotModalDialog(this._layoutService, this._keybindingService, this._fileDialogService, plot, this.savePlotAs, suggestedPath); | ||
} else { | ||
// if it's a webview plot, do nothing | ||
return; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we show a warning in this case?
const acceptHandler = async () => { | ||
if (validateInput()) { | ||
setRendering(true); | ||
const plotResult = await generatePreview(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If an exception is thrown while generating the plot prior to saving it, we should show that error to the user in an error dialog.
|
||
const acceptHandler = async () => { | ||
if (validateInput()) { | ||
setRendering(true); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In my testing, this caused the plot to be unnecessarily rendered a second time even though I'd already done a preview render at the correct size -- maybe we can avoid rendering in this case?
} | ||
setRendering(true); | ||
try { | ||
const plotResult = await generatePreview(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should catch errors thrown during preview and show them to the user -- probably in the error
area already established.
(A very common error here would e.g. be the "figure margins too large" error you get in R when you try to draw a plot that's too small for its contents)
Preview before saving dynamic plots Use a progress bar when rendering Save plot validation Use save callback
Intent
Address #2169
Approach
For static plots, present a file dialog to select the save location. It saves the data uri that is shown in the plots pane.
For dynamic plots, a dialog is presented to select the save location, width, height, and DPI. A preview is available if the user wants to see what it looks like before saving. At save, the plot is rendered and saved.
The modal dialog uses callbacks as was changed with the recent refactoring with modals.
The file formats was mostly scaffolding for future support and this continues that with a bit of hardcoding to PNG.
QA Notes
See https://docs.google.com/document/d/1CqA8Ypji8FhiqZ5MujvilfV58btoNfIbsjK67qFtvd8/edit?usp=sharing for the spec.
The dialog does validate the values before generating a preview and saving. This wasn't tried on Windows so there may be bugs with the path validation that may need fixing.
The file format is PNG only at the moment