@malloy-publisher/sdk 0.0.80 → 0.0.81

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.
Files changed (45) hide show
  1. package/dist/components/Model/Model.d.ts +3 -1
  2. package/dist/components/Package/index.d.ts +1 -0
  3. package/dist/components/Project/index.d.ts +1 -0
  4. package/dist/components/Workbook/BrowserWorkbookStorage.d.ts +11 -0
  5. package/dist/components/{MutableNotebook → Workbook}/EditableMalloyCell.d.ts +2 -2
  6. package/dist/components/{MutableNotebook → Workbook}/MutableCell.d.ts +3 -3
  7. package/dist/components/Workbook/Workbook.d.ts +9 -0
  8. package/dist/components/Workbook/WorkbookList.d.ts +7 -0
  9. package/dist/components/Workbook/WorkbookManager.d.ts +87 -0
  10. package/dist/components/Workbook/WorkbookStorage.d.ts +18 -0
  11. package/dist/components/Workbook/WorkbookStorageProvider.d.ts +12 -0
  12. package/dist/components/Workbook/index.d.ts +7 -0
  13. package/dist/components/index.d.ts +3 -1
  14. package/dist/index.cjs.js +62 -62
  15. package/dist/index.es.js +6466 -6337
  16. package/package.json +3 -2
  17. package/src/components/AnalyzePackageButton.tsx +121 -24
  18. package/src/components/Model/Model.tsx +7 -1
  19. package/src/components/Package/index.ts +1 -0
  20. package/src/components/Project/index.ts +1 -0
  21. package/src/components/Workbook/BrowserWorkbookStorage.ts +100 -0
  22. package/src/components/{MutableNotebook → Workbook}/EditableMalloyCell.tsx +2 -2
  23. package/src/components/{MutableNotebook → Workbook}/MutableCell.tsx +3 -3
  24. package/src/components/{MutableNotebook/MutableNotebook.tsx → Workbook/Workbook.tsx} +81 -57
  25. package/src/components/Workbook/WorkbookList.tsx +111 -0
  26. package/src/components/Workbook/WorkbookManager.ts +230 -0
  27. package/src/components/Workbook/WorkbookStorage.ts +54 -0
  28. package/src/components/Workbook/WorkbookStorageProvider.tsx +37 -0
  29. package/src/components/Workbook/index.ts +7 -0
  30. package/src/components/index.ts +3 -1
  31. package/dist/components/MutableNotebook/BrowserNotebookStorage.d.ts +0 -9
  32. package/dist/components/MutableNotebook/MutableNotebook.d.ts +0 -8
  33. package/dist/components/MutableNotebook/MutableNotebookList.d.ts +0 -6
  34. package/dist/components/MutableNotebook/NotebookStorage.d.ts +0 -11
  35. package/dist/components/MutableNotebook/NotebookStorageProvider.d.ts +0 -14
  36. package/dist/components/MutableNotebook/index.d.ts +0 -5
  37. package/dist/components/NotebookManager.d.ts +0 -86
  38. package/src/components/MutableNotebook/BrowserNotebookStorage.ts +0 -58
  39. package/src/components/MutableNotebook/MutableNotebookList.tsx +0 -69
  40. package/src/components/MutableNotebook/NotebookStorage.ts +0 -27
  41. package/src/components/MutableNotebook/NotebookStorageProvider.tsx +0 -43
  42. package/src/components/MutableNotebook/index.ts +0 -8
  43. package/src/components/NotebookManager.ts +0 -225
  44. /package/dist/components/{MutableNotebook → Workbook}/ModelPicker.d.ts +0 -0
  45. /package/src/components/{MutableNotebook → Workbook}/ModelPicker.tsx +0 -0
@@ -20,21 +20,22 @@ import React from "react";
20
20
  import { Configuration, ModelsApi } from "../../client";
21
21
  import { useRouterClickHandler } from "../click_helper";
22
22
  import { SourceAndPath } from "../Model/SourcesExplorer";
23
- import { NotebookManager } from "../NotebookManager";
23
+ import { WorkbookManager } from "./WorkbookManager";
24
24
  import { usePackage } from "../Package";
25
25
  import { useServer } from "../ServerProvider";
26
26
  import { StyledCard, StyledCardContent, StyledCardMedia } from "../styles";
27
27
  import { MutableCell } from "./MutableCell";
28
- import { useNotebookStorage } from "./NotebookStorageProvider";
28
+ import { useWorkbookStorage } from "./WorkbookStorageProvider";
29
29
 
30
30
  import * as Malloy from "@malloydata/malloy-interfaces";
31
31
  import { ModelPicker } from "./ModelPicker";
32
32
  import { getAxiosConfig } from "../../hooks";
33
+ import { WorkbookLocator } from "./WorkbookStorage";
33
34
 
34
35
  const modelsApi = new ModelsApi(new Configuration());
35
36
 
36
- interface MutableNotebookProps {
37
- notebookPath?: string;
37
+ interface WorkbookProps {
38
+ workbookPath?: WorkbookLocator;
38
39
  expandCodeCells?: boolean;
39
40
  expandEmbeddings?: boolean;
40
41
  hideEmbeddingIcons?: boolean;
@@ -45,28 +46,32 @@ interface PathToSources {
45
46
  sourceInfos: Malloy.SourceInfo[];
46
47
  }
47
48
 
48
- export default function MutableNotebook({
49
- notebookPath,
49
+ export default function Workbook({
50
+ workbookPath,
50
51
  expandCodeCells,
51
52
  expandEmbeddings,
52
53
  hideEmbeddingIcons,
53
- }: MutableNotebookProps) {
54
+ }: WorkbookProps) {
54
55
  const navigate = useRouterClickHandler();
55
- const { projectName, packageName, versionId } = usePackage();
56
+ const packageContext = usePackage();
57
+ const { projectName, packageName, versionId } = packageContext;
56
58
  const { server, getAccessToken } = useServer();
57
- const { notebookStorage, userContext } = useNotebookStorage();
59
+ const { workbookStorage } = useWorkbookStorage();
60
+ const [lastError, setLastError] = React.useState<string | undefined>(
61
+ undefined,
62
+ );
58
63
  if (!projectName || !packageName) {
59
64
  throw new Error(
60
65
  "Project and package must be provided via PubliserPackageProvider",
61
66
  );
62
67
  }
63
- if (!notebookStorage || !userContext) {
68
+ if (!workbookStorage) {
64
69
  throw new Error(
65
- "Notebook storage and user context must be provided via NotebookStorageProvider",
70
+ "Workbook storage be provided via WorkbookStorageProvider",
66
71
  );
67
72
  }
68
- const [notebookData, setNotebookData] = React.useState<
69
- NotebookManager | undefined
73
+ const [workbookData, setWorkbookData] = React.useState<
74
+ WorkbookManager | undefined
70
75
  >();
71
76
  const [editingMalloyIndex, setEditingMalloyIndex] = React.useState<
72
77
  number | undefined
@@ -87,11 +92,11 @@ export default function MutableNotebook({
87
92
  setMenuIndex(null);
88
93
  };
89
94
  const handleAddCell = (isMarkdown: boolean, index: number) => {
90
- notebookData.insertCell(index, {
95
+ workbookData.insertCell(index, {
91
96
  isMarkdown,
92
97
  value: "",
93
98
  });
94
- saveNotebook();
99
+ saveWorkbook();
95
100
  if (isMarkdown) {
96
101
  setEditingMarkdownIndex(index);
97
102
  } else {
@@ -105,9 +110,16 @@ export default function MutableNotebook({
105
110
  setDeleteDialogOpen(true);
106
111
  };
107
112
 
108
- const handleDeleteConfirm = (event?: React.MouseEvent) => {
109
- if (notebookPath && notebookStorage && userContext) {
110
- notebookStorage.deleteNotebook(userContext, notebookPath);
113
+ const handleDeleteConfirm = async (event?: React.MouseEvent) => {
114
+ if (workbookPath && workbookStorage && packageContext) {
115
+ await workbookStorage
116
+ .deleteWorkbook(packageContext, workbookPath)
117
+ .then(() => {
118
+ setLastError(undefined);
119
+ })
120
+ .catch((error) => {
121
+ setLastError(`Error deleting workbook: ${error.message}`);
122
+ });
111
123
  }
112
124
  setDeleteDialogOpen(false);
113
125
  navigate(`/${projectName}/${packageName}`, event);
@@ -117,12 +129,17 @@ export default function MutableNotebook({
117
129
  setDeleteDialogOpen(false);
118
130
  };
119
131
 
120
- const saveNotebook = React.useCallback(() => {
121
- setNotebookData(notebookData.saveNotebook());
122
- }, [notebookData]);
132
+ const saveWorkbook = React.useCallback(async () => {
133
+ try {
134
+ setWorkbookData(await workbookData.saveWorkbook());
135
+ setLastError(undefined);
136
+ } catch (error) {
137
+ setLastError(`Error saving workbook: ${error.message}`);
138
+ }
139
+ }, [workbookData]);
123
140
  React.useEffect(() => {
124
141
  // Load SourceInfos from selected models and sync PathsToSources
125
- if (!notebookData) {
142
+ if (!workbookData) {
126
143
  return;
127
144
  }
128
145
 
@@ -136,7 +153,7 @@ export default function MutableNotebook({
136
153
  const newSourceAndPaths = [];
137
154
  const promises = [];
138
155
 
139
- for (const model of notebookData.getModels()) {
156
+ for (const model of workbookData.getModels()) {
140
157
  if (!modelPathToSourceInfo.has(model)) {
141
158
  console.log("Fetching model from Publisher", model);
142
159
  promises.push(
@@ -173,9 +190,9 @@ export default function MutableNotebook({
173
190
 
174
191
  fetchModels();
175
192
  }, [
176
- // Note this cannot depend on sourceAndPaths because it will cause an infinite loop.
193
+ // Work this cannot depend on sourceAndPaths because it will cause an infinite loop.
177
194
  getAccessToken,
178
- notebookData,
195
+ workbookData,
179
196
  packageName,
180
197
  projectName,
181
198
  server,
@@ -183,19 +200,19 @@ export default function MutableNotebook({
183
200
  ]);
184
201
 
185
202
  React.useEffect(() => {
186
- if (!notebookPath) {
203
+ if (!workbookPath) {
187
204
  return;
188
205
  }
189
- setNotebookData(
190
- NotebookManager.loadNotebook(
191
- notebookStorage,
192
- userContext,
193
- notebookPath,
194
- ),
195
- );
196
- }, [notebookPath, notebookStorage, userContext]);
206
+ WorkbookManager.loadWorkbook(
207
+ workbookStorage,
208
+ packageContext,
209
+ workbookPath,
210
+ ).then((workbookData) => {
211
+ setWorkbookData(workbookData);
212
+ });
213
+ }, [workbookPath, workbookStorage, packageContext]);
197
214
 
198
- if (!notebookData) {
215
+ if (!workbookData) {
199
216
  return <div>Loading...</div>;
200
217
  }
201
218
  const getSourceList = (sourceAndPaths: PathToSources[]): SourceAndPath[] => {
@@ -246,14 +263,21 @@ export default function MutableNotebook({
246
263
  flex: 2,
247
264
  }}
248
265
  >
249
- {plusButton(false, notebookData.getCells().length)}
250
- {plusButton(true, notebookData.getCells().length)}
266
+ {plusButton(false, workbookData.getCells().length)}
267
+ {plusButton(true, workbookData.getCells().length)}
251
268
  </Box>
252
269
  );
253
270
 
254
271
  return (
255
272
  <StyledCard variant="outlined">
256
273
  <StyledCardContent>
274
+ {lastError && (
275
+ <Box sx={{ mb: 2 }}>
276
+ <Typography color="error" variant="body2">
277
+ {lastError}
278
+ </Typography>
279
+ </Box>
280
+ )}
257
281
  <Stack
258
282
  sx={{
259
283
  flexDirection: "row",
@@ -281,7 +305,7 @@ export default function MutableNotebook({
281
305
  ml: 1,
282
306
  }}
283
307
  >
284
- {`${projectName} > ${packageName} > ${notebookPath}`}
308
+ {`${projectName} > ${packageName} > ${workbookPath.path}`}
285
309
  </Typography>
286
310
  </Stack>
287
311
  <Stack sx={{ display: "flex", flexDirection: "row", gap: 1 }}>
@@ -293,7 +317,7 @@ export default function MutableNotebook({
293
317
  mb: 1,
294
318
  }}
295
319
  >
296
- <ExportMalloyButton notebookData={notebookData} />
320
+ <ExportMalloyButton workbookData={workbookData} />
297
321
  </Box>
298
322
  <Box
299
323
  sx={{
@@ -314,12 +338,12 @@ export default function MutableNotebook({
314
338
  open={deleteDialogOpen}
315
339
  onClose={handleDeleteCancel}
316
340
  >
317
- <DialogTitle>Delete Notebook</DialogTitle>
341
+ <DialogTitle>Delete Workbook</DialogTitle>
318
342
  <DialogContent>
319
343
  <DialogContentText>
320
- Are you sure you want to delete the notebook
344
+ Are you sure you want to delete the workbook
321
345
  &quot;
322
- {notebookPath}&quot;? This action cannot be
346
+ {workbookPath.path}&quot;? This action cannot be
323
347
  undone.
324
348
  </DialogContentText>
325
349
  </DialogContent>
@@ -355,10 +379,10 @@ export default function MutableNotebook({
355
379
  >
356
380
  <Box sx={{ flex: 1 }}>
357
381
  <ModelPicker
358
- initialSelectedModels={notebookData.getModels()}
382
+ initialSelectedModels={workbookData.getModels()}
359
383
  onModelChange={(models) => {
360
- setNotebookData(notebookData.setModels(models));
361
- saveNotebook();
384
+ setWorkbookData(workbookData.setModels(models));
385
+ saveWorkbook();
362
386
  }}
363
387
  />
364
388
  </Box>
@@ -367,7 +391,7 @@ export default function MutableNotebook({
367
391
  </StyledCardContent>
368
392
  <StyledCardMedia>
369
393
  <Stack>
370
- {notebookData.getCells().length === 0 && (
394
+ {workbookData.getCells().length === 0 && (
371
395
  <>
372
396
  <Typography
373
397
  sx={{
@@ -387,12 +411,12 @@ export default function MutableNotebook({
387
411
  </Typography>
388
412
  </>
389
413
  )}
390
- {notebookData.getCells().map((cell, index) => (
414
+ {workbookData.getCells().map((cell, index) => (
391
415
  <React.Fragment
392
- key={`${index}-${notebookData.getCells().length}`}
416
+ key={`${index}-${workbookData.getCells().length}`}
393
417
  >
394
418
  <MutableCell
395
- key={`${index}-${cell.isMarkdown}-${notebookPath}-${projectName}-${packageName}`}
419
+ key={`${index}-${cell.isMarkdown}-${workbookPath}-${projectName}-${packageName}`}
396
420
  cell={cell}
397
421
  addButtonCallback={(isMarkdown) =>
398
422
  plusButton(isMarkdown, index)
@@ -404,12 +428,12 @@ export default function MutableNotebook({
404
428
  editingMarkdown={editingMarkdownIndex === index}
405
429
  editingMalloy={editingMalloyIndex === index}
406
430
  onDelete={() => {
407
- setNotebookData(notebookData.deleteCell(index));
408
- saveNotebook();
431
+ setWorkbookData(workbookData.deleteCell(index));
432
+ saveWorkbook();
409
433
  }}
410
434
  onCellChange={(cell) => {
411
- setNotebookData(notebookData.setCell(index, cell));
412
- saveNotebook();
435
+ setWorkbookData(workbookData.setCell(index, cell));
436
+ saveWorkbook();
413
437
  }}
414
438
  onEdit={() => {
415
439
  if (cell.isMarkdown) {
@@ -459,14 +483,14 @@ export default function MutableNotebook({
459
483
  }
460
484
 
461
485
  function ExportMalloyButton({
462
- notebookData,
486
+ workbookData,
463
487
  }: {
464
- notebookData: NotebookManager;
488
+ workbookData: WorkbookManager;
465
489
  }) {
466
490
  const [copied, setCopied] = React.useState(false);
467
491
  const handleExport = async () => {
468
- if (!notebookData) return;
469
- const malloy = notebookData.toMalloyNotebook();
492
+ if (!workbookData) return;
493
+ const malloy = workbookData.toMalloyWorkbook();
470
494
  try {
471
495
  await navigator.clipboard.writeText(malloy);
472
496
  setCopied(true);
@@ -0,0 +1,111 @@
1
+ import {
2
+ Box,
3
+ Divider,
4
+ List,
5
+ ListItem,
6
+ ListItemText,
7
+ Typography,
8
+ } from "@mui/material";
9
+ import React from "react";
10
+ import { useWorkbookStorage } from "./WorkbookStorageProvider";
11
+ import { usePackage } from "../Package";
12
+ import { WorkbookLocator } from "./WorkbookStorage";
13
+
14
+ interface WorkbookListProps {
15
+ onWorkbookClick: (
16
+ workbook: WorkbookLocator,
17
+ event: React.MouseEvent,
18
+ ) => void;
19
+ }
20
+
21
+ export function WorkbookList({ onWorkbookClick }: WorkbookListProps) {
22
+ const { workbookStorage } = useWorkbookStorage();
23
+ const packageContext = usePackage();
24
+ const [workbooks, setWorkbooks] = React.useState<WorkbookLocator[]>([]);
25
+ const [lastError, setLastError] = React.useState<string | undefined>(
26
+ undefined,
27
+ );
28
+
29
+ React.useEffect(() => {
30
+ if (workbookStorage) {
31
+ workbookStorage
32
+ .listWorkspaces(packageContext, false)
33
+ .then((workspaces) => {
34
+ const allWorkbooks: WorkbookLocator[] = [];
35
+ Promise.all(
36
+ workspaces.map(async (workspace) => {
37
+ await workbookStorage
38
+ .listWorkbooks(workspace, packageContext)
39
+ .then((newWorkbooks) => {
40
+ allWorkbooks.push(...newWorkbooks);
41
+ })
42
+ .catch((error) => {
43
+ setLastError(
44
+ `Error listing workbooks: ${error.message}`,
45
+ );
46
+ });
47
+ }),
48
+ ).then(() => {
49
+ setWorkbooks(allWorkbooks);
50
+ setLastError(undefined);
51
+ });
52
+ });
53
+ }
54
+ }, [workbookStorage, packageContext]);
55
+
56
+ return (
57
+ <>
58
+ {lastError && (
59
+ <Box sx={{ mb: 2 }}>
60
+ <Typography color="error" variant="body2">
61
+ {lastError}
62
+ </Typography>
63
+ </Box>
64
+ )}
65
+ <Divider />
66
+ <Box
67
+ sx={{
68
+ maxHeight: "300px",
69
+ overflow: "auto",
70
+ "&::-webkit-scrollbar": {
71
+ width: "8px",
72
+ },
73
+ "&::-webkit-scrollbar-track": {
74
+ background: "transparent",
75
+ },
76
+ "&::-webkit-scrollbar-thumb": {
77
+ background: "rgba(0,0,0,0.2)",
78
+ borderRadius: "4px",
79
+ },
80
+ }}
81
+ >
82
+ <List dense>
83
+ {workbooks.length === 0 && (
84
+ <ListItem>
85
+ <ListItemText
86
+ primary="No workbooks found."
87
+ sx={{ textAlign: "center" }}
88
+ />
89
+ </ListItem>
90
+ )}
91
+ {workbooks.map((workbook) => (
92
+ <ListItem
93
+ key={workbook.path}
94
+ onClick={(event: React.MouseEvent) =>
95
+ onWorkbookClick(workbook, event)
96
+ }
97
+ sx={{
98
+ cursor: "pointer",
99
+ "&:hover": {
100
+ backgroundColor: "action.hover",
101
+ },
102
+ }}
103
+ >
104
+ <ListItemText primary={workbook.path} />
105
+ </ListItem>
106
+ ))}
107
+ </List>
108
+ </Box>
109
+ </>
110
+ );
111
+ }
@@ -0,0 +1,230 @@
1
+ import { PackageContextProps } from "../Package";
2
+ import type { WorkbookLocator, WorkbookStorage } from "./WorkbookStorage";
3
+
4
+ /**
5
+ * Interface representing the data structure of a Mutable Workbook
6
+ * @interface WorkbookData
7
+ * @property {string[]} models - Array of model paths used in the workbook
8
+ * @property {WorkbookCellValue[]} cells - Array of cells in the workbook
9
+ * @property {WorkbookLocator} workbookPath - Path to the workbook file (relative to project/package)
10
+ */
11
+ export interface WorkbookData {
12
+ models: string[];
13
+ cells: WorkbookCellValue[];
14
+ workbookPath: WorkbookLocator;
15
+ }
16
+
17
+ /**
18
+ * Interface representing a cell in the workbook
19
+ * @interface WorkbookCellValue
20
+ * @property {boolean} isMarkdown - Whether the cell is a markdown cell
21
+ * @property {string} [value] - The content of the cell
22
+ * @property {string} [result] - The result of executing the cell
23
+ * @property {string} [modelPath] - modelPath associated with the query in the cell
24
+ * @property {string} [sourceName] - Name of the source associated with the cell
25
+ * @property {string} [queryInfo] - Information about the query in the cell
26
+ */
27
+ export interface WorkbookCellValue {
28
+ isMarkdown: boolean;
29
+ value?: string;
30
+ result?: string;
31
+ modelPath?: string;
32
+ sourceName?: string;
33
+ queryInfo?: string;
34
+ }
35
+
36
+ /**
37
+ * Class for managing workbook operations
38
+ * @class WorkbookManager
39
+ */
40
+ export class WorkbookManager {
41
+ private isSaved: boolean;
42
+ private workbookStorage: WorkbookStorage;
43
+ private packageContext: PackageContextProps;
44
+
45
+ /**
46
+ * Creates a new WorkbookManager instance
47
+ * @param {WorkbookStorage} workbookStorage - Storage implementation
48
+ * @param {PackageContextProps} packageContext - Package context for storage
49
+ * @param {WorkbookData} workbookData - Initial workbook data
50
+ */
51
+ constructor(
52
+ workbookStorage: WorkbookStorage,
53
+ packageContext: PackageContextProps,
54
+ private workbookData: WorkbookData,
55
+ ) {
56
+ this.workbookStorage = workbookStorage;
57
+ this.packageContext = packageContext;
58
+ if (this.workbookData) {
59
+ this.isSaved = true;
60
+ } else {
61
+ this.workbookData = {
62
+ models: [],
63
+ cells: [],
64
+ workbookPath: undefined,
65
+ };
66
+ this.isSaved = false;
67
+ }
68
+ }
69
+
70
+ /**
71
+ * Gets the current workbook data
72
+ * @returns {WorkbookData} The current workbook data
73
+ */
74
+ getWorkbookData(): WorkbookData {
75
+ return this.workbookData;
76
+ }
77
+
78
+ /**
79
+ * Gets the current workbook path
80
+ * @returns {string} The path to the workbook
81
+ */
82
+ getWorkbookPath(): WorkbookLocator {
83
+ return this.workbookData.workbookPath;
84
+ }
85
+
86
+ /**
87
+ * Renames the workbook and updates storage
88
+ * @param {string} workbookPath - New path for the workbook
89
+ * @returns {WorkbookManager} The updated WorkbookManager instance
90
+ */
91
+ async renameWorkbook(workbookPath: string): Promise<WorkbookManager> {
92
+ if (this.workbookData.workbookPath.path !== workbookPath) {
93
+ try {
94
+ await this.workbookStorage.moveWorkbook(
95
+ this.packageContext,
96
+ this.workbookData.workbookPath,
97
+ {
98
+ path: workbookPath,
99
+ workspace: this.workbookData.workbookPath.workspace,
100
+ },
101
+ );
102
+ } catch {
103
+ // ignore if not found
104
+ }
105
+ }
106
+ this.workbookData.workbookPath.path = workbookPath;
107
+ this.isSaved = false;
108
+ return await this.saveWorkbook();
109
+ }
110
+
111
+ getCells(): WorkbookCellValue[] {
112
+ return this.workbookData.cells;
113
+ }
114
+ deleteCell(index: number): WorkbookManager {
115
+ this.workbookData.cells = [
116
+ ...this.workbookData.cells.slice(0, index),
117
+ ...this.workbookData.cells.slice(index + 1),
118
+ ];
119
+ this.isSaved = false;
120
+ return this;
121
+ }
122
+ insertCell(index: number, cell: WorkbookCellValue): WorkbookManager {
123
+ this.workbookData.cells = [
124
+ ...this.workbookData.cells.slice(0, index),
125
+ cell,
126
+ ...this.workbookData.cells.slice(index),
127
+ ];
128
+ this.isSaved = false;
129
+ return this;
130
+ }
131
+ setCell(index: number, cell: WorkbookCellValue): WorkbookManager {
132
+ this.workbookData.cells[index] = cell;
133
+ this.isSaved = false;
134
+ return this;
135
+ }
136
+ setModels(models: string[]): WorkbookManager {
137
+ this.workbookData.models = models;
138
+ this.isSaved = false;
139
+ return this;
140
+ }
141
+ getModels(): string[] {
142
+ return this.workbookData.models;
143
+ }
144
+
145
+ updateWorkbookData(workbookData: WorkbookData): WorkbookManager {
146
+ this.workbookData = workbookData;
147
+ this.isSaved = false;
148
+ return this;
149
+ }
150
+
151
+ async saveWorkbook(): Promise<WorkbookManager> {
152
+ if (!this.isSaved) {
153
+ if (!this.workbookData.workbookPath) {
154
+ throw new Error("Workbook path is not set");
155
+ }
156
+ await this.workbookStorage.saveWorkbook(
157
+ this.packageContext,
158
+ this.workbookData.workbookPath,
159
+ JSON.stringify(this.workbookData),
160
+ );
161
+ this.isSaved = true;
162
+ }
163
+ return new WorkbookManager(
164
+ this.workbookStorage,
165
+ this.packageContext,
166
+ this.workbookData,
167
+ );
168
+ }
169
+
170
+ /**
171
+ * Converts the workbook data to a Malloy workbook string.
172
+ * @returns {string} The Malloy workbook string
173
+ */
174
+ toMalloyWorkbook(): string {
175
+ return this.workbookData.cells
176
+ .map((cell) => {
177
+ if (cell.isMarkdown) {
178
+ return ">>>markdown\n" + cell.value;
179
+ } else {
180
+ return (
181
+ ">>>malloy\n" +
182
+ `import {${cell.sourceName}}" from '${cell.modelPath}'"\n` +
183
+ cell.value +
184
+ "\n"
185
+ );
186
+ }
187
+ })
188
+ .join("\n");
189
+ }
190
+
191
+ static newWorkbook(
192
+ workbookStorage: WorkbookStorage,
193
+ packageContext: PackageContextProps,
194
+ ): WorkbookManager {
195
+ return new WorkbookManager(workbookStorage, packageContext, undefined);
196
+ }
197
+
198
+ /**
199
+ * Creates a new workbook manager by loading from local storage.
200
+ * Returns an empty instance if the workbook is not found.
201
+ * @param workbookStorage - The storage implementation
202
+ * @param userContext - The user context for storage
203
+ * @param workbookPath - The path to the workbook file (relative to project/package)
204
+ */
205
+ static async loadWorkbook(
206
+ workbookStorage: WorkbookStorage,
207
+ packageContext: PackageContextProps,
208
+ workbookPath: WorkbookLocator,
209
+ ): Promise<WorkbookManager> {
210
+ let workbookData: WorkbookData | undefined = undefined;
211
+ console.log("loadWorkbook", workbookPath);
212
+ try {
213
+ const saved = await workbookStorage.getWorkbook(
214
+ packageContext,
215
+ workbookPath,
216
+ );
217
+ if (saved) {
218
+ workbookData = JSON.parse(saved);
219
+ }
220
+ } catch {
221
+ // Not found, create a new workbook
222
+ workbookData = {
223
+ models: [],
224
+ cells: [],
225
+ workbookPath: workbookPath,
226
+ };
227
+ }
228
+ return new WorkbookManager(workbookStorage, packageContext, workbookData);
229
+ }
230
+ }