Skip to content

Commit 1ac2559

Browse files
Attempt to move the save-to-board feature into a button instead of just magic cmd
1 parent 90cb4af commit 1ac2559

File tree

5 files changed

+125
-51
lines changed

5 files changed

+125
-51
lines changed

kernel/src/components/Dialog.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,20 @@
11
import { ConnectCard } from './ConnectCard';
22
import { ServiceContainer } from '../services/ServiceContainer';
3+
import { Card } from './Card';
4+
import { LabShell } from '@jupyterlab/application';
5+
import { NotebookPanel } from '@jupyterlab/notebook';
6+
import type { JupyterLiteServer } from '@jupyterlite/server';
37

48
export interface DialogProps {
59
closeDialog: () => void;
610
serviceContainer: ServiceContainer;
11+
app?: JupyterLiteServer;
712
}
813

914
export class Dialog {
1015
private element: HTMLDivElement;
1116
private connectCard: ConnectCard;
17+
private saveCard: Card
1218

1319
constructor(props: DialogProps) {
1420
this.element = document.createElement('div');
@@ -33,7 +39,45 @@ export class Dialog {
3339
},
3440
props.serviceContainer.deviceService);
3541

42+
this.saveCard = new Card({
43+
action: 'save',
44+
icon: '📥',
45+
title: 'Save Notebook',
46+
description: 'Save notebook to RedBoard',
47+
color: 'var(--ui-red)'
48+
},
49+
// create an arrow function that calls the save method on the serviceContainer but first iterates over all cells and gathers their code content if they are code cells
50+
async () => {
51+
// TODO: current widget might not be correct at this level, need to check and possibly change how we're getting this
52+
const notebook = props.app?.shell.currentWidget as NotebookPanel | null;
53+
var allCellContent : string = '';
54+
55+
console.log("[Dialog] saveCard: Saving notebook content...");
56+
57+
if (notebook) {
58+
console.log("[Dialog] saveCard: Found notebook:", notebook.title.label);
59+
notebook.revealed.then(() => {
60+
61+
62+
for (const cell of notebook.content.model.cells) {
63+
// TODO: nbformat has this property as 'cell_type' not 'type'
64+
// but it seems from the JupyterLab direct docs that it should be 'type'
65+
if (cell.type === 'code') {
66+
allCellContent += cell.source + '\n\n';
67+
}
68+
}
69+
70+
console.log("[Dialog] saveCard: Cells to save:", allCellContent);
71+
});
72+
}
73+
74+
return props.serviceContainer.saveCodeToDevice(allCellContent, (content) => {
75+
console.log("[Dialog] saveCard: Streaming output:", content);
76+
});
77+
});
78+
3679
optionsContainer.appendChild(this.connectCard.getElement());
80+
optionsContainer.appendChild(this.saveCard.getElement());
3781

3882
let content = document.createElement('div');
3983
content.className = 'welcome-dialog';
@@ -69,3 +113,4 @@ export class Dialog {
69113
return this.element;
70114
}
71115
}
116+

kernel/src/index.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Removed Widget import as we're no longer using @lumino/widgets
22
import { JupyterLiteServer, JupyterLiteServerPlugin } from '@jupyterlite/server';
33
import { IKernel, IKernelSpecs } from '@jupyterlite/kernel';
4+
// import { INotebookTracker, NotebookPanel } from '@jupyterlab/notebook';
45
import { EmbeddedKernel } from './kernel';
56
import WelcomePanel from './panel';
67
import { ServiceContainer } from './services/ServiceContainer';
@@ -51,7 +52,10 @@ const kernelPlugin: JupyterLiteServerPlugin<void> = {
5152
// Save the DeviceService instance so we can restore it if kernel is restarted
5253
devService = serviceContainer.deviceService;
5354

54-
const welcomePanel = new WelcomePanel(serviceContainer);
55+
const welcomePanel = new WelcomePanel(
56+
serviceContainer,
57+
app
58+
);
5559
document.body.appendChild(welcomePanel.getElement());
5660
const kernel = new EmbeddedKernel(options, serviceContainer);
5761

@@ -69,6 +73,18 @@ const kernelPlugin: JupyterLiteServerPlugin<void> = {
6973
return kernel;
7074
}
7175
});
76+
77+
// notebookTracker.currentChanged.connect((tracker, panel: NotebookPanel | null) => {
78+
// if (panel) {
79+
// console.log("Notebook changed to: ", panel.context.path);
80+
// const currentNotebookPanel = notebookTracker.currentWidget;
81+
82+
83+
// }
84+
// else{
85+
// console.log("No notebook currently active on currentChanged event.")
86+
// }
87+
// });
7288
}
7389
};
7490

kernel/src/kernel.ts

Lines changed: 4 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,6 @@ import { ServiceContainer } from './services/ServiceContainer';
44

55
const reconnectString: string = "%connect%"
66
const saveString: string = "%save%";
7-
const bootFileName: string = "main.py";
8-
const bootSavePrefix: string = `with open('${bootFileName}', 'w') as f:\n f.write('''\n`;
9-
const bootSaveSuffix: string = "\n''')\n";
107

118
export class EmbeddedKernel extends BaseKernel {
129

@@ -83,53 +80,11 @@ export class EmbeddedKernel extends BaseKernel {
8380
}
8481
if (code.includes(saveString)) {
8582
// Burn the code after the save string into the main.py file
86-
this.outputResponse(`Save command detected, saving code to ${bootFileName}...`);
83+
this.outputResponse(`Save command detected, saving code...`);
8784
const codeToSave = code.split(saveString)[1].trim();
88-
if (!codeToSave) {
89-
return {
90-
status: 'error',
91-
execution_count: this.executionCount,
92-
ename: 'ValueError',
93-
evalue: 'No code provided to save',
94-
traceback: ['Please provide code to save after the %save% command']
95-
};
96-
}
97-
// Burn the code by adding it to the main.py file. Can call executeRequest with the code to save
98-
// but append the code to write it to the main.py file at the start of the code.
99-
const bootCode = `# This code is automatically generated by the Embedded Kernel\n${codeToSave}`;
100-
const bootCodeWithSave = `${bootSavePrefix}${bootCode}${bootSaveSuffix}`;
101-
102-
// Execute the command to save the main.py file
103-
if (!this.serviceContainer.deviceService.getTransport()) {
104-
console.log(`[Kernel] executeRequest - No transport available for saving ${bootFileName}`);
105-
return {
106-
status: 'error',
107-
execution_count: this.executionCount,
108-
ename: 'TransportError',
109-
evalue: `No transport available to save ${bootFileName}`,
110-
traceback: ['Please connect a device first']
111-
};
112-
}
113-
const result = await this.serviceContainer.consoleService.executeCommand(bootCodeWithSave, (content) => {
114-
console.log("[Kernel] executeRequest - Streaming output:", content);
115-
this.stream(content);
116-
});
117-
if (!result.success) {
118-
console.log("[Kernel] executeRequest - Command execution failed:", result.error);
119-
return {
120-
status: 'error',
121-
execution_count: this.executionCount,
122-
ename: 'ExecutionError',
123-
evalue: result.error || 'Unknown error',
124-
traceback: [result.error || 'Unknown error']
125-
};
126-
}
127-
console.log(`[Kernel] executeRequest - Code saved to ${bootFileName} successfully`);
128-
return {
129-
status: 'ok',
130-
execution_count: this.executionCount,
131-
user_expressions: {},
132-
};
85+
// pass the "stream" to the saveCodeToDevice method
86+
const result = await this.serviceContainer.saveCodeToDevice(codeToSave, this.outputResponse.bind(this));
87+
return result;
13388
}
13489

13590
try {

kernel/src/panel.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { globalStyles, animations, overlayStyles, dialogStyles, minimizedStyles,
22
import { MinimizedButton } from './components/MinimizedButton';
33
import { Dialog } from './components/Dialog';
44
import { ServiceContainer } from './services/ServiceContainer';
5+
import type { JupyterLiteServer } from '@jupyterlite/server';
56

67
class DialogPanel {
78
private element: HTMLDivElement;
@@ -91,7 +92,10 @@ export default class WelcomePanel {
9192
private minimizedPanel: MinimizedPanel;
9293
private dialogPanel: DialogPanel;
9394

94-
constructor(private serviceContainer: ServiceContainer) {
95+
constructor(
96+
private serviceContainer: ServiceContainer,
97+
private app?: JupyterLiteServer
98+
) {
9599

96100
this.element = document.createElement('div');
97101
this.element.id = 'jp-kernel-welcome-panel';
@@ -115,6 +119,7 @@ export default class WelcomePanel {
115119
const dialog = new Dialog({
116120
closeDialog: () => this.hide(),
117121
serviceContainer: this.serviceContainer,
122+
app: this.app,
118123
});
119124
this.dialogPanel = new DialogPanel(dialog);
120125

kernel/src/services/ServiceContainer.ts

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@ import { ConsoleService } from './ConsoleService';
33
import { FirmwareService } from './FirmwareService';
44
import { FlashService } from './FlashService';
55

6+
const bootFileName: string = "main.py";
7+
const bootSavePrefix: string = `with open('${bootFileName}', 'w') as f:\n f.write('''\n`;
8+
const bootSaveSuffix: string = "\n''')\n";
9+
610
export class ServiceContainer {
711

812
private _deviceService: DeviceService;
@@ -44,4 +48,53 @@ export class ServiceContainer {
4448
public get flashService(): FlashService {
4549
return this._flashService;
4650
}
51+
52+
public async saveCodeToDevice(
53+
codeToSave: string,
54+
stream: (content: string) => void
55+
): Promise<{ status: string; execution_count: number; ename?: string; evalue?: string; traceback?: string[] }> {
56+
if (!codeToSave) {
57+
return {
58+
status: 'error',
59+
execution_count: 0,
60+
ename: 'ValueError',
61+
evalue: 'No code provided to save',
62+
traceback: ['Please provide code to save after the %save% command']
63+
};
64+
}
65+
66+
const bootCode = `# This code is automatically generated by the Embedded Kernel\n${codeToSave}`;
67+
const bootCodeWithSave = `${bootSavePrefix}${bootCode}${bootSaveSuffix}`;
68+
69+
if (!this._deviceService.getTransport()) {
70+
console.log(`[ServiceContainer] saveCodeToDevice - No transport available for saving`);
71+
return {
72+
status: 'error',
73+
execution_count: 0,
74+
ename: 'TransportError',
75+
evalue: 'No transport available to save code',
76+
traceback: ['Please connect a device first']
77+
};
78+
}
79+
80+
const result = await this._consoleService.executeCommand(bootCodeWithSave, stream);
81+
82+
if (!result.success) {
83+
console.log("[ServiceContainer] saveCodeToDevice - Command execution failed:", result.error);
84+
return {
85+
status: 'error',
86+
execution_count: 0,
87+
ename: 'ExecutionError',
88+
evalue: result.error || 'Unknown error',
89+
traceback: [result.error || 'Unknown error']
90+
};
91+
}
92+
93+
console.log(`[ServiceContainer] saveCodeToDevice - Code saved successfully`);
94+
return {
95+
status: 'ok',
96+
execution_count: 0,
97+
user_expressions: {},
98+
};
99+
}
47100
}

0 commit comments

Comments
 (0)