Skip to content

Commit 248c709

Browse files
authored
Zoom plots in editor (#8126)
An editor zoom action has been added as a menu button for the zoom levels. The currently selected zoom level is checked when opening the menu.
1 parent 725c7c2 commit 248c709

17 files changed

+414
-66
lines changed

src/vs/workbench/contrib/positronPlots/browser/components/dynamicPlotInstance.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,12 @@ import { ProgressBar } from '../../../../../base/browser/ui/progressbar/progress
1212
import { DisposableStore } from '../../../../../base/common/lifecycle.js';
1313
import { localize } from '../../../../../nls.js';
1414
import { PanZoomImage } from './panZoomImage.js';
15-
import { ZoomLevel } from './zoomPlotMenuButton.js';
1615
import { usePositronPlotsContext } from '../positronPlotsContext.js';
1716
import { PlotClientInstance, PlotClientState } from '../../../../services/languageRuntime/common/languageRuntimePlotClient.js';
1817
import { IPositronPlotSizingPolicy } from '../../../../services/positronPlots/common/sizingPolicy.js';
1918
import { PlotSizingPolicyAuto } from '../../../../services/positronPlots/common/sizingPolicyAuto.js';
2019
import { PlotSizingPolicyIntrinsic } from '../../../../services/positronPlots/common/sizingPolicyIntrinsic.js';
20+
import { ZoomLevel } from '../../../../services/positronPlots/common/positronPlots.js';
2121

2222
/**
2323
* DynamicPlotInstanceProps interface.

src/vs/workbench/contrib/positronPlots/browser/components/panZoomImage.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import React from 'react';
88

99
// Other dependencies.
1010
import { Scrollable } from '../../../../../base/browser/ui/positronComponents/scrollable/Scrollable.js';
11-
import { ZoomLevel } from './zoomPlotMenuButton.js';
11+
import { ZoomLevel } from '../../../../services/positronPlots/common/positronPlots.js';
1212

1313
interface PanZoomImageProps {
1414
width: number;

src/vs/workbench/contrib/positronPlots/browser/components/plotsContainer.tsx

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import { WebviewPlotThumbnail } from './webviewPlotThumbnail.js';
2121
import { usePositronPlotsContext } from '../positronPlotsContext.js';
2222
import { WebviewPlotClient } from '../webviewPlotClient.js';
2323
import { PlotClientInstance } from '../../../../services/languageRuntime/common/languageRuntimePlotClient.js';
24-
import { DarkFilter, IPositronPlotClient, IPositronPlotsService, PlotRenderFormat } from '../../../../services/positronPlots/common/positronPlots.js';
24+
import { DarkFilter, IPositronPlotClient, IPositronPlotsService, isZoomablePlotClient, PlotRenderFormat, ZoomLevel } from '../../../../services/positronPlots/common/positronPlots.js';
2525
import { StaticPlotClient } from '../../../../services/positronPlots/common/staticPlotClient.js';
2626
import { PlotSizingPolicyIntrinsic } from '../../../../services/positronPlots/common/sizingPolicyIntrinsic.js';
2727
import { PlotSizingPolicyAuto } from '../../../../services/positronPlots/common/sizingPolicyAuto.js';
@@ -39,7 +39,6 @@ interface PlotContainerProps {
3939
visible: boolean;
4040
showHistory: boolean;
4141
darkFilterMode: DarkFilter;
42-
zoom: number;
4342
}
4443

4544
/**
@@ -59,6 +58,7 @@ export const PlotsContainer = (props: PlotContainerProps) => {
5958
const positronPlotsContext = usePositronPlotsContext();
6059
const plotHistoryRef = React.createRef<HTMLDivElement>();
6160
const containerRef = useRef<HTMLDivElement>(undefined!);
61+
const [zoom, setZoom] = React.useState<ZoomLevel>(ZoomLevel.Fit);
6262

6363
// We generally prefer showing the plot history on the bottom (making the
6464
// plot wider), but if the plot container is too wide, we show it on the
@@ -165,6 +165,29 @@ export const PlotsContainer = (props: PlotContainerProps) => {
165165
};
166166
}, [plotWidth, plotHeight, props.positronPlotsService]);
167167

168+
useEffect(() => {
169+
// Create the disposable store for cleanup.
170+
const disposableStore = new DisposableStore();
171+
172+
// Get the current plot instance using the selected instance ID from the
173+
// PositronPlotsContext.
174+
const currentPlotInstance = positronPlotsContext.positronPlotInstances.find(
175+
(plotInstance) => plotInstance.id === positronPlotsContext.selectedInstanceId
176+
);
177+
if (currentPlotInstance && isZoomablePlotClient(currentPlotInstance)) {
178+
// Listen to the plot instance for zoom level changes.
179+
disposableStore.add(currentPlotInstance.onDidChangeZoomLevel((zoomLevel) => {
180+
setZoom(zoomLevel);
181+
}));
182+
// Set the initial zoom level.
183+
setZoom(currentPlotInstance.zoomLevel);
184+
}
185+
return () => {
186+
// Dispose of the disposable store when the component unmounts.
187+
disposableStore.dispose();
188+
}
189+
}, [positronPlotsContext.positronPlotInstances, positronPlotsContext.selectedInstanceId]);
190+
168191
/**
169192
* Renders either a DynamicPlotInstance (resizable plot), a
170193
* StaticPlotInstance (static plot image), or a WebviewPlotInstance
@@ -180,12 +203,12 @@ export const PlotsContainer = (props: PlotContainerProps) => {
180203
height={plotHeight}
181204
plotClient={plotInstance}
182205
width={plotWidth}
183-
zoom={props.zoom} />;
206+
zoom={zoom} />;
184207
} else if (plotInstance instanceof StaticPlotClient) {
185208
return <StaticPlotInstance
186209
key={plotInstance.id}
187210
plotClient={plotInstance}
188-
zoom={props.zoom} />;
211+
zoom={zoom} />;
189212
} else if (plotInstance instanceof WebviewPlotClient) {
190213
return <WebviewPlotInstance
191214
key={plotInstance.id}

src/vs/workbench/contrib/positronPlots/browser/components/staticPlotInstance.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ import React, { useEffect, useRef, useState } from 'react';
88

99
// Other dependencies.
1010
import { PanZoomImage } from './panZoomImage.js';
11-
import { ZoomLevel } from './zoomPlotMenuButton.js';
1211
import { StaticPlotClient } from '../../../../services/positronPlots/common/staticPlotClient.js';
12+
import { ZoomLevel } from '../../../../services/positronPlots/common/positronPlots.js';
1313

1414
/**
1515
* StaticPlotInstanceProps interface.

src/vs/workbench/contrib/positronPlots/browser/components/zoomPlotMenuButton.tsx

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,21 +11,14 @@ import * as nls from '../../../../../nls.js';
1111
import { IAction } from '../../../../../base/common/actions.js';
1212
import { ActionBarMenuButton } from '../../../../../platform/positronActionBar/browser/components/actionBarMenuButton.js';
1313
import { ThemeIcon } from '../../../../../base/common/themables.js';
14-
15-
export enum ZoomLevel {
16-
Fit = 0,
17-
Fifty = 0.5,
18-
SeventyFive = 0.75,
19-
OneHundred = 1,
20-
TwoHundred = 2,
21-
}
14+
import { ZoomLevel } from '../../../../services/positronPlots/common/positronPlots.js';
2215

2316
interface ZoomPlotMenuButtonProps {
2417
readonly actionHandler: (zoomLevel: ZoomLevel) => void;
2518
readonly zoomLevel: number;
2619
}
2720

28-
const zoomLevelMap = new Map<ZoomLevel, string>([
21+
export const zoomLevelMap = new Map<ZoomLevel, string>([
2922
[ZoomLevel.Fit, nls.localize('positronZoomFit', 'Fit')],
3023
[ZoomLevel.Fifty, nls.localize('positronZoomFifty', '50%')],
3124
[ZoomLevel.SeventyFive, nls.localize('positronZoomSeventyFive', '75%')],

src/vs/workbench/contrib/positronPlots/browser/positronPlots.contribution.ts

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,13 @@ import { PositronPlotsService } from './positronPlotsService.js';
1717
import { IPositronPlotsService, POSITRON_PLOTS_VIEW_ID } from '../../../services/positronPlots/common/positronPlots.js';
1818
import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from '../../../common/contributions.js';
1919
import { Extensions as ViewContainerExtensions, IViewsRegistry } from '../../../common/views.js';
20-
import { registerAction2 } from '../../../../platform/actions/common/actions.js';
21-
import { PlotsActiveEditorCopyAction, PlotsActiveEditorSaveAction, PlotsClearAction, PlotsCopyAction, PlotsEditorAction, PlotsNextAction, PlotsPopoutAction, PlotsPreviousAction, PlotsRefreshAction, PlotsSaveAction, PlotsSizingPolicyAction } from './positronPlotsActions.js';
20+
import { MenuRegistry, registerAction2, MenuId, ISubmenuItem } from '../../../../platform/actions/common/actions.js';
21+
import { PlotsActiveEditorCopyAction, PlotsActiveEditorSaveAction, PlotsClearAction, PlotsEditorZoomAction, PlotsCopyAction, PlotsEditorAction, PlotsNextAction, PlotsPopoutAction, PlotsPreviousAction, PlotsRefreshAction, PlotsSaveAction, PlotsSizingPolicyAction, ZoomFiftyAction, ZoomOneHundredAction, ZoomSeventyFiveAction, ZoomToFitAction, ZoomTwoHundredAction } from './positronPlotsActions.js';
2222
import { POSITRON_SESSION_CONTAINER } from '../../positronSession/browser/positronSessionContainer.js';
2323
import { Extensions as ConfigurationExtensions, IConfigurationRegistry } from '../../../../platform/configuration/common/configurationRegistry.js';
24-
import { localize } from '../../../../nls.js';
24+
import { localize, localize2 } from '../../../../nls.js';
2525
import { FreezeSlowPlotsConfigKey } from '../../../services/languageRuntime/common/languageRuntimePlotClient.js';
26+
import { PLOT_IS_ACTIVE_EDITOR } from '../../positronPlotsEditor/browser/positronPlotsEditor.contribution.js';
2627

2728
// Register the Positron plots service.
2829
registerSingleton(IPositronPlotsService, PositronPlotsService, InstantiationType.Delayed);
@@ -76,6 +77,27 @@ class PositronPlotsContribution extends Disposable implements IWorkbenchContribu
7677
registerAction2(PlotsActiveEditorCopyAction);
7778
registerAction2(PlotsActiveEditorSaveAction);
7879
registerAction2(PlotsSizingPolicyAction);
80+
this.registerEditorZoomSubMenu();
81+
}
82+
83+
private registerEditorZoomSubMenu(): void {
84+
// Register the main submenu for the editor action bar
85+
const zoomSubmenu: ISubmenuItem = {
86+
title: localize2('positronPlots.zoomSubMenuTitle', 'Set the plot zoom'),
87+
submenu: PlotsEditorZoomAction.SUBMENU_ID,
88+
when: PLOT_IS_ACTIVE_EDITOR,
89+
group: 'navigation',
90+
order: 3,
91+
icon: Codicon.positronSizeToFit
92+
};
93+
MenuRegistry.appendMenuItem(MenuId.EditorActionsLeft, zoomSubmenu);
94+
95+
// Register all the zoom actions
96+
registerAction2(ZoomToFitAction);
97+
registerAction2(ZoomFiftyAction);
98+
registerAction2(ZoomSeventyFiveAction);
99+
registerAction2(ZoomOneHundredAction);
100+
registerAction2(ZoomTwoHundredAction);
79101
}
80102
}
81103

src/vs/workbench/contrib/positronPlots/browser/positronPlots.tsx

Lines changed: 44 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,12 @@ import React, { PropsWithChildren, useCallback, useEffect, useState } from 'reac
1313
import { IWorkbenchLayoutService } from '../../../services/layout/browser/layoutService.js';
1414
import { PositronPlotsServices } from './positronPlotsState.js';
1515
import { PositronPlotsContextProvider } from './positronPlotsContext.js';
16-
import { HistoryPolicy, IPositronPlotsService } from '../../../services/positronPlots/common/positronPlots.js';
16+
import { HistoryPolicy, IPositronPlotsService, isZoomablePlotClient, ZoomLevel } from '../../../services/positronPlots/common/positronPlots.js';
1717
import { DisposableStore } from '../../../../base/common/lifecycle.js';
1818
import { PlotsContainer } from './components/plotsContainer.js';
1919
import { ActionBars } from './components/actionBars.js';
2020
import { INotificationService } from '../../../../platform/notification/common/notification.js';
2121
import { PositronPlotsViewPane } from './positronPlotsView.js';
22-
import { ZoomLevel } from './components/zoomPlotMenuButton.js';
2322
import { IPreferencesService } from '../../../services/preferences/common/preferences.js';
2423

2524
/**
@@ -66,7 +65,16 @@ export const PositronPlots = (props: PropsWithChildren<PositronPlotsProps>) => {
6665
}, [props.positronPlotsService.positronPlotInstances.length, props.reactComponentContainer.height, props.reactComponentContainer.width]);
6766

6867
const zoomHandler = (zoom: number) => {
69-
setZoom(zoom);
68+
const currentPlotId = props.positronPlotsService.selectedPlotId;
69+
if (!currentPlotId) {
70+
return;
71+
}
72+
73+
const plot = props.positronPlotsService.positronPlotInstances.find(plot => plot.id === currentPlotId);
74+
if (isZoomablePlotClient(plot)) {
75+
// Update the zoom level in the plot metadata.
76+
plot.zoomLevel = zoom;
77+
}
7078
};
7179

7280
// Hooks.
@@ -129,10 +137,41 @@ export const PositronPlots = (props: PropsWithChildren<PositronPlotsProps>) => {
129137
return () => disposableStore.dispose();
130138
}, [computeHistoryVisibility, props.positronPlotsService, props.reactComponentContainer]);
131139

140+
useEffect(() => {
141+
// Set the initial zoom level for the current plot.
142+
const disposableStore = new DisposableStore();
143+
144+
disposableStore.add(props.positronPlotsService.onDidSelectPlot(plotId => {
145+
const currentPlot = props.positronPlotsService.selectedPlotId;
146+
147+
if (currentPlot) {
148+
const plot = props.positronPlotsService.positronPlotInstances.find(plot => plot.id === currentPlot);
149+
if (isZoomablePlotClient(plot)) {
150+
disposableStore.add(plot.onDidChangeZoomLevel((zoomLevel) => {
151+
setZoom(zoomLevel);
152+
}));
153+
setZoom(plot.zoomLevel);
154+
} else {
155+
setZoom(ZoomLevel.Fit);
156+
}
157+
}
158+
}));
159+
160+
return () => {
161+
// Dispose of the disposable store to clean up event handlers.
162+
disposableStore.dispose();
163+
}
164+
}, [props.positronPlotsService]);
165+
132166
// Render.
133167
return (
134168
<PositronPlotsContextProvider {...props}>
135-
<ActionBars {...props} zoomHandler={zoomHandler} zoomLevel={zoom} />
169+
<ActionBars
170+
{...props}
171+
key={props.positronPlotsService.selectedPlotId}
172+
zoomHandler={zoomHandler}
173+
zoomLevel={zoom}
174+
/>
136175
<PlotsContainer
137176
darkFilterMode={darkFilterMode}
138177
height={height > 0 ? height - 34 : 0}
@@ -142,7 +181,7 @@ export const PositronPlots = (props: PropsWithChildren<PositronPlotsProps>) => {
142181
width={width}
143182
x={posX}
144183
y={posY}
145-
zoom={zoom} />
184+
/>
146185
</PositronPlotsContextProvider>
147186
);
148187

0 commit comments

Comments
 (0)