@marimo-team/frontend 0.24.1-dev84 → 0.24.1-dev86
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-GcHBZKLm.js → CellStatus-D40JXMpi.js} +1 -1
- package/dist/assets/{ConnectedDataExplorerComponent-BU1NZpRe.js → ConnectedDataExplorerComponent-Dkqk66Mv.js} +1 -1
- package/dist/assets/{JsonOutput-CUJlEdsN.js → JsonOutput-DeeAtdky.js} +1 -1
- package/dist/assets/{MarimoErrorOutput-DCAezqcJ.js → MarimoErrorOutput-Dh-877oV.js} +1 -1
- package/dist/assets/{RenderHTML-D9-75ltP.js → RenderHTML-C7JgAfHC.js} +1 -1
- package/dist/assets/{RunButton-plSsbubG.js → RunButton-C9d_w3qn.js} +1 -1
- package/dist/assets/{add-cell-with-ai-07LjLydm.js → add-cell-with-ai-zrX9HOPy.js} +1 -1
- package/dist/assets/{add-connection-dialog-By0i0Fq5.js → add-connection-dialog-OQxtKyCV.js} +1 -1
- package/dist/assets/{agent-panel-D01f7IIt.js → agent-panel-DF7_IEEH.js} +1 -1
- package/dist/assets/{ai-model-dropdown-DPxSumqI.js → ai-model-dropdown-DIItpu01.js} +1 -1
- package/dist/assets/{app-config-button-DV7ZvBMx.js → app-config-button-CeDIYZUc.js} +1 -1
- package/dist/assets/{blob-yxfhyyvD.js → blob-iewSQmkZ.js} +1 -1
- package/dist/assets/{cell-editor-Bl5DkKIR.js → cell-editor-BkHTcrF-.js} +1 -1
- package/dist/assets/{cell-link-hHrWcozB.js → cell-link-ZLZTXwR8.js} +1 -1
- package/dist/assets/{cells-yuYDzbAy.js → cells-DyE9dSiM.js} +1 -1
- package/dist/assets/{chat-display-BFCcdgia.js → chat-display-B9XOYJaA.js} +1 -1
- package/dist/assets/{chat-panel-CS9bLpq9.js → chat-panel-Drx8ToF5.js} +1 -1
- package/dist/assets/{chat-ui-lssAqY7E.js → chat-ui-CJ1VFz1m.js} +1 -1
- package/dist/assets/{column-preview-BEmcYaKl.js → column-preview-Ls8UnJOt.js} +1 -1
- package/dist/assets/{command-palette-DInNqneC.js → command-palette-DWyHSatV.js} +1 -1
- package/dist/assets/{common-Bs2kaOs2.js → common-03vbDqrN.js} +1 -1
- package/dist/assets/{components-ib4asSo7.js → components-CBlGIIAX.js} +1 -1
- package/dist/assets/{components-ByyAzQxP.js → components-J_vBLfsO.js} +1 -1
- package/dist/assets/{config-DaNyNpZL.js → config-D4frVGQ4.js} +1 -1
- package/dist/assets/{config-Cl3eMWxb.js → config-ycxJQP2b.js} +1 -1
- package/dist/assets/{datasource-DeZYq5UY.js → datasource-B4VfwXg_.js} +1 -1
- package/dist/assets/{dependency-graph-panel-g10osAFc.js → dependency-graph-panel-B24NhlCS.js} +1 -1
- package/dist/assets/{documentation-panel-CZ1mKkDh.js → documentation-panel-C4y_ufGj.js} +1 -1
- package/dist/assets/{download-D1crMjD8.js → download-Kg8_rqQH.js} +1 -1
- package/dist/assets/{edit-page-ZyGzkf7q.js → edit-page-DL_yo5rn.js} +4 -4
- package/dist/assets/{error-entries-B6XTLRyE.js → error-entries-Cy1y8FaI.js} +1 -1
- package/dist/assets/{error-panel-lexB3L6f.js → error-panel-D_pwhWfh.js} +1 -1
- package/dist/assets/file-explorer-panel-QoJu0RbE.js +1 -0
- package/dist/assets/{file-icons-Dx3v6lMu.js → file-icons-BN7fZ7-c.js} +1 -1
- package/dist/assets/file-name-input-qU-dl8Og.js +3 -0
- package/dist/assets/{floating-outline-3rQEbxxk.js → floating-outline-Coq72JrA.js} +1 -1
- package/dist/assets/{glide-data-editor-DZ2Minh8.js → glide-data-editor-DcY3nXI8.js} +1 -1
- package/dist/assets/{home-page-Ck-_oVJT.js → home-page-C6NwS9L9.js} +1 -1
- package/dist/assets/{index-g9ny2vs0.js → index-DMvKFHTV.js} +5 -5
- package/dist/assets/{kiosk-mode-CjT3sqy8.js → kiosk-mode-CzGt3mc2.js} +1 -1
- package/dist/assets/{layout-D0Q03BhB.js → layout-ByAlvlct.js} +3 -3
- package/dist/assets/{logs-panel-CSbUhhRo.js → logs-panel-zYelRd3A.js} +1 -1
- package/dist/assets/{markdown-renderer-BgUTGtM0.js → markdown-renderer-BqMslrdb.js} +1 -1
- package/dist/assets/{mermaid-Nzih1WTG.js → mermaid-CeseGGnS.js} +1 -1
- package/dist/assets/{name-cell-input-pEE4FGAL.js → name-cell-input-jrtswt5e.js} +1 -1
- package/dist/assets/{outline-panel-BgnjEQWg.js → outline-panel-B0P8XNnQ.js} +1 -1
- package/dist/assets/{packages-panel-BBP_xysn.js → packages-panel-C08E4Wjm.js} +1 -1
- package/dist/assets/{pair-with-agent-modal-Gg8MZHWW.js → pair-with-agent-modal-BZBengJw.js} +1 -1
- package/dist/assets/{panel-accordion-state-vpqEc3Kd.js → panel-accordion-state-Cyy4afrx.js} +1 -1
- package/dist/assets/{panels-fKt2XFRB.js → panels-DRtPsJ1u.js} +1 -1
- package/dist/assets/{process-output-siRm_lza.js → process-output-BiA5MKpM.js} +1 -1
- package/dist/assets/{readonly-code-display-CLWV76FT.js → readonly-code-display-B45Jy4vH.js} +1 -1
- package/dist/assets/{readonly-python-code-Bor9b9pV.js → readonly-python-code-BQ5Ti6Hw.js} +1 -1
- package/dist/assets/{renderShortcut-CS4ccH3x.js → renderShortcut-DK_OucLT.js} +1 -1
- package/dist/assets/{reveal-component-B55bWtCO.js → reveal-component-Bna3jncc.js} +1 -1
- package/dist/assets/{run-page-mD_6P_t5.js → run-page-CFC5C_It.js} +1 -1
- package/dist/assets/{scratchpad-panel-B_7IHnPD.js → scratchpad-panel-m2LRBFI5.js} +1 -1
- package/dist/assets/{session-panel-DB5qEYqt.js → session-panel-BV8Whoy8.js} +1 -1
- package/dist/assets/{skeleton-TY77TiqZ.js → skeleton-CWFWp9eU.js} +1 -1
- package/dist/assets/{snippets-panel-B8Yi81tz.js → snippets-panel-DBzdOGwQ.js} +1 -1
- package/dist/assets/state-DPS9Q717.js +3 -0
- package/dist/assets/{state-O8w98G11.js → state-rP7TRCla.js} +1 -1
- package/dist/assets/{switch-Gl6cozkm.js → switch-CtH5Ct94.js} +1 -1
- package/dist/assets/{terminal-DOqqA9Ha.js → terminal-C73iz1fU.js} +1 -1
- package/dist/assets/{tracing-DuLkr7ts.js → tracing-BP1J14GT.js} +1 -1
- package/dist/assets/{tracing-panel-CeLKVG8M.js → tracing-panel-CQasWudc.js} +2 -2
- package/dist/assets/{tree-actions-Bh-Nnaub.js → tree-actions-CrB9REp3.js} +1 -1
- package/dist/assets/{useAddCell-_JRYhAFd.js → useAddCell-DHYPnl7W.js} +1 -1
- package/dist/assets/{useBoolean-Cu7Eado1.js → useBoolean-ZU5StAbr.js} +1 -1
- package/dist/assets/{useCellActionButton-Bi8Lnv3F.js → useCellActionButton-C7bEu9tJ.js} +1 -1
- package/dist/assets/{useDeleteCell-CBQiavzs.js → useDeleteCell-CIzxZKUw.js} +1 -1
- package/dist/assets/{useDependencyPanelTab-BOn3UJ07.js → useDependencyPanelTab-CLmRGbOD.js} +1 -1
- package/dist/assets/{useNotebookActions-C1YIorGp.js → useNotebookActions-Q97EgZxR.js} +1 -1
- package/dist/assets/{useRunCells-BFQfLlUl.js → useRunCells-Csh5VYKu.js} +1 -1
- package/dist/assets/{useSplitCell-Ig9P6lAn.js → useSplitCell-BfT7UcKM.js} +1 -1
- package/dist/assets/{useTheme-D3zvxdF3.js → useTheme-CF-zG9YH.js} +1 -1
- package/dist/assets/{utils-CUZQNAWJ.js → utils-CWSZQEjr.js} +1 -1
- package/dist/assets/{vega-component-B0MZ_r-y.js → vega-component-Bel2PDO-.js} +1 -1
- package/dist/index.html +26 -26
- package/package.json +1 -1
- package/src/__mocks__/requests.ts +1 -0
- package/src/components/editor/chrome/panels/file-explorer-panel.tsx +3 -2
- package/src/components/editor/file-tree/__tests__/file-explorer-interactions.test.tsx +31 -18
- package/src/components/editor/file-tree/__tests__/file-explorer.test.ts +18 -12
- package/src/components/editor/file-tree/__tests__/requesting-tree.test.ts +364 -387
- package/src/components/editor/file-tree/file-explorer.tsx +137 -97
- package/src/components/editor/file-tree/file-name-input.tsx +5 -1
- package/src/components/editor/file-tree/file-viewer.tsx +2 -2
- package/src/components/editor/file-tree/requesting-tree.tsx +291 -171
- package/src/components/editor/file-tree/state.tsx +1 -0
- package/src/core/config/__tests__/config-schema.test.ts +15 -0
- package/src/core/config/config-schema.ts +15 -0
- package/src/core/islands/bridge.ts +1 -0
- package/src/core/network/__tests__/requests-network.test.ts +7 -0
- package/src/core/network/__tests__/requests-toasting.test.tsx +10 -0
- package/src/core/network/requests-lazy.ts +1 -0
- package/src/core/network/requests-network.ts +4 -0
- package/src/core/network/requests-static.ts +1 -0
- package/src/core/network/requests-toasting.tsx +1 -0
- package/src/core/network/types.ts +3 -0
- package/src/core/wasm/bridge.ts +9 -0
- package/src/core/wasm/worker/types.ts +2 -0
- package/dist/assets/file-explorer-panel-DHRH2J-3.js +0 -1
- package/dist/assets/file-name-input-DzL8D_7x.js +0 -3
- package/dist/assets/state-D1N9_41P.js +0 -3
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
ExternalLinkIcon,
|
|
12
12
|
FilePlus2Icon,
|
|
13
13
|
FolderPlusIcon,
|
|
14
|
+
FolderRootIcon,
|
|
14
15
|
ListTreeIcon,
|
|
15
16
|
PlaySquareIcon,
|
|
16
17
|
UploadIcon,
|
|
@@ -58,7 +59,6 @@ import { useCellActions } from "@/core/cells/cells";
|
|
|
58
59
|
import { useLastFocusedCellId } from "@/core/cells/focus";
|
|
59
60
|
import { disableFileDownloadsAtom } from "@/core/config/config";
|
|
60
61
|
import { useRequestClient } from "@/core/network/requests";
|
|
61
|
-
import type { FileInfo } from "@/core/network/types";
|
|
62
62
|
import { isWasm } from "@/core/wasm/utils";
|
|
63
63
|
import { useAsyncData } from "@/hooks/useAsyncData";
|
|
64
64
|
import { ErrorBanner } from "@/plugins/impl/common/error-banner";
|
|
@@ -71,7 +71,7 @@ import { jotaiJsonStorage } from "@/utils/storage/jotai";
|
|
|
71
71
|
import { useTreeDndManager } from "./dnd-wrapper";
|
|
72
72
|
import { downloadFile } from "./download";
|
|
73
73
|
import { FileViewer } from "./file-viewer";
|
|
74
|
-
import type { RequestingTree } from "./requesting-tree";
|
|
74
|
+
import type { FileTreeNode, RequestingTree } from "./requesting-tree";
|
|
75
75
|
import { openStateAtom, treeAtom } from "./state";
|
|
76
76
|
import { PYTHON_CODE_FOR_FILE_TYPE } from "./types";
|
|
77
77
|
import {
|
|
@@ -101,14 +101,15 @@ export const FileExplorer: React.FC<{
|
|
|
101
101
|
height: number;
|
|
102
102
|
externalDropDestinationPath?: FilePath | null;
|
|
103
103
|
}> = ({ height, externalDropDestinationPath = null }) => {
|
|
104
|
-
const treeRef = useRef<TreeApi<
|
|
104
|
+
const treeRef = useRef<TreeApi<FileTreeNode>>(null);
|
|
105
105
|
const dndManager = useTreeDndManager();
|
|
106
106
|
const [tree] = useAtom(treeAtom);
|
|
107
|
-
const [data, setData] = useState<
|
|
108
|
-
const [openFile, setOpenFile] = useState<
|
|
109
|
-
const [
|
|
110
|
-
|
|
111
|
-
|
|
107
|
+
const [data, setData] = useState<FileTreeNode[]>([]);
|
|
108
|
+
const [openFile, setOpenFile] = useState<FileTreeNode | null>(null);
|
|
109
|
+
const [selectedFolder, setSelectedFolder] = useState<{
|
|
110
|
+
id: string;
|
|
111
|
+
path: FilePath;
|
|
112
|
+
} | null>(null);
|
|
112
113
|
const [showHiddenFiles, setShowHiddenFiles] =
|
|
113
114
|
useAtom<boolean>(hiddenFilesState);
|
|
114
115
|
// Keep external state to remember which folders are open when this
|
|
@@ -153,7 +154,7 @@ export const FileExplorer: React.FC<{
|
|
|
153
154
|
openPrompt({
|
|
154
155
|
title: "Folder name",
|
|
155
156
|
onConfirm: async (name) => {
|
|
156
|
-
tree.createFolder(name, null);
|
|
157
|
+
tree.createFolder(name, selectedFolder?.id ?? null);
|
|
157
158
|
},
|
|
158
159
|
});
|
|
159
160
|
});
|
|
@@ -162,7 +163,7 @@ export const FileExplorer: React.FC<{
|
|
|
162
163
|
openPrompt({
|
|
163
164
|
title: "File name",
|
|
164
165
|
onConfirm: async (name) => {
|
|
165
|
-
tree.createFile({ name, parentId: null });
|
|
166
|
+
tree.createFile({ name, parentId: selectedFolder?.id ?? null });
|
|
166
167
|
},
|
|
167
168
|
});
|
|
168
169
|
});
|
|
@@ -171,7 +172,11 @@ export const FileExplorer: React.FC<{
|
|
|
171
172
|
openPrompt({
|
|
172
173
|
title: "Notebook name",
|
|
173
174
|
onConfirm: async (name) => {
|
|
174
|
-
tree.createFile({
|
|
175
|
+
tree.createFile({
|
|
176
|
+
name,
|
|
177
|
+
parentId: selectedFolder?.id ?? null,
|
|
178
|
+
type: "notebook",
|
|
179
|
+
});
|
|
175
180
|
},
|
|
176
181
|
});
|
|
177
182
|
});
|
|
@@ -186,13 +191,10 @@ export const FileExplorer: React.FC<{
|
|
|
186
191
|
[data, showHiddenFiles],
|
|
187
192
|
);
|
|
188
193
|
React.useEffect(() => {
|
|
189
|
-
if (
|
|
190
|
-
|
|
191
|
-
!treeContainsPath(visibleData, selectedFolderPath)
|
|
192
|
-
) {
|
|
193
|
-
setSelectedFolderPath(null);
|
|
194
|
+
if (selectedFolder && !treeContainsId(visibleData, selectedFolder.id)) {
|
|
195
|
+
setSelectedFolder(null);
|
|
194
196
|
}
|
|
195
|
-
}, [
|
|
197
|
+
}, [selectedFolder, visibleData]);
|
|
196
198
|
const contextValue = React.useMemo<FileExplorerContextValue>(
|
|
197
199
|
() => ({
|
|
198
200
|
tree,
|
|
@@ -227,11 +229,17 @@ export const FileExplorer: React.FC<{
|
|
|
227
229
|
</div>
|
|
228
230
|
<Suspense>
|
|
229
231
|
<FileViewer
|
|
230
|
-
onOpenNotebook={
|
|
231
|
-
|
|
232
|
-
evt
|
|
233
|
-
|
|
234
|
-
|
|
232
|
+
onOpenNotebook={
|
|
233
|
+
tree.isPrimaryNode(openFile)
|
|
234
|
+
? (evt) => {
|
|
235
|
+
const path = tree.getPrimaryRelativePath(
|
|
236
|
+
openFile.path as FilePath,
|
|
237
|
+
);
|
|
238
|
+
if (path !== null) {
|
|
239
|
+
openMarimoNotebook(evt, path);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
: undefined
|
|
235
243
|
}
|
|
236
244
|
file={openFile}
|
|
237
245
|
/>
|
|
@@ -254,15 +262,17 @@ export const FileExplorer: React.FC<{
|
|
|
254
262
|
onCreateNotebook={handleCreateNotebook}
|
|
255
263
|
onCreateFolder={handleCreateFolder}
|
|
256
264
|
onCollapseAll={handleCollapseAll}
|
|
257
|
-
uploadDestinationPath={
|
|
265
|
+
uploadDestinationPath={
|
|
266
|
+
selectedFolder?.path ?? tree.getPrimaryRootPath()
|
|
267
|
+
}
|
|
258
268
|
uploadDestinationLabel={getUploadDestinationLabel(
|
|
259
269
|
tree,
|
|
260
|
-
|
|
270
|
+
selectedFolder?.path ?? tree.getPrimaryRootPath(),
|
|
261
271
|
)}
|
|
262
272
|
onUpload={handleUploadFiles}
|
|
263
273
|
/>
|
|
264
274
|
<RequestingTreeContext value={contextValue}>
|
|
265
|
-
<Tree<
|
|
275
|
+
<Tree<FileTreeNode>
|
|
266
276
|
width="100%"
|
|
267
277
|
ref={treeRef}
|
|
268
278
|
height={height - 33}
|
|
@@ -275,7 +285,11 @@ export const FileExplorer: React.FC<{
|
|
|
275
285
|
// Hide the drop cursor
|
|
276
286
|
renderCursor={() => null}
|
|
277
287
|
// Disable dropping files into files
|
|
278
|
-
|
|
288
|
+
disableDrag={(node) => node.isRoot}
|
|
289
|
+
disableDrop={({ parentNode, dragNodes }) =>
|
|
290
|
+
dragNodes.some((node) => node.data.isRoot) ||
|
|
291
|
+
(parentNode ? !parentNode.data.isDirectory : false)
|
|
292
|
+
}
|
|
279
293
|
onDelete={async ({ ids }) => {
|
|
280
294
|
for (const id of ids) {
|
|
281
295
|
await tree.delete(id);
|
|
@@ -290,14 +304,17 @@ export const FileExplorer: React.FC<{
|
|
|
290
304
|
onSelect={(nodes) => {
|
|
291
305
|
const first = nodes[0];
|
|
292
306
|
if (!first) {
|
|
293
|
-
|
|
307
|
+
setSelectedFolder(null);
|
|
294
308
|
return;
|
|
295
309
|
}
|
|
296
310
|
if (first.data.isDirectory) {
|
|
297
|
-
|
|
311
|
+
setSelectedFolder({
|
|
312
|
+
id: first.id,
|
|
313
|
+
path: first.data.path as FilePath,
|
|
314
|
+
});
|
|
298
315
|
return;
|
|
299
316
|
}
|
|
300
|
-
|
|
317
|
+
setSelectedFolder(null);
|
|
301
318
|
setOpenFile(first.data);
|
|
302
319
|
}}
|
|
303
320
|
onToggle={async (id) => {
|
|
@@ -422,12 +439,12 @@ const Show = ({
|
|
|
422
439
|
node,
|
|
423
440
|
onOpenMarimoFile,
|
|
424
441
|
}: {
|
|
425
|
-
node: NodeApi<
|
|
442
|
+
node: NodeApi<FileTreeNode>;
|
|
426
443
|
onOpenMarimoFile: (
|
|
427
444
|
evt: Pick<Event, "stopPropagation" | "preventDefault">,
|
|
428
445
|
) => void;
|
|
429
446
|
}) => {
|
|
430
|
-
|
|
447
|
+
const label = (
|
|
431
448
|
<span
|
|
432
449
|
className="flex-1 overflow-hidden text-ellipsis"
|
|
433
450
|
onClick={(e) => {
|
|
@@ -439,7 +456,7 @@ const Show = ({
|
|
|
439
456
|
}}
|
|
440
457
|
>
|
|
441
458
|
{node.data.name}
|
|
442
|
-
{node.data.isMarimoFile && !isWasm() && (
|
|
459
|
+
{node.data.isMarimoFile && node.data.isPrimaryRoot && !isWasm() && (
|
|
443
460
|
<span
|
|
444
461
|
data-testid="file-explorer-open-marimo-button"
|
|
445
462
|
className="shrink-0 ml-2 text-sm hidden group-hover:inline hover:underline"
|
|
@@ -450,9 +467,15 @@ const Show = ({
|
|
|
450
467
|
)}
|
|
451
468
|
</span>
|
|
452
469
|
);
|
|
470
|
+
|
|
471
|
+
return node.data.isRoot ? (
|
|
472
|
+
<Tooltip content={node.data.path}>{label}</Tooltip>
|
|
473
|
+
) : (
|
|
474
|
+
label
|
|
475
|
+
);
|
|
453
476
|
};
|
|
454
477
|
|
|
455
|
-
const Node = ({ node, style, dragHandle }: NodeRendererProps<
|
|
478
|
+
const Node = ({ node, style, dragHandle }: NodeRendererProps<FileTreeNode>) => {
|
|
456
479
|
const { openFile } = useRequestClient();
|
|
457
480
|
const disableFileDownloads = useAtomValue(disableFileDownloadsAtom);
|
|
458
481
|
|
|
@@ -482,10 +505,10 @@ const Node = ({ node, style, dragHandle }: NodeRendererProps<FileInfo>) => {
|
|
|
482
505
|
const handleOpenMarimoFile = async (
|
|
483
506
|
evt: Pick<Event, "stopPropagation" | "preventDefault">,
|
|
484
507
|
) => {
|
|
485
|
-
const path = tree
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
508
|
+
const path = tree?.getPrimaryRelativePath(node.data.path as FilePath);
|
|
509
|
+
if (path !== null && path !== undefined) {
|
|
510
|
+
openMarimoNotebook(evt, path);
|
|
511
|
+
}
|
|
489
512
|
};
|
|
490
513
|
|
|
491
514
|
const handleDeleteFile = async (evt: Event) => {
|
|
@@ -555,7 +578,7 @@ const Node = ({ node, style, dragHandle }: NodeRendererProps<FileInfo>) => {
|
|
|
555
578
|
className={cn(
|
|
556
579
|
"flex items-center cursor-pointer ml-1 text-muted-foreground whitespace-nowrap group",
|
|
557
580
|
)}
|
|
558
|
-
draggable={
|
|
581
|
+
draggable={!node.data.isRoot}
|
|
559
582
|
onClick={(evt) => {
|
|
560
583
|
evt.stopPropagation();
|
|
561
584
|
if (node.data.isDirectory) {
|
|
@@ -577,7 +600,12 @@ const Node = ({ node, style, dragHandle }: NodeRendererProps<FileInfo>) => {
|
|
|
577
600
|
"bg-primary/15 hover:bg-primary/15 text-accent-foreground ring-1 ring-inset ring-primary",
|
|
578
601
|
)}
|
|
579
602
|
>
|
|
580
|
-
{node.data.
|
|
603
|
+
{node.data.isRoot ? (
|
|
604
|
+
<FolderRootIcon
|
|
605
|
+
className="w-5 h-5 shrink-0 mr-2 text-primary"
|
|
606
|
+
strokeWidth={1.5}
|
|
607
|
+
/>
|
|
608
|
+
) : node.data.isMarimoFile ? (
|
|
581
609
|
<MarimoIcon className="w-5 h-5 shrink-0 mr-2" strokeWidth={1.5} />
|
|
582
610
|
) : (
|
|
583
611
|
<Icon
|
|
@@ -649,14 +677,18 @@ const Node = ({ node, style, dragHandle }: NodeRendererProps<FileInfo>) => {
|
|
|
649
677
|
<DropdownMenuSeparator />
|
|
650
678
|
</>
|
|
651
679
|
)}
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
680
|
+
{!node.data.isRoot && (
|
|
681
|
+
<>
|
|
682
|
+
<RenameMenuItem
|
|
683
|
+
onSelect={() => node.edit()}
|
|
684
|
+
testId="file-explorer-rename-menu-item"
|
|
685
|
+
/>
|
|
686
|
+
<DuplicateMenuItem
|
|
687
|
+
onSelect={handleDuplicate}
|
|
688
|
+
testId="file-explorer-duplicate-menu-item"
|
|
689
|
+
/>
|
|
690
|
+
</>
|
|
691
|
+
)}
|
|
660
692
|
<DropdownMenuItem
|
|
661
693
|
onSelect={async () => {
|
|
662
694
|
await copyToClipboard(node.data.path);
|
|
@@ -667,13 +699,16 @@ const Node = ({ node, style, dragHandle }: NodeRendererProps<FileInfo>) => {
|
|
|
667
699
|
<ListTreeIcon className={MENU_ITEM_ICON_CLASS} />
|
|
668
700
|
Copy path
|
|
669
701
|
</DropdownMenuItem>
|
|
670
|
-
{tree && (
|
|
702
|
+
{tree && node.data.isPrimaryRoot && !node.data.isRoot && (
|
|
671
703
|
<DropdownMenuItem
|
|
672
704
|
onSelect={async () => {
|
|
673
|
-
|
|
674
|
-
|
|
705
|
+
const path = tree.getPrimaryRelativePath(
|
|
706
|
+
node.data.path as FilePath,
|
|
675
707
|
);
|
|
676
|
-
|
|
708
|
+
if (path !== null) {
|
|
709
|
+
await copyToClipboard(path);
|
|
710
|
+
toast({ title: "Copied to clipboard" });
|
|
711
|
+
}
|
|
677
712
|
}}
|
|
678
713
|
data-testid="file-explorer-copy-relative-path-menu-item"
|
|
679
714
|
>
|
|
@@ -681,35 +716,39 @@ const Node = ({ node, style, dragHandle }: NodeRendererProps<FileInfo>) => {
|
|
|
681
716
|
Copy relative path
|
|
682
717
|
</DropdownMenuItem>
|
|
683
718
|
)}
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
719
|
+
{!node.data.isRoot && (
|
|
720
|
+
<>
|
|
721
|
+
<DropdownMenuSeparator />
|
|
722
|
+
<DropdownMenuItem
|
|
723
|
+
onSelect={() => {
|
|
724
|
+
const { path } = node.data;
|
|
725
|
+
const pythonCode = PYTHON_CODE_FOR_FILE_TYPE[fileType](path);
|
|
726
|
+
handleInsertCode(pythonCode);
|
|
727
|
+
}}
|
|
728
|
+
data-testid="file-explorer-insert-snippet-menu-item"
|
|
729
|
+
>
|
|
730
|
+
<BetweenHorizontalStartIcon className={MENU_ITEM_ICON_CLASS} />
|
|
731
|
+
Insert snippet for reading file
|
|
732
|
+
</DropdownMenuItem>
|
|
733
|
+
<DropdownMenuItem
|
|
734
|
+
onSelect={async () => {
|
|
735
|
+
toast({
|
|
736
|
+
title: "Copied to clipboard",
|
|
737
|
+
description:
|
|
738
|
+
"Code to open the file has been copied to your clipboard. You can also drag and drop this file into the editor",
|
|
739
|
+
});
|
|
740
|
+
const { path } = node.data;
|
|
741
|
+
const pythonCode = PYTHON_CODE_FOR_FILE_TYPE[fileType](path);
|
|
742
|
+
await copyToClipboard(pythonCode);
|
|
743
|
+
}}
|
|
744
|
+
data-testid="file-explorer-copy-snippet-menu-item"
|
|
745
|
+
>
|
|
746
|
+
<BracesIcon className={MENU_ITEM_ICON_CLASS} />
|
|
747
|
+
Copy snippet for reading file
|
|
748
|
+
</DropdownMenuItem>
|
|
749
|
+
</>
|
|
750
|
+
)}
|
|
751
|
+
{node.data.isMarimoFile && node.data.isPrimaryRoot && !isWasm() && (
|
|
713
752
|
<>
|
|
714
753
|
<DropdownMenuSeparator />
|
|
715
754
|
<DropdownMenuItem
|
|
@@ -721,9 +760,9 @@ const Node = ({ node, style, dragHandle }: NodeRendererProps<FileInfo>) => {
|
|
|
721
760
|
</DropdownMenuItem>
|
|
722
761
|
</>
|
|
723
762
|
)}
|
|
724
|
-
<DropdownMenuSeparator />
|
|
725
763
|
{!node.data.isDirectory && !disableFileDownloads && (
|
|
726
764
|
<>
|
|
765
|
+
<DropdownMenuSeparator />
|
|
727
766
|
<DropdownMenuItem
|
|
728
767
|
onSelect={async () => {
|
|
729
768
|
await downloadFile(node.data.path, node.data.name);
|
|
@@ -733,20 +772,24 @@ const Node = ({ node, style, dragHandle }: NodeRendererProps<FileInfo>) => {
|
|
|
733
772
|
<DownloadIcon className={MENU_ITEM_ICON_CLASS} />
|
|
734
773
|
Download
|
|
735
774
|
</DropdownMenuItem>
|
|
775
|
+
</>
|
|
776
|
+
)}
|
|
777
|
+
{!node.data.isRoot && (
|
|
778
|
+
<>
|
|
736
779
|
<DropdownMenuSeparator />
|
|
780
|
+
<DeleteMenuItem
|
|
781
|
+
onSelect={handleDeleteFile}
|
|
782
|
+
testId="file-explorer-delete-menu-item"
|
|
783
|
+
/>
|
|
737
784
|
</>
|
|
738
785
|
)}
|
|
739
|
-
<DeleteMenuItem
|
|
740
|
-
onSelect={handleDeleteFile}
|
|
741
|
-
testId="file-explorer-delete-menu-item"
|
|
742
|
-
/>
|
|
743
786
|
</FileActionsDropdown>
|
|
744
787
|
</span>
|
|
745
788
|
</div>
|
|
746
789
|
);
|
|
747
790
|
};
|
|
748
791
|
|
|
749
|
-
const FolderArrow = ({ node }: { node: NodeApi<
|
|
792
|
+
const FolderArrow = ({ node }: { node: NodeApi<FileTreeNode> }) => {
|
|
750
793
|
if (!node.data.isDirectory) {
|
|
751
794
|
return <span className="w-4 h-4 shrink-0" />;
|
|
752
795
|
}
|
|
@@ -767,23 +810,20 @@ export function getUploadDestinationLabel(
|
|
|
767
810
|
tree: RequestingTree,
|
|
768
811
|
destinationPath: FilePath,
|
|
769
812
|
): string {
|
|
770
|
-
|
|
771
|
-
return "workspace root";
|
|
772
|
-
}
|
|
773
|
-
return tree.relativeFromRoot(destinationPath);
|
|
813
|
+
return tree.getDisplayPath(destinationPath);
|
|
774
814
|
}
|
|
775
815
|
|
|
776
816
|
export function filterHiddenTree(
|
|
777
|
-
list:
|
|
817
|
+
list: FileTreeNode[],
|
|
778
818
|
showHidden: boolean,
|
|
779
|
-
):
|
|
819
|
+
): FileTreeNode[] {
|
|
780
820
|
if (showHidden) {
|
|
781
821
|
return list;
|
|
782
822
|
}
|
|
783
823
|
|
|
784
|
-
const out:
|
|
824
|
+
const out: FileTreeNode[] = [];
|
|
785
825
|
for (const item of list) {
|
|
786
|
-
if (isDirectoryOrFileHidden(item.name)) {
|
|
826
|
+
if (!item.isRoot && isDirectoryOrFileHidden(item.name)) {
|
|
787
827
|
continue;
|
|
788
828
|
}
|
|
789
829
|
let next = item;
|
|
@@ -805,10 +845,10 @@ export function isDirectoryOrFileHidden(filename: string): boolean {
|
|
|
805
845
|
return false;
|
|
806
846
|
}
|
|
807
847
|
|
|
808
|
-
function
|
|
848
|
+
function treeContainsId(list: FileTreeNode[], id: string): boolean {
|
|
809
849
|
return list.some(
|
|
810
850
|
(item) =>
|
|
811
|
-
item.
|
|
812
|
-
(item.children ?
|
|
851
|
+
item.id === id ||
|
|
852
|
+
(item.children ? treeContainsId(item.children, id) : false),
|
|
813
853
|
);
|
|
814
854
|
}
|
|
@@ -9,7 +9,11 @@ import type { FileInfo } from "@/core/network/types";
|
|
|
9
9
|
* is true. Auto-focuses and selects everything except the extension so a
|
|
10
10
|
* user can type straight into the name.
|
|
11
11
|
*/
|
|
12
|
-
export const FileNameInput =
|
|
12
|
+
export const FileNameInput = <T extends FileInfo>({
|
|
13
|
+
node,
|
|
14
|
+
}: {
|
|
15
|
+
node: NodeApi<T>;
|
|
16
|
+
}) => {
|
|
13
17
|
const ref = useRef<HTMLInputElement>(null);
|
|
14
18
|
useEffect(() => {
|
|
15
19
|
ref.current?.focus();
|
|
@@ -32,7 +32,7 @@ export const MAX_FILE_PREVIEW_BYTES = 10 * 1024 * 1024;
|
|
|
32
32
|
|
|
33
33
|
interface Props {
|
|
34
34
|
file: FileInfo;
|
|
35
|
-
onOpenNotebook
|
|
35
|
+
onOpenNotebook?: (
|
|
36
36
|
evt: Pick<Event, "stopPropagation" | "preventDefault">,
|
|
37
37
|
) => void;
|
|
38
38
|
}
|
|
@@ -156,7 +156,7 @@ export const FileViewer: React.FC<Props> = ({ file, onOpenNotebook }) => {
|
|
|
156
156
|
onDownload={disableFileDownloads ? undefined : handleDownload}
|
|
157
157
|
actions={
|
|
158
158
|
<>
|
|
159
|
-
{file.isMarimoFile && !isWasm() && (
|
|
159
|
+
{file.isMarimoFile && onOpenNotebook && !isWasm() && (
|
|
160
160
|
<Tooltip content="Open notebook">
|
|
161
161
|
<Button
|
|
162
162
|
variant="text"
|