@marimo-team/islands 0.20.5-dev11 → 0.20.5-dev15
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/{Combination-Du-o_hC9.js → Combination-DPrRlMyq.js} +1 -1
- package/dist/{ConnectedDataExplorerComponent-DUS-zJoR.js → ConnectedDataExplorerComponent-Bp9TusI-.js} +10 -10
- package/dist/{any-language-editor-BL9o7y0_.js → any-language-editor-BGcQxkAL.js} +19 -19
- package/dist/{architectureDiagram-VXUJARFQ-DrJeyFHq.js → architectureDiagram-VXUJARFQ-IZt4NuSd.js} +5 -5
- package/dist/{blockDiagram-VD42YOAC-BJrP6qKc.js → blockDiagram-VD42YOAC-mhFHC3Ty.js} +5 -5
- package/dist/{button-KYalaJYu.js → button-BJaX_M34.js} +8 -3
- package/dist/{c4Diagram-YG6GDRKO-Bo4gytQ5.js → c4Diagram-YG6GDRKO-BzStmvfT.js} +4 -4
- package/dist/{channel-IWLGkaBE.js → channel-CUFaIkTh.js} +1 -1
- package/dist/{check-C50jsehH.js → check-C9c-GtvX.js} +1 -1
- package/dist/{chunk-ABZYJK2D-CRwanrkd.js → chunk-ABZYJK2D-7QYXAAhe.js} +1 -1
- package/dist/{chunk-ATLVNIR6-CMMCMvOK.js → chunk-ATLVNIR6-pmHPAPSd.js} +1 -1
- package/dist/{chunk-B4BG7PRW-BNsHrGHG.js → chunk-B4BG7PRW-C9mfKT9i.js} +4 -4
- package/dist/{chunk-DI55MBZ5-DQeYbfMV.js → chunk-DI55MBZ5-IKrK49rX.js} +4 -4
- package/dist/{chunk-EXTU4WIE-CV_DQeaX.js → chunk-EXTU4WIE-BRFl4iNd.js} +1 -1
- package/dist/{chunk-JA3XYJ7Z-Cmt--e0q.js → chunk-JA3XYJ7Z-C9q_MXZQ.js} +2 -2
- package/dist/{chunk-JZLCHNYA-CkyMJnI9.js → chunk-JZLCHNYA-DVjoFib5.js} +4 -4
- package/dist/{chunk-N4CR4FBY-BJfHtJbD.js → chunk-N4CR4FBY-BYr5N5mX.js} +5 -5
- package/dist/{chunk-QN33PNHL-WOLIPUAJ.js → chunk-QN33PNHL-CXfJywHv.js} +1 -1
- package/dist/{chunk-QXUST7PY-DYuD50pU.js → chunk-QXUST7PY-YO0PM8b3.js} +5 -5
- package/dist/{chunk-S3R3BYOJ-CsnX6RKs.js → chunk-S3R3BYOJ-DgI4FlvW.js} +1 -1
- package/dist/{chunk-TZMSLE5B-B3eYTGCw.js → chunk-TZMSLE5B-DSfBOnzx.js} +1 -1
- package/dist/{classDiagram-2ON5EDUG-C7C-oefv.js → classDiagram-2ON5EDUG-CvpnTWzz.js} +10 -10
- package/dist/{classDiagram-v2-WZHVMYZB-UTw37Gg8.js → classDiagram-v2-WZHVMYZB-DEQrBHLI.js} +10 -10
- package/dist/{copy-oc-FcZzt.js → copy-DhB8Zxuz.js} +2 -2
- package/dist/{dagre-6UL2VRFP-BgsUhJrV.js → dagre-6UL2VRFP-DC-emrm5.js} +7 -7
- package/dist/{diagram-PSM6KHXK-BIUUOfKo.js → diagram-PSM6KHXK-BAgNlpL8.js} +6 -6
- package/dist/{diagram-QEK2KX5R-BFjolZQv.js → diagram-QEK2KX5R-BM7QE5WA.js} +4 -4
- package/dist/{diagram-S2PKOQOG-4jfkWoZw.js → diagram-S2PKOQOG-qs4mB1gW.js} +4 -4
- package/dist/dist-B4MxkKHf.js +8 -0
- package/dist/{dist-De9X_Des.js → dist-B9EjSb9T.js} +1 -1
- package/dist/{dist-IW_ARJ3S.js → dist-BFxYppVR.js} +4 -4
- package/dist/{dist-D7ZGWV_9.js → dist-BGZ7TWS9.js} +3 -3
- package/dist/{dist-CwtEWuFb.js → dist-BSfYc7vq.js} +2 -2
- package/dist/{dist-DMS81OrU.js → dist-BUrWeMEP.js} +1 -1
- package/dist/dist-BYghZv6b.js +5 -0
- package/dist/dist-Be-uQhz5.js +6 -0
- package/dist/{dist-Ch_JuCvc.js → dist-BpMlUdNO.js} +3 -3
- package/dist/{dist-C6z8U-ms.js → dist-Bq5eYK43.js} +2 -2
- package/dist/{dist-BFL9TlzD.js → dist-Bq9zYwJs.js} +5 -5
- package/dist/{dist-7ZF--V_D.js → dist-C4K7pumm.js} +2 -2
- package/dist/{dist-Qjf6pcqK.js → dist-CAKwXCWI.js} +2 -2
- package/dist/dist-CB_xf0ju.js +5 -0
- package/dist/{dist-BwQHkjA9.js → dist-CDHl2i1x.js} +4 -4
- package/dist/dist-CK0qFAbF.js +8 -0
- package/dist/{dist-C4XMUaob.js → dist-CPlGUbk-.js} +2 -2
- package/dist/{dist-BT6_J2eq.js → dist-CSEWGuDq.js} +7 -2
- package/dist/dist-CYEk-qrr.js +8 -0
- package/dist/{dist-CYo3w-nC.js → dist-Cl5iM8xL.js} +3 -3
- package/dist/dist-CmKoWpMk.js +5 -0
- package/dist/{dist-I8MQW60_.js → dist-CseYuPtL.js} +2 -2
- package/dist/dist-D1nf4IQl.js +5 -0
- package/dist/{dist-CsqiXw7J.js → dist-D4gcY469.js} +2 -2
- package/dist/{dist-DUxS2paD.js → dist-D5NMgbbv.js} +2 -2
- package/dist/{dist-UYm1IE5s.js → dist-DERtJN02.js} +2 -2
- package/dist/{dist-CFToYDWO.js → dist-DEj2X26M.js} +2 -2
- package/dist/{dist-BuapEdlD.js → dist-DOoqn-VL.js} +70 -67
- package/dist/{dist-BLThQiU4.js → dist-DUretbKK.js} +2 -2
- package/dist/{dist-DEFZ7dnD.js → dist-D_-CGmlh.js} +2 -2
- package/dist/dist-Df3AcKpt.js +6 -0
- package/dist/dist-DgaFHt_I.js +5 -0
- package/dist/dist-Dk10C3ui.js +5 -0
- package/dist/{dist-D0f6Yrrb.js → dist-DodLQWPg.js} +1 -1
- package/dist/{dist-B8Y11RWn.js → dist-DpeHj-vP.js} +2 -2
- package/dist/dist-DtyPVMHR.js +5 -0
- package/dist/{dist-Cb3cLT39.js → dist-HoZO6brh.js} +2 -2
- package/dist/{dist-Cqpjy6bK.js → dist-RNGn_-uD.js} +1 -1
- package/dist/{dist-BBcqvpvP.js → dist-Ux6dL_VB.js} +1 -1
- package/dist/{dist-CB6qhQ8K.js → dist-gc9KgJuA.js} +1 -1
- package/dist/{dist-ovDpXuSB.js → dist-i-ud9aCA.js} +1 -1
- package/dist/dist-ko7WnHAO.js +5 -0
- package/dist/{dist-BTQbjEKU.js → dist-lNe4i1Nm.js} +1 -1
- package/dist/dist-of7gLRFK.js +8 -0
- package/dist/{erDiagram-Q2GNP2WA-Cq5Bz5lG.js → erDiagram-Q2GNP2WA-Dh5nhgY3.js} +10 -10
- package/dist/{error-banner-D0tXnwl4.js → error-banner-CJWv_j2x.js} +2 -2
- package/dist/{esm-BxMbHo0y.js → esm-DxAOF89q.js} +29 -27
- package/dist/{flowDiagram-NV44I4VS-6WPJVFl7.js → flowDiagram-NV44I4VS-ChR1Vbmj.js} +10 -10
- package/dist/{ganttDiagram-JELNMOA3-AfDhh9CI.js → ganttDiagram-JELNMOA3-sK0z-5KM.js} +3 -3
- package/dist/{gitGraphDiagram-V2S2FVAM-BRSwuj0Q.js → gitGraphDiagram-V2S2FVAM-9S1VqQrL.js} +3 -3
- package/dist/{glide-data-editor-ByPNTNVG.js → glide-data-editor-C2dhh3XY.js} +6 -6
- package/dist/{infoDiagram-HS3SLOUP-Cmxo6jKx.js → infoDiagram-HS3SLOUP-C5A8b-2O.js} +3 -3
- package/dist/{journeyDiagram-XKPGCS4Q-CKYr8cSR.js → journeyDiagram-XKPGCS4Q-D5BIjS4N.js} +3 -3
- package/dist/{kanban-definition-3W4ZIXB7-DVvAZzQD.js → kanban-definition-3W4ZIXB7-C1vZZabj.js} +7 -7
- package/dist/{label-CV0KYhtH.js → label-kL_UbziI.js} +4 -4
- package/dist/{loader-eJCvvApN.js → loader-C6xHQhFz.js} +1 -1
- package/dist/main.js +151 -154
- package/dist/{mermaid-COOB_abB.js → mermaid-CzRLRASw.js} +41 -41
- package/dist/{mindmap-definition-VGOIOE7T-1ExmnvYy.js → mindmap-definition-VGOIOE7T-Cn9_H_5f.js} +9 -9
- package/dist/{pieDiagram-ADFJNKIX-CJlIsdsU.js → pieDiagram-ADFJNKIX-iA0mvRW9.js} +4 -4
- package/dist/{purify.es-CyOIw8ru.js → purify.es-DGenX2XH.js} +67 -67
- package/dist/{quadrantDiagram-AYHSOK5B-BU78RiaH.js → quadrantDiagram-AYHSOK5B-CAcVWXc-.js} +2 -2
- package/dist/{requirementDiagram-UZGBJVZJ-DACHtrFr.js → requirementDiagram-UZGBJVZJ-1HxQ6I5Z.js} +9 -9
- package/dist/{sankeyDiagram-TZEHDZUN-Bzg7_UWs.js → sankeyDiagram-TZEHDZUN-BVJnR4_b.js} +2 -2
- package/dist/{sequenceDiagram-WL72ISMW-agybEe9J.js → sequenceDiagram-WL72ISMW-ByirOtHb.js} +4 -4
- package/dist/{slides-component-B0yK5GXP.js → slides-component-D9AVSv2B.js} +2 -2
- package/dist/{spec-Dq_reDGM.js → spec-CXpiliig.js} +4 -4
- package/dist/{stateDiagram-FKZM4ZOC-DehQAt8g.js → stateDiagram-FKZM4ZOC-DrYNXdQr.js} +10 -10
- package/dist/{stateDiagram-v2-4FDKWEC3-8VzeREl9.js → stateDiagram-v2-4FDKWEC3-C9CFKCSr.js} +10 -10
- package/dist/style.css +1 -1
- package/dist/{timeline-definition-IT6M3QCI-CdCfdaCF.js → timeline-definition-IT6M3QCI-D8B3p7ID.js} +2 -2
- package/dist/{tooltip-CL8m4f9y.js → tooltip-CRsmtPeF.js} +3 -3
- package/dist/{types-BwnzGcE4.js → types-ChPKdlru.js} +6 -6
- package/dist/{useAsyncData-B4hMFGnF.js → useAsyncData-CHYjZCox.js} +1 -1
- package/dist/{useDeepCompareMemoize-DuPhOXzr.js → useDeepCompareMemoize-C1YH9SDF.js} +4 -4
- package/dist/{useIframeCapabilities-CAt6D2EI.js → useIframeCapabilities-Cc4UBysp.js} +1 -1
- package/dist/{useTheme-BNYQnvu-.js → useTheme-B-ZI3PUl.js} +2 -2
- package/dist/{vega-component-DouPy8AI.js → vega-component-BX_oDKB3.js} +8 -8
- package/dist/{xychartDiagram-PRI3JC2R-rEm_SIsC.js → xychartDiagram-PRI3JC2R-KuxgQuK9.js} +5 -5
- package/package.json +1 -1
- package/src/components/datasources/components.tsx +3 -6
- package/src/components/datasources/datasources.tsx +8 -21
- package/src/components/editor/controls/keyboard-shortcuts.tsx +3 -1
- package/src/components/editor/file-tree/file-explorer.tsx +48 -62
- package/src/components/editor/file-tree/file-icons.tsx +132 -0
- package/src/components/editor/file-tree/file-viewer.tsx +1 -1
- package/src/components/editor/file-tree/tree-actions.tsx +107 -0
- package/src/components/editor/file-tree/types.ts +2 -96
- package/src/components/editor/header/filename-input.tsx +4 -1
- package/src/components/pages/home-page.tsx +5 -5
- package/src/components/storage/components.tsx +0 -38
- package/src/components/storage/storage-file-viewer.tsx +1 -1
- package/src/components/storage/storage-inspector.tsx +28 -45
- package/src/core/hotkeys/__tests__/hotkeys.test.ts +64 -1
- package/src/core/hotkeys/hotkeys.ts +18 -2
- package/src/plugins/impl/FileBrowserPlugin.tsx +4 -4
- package/dist/dist-BAeGo2rp.js +0 -5
- package/dist/dist-BqwCMSEa.js +0 -5
- package/dist/dist-Bum8FwTO.js +0 -6
- package/dist/dist-C0YiOwt_.js +0 -5
- package/dist/dist-C2uPv4iU.js +0 -5
- package/dist/dist-C5hOLsJN.js +0 -8
- package/dist/dist-C9NIAKMs.js +0 -8
- package/dist/dist-CCrzTtvk.js +0 -5
- package/dist/dist-CFS9i1rS.js +0 -8
- package/dist/dist-CyHZuhPH.js +0 -5
- package/dist/dist-CzcjWdIk.js +0 -6
- package/dist/dist-DaYyUSNC.js +0 -5
- package/dist/dist-DpDcJYNh.js +0 -8
- package/dist/dist-U_BfxcPn.js +0 -5
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
FileAudioIcon,
|
|
5
|
+
FileCodeIcon,
|
|
6
|
+
FileIcon,
|
|
7
|
+
FileJsonIcon,
|
|
8
|
+
FileSpreadsheetIcon,
|
|
9
|
+
FileTextIcon,
|
|
10
|
+
FileVideoIcon,
|
|
11
|
+
FolderArchiveIcon,
|
|
12
|
+
FolderIcon,
|
|
13
|
+
ImageIcon,
|
|
14
|
+
type LucideIcon,
|
|
15
|
+
} from "lucide-react";
|
|
16
|
+
import { cn } from "@/utils/cn";
|
|
17
|
+
|
|
18
|
+
export type FileIconType =
|
|
19
|
+
| "directory"
|
|
20
|
+
| "python"
|
|
21
|
+
| "code"
|
|
22
|
+
| "json"
|
|
23
|
+
| "text"
|
|
24
|
+
| "image"
|
|
25
|
+
| "audio"
|
|
26
|
+
| "video"
|
|
27
|
+
| "data"
|
|
28
|
+
| "pdf"
|
|
29
|
+
| "zip"
|
|
30
|
+
| "unknown";
|
|
31
|
+
|
|
32
|
+
const EXT_TO_TYPE: Record<string, FileIconType> = {
|
|
33
|
+
py: "python",
|
|
34
|
+
// Text / docs
|
|
35
|
+
txt: "text",
|
|
36
|
+
md: "text",
|
|
37
|
+
qmd: "text",
|
|
38
|
+
log: "text",
|
|
39
|
+
// Images
|
|
40
|
+
png: "image",
|
|
41
|
+
jpg: "image",
|
|
42
|
+
jpeg: "image",
|
|
43
|
+
gif: "image",
|
|
44
|
+
svg: "image",
|
|
45
|
+
webp: "image",
|
|
46
|
+
// Data / spreadsheets
|
|
47
|
+
csv: "data",
|
|
48
|
+
parquet: "data",
|
|
49
|
+
arrow: "data",
|
|
50
|
+
xlsx: "data",
|
|
51
|
+
// JSON
|
|
52
|
+
json: "json",
|
|
53
|
+
// Code
|
|
54
|
+
js: "code",
|
|
55
|
+
ts: "code",
|
|
56
|
+
tsx: "code",
|
|
57
|
+
html: "code",
|
|
58
|
+
css: "code",
|
|
59
|
+
toml: "code",
|
|
60
|
+
yaml: "code",
|
|
61
|
+
yml: "code",
|
|
62
|
+
wasm: "code",
|
|
63
|
+
// Audio
|
|
64
|
+
mp3: "audio",
|
|
65
|
+
m4a: "audio",
|
|
66
|
+
ogg: "audio",
|
|
67
|
+
wav: "audio",
|
|
68
|
+
// Video
|
|
69
|
+
mp4: "video",
|
|
70
|
+
m4v: "video",
|
|
71
|
+
mpeg: "video",
|
|
72
|
+
webm: "video",
|
|
73
|
+
mkv: "video",
|
|
74
|
+
// PDF
|
|
75
|
+
pdf: "pdf",
|
|
76
|
+
// Archives
|
|
77
|
+
zip: "zip",
|
|
78
|
+
tar: "zip",
|
|
79
|
+
gz: "zip",
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
export function guessFileIconType(name: string): FileIconType {
|
|
83
|
+
const ext = name.split(".").pop()?.toLowerCase();
|
|
84
|
+
if (!ext) {
|
|
85
|
+
return "unknown";
|
|
86
|
+
}
|
|
87
|
+
return EXT_TO_TYPE[ext] ?? "unknown";
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export const FILE_ICON: Record<FileIconType, LucideIcon> = {
|
|
91
|
+
directory: FolderIcon,
|
|
92
|
+
python: FileCodeIcon,
|
|
93
|
+
code: FileCodeIcon,
|
|
94
|
+
json: FileJsonIcon,
|
|
95
|
+
text: FileTextIcon,
|
|
96
|
+
image: ImageIcon,
|
|
97
|
+
audio: FileAudioIcon,
|
|
98
|
+
video: FileVideoIcon,
|
|
99
|
+
data: FileSpreadsheetIcon,
|
|
100
|
+
pdf: FileTextIcon,
|
|
101
|
+
zip: FolderArchiveIcon,
|
|
102
|
+
unknown: FileIcon,
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
export const FILE_ICON_COLOR: Record<FileIconType, string> = {
|
|
106
|
+
directory: "text-amber-500",
|
|
107
|
+
python: "text-blue-500",
|
|
108
|
+
code: "text-blue-500",
|
|
109
|
+
json: "text-blue-500",
|
|
110
|
+
text: "text-muted-foreground",
|
|
111
|
+
image: "text-purple-500",
|
|
112
|
+
audio: "text-orange-500",
|
|
113
|
+
video: "text-orange-500",
|
|
114
|
+
data: "text-green-500",
|
|
115
|
+
pdf: "text-red-500",
|
|
116
|
+
zip: "text-muted-foreground",
|
|
117
|
+
unknown: "text-muted-foreground",
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Render a colored file-type icon for a given filename.
|
|
122
|
+
* Pass `className` to control size (defaults to `h-3.5 w-3.5`).
|
|
123
|
+
*/
|
|
124
|
+
export function renderFileIcon(
|
|
125
|
+
name: string,
|
|
126
|
+
className?: string,
|
|
127
|
+
): React.ReactNode {
|
|
128
|
+
const type = guessFileIconType(name);
|
|
129
|
+
const Icon = FILE_ICON[type];
|
|
130
|
+
const color = FILE_ICON_COLOR[type];
|
|
131
|
+
return <Icon className={cn("h-3.5 w-3.5 shrink-0", color, className)} />;
|
|
132
|
+
}
|
|
@@ -166,7 +166,7 @@ export const FileViewer: React.FC<Props> = ({ file, onOpenNotebook }) => {
|
|
|
166
166
|
</Tooltip>
|
|
167
167
|
<Tooltip content={renderShortcut("global.save")}>
|
|
168
168
|
<Button
|
|
169
|
-
variant=
|
|
169
|
+
variant="text"
|
|
170
170
|
size="xs"
|
|
171
171
|
onClick={handleSaveFile}
|
|
172
172
|
disabled={internalValue === data.contents}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
ChevronRightIcon,
|
|
5
|
+
MoreVerticalIcon,
|
|
6
|
+
RefreshCwIcon,
|
|
7
|
+
} from "lucide-react";
|
|
8
|
+
import React, { useCallback, useState } from "react";
|
|
9
|
+
import { Button } from "@/components/ui/button";
|
|
10
|
+
import { Tooltip } from "@/components/ui/tooltip";
|
|
11
|
+
import { cn } from "@/utils/cn";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Animated chevron for tree expand/collapse.
|
|
15
|
+
* Rotates 90° when `isExpanded` is true.
|
|
16
|
+
*/
|
|
17
|
+
export const TreeChevron: React.FC<{
|
|
18
|
+
isExpanded: boolean;
|
|
19
|
+
className?: string;
|
|
20
|
+
}> = ({ isExpanded, className }) => (
|
|
21
|
+
<ChevronRightIcon
|
|
22
|
+
className={cn(
|
|
23
|
+
"shrink-0 transition-transform",
|
|
24
|
+
isExpanded && "rotate-90",
|
|
25
|
+
className,
|
|
26
|
+
)}
|
|
27
|
+
/>
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Refresh button that briefly spins its icon when clicked.
|
|
32
|
+
* Keeps spinning for at least 500ms, or until the onClick Promise resolves.
|
|
33
|
+
*/
|
|
34
|
+
export const RefreshIconButton: React.FC<{
|
|
35
|
+
onClick: (e: React.MouseEvent) => void | Promise<void>;
|
|
36
|
+
tooltip?: string;
|
|
37
|
+
className?: string;
|
|
38
|
+
iconClassName?: string;
|
|
39
|
+
}> = ({ onClick, tooltip = "Refresh", className, iconClassName }) => {
|
|
40
|
+
const [isSpinning, setIsSpinning] = useState(false);
|
|
41
|
+
|
|
42
|
+
const handleClick = useCallback(
|
|
43
|
+
async (e: React.MouseEvent) => {
|
|
44
|
+
setIsSpinning(true);
|
|
45
|
+
// Artificially spin for 500ms to show the user that the button is working.
|
|
46
|
+
const minDelay = new Promise<void>((r) => setTimeout(r, 500));
|
|
47
|
+
try {
|
|
48
|
+
await Promise.all([onClick(e), minDelay]);
|
|
49
|
+
} finally {
|
|
50
|
+
setIsSpinning(false);
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
[onClick],
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
const button = (
|
|
57
|
+
<Button
|
|
58
|
+
variant="text"
|
|
59
|
+
size="xs"
|
|
60
|
+
className={className}
|
|
61
|
+
onClick={handleClick}
|
|
62
|
+
>
|
|
63
|
+
<RefreshCwIcon
|
|
64
|
+
className={cn(
|
|
65
|
+
"h-4 w-4",
|
|
66
|
+
iconClassName,
|
|
67
|
+
isSpinning && "animate-[spin_0.5s]",
|
|
68
|
+
)}
|
|
69
|
+
/>
|
|
70
|
+
</Button>
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
return <Tooltip content={tooltip}>{button}</Tooltip>;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Three-dot menu trigger that fades in on row hover.
|
|
78
|
+
* Must be inside a `group` container.
|
|
79
|
+
* Forwards ref so it works with Radix `asChild`.
|
|
80
|
+
*/
|
|
81
|
+
export const MoreActionsButton = React.forwardRef<
|
|
82
|
+
HTMLButtonElement,
|
|
83
|
+
{
|
|
84
|
+
onClick?: (e: React.MouseEvent) => void;
|
|
85
|
+
className?: string;
|
|
86
|
+
iconClassName?: string;
|
|
87
|
+
} & Omit<React.ComponentPropsWithoutRef<typeof Button>, "variant" | "size">
|
|
88
|
+
>(({ className, iconClassName, ...props }, ref) => (
|
|
89
|
+
<Button
|
|
90
|
+
ref={ref}
|
|
91
|
+
variant="text"
|
|
92
|
+
tabIndex={-1}
|
|
93
|
+
size="xs"
|
|
94
|
+
className={cn(
|
|
95
|
+
"mb-0 opacity-0 group-hover:opacity-100 transition-opacity",
|
|
96
|
+
className,
|
|
97
|
+
)}
|
|
98
|
+
aria-label="More options"
|
|
99
|
+
{...props}
|
|
100
|
+
>
|
|
101
|
+
<MoreVerticalIcon className={cn("w-4 h-4", iconClassName)} />
|
|
102
|
+
</Button>
|
|
103
|
+
));
|
|
104
|
+
MoreActionsButton.displayName = "MoreActionsButton";
|
|
105
|
+
|
|
106
|
+
/** Standard class string for icons inside dropdown menu items. */
|
|
107
|
+
export const MENU_ITEM_ICON_CLASS = "h-3.5 w-3.5 mr-2";
|
|
@@ -1,105 +1,11 @@
|
|
|
1
1
|
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
-
import {
|
|
3
|
-
DatabaseIcon,
|
|
4
|
-
FileAudioIcon,
|
|
5
|
-
FileCodeIcon,
|
|
6
|
-
FileIcon,
|
|
7
|
-
FileImageIcon,
|
|
8
|
-
FileJsonIcon,
|
|
9
|
-
FileTextIcon,
|
|
10
|
-
FileVideo2Icon,
|
|
11
|
-
FolderArchiveIcon,
|
|
12
|
-
FolderIcon,
|
|
13
|
-
type LucideIcon,
|
|
14
|
-
} from "lucide-react";
|
|
15
2
|
|
|
16
|
-
|
|
17
|
-
export type FileType =
|
|
18
|
-
| "directory"
|
|
19
|
-
| "python"
|
|
20
|
-
| "code"
|
|
21
|
-
| "json"
|
|
22
|
-
| "text"
|
|
23
|
-
| "image"
|
|
24
|
-
| "audio"
|
|
25
|
-
| "video"
|
|
26
|
-
| "data"
|
|
27
|
-
| "pdf"
|
|
28
|
-
| "zip"
|
|
29
|
-
| "unknown";
|
|
30
|
-
|
|
31
|
-
export function guessFileType(name: string): FileType {
|
|
32
|
-
const ext = name.split(".").pop();
|
|
33
|
-
if (ext === undefined) {
|
|
34
|
-
return "unknown";
|
|
35
|
-
}
|
|
36
|
-
switch (ext.toLowerCase()) {
|
|
37
|
-
case "py":
|
|
38
|
-
return "python";
|
|
39
|
-
case "txt":
|
|
40
|
-
case "md":
|
|
41
|
-
case "qmd":
|
|
42
|
-
return "text";
|
|
43
|
-
case "png":
|
|
44
|
-
case "jpg":
|
|
45
|
-
case "jpeg":
|
|
46
|
-
case "gif":
|
|
47
|
-
return "image";
|
|
48
|
-
case "csv":
|
|
49
|
-
return "data";
|
|
50
|
-
case "json":
|
|
51
|
-
return "json";
|
|
52
|
-
// Non-exhaustive list of code file extensions
|
|
53
|
-
case "js":
|
|
54
|
-
case "ts":
|
|
55
|
-
case "tsx":
|
|
56
|
-
case "html":
|
|
57
|
-
case "css":
|
|
58
|
-
case "toml":
|
|
59
|
-
case "yaml":
|
|
60
|
-
case "yml":
|
|
61
|
-
case "wasm":
|
|
62
|
-
return "code";
|
|
63
|
-
case "mp3":
|
|
64
|
-
case "m4a":
|
|
65
|
-
case "m4v":
|
|
66
|
-
case "ogg":
|
|
67
|
-
case "wav":
|
|
68
|
-
return "audio";
|
|
69
|
-
case "mp4":
|
|
70
|
-
case "webm":
|
|
71
|
-
case "mkv":
|
|
72
|
-
return "video";
|
|
73
|
-
case "pdf":
|
|
74
|
-
return "pdf";
|
|
75
|
-
case "zip":
|
|
76
|
-
case "tar":
|
|
77
|
-
case "gz":
|
|
78
|
-
return "zip";
|
|
79
|
-
default:
|
|
80
|
-
return "unknown";
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
export const FILE_TYPE_ICONS: Record<FileType, LucideIcon> = {
|
|
85
|
-
directory: FolderIcon,
|
|
86
|
-
python: FileCodeIcon,
|
|
87
|
-
json: FileJsonIcon,
|
|
88
|
-
code: FileCodeIcon,
|
|
89
|
-
text: FileTextIcon,
|
|
90
|
-
image: FileImageIcon,
|
|
91
|
-
audio: FileAudioIcon,
|
|
92
|
-
video: FileVideo2Icon,
|
|
93
|
-
pdf: FileCodeIcon,
|
|
94
|
-
zip: FolderArchiveIcon,
|
|
95
|
-
data: DatabaseIcon,
|
|
96
|
-
unknown: FileIcon,
|
|
97
|
-
};
|
|
3
|
+
import type { FileIconType } from "@/components/editor/file-tree/file-icons";
|
|
98
4
|
|
|
99
5
|
const TAB = " ";
|
|
100
6
|
|
|
101
7
|
export const PYTHON_CODE_FOR_FILE_TYPE: Record<
|
|
102
|
-
|
|
8
|
+
FileIconType,
|
|
103
9
|
(path: string) => string
|
|
104
10
|
> = {
|
|
105
11
|
directory: (path) => `os.listdir("${path}")`,
|
|
@@ -3,6 +3,10 @@
|
|
|
3
3
|
import { PopoverAnchor } from "@radix-ui/react-popover";
|
|
4
4
|
import { FilePenIcon } from "lucide-react";
|
|
5
5
|
import { type JSX, useEffect, useRef, useState } from "react";
|
|
6
|
+
import {
|
|
7
|
+
FILE_ICON as FILE_TYPE_ICONS,
|
|
8
|
+
guessFileIconType as guessFileType,
|
|
9
|
+
} from "@/components/editor/file-tree/file-icons";
|
|
6
10
|
import type { FileInfo } from "@/core/network/types";
|
|
7
11
|
import { useAsyncData } from "@/hooks/useAsyncData";
|
|
8
12
|
import { Paths } from "@/utils/paths";
|
|
@@ -15,7 +19,6 @@ import {
|
|
|
15
19
|
CommandList,
|
|
16
20
|
} from "../../ui/command";
|
|
17
21
|
import { Popover, PopoverContent } from "../../ui/popover";
|
|
18
|
-
import { FILE_TYPE_ICONS, guessFileType } from "../file-tree/types";
|
|
19
22
|
|
|
20
23
|
import "./filename-input.css";
|
|
21
24
|
import { getFeatureFlag } from "@/core/config/feature-flag";
|
|
@@ -23,6 +23,11 @@ import {
|
|
|
23
23
|
} from "react-arborist";
|
|
24
24
|
import { useLocale } from "react-aria";
|
|
25
25
|
import { MarkdownIcon } from "@/components/editor/cell/code/icons";
|
|
26
|
+
import {
|
|
27
|
+
FILE_ICON as FILE_TYPE_ICONS,
|
|
28
|
+
type FileIconType as FileType,
|
|
29
|
+
guessFileIconType as guessFileType,
|
|
30
|
+
} from "@/components/editor/file-tree/file-icons";
|
|
26
31
|
import { useImperativeModal } from "@/components/modal/ImperativeModal";
|
|
27
32
|
import { AlertDialogDestructiveAction } from "@/components/ui/alert-dialog";
|
|
28
33
|
import { Button } from "@/components/ui/button";
|
|
@@ -47,11 +52,6 @@ import { newNotebookURL } from "@/utils/urls";
|
|
|
47
52
|
import { ConfigButton } from "../app-config/app-config-button";
|
|
48
53
|
import { ErrorBoundary } from "../editor/boundary/ErrorBoundary";
|
|
49
54
|
import { ShutdownButton } from "../editor/controls/shutdown-button";
|
|
50
|
-
import {
|
|
51
|
-
FILE_TYPE_ICONS,
|
|
52
|
-
type FileType,
|
|
53
|
-
guessFileType,
|
|
54
|
-
} from "../editor/file-tree/types";
|
|
55
55
|
import {
|
|
56
56
|
Header,
|
|
57
57
|
OpenTutorialDropDown,
|
|
@@ -8,15 +8,9 @@ import CoreweaveIcon from "@marimo-team/llm-info/icons/coreweave.svg?inline";
|
|
|
8
8
|
import CoreweaveDarkIcon from "@marimo-team/llm-info/icons/coreweave-dark.svg?inline";
|
|
9
9
|
import {
|
|
10
10
|
DatabaseZapIcon,
|
|
11
|
-
FileCodeIcon,
|
|
12
|
-
FileIcon,
|
|
13
|
-
FileSpreadsheetIcon,
|
|
14
|
-
FileTextIcon,
|
|
15
|
-
FileVideoIcon,
|
|
16
11
|
GithubIcon,
|
|
17
12
|
GlobeIcon,
|
|
18
13
|
HardDriveIcon,
|
|
19
|
-
ImageIcon,
|
|
20
14
|
} from "lucide-react";
|
|
21
15
|
import GoogleCloudIcon from "@/components/databases/icons/google-cloud-storage.svg?inline";
|
|
22
16
|
import GoogleDriveIcon from "@/components/databases/icons/google-drive.svg?inline";
|
|
@@ -24,38 +18,6 @@ import type { KnownStorageProtocol } from "@/core/storage/types";
|
|
|
24
18
|
import { useTheme } from "@/theme/useTheme";
|
|
25
19
|
import { cn } from "@/utils/cn";
|
|
26
20
|
|
|
27
|
-
export function renderFileIcon(name: string): React.ReactNode {
|
|
28
|
-
const ext = name.split(".").pop()?.toLowerCase();
|
|
29
|
-
switch (ext) {
|
|
30
|
-
case "png":
|
|
31
|
-
case "jpg":
|
|
32
|
-
case "jpeg":
|
|
33
|
-
case "gif":
|
|
34
|
-
case "svg":
|
|
35
|
-
case "webp":
|
|
36
|
-
return <ImageIcon className="h-3.5 w-3.5 text-purple-500" />;
|
|
37
|
-
case "csv":
|
|
38
|
-
case "parquet":
|
|
39
|
-
case "arrow":
|
|
40
|
-
case "xlsx":
|
|
41
|
-
return <FileSpreadsheetIcon className="h-3.5 w-3.5 text-green-500" />;
|
|
42
|
-
case "py":
|
|
43
|
-
case "js":
|
|
44
|
-
case "ts":
|
|
45
|
-
case "json":
|
|
46
|
-
return <FileCodeIcon className="h-3.5 w-3.5 text-blue-500" />;
|
|
47
|
-
case "mp4":
|
|
48
|
-
case "mpeg":
|
|
49
|
-
return <FileVideoIcon className="h-3.5 w-3.5 text-orange-500" />;
|
|
50
|
-
case "txt":
|
|
51
|
-
case "md":
|
|
52
|
-
case "log":
|
|
53
|
-
return <FileTextIcon className="h-3.5 w-3.5 text-muted-foreground" />;
|
|
54
|
-
default:
|
|
55
|
-
return <FileIcon className="h-3.5 w-3.5 text-muted-foreground" />;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
21
|
type IconEntry =
|
|
60
22
|
| { src: string; dark?: string }
|
|
61
23
|
| React.ComponentType<{ className?: string }>;
|
|
@@ -5,6 +5,7 @@ import type React from "react";
|
|
|
5
5
|
import { useCallback } from "react";
|
|
6
6
|
import { useLocale } from "react-aria";
|
|
7
7
|
import { FilePreviewHeader } from "@/components/editor/file-tree/file-header";
|
|
8
|
+
import { renderFileIcon } from "@/components/editor/file-tree/file-icons";
|
|
8
9
|
import {
|
|
9
10
|
FileContentRenderer,
|
|
10
11
|
isMediaMime,
|
|
@@ -18,7 +19,6 @@ import { formatBytes } from "@/utils/formatting";
|
|
|
18
19
|
import { Logger } from "@/utils/Logger";
|
|
19
20
|
import { CopyClipboardIcon } from "../icons/copy-icon";
|
|
20
21
|
import { Button } from "../ui/button";
|
|
21
|
-
import { renderFileIcon } from "./components";
|
|
22
22
|
|
|
23
23
|
const MAX_MEDIA_PREVIEW_SIZE = 100 * 1024 * 1024; // 100 MB
|
|
24
24
|
|
|
@@ -2,16 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
import { CommandList } from "cmdk";
|
|
4
4
|
import {
|
|
5
|
-
ChevronRightIcon,
|
|
6
5
|
CopyIcon,
|
|
7
6
|
DownloadIcon,
|
|
8
7
|
FolderIcon,
|
|
9
8
|
HardDriveIcon,
|
|
10
9
|
HelpCircleIcon,
|
|
11
10
|
LoaderCircle,
|
|
12
|
-
MoreVerticalIcon,
|
|
13
11
|
PlusIcon,
|
|
14
|
-
RefreshCwIcon,
|
|
15
12
|
ViewIcon,
|
|
16
13
|
XIcon,
|
|
17
14
|
} from "lucide-react";
|
|
@@ -20,6 +17,16 @@ import { useLocale } from "react-aria";
|
|
|
20
17
|
import { EngineVariable } from "@/components/databases/engine-variable";
|
|
21
18
|
import { PanelEmptyState } from "@/components/editor/chrome/panels/empty-state";
|
|
22
19
|
import { AddConnectionDialog } from "@/components/editor/connections/add-connection-dialog";
|
|
20
|
+
import {
|
|
21
|
+
FILE_ICON_COLOR,
|
|
22
|
+
renderFileIcon,
|
|
23
|
+
} from "@/components/editor/file-tree/file-icons";
|
|
24
|
+
import {
|
|
25
|
+
MENU_ITEM_ICON_CLASS,
|
|
26
|
+
MoreActionsButton,
|
|
27
|
+
RefreshIconButton,
|
|
28
|
+
TreeChevron,
|
|
29
|
+
} from "@/components/editor/file-tree/tree-actions";
|
|
23
30
|
import { Command, CommandInput, CommandItem } from "@/components/ui/command";
|
|
24
31
|
import {
|
|
25
32
|
DropdownMenu,
|
|
@@ -49,7 +56,7 @@ import { formatBytes } from "@/utils/formatting";
|
|
|
49
56
|
import { Logger } from "@/utils/Logger";
|
|
50
57
|
import { ErrorState } from "../datasources/components";
|
|
51
58
|
import { Button } from "../ui/button";
|
|
52
|
-
import { ProtocolIcon
|
|
59
|
+
import { ProtocolIcon } from "./components";
|
|
53
60
|
import { StorageFileViewer } from "./storage-file-viewer";
|
|
54
61
|
|
|
55
62
|
interface OpenFileInfo {
|
|
@@ -305,17 +312,14 @@ const StorageEntryRow: React.FC<{
|
|
|
305
312
|
}}
|
|
306
313
|
>
|
|
307
314
|
{isDir ? (
|
|
308
|
-
<
|
|
309
|
-
className={cn(
|
|
310
|
-
"h-3 w-3 shrink-0 transition-transform",
|
|
311
|
-
effectiveExpanded && "rotate-90",
|
|
312
|
-
)}
|
|
313
|
-
/>
|
|
315
|
+
<TreeChevron isExpanded={effectiveExpanded} className="h-3 w-3" />
|
|
314
316
|
) : (
|
|
315
317
|
<span className="w-3 shrink-0" />
|
|
316
318
|
)}
|
|
317
319
|
{isDir ? (
|
|
318
|
-
<FolderIcon
|
|
320
|
+
<FolderIcon
|
|
321
|
+
className={cn("h-3.5 w-3.5 shrink-0", FILE_ICON_COLOR.directory)}
|
|
322
|
+
/>
|
|
319
323
|
) : (
|
|
320
324
|
renderFileIcon(name)
|
|
321
325
|
)}
|
|
@@ -337,14 +341,10 @@ const StorageEntryRow: React.FC<{
|
|
|
337
341
|
)}
|
|
338
342
|
<DropdownMenu>
|
|
339
343
|
<DropdownMenuTrigger asChild={true}>
|
|
340
|
-
<
|
|
341
|
-
|
|
342
|
-
size="icon"
|
|
343
|
-
className="opacity-0 group-hover:opacity-100 transition-opacity hover:shadow-none hover:text-link text-muted-foreground"
|
|
344
|
+
<MoreActionsButton
|
|
345
|
+
iconClassName="h-3 w-3"
|
|
344
346
|
onClick={(e) => e.stopPropagation()}
|
|
345
|
-
|
|
346
|
-
<MoreVerticalIcon className="h-3 w-3" />
|
|
347
|
-
</Button>
|
|
347
|
+
/>
|
|
348
348
|
</DropdownMenuTrigger>
|
|
349
349
|
<DropdownMenuContent
|
|
350
350
|
align="end"
|
|
@@ -355,7 +355,7 @@ const StorageEntryRow: React.FC<{
|
|
|
355
355
|
<DropdownMenuItem
|
|
356
356
|
onSelect={() => onOpenFile({ entry, namespace })}
|
|
357
357
|
>
|
|
358
|
-
<ViewIcon className=
|
|
358
|
+
<ViewIcon className={MENU_ITEM_ICON_CLASS} />
|
|
359
359
|
View
|
|
360
360
|
</DropdownMenuItem>
|
|
361
361
|
)}
|
|
@@ -366,12 +366,12 @@ const StorageEntryRow: React.FC<{
|
|
|
366
366
|
toast({ title: "Copied to clipboard" });
|
|
367
367
|
}}
|
|
368
368
|
>
|
|
369
|
-
<CopyIcon className=
|
|
369
|
+
<CopyIcon className={MENU_ITEM_ICON_CLASS} />
|
|
370
370
|
Copy URL
|
|
371
371
|
</DropdownMenuItem>
|
|
372
372
|
{!isDir && (
|
|
373
373
|
<DropdownMenuItem onSelect={() => handleDownload()}>
|
|
374
|
-
<DownloadIcon className=
|
|
374
|
+
<DownloadIcon className={MENU_ITEM_ICON_CLASS} />
|
|
375
375
|
Download
|
|
376
376
|
</DropdownMenuItem>
|
|
377
377
|
)}
|
|
@@ -402,7 +402,6 @@ const StorageNamespaceSection: React.FC<{
|
|
|
402
402
|
onOpenFile: (info: OpenFileInfo) => void;
|
|
403
403
|
}> = ({ namespace, locale, searchValue, onOpenFile }) => {
|
|
404
404
|
const [isExpanded, setIsExpanded] = useState(true);
|
|
405
|
-
const [isSpinning, setIsSpinning] = useState(false);
|
|
406
405
|
const { entriesByPath } = useStorage();
|
|
407
406
|
const { clearNamespaceCache } = useStorageActions();
|
|
408
407
|
const namespaceName = namespace.name ?? namespace.displayName;
|
|
@@ -417,10 +416,8 @@ const StorageNamespaceSection: React.FC<{
|
|
|
417
416
|
const handleRefresh = useCallback(
|
|
418
417
|
(e: React.MouseEvent) => {
|
|
419
418
|
e.stopPropagation();
|
|
420
|
-
setIsSpinning(true);
|
|
421
419
|
clearNamespaceCache(namespaceName);
|
|
422
420
|
refetch();
|
|
423
|
-
setTimeout(() => setIsSpinning(false), 500);
|
|
424
421
|
},
|
|
425
422
|
[namespaceName, clearNamespaceCache, refetch],
|
|
426
423
|
);
|
|
@@ -441,12 +438,7 @@ const StorageNamespaceSection: React.FC<{
|
|
|
441
438
|
onSelect={() => setIsExpanded(!isExpanded)}
|
|
442
439
|
className="flex flex-row font-semibold h-7 text-xs gap-1.5 bg-(--slate-2) text-muted-foreground rounded-none"
|
|
443
440
|
>
|
|
444
|
-
<
|
|
445
|
-
className={cn(
|
|
446
|
-
"h-3 w-3 shrink-0 transition-transform",
|
|
447
|
-
isExpanded && "rotate-90",
|
|
448
|
-
)}
|
|
449
|
-
/>
|
|
441
|
+
<TreeChevron isExpanded={isExpanded} className="h-3 w-3" />
|
|
450
442
|
<ProtocolIcon protocol={namespace.protocol} />
|
|
451
443
|
<span>{namespace.displayName}</span>
|
|
452
444
|
{namespace.name && (
|
|
@@ -454,21 +446,12 @@ const StorageNamespaceSection: React.FC<{
|
|
|
454
446
|
(<EngineVariable variableName={namespace.name as VariableName} />)
|
|
455
447
|
</span>
|
|
456
448
|
)}
|
|
457
|
-
<
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
>
|
|
464
|
-
<RefreshCwIcon
|
|
465
|
-
className={cn(
|
|
466
|
-
"h-3 w-3 text-muted-foreground hover:text-foreground",
|
|
467
|
-
isSpinning && "animate-[spin_0.5s]",
|
|
468
|
-
)}
|
|
469
|
-
/>
|
|
470
|
-
</Button>
|
|
471
|
-
</Tooltip>
|
|
449
|
+
<RefreshIconButton
|
|
450
|
+
onClick={handleRefresh}
|
|
451
|
+
tooltip="Refresh storage connection"
|
|
452
|
+
className="p-0"
|
|
453
|
+
iconClassName="h-3 w-3"
|
|
454
|
+
/>
|
|
472
455
|
<span className="text-[10px] text-muted-foreground font-normal tabular-nums ml-auto">
|
|
473
456
|
{namespace.rootPath || "(root)"}
|
|
474
457
|
</span>
|