@marimo-team/islands 0.19.5-dev40 → 0.19.5-dev43
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 +11 -8
- package/package.json +1 -1
- package/src/components/ai/__tests__/ai-utils.test.ts +276 -0
- package/src/components/ai/ai-utils.ts +101 -0
- package/src/components/app-config/ai-config.tsx +56 -16
- package/src/components/app-config/user-config-form.tsx +29 -0
- package/src/components/chat/chat-panel.tsx +3 -3
- package/src/components/editor/actions/useCellActionButton.tsx +2 -2
- package/src/components/editor/actions/useNotebookActions.tsx +18 -10
- package/src/components/editor/renderers/vertical-layout/vertical-layout.tsx +1 -1
- package/src/core/ai/model-registry.ts +21 -3
- package/src/core/export/hooks.ts +7 -11
- package/src/utils/__tests__/download.test.tsx +398 -2
- package/src/utils/download.ts +107 -6
- package/src/components/export/export-output-button.tsx +0 -14
package/src/utils/download.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
2
|
import { toPng } from "html-to-image";
|
|
3
3
|
import { toast } from "@/components/ui/use-toast";
|
|
4
|
+
import { type CellId, CellOutputId } from "@/core/cells/ids";
|
|
4
5
|
import { getRequestClient } from "@/core/network/requests";
|
|
5
6
|
import { Filenames } from "@/utils/filenames";
|
|
6
7
|
import { Paths } from "@/utils/paths";
|
|
7
8
|
import { prettyError } from "./errors";
|
|
9
|
+
import { Logger } from "./Logger";
|
|
8
10
|
|
|
9
11
|
/**
|
|
10
12
|
* Show a loading toast while an async operation is in progress.
|
|
@@ -16,6 +18,7 @@ export async function withLoadingToast<T>(
|
|
|
16
18
|
): Promise<T> {
|
|
17
19
|
const loadingToast = toast({
|
|
18
20
|
title,
|
|
21
|
+
duration: Infinity,
|
|
19
22
|
});
|
|
20
23
|
try {
|
|
21
24
|
const result = await fn();
|
|
@@ -27,15 +30,111 @@ export async function withLoadingToast<T>(
|
|
|
27
30
|
}
|
|
28
31
|
}
|
|
29
32
|
|
|
30
|
-
|
|
31
|
-
element
|
|
33
|
+
function findElementForCell(cellId: CellId): HTMLElement | undefined {
|
|
34
|
+
const element = document.getElementById(CellOutputId.create(cellId));
|
|
35
|
+
if (!element) {
|
|
36
|
+
Logger.error(`Output element not found for cell ${cellId}`);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
return element;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Reference counter for body.printing class to handle concurrent screenshot captures.
|
|
44
|
+
* Only adds the class when count goes 0→1, only removes when count goes 1→0.
|
|
45
|
+
*/
|
|
46
|
+
let bodyPrintingRefCount = 0;
|
|
47
|
+
|
|
48
|
+
function acquireBodyPrinting() {
|
|
49
|
+
bodyPrintingRefCount++;
|
|
50
|
+
if (bodyPrintingRefCount === 1) {
|
|
51
|
+
document.body.classList.add("printing");
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function releaseBodyPrinting() {
|
|
56
|
+
bodyPrintingRefCount--;
|
|
57
|
+
if (bodyPrintingRefCount === 0) {
|
|
58
|
+
document.body.classList.remove("printing");
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/*
|
|
63
|
+
* Prepare a cell element for screenshot capture.
|
|
64
|
+
* Returns a cleanup function that should be called when the screenshot is complete.
|
|
65
|
+
*/
|
|
66
|
+
function prepareCellElementForScreenshot(element: HTMLElement) {
|
|
67
|
+
element.classList.add("printing-output");
|
|
68
|
+
acquireBodyPrinting();
|
|
69
|
+
const originalOverflow = element.style.overflow;
|
|
70
|
+
element.style.overflow = "auto";
|
|
71
|
+
|
|
72
|
+
return () => {
|
|
73
|
+
element.classList.remove("printing-output");
|
|
74
|
+
releaseBodyPrinting();
|
|
75
|
+
element.style.overflow = originalOverflow;
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Capture a cell output as a PNG data URL.
|
|
81
|
+
*/
|
|
82
|
+
export async function getImageDataUrlForCell(
|
|
83
|
+
cellId: CellId,
|
|
84
|
+
): Promise<string | undefined> {
|
|
85
|
+
const element = findElementForCell(cellId);
|
|
86
|
+
if (!element) {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
const cleanup = prepareCellElementForScreenshot(element);
|
|
90
|
+
|
|
91
|
+
try {
|
|
92
|
+
return await toPng(element);
|
|
93
|
+
} finally {
|
|
94
|
+
cleanup();
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Download a cell output as a PNG image file.
|
|
100
|
+
*/
|
|
101
|
+
export async function downloadCellOutputAsImage(
|
|
102
|
+
cellId: CellId,
|
|
32
103
|
filename: string,
|
|
33
104
|
) {
|
|
105
|
+
const element = findElementForCell(cellId);
|
|
106
|
+
if (!element) {
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
await downloadHTMLAsImage({
|
|
111
|
+
element,
|
|
112
|
+
filename,
|
|
113
|
+
prepare: prepareCellElementForScreenshot,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export async function downloadHTMLAsImage(opts: {
|
|
118
|
+
element: HTMLElement;
|
|
119
|
+
filename: string;
|
|
120
|
+
prepare?: (element: HTMLElement) => () => void;
|
|
121
|
+
}) {
|
|
122
|
+
const { element, filename, prepare } = opts;
|
|
123
|
+
|
|
34
124
|
// Capture current scroll position
|
|
35
125
|
const appEl = document.getElementById("App");
|
|
36
126
|
const currentScrollY = appEl?.scrollTop ?? 0;
|
|
37
|
-
|
|
38
|
-
|
|
127
|
+
|
|
128
|
+
let cleanup: (() => void) | undefined;
|
|
129
|
+
if (prepare) {
|
|
130
|
+
// Let the prepare function handle adding classes (e.g., body.printing)
|
|
131
|
+
cleanup = prepare(element);
|
|
132
|
+
} else {
|
|
133
|
+
// When no prepare function is provided (e.g., downloading full notebook),
|
|
134
|
+
// add body.printing ourselves
|
|
135
|
+
document.body.classList.add("printing");
|
|
136
|
+
}
|
|
137
|
+
|
|
39
138
|
try {
|
|
40
139
|
// Get screenshot
|
|
41
140
|
const dataUrl = await toPng(element);
|
|
@@ -47,8 +146,10 @@ export async function downloadHTMLAsImage(
|
|
|
47
146
|
variant: "danger",
|
|
48
147
|
});
|
|
49
148
|
} finally {
|
|
50
|
-
|
|
51
|
-
document.body.classList.
|
|
149
|
+
cleanup?.();
|
|
150
|
+
if (document.body.classList.contains("printing")) {
|
|
151
|
+
document.body.classList.remove("printing");
|
|
152
|
+
}
|
|
52
153
|
// Restore scroll position
|
|
53
154
|
requestAnimationFrame(() => {
|
|
54
155
|
appEl?.scrollTo(0, currentScrollY);
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
-
|
|
3
|
-
import { type CellId, CellOutputId } from "@/core/cells/ids";
|
|
4
|
-
import { downloadHTMLAsImage } from "@/utils/download";
|
|
5
|
-
|
|
6
|
-
export function downloadCellOutput(cellId: CellId) {
|
|
7
|
-
const output = document.getElementById(CellOutputId.create(cellId));
|
|
8
|
-
if (output) {
|
|
9
|
-
output.classList.add("printing-output");
|
|
10
|
-
downloadHTMLAsImage(output, "result.png").finally(() => {
|
|
11
|
-
output.classList.remove("printing-output");
|
|
12
|
-
});
|
|
13
|
-
}
|
|
14
|
-
}
|