@malloy-publisher/sdk 0.0.17 → 0.0.18
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/.prettierrc +3 -3
- package/README.md +0 -1
- package/openapitools.json +5 -5
- package/package.json +2 -1
- package/src/components/Model/Model.tsx +13 -164
- package/src/components/Model/SourcesExplorer.tsx +302 -0
- package/src/components/Model/index.ts +3 -0
- package/src/components/MutableNotebook/BrowserNotebookStorage.ts +58 -0
- package/src/components/MutableNotebook/EditableMalloyCell.tsx +47 -0
- package/src/components/MutableNotebook/ModelPicker.tsx +152 -0
- package/src/components/MutableNotebook/MutableCell.tsx +328 -0
- package/src/components/MutableNotebook/MutableNotebook.tsx +372 -0
- package/src/components/MutableNotebook/MutableNotebookList.tsx +138 -0
- package/src/components/MutableNotebook/NotebookStorage.ts +27 -0
- package/src/components/MutableNotebook/NotebookStorageProvider.tsx +43 -0
- package/src/components/MutableNotebook/index.ts +5 -0
- package/src/components/NotebookManager.ts +220 -0
- package/src/components/Package/Databases.tsx +121 -71
- package/src/components/Package/Package.tsx +10 -1
- package/src/components/Package/PublisherPackageProvider.tsx +48 -0
- package/src/components/Package/index.ts +1 -0
- package/src/components/QueryResult/QueryResult.tsx +1 -3
- package/src/components/index.ts +1 -0
- package/vite.config.ts.timestamp-1732998513502-40117fb4923d1.mjs +39 -41
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
// TODO(jjs) - Export to .malloynb
|
|
2
|
+
// TOOD(jjs) - Import via Publisher API that parses whole NB
|
|
3
|
+
|
|
4
|
+
import React from "react";
|
|
5
|
+
import Stack from "@mui/material/Stack";
|
|
6
|
+
import {
|
|
7
|
+
Box,
|
|
8
|
+
Button,
|
|
9
|
+
CardActions,
|
|
10
|
+
TextField,
|
|
11
|
+
Typography,
|
|
12
|
+
Menu,
|
|
13
|
+
MenuItem,
|
|
14
|
+
} from "@mui/material";
|
|
15
|
+
import { StyledCard, StyledCardContent, StyledCardMedia } from "../styles";
|
|
16
|
+
import { MutableCell } from "./MutableCell";
|
|
17
|
+
import { Configuration, ModelsApi } from "../../client";
|
|
18
|
+
import { ModelPicker } from "./ModelPicker";
|
|
19
|
+
import { usePublisherPackage } from "../Package";
|
|
20
|
+
import { NotebookManager } from "../NotebookManager";
|
|
21
|
+
import { SourceAndPath } from "../Model/SourcesExplorer";
|
|
22
|
+
import { useNotebookStorage } from "./NotebookStorageProvider";
|
|
23
|
+
|
|
24
|
+
import * as Malloy from "@malloydata/malloy-interfaces";
|
|
25
|
+
|
|
26
|
+
const modelsApi = new ModelsApi(new Configuration());
|
|
27
|
+
|
|
28
|
+
interface MutableNotebookProps {
|
|
29
|
+
inputNotebookPath: string | undefined;
|
|
30
|
+
expandCodeCells?: boolean;
|
|
31
|
+
expandEmbeddings?: boolean;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function getNotebookPath() {
|
|
35
|
+
const params = new URLSearchParams(window.location.hash.substring(1));
|
|
36
|
+
return params.get("notebookPath");
|
|
37
|
+
}
|
|
38
|
+
interface PathToSources {
|
|
39
|
+
modelPath: string;
|
|
40
|
+
sourceInfos: Malloy.SourceInfo[];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export default function MutableNotebook({
|
|
44
|
+
inputNotebookPath,
|
|
45
|
+
expandCodeCells,
|
|
46
|
+
expandEmbeddings,
|
|
47
|
+
}: MutableNotebookProps) {
|
|
48
|
+
const { server, projectName, packageName, versionId, accessToken } =
|
|
49
|
+
usePublisherPackage();
|
|
50
|
+
if (!projectName || !packageName) {
|
|
51
|
+
throw new Error(
|
|
52
|
+
"Project and package must be provided via PubliserPackageProvider",
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
const { notebookStorage, userContext } = useNotebookStorage();
|
|
56
|
+
if (!notebookStorage || !userContext) {
|
|
57
|
+
throw new Error(
|
|
58
|
+
"Notebook storage and user context must be provided via NotebookStorageProvider",
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
const [notebookData, setNotebookData] = React.useState<
|
|
62
|
+
NotebookManager | undefined
|
|
63
|
+
>();
|
|
64
|
+
const [notebookPath, setNotebookPath] = React.useState(inputNotebookPath);
|
|
65
|
+
const [editingMalloyIndex, setEditingMalloyIndex] = React.useState<
|
|
66
|
+
number | undefined
|
|
67
|
+
>();
|
|
68
|
+
const [editingMarkdownIndex, setEditingMarkdownIndex] = React.useState<
|
|
69
|
+
number | undefined
|
|
70
|
+
>();
|
|
71
|
+
const [sourceAndPaths, setSourceAndPaths] = React.useState<PathToSources[]>(
|
|
72
|
+
[],
|
|
73
|
+
);
|
|
74
|
+
const [menuAnchorEl, setMenuAnchorEl] = React.useState<null | HTMLElement>(
|
|
75
|
+
null,
|
|
76
|
+
);
|
|
77
|
+
const [menuIndex, setMenuIndex] = React.useState<number | null>(null);
|
|
78
|
+
const menuOpen = Boolean(menuAnchorEl);
|
|
79
|
+
const handleMenuClick = (
|
|
80
|
+
event: React.MouseEvent<HTMLButtonElement>,
|
|
81
|
+
index: number,
|
|
82
|
+
) => {
|
|
83
|
+
setMenuAnchorEl(event.currentTarget);
|
|
84
|
+
setMenuIndex(index);
|
|
85
|
+
};
|
|
86
|
+
const handleMenuClose = () => {
|
|
87
|
+
setMenuAnchorEl(null);
|
|
88
|
+
setMenuIndex(null);
|
|
89
|
+
};
|
|
90
|
+
const handleAddCell = (isMarkdown: boolean, index: number) => {
|
|
91
|
+
notebookData.insertCell(index, {
|
|
92
|
+
isMarkdown,
|
|
93
|
+
value: "",
|
|
94
|
+
});
|
|
95
|
+
saveNotebook();
|
|
96
|
+
if (isMarkdown) {
|
|
97
|
+
setEditingMarkdownIndex(index);
|
|
98
|
+
} else {
|
|
99
|
+
setEditingMalloyIndex(index);
|
|
100
|
+
}
|
|
101
|
+
handleMenuClose();
|
|
102
|
+
};
|
|
103
|
+
const saveNotebook = React.useCallback(() => {
|
|
104
|
+
setNotebookData(notebookData.saveNotebook());
|
|
105
|
+
}, [notebookData]);
|
|
106
|
+
React.useEffect(() => {
|
|
107
|
+
// Load SourceInfos from selected models and sync PathsToSources
|
|
108
|
+
if (!notebookData) {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
const modelPathToSourceInfo = new Map(
|
|
112
|
+
sourceAndPaths.map(({ modelPath, sourceInfos }) => [
|
|
113
|
+
modelPath,
|
|
114
|
+
sourceInfos,
|
|
115
|
+
]),
|
|
116
|
+
);
|
|
117
|
+
const newSourceAndPaths = [];
|
|
118
|
+
const promises = [];
|
|
119
|
+
for (const model of notebookData.getModels()) {
|
|
120
|
+
if (!modelPathToSourceInfo.has(model)) {
|
|
121
|
+
console.log("Fetching model from Publisher", model);
|
|
122
|
+
promises.push(
|
|
123
|
+
modelsApi
|
|
124
|
+
.getModel(projectName, packageName, model, versionId, {
|
|
125
|
+
baseURL: server,
|
|
126
|
+
withCredentials: !accessToken,
|
|
127
|
+
})
|
|
128
|
+
.then((data) => ({
|
|
129
|
+
modelPath: model,
|
|
130
|
+
sourceInfos: data.data.sourceInfos.map((source) =>
|
|
131
|
+
JSON.parse(source),
|
|
132
|
+
),
|
|
133
|
+
})),
|
|
134
|
+
);
|
|
135
|
+
} else {
|
|
136
|
+
newSourceAndPaths.push({
|
|
137
|
+
modelPath: model,
|
|
138
|
+
sourceInfos: modelPathToSourceInfo.get(model),
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
if (promises.length > 0) {
|
|
143
|
+
Promise.all(promises).then((loadedSourceAndPaths) => {
|
|
144
|
+
setSourceAndPaths([...newSourceAndPaths, ...loadedSourceAndPaths]);
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
}, [
|
|
148
|
+
accessToken,
|
|
149
|
+
notebookData,
|
|
150
|
+
packageName,
|
|
151
|
+
projectName,
|
|
152
|
+
server,
|
|
153
|
+
sourceAndPaths,
|
|
154
|
+
versionId,
|
|
155
|
+
]);
|
|
156
|
+
|
|
157
|
+
React.useEffect(() => {
|
|
158
|
+
if (!notebookData) {
|
|
159
|
+
setNotebookData(
|
|
160
|
+
NotebookManager.loadNotebook(
|
|
161
|
+
notebookStorage,
|
|
162
|
+
userContext,
|
|
163
|
+
getNotebookPath(),
|
|
164
|
+
),
|
|
165
|
+
);
|
|
166
|
+
setNotebookPath(getNotebookPath());
|
|
167
|
+
}
|
|
168
|
+
}, [notebookData, packageName, projectName, notebookStorage, userContext]);
|
|
169
|
+
|
|
170
|
+
if (!notebookData) {
|
|
171
|
+
return <div>Loading...</div>;
|
|
172
|
+
}
|
|
173
|
+
const getSourceList = (sourceAndPaths: PathToSources[]): SourceAndPath[] => {
|
|
174
|
+
const sourceAndPath = [];
|
|
175
|
+
for (const sources of sourceAndPaths) {
|
|
176
|
+
for (const sourceInfo of sources.sourceInfos) {
|
|
177
|
+
sourceAndPath.push({
|
|
178
|
+
modelPath: sources.modelPath,
|
|
179
|
+
sourceInfo: sourceInfo,
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
return sourceAndPath;
|
|
184
|
+
};
|
|
185
|
+
const createButtons = (index: number) => {
|
|
186
|
+
return (
|
|
187
|
+
<CardActions
|
|
188
|
+
sx={{
|
|
189
|
+
padding: "0px 10px 0px 10px",
|
|
190
|
+
mb: "auto",
|
|
191
|
+
mt: "auto",
|
|
192
|
+
justifyContent: "flex-end",
|
|
193
|
+
}}
|
|
194
|
+
>
|
|
195
|
+
<Button
|
|
196
|
+
variant="outlined"
|
|
197
|
+
size="small"
|
|
198
|
+
startIcon={<AddIcon />}
|
|
199
|
+
onClick={(e) => handleMenuClick(e, index)}
|
|
200
|
+
>
|
|
201
|
+
New Cell
|
|
202
|
+
</Button>
|
|
203
|
+
</CardActions>
|
|
204
|
+
);
|
|
205
|
+
};
|
|
206
|
+
return (
|
|
207
|
+
<StyledCard variant="outlined">
|
|
208
|
+
<StyledCardContent>
|
|
209
|
+
<Stack
|
|
210
|
+
sx={{
|
|
211
|
+
flexDirection: "row",
|
|
212
|
+
justifyContent: "space-between",
|
|
213
|
+
}}
|
|
214
|
+
>
|
|
215
|
+
<Box sx={{ display: "flex", alignItems: "top", gap: 1 }}>
|
|
216
|
+
<Typography
|
|
217
|
+
sx={{
|
|
218
|
+
fontSize: "150%",
|
|
219
|
+
minHeight: "56px",
|
|
220
|
+
fontWeight: "bold",
|
|
221
|
+
}}
|
|
222
|
+
>
|
|
223
|
+
Notebook :
|
|
224
|
+
</Typography>
|
|
225
|
+
<TextField
|
|
226
|
+
value={notebookPath}
|
|
227
|
+
onChange={(e) => {
|
|
228
|
+
setNotebookPath(e.target.value);
|
|
229
|
+
}}
|
|
230
|
+
onBlur={(e) => {
|
|
231
|
+
const url = new URL(window.location.href);
|
|
232
|
+
url.hash = `notebookPath=${e.target.value}`;
|
|
233
|
+
notebookData.renameNotebook(e.target.value);
|
|
234
|
+
window.history.pushState({}, "", url);
|
|
235
|
+
}}
|
|
236
|
+
size="medium"
|
|
237
|
+
variant="standard"
|
|
238
|
+
error={!notebookPath}
|
|
239
|
+
helperText={
|
|
240
|
+
!notebookPath
|
|
241
|
+
? "Please enter a notebook name"
|
|
242
|
+
: undefined
|
|
243
|
+
}
|
|
244
|
+
sx={{ flex: 1 }}
|
|
245
|
+
/>
|
|
246
|
+
</Box>
|
|
247
|
+
<Box
|
|
248
|
+
sx={{ display: "flex", alignItems: "center", mt: 1, mb: 1 }}
|
|
249
|
+
>
|
|
250
|
+
<ExportMalloyButton notebookData={notebookData} />
|
|
251
|
+
</Box>
|
|
252
|
+
</Stack>
|
|
253
|
+
</StyledCardContent>
|
|
254
|
+
<ModelPicker
|
|
255
|
+
initialSelectedModels={notebookData.getModels()}
|
|
256
|
+
onModelChange={(models) => {
|
|
257
|
+
setNotebookData(notebookData.setModels(models));
|
|
258
|
+
saveNotebook();
|
|
259
|
+
}}
|
|
260
|
+
/>
|
|
261
|
+
|
|
262
|
+
<StyledCardMedia>
|
|
263
|
+
<Stack>
|
|
264
|
+
{notebookData.getCells().map((cell, index) => (
|
|
265
|
+
<React.Fragment
|
|
266
|
+
key={`${index}-${notebookData.getCells().length}`}
|
|
267
|
+
>
|
|
268
|
+
<MutableCell
|
|
269
|
+
cell={cell}
|
|
270
|
+
newCell={createButtons(index)}
|
|
271
|
+
sourceAndPaths={getSourceList(sourceAndPaths)}
|
|
272
|
+
expandCodeCell={expandCodeCells}
|
|
273
|
+
expandEmbedding={expandEmbeddings}
|
|
274
|
+
editingMarkdown={editingMarkdownIndex === index}
|
|
275
|
+
editingMalloy={editingMalloyIndex === index}
|
|
276
|
+
onDelete={() => {
|
|
277
|
+
setNotebookData(notebookData.deleteCell(index));
|
|
278
|
+
saveNotebook();
|
|
279
|
+
}}
|
|
280
|
+
onCellChange={(cell) => {
|
|
281
|
+
setNotebookData(notebookData.setCell(index, cell));
|
|
282
|
+
saveNotebook();
|
|
283
|
+
}}
|
|
284
|
+
onEdit={() => {
|
|
285
|
+
if (cell.isMarkdown) {
|
|
286
|
+
setEditingMarkdownIndex(index);
|
|
287
|
+
} else {
|
|
288
|
+
setEditingMalloyIndex(index);
|
|
289
|
+
}
|
|
290
|
+
}}
|
|
291
|
+
onClose={() => {
|
|
292
|
+
if (cell.isMarkdown) {
|
|
293
|
+
setEditingMarkdownIndex(undefined);
|
|
294
|
+
} else {
|
|
295
|
+
setEditingMalloyIndex(undefined);
|
|
296
|
+
}
|
|
297
|
+
}}
|
|
298
|
+
/>
|
|
299
|
+
</React.Fragment>
|
|
300
|
+
))}
|
|
301
|
+
<Box style={{ paddingRight: "7px", paddingTop: "10px" }}>
|
|
302
|
+
{createButtons(notebookData.getCells().length)}
|
|
303
|
+
</Box>
|
|
304
|
+
<Menu
|
|
305
|
+
anchorEl={menuAnchorEl}
|
|
306
|
+
open={menuOpen}
|
|
307
|
+
onClose={handleMenuClose}
|
|
308
|
+
anchorOrigin={{ vertical: "bottom", horizontal: "right" }}
|
|
309
|
+
transformOrigin={{ vertical: "top", horizontal: "right" }}
|
|
310
|
+
>
|
|
311
|
+
<MenuItem onClick={() => handleAddCell(true, menuIndex ?? 0)}>
|
|
312
|
+
Add Markdown
|
|
313
|
+
</MenuItem>
|
|
314
|
+
<MenuItem
|
|
315
|
+
onClick={() => handleAddCell(false, menuIndex ?? 0)}
|
|
316
|
+
>
|
|
317
|
+
Add Malloy
|
|
318
|
+
</MenuItem>
|
|
319
|
+
</Menu>
|
|
320
|
+
<Stack
|
|
321
|
+
sx={{
|
|
322
|
+
flexDirection: "row",
|
|
323
|
+
justifyContent: "flex-end",
|
|
324
|
+
p: 1,
|
|
325
|
+
}}
|
|
326
|
+
></Stack>
|
|
327
|
+
</Stack>
|
|
328
|
+
</StyledCardMedia>
|
|
329
|
+
</StyledCard>
|
|
330
|
+
);
|
|
331
|
+
}
|
|
332
|
+
function AddIcon() {
|
|
333
|
+
return (
|
|
334
|
+
<svg
|
|
335
|
+
width="24"
|
|
336
|
+
height="24"
|
|
337
|
+
viewBox="0 0 24 24"
|
|
338
|
+
fill="none"
|
|
339
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
340
|
+
>
|
|
341
|
+
<path
|
|
342
|
+
d="M19 13H13V19H11V13H5V11H11V5H13V11H19V13Z"
|
|
343
|
+
fill="currentColor"
|
|
344
|
+
/>
|
|
345
|
+
</svg>
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
function ExportMalloyButton({
|
|
350
|
+
notebookData,
|
|
351
|
+
}: {
|
|
352
|
+
notebookData: NotebookManager;
|
|
353
|
+
}) {
|
|
354
|
+
const [copied, setCopied] = React.useState(false);
|
|
355
|
+
const handleExport = async () => {
|
|
356
|
+
if (!notebookData) return;
|
|
357
|
+
const malloy = notebookData.toMalloyNotebook();
|
|
358
|
+
try {
|
|
359
|
+
await navigator.clipboard.writeText(malloy);
|
|
360
|
+
setCopied(true);
|
|
361
|
+
setTimeout(() => setCopied(false), 1500);
|
|
362
|
+
} catch {
|
|
363
|
+
setCopied(false);
|
|
364
|
+
alert("Failed to copy to clipboard");
|
|
365
|
+
}
|
|
366
|
+
};
|
|
367
|
+
return (
|
|
368
|
+
<Button variant="contained" color="primary" onClick={handleExport}>
|
|
369
|
+
{copied ? "Copied!" : "Export To Malloy"}
|
|
370
|
+
</Button>
|
|
371
|
+
);
|
|
372
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { useNavigate, useParams } from "react-router-dom";
|
|
3
|
+
import {
|
|
4
|
+
List,
|
|
5
|
+
ListItem,
|
|
6
|
+
ListItemText,
|
|
7
|
+
IconButton,
|
|
8
|
+
Dialog,
|
|
9
|
+
DialogTitle,
|
|
10
|
+
DialogContent,
|
|
11
|
+
DialogContentText,
|
|
12
|
+
DialogActions,
|
|
13
|
+
Button,
|
|
14
|
+
Typography,
|
|
15
|
+
Box,
|
|
16
|
+
ListItemButton,
|
|
17
|
+
} from "@mui/material";
|
|
18
|
+
import DeleteIcon from "@mui/icons-material/Delete";
|
|
19
|
+
import { useNotebookStorage } from "./NotebookStorageProvider";
|
|
20
|
+
|
|
21
|
+
export function MutableNotebookList() {
|
|
22
|
+
const { projectName, packageName } = useParams();
|
|
23
|
+
const navigate = useNavigate();
|
|
24
|
+
const { notebookStorage, userContext } = useNotebookStorage();
|
|
25
|
+
const [notebooks, setNotebooks] = React.useState<string[]>([]);
|
|
26
|
+
const [deleteDialogOpen, setDeleteDialogOpen] = React.useState(false);
|
|
27
|
+
const [notebookToDelete, setNotebookToDelete] = React.useState<
|
|
28
|
+
string | null
|
|
29
|
+
>(null);
|
|
30
|
+
|
|
31
|
+
React.useEffect(() => {
|
|
32
|
+
if (notebookStorage && userContext) {
|
|
33
|
+
setNotebooks(notebookStorage.listNotebooks(userContext));
|
|
34
|
+
}
|
|
35
|
+
}, [notebookStorage, userContext]);
|
|
36
|
+
|
|
37
|
+
const handleDeleteClick = (notebook: string) => {
|
|
38
|
+
setNotebookToDelete(notebook);
|
|
39
|
+
setDeleteDialogOpen(true);
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const handleDeleteConfirm = () => {
|
|
43
|
+
if (notebookToDelete && notebookStorage && userContext) {
|
|
44
|
+
notebookStorage.deleteNotebook(userContext, notebookToDelete);
|
|
45
|
+
setNotebooks(notebookStorage.listNotebooks(userContext));
|
|
46
|
+
}
|
|
47
|
+
setDeleteDialogOpen(false);
|
|
48
|
+
setNotebookToDelete(null);
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const handleDeleteCancel = () => {
|
|
52
|
+
setDeleteDialogOpen(false);
|
|
53
|
+
setNotebookToDelete(null);
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const handleNotebookClick = (notebook: string) => {
|
|
57
|
+
if (projectName && packageName) {
|
|
58
|
+
// Navigate to the ScratchNotebookPage with anchor text for notebookPath
|
|
59
|
+
navigate(
|
|
60
|
+
`/${projectName}/${packageName}/scratch_notebook#notebookPath=${encodeURIComponent(notebook)}`,
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
return (
|
|
66
|
+
<Box>
|
|
67
|
+
<Typography variant="h5" sx={{ mb: 2 }}>
|
|
68
|
+
Notebooks
|
|
69
|
+
</Typography>
|
|
70
|
+
<Button
|
|
71
|
+
variant="contained"
|
|
72
|
+
onClick={() => handleNotebookClick("")}
|
|
73
|
+
sx={{ mb: 2 }}
|
|
74
|
+
>
|
|
75
|
+
New Notebook
|
|
76
|
+
</Button>
|
|
77
|
+
|
|
78
|
+
<List>
|
|
79
|
+
{notebooks.length === 0 && (
|
|
80
|
+
<ListItem>
|
|
81
|
+
<ListItemText primary="No notebooks found." />
|
|
82
|
+
</ListItem>
|
|
83
|
+
)}
|
|
84
|
+
{notebooks.map((notebook) => (
|
|
85
|
+
<ListItem
|
|
86
|
+
key={notebook}
|
|
87
|
+
secondaryAction={
|
|
88
|
+
<IconButton
|
|
89
|
+
edge="end"
|
|
90
|
+
aria-label="delete"
|
|
91
|
+
onClick={(e) => {
|
|
92
|
+
e.stopPropagation();
|
|
93
|
+
handleDeleteClick(notebook);
|
|
94
|
+
}}
|
|
95
|
+
>
|
|
96
|
+
<DeleteIcon />
|
|
97
|
+
</IconButton>
|
|
98
|
+
}
|
|
99
|
+
disablePadding
|
|
100
|
+
>
|
|
101
|
+
<ListItemButton onClick={() => handleNotebookClick(notebook)}>
|
|
102
|
+
<ListItemText
|
|
103
|
+
primary={
|
|
104
|
+
<Typography
|
|
105
|
+
component="span"
|
|
106
|
+
sx={{
|
|
107
|
+
color: "primary.main",
|
|
108
|
+
textDecoration: "underline",
|
|
109
|
+
}}
|
|
110
|
+
>
|
|
111
|
+
{notebook}
|
|
112
|
+
</Typography>
|
|
113
|
+
}
|
|
114
|
+
/>
|
|
115
|
+
</ListItemButton>
|
|
116
|
+
</ListItem>
|
|
117
|
+
))}
|
|
118
|
+
</List>
|
|
119
|
+
<Dialog open={deleteDialogOpen} onClose={handleDeleteCancel}>
|
|
120
|
+
<DialogTitle>Delete Notebook</DialogTitle>
|
|
121
|
+
<DialogContent>
|
|
122
|
+
<DialogContentText>
|
|
123
|
+
Are you sure you want to delete the notebook "
|
|
124
|
+
{notebookToDelete}"? This action cannot be undone.
|
|
125
|
+
</DialogContentText>
|
|
126
|
+
</DialogContent>
|
|
127
|
+
<DialogActions>
|
|
128
|
+
<Button onClick={handleDeleteCancel} color="primary">
|
|
129
|
+
Cancel
|
|
130
|
+
</Button>
|
|
131
|
+
<Button onClick={handleDeleteConfirm} color="error" autoFocus>
|
|
132
|
+
Delete
|
|
133
|
+
</Button>
|
|
134
|
+
</DialogActions>
|
|
135
|
+
</Dialog>
|
|
136
|
+
</Box>
|
|
137
|
+
);
|
|
138
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export interface UserContext {
|
|
2
|
+
// UserContext holds the project & package associated with
|
|
3
|
+
// the notebook. PublisherStorage interfaces will
|
|
4
|
+
// implement this and add data representing configuration,
|
|
5
|
+
// user permissions, and access tokens.
|
|
6
|
+
project: string;
|
|
7
|
+
package: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface NotebookStorage {
|
|
11
|
+
// Lists all available notebooks for the context.
|
|
12
|
+
// Notebooks names are like S3 paths- / denote hierarchical
|
|
13
|
+
// folders, but otherwise folders are not "real" objects
|
|
14
|
+
listNotebooks(context: UserContext): string[];
|
|
15
|
+
|
|
16
|
+
// Returns the notebook at the specific path, throws an exception if no such notebook exists (or cannot be accessed)
|
|
17
|
+
getNotebook(context: UserContext, path: string): string;
|
|
18
|
+
|
|
19
|
+
// Deletes the notebook at the specified path, or throws an
|
|
20
|
+
// Exception on failure
|
|
21
|
+
deleteNotebook(context: UserContext, path: string): void;
|
|
22
|
+
|
|
23
|
+
saveNotebook(context: UserContext, path: string, notebook: string): void;
|
|
24
|
+
|
|
25
|
+
// Moves notebook from the "from" path to the "to" path
|
|
26
|
+
moveNotebook(context: UserContext, from: string, to: string): void;
|
|
27
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import React, { createContext, useContext, useMemo } from "react";
|
|
2
|
+
import type { NotebookStorage, UserContext } from "./NotebookStorage";
|
|
3
|
+
|
|
4
|
+
interface NotebookStorageProviderProps {
|
|
5
|
+
children: React.ReactNode;
|
|
6
|
+
userContext: UserContext;
|
|
7
|
+
notebookStorage: NotebookStorage;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
interface NotebookStorageContextValue {
|
|
11
|
+
notebookStorage: NotebookStorage;
|
|
12
|
+
userContext: UserContext;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const NotebookStorageContext = createContext<
|
|
16
|
+
NotebookStorageContextValue | undefined
|
|
17
|
+
>(undefined);
|
|
18
|
+
|
|
19
|
+
export default function NotebookStorageProvider({
|
|
20
|
+
children,
|
|
21
|
+
userContext,
|
|
22
|
+
notebookStorage,
|
|
23
|
+
}: NotebookStorageProviderProps) {
|
|
24
|
+
const value = useMemo(
|
|
25
|
+
() => ({ notebookStorage, userContext }),
|
|
26
|
+
[notebookStorage, userContext],
|
|
27
|
+
);
|
|
28
|
+
return (
|
|
29
|
+
<NotebookStorageContext.Provider value={value}>
|
|
30
|
+
{children}
|
|
31
|
+
</NotebookStorageContext.Provider>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function useNotebookStorage() {
|
|
36
|
+
const context = useContext(NotebookStorageContext);
|
|
37
|
+
if (!context) {
|
|
38
|
+
throw new Error(
|
|
39
|
+
"useNotebookStorage must be used within a NotebookStorageProvider",
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
return context;
|
|
43
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { default as MutableNotebook } from "./MutableNotebook";
|
|
2
|
+
export { default as NotebookStorageProvider } from "./NotebookStorageProvider";
|
|
3
|
+
export { BrowserNotebookStorage } from "./BrowserNotebookStorage";
|
|
4
|
+
export type { UserContext, NotebookStorage } from "./NotebookStorage";
|
|
5
|
+
export { MutableNotebookList } from "./MutableNotebookList";
|