@marimo-team/frontend 0.19.7-dev27 → 0.19.7-dev30
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/assets/{CellStatus-oBL2iale.js → CellStatus-DpVrMkoX.js} +1 -1
- package/dist/assets/{JsonOutput-BKP4rBIw.js → JsonOutput-DqRUfM9x.js} +2 -2
- package/dist/assets/{MarimoErrorOutput-cw9gEb4T.js → MarimoErrorOutput-CI9ZOqK5.js} +1 -1
- package/dist/assets/{RenderHTML-Bgz4e362.js → RenderHTML-DCy5wLx9.js} +1 -1
- package/dist/assets/{add-cell-with-ai-DNbX7Ctg.js → add-cell-with-ai-1AcZwwMS.js} +1 -1
- package/dist/assets/{add-database-form-CP39qGit.js → add-database-form-B45yZ_nV.js} +1 -1
- package/dist/assets/{agent-panel-Bak-DtIc.js → agent-panel-BuwVy_u2.js} +1 -1
- package/dist/assets/{ai-model-dropdown-Dpr0DUJN.js → ai-model-dropdown-Ce9P2gAN.js} +1 -1
- package/dist/assets/{app-config-button-CAaYaq0L.js → app-config-button-V0ZGbZwL.js} +1 -1
- package/dist/assets/{cell-editor-B6jD1bv8.js → cell-editor-Bs8pOtdM.js} +1 -1
- package/dist/assets/{cell-link-CyIWDVXR.js → cell-link-Draa5Cqg.js} +1 -1
- package/dist/assets/{cells-DRiwSVs0.js → cells-D3A832Pq.js} +55 -55
- package/dist/assets/{chat-components-DDIZD9FU.js → chat-components-BIikypOv.js} +1 -1
- package/dist/assets/{chat-display-D-Wa-1Hv.js → chat-display-C9Ma_Z24.js} +1 -1
- package/dist/assets/{chat-panel-RtFQiyUV.js → chat-panel-hwTI5twF.js} +1 -1
- package/dist/assets/{column-preview-C72fVZoP.js → column-preview-BI7AO_9o.js} +1 -1
- package/dist/assets/{command-Bq3e85NA.js → command-BmRMpmeq.js} +1 -1
- package/dist/assets/{command-palette-D4NcN7PV.js → command-palette-Bf5XRuku.js} +1 -1
- package/dist/assets/{common-CeF2TOUZ.js → common-DhAkBsKv.js} +1 -1
- package/dist/assets/{datasource-AZ3l2P48.js → datasource-DH4rSucG.js} +1 -1
- package/dist/assets/{dependency-graph-panel-i3yTswTN.js → dependency-graph-panel-BgRP2qVg.js} +1 -1
- package/dist/assets/{documentation-panel-J2fwLjEP.js → documentation-panel-BKQG1zId.js} +1 -1
- package/dist/assets/download-BJ50folP.js +6 -0
- package/dist/assets/{edit-page-Bpl6BN5G.js → edit-page-Dzfy3wo-.js} +3 -3
- package/dist/assets/{error-panel-Cpfr8TZw.js → error-panel-B23uWV5g.js} +1 -1
- package/dist/assets/{file-explorer-panel-DsMwvQX7.js → file-explorer-panel-DQucxlTZ.js} +1 -1
- package/dist/assets/{floating-outline-yvPiOGQ2.js → floating-outline-C0NCQURJ.js} +1 -1
- package/dist/assets/{focus-B7xu7kpl.js → focus-DdU24YN9.js} +1 -1
- package/dist/assets/{form-BpwI8bBX.js → form-C4ZlGvVV.js} +1 -1
- package/dist/assets/{globals-BW23ny3Q.js → globals-DJ12V9Kv.js} +1 -1
- package/dist/assets/{home-page-C-JIKt-2.js → home-page-DUgD5VOD.js} +1 -1
- package/dist/assets/{hooks-2c2seyHG.js → hooks-ntzLWruV.js} +1 -1
- package/dist/assets/{html-to-image-Cy_LYuWW.js → html-to-image-B02su1Lp.js} +2 -2
- package/dist/assets/{index-CPfohPCM.js → index-BaIb0_Ut.js} +3 -3
- package/dist/assets/{kiosk-mode-D9jdUD5P.js → kiosk-mode-CiC9R5CJ.js} +1 -1
- package/dist/assets/{layout-BjIcxFO0.js → layout-CQQNB4AA.js} +1 -1
- package/dist/assets/{logs-panel-XVSOzGFv.js → logs-panel-BKwAB4Sl.js} +1 -1
- package/dist/assets/{markdown-renderer-BM4a9QeZ.js → markdown-renderer-BxPFtrG5.js} +1 -1
- package/dist/assets/{mode-BD6zDBBd.js → mode-BGSml4Q4.js} +1 -1
- package/dist/assets/{name-cell-input-CGevX71g.js → name-cell-input-wYLF2zUR.js} +1 -1
- package/dist/assets/{outline-panel-BgZXYbEO.js → outline-panel-Cp48IMlZ.js} +1 -1
- package/dist/assets/{packages-panel-8NDUaEZw.js → packages-panel-Drho5urs.js} +1 -1
- package/dist/assets/{panels-05G4QYfq.js → panels-DCSfU3wX.js} +1 -1
- package/dist/assets/{process-output-nNOt7QtH.js → process-output-Bw6cqAbr.js} +1 -1
- package/dist/assets/{readonly-python-code-CfLdThzF.js → readonly-python-code-CxRVkq45.js} +1 -1
- package/dist/assets/{run-page-DvWw2rQu.js → run-page-DI11aUJZ.js} +1 -1
- package/dist/assets/{scratchpad-panel-Bzreyj8q.js → scratchpad-panel-vzWFhKxS.js} +1 -1
- package/dist/assets/{session-panel-CxCUFR_x.js → session-panel-HNDQ-XhU.js} +1 -1
- package/dist/assets/{snippets-panel-BeqbqiKB.js → snippets-panel-Dx1FdjOx.js} +1 -1
- package/dist/assets/{state-DnUQ1uxR.js → state-D1OFbTRC.js} +1 -1
- package/dist/assets/{switch-Cx8dJhf6.js → switch-Cqx3FjIU.js} +1 -1
- package/dist/assets/{textarea-DellDgP4.js → textarea-BqB4s8zj.js} +1 -1
- package/dist/assets/{tracing-7U4WTsN0.js → tracing-Bu-VQRRo.js} +1 -1
- package/dist/assets/{tracing-panel-BPW1q7K3.js → tracing-panel-Fwxu1-K1.js} +2 -2
- package/dist/assets/{types-D5X7ikSD.js → types-D8lESWyo.js} +1 -1
- package/dist/assets/{useAddCell-bMoxoWAg.js → useAddCell-B_b-bEhC.js} +1 -1
- package/dist/assets/{useCellActionButton-0MDUWMcl.js → useCellActionButton-DjwaDuay.js} +1 -1
- package/dist/assets/{useDeleteCell-BUAQb9OH.js → useDeleteCell-7CQhKujJ.js} +1 -1
- package/dist/assets/{useDependencyPanelTab-odtlfdG2.js → useDependencyPanelTab-CB_Ogk32.js} +1 -1
- package/dist/assets/{useNotebookActions-CNWP5yqL.js → useNotebookActions-C47M79_m.js} +1 -1
- package/dist/assets/{useRunCells-DyBzMbHe.js → useRunCells-DCMO0B1l.js} +1 -1
- package/dist/assets/{useSplitCell-CyFavQ2l.js → useSplitCell-BAsTzBSc.js} +1 -1
- package/dist/assets/{utilities.esm-j_F9mYkM.js → utilities.esm-ZpMudNeK.js} +1 -1
- package/dist/index.html +31 -31
- package/package.json +1 -1
- package/src/components/data-table/TableActions.tsx +1 -1
- package/src/components/editor/header/__tests__/filename-form.test.tsx +124 -0
- package/src/core/codemirror/lsp/__tests__/notebook-lsp.test.ts +120 -0
- package/src/core/codemirror/lsp/lens.ts +17 -1
- package/src/core/codemirror/lsp/notebook-lsp.ts +107 -37
- package/src/utils/download.ts +13 -1
- package/src/utils/html-to-image.ts +8 -2
- package/dist/assets/download-Y3BpaOoI.js +0 -6
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
// @vitest-environment jsdom
|
|
3
|
+
|
|
4
|
+
import { fireEvent, render, waitFor } from "@testing-library/react";
|
|
5
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
6
|
+
import { FilenameForm } from "../filename-form";
|
|
7
|
+
|
|
8
|
+
// Mock the hooks
|
|
9
|
+
vi.mock("@/core/saving/filename", () => ({
|
|
10
|
+
useUpdateFilename: vi.fn(),
|
|
11
|
+
}));
|
|
12
|
+
|
|
13
|
+
vi.mock("@/core/saving/save-component", () => ({
|
|
14
|
+
useSaveNotebook: vi.fn(),
|
|
15
|
+
}));
|
|
16
|
+
|
|
17
|
+
// Mock FilenameInput to make testing easier
|
|
18
|
+
vi.mock("@/components/editor/header/filename-input", () => ({
|
|
19
|
+
FilenameInput: ({
|
|
20
|
+
onNameChange,
|
|
21
|
+
initialValue,
|
|
22
|
+
}: {
|
|
23
|
+
onNameChange: (value: string) => void;
|
|
24
|
+
initialValue?: string | null;
|
|
25
|
+
}) => (
|
|
26
|
+
<input
|
|
27
|
+
data-testid="filename-input"
|
|
28
|
+
data-initial-value={initialValue || ""}
|
|
29
|
+
onChange={(e) => onNameChange(e.target.value)}
|
|
30
|
+
/>
|
|
31
|
+
),
|
|
32
|
+
}));
|
|
33
|
+
|
|
34
|
+
const mockUseUpdateFilename = vi.mocked(
|
|
35
|
+
await import("@/core/saving/filename"),
|
|
36
|
+
).useUpdateFilename;
|
|
37
|
+
|
|
38
|
+
const mockUseSaveNotebook = vi.mocked(
|
|
39
|
+
await import("@/core/saving/save-component"),
|
|
40
|
+
).useSaveNotebook;
|
|
41
|
+
|
|
42
|
+
describe("FilenameForm", () => {
|
|
43
|
+
const mockUpdateFilename = vi.fn();
|
|
44
|
+
const mockSaveNotebook = vi.fn();
|
|
45
|
+
const mockSaveOrNameNotebook = vi.fn();
|
|
46
|
+
const mockSaveIfNotebookIsPersistent = vi.fn();
|
|
47
|
+
|
|
48
|
+
beforeEach(() => {
|
|
49
|
+
vi.clearAllMocks();
|
|
50
|
+
|
|
51
|
+
mockUseUpdateFilename.mockReturnValue(mockUpdateFilename);
|
|
52
|
+
mockUseSaveNotebook.mockReturnValue({
|
|
53
|
+
saveNotebook: mockSaveNotebook,
|
|
54
|
+
saveOrNameNotebook: mockSaveOrNameNotebook,
|
|
55
|
+
saveIfNotebookIsPersistent: mockSaveIfNotebookIsPersistent,
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("should call saveNotebook when creating a new file from an unnamed notebook", async () => {
|
|
60
|
+
// Setup: updateFilename resolves with the new filename
|
|
61
|
+
mockUpdateFilename.mockResolvedValue("/path/to/new-file.py");
|
|
62
|
+
|
|
63
|
+
const { getByTestId } = render(<FilenameForm filename={null} />);
|
|
64
|
+
|
|
65
|
+
const input = getByTestId("filename-input");
|
|
66
|
+
|
|
67
|
+
// Simulate name change (user entering a filename)
|
|
68
|
+
fireEvent.change(input, { target: { value: "new-file.py" } });
|
|
69
|
+
|
|
70
|
+
// Wait for the promise to resolve
|
|
71
|
+
await waitFor(() => {
|
|
72
|
+
expect(mockUpdateFilename).toHaveBeenCalledWith("new-file.py");
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
await waitFor(() => {
|
|
76
|
+
expect(mockSaveNotebook).toHaveBeenCalledWith(
|
|
77
|
+
"/path/to/new-file.py",
|
|
78
|
+
true,
|
|
79
|
+
);
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it("should not call saveNotebook when renaming an existing file", async () => {
|
|
84
|
+
// Setup: updateFilename resolves with the new filename
|
|
85
|
+
mockUpdateFilename.mockResolvedValue("/path/to/renamed-file.py");
|
|
86
|
+
|
|
87
|
+
const { getByTestId } = render(
|
|
88
|
+
<FilenameForm filename="/path/to/existing-file.py" />,
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
const input = getByTestId("filename-input");
|
|
92
|
+
|
|
93
|
+
// Simulate name change
|
|
94
|
+
fireEvent.change(input, { target: { value: "renamed-file.py" } });
|
|
95
|
+
|
|
96
|
+
// Wait for the promise to resolve
|
|
97
|
+
await waitFor(() => {
|
|
98
|
+
expect(mockUpdateFilename).toHaveBeenCalledWith("renamed-file.py");
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
// saveNotebook should NOT be called because the file was already named
|
|
102
|
+
expect(mockSaveNotebook).not.toHaveBeenCalled();
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it("should not call saveNotebook when updateFilename returns null", async () => {
|
|
106
|
+
// Setup: updateFilename resolves with null (e.g., user cancelled or error)
|
|
107
|
+
mockUpdateFilename.mockResolvedValue(null);
|
|
108
|
+
|
|
109
|
+
const { getByTestId } = render(<FilenameForm filename={null} />);
|
|
110
|
+
|
|
111
|
+
const input = getByTestId("filename-input");
|
|
112
|
+
|
|
113
|
+
// Simulate name change
|
|
114
|
+
fireEvent.change(input, { target: { value: "new-file.py" } });
|
|
115
|
+
|
|
116
|
+
// Wait for the promise to resolve
|
|
117
|
+
await waitFor(() => {
|
|
118
|
+
expect(mockUpdateFilename).toHaveBeenCalled();
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
// saveNotebook should NOT be called because updateFilename returned null
|
|
122
|
+
expect(mockSaveNotebook).not.toHaveBeenCalled();
|
|
123
|
+
});
|
|
124
|
+
});
|
|
@@ -25,6 +25,30 @@ const Cells = {
|
|
|
25
25
|
};
|
|
26
26
|
|
|
27
27
|
describe("createNotebookLens", () => {
|
|
28
|
+
it("should produce correct lens for same inputs", () => {
|
|
29
|
+
// Use unique content for this test
|
|
30
|
+
const cellIds: CellId[] = [Cells.cell1, Cells.cell2];
|
|
31
|
+
const codes: Record<CellId, string> = {
|
|
32
|
+
[Cells.cell1]: "unique_memo_test_line1",
|
|
33
|
+
[Cells.cell2]: "unique_memo_test_line2",
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const lens1 = createNotebookLens(cellIds, codes);
|
|
37
|
+
const lens2 = createNotebookLens(cellIds, codes);
|
|
38
|
+
|
|
39
|
+
// Same inputs should produce equivalent lens (same merged text)
|
|
40
|
+
expect(lens1.mergedText).toBe(lens2.mergedText);
|
|
41
|
+
expect(lens1.cellIds).toEqual(lens2.cellIds);
|
|
42
|
+
|
|
43
|
+
// Different content should produce a different lens
|
|
44
|
+
const differentCodes: Record<CellId, string> = {
|
|
45
|
+
[Cells.cell1]: "unique_memo_different",
|
|
46
|
+
[Cells.cell2]: "unique_memo_content",
|
|
47
|
+
};
|
|
48
|
+
const lens3 = createNotebookLens(cellIds, differentCodes);
|
|
49
|
+
expect(lens3.mergedText).not.toBe(lens1.mergedText);
|
|
50
|
+
});
|
|
51
|
+
|
|
28
52
|
it("should calculate correct line offsets", () => {
|
|
29
53
|
const cellIds: CellId[] = [Cells.cell1, Cells.cell2, Cells.cell3];
|
|
30
54
|
const codes: Record<CellId, string> = {
|
|
@@ -111,6 +135,7 @@ describe("createNotebookLens", () => {
|
|
|
111
135
|
),
|
|
112
136
|
).toBe(true);
|
|
113
137
|
|
|
138
|
+
// This triggers the cross-cell diagnostic warning (start in range, end outside)
|
|
114
139
|
expect(
|
|
115
140
|
lens.isInRange(
|
|
116
141
|
{
|
|
@@ -122,6 +147,24 @@ describe("createNotebookLens", () => {
|
|
|
122
147
|
).toBe(false);
|
|
123
148
|
});
|
|
124
149
|
|
|
150
|
+
it("should detect cross-cell diagnostics where start is in range but end is outside", () => {
|
|
151
|
+
const cellIds: CellId[] = [Cells.cell1, Cells.cell2];
|
|
152
|
+
const codes: Record<CellId, string> = {
|
|
153
|
+
[Cells.cell1]: "line1\nline2",
|
|
154
|
+
[Cells.cell2]: "line3",
|
|
155
|
+
};
|
|
156
|
+
const lens = createNotebookLens(cellIds, codes);
|
|
157
|
+
|
|
158
|
+
// Range that starts in cell1 (line 1) but ends in cell2 (line 2 = first line of cell2)
|
|
159
|
+
// This should return false and log a warning
|
|
160
|
+
const crossCellRange = {
|
|
161
|
+
start: { line: 1, character: 0 }, // line 1 is in cell1
|
|
162
|
+
end: { line: 2, character: 0 }, // line 2 is in cell2
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
expect(lens.isInRange(crossCellRange, Cells.cell1)).toBe(false);
|
|
166
|
+
});
|
|
167
|
+
|
|
125
168
|
it("should join all code into merged text", () => {
|
|
126
169
|
const cellIds: CellId[] = [Cells.cell1, Cells.cell2, Cells.cell3];
|
|
127
170
|
const codes: Record<CellId, string> = {
|
|
@@ -1120,6 +1163,83 @@ describe("NotebookLanguageServerClient", () => {
|
|
|
1120
1163
|
});
|
|
1121
1164
|
});
|
|
1122
1165
|
|
|
1166
|
+
describe("sync returns lens to prevent race conditions", () => {
|
|
1167
|
+
it("should return the lens used for synchronization", async () => {
|
|
1168
|
+
mockClient.textDocumentDidChange = vi
|
|
1169
|
+
.fn()
|
|
1170
|
+
.mockImplementation((params) => params);
|
|
1171
|
+
|
|
1172
|
+
const result = await notebookClient.sync();
|
|
1173
|
+
|
|
1174
|
+
// sync() should return both params and the lens
|
|
1175
|
+
expect(result).toHaveProperty("params");
|
|
1176
|
+
expect(result).toHaveProperty("lens");
|
|
1177
|
+
expect(result.lens.cellIds).toEqual([
|
|
1178
|
+
Cells.cell1,
|
|
1179
|
+
Cells.cell2,
|
|
1180
|
+
Cells.cell3,
|
|
1181
|
+
]);
|
|
1182
|
+
expect(result.lens.mergedText).toContain("# this is a comment");
|
|
1183
|
+
});
|
|
1184
|
+
|
|
1185
|
+
it("should return consistent lens even when called multiple times rapidly", async () => {
|
|
1186
|
+
mockClient.textDocumentDidChange = vi
|
|
1187
|
+
.fn()
|
|
1188
|
+
.mockImplementation((params) => params);
|
|
1189
|
+
|
|
1190
|
+
// Call sync multiple times rapidly
|
|
1191
|
+
const [result1, result2, result3] = await Promise.all([
|
|
1192
|
+
notebookClient.sync(),
|
|
1193
|
+
notebookClient.sync(),
|
|
1194
|
+
notebookClient.sync(),
|
|
1195
|
+
]);
|
|
1196
|
+
|
|
1197
|
+
// All should return the same lens content (memoized)
|
|
1198
|
+
expect(result1.lens.mergedText).toBe(result2.lens.mergedText);
|
|
1199
|
+
expect(result2.lens.mergedText).toBe(result3.lens.mergedText);
|
|
1200
|
+
});
|
|
1201
|
+
});
|
|
1202
|
+
|
|
1203
|
+
describe("SEEN_CELL_DOCUMENT_URIS memory management", () => {
|
|
1204
|
+
it("should track opened cells in SEEN_CELL_DOCUMENT_URIS", async () => {
|
|
1205
|
+
// Clear any existing state
|
|
1206
|
+
const seenUris = (NotebookLanguageServerClient as any)
|
|
1207
|
+
.SEEN_CELL_DOCUMENT_URIS;
|
|
1208
|
+
seenUris.clear();
|
|
1209
|
+
|
|
1210
|
+
// Open some cells to add them to SEEN_CELL_DOCUMENT_URIS
|
|
1211
|
+
await notebookClient.textDocumentDidOpen({
|
|
1212
|
+
textDocument: {
|
|
1213
|
+
uri: CellDocumentUri.of(Cells.cell1),
|
|
1214
|
+
languageId: "python",
|
|
1215
|
+
version: 1,
|
|
1216
|
+
text: "code1",
|
|
1217
|
+
},
|
|
1218
|
+
});
|
|
1219
|
+
|
|
1220
|
+
await notebookClient.textDocumentDidOpen({
|
|
1221
|
+
textDocument: {
|
|
1222
|
+
uri: CellDocumentUri.of(Cells.cell2),
|
|
1223
|
+
languageId: "python",
|
|
1224
|
+
version: 1,
|
|
1225
|
+
text: "code2",
|
|
1226
|
+
},
|
|
1227
|
+
});
|
|
1228
|
+
|
|
1229
|
+
// Verify cells were added
|
|
1230
|
+
expect(seenUris.has(CellDocumentUri.of(Cells.cell1))).toBe(true);
|
|
1231
|
+
expect(seenUris.has(CellDocumentUri.of(Cells.cell2))).toBe(true);
|
|
1232
|
+
expect(seenUris.size).toBe(2);
|
|
1233
|
+
});
|
|
1234
|
+
|
|
1235
|
+
it("should have pruneSeenCellUris static method", () => {
|
|
1236
|
+
// Verify the method exists and is callable
|
|
1237
|
+
expect(
|
|
1238
|
+
typeof (NotebookLanguageServerClient as any).pruneSeenCellUris,
|
|
1239
|
+
).toBe("function");
|
|
1240
|
+
});
|
|
1241
|
+
});
|
|
1242
|
+
|
|
1123
1243
|
describe("initialization and configuration", () => {
|
|
1124
1244
|
it("should send configuration after initialization", async () => {
|
|
1125
1245
|
const configNotifications: any[] = [];
|
|
@@ -68,12 +68,28 @@ export function createNotebookLens(
|
|
|
68
68
|
reverseRange: (range: LSP.Range, cellId: CellId) =>
|
|
69
69
|
shiftRange(range, -getCurrentLineOffset(cellId)),
|
|
70
70
|
|
|
71
|
+
/**
|
|
72
|
+
* Check if a range falls entirely within the given cell.
|
|
73
|
+
* Returns false for ranges that span multiple cells (cross-cell diagnostics).
|
|
74
|
+
*/
|
|
71
75
|
isInRange: (range: LSP.Range, cellId: CellId) => {
|
|
72
76
|
const cellLines = codes[cellId].split("\n").length;
|
|
73
77
|
const offset = cellLineOffsets.get(cellId) || 0;
|
|
74
78
|
const startLine = range.start.line - offset;
|
|
75
79
|
const endLine = range.end.line - offset;
|
|
76
|
-
|
|
80
|
+
|
|
81
|
+
const startInRange = startLine >= 0 && startLine < cellLines;
|
|
82
|
+
const endInRange = endLine >= 0 && endLine < cellLines;
|
|
83
|
+
|
|
84
|
+
// Warn about cross-cell diagnostics that start in this cell but end outside
|
|
85
|
+
if (startInRange && !endInRange) {
|
|
86
|
+
Logger.warn(
|
|
87
|
+
"[lsp] Cross-cell diagnostic detected: range starts in cell but ends outside",
|
|
88
|
+
{ cellId, range, cellLines, offset },
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return startInRange && endInRange;
|
|
77
93
|
},
|
|
78
94
|
|
|
79
95
|
getEditsForNewText: (newText: string) => {
|
|
@@ -113,8 +113,28 @@ export class NotebookLanguageServerClient implements ILanguageServerClient {
|
|
|
113
113
|
EditorView | null | undefined
|
|
114
114
|
>;
|
|
115
115
|
private readonly initialSettings: Record<string, unknown>;
|
|
116
|
+
/**
|
|
117
|
+
* Tracks which cell document URIs have been opened with the LSP server.
|
|
118
|
+
* Used to clear diagnostics for cells that no longer have any.
|
|
119
|
+
*
|
|
120
|
+
* This set is pruned when diagnostics are processed to only include
|
|
121
|
+
* cells that exist in the current notebook snapshot.
|
|
122
|
+
*/
|
|
116
123
|
private static readonly SEEN_CELL_DOCUMENT_URIS = new Set<CellDocumentUri>();
|
|
117
124
|
|
|
125
|
+
/**
|
|
126
|
+
* Remove cell URIs that are no longer in the notebook.
|
|
127
|
+
* Called during diagnostic processing to prevent memory leaks.
|
|
128
|
+
*/
|
|
129
|
+
private static pruneSeenCellUris(currentCellIds: Set<CellId>): void {
|
|
130
|
+
for (const uri of NotebookLanguageServerClient.SEEN_CELL_DOCUMENT_URIS) {
|
|
131
|
+
const cellId = CellDocumentUri.parse(uri);
|
|
132
|
+
if (!currentCellIds.has(cellId)) {
|
|
133
|
+
NotebookLanguageServerClient.SEEN_CELL_DOCUMENT_URIS.delete(uri);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
118
138
|
/**
|
|
119
139
|
* Cache of completion items to avoid jitter while typing in the same completion item
|
|
120
140
|
*/
|
|
@@ -274,26 +294,30 @@ export class NotebookLanguageServerClient implements ILanguageServerClient {
|
|
|
274
294
|
};
|
|
275
295
|
}
|
|
276
296
|
|
|
277
|
-
|
|
297
|
+
/**
|
|
298
|
+
* Synchronize the document with the LSP server and return the lens used.
|
|
299
|
+
* This ensures the caller uses the same lens that was sent to the server,
|
|
300
|
+
* avoiding race conditions if cells change between sync and subsequent operations.
|
|
301
|
+
*/
|
|
302
|
+
async sync(): Promise<{
|
|
303
|
+
params: LSP.DidChangeTextDocumentParams;
|
|
304
|
+
lens: NotebookLens;
|
|
305
|
+
}> {
|
|
278
306
|
const { lens, version, didChange } = this.snapshotter.snapshot();
|
|
279
|
-
|
|
280
|
-
return {
|
|
281
|
-
textDocument: {
|
|
282
|
-
uri: this.documentUri,
|
|
283
|
-
version: version,
|
|
284
|
-
},
|
|
285
|
-
contentChanges: [{ text: lens.mergedText }],
|
|
286
|
-
};
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
// Update changes for merged doc, etc.
|
|
290
|
-
return this.client.textDocumentDidChange({
|
|
307
|
+
const params: LSP.DidChangeTextDocumentParams = {
|
|
291
308
|
textDocument: {
|
|
292
309
|
uri: this.documentUri,
|
|
293
310
|
version: version,
|
|
294
311
|
},
|
|
295
312
|
contentChanges: [{ text: lens.mergedText }],
|
|
296
|
-
}
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
if (didChange) {
|
|
316
|
+
// Update changes for merged doc
|
|
317
|
+
await this.client.textDocumentDidChange(params);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
return { params, lens };
|
|
297
321
|
}
|
|
298
322
|
|
|
299
323
|
public async textDocumentDidChange(params: LSP.DidChangeTextDocumentParams) {
|
|
@@ -301,7 +325,8 @@ export class NotebookLanguageServerClient implements ILanguageServerClient {
|
|
|
301
325
|
// We know how to only handle single content changes
|
|
302
326
|
// But that is all we expect to receive
|
|
303
327
|
if (params.contentChanges.length === 1) {
|
|
304
|
-
|
|
328
|
+
const { params: syncParams } = await this.sync();
|
|
329
|
+
return syncParams;
|
|
305
330
|
}
|
|
306
331
|
|
|
307
332
|
Logger.warn("[lsp] Unhandled textDocumentDidChange", params);
|
|
@@ -319,9 +344,8 @@ export class NotebookLanguageServerClient implements ILanguageServerClient {
|
|
|
319
344
|
return null;
|
|
320
345
|
}
|
|
321
346
|
|
|
322
|
-
//
|
|
323
|
-
await this.sync();
|
|
324
|
-
const { lens } = this.snapshotter.getLatestSnapshot();
|
|
347
|
+
// Sync and use the same lens that was sent to the server
|
|
348
|
+
const { lens } = await this.sync();
|
|
325
349
|
|
|
326
350
|
// Find the lens for this cell
|
|
327
351
|
const cellId = CellDocumentUri.parse(cellDocumentUri);
|
|
@@ -345,9 +369,8 @@ export class NotebookLanguageServerClient implements ILanguageServerClient {
|
|
|
345
369
|
return null;
|
|
346
370
|
}
|
|
347
371
|
|
|
348
|
-
//
|
|
349
|
-
await this.sync();
|
|
350
|
-
const { lens } = this.snapshotter.getLatestSnapshot();
|
|
372
|
+
// Sync and use the same lens that was sent to the server
|
|
373
|
+
const { lens } = await this.sync();
|
|
351
374
|
|
|
352
375
|
const cellId = CellDocumentUri.parse(cellDocumentUri);
|
|
353
376
|
|
|
@@ -367,6 +390,23 @@ export class NotebookLanguageServerClient implements ILanguageServerClient {
|
|
|
367
390
|
return response;
|
|
368
391
|
}
|
|
369
392
|
|
|
393
|
+
/**
|
|
394
|
+
* Code actions are currently disabled because mapping code action edits
|
|
395
|
+
* back to individual cells is complex and error-prone.
|
|
396
|
+
*
|
|
397
|
+
* The LSP server returns edits in merged document coordinates, but we need
|
|
398
|
+
* to apply them to individual cell editors. Unlike simple position transforms
|
|
399
|
+
* (hover, completion), code actions can include arbitrary text edits that
|
|
400
|
+
* may span multiple cells or change line counts, making the reverse mapping
|
|
401
|
+
* unreliable.
|
|
402
|
+
*
|
|
403
|
+
* To enable this, we would need to:
|
|
404
|
+
* 1. Transform edit ranges from merged doc coordinates to cell coordinates
|
|
405
|
+
* 2. Handle edits that span cell boundaries (split or reject them)
|
|
406
|
+
* 3. Apply edits atomically across multiple cell editors
|
|
407
|
+
*
|
|
408
|
+
* See textDocumentRename for a similar workaround that manually applies edits.
|
|
409
|
+
*/
|
|
370
410
|
textDocumentCodeAction(
|
|
371
411
|
params: LSP.CodeActionParams,
|
|
372
412
|
): Promise<(LSP.Command | LSP.CodeAction)[] | null> {
|
|
@@ -378,12 +418,23 @@ export class NotebookLanguageServerClient implements ILanguageServerClient {
|
|
|
378
418
|
}
|
|
379
419
|
|
|
380
420
|
/**
|
|
381
|
-
*
|
|
382
|
-
*
|
|
383
|
-
*
|
|
421
|
+
* Rename implementation with manual edit application.
|
|
422
|
+
*
|
|
423
|
+
* This is a workaround because the standard LSP rename flow doesn't work well
|
|
424
|
+
* with our notebook architecture. The LSP server returns a WorkspaceEdit with
|
|
425
|
+
* edits in merged document coordinates, but CodeMirror's LSP plugin expects
|
|
426
|
+
* edits for individual cell documents.
|
|
427
|
+
*
|
|
428
|
+
* Instead of trying to transform the WorkspaceEdit (which is complex because
|
|
429
|
+
* edits can span cells, change line counts, etc.), we:
|
|
430
|
+
* 1. Request the rename from the LSP server
|
|
431
|
+
* 2. Extract the new merged text from the response
|
|
432
|
+
* 3. Split it back into per-cell text using the lens
|
|
433
|
+
* 4. Manually update each cell's editor
|
|
384
434
|
*
|
|
385
|
-
*
|
|
386
|
-
*
|
|
435
|
+
* This approach is simpler and more reliable, though it bypasses the normal
|
|
436
|
+
* LSP edit application flow. The trade-off is that we lose undo grouping
|
|
437
|
+
* across cells.
|
|
387
438
|
*/
|
|
388
439
|
async textDocumentRename(
|
|
389
440
|
params: LSP.RenameParams,
|
|
@@ -397,9 +448,8 @@ export class NotebookLanguageServerClient implements ILanguageServerClient {
|
|
|
397
448
|
|
|
398
449
|
const cellId = CellDocumentUri.parse(cellDocumentUri);
|
|
399
450
|
|
|
400
|
-
//
|
|
401
|
-
await this.sync();
|
|
402
|
-
const { lens } = this.snapshotter.getLatestSnapshot();
|
|
451
|
+
// Sync and use the same lens that was sent to the server
|
|
452
|
+
const { lens } = await this.sync();
|
|
403
453
|
|
|
404
454
|
const transformedPosition = lens.transformPosition(params.position, cellId);
|
|
405
455
|
|
|
@@ -468,6 +518,9 @@ export class NotebookLanguageServerClient implements ILanguageServerClient {
|
|
|
468
518
|
}
|
|
469
519
|
}
|
|
470
520
|
|
|
521
|
+
const failedCells: CellId[] = [];
|
|
522
|
+
let updatedCount = 0;
|
|
523
|
+
|
|
471
524
|
for (const [currentCellId, ev] of Objects.entries(editors)) {
|
|
472
525
|
// For private variable renames, only update the originating cell
|
|
473
526
|
if (isPrivateRename && currentCellId !== cellId) {
|
|
@@ -476,12 +529,17 @@ export class NotebookLanguageServerClient implements ILanguageServerClient {
|
|
|
476
529
|
|
|
477
530
|
const newCode = editsToNewCode.get(currentCellId);
|
|
478
531
|
if (newCode == null) {
|
|
479
|
-
Logger.warn("No new code for cell", currentCellId);
|
|
532
|
+
Logger.warn("[lsp] No new code for cell during rename", currentCellId);
|
|
533
|
+
failedCells.push(currentCellId);
|
|
480
534
|
continue;
|
|
481
535
|
}
|
|
482
536
|
|
|
483
537
|
if (!ev) {
|
|
484
|
-
Logger.warn(
|
|
538
|
+
Logger.warn(
|
|
539
|
+
"[lsp] No editor view for cell during rename",
|
|
540
|
+
currentCellId,
|
|
541
|
+
);
|
|
542
|
+
failedCells.push(currentCellId);
|
|
485
543
|
continue;
|
|
486
544
|
}
|
|
487
545
|
|
|
@@ -489,9 +547,19 @@ export class NotebookLanguageServerClient implements ILanguageServerClient {
|
|
|
489
547
|
const code = getEditorCodeAsPython(ev);
|
|
490
548
|
if (code !== newCode) {
|
|
491
549
|
updateEditorCodeFromPython(ev, newCode);
|
|
550
|
+
updatedCount++;
|
|
492
551
|
}
|
|
493
552
|
}
|
|
494
553
|
|
|
554
|
+
if (failedCells.length > 0) {
|
|
555
|
+
Logger.error(
|
|
556
|
+
`[lsp] Rename partially failed: could not update ${failedCells.length} cell(s)`,
|
|
557
|
+
failedCells,
|
|
558
|
+
);
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
Logger.debug(`[lsp] Rename completed: updated ${updatedCount} cell(s)`);
|
|
562
|
+
|
|
495
563
|
return {
|
|
496
564
|
...response,
|
|
497
565
|
documentChanges: [
|
|
@@ -531,9 +599,8 @@ export class NotebookLanguageServerClient implements ILanguageServerClient {
|
|
|
531
599
|
return null;
|
|
532
600
|
}
|
|
533
601
|
|
|
534
|
-
//
|
|
535
|
-
await this.sync();
|
|
536
|
-
const { lens } = this.snapshotter.getLatestSnapshot();
|
|
602
|
+
// Sync and use the same lens that was sent to the server
|
|
603
|
+
const { lens } = await this.sync();
|
|
537
604
|
|
|
538
605
|
const cellId = CellDocumentUri.parse(cellDocumentUri);
|
|
539
606
|
|
|
@@ -549,9 +616,8 @@ export class NotebookLanguageServerClient implements ILanguageServerClient {
|
|
|
549
616
|
public async textDocumentHover(params: LSP.HoverParams) {
|
|
550
617
|
Logger.debug("[lsp] textDocumentHover", params);
|
|
551
618
|
|
|
552
|
-
//
|
|
553
|
-
await this.sync();
|
|
554
|
-
const { lens } = this.snapshotter.getLatestSnapshot();
|
|
619
|
+
// Sync and use the same lens that was sent to the server
|
|
620
|
+
const { lens } = await this.sync();
|
|
555
621
|
|
|
556
622
|
const cellId = CellDocumentUri.parse(params.textDocument.uri);
|
|
557
623
|
|
|
@@ -655,6 +721,10 @@ export class NotebookLanguageServerClient implements ILanguageServerClient {
|
|
|
655
721
|
}
|
|
656
722
|
}
|
|
657
723
|
|
|
724
|
+
// Prune any cell URIs for cells that no longer exist
|
|
725
|
+
const currentCellIds = new Set(lens.cellIds);
|
|
726
|
+
NotebookLanguageServerClient.pruneSeenCellUris(currentCellIds);
|
|
727
|
+
|
|
658
728
|
const cellsToClear = new Set(
|
|
659
729
|
NotebookLanguageServerClient.SEEN_CELL_DOCUMENT_URIS,
|
|
660
730
|
);
|
package/src/utils/download.ts
CHANGED
|
@@ -94,6 +94,8 @@ function prepareCellElementForScreenshot(
|
|
|
94
94
|
};
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
+
const THRESHOLD_TIME_MS = 500;
|
|
98
|
+
|
|
97
99
|
/**
|
|
98
100
|
* Capture a cell output as a PNG data URL.
|
|
99
101
|
*
|
|
@@ -119,7 +121,17 @@ export async function getImageDataUrlForCell(
|
|
|
119
121
|
const cleanup = prepareCellElementForScreenshot(element, enablePrintMode);
|
|
120
122
|
|
|
121
123
|
try {
|
|
122
|
-
|
|
124
|
+
const startTime = Date.now();
|
|
125
|
+
const dataUrl = await toPng(element);
|
|
126
|
+
const timeTaken = Date.now() - startTime;
|
|
127
|
+
if (timeTaken > THRESHOLD_TIME_MS) {
|
|
128
|
+
Logger.debug(
|
|
129
|
+
"toPng operation for element",
|
|
130
|
+
element,
|
|
131
|
+
`took ${timeTaken} ms (exceeds threshold)`,
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
return dataUrl;
|
|
123
135
|
} finally {
|
|
124
136
|
cleanup();
|
|
125
137
|
}
|
|
@@ -13,7 +13,13 @@ export const defaultHtmlToImageOptions: HtmlToImageOptions = {
|
|
|
13
13
|
try {
|
|
14
14
|
if ("classList" in node) {
|
|
15
15
|
// Filter out matplotlib toolbars
|
|
16
|
-
|
|
16
|
+
if (node.classList.contains("mpl-toolbar")) {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (node.classList.contains("no-print")) {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
17
23
|
}
|
|
18
24
|
return true;
|
|
19
25
|
} catch (error) {
|
|
@@ -30,7 +36,7 @@ export const defaultHtmlToImageOptions: HtmlToImageOptions = {
|
|
|
30
36
|
* Convert an HTML element to a PNG data URL.
|
|
31
37
|
* This is a wrapper around html-to-image's toPng with default options applied.
|
|
32
38
|
*/
|
|
33
|
-
export function toPng(
|
|
39
|
+
export async function toPng(
|
|
34
40
|
element: HTMLElement,
|
|
35
41
|
options?: HtmlToImageOptions,
|
|
36
42
|
): Promise<string> {
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
var ue=Object.defineProperty;var ce=(r,e,t)=>e in r?ue(r,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):r[e]=t;var O=(r,e,t)=>ce(r,typeof e!="symbol"?e+"":e,t);import{s as V,t as G}from"./chunk-LvLJmgfZ.js";import{t as q}from"./react-BGmjiNul.js";import{ii as de,zt as fe}from"./cells-DRiwSVs0.js";import{t as B}from"./compiler-runtime-DeeZ7FnK.js";import{d as me}from"./hotkeys-BHHWjLlp.js";import{t as pe}from"./jsx-runtime-ZmTK25f3.js";import{t as W}from"./cn-BKtXLv3a.js";import{t as ve}from"./requests-BsVD4CdD.js";import{t as he}from"./createLucideIcon-CnW3RofX.js";import{t as _}from"./use-toast-rmUWldD_.js";import{d as ge}from"./popover-D16ZremR.js";import{r as ye}from"./errors-2SszdW9t.js";import{t as z}from"./dist-CdxIjAOP.js";import{t as M}from"./html-to-image-Cy_LYuWW.js";var we=he("chevron-left",[["path",{d:"m15 18-6-6 6-6",key:"1wnfg3"}]]),H=r=>{let e,t=new Set,a=(l,s)=>{let c=typeof l=="function"?l(e):l;if(!Object.is(c,e)){let d=e;e=s??(typeof c!="object"||!c)?c:Object.assign({},e,c),t.forEach(u=>u(e,d))}},n=()=>e,o={setState:a,getState:n,getInitialState:()=>i,subscribe:l=>(t.add(l),()=>t.delete(l)),destroy:()=>{t.clear()}},i=e=r(a,n,o);return o},xe=r=>r?H(r):H,be=G((r=>{var e=q(),t=ge();function a(d,u){return d===u&&(d!==0||1/d==1/u)||d!==d&&u!==u}var n=typeof Object.is=="function"?Object.is:a,o=t.useSyncExternalStore,i=e.useRef,l=e.useEffect,s=e.useMemo,c=e.useDebugValue;r.useSyncExternalStoreWithSelector=function(d,u,f,g,m){var p=i(null);if(p.current===null){var x={hasValue:!1,value:null};p.current=x}else x=p.current;p=s(function(){function U(y){if(!F){if(F=!0,L=y,y=g(y),m!==void 0&&x.hasValue){var N=x.value;if(m(N,y))return S=N}return S=y}if(N=S,n(L,y))return N;var C=g(y);return m!==void 0&&m(N,C)?(L=y,N):(L=y,S=C)}var F=!1,L,S,I=f===void 0?null:f;return[function(){return U(u())},I===null?void 0:function(){return U(I())}]},[u,f,g,m]);var b=o(d,p[0],p[1]);return l(function(){x.hasValue=!0,x.value=b},[b]),c(b),b}})),Ne=G(((r,e)=>{e.exports=be()}));const h={toMarkdown:r=>h.replace(r,"md"),toHTML:r=>h.replace(r,"html"),toPNG:r=>h.replace(r,"png"),toPDF:r=>h.replace(r,"pdf"),toPY:r=>h.replace(r,"py"),withoutExtension:r=>{let e=r.split(".");return e.length===1?r:e.slice(0,-1).join(".")},replace:(r,e)=>r.endsWith(`.${e}`)?r:`${h.withoutExtension(r)}.${e}`};var P=320,$=180;function X(r){if(!r||r==="about:blank")return null;try{let e=new URL(r,window.location.href);return e.origin===window.location.origin?null:e.href}catch{return r}}function Pe(r,e,t){let a=[],n="";for(let o of e){let i=n+o;r.measureText(i).width<=t?n=i:(n&&a.push(n),n=o)}return n&&a.push(n),a}function j(r){let e=window.devicePixelRatio||1,t=document.createElement("canvas");t.width=P*e,t.height=$*e;let a=t.getContext("2d");if(!a)return t.toDataURL("image/png");a.scale(e,e),a.fillStyle="#f3f4f6",a.fillRect(0,0,P,$),a.strokeStyle="#d1d5db",a.strokeRect(.5,.5,P-1,$-1),a.fillStyle="#6b7280",a.font="8px sans-serif",a.textAlign="center",a.textBaseline="middle";let n=P-32,o=r?Pe(a,r,n):[],i=($-(1+o.length)*14)/2+14/2;a.fillText("External iframe",P/2,i),i+=14;for(let l of o)a.fillText(l,P/2,i),i+=14;return t.toDataURL("image/png")}async function Y(r){var n;let e=r.querySelector("iframe");if(!e)return null;let t=X(e.getAttribute("src"));if(t)return j(t);let a;try{let o=e.contentDocument||((n=e.contentWindow)==null?void 0:n.document);if(!(o!=null&&o.body))return null;a=o}catch{return j(null)}for(let o of a.querySelectorAll("iframe")){let i=X(o.getAttribute("src"));if(i)return j(i)}try{return await M(a.body)}catch{return j(null)}}var J=class se{constructor(e){O(this,"progress",0);O(this,"listeners",new Set);this.total=e}static indeterminate(){return new se("indeterminate")}addTotal(e){this.total==="indeterminate"?this.total=e:this.total+=e,this.notifyListeners()}increment(e){this.progress+=e,this.notifyListeners()}getProgress(){return this.total==="indeterminate"?"indeterminate":this.progress/this.total*100}subscribe(e){return this.listeners.add(e),()=>{this.listeners.delete(e)}}notifyListeners(){let e=this.getProgress();for(let t of this.listeners)t(e)}},v=V(q(),1),w=V(pe(),1);function Le(r,e=[]){let t=[];function a(o,i){let l=v.createContext(i);l.displayName=o+"Context";let s=t.length;t=[...t,i];let c=u=>{var b;let{scope:f,children:g,...m}=u,p=((b=f==null?void 0:f[r])==null?void 0:b[s])||l,x=v.useMemo(()=>m,Object.values(m));return(0,w.jsx)(p.Provider,{value:x,children:g})};c.displayName=o+"Provider";function d(u,f){var p;let g=((p=f==null?void 0:f[r])==null?void 0:p[s])||l,m=v.useContext(g);if(m)return m;if(i!==void 0)return i;throw Error(`\`${u}\` must be used within \`${o}\``)}return[c,d]}let n=()=>{let o=t.map(i=>v.createContext(i));return function(i){let l=(i==null?void 0:i[r])||o;return v.useMemo(()=>({[`__scope${r}`]:{...i,[r]:l}}),[i,l])}};return n.scopeName=r,[a,Se(n,...e)]}function Se(...r){let e=r[0];if(r.length===1)return e;let t=()=>{let a=r.map(n=>({useScope:n(),scopeName:n.scopeName}));return function(n){let o=a.reduce((i,{useScope:l,scopeName:s})=>{let c=l(n)[`__scope${s}`];return{...i,...c}},{});return v.useMemo(()=>({[`__scope${e.scopeName}`]:o}),[o])}};return t.scopeName=e.scopeName,t}var T="Progress",k=100,[$e,nt]=Le(T),[je,Ee]=$e(T),K=v.forwardRef((r,e)=>{let{__scopeProgress:t,value:a=null,max:n,getValueLabel:o=Re,...i}=r;(n||n===0)&&!te(n)&&console.error(De(`${n}`,"Progress"));let l=te(n)?n:k;a!==null&&!re(a,l)&&console.error(Oe(`${a}`,"Progress"));let s=re(a,l)?a:null,c=E(s)?o(s,l):void 0;return(0,w.jsx)(je,{scope:t,value:s,max:l,children:(0,w.jsx)(z.div,{"aria-valuemax":l,"aria-valuemin":0,"aria-valuenow":E(s)?s:void 0,"aria-valuetext":c,role:"progressbar","data-state":ee(s,l),"data-value":s??void 0,"data-max":l,...i,ref:e})})});K.displayName=T;var Q="ProgressIndicator",Z=v.forwardRef((r,e)=>{let{__scopeProgress:t,...a}=r,n=Ee(Q,t);return(0,w.jsx)(z.div,{"data-state":ee(n.value,n.max),"data-value":n.value??void 0,"data-max":n.max,...a,ref:e})});Z.displayName=Q;function Re(r,e){return`${Math.round(r/e*100)}%`}function ee(r,e){return r==null?"indeterminate":r===e?"complete":"loading"}function E(r){return typeof r=="number"}function te(r){return E(r)&&!isNaN(r)&&r>0}function re(r,e){return E(r)&&!isNaN(r)&&r<=e&&r>=0}function De(r,e){return`Invalid prop \`max\` of value \`${r}\` supplied to \`${e}\`. Only numbers greater than 0 are valid max values. Defaulting to \`${k}\`.`}function Oe(r,e){return`Invalid prop \`value\` of value \`${r}\` supplied to \`${e}\`. The \`value\` prop must be:
|
|
2
|
-
- a positive number
|
|
3
|
-
- less than the value passed to \`max\` (or ${k} if no \`max\` prop is set)
|
|
4
|
-
- \`null\` or \`undefined\` if the progress is indeterminate.
|
|
5
|
-
|
|
6
|
-
Defaulting to \`null\`.`}var ne=K,_e=Z,Me=B(),A=v.forwardRef((r,e)=>{let t=(0,Me.c)(20),a,n,o,i;t[0]===r?(a=t[1],n=t[2],o=t[3],i=t[4]):({className:a,value:i,indeterminate:n,...o}=r,t[0]=r,t[1]=a,t[2]=n,t[3]=o,t[4]=i);let l;t[5]===a?l=t[6]:(l=W("relative h-2 w-full overflow-hidden rounded-full bg-primary/20",a),t[5]=a,t[6]=l);let s=n?"w-1/3 animate-progress-indeterminate":"w-full transition-transform duration-300 ease-out",c;t[7]===s?c=t[8]:(c=W("h-full flex-1 bg-primary",s),t[7]=s,t[8]=c);let d;t[9]!==n||t[10]!==i?(d=n?void 0:{transform:`translateX(-${100-(i||0)}%)`},t[9]=n,t[10]=i,t[11]=d):d=t[11];let u;t[12]!==c||t[13]!==d?(u=(0,w.jsx)(_e,{className:c,style:d}),t[12]=c,t[13]=d,t[14]=u):u=t[14];let f;return t[15]!==o||t[16]!==e||t[17]!==l||t[18]!==u?(f=(0,w.jsx)(ne,{ref:e,className:l,...o,children:u}),t[15]=o,t[16]=e,t[17]=l,t[18]=u,t[19]=f):f=t[19],f});A.displayName=ne.displayName;var Te=B();const ke=r=>{let e=(0,Te.c)(13),{progress:t,showPercentage:a}=r,n=a===void 0?!1:a,o,i;e[0]===t?(o=e[1],i=e[2]):(o=g=>t.subscribe(g),i=()=>t.getProgress(),e[0]=t,e[1]=o,e[2]=i);let l=(0,v.useSyncExternalStore)(o,i),s=l==="indeterminate"||l===100,c=s?void 0:l,d;e[3]!==s||e[4]!==c?(d=(0,w.jsx)(A,{value:c,indeterminate:s}),e[3]=s,e[4]=c,e[5]=d):d=e[5];let u;e[6]!==s||e[7]!==n||e[8]!==l?(u=!s&&n&&(0,w.jsxs)("div",{className:"mt-1 text-xs text-muted-foreground text-right",children:[Math.round(l),"%"]}),e[6]=s,e[7]=n,e[8]=l,e[9]=u):u=e[9];let f;return e[10]!==d||e[11]!==u?(f=(0,w.jsxs)("div",{className:"mt-2 w-full min-w-[200px]",children:[d,u]}),e[10]=d,e[11]=u,e[12]=f):f=e[12],f};async function Ae(r,e){let t=J.indeterminate(),a=_({title:r,description:v.createElement(ke,{progress:t}),duration:1/0});try{let n=await e(t);return a.dismiss(),n}catch(n){throw a.dismiss(),n}}function ae(r){let e=document.getElementById(de.create(r));if(!e){me.error(`Output element not found for cell ${r}`);return}return e}var R=0;function Ue(){R++,R===1&&document.body.classList.add("printing")}function Fe(){R--,R===0&&document.body.classList.remove("printing")}function ie(r,e){r.classList.add("printing-output"),e&&Ue();let t=r.style.overflow;return r.style.overflow="auto",()=>{r.classList.remove("printing-output"),e&&Fe(),r.style.overflow=t}}async function Ie(r,e=!0){let t=ae(r);if(!t)return;let a=await Y(t);if(a)return a;let n=ie(t,e);try{return await M(t)}finally{n()}}async function Ce(r,e){let t=ae(r);if(!t)return;let a=await Y(t);if(a){D(a,h.toPNG(e));return}await oe({element:t,filename:e,prepare:()=>ie(t,!0)})}async function oe(r){let{element:e,filename:t,prepare:a}=r,n=document.getElementById("App"),o=(n==null?void 0:n.scrollTop)??0,i;a?i=a(e):document.body.classList.add("printing");try{D(await M(e),h.toPNG(t))}catch{_({title:"Error",description:"Failed to download as PNG.",variant:"danger"})}finally{i==null||i(),document.body.classList.contains("printing")&&document.body.classList.remove("printing"),requestAnimationFrame(()=>{n==null||n.scrollTo(0,o)})}}function D(r,e){let t=document.createElement("a");t.href=r,t.download=e,t.click(),t.remove()}function le(r,e){let t=URL.createObjectURL(r);D(t,e),URL.revokeObjectURL(t)}async function Ve(r){let e=ve(),{filename:t,webpdf:a}=r;try{let n=await e.exportAsPDF({webpdf:a}),o=fe.basename(t);le(n,h.toPDF(o))}catch(n){throw _({title:"Failed to download",description:ye(n),variant:"danger"}),n}}export{oe as a,A as c,Ne as d,xe as f,Ce as i,J as l,le as n,Ie as o,we as p,D as r,Ae as s,Ve as t,h as u};
|