@marimo-team/islands 0.17.8 → 0.18.0
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-BH_L276x.js → Combination-D68fi0fY.js} +22 -21
- package/dist/{ConnectedDataExplorerComponent-WbiFXhKG.js → ConnectedDataExplorerComponent-BUgUSo2B.js} +7 -7
- package/dist/{any-language-editor-YPQMljy9.js → any-language-editor-BS-Z5AY5.js} +3 -3
- package/dist/assets/__vite-browser-external-CSegkGa0.js +1 -0
- package/dist/assets/{worker-BrDpRi2I.js → worker-CiT2i-Vo.js} +2 -2
- package/dist/{error-banner-BqE1uF21.js → error-banner-CPLhCPHA.js} +24 -24
- package/dist/{esm-hR1r0nyt.js → esm-DxgKy8Wv.js} +1 -1
- package/dist/{formats-dvT8nDgH.js → formats-oddMfm9_.js} +27 -7
- package/dist/{glide-data-editor-B26PhZvE.js → glide-data-editor-BFv4VQnc.js} +4 -4
- package/dist/{label-D3LNCORf.js → label-Dsm6T1fr.js} +72 -72
- package/dist/main.js +359 -250
- package/dist/{mermaid-Dl3ywmV2.js → mermaid-BeGlg1JH.js} +2 -2
- package/dist/{react-vega-ypEMYp9o.js → react-vega-DDXWt_PN.js} +852 -1544
- package/dist/{react-vega-BIDT9Ttp.js → react-vega-DV2IwPx_.js} +1 -1
- package/dist/{spec-qDDGe5hl.js → spec-BotzCMo3.js} +2 -2
- package/dist/style.css +1 -1
- package/dist/{types-2eTEqSwS.js → types-IRrkdH-H.js} +14 -14
- package/dist/{useAsyncData-6gisQ4pR.js → useAsyncData-CsSW6_Zh.js} +1 -1
- package/dist/{useTheme-B-2frT0L.js → useTheme-D56Xlrez.js} +1 -0
- package/dist/{vega-component-C-bCSv1b.js → vega-component-CLjz4see.js} +6 -6
- package/package.json +2 -2
- package/src/components/chat/chat-panel.tsx +6 -2
- package/src/components/data-table/TableActions.tsx +18 -14
- package/src/components/data-table/data-table.tsx +3 -0
- package/src/components/editor/chrome/panels/packages-panel.tsx +3 -1
- package/src/components/editor/file-tree/__tests__/file-expolorer.test.ts +178 -0
- package/src/components/editor/file-tree/file-explorer.tsx +70 -1
- package/src/components/pages/home-page.tsx +8 -3
- package/src/core/ai/tools/__tests__/registry.test.ts +6 -2
- package/src/core/ai/tools/registry.ts +5 -2
- package/src/core/cells/__tests__/session.test.ts +0 -9
- package/src/core/cells/session.ts +0 -1
- package/src/core/codemirror/copilot/client.ts +21 -1
- package/src/core/codemirror/copilot/copilot-config.tsx +29 -1
- package/src/core/config/__tests__/config-schema.test.ts +2 -0
- package/src/core/config/config-schema.ts +1 -0
- package/src/core/packages/__tests__/package-input-utils.test.ts +93 -0
- package/src/core/packages/package-input-utils.ts +36 -0
- package/src/css/md.css +5 -0
- package/src/plugins/core/__test__/sanitize.test.ts +1 -1
- package/src/plugins/core/sanitize.ts +3 -1
- package/src/plugins/impl/DataTablePlugin.tsx +10 -1
- package/src/plugins/impl/chat/ChatPlugin.tsx +1 -0
- package/src/plugins/impl/chat/chat-ui.tsx +140 -10
- package/src/plugins/impl/data-frames/DataFramePlugin.tsx +1 -0
- package/src/plugins/layout/NavigationMenuPlugin.tsx +14 -3
- package/src/plugins/layout/ProgressPlugin.tsx +8 -5
- package/src/plugins/layout/StatPlugin.tsx +11 -4
- package/src/plugins/layout/__test__/ProgressPlugin.test.ts +37 -21
- package/src/utils/__tests__/urls.test.ts +165 -1
- package/src/utils/urls.ts +120 -0
- package/src/utils/vitals.ts +1 -1
- package/dist/assets/__vite-browser-external-BTNiCQ6O.js +0 -1
|
@@ -3,14 +3,14 @@ import { t as require_react } from "./react-DXQlph6m.js";
|
|
|
3
3
|
import { t as require_compiler_runtime } from "./compiler-runtime-DTozApu4.js";
|
|
4
4
|
import { t as createLucideIcon } from "./createLucideIcon-ixdBmsqu.js";
|
|
5
5
|
import { t as Check } from "./check-DvmFwGOM.js";
|
|
6
|
-
import { A as upperFirst_default, C as createCollection, I as $b5e257d569688ac6$export$619500959fc48b26, L as X, M as $488c6ddbf4ef74c2$export$cc77c4ff7e8673c5, N as $18f2051aff69b9bf$export$43bb16f9c6d9e3f7, R as ChevronUp, S as useDirection, _ as menuLabelVariants, g as menuItemVariants, h as menuControlVariants, j as $a916eb452884faea$export$b7a616150fdb9f44, m as menuControlCheckVariants, p as menuContentCommon, v as menuSeparatorVariants, y as menuSubTriggerVariants, z as ChevronDown } from "./label-
|
|
6
|
+
import { A as upperFirst_default, C as createCollection, I as $b5e257d569688ac6$export$619500959fc48b26, L as X, M as $488c6ddbf4ef74c2$export$cc77c4ff7e8673c5, N as $18f2051aff69b9bf$export$43bb16f9c6d9e3f7, R as ChevronUp, S as useDirection, _ as menuLabelVariants, g as menuItemVariants, h as menuControlVariants, j as $a916eb452884faea$export$b7a616150fdb9f44, m as menuControlCheckVariants, p as menuContentCommon, v as menuSeparatorVariants, y as menuSubTriggerVariants, z as ChevronDown } from "./label-Dsm6T1fr.js";
|
|
7
7
|
import { n as clsx_default } from "./clsx-JH8KOlt9.js";
|
|
8
8
|
import { c as composeRefs, d as cn, l as useComposedRefs, o as createSlot, t as Button, u as Events } from "./button-Hye6-2X8.js";
|
|
9
9
|
import { s as Logger } from "./hotkeys-C3KM59Ph.js";
|
|
10
|
-
import {
|
|
10
|
+
import { A as composeEventHandlers, D as dispatchDiscreteCustomEvent, E as Primitive, S as useId, T as useCallbackRef, _ as Arrow, b as createPopperScope, c as withSmartCollisionBoundary, g as Anchor, h as Portal, i as useFocusGuards, k as createContextScope, m as Presence, n as hideOthers, o as MAX_HEIGHT_OFFSET, p as useControllableState, r as FocusScope, s as withFullScreenAsRoot, t as Combination_default, u as StyleNamespace, v as Content, w as DismissableLayer, y as Root2$1 } from "./Combination-D68fi0fY.js";
|
|
11
11
|
import { t as require_jsx_runtime } from "./jsx-runtime-Duz3IlLt.js";
|
|
12
12
|
import { t as require_react_dom } from "./react-dom-DBPBYhx0.js";
|
|
13
|
-
import { m as useEvent_default } from "./useTheme-
|
|
13
|
+
import { m as useEvent_default } from "./useTheme-D56Xlrez.js";
|
|
14
14
|
import { t as toString_default } from "./toString-MxUAFb1C.js";
|
|
15
15
|
import { r as debounce_default, t as Constants } from "./constants-CN19buVX.js";
|
|
16
16
|
import { t as memoizeLastValue } from "./once-BdyR-H8C.js";
|
|
@@ -3029,10 +3029,10 @@ var CONTENT_NAME$1 = "MenuContent", [MenuContentProvider, useMenuContentContext]
|
|
|
3029
3029
|
onDismiss: () => n.onOpenChange(false)
|
|
3030
3030
|
});
|
|
3031
3031
|
}), Slot = createSlot("MenuContent.ScrollLock"), MenuContentImpl = import_react.forwardRef((e2, t) => {
|
|
3032
|
-
let { __scopeMenu: n, loop: r = false, trapFocus: i, onOpenAutoFocus: a, onCloseAutoFocus: o, disableOutsidePointerEvents: s, onEntryFocus: c, onEscapeKeyDown: l, onPointerDownOutside: u, onFocusOutside: d, onInteractOutside: f, onDismiss: p, disableOutsideScroll: m, ...h } = e2, g = useMenuContext(CONTENT_NAME$1, n), _ = useMenuRootContext(CONTENT_NAME$1, n), v = usePopperScope(n), y = useRovingFocusGroupScope(n), b = useCollection(n), [x, S] = import_react.useState(null), C = import_react.useRef(null), w = useComposedRefs(t, C, g.onContentChange), E = import_react.useRef(0), D = import_react.useRef(""), O = import_react.useRef(0), k = import_react.useRef(null), j = import_react.useRef("right"), M = import_react.useRef(0), N = m ? Combination_default : import_react.Fragment,
|
|
3032
|
+
let { __scopeMenu: n, loop: r = false, trapFocus: i, onOpenAutoFocus: a, onCloseAutoFocus: o, disableOutsidePointerEvents: s, onEntryFocus: c, onEscapeKeyDown: l, onPointerDownOutside: u, onFocusOutside: d, onInteractOutside: f, onDismiss: p, disableOutsideScroll: m, ...h } = e2, g = useMenuContext(CONTENT_NAME$1, n), _ = useMenuRootContext(CONTENT_NAME$1, n), v = usePopperScope(n), y = useRovingFocusGroupScope(n), b = useCollection(n), [x, S] = import_react.useState(null), C = import_react.useRef(null), w = useComposedRefs(t, C, g.onContentChange), E = import_react.useRef(0), D = import_react.useRef(""), O = import_react.useRef(0), k = import_react.useRef(null), j = import_react.useRef("right"), M = import_react.useRef(0), N = m ? Combination_default : import_react.Fragment, P = m ? {
|
|
3033
3033
|
as: Slot,
|
|
3034
3034
|
allowPinchZoom: true
|
|
3035
|
-
} : void 0,
|
|
3035
|
+
} : void 0, F = (e3) => {
|
|
3036
3036
|
var _a, _b;
|
|
3037
3037
|
let t2 = D.current + e3, n2 = b().filter((e4) => !e4.disabled), r2 = document.activeElement, i2 = (_a = n2.find((e4) => e4.ref.current === r2)) == null ? void 0 : _a.textValue, a2 = getNextMatch(n2.map((e4) => e4.textValue), t2, i2), o2 = (_b = n2.find((e4) => e4.textValue === a2)) == null ? void 0 : _b.ref.current;
|
|
3038
3038
|
(function e4(t3) {
|
|
@@ -3040,7 +3040,7 @@ var CONTENT_NAME$1 = "MenuContent", [MenuContentProvider, useMenuContentContext]
|
|
|
3040
3040
|
})(t2), o2 && setTimeout(() => o2.focus());
|
|
3041
3041
|
};
|
|
3042
3042
|
import_react.useEffect(() => () => window.clearTimeout(E.current), []), useFocusGuards();
|
|
3043
|
-
let
|
|
3043
|
+
let I = import_react.useCallback((e3) => {
|
|
3044
3044
|
var _a, _b;
|
|
3045
3045
|
return j.current === ((_a = k.current) == null ? void 0 : _a.side) && isPointerInGraceArea(e3, (_b = k.current) == null ? void 0 : _b.area);
|
|
3046
3046
|
}, []);
|
|
@@ -3048,21 +3048,21 @@ var CONTENT_NAME$1 = "MenuContent", [MenuContentProvider, useMenuContentContext]
|
|
|
3048
3048
|
scope: n,
|
|
3049
3049
|
searchRef: D,
|
|
3050
3050
|
onItemEnter: import_react.useCallback((e3) => {
|
|
3051
|
-
|
|
3052
|
-
}, [
|
|
3051
|
+
I(e3) && e3.preventDefault();
|
|
3052
|
+
}, [I]),
|
|
3053
3053
|
onItemLeave: import_react.useCallback((e3) => {
|
|
3054
3054
|
var _a;
|
|
3055
|
-
|
|
3056
|
-
}, [
|
|
3055
|
+
I(e3) || ((_a = C.current) == null ? void 0 : _a.focus(), S(null));
|
|
3056
|
+
}, [I]),
|
|
3057
3057
|
onTriggerLeave: import_react.useCallback((e3) => {
|
|
3058
|
-
|
|
3059
|
-
}, [
|
|
3058
|
+
I(e3) && e3.preventDefault();
|
|
3059
|
+
}, [I]),
|
|
3060
3060
|
pointerGraceTimerRef: O,
|
|
3061
3061
|
onPointerGraceIntentChange: import_react.useCallback((e3) => {
|
|
3062
3062
|
k.current = e3;
|
|
3063
3063
|
}, []),
|
|
3064
3064
|
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(N, {
|
|
3065
|
-
...
|
|
3065
|
+
...P,
|
|
3066
3066
|
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FocusScope, {
|
|
3067
3067
|
asChild: true,
|
|
3068
3068
|
trapped: i,
|
|
@@ -3106,7 +3106,7 @@ var CONTENT_NAME$1 = "MenuContent", [MenuContentProvider, useMenuContentContext]
|
|
|
3106
3106
|
},
|
|
3107
3107
|
onKeyDown: composeEventHandlers(h.onKeyDown, (e3) => {
|
|
3108
3108
|
let t2 = e3.target.closest("[data-radix-menu-content]") === e3.currentTarget, n2 = e3.ctrlKey || e3.altKey || e3.metaKey, r2 = e3.key.length === 1;
|
|
3109
|
-
t2 && (e3.key === "Tab" && e3.preventDefault(), !n2 && r2 &&
|
|
3109
|
+
t2 && (e3.key === "Tab" && e3.preventDefault(), !n2 && r2 && F(e3.key));
|
|
3110
3110
|
let i2 = C.current;
|
|
3111
3111
|
if (e3.target !== i2 || !FIRST_LAST_KEYS.includes(e3.key)) return;
|
|
3112
3112
|
e3.preventDefault();
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { s as __toESM } from "./chunk-DgPTj83v.js";
|
|
2
2
|
import { t as require_react } from "./react-DXQlph6m.js";
|
|
3
3
|
import { t as require_compiler_runtime } from "./compiler-runtime-DTozApu4.js";
|
|
4
|
-
import { m as useEvent_default } from "./useTheme-
|
|
4
|
+
import { m as useEvent_default } from "./useTheme-D56Xlrez.js";
|
|
5
5
|
import { t as invariant } from "./invariant-BEpJUgj9.js";
|
|
6
6
|
var import_compiler_runtime = /* @__PURE__ */ __toESM(require_compiler_runtime(), 1), import_react = /* @__PURE__ */ __toESM(require_react(), 1), Result = {
|
|
7
7
|
error(e, s) {
|
|
@@ -487,6 +487,7 @@ const UserConfigSchema = looseObject({
|
|
|
487
487
|
"lazy",
|
|
488
488
|
"autorun"
|
|
489
489
|
]).prefault("off"),
|
|
490
|
+
reactive_tests: boolean().prefault(true),
|
|
490
491
|
watcher_on_save: _enum(["lazy", "autorun"]).prefault("lazy"),
|
|
491
492
|
default_sql_output: _enum(VALID_SQL_OUTPUT_FORMATS).prefault("auto"),
|
|
492
493
|
default_auto_download: array(_enum(AUTO_DOWNLOAD_FORMATS)).prefault([])
|
|
@@ -2,15 +2,15 @@ import { s as __toESM } from "./chunk-DgPTj83v.js";
|
|
|
2
2
|
import { t as require_react } from "./react-DXQlph6m.js";
|
|
3
3
|
import { t as require_compiler_runtime } from "./compiler-runtime-DTozApu4.js";
|
|
4
4
|
import "./createLucideIcon-ixdBmsqu.js";
|
|
5
|
-
import {
|
|
5
|
+
import { C as CircleQuestionMark, h as asRemoteURL, i as Alert, n as useDeepCompareMemoize, o as AlertTitle, s as isValid, t as arrow } from "./formats-oddMfm9_.js";
|
|
6
6
|
import "./clsx-JH8KOlt9.js";
|
|
7
7
|
import { u as Events } from "./button-Hye6-2X8.js";
|
|
8
8
|
import { o as Objects, s as Logger } from "./hotkeys-C3KM59Ph.js";
|
|
9
|
-
import "./Combination-
|
|
9
|
+
import "./Combination-D68fi0fY.js";
|
|
10
10
|
import { t as require_jsx_runtime } from "./jsx-runtime-Duz3IlLt.js";
|
|
11
11
|
import "./react-dom-DBPBYhx0.js";
|
|
12
|
-
import { c as Tooltip, n as ErrorBanner } from "./error-banner-
|
|
13
|
-
import { m as useEvent_default, t as useTheme } from "./useTheme-
|
|
12
|
+
import { c as Tooltip, n as ErrorBanner } from "./error-banner-CPLhCPHA.js";
|
|
13
|
+
import { m as useEvent_default, t as useTheme } from "./useTheme-D56Xlrez.js";
|
|
14
14
|
import "./isArrayLikeObject-LulrZoeO.js";
|
|
15
15
|
import "./isSymbol-bQx0U8Lk.js";
|
|
16
16
|
import "./toNumber-C6hfsNmF.js";
|
|
@@ -24,11 +24,11 @@ import { a as tooltipHandler, n as vegaLoadData } from "./loader-BCInj1Mf.js";
|
|
|
24
24
|
import { t as uniq_default } from "./uniq-CMMw6UdM.js";
|
|
25
25
|
import "./zod-AnT9BweX.js";
|
|
26
26
|
import "./invariant-BEpJUgj9.js";
|
|
27
|
-
import { t as useAsyncData } from "./useAsyncData-
|
|
27
|
+
import { t as useAsyncData } from "./useAsyncData-CsSW6_Zh.js";
|
|
28
28
|
import { n as formats } from "./vega-loader.browser-DQF9pFhs.js";
|
|
29
29
|
import "./precisionRound-vxMVRvgy.js";
|
|
30
30
|
import "./linear-B8XR-NVr.js";
|
|
31
|
-
import { t as j } from "./react-vega-
|
|
31
|
+
import { t as j } from "./react-vega-DDXWt_PN.js";
|
|
32
32
|
import "./ordinal-DncHRC0c.js";
|
|
33
33
|
import "./time-DRnNkqSS.js";
|
|
34
34
|
import "./range-CqZlY8CG.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@marimo-team/islands",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.0",
|
|
4
4
|
"main": "dist/main.js",
|
|
5
5
|
"types": "dist/index.d.ts",
|
|
6
6
|
"type": "module",
|
|
@@ -168,7 +168,7 @@
|
|
|
168
168
|
"typescript-memoize": "^1.1.1",
|
|
169
169
|
"use-acp": "0.2.5",
|
|
170
170
|
"use-resize-observer": "^9.1.0",
|
|
171
|
-
"vega-lite": "6.
|
|
171
|
+
"vega-lite": "6.4.0",
|
|
172
172
|
"vega-loader": "^5.1.0",
|
|
173
173
|
"vega-parser": "^7.1.0",
|
|
174
174
|
"vega-tooltip": "^1.1.0",
|
|
@@ -182,7 +182,7 @@ const ChatMessageDisplay: React.FC<ChatMessageProps> = memo(
|
|
|
182
182
|
const content = textParts.map((p) => p.text).join("\n");
|
|
183
183
|
|
|
184
184
|
return (
|
|
185
|
-
<div className="w-[95%] break-
|
|
185
|
+
<div className="w-[95%] wrap-break-word">
|
|
186
186
|
<div className="absolute right-1 top-1 opacity-0 group-hover:opacity-100 transition-opacity">
|
|
187
187
|
<CopyClipboardIcon className="h-3 w-3" value={content || ""} />
|
|
188
188
|
</div>
|
|
@@ -567,9 +567,13 @@ const ChatPanelBody = () => {
|
|
|
567
567
|
options.messages,
|
|
568
568
|
);
|
|
569
569
|
|
|
570
|
+
// Call this here to ensure the value is not stale
|
|
571
|
+
const chatMode = store.get(aiAtom)?.mode || DEFAULT_MODE;
|
|
572
|
+
const tools = FRONTEND_TOOL_REGISTRY.getToolSchemas(chatMode);
|
|
573
|
+
|
|
570
574
|
return {
|
|
571
575
|
body: {
|
|
572
|
-
tools
|
|
576
|
+
tools,
|
|
573
577
|
...options,
|
|
574
578
|
...completionBody,
|
|
575
579
|
},
|
|
@@ -34,6 +34,7 @@ interface TableActionsProps<TData> {
|
|
|
34
34
|
toggleDisplayHeader?: () => void;
|
|
35
35
|
showChartBuilder?: boolean;
|
|
36
36
|
showColumnExplorer?: boolean;
|
|
37
|
+
showRowExplorer?: boolean;
|
|
37
38
|
showPageSizeSelector?: boolean;
|
|
38
39
|
togglePanel?: (panelType: PanelType) => void;
|
|
39
40
|
isPanelOpen?: (panelType: PanelType) => boolean;
|
|
@@ -55,6 +56,7 @@ export const TableActions = <TData,>({
|
|
|
55
56
|
toggleDisplayHeader,
|
|
56
57
|
showChartBuilder,
|
|
57
58
|
showColumnExplorer,
|
|
59
|
+
showRowExplorer,
|
|
58
60
|
showPageSizeSelector,
|
|
59
61
|
togglePanel,
|
|
60
62
|
isPanelOpen,
|
|
@@ -132,20 +134,22 @@ export const TableActions = <TData,>({
|
|
|
132
134
|
)}
|
|
133
135
|
{togglePanel && isPanelOpen !== undefined && (
|
|
134
136
|
<>
|
|
135
|
-
|
|
136
|
-
<
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
137
|
+
{showRowExplorer && (
|
|
138
|
+
<Tooltip content="Toggle row viewer">
|
|
139
|
+
<Button
|
|
140
|
+
variant="text"
|
|
141
|
+
size="xs"
|
|
142
|
+
onClick={() => togglePanel("row-viewer")}
|
|
143
|
+
>
|
|
144
|
+
<PanelRightIcon
|
|
145
|
+
className={cn(
|
|
146
|
+
"w-4 h-4 text-muted-foreground",
|
|
147
|
+
isPanelOpen("row-viewer") && "text-primary",
|
|
148
|
+
)}
|
|
149
|
+
/>
|
|
150
|
+
</Button>
|
|
151
|
+
</Tooltip>
|
|
152
|
+
)}
|
|
149
153
|
{showColumnExplorer && (
|
|
150
154
|
<Tooltip content="Toggle column explorer">
|
|
151
155
|
<Button
|
|
@@ -91,6 +91,7 @@ interface DataTableProps<TData> extends Partial<DownloadActionProps> {
|
|
|
91
91
|
showChartBuilder?: boolean;
|
|
92
92
|
showPageSizeSelector?: boolean;
|
|
93
93
|
showColumnExplorer?: boolean;
|
|
94
|
+
showRowExplorer?: boolean;
|
|
94
95
|
togglePanel?: (panelType: PanelType) => void;
|
|
95
96
|
isPanelOpen?: (panelType: PanelType) => boolean;
|
|
96
97
|
}
|
|
@@ -133,6 +134,7 @@ const DataTableInternal = <TData,>({
|
|
|
133
134
|
showChartBuilder,
|
|
134
135
|
showPageSizeSelector,
|
|
135
136
|
showColumnExplorer,
|
|
137
|
+
showRowExplorer,
|
|
136
138
|
togglePanel,
|
|
137
139
|
isPanelOpen,
|
|
138
140
|
viewedRowIdx,
|
|
@@ -337,6 +339,7 @@ const DataTableInternal = <TData,>({
|
|
|
337
339
|
showChartBuilder={showChartBuilder}
|
|
338
340
|
showPageSizeSelector={showPageSizeSelector}
|
|
339
341
|
showColumnExplorer={showColumnExplorer}
|
|
342
|
+
showRowExplorer={showRowExplorer}
|
|
340
343
|
togglePanel={togglePanel}
|
|
341
344
|
isPanelOpen={isPanelOpen}
|
|
342
345
|
tableLoading={reloading}
|
|
@@ -23,6 +23,7 @@ import { toast } from "@/components/ui/use-toast";
|
|
|
23
23
|
import { useResolvedMarimoConfig } from "@/core/config/config";
|
|
24
24
|
import { useRequestClient } from "@/core/network/requests";
|
|
25
25
|
import type { DependencyTreeNode } from "@/core/network/types";
|
|
26
|
+
import { stripPackageManagerPrefix } from "@/core/packages/package-input-utils";
|
|
26
27
|
import {
|
|
27
28
|
showRemovePackageToast,
|
|
28
29
|
showUpgradePackageToast,
|
|
@@ -191,8 +192,9 @@ const InstallPackageForm: React.FC<{
|
|
|
191
192
|
};
|
|
192
193
|
|
|
193
194
|
const installPackages = () => {
|
|
195
|
+
const cleanedInput = stripPackageManagerPrefix(input);
|
|
194
196
|
handleInstallPackages(
|
|
195
|
-
|
|
197
|
+
cleanedInput.split(",").map((p) => p.trim()),
|
|
196
198
|
onSuccessInstallPackages,
|
|
197
199
|
);
|
|
198
200
|
};
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import type { FileInfo } from "@/core/network/types";
|
|
3
|
+
import { filterHiddenTree, isDirectoryOrFileHidden } from "../file-explorer";
|
|
4
|
+
|
|
5
|
+
// Helpers to build FileInfo objects for tests
|
|
6
|
+
const file = (name: string, path: string): FileInfo => ({
|
|
7
|
+
id: path,
|
|
8
|
+
name,
|
|
9
|
+
path,
|
|
10
|
+
isDirectory: false,
|
|
11
|
+
isMarimoFile: false,
|
|
12
|
+
children: [],
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
const dir = (
|
|
16
|
+
name: string,
|
|
17
|
+
path: string,
|
|
18
|
+
children: FileInfo[] = [],
|
|
19
|
+
): FileInfo => ({
|
|
20
|
+
id: path,
|
|
21
|
+
name,
|
|
22
|
+
path,
|
|
23
|
+
isDirectory: true,
|
|
24
|
+
isMarimoFile: false,
|
|
25
|
+
children,
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
describe("isDirectoryOrFileHidden", () => {
|
|
29
|
+
it("should return true for files starting with dot", () => {
|
|
30
|
+
expect(isDirectoryOrFileHidden(".git")).toBe(true);
|
|
31
|
+
expect(isDirectoryOrFileHidden(".env")).toBe(true);
|
|
32
|
+
expect(isDirectoryOrFileHidden(".gitignore")).toBe(true);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("should return false for normal files", () => {
|
|
36
|
+
expect(isDirectoryOrFileHidden("README.md")).toBe(false);
|
|
37
|
+
expect(isDirectoryOrFileHidden("package.json")).toBe(false);
|
|
38
|
+
expect(isDirectoryOrFileHidden("index.ts")).toBe(false);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("should return false for files with dots in the middle", () => {
|
|
42
|
+
expect(isDirectoryOrFileHidden("file.test.ts")).toBe(false);
|
|
43
|
+
expect(isDirectoryOrFileHidden("my.config.js")).toBe(false);
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
describe("filterHiddenTree", () => {
|
|
48
|
+
it("should return all items when showHidden is true", () => {
|
|
49
|
+
const list: FileInfo[] = [
|
|
50
|
+
dir(".git", "/.git", []),
|
|
51
|
+
file("README.md", "/README.md"),
|
|
52
|
+
file(".env", "/.env"),
|
|
53
|
+
];
|
|
54
|
+
|
|
55
|
+
const result = filterHiddenTree(list, true);
|
|
56
|
+
|
|
57
|
+
expect(result).toBe(list); // should be the exact same reference
|
|
58
|
+
expect(result).toHaveLength(3);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("should filter out hidden files when showHidden is false", () => {
|
|
62
|
+
const list: FileInfo[] = [
|
|
63
|
+
dir(".git", "/.git"),
|
|
64
|
+
file("README.md", "/README.md"),
|
|
65
|
+
file(".env", "/.env"),
|
|
66
|
+
file("package.json", "/package.json"),
|
|
67
|
+
];
|
|
68
|
+
|
|
69
|
+
const result = filterHiddenTree(list, false);
|
|
70
|
+
|
|
71
|
+
expect(result).toHaveLength(2);
|
|
72
|
+
expect(result[0].name).toBe("README.md");
|
|
73
|
+
expect(result[1].name).toBe("package.json");
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it("should filter hidden directories recursively", () => {
|
|
77
|
+
const list: FileInfo[] = [
|
|
78
|
+
dir("src", "/src", [
|
|
79
|
+
file("index.ts", "/src/index.ts"),
|
|
80
|
+
file(".DS_Store", "/src/.DS_Store"),
|
|
81
|
+
file("utils.ts", "/src/utils.ts"),
|
|
82
|
+
]),
|
|
83
|
+
dir(".git", "/.git", [file("config", "/.git/config")]),
|
|
84
|
+
];
|
|
85
|
+
|
|
86
|
+
const result = filterHiddenTree(list, false);
|
|
87
|
+
|
|
88
|
+
expect(result).toHaveLength(1);
|
|
89
|
+
expect(result[0].name).toBe("src");
|
|
90
|
+
expect(result[0].children).toHaveLength(2);
|
|
91
|
+
expect(result[0].children?.[0].name).toBe("index.ts");
|
|
92
|
+
expect(result[0].children?.[1].name).toBe("utils.ts");
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it("should handle nested hidden files", () => {
|
|
96
|
+
const list: FileInfo[] = [
|
|
97
|
+
dir("project", "/project", [
|
|
98
|
+
dir("src", "/project/src", [
|
|
99
|
+
file("index.ts", "/project/src/index.ts"),
|
|
100
|
+
file(".backup", "/project/src/.backup"),
|
|
101
|
+
]),
|
|
102
|
+
file(".env", "/project/.env"),
|
|
103
|
+
]),
|
|
104
|
+
];
|
|
105
|
+
|
|
106
|
+
const result = filterHiddenTree(list, false);
|
|
107
|
+
|
|
108
|
+
expect(result).toHaveLength(1);
|
|
109
|
+
expect(result[0].children).toHaveLength(1);
|
|
110
|
+
expect(result[0].children?.[0].name).toBe("src");
|
|
111
|
+
expect(result[0].children?.[0].children).toHaveLength(1);
|
|
112
|
+
expect(result[0].children?.[0].children?.[0].name).toBe("index.ts");
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it("should preserve directory structure when no children are filtered", () => {
|
|
116
|
+
const list: FileInfo[] = [
|
|
117
|
+
dir("src", "/src", [
|
|
118
|
+
file("index.ts", "/src/index.ts"),
|
|
119
|
+
file("utils.ts", "/src/utils.ts"),
|
|
120
|
+
]),
|
|
121
|
+
];
|
|
122
|
+
|
|
123
|
+
const result = filterHiddenTree(list, true);
|
|
124
|
+
|
|
125
|
+
// Should return the same reference since nothing changed
|
|
126
|
+
expect(result[0]).toBe(list[0]);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it("should create new object only when children are filtered", () => {
|
|
130
|
+
const list: FileInfo[] = [
|
|
131
|
+
dir("src", "/src", [
|
|
132
|
+
file("index.ts", "/src/index.ts"),
|
|
133
|
+
file(".hidden", "/src/.hidden"),
|
|
134
|
+
]),
|
|
135
|
+
];
|
|
136
|
+
|
|
137
|
+
const result = filterHiddenTree(list, false);
|
|
138
|
+
|
|
139
|
+
// Should be a new object since children changed
|
|
140
|
+
expect(result[0]).not.toBe(list[0]);
|
|
141
|
+
expect(result[0].children).not.toBe(list[0].children);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it("should handle empty list", () => {
|
|
145
|
+
const result = filterHiddenTree([], false);
|
|
146
|
+
expect(result).toEqual([]);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it("should handle empty children arrays", () => {
|
|
150
|
+
const list: FileInfo[] = [dir("empty-dir", "/empty-dir", [])];
|
|
151
|
+
|
|
152
|
+
const result = filterHiddenTree(list, false);
|
|
153
|
+
|
|
154
|
+
expect(result).toHaveLength(1);
|
|
155
|
+
expect(result[0].children).toEqual([]);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it("should handle deeply nested structures", () => {
|
|
159
|
+
const list: FileInfo[] = [
|
|
160
|
+
dir("level1", "/level1", [
|
|
161
|
+
dir("level2", "/level1/level2", [
|
|
162
|
+
dir("level3", "/level1/level2/level3", [
|
|
163
|
+
file("file.ts", "/level1/level2/level3/file.ts"),
|
|
164
|
+
file(".hidden", "/level1/level2/level3/.hidden"),
|
|
165
|
+
]),
|
|
166
|
+
]),
|
|
167
|
+
file(".ignore", "/level1/.ignore"),
|
|
168
|
+
]),
|
|
169
|
+
];
|
|
170
|
+
|
|
171
|
+
const result = filterHiddenTree(list, false);
|
|
172
|
+
|
|
173
|
+
expect(result[0].children?.[0].children?.[0].children).toHaveLength(1);
|
|
174
|
+
expect(result[0].children?.[0].children?.[0].children?.[0].name).toBe(
|
|
175
|
+
"file.ts",
|
|
176
|
+
);
|
|
177
|
+
});
|
|
178
|
+
});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/* Copyright 2024 Marimo. All rights reserved. */
|
|
2
2
|
|
|
3
3
|
import { useAtom } from "jotai";
|
|
4
|
+
import { atomWithStorage } from "jotai/utils";
|
|
4
5
|
import {
|
|
5
6
|
ArrowLeftIcon,
|
|
6
7
|
BetweenHorizontalStartIcon,
|
|
@@ -12,6 +13,7 @@ import {
|
|
|
12
13
|
DownloadIcon,
|
|
13
14
|
Edit3Icon,
|
|
14
15
|
ExternalLinkIcon,
|
|
16
|
+
EyeOffIcon,
|
|
15
17
|
FilePlus2Icon,
|
|
16
18
|
FolderPlusIcon,
|
|
17
19
|
ListTreeIcon,
|
|
@@ -56,6 +58,7 @@ import { downloadBlob } from "@/utils/download";
|
|
|
56
58
|
import { openNotebook } from "@/utils/links";
|
|
57
59
|
import type { FilePath } from "@/utils/paths";
|
|
58
60
|
import { fileSplit } from "@/utils/pathUtils";
|
|
61
|
+
import { jotaiJsonStorage } from "@/utils/storage/jotai";
|
|
59
62
|
import marimoIcon from "../../../assets/icon-32x32.png";
|
|
60
63
|
import { FileViewer } from "./file-viewer";
|
|
61
64
|
import type { RequestingTree } from "./requesting-tree";
|
|
@@ -68,6 +71,15 @@ import {
|
|
|
68
71
|
} from "./types";
|
|
69
72
|
import { useFileExplorerUpload } from "./upload";
|
|
70
73
|
|
|
74
|
+
const hiddenFilesState = atomWithStorage(
|
|
75
|
+
"marimo:showHiddenFiles",
|
|
76
|
+
true,
|
|
77
|
+
jotaiJsonStorage,
|
|
78
|
+
{
|
|
79
|
+
getOnInit: true,
|
|
80
|
+
},
|
|
81
|
+
);
|
|
82
|
+
|
|
71
83
|
const RequestingTreeContext = React.createContext<RequestingTree | null>(null);
|
|
72
84
|
|
|
73
85
|
export const FileExplorer: React.FC<{
|
|
@@ -77,6 +89,9 @@ export const FileExplorer: React.FC<{
|
|
|
77
89
|
const [tree] = useAtom(treeAtom);
|
|
78
90
|
const [data, setData] = useState<FileInfo[]>([]);
|
|
79
91
|
const [openFile, setOpenFile] = useState<FileInfo | null>(null);
|
|
92
|
+
const [showHiddenFiles, setShowHiddenFiles] =
|
|
93
|
+
useAtom<boolean>(hiddenFilesState);
|
|
94
|
+
|
|
80
95
|
const { openPrompt } = useImperativeModal();
|
|
81
96
|
// Keep external state to remember which folders are open
|
|
82
97
|
// when this component is unmounted
|
|
@@ -87,6 +102,11 @@ export const FileExplorer: React.FC<{
|
|
|
87
102
|
tree.refreshAll(Object.keys(openState).filter((id) => openState[id]));
|
|
88
103
|
});
|
|
89
104
|
|
|
105
|
+
const handleHiddenFilesToggle = useEvent(() => {
|
|
106
|
+
const newValue = !showHiddenFiles;
|
|
107
|
+
setShowHiddenFiles(newValue);
|
|
108
|
+
});
|
|
109
|
+
|
|
90
110
|
const handleCreateFolder = useEvent(async () => {
|
|
91
111
|
openPrompt({
|
|
92
112
|
title: "Folder name",
|
|
@@ -148,10 +168,15 @@ export const FileExplorer: React.FC<{
|
|
|
148
168
|
);
|
|
149
169
|
}
|
|
150
170
|
|
|
171
|
+
const visibleData = React.useMemo(
|
|
172
|
+
() => filterHiddenTree(data, showHiddenFiles),
|
|
173
|
+
[data, showHiddenFiles],
|
|
174
|
+
);
|
|
151
175
|
return (
|
|
152
176
|
<>
|
|
153
177
|
<Toolbar
|
|
154
178
|
onRefresh={handleRefresh}
|
|
179
|
+
onHidden={handleHiddenFilesToggle}
|
|
155
180
|
onCreateFile={handleCreateFile}
|
|
156
181
|
onCreateFolder={handleCreateFolder}
|
|
157
182
|
onCollapseAll={handleCollapseAll}
|
|
@@ -163,7 +188,7 @@ export const FileExplorer: React.FC<{
|
|
|
163
188
|
ref={treeRef}
|
|
164
189
|
height={height - 33}
|
|
165
190
|
className="h-full"
|
|
166
|
-
data={
|
|
191
|
+
data={visibleData}
|
|
167
192
|
initialOpenState={openState}
|
|
168
193
|
openByDefault={false}
|
|
169
194
|
// Hide the drop cursor
|
|
@@ -215,6 +240,7 @@ const INDENT_STEP = 15;
|
|
|
215
240
|
|
|
216
241
|
interface ToolbarProps {
|
|
217
242
|
onRefresh: () => void;
|
|
243
|
+
onHidden: () => void;
|
|
218
244
|
onCreateFile: () => void;
|
|
219
245
|
onCreateFolder: () => void;
|
|
220
246
|
onCollapseAll: () => void;
|
|
@@ -223,6 +249,7 @@ interface ToolbarProps {
|
|
|
223
249
|
|
|
224
250
|
const Toolbar = ({
|
|
225
251
|
onRefresh,
|
|
252
|
+
onHidden,
|
|
226
253
|
onCreateFile,
|
|
227
254
|
onCreateFolder,
|
|
228
255
|
onCollapseAll,
|
|
@@ -277,6 +304,16 @@ const Toolbar = ({
|
|
|
277
304
|
<RefreshCcwIcon size={16} />
|
|
278
305
|
</Button>
|
|
279
306
|
</Tooltip>
|
|
307
|
+
<Tooltip content="Toggle hidden files">
|
|
308
|
+
<Button
|
|
309
|
+
data-testid="file-explorer-hidden-files-button"
|
|
310
|
+
onClick={onHidden}
|
|
311
|
+
variant="text"
|
|
312
|
+
size="xs"
|
|
313
|
+
>
|
|
314
|
+
<EyeOffIcon size={16} />
|
|
315
|
+
</Button>
|
|
316
|
+
</Tooltip>
|
|
280
317
|
<Tooltip content="Collapse all folders">
|
|
281
318
|
<Button
|
|
282
319
|
data-testid="file-explorer-collapse-button"
|
|
@@ -677,3 +714,35 @@ function openMarimoNotebook(
|
|
|
677
714
|
event.preventDefault();
|
|
678
715
|
openNotebook(path);
|
|
679
716
|
}
|
|
717
|
+
|
|
718
|
+
export function filterHiddenTree(
|
|
719
|
+
list: FileInfo[],
|
|
720
|
+
showHidden: boolean,
|
|
721
|
+
): FileInfo[] {
|
|
722
|
+
if (showHidden) {
|
|
723
|
+
return list;
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
const out: FileInfo[] = [];
|
|
727
|
+
for (const item of list) {
|
|
728
|
+
if (isDirectoryOrFileHidden(item.name)) {
|
|
729
|
+
continue;
|
|
730
|
+
}
|
|
731
|
+
let next = item;
|
|
732
|
+
if (item.children && item.children.length) {
|
|
733
|
+
const kids = filterHiddenTree(item.children, showHidden);
|
|
734
|
+
if (kids !== item.children) {
|
|
735
|
+
next = { ...item, children: kids };
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
out.push(next);
|
|
739
|
+
}
|
|
740
|
+
return out;
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
export function isDirectoryOrFileHidden(filename: string): boolean {
|
|
744
|
+
if (filename.startsWith(".")) {
|
|
745
|
+
return true;
|
|
746
|
+
}
|
|
747
|
+
return false;
|
|
748
|
+
}
|
|
@@ -195,10 +195,15 @@ const WorkspaceNotebooks: React.FC = () => {
|
|
|
195
195
|
}
|
|
196
196
|
>
|
|
197
197
|
Workspace
|
|
198
|
-
<
|
|
199
|
-
|
|
198
|
+
<Button
|
|
199
|
+
variant="text"
|
|
200
|
+
size="icon"
|
|
201
|
+
className="w-4 h-4 ml-1 p-0 opacity-70 hover:opacity-100"
|
|
200
202
|
onClick={() => refetch()}
|
|
201
|
-
|
|
203
|
+
aria-label="Refresh workspace"
|
|
204
|
+
>
|
|
205
|
+
<RefreshCcwIcon className="w-4 h-4" />
|
|
206
|
+
</Button>
|
|
202
207
|
{isFetching && <Spinner size="small" />}
|
|
203
208
|
</Header>
|
|
204
209
|
<div className="flex flex-col divide-y divide-(--slate-3) border rounded overflow-hidden max-h-192 overflow-y-auto shadow-sm bg-background">
|
|
@@ -63,7 +63,7 @@ describe("FrontendToolRegistry", () => {
|
|
|
63
63
|
it("returns tool schemas with expected shape and memoizes the result", () => {
|
|
64
64
|
const registry = new FrontendToolRegistry([new TestFrontendTool()]);
|
|
65
65
|
|
|
66
|
-
const schemas1 = registry.getToolSchemas();
|
|
66
|
+
const schemas1 = registry.getToolSchemas("ask");
|
|
67
67
|
expect(Array.isArray(schemas1)).toBe(true);
|
|
68
68
|
expect(schemas1.length).toBe(1);
|
|
69
69
|
|
|
@@ -80,7 +80,11 @@ describe("FrontendToolRegistry", () => {
|
|
|
80
80
|
expect(properties && typeof properties === "object").toBe(true);
|
|
81
81
|
expect("name" in (properties ?? {})).toBe(true);
|
|
82
82
|
|
|
83
|
-
const schemas2 = registry.getToolSchemas();
|
|
83
|
+
const schemas2 = registry.getToolSchemas("ask");
|
|
84
84
|
expect(schemas2).toBe(schemas1);
|
|
85
|
+
|
|
86
|
+
// Should not include tools for other modes
|
|
87
|
+
const schemas3 = registry.getToolSchemas("agent");
|
|
88
|
+
expect(schemas3.length).toBe(0);
|
|
85
89
|
});
|
|
86
90
|
});
|
|
@@ -123,8 +123,11 @@ export class FrontendToolRegistry {
|
|
|
123
123
|
}
|
|
124
124
|
|
|
125
125
|
@Memoize()
|
|
126
|
-
getToolSchemas(): FrontendToolDefinition[] {
|
|
127
|
-
|
|
126
|
+
getToolSchemas(mode: CopilotMode): FrontendToolDefinition[] {
|
|
127
|
+
const tools = [...this.tools.values()].filter((tool) =>
|
|
128
|
+
tool.mode.includes(mode),
|
|
129
|
+
);
|
|
130
|
+
return tools.map((tool) => ({
|
|
128
131
|
name: tool.name,
|
|
129
132
|
description: formatToolDescription(tool.description),
|
|
130
133
|
parameters: z.toJSONSchema(tool.schema),
|