Skip to content

Commit 74fd52a

Browse files
authored
Add plot thumbnail cache feature so plots pane shows thumbnails after a reload (#7752)
Addresses #397 by adding a thumbnail cache feature to plots. Now, when Positron is reloaded, the Plots pane will show thumbnails for all the current plots.
1 parent 09ec2df commit 74fd52a

File tree

5 files changed

+123
-23
lines changed

5 files changed

+123
-23
lines changed

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

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import React, { useEffect, useState } from 'react';
88

99
// Other dependencies.
1010
import { PlaceholderThumbnail } from './placeholderThumbnail.js';
11+
import { usePositronPlotsContext } from '../positronPlotsContext.js';
1112
import { PlotClientInstance } from '../../../../services/languageRuntime/common/languageRuntimePlotClient.js';
1213

1314
/**
@@ -24,21 +25,25 @@ interface DynamicPlotThumbnailProps {
2425
* @returns The rendered component.
2526
*/
2627
export const DynamicPlotThumbnail = (props: DynamicPlotThumbnailProps) => {
27-
28-
const [uri, setUri] = useState('');
29-
30-
useEffect(() => {
31-
// If the plot is already rendered, show the URI; otherwise, wait for
32-
// the plot to render.
28+
const context = usePositronPlotsContext();
29+
const [uri, setUri] = useState(() => {
30+
// If the plot is already rendered, set the URI; otherwise, try to use the cached URI until
31+
// the plot is rendered.
3332
if (props.plotClient.lastRender) {
34-
setUri(props.plotClient.lastRender.uri);
33+
return props.plotClient.lastRender.uri;
34+
} else {
35+
return context.positronPlotsService.getCachedPlotThumbnailURI(props.plotClient.id);
3536
}
37+
});
3638

39+
useEffect(() => {
3740
// When the plot is rendered, update the URI. This can happen multiple times if the plot
3841
// is resized.
39-
props.plotClient.onDidCompleteRender((result) => {
42+
const disposable = props.plotClient.onDidCompleteRender(result => {
4043
setUri(result.uri);
4144
});
45+
46+
return () => disposable.dispose();
4247
}, [props.plotClient]);
4348

4449
// If the plot is not yet rendered yet (no URI), show a placeholder;

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

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,9 @@
77
import React, { useEffect, useState } from 'react';
88

99
// Other dependencies.
10-
import { PlaceholderThumbnail } from './placeholderThumbnail.js';
1110
import { WebviewPlotClient } from '../webviewPlotClient.js';
11+
import { PlaceholderThumbnail } from './placeholderThumbnail.js';
12+
import { usePositronPlotsContext } from '../positronPlotsContext.js';
1213

1314
/**
1415
* WebviewPlotThumbnailProps interface.
@@ -25,25 +26,26 @@ interface WebviewPlotThumbnailProps {
2526
* @returns The rendered component.
2627
*/
2728
export const WebviewPlotThumbnail = (props: WebviewPlotThumbnailProps) => {
28-
29-
const [uri, setUri] = useState('');
30-
31-
useEffect(() => {
32-
// If the plot is already rendered, show the URI; otherwise, wait for
33-
// the plot to render.
29+
const context = usePositronPlotsContext();
30+
const [uri, setUri] = useState(() => {
31+
// If the plot is already rendered, set the URI; otherwise, try to use the cached URI until
32+
// the plot is rendered.
3433
if (props.plotClient.thumbnailUri) {
35-
setUri(props.plotClient.thumbnailUri);
34+
return props.plotClient.thumbnailUri;
35+
} else {
36+
return context.positronPlotsService.getCachedPlotThumbnailURI(props.plotClient.id);
3637
}
38+
});
3739

40+
useEffect(() => {
3841
// When the plot is rendered, update the URI. This can happen multiple times if the plot
3942
// is resized.
4043
const disposable = props.plotClient.onDidRenderThumbnail((result) => {
4144
setUri(result);
4245
});
43-
return () => {
44-
disposable.dispose();
45-
};
46-
}, [props.plotClient]);
46+
47+
return () => disposable.dispose();
48+
}, [context.positronPlotsService, props.plotClient]);
4749

4850
// If the plot is not yet rendered yet (no URI), show a placeholder;
4951
// otherwise, show the rendered thumbnail.

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

Lines changed: 80 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,9 @@ const WebviewPlotInactiveTimeout = 120_000;
6767
/** Interval in milliseconds at which inactive webview plots are checked. */
6868
const WebviewPlotInactiveInterval = 1_000;
6969

70+
/** The key used to store the cached plot thumbnail descriptors */
71+
const CachedPlotThumbnailDescriptorsKey = 'positron.plots.cachedPlotThumbnailDescriptors';
72+
7073
/** The key used to store the preferred history policy */
7174
const HistoryPolicyStorageKey = 'positron.plots.historyPolicy';
7275

@@ -86,12 +89,23 @@ interface DataUri {
8689
}
8790

8891
/**
89-
* PositronPlotsService class.
92+
* ICachedPlotThumbnailDescriptor interface.
9093
*/
94+
interface ICachedPlotThumbnailDescriptor {
95+
readonly plotClientId: string;
96+
readonly thumbnailURI: string;
97+
}
98+
99+
/**
100+
* PositronPlotsService class.
101+
*/
91102
export class PositronPlotsService extends Disposable implements IPositronPlotsService {
92103
/** Needed for service branding in dependency injector. */
93104
declare readonly _serviceBrand: undefined;
94105

106+
/** The map of cached plot thumbnail descriptors. */
107+
private readonly _cachedPlotThumbnailDescriptors = new Map<string, ICachedPlotThumbnailDescriptor>();
108+
95109
/** The list of Positron plots. */
96110
private readonly _plots: IPositronPlotClient[] = [];
97111

@@ -224,8 +238,7 @@ export class PositronPlotsService extends Disposable implements IPositronPlotsSe
224238
await this.registerWebviewPlotClient(plotClient);
225239
}));
226240

227-
// When the storage service is about to save state, store the current history policy
228-
// and storage policy in the workspace storage.
241+
// When the storage service is about to save state, store policies and cached plot thumbnail descriptors.
229242
this._register(this._storageService.onWillSaveState(() => {
230243
this._storageService.store(
231244
HistoryPolicyStorageKey,
@@ -254,6 +267,45 @@ export class PositronPlotsService extends Disposable implements IPositronPlotsSe
254267
StorageScope.WORKSPACE,
255268
StorageTarget.MACHINE);
256269
}
270+
271+
// Enumerate the plot clients and update the cached plot thumbnail descriptors.
272+
const keysToDelete: Set<string> = new Set(this._cachedPlotThumbnailDescriptors.keys());
273+
this._plots.forEach(plotClient => {
274+
keysToDelete.delete(plotClient.id);
275+
if (plotClient instanceof PlotClientInstance) {
276+
if (plotClient.lastRender?.uri) {
277+
this._cachedPlotThumbnailDescriptors.set(plotClient.id, {
278+
plotClientId: plotClient.id,
279+
thumbnailURI: plotClient.lastRender.uri
280+
});
281+
}
282+
} else if (plotClient instanceof HtmlPlotClient) {
283+
if (plotClient.thumbnailUri) {
284+
this._cachedPlotThumbnailDescriptors.set(plotClient.id, {
285+
plotClientId: plotClient.id,
286+
thumbnailURI: plotClient.thumbnailUri
287+
});
288+
}
289+
}
290+
});
291+
292+
// Delete any cached plot thumbnail descriptors that are no longer valid.
293+
keysToDelete.forEach(key => this._cachedPlotThumbnailDescriptors.delete(key));
294+
295+
// Update the cached plot thumbnail descriptors in workspace storage.
296+
if (this._cachedPlotThumbnailDescriptors.size) {
297+
this._storageService.store(
298+
CachedPlotThumbnailDescriptorsKey,
299+
JSON.stringify([...this._cachedPlotThumbnailDescriptors.values()]),
300+
StorageScope.WORKSPACE,
301+
StorageTarget.MACHINE);
302+
} else {
303+
this._storageService.store(
304+
CachedPlotThumbnailDescriptorsKey,
305+
undefined,
306+
StorageScope.WORKSPACE,
307+
StorageTarget.MACHINE);
308+
}
257309
}));
258310

259311
// Listen for changes to the dark mode configuration
@@ -331,6 +383,22 @@ export class PositronPlotsService extends Disposable implements IPositronPlotsSe
331383
this._selectedHistoryPolicy = preferredHistoryPolicy as HistoryPolicy;
332384
}
333385

386+
// Load the cached plot thumbnail descriptors from workspace storage.
387+
const cachedPlotThumbnailDescriptorsJSON = this._storageService.get(CachedPlotThumbnailDescriptorsKey, StorageScope.WORKSPACE);
388+
if (cachedPlotThumbnailDescriptorsJSON) {
389+
try {
390+
// Parse the cached plot thumbnail descriptors.
391+
const cachedPlotThumbnailDescriptors = JSON.parse(cachedPlotThumbnailDescriptorsJSON) as ICachedPlotThumbnailDescriptor[];
392+
393+
// Initialize the cached plot thumbnail descriptors.
394+
for (const cachedPlotThumbnailDescriptor of cachedPlotThumbnailDescriptors) {
395+
this._cachedPlotThumbnailDescriptors.set(cachedPlotThumbnailDescriptor.plotClientId, cachedPlotThumbnailDescriptor);
396+
}
397+
} catch (error) {
398+
this._logService.error(`Error parsing cached plot thumbnail descriptors: ${error}`);
399+
}
400+
}
401+
334402
// When a plot is selected, update its last selected time.
335403
this._register(this._onDidSelectPlot.event(async (id) => {
336404
this._lastSelectedTimeByPlotId.set(id, Date.now());
@@ -427,6 +495,15 @@ export class PositronPlotsService extends Disposable implements IPositronPlotsSe
427495
return this._selectedDarkFilterMode;
428496
}
429497

498+
/**
499+
* Gets the cached plot thumbnail URI for a given plot ID.
500+
* @param plotId The plot ID to get the thumbnail URI for.
501+
* @returns The thumbnail URI for the plot, or undefined if not found.
502+
*/
503+
getCachedPlotThumbnailURI(plotId: string) {
504+
return this._cachedPlotThumbnailDescriptors.get(plotId)?.thumbnailURI;
505+
}
506+
430507
/**
431508
* Selects a new sizing policy and fires an event indicating that the policy
432509
* has changed.

src/vs/workbench/services/positronPlots/common/positronPlots.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,13 @@ export interface IPositronPlotsService {
164164
*/
165165
readonly onDidChangeSizingPolicy: Event<IPositronPlotSizingPolicy>;
166166

167+
/**
168+
* Gets the cached plot thumbnail URI for a given plot ID.
169+
* @param plotId The plot ID to get the thumbnail URI for.
170+
* @returns The thumbnail URI for the plot, or undefined if not found.
171+
*/
172+
getCachedPlotThumbnailURI(plotId: string): string | undefined;
173+
167174
/**
168175
* Selects the plot with the specified ID.
169176
*

src/vs/workbench/services/positronPlots/test/common/testPositronPlotsService.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,15 @@ export class TestPositronPlotsService extends Disposable implements IPositronPlo
208208
*/
209209
readonly onDidChangeSizingPolicy = this._onDidChangeSizingPolicyEmitter.event;
210210

211+
/**
212+
* Gets the cached plot thumbnail URI for a given plot ID.
213+
* @param plotId The plot ID to get the thumbnail URI for.
214+
* @returns The thumbnail URI for the plot, or undefined if not found.
215+
*/
216+
getCachedPlotThumbnailURI(plotId: string) {
217+
// Noop in test implementation. In a real implementation, this would return the URI of the cached thumbnail.
218+
return undefined;
219+
}
211220

212221
/**
213222
* Selects the plot with the specified ID.

0 commit comments

Comments
 (0)