@marimo-team/islands 0.20.5-dev15 → 0.20.5-dev17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/main.js CHANGED
@@ -58409,6 +58409,7 @@ ${r}
58409
58409
  toPNG: (e) => Filenames.replace(e, "png"),
58410
58410
  toPDF: (e) => Filenames.replace(e, "pdf"),
58411
58411
  toPY: (e) => Filenames.replace(e, "py"),
58412
+ toIPYNB: (e) => Filenames.replace(e, "ipynb"),
58412
58413
  withoutExtension: (e) => {
58413
58414
  let r = e.split(".");
58414
58415
  return r.length === 1 ? e : r.slice(0, -1).join(".");
@@ -70403,7 +70404,7 @@ Image URL: ${r.imageUrl}`)), contextToXml({
70403
70404
  return Logger.warn("Failed to get version from mount config"), null;
70404
70405
  }
70405
70406
  }
70406
- const marimoVersionAtom = atom(getVersionFromMountConfig() || "0.20.5-dev15"), showCodeInRunModeAtom = atom(true);
70407
+ const marimoVersionAtom = atom(getVersionFromMountConfig() || "0.20.5-dev17"), showCodeInRunModeAtom = atom(true);
70407
70408
  atom(null);
70408
70409
  var import_compiler_runtime$88 = require_compiler_runtime();
70409
70410
  function useKeydownOnElement(e, r) {
@@ -101072,6 +101073,7 @@ ${r}
101072
101073
  __publicField(this, "sendFileDetails", throwNotImplemented);
101073
101074
  __publicField(this, "openTutorial", throwNotImplemented);
101074
101075
  __publicField(this, "exportAsHTML", throwNotImplemented);
101076
+ __publicField(this, "exportAsIPYNB", throwNotImplemented);
101075
101077
  __publicField(this, "exportAsMarkdown", throwNotImplemented);
101076
101078
  __publicField(this, "exportAsPDF", throwNotImplemented);
101077
101079
  __publicField(this, "autoExportAsHTML", throwNotImplemented);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marimo-team/islands",
3
- "version": "0.20.5-dev15",
3
+ "version": "0.20.5-dev17",
4
4
  "main": "dist/main.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "type": "module",
@@ -59,6 +59,7 @@ export const MockRequestClient = {
59
59
  getRunningNotebooks: vi.fn().mockResolvedValue({ files: [] }),
60
60
  shutdownSession: vi.fn().mockResolvedValue({}),
61
61
  exportAsHTML: vi.fn().mockResolvedValue({ html: "" }),
62
+ exportAsIPYNB: vi.fn().mockResolvedValue(""),
62
63
  exportAsMarkdown: vi.fn().mockResolvedValue({ markdown: "" }),
63
64
  exportAsPDF: vi.fn().mockResolvedValue(new Blob()),
64
65
  autoExportAsHTML: vi.fn().mockResolvedValue({}),
@@ -31,6 +31,7 @@ import {
31
31
  LayoutTemplateIcon,
32
32
  LinkIcon,
33
33
  MessagesSquareIcon,
34
+ NotebookIcon,
34
35
  PanelLeftIcon,
35
36
  PowerSquareIcon,
36
37
  PresentationIcon,
@@ -123,8 +124,13 @@ export function useNotebookActions() {
123
124
  const setCommandPaletteOpen = useSetAtom(commandPaletteAtom);
124
125
  const setSettingsDialogOpen = useSetAtom(settingDialogAtom);
125
126
  const setKeyboardShortcutsOpen = useSetAtom(keyboardShortcutsAtom);
126
- const { exportAsMarkdown, readCode, saveCellConfig, updateCellOutputs } =
127
- useRequestClient();
127
+ const {
128
+ exportAsIPYNB,
129
+ exportAsMarkdown,
130
+ readCode,
131
+ saveCellConfig,
132
+ updateCellOutputs,
133
+ } = useRequestClient();
128
134
  const takeScreenshots = useEnrichCellOutputs();
129
135
 
130
136
  const hasDisabledCells = useAtomValue(hasDisabledCellsAtom);
@@ -199,6 +205,27 @@ export function useNotebookActions() {
199
205
  setTimeout(() => window.dispatchEvent(afterprint), 0);
200
206
  };
201
207
 
208
+ const handleDownloadAsIPYNB = async () => {
209
+ if (!filename) {
210
+ toastNotebookMustBeNamed();
211
+ return;
212
+ }
213
+
214
+ const runDownload = async (progress: ProgressState) => {
215
+ await updateCellOutputsWithScreenshots({
216
+ takeScreenshots: () => takeScreenshots({ progress }),
217
+ updateCellOutputs,
218
+ });
219
+ const ipynb = await exportAsIPYNB({ download: false });
220
+ downloadBlob(
221
+ new Blob([ipynb], { type: "application/x-ipynb+json" }),
222
+ Filenames.toIPYNB(document.title),
223
+ );
224
+ };
225
+
226
+ await withLoadingToast("Downloading IPYNB...", runDownload);
227
+ };
228
+
202
229
  const actions: ActionButton[] = [
203
230
  {
204
231
  icon: <DownloadIcon size={14} strokeWidth={1.5} />,
@@ -240,6 +267,11 @@ export function useNotebookActions() {
240
267
  );
241
268
  },
242
269
  },
270
+ {
271
+ icon: <NotebookIcon size={14} strokeWidth={1.5} />,
272
+ label: "Download as ipynb",
273
+ handle: handleDownloadAsIPYNB,
274
+ },
243
275
  {
244
276
  icon: <CodeIcon size={14} strokeWidth={1.5} />,
245
277
  label: "Download Python code",
@@ -181,6 +181,7 @@ export class IslandsPyodideBridge implements RunRequests, EditRequests {
181
181
  sendFileDetails = throwNotImplemented;
182
182
  openTutorial = throwNotImplemented;
183
183
  exportAsHTML = throwNotImplemented;
184
+ exportAsIPYNB = throwNotImplemented;
184
185
  exportAsMarkdown = throwNotImplemented;
185
186
  exportAsPDF = throwNotImplemented;
186
187
  autoExportAsHTML = throwNotImplemented;
@@ -113,5 +113,22 @@ describe("createNetworkRequests", () => {
113
113
  }),
114
114
  );
115
115
  });
116
+
117
+ it("exportAsIPYNB should call the new endpoint as text", async () => {
118
+ const requests = createNetworkRequests();
119
+ await requests.exportAsIPYNB({
120
+ download: false,
121
+ } as any);
122
+
123
+ expect(mockClient.POST).toHaveBeenCalledWith(
124
+ "/api/export/ipynb",
125
+ expect.objectContaining({
126
+ body: expect.objectContaining({
127
+ download: false,
128
+ }),
129
+ parseAs: "text",
130
+ }),
131
+ );
132
+ });
116
133
  });
117
134
  });
@@ -49,6 +49,7 @@ const ACTIONS: Record<keyof AllRequests, Action> = {
49
49
 
50
50
  // Export operations start a connection
51
51
  exportAsHTML: "startConnection",
52
+ exportAsIPYNB: "startConnection",
52
53
  exportAsMarkdown: "startConnection",
53
54
  exportAsPDF: "startConnection",
54
55
  readCode: "startConnection",
@@ -381,6 +381,15 @@ export function createNetworkRequests(): EditRequests & RunRequests {
381
381
  })
382
382
  .then(handleResponse);
383
383
  },
384
+ exportAsIPYNB: async (request) => {
385
+ return getClient()
386
+ .POST("/api/export/ipynb", {
387
+ body: request,
388
+ parseAs: "text",
389
+ params: getParams(),
390
+ })
391
+ .then(handleResponse);
392
+ },
384
393
  exportAsPDF: async (request) => {
385
394
  return getClient()
386
395
  .POST("/api/export/pdf", {
@@ -75,6 +75,7 @@ export function createStaticRequests(): EditRequests & RunRequests {
75
75
  getRunningNotebooks: throwNotInEditMode,
76
76
  shutdownSession: throwNotInEditMode,
77
77
  exportAsHTML: throwNotInEditMode,
78
+ exportAsIPYNB: throwNotInEditMode,
78
79
  exportAsMarkdown: throwNotInEditMode,
79
80
  exportAsPDF: throwNotInEditMode,
80
81
  autoExportAsHTML: throwNotInEditMode,
@@ -60,6 +60,7 @@ export function createErrorToastingRequests(
60
60
  getRunningNotebooks: "Failed to get running notebooks",
61
61
  shutdownSession: "Failed to shutdown session",
62
62
  exportAsHTML: "Failed to export HTML",
63
+ exportAsIPYNB: "Failed to export ipynb",
63
64
  exportAsMarkdown: "Failed to export Markdown",
64
65
  exportAsPDF: "Failed to export PDF",
65
66
  autoExportAsHTML: "", // No toast
@@ -180,6 +180,7 @@ export interface EditRequests {
180
180
  ) => Promise<RunningNotebooksResponse>;
181
181
  // Export requests
182
182
  exportAsHTML: (request: ExportAsHTMLRequest) => Promise<string>;
183
+ exportAsIPYNB: (request: ExportAsIPYNBRequest) => Promise<string>;
183
184
  exportAsMarkdown: (request: ExportAsMarkdownRequest) => Promise<string>;
184
185
  exportAsPDF: (request: ExportAsPDFRequest) => Promise<Blob>;
185
186
  autoExportAsHTML: (request: ExportAsHTMLRequest) => Promise<null>;
@@ -587,6 +587,7 @@ export class PyodideBridge implements RunRequests, EditRequests {
587
587
  getWorkspaceFiles = throwNotImplemented;
588
588
  getRunningNotebooks = throwNotImplemented;
589
589
  shutdownSession = throwNotImplemented;
590
+ exportAsIPYNB = throwNotImplemented;
590
591
  exportAsPDF = throwNotImplemented;
591
592
  autoExportAsHTML = throwNotImplemented;
592
593
  autoExportAsMarkdown = throwNotImplemented;
@@ -23,6 +23,12 @@ describe("Filenames", () => {
23
23
  expect(Filenames.toPNG("test.foo.py")).toEqual("test.foo.png");
24
24
  });
25
25
 
26
+ it("should convert filename to ipynb", () => {
27
+ expect(Filenames.toIPYNB("test")).toEqual("test.ipynb");
28
+ expect(Filenames.toIPYNB("test.txt")).toEqual("test.ipynb");
29
+ expect(Filenames.toIPYNB("test.foo.py")).toEqual("test.foo.ipynb");
30
+ });
31
+
26
32
  it("should remove extension from filename", () => {
27
33
  expect(Filenames.withoutExtension("test")).toEqual("test");
28
34
  expect(Filenames.withoutExtension("test.txt")).toEqual("test");
@@ -39,6 +45,7 @@ describe("Filenames", () => {
39
45
  expect(Filenames.toHTML(filename)).toEqual(`${withoutExt}.html`);
40
46
  expect(Filenames.toPNG(filename)).toEqual(`${withoutExt}.png`);
41
47
  expect(Filenames.toPY(filename)).toEqual(`${withoutExt}.py`);
48
+ expect(Filenames.toIPYNB(filename)).toEqual(`${withoutExt}.ipynb`);
42
49
 
43
50
  // Ensure operations preserve unicode and special characters in base name
44
51
  expect(withoutExt).not.toEqual("");
@@ -15,6 +15,9 @@ export const Filenames = {
15
15
  toPY: (filename: string): string => {
16
16
  return Filenames.replace(filename, "py");
17
17
  },
18
+ toIPYNB: (filename: string): string => {
19
+ return Filenames.replace(filename, "ipynb");
20
+ },
18
21
  withoutExtension: (filename: string): string => {
19
22
  // Just remove the last extension
20
23
  const parts = filename.split(".");