@marimo-team/islands 0.20.3-dev94 → 0.20.3-dev97
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/main.js +2 -2
- package/dist/style.css +1 -1
- package/package.json +1 -1
- package/src/components/chat/acp/agent-panel.tsx +29 -8
- package/src/components/databases/icons/google-drive.svg +8 -0
- package/src/components/datasources/datasources.tsx +5 -5
- package/src/components/editor/actions/useNotebookActions.tsx +15 -2
- package/src/components/editor/connections/add-connection-dialog.tsx +83 -0
- package/src/components/editor/connections/components.tsx +177 -0
- package/src/components/editor/{database → connections/database}/__tests__/as-code.test.ts +1 -1
- package/src/components/editor/connections/database/add-database-form.tsx +303 -0
- package/src/components/editor/{database → connections/database}/as-code.ts +1 -1
- package/src/components/editor/connections/storage/__tests__/__snapshots__/as-code.test.ts.snap +100 -0
- package/src/components/editor/connections/storage/__tests__/as-code.test.ts +166 -0
- package/src/components/editor/connections/storage/add-storage-form.tsx +135 -0
- package/src/components/editor/connections/storage/as-code.ts +188 -0
- package/src/components/editor/connections/storage/schemas.ts +141 -0
- package/src/components/storage/components.tsx +9 -3
- package/src/components/storage/storage-inspector.tsx +20 -1
- package/src/core/codemirror/__tests__/format.test.ts +9 -1
- package/src/core/saving/file-state.ts +7 -0
- package/src/core/storage/types.ts +1 -0
- package/src/mount.tsx +6 -1
- package/src/components/editor/database/add-database-form.tsx +0 -420
- /package/src/components/editor/{database → connections}/__tests__/secrets.test.ts +0 -0
- /package/src/components/editor/{database → connections/database}/__tests__/__snapshots__/as-code.test.ts.snap +0 -0
- /package/src/components/editor/{database → connections/database}/schemas.ts +0 -0
- /package/src/components/editor/{database → connections}/form-renderers.tsx +0 -0
- /package/src/components/editor/{database → connections}/secrets.ts +0 -0
package/package.json
CHANGED
|
@@ -60,11 +60,11 @@ import {
|
|
|
60
60
|
import { toast } from "@/components/ui/use-toast";
|
|
61
61
|
import { DelayMount } from "@/components/utils/delay-mount";
|
|
62
62
|
import { useRequestClient } from "@/core/network/requests";
|
|
63
|
-
import { filenameAtom } from "@/core/saving/file-state";
|
|
63
|
+
import { cwdAtom, filenameAtom } from "@/core/saving/file-state";
|
|
64
64
|
import { store } from "@/core/state/jotai";
|
|
65
65
|
import { ErrorBanner } from "@/plugins/impl/common/error-banner";
|
|
66
66
|
import { Functions } from "@/utils/functions";
|
|
67
|
-
import { Paths } from "@/utils/paths";
|
|
67
|
+
import { PathBuilder, Paths } from "@/utils/paths";
|
|
68
68
|
import {
|
|
69
69
|
AddContextButton,
|
|
70
70
|
AttachFileButton,
|
|
@@ -614,7 +614,11 @@ ChatContent.displayName = "ChatContent";
|
|
|
614
614
|
|
|
615
615
|
const NO_WS_SET = "_skip_auto_connect_";
|
|
616
616
|
|
|
617
|
-
function getCwd() {
|
|
617
|
+
function getCwd(): string {
|
|
618
|
+
const cwd = store.get(cwdAtom);
|
|
619
|
+
if (cwd) {
|
|
620
|
+
return cwd;
|
|
621
|
+
}
|
|
618
622
|
const filename = store.get(filenameAtom);
|
|
619
623
|
if (!filename) {
|
|
620
624
|
throw new Error(
|
|
@@ -624,6 +628,21 @@ function getCwd() {
|
|
|
624
628
|
return Paths.dirname(filename);
|
|
625
629
|
}
|
|
626
630
|
|
|
631
|
+
function getAbsoluteFilename(): string {
|
|
632
|
+
const filename = store.get(filenameAtom);
|
|
633
|
+
if (!filename) {
|
|
634
|
+
throw new Error(
|
|
635
|
+
"Please save the notebook and refresh the browser to use the agent",
|
|
636
|
+
);
|
|
637
|
+
}
|
|
638
|
+
const cwd = store.get(cwdAtom);
|
|
639
|
+
if (cwd) {
|
|
640
|
+
const builder = PathBuilder.guessDeliminator(cwd);
|
|
641
|
+
return builder.join(cwd, String(Paths.basename(filename)));
|
|
642
|
+
}
|
|
643
|
+
return filename;
|
|
644
|
+
}
|
|
645
|
+
|
|
627
646
|
const AgentPanel: React.FC = () => {
|
|
628
647
|
const [isLoading, setIsLoading] = useState(false);
|
|
629
648
|
const [error, setError] = useState<Error | string | null>(null);
|
|
@@ -862,8 +881,10 @@ const AgentPanel: React.FC = () => {
|
|
|
862
881
|
setSessionState((prev) => updateSessionTitle(prev, prompt));
|
|
863
882
|
}
|
|
864
883
|
|
|
865
|
-
|
|
866
|
-
|
|
884
|
+
let absoluteFilename: string;
|
|
885
|
+
try {
|
|
886
|
+
absoluteFilename = getAbsoluteFilename();
|
|
887
|
+
} catch {
|
|
867
888
|
toast({
|
|
868
889
|
title: "Notebook must be named",
|
|
869
890
|
description: "Please name the notebook to use the agent",
|
|
@@ -894,16 +915,16 @@ const AgentPanel: React.FC = () => {
|
|
|
894
915
|
promptBlocks.push(
|
|
895
916
|
{
|
|
896
917
|
type: "resource_link",
|
|
897
|
-
uri:
|
|
918
|
+
uri: absoluteFilename,
|
|
898
919
|
mimeType: "text/x-python",
|
|
899
|
-
name:
|
|
920
|
+
name: absoluteFilename,
|
|
900
921
|
},
|
|
901
922
|
{
|
|
902
923
|
type: "resource",
|
|
903
924
|
resource: {
|
|
904
925
|
uri: "marimo_rules.md",
|
|
905
926
|
mimeType: "text/plain",
|
|
906
|
-
text: getAgentPrompt(
|
|
927
|
+
text: getAgentPrompt(absoluteFilename),
|
|
907
928
|
},
|
|
908
929
|
},
|
|
909
930
|
);
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
<svg viewBox="0 0 87.3 78" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<path d="m6.6 66.85 3.85 6.65c.8 1.4 1.95 2.5 3.3 3.3l13.75-23.8h-27.5c0 1.55.4 3.1 1.2 4.5z" fill="#0066da"/>
|
|
3
|
+
<path d="m43.65 25-13.75-23.8c-1.35.8-2.5 1.9-3.3 3.3l-25.4 44a9.06 9.06 0 0 0 -1.2 4.5h27.5z" fill="#00ac47"/>
|
|
4
|
+
<path d="m73.55 76.8c1.35-.8 2.5-1.9 3.3-3.3l1.6-2.75 7.65-13.25c.8-1.4 1.2-2.95 1.2-4.5h-27.502l5.852 11.5z" fill="#ea4335"/>
|
|
5
|
+
<path d="m43.65 25 13.75-23.8c-1.35-.8-2.9-1.2-4.5-1.2h-18.5c-1.6 0-3.15.45-4.5 1.2z" fill="#00832d"/>
|
|
6
|
+
<path d="m59.8 53h-32.3l-13.75 23.8c1.35.8 2.9 1.2 4.5 1.2h50.8c1.6 0 3.15-.45 4.5-1.2z" fill="#2684fc"/>
|
|
7
|
+
<path d="m73.4 26.5-12.7-22c-.8-1.4-1.95-2.5-3.3-3.3l-13.75 23.8 16.15 28h27.45c0-1.55-.4-3.1-1.2-4.5z" fill="#ffba00"/>
|
|
8
|
+
</svg>
|
|
@@ -60,7 +60,7 @@ import { ErrorBoundary } from "../editor/boundary/ErrorBoundary";
|
|
|
60
60
|
import { PythonIcon } from "../editor/cell/code/icons";
|
|
61
61
|
import { useAddCodeToNewCell } from "../editor/cell/useAddCell";
|
|
62
62
|
import { PanelEmptyState } from "../editor/chrome/panels/empty-state";
|
|
63
|
-
import {
|
|
63
|
+
import { AddConnectionDialog } from "../editor/connections/add-connection-dialog";
|
|
64
64
|
import { DatasetColumnPreview } from "./column-preview";
|
|
65
65
|
import {
|
|
66
66
|
ColumnName,
|
|
@@ -160,12 +160,12 @@ export const DataSources: React.FC = () => {
|
|
|
160
160
|
title="No tables found"
|
|
161
161
|
description="Any datasets/dataframes in the global scope will be shown here."
|
|
162
162
|
action={
|
|
163
|
-
<
|
|
163
|
+
<AddConnectionDialog>
|
|
164
164
|
<Button variant="outline" size="sm">
|
|
165
165
|
Add database or catalog
|
|
166
166
|
<PlusIcon className="h-4 w-4 ml-2" />
|
|
167
167
|
</Button>
|
|
168
|
-
</
|
|
168
|
+
</AddConnectionDialog>
|
|
169
169
|
}
|
|
170
170
|
icon={<DatabaseIcon />}
|
|
171
171
|
/>
|
|
@@ -201,7 +201,7 @@ export const DataSources: React.FC = () => {
|
|
|
201
201
|
</button>
|
|
202
202
|
)}
|
|
203
203
|
|
|
204
|
-
<
|
|
204
|
+
<AddConnectionDialog>
|
|
205
205
|
<Button
|
|
206
206
|
variant="ghost"
|
|
207
207
|
size="sm"
|
|
@@ -209,7 +209,7 @@ export const DataSources: React.FC = () => {
|
|
|
209
209
|
>
|
|
210
210
|
<PlusIcon className="h-4 w-4" />
|
|
211
211
|
</Button>
|
|
212
|
-
</
|
|
212
|
+
</AddConnectionDialog>
|
|
213
213
|
</div>
|
|
214
214
|
|
|
215
215
|
<CommandList className="flex flex-col">
|
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
FolderDownIcon,
|
|
25
25
|
GithubIcon,
|
|
26
26
|
GlobeIcon,
|
|
27
|
+
HardDrive,
|
|
27
28
|
Home,
|
|
28
29
|
ImageIcon,
|
|
29
30
|
KeyboardIcon,
|
|
@@ -78,9 +79,9 @@ import { newNotebookURL } from "@/utils/urls";
|
|
|
78
79
|
import { useRunAllCells } from "../cell/useRunCells";
|
|
79
80
|
import { useChromeActions, useChromeState } from "../chrome/state";
|
|
80
81
|
import { PANELS } from "../chrome/types";
|
|
82
|
+
import { AddConnectionDialogContent } from "../connections/add-connection-dialog";
|
|
81
83
|
import { keyboardShortcutsAtom } from "../controls/keyboard-shortcuts";
|
|
82
84
|
import { commandPaletteAtom } from "../controls/state";
|
|
83
|
-
import { AddDatabaseDialogContent } from "../database/add-database-form";
|
|
84
85
|
import { displayLayoutName, getLayoutIcon } from "../renderers/layout-select";
|
|
85
86
|
import { LAYOUT_TYPES } from "../renderers/types";
|
|
86
87
|
import { runServerSidePDFDownload } from "./pdf-export";
|
|
@@ -450,7 +451,19 @@ export function useNotebookActions() {
|
|
|
450
451
|
icon: <DatabaseIcon size={14} strokeWidth={1.5} />,
|
|
451
452
|
label: "Add database connection",
|
|
452
453
|
handle: () => {
|
|
453
|
-
openModal(<
|
|
454
|
+
openModal(<AddConnectionDialogContent onClose={closeModal} />);
|
|
455
|
+
},
|
|
456
|
+
},
|
|
457
|
+
{
|
|
458
|
+
icon: <HardDrive size={14} strokeWidth={1.5} />,
|
|
459
|
+
label: "Add remote storage",
|
|
460
|
+
handle: () => {
|
|
461
|
+
openModal(
|
|
462
|
+
<AddConnectionDialogContent
|
|
463
|
+
defaultTab="storage"
|
|
464
|
+
onClose={closeModal}
|
|
465
|
+
/>,
|
|
466
|
+
);
|
|
454
467
|
},
|
|
455
468
|
},
|
|
456
469
|
{
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
import {
|
|
5
|
+
Dialog,
|
|
6
|
+
DialogContent,
|
|
7
|
+
DialogDescription,
|
|
8
|
+
DialogHeader,
|
|
9
|
+
DialogTitle,
|
|
10
|
+
DialogTrigger,
|
|
11
|
+
} from "@/components/ui/dialog";
|
|
12
|
+
import { ExternalLink } from "@/components/ui/links";
|
|
13
|
+
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
|
14
|
+
import { AddDatabaseForm } from "./database/add-database-form";
|
|
15
|
+
import { AddStorageForm } from "./storage/add-storage-form";
|
|
16
|
+
|
|
17
|
+
type ConnectionTab = "databases" | "storage";
|
|
18
|
+
|
|
19
|
+
export const AddConnectionDialog: React.FC<{
|
|
20
|
+
children: React.ReactNode;
|
|
21
|
+
defaultTab?: ConnectionTab;
|
|
22
|
+
}> = ({ children, defaultTab = "databases" }) => {
|
|
23
|
+
const [open, setOpen] = useState(false);
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<Dialog open={open} onOpenChange={setOpen}>
|
|
27
|
+
<DialogTrigger asChild={true}>{children}</DialogTrigger>
|
|
28
|
+
<AddConnectionDialogContent
|
|
29
|
+
defaultTab={defaultTab}
|
|
30
|
+
onClose={() => setOpen(false)}
|
|
31
|
+
/>
|
|
32
|
+
</Dialog>
|
|
33
|
+
);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export const AddConnectionDialogContent: React.FC<{
|
|
37
|
+
defaultTab?: ConnectionTab;
|
|
38
|
+
onClose: () => void;
|
|
39
|
+
}> = ({ defaultTab = "databases", onClose }) => {
|
|
40
|
+
const tabHeader = (
|
|
41
|
+
<TabsList className="w-full mb-4">
|
|
42
|
+
<TabsTrigger value="databases" className="flex-1">
|
|
43
|
+
Databases & Catalogs
|
|
44
|
+
</TabsTrigger>
|
|
45
|
+
<TabsTrigger value="storage" className="flex-1">
|
|
46
|
+
Remote Storages
|
|
47
|
+
</TabsTrigger>
|
|
48
|
+
</TabsList>
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
<DialogContent className="max-h-[75vh] overflow-y-auto">
|
|
53
|
+
<DialogHeader>
|
|
54
|
+
<DialogTitle>Add Connection</DialogTitle>
|
|
55
|
+
<DialogDescription>
|
|
56
|
+
Connect to a{" "}
|
|
57
|
+
<ExternalLink href="https://docs.marimo.io/guides/working_with_data/sql/#connecting-to-a-custom-database">
|
|
58
|
+
database, data catalog
|
|
59
|
+
</ExternalLink>{" "}
|
|
60
|
+
or{" "}
|
|
61
|
+
<ExternalLink href="https://docs.marimo.io/guides/working_with_data/remote_storage/">
|
|
62
|
+
remote storage
|
|
63
|
+
</ExternalLink>{" "}
|
|
64
|
+
to work with data directly from your notebook.
|
|
65
|
+
</DialogDescription>
|
|
66
|
+
</DialogHeader>
|
|
67
|
+
<Tabs defaultValue={defaultTab}>
|
|
68
|
+
<TabsContent
|
|
69
|
+
value="databases"
|
|
70
|
+
className="mt-0 focus-visible:ring-0 focus-visible:ring-offset-0"
|
|
71
|
+
>
|
|
72
|
+
<AddDatabaseForm onSubmit={onClose} header={tabHeader} />
|
|
73
|
+
</TabsContent>
|
|
74
|
+
<TabsContent
|
|
75
|
+
value="storage"
|
|
76
|
+
className="mt-0 focus-visible:ring-0 focus-visible:ring-offset-0"
|
|
77
|
+
>
|
|
78
|
+
<AddStorageForm onSubmit={onClose} header={tabHeader} />
|
|
79
|
+
</TabsContent>
|
|
80
|
+
</Tabs>
|
|
81
|
+
</DialogContent>
|
|
82
|
+
);
|
|
83
|
+
};
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import { zodResolver } from "@hookform/resolvers/zod";
|
|
4
|
+
import React from "react";
|
|
5
|
+
import { type DefaultValues, type FieldValues, useForm } from "react-hook-form";
|
|
6
|
+
import type { z } from "zod";
|
|
7
|
+
import { type FormRenderer, ZodForm } from "@/components/forms/form";
|
|
8
|
+
import { getDefaults } from "@/components/forms/form-utils";
|
|
9
|
+
import { Button } from "@/components/ui/button";
|
|
10
|
+
import { FormErrorsBanner } from "@/components/ui/form";
|
|
11
|
+
import {
|
|
12
|
+
Select,
|
|
13
|
+
SelectContent,
|
|
14
|
+
SelectGroup,
|
|
15
|
+
SelectItem,
|
|
16
|
+
SelectTrigger,
|
|
17
|
+
SelectValue,
|
|
18
|
+
} from "@/components/ui/select";
|
|
19
|
+
import { useCellActions } from "@/core/cells/cells";
|
|
20
|
+
import { useLastFocusedCellId } from "@/core/cells/focus";
|
|
21
|
+
import { ENV_RENDERER, SecretsProvider } from "./form-renderers";
|
|
22
|
+
|
|
23
|
+
const RENDERERS: FormRenderer[] = [ENV_RENDERER];
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Grid layout for provider/database selector buttons.
|
|
27
|
+
*/
|
|
28
|
+
export const SelectorGrid: React.FC<{ children: React.ReactNode }> = ({
|
|
29
|
+
children,
|
|
30
|
+
}) => <div className="grid grid-cols-2 md:grid-cols-3 gap-4">{children}</div>;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* A colored button tile for selecting a provider/database.
|
|
34
|
+
*/
|
|
35
|
+
export const SelectorButton: React.FC<{
|
|
36
|
+
name: string;
|
|
37
|
+
color: string;
|
|
38
|
+
icon: React.ReactNode;
|
|
39
|
+
onSelect: () => void;
|
|
40
|
+
}> = ({ name, color, icon, onSelect }) => (
|
|
41
|
+
<button
|
|
42
|
+
type="button"
|
|
43
|
+
className="py-3 flex flex-col items-center justify-center gap-1 transition-all hover:scale-105 hover:brightness-110 rounded shadow-sm-solid hover:shadow-md-solid"
|
|
44
|
+
style={{ backgroundColor: color }}
|
|
45
|
+
onClick={onSelect}
|
|
46
|
+
>
|
|
47
|
+
{icon}
|
|
48
|
+
<span className="text-white font-medium text-lg">{name}</span>
|
|
49
|
+
</button>
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Footer with Back/Add buttons and a library picker.
|
|
54
|
+
*/
|
|
55
|
+
export const ConnectionFormFooter = <L extends string>({
|
|
56
|
+
onBack,
|
|
57
|
+
isValid,
|
|
58
|
+
libraries,
|
|
59
|
+
preferredLibrary,
|
|
60
|
+
onLibraryChange,
|
|
61
|
+
displayNames,
|
|
62
|
+
libraryLabel = "Preferred library",
|
|
63
|
+
}: {
|
|
64
|
+
onBack: () => void;
|
|
65
|
+
isValid: boolean;
|
|
66
|
+
libraries: L[];
|
|
67
|
+
preferredLibrary: L;
|
|
68
|
+
onLibraryChange: (library: L) => void;
|
|
69
|
+
displayNames: Record<L, string>;
|
|
70
|
+
libraryLabel?: string;
|
|
71
|
+
}) => (
|
|
72
|
+
<div className="flex gap-2 justify-between">
|
|
73
|
+
<div className="flex gap-2">
|
|
74
|
+
<Button type="button" variant="outline" onClick={onBack}>
|
|
75
|
+
Back
|
|
76
|
+
</Button>
|
|
77
|
+
<Button type="submit" disabled={!isValid}>
|
|
78
|
+
Add
|
|
79
|
+
</Button>
|
|
80
|
+
</div>
|
|
81
|
+
<div>
|
|
82
|
+
<Select value={preferredLibrary} onValueChange={onLibraryChange}>
|
|
83
|
+
<div className="flex flex-col gap-1 items-end">
|
|
84
|
+
<SelectTrigger>
|
|
85
|
+
<SelectValue placeholder="Select a library" />
|
|
86
|
+
</SelectTrigger>
|
|
87
|
+
<span className="text-xs text-muted-foreground">{libraryLabel}</span>
|
|
88
|
+
</div>
|
|
89
|
+
<SelectContent>
|
|
90
|
+
<SelectGroup>
|
|
91
|
+
{libraries.map((library) => (
|
|
92
|
+
<SelectItem key={library} value={library}>
|
|
93
|
+
{displayNames[library] ?? library}
|
|
94
|
+
</SelectItem>
|
|
95
|
+
))}
|
|
96
|
+
</SelectGroup>
|
|
97
|
+
</SelectContent>
|
|
98
|
+
</Select>
|
|
99
|
+
</div>
|
|
100
|
+
</div>
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Returns a callback that inserts code into a new cell after the last focused cell.
|
|
105
|
+
*/
|
|
106
|
+
export function useInsertCode() {
|
|
107
|
+
const { createNewCell } = useCellActions();
|
|
108
|
+
const lastFocusedCellId = useLastFocusedCellId();
|
|
109
|
+
|
|
110
|
+
return (code: string) => {
|
|
111
|
+
createNewCell({
|
|
112
|
+
code,
|
|
113
|
+
before: false,
|
|
114
|
+
cellId: lastFocusedCellId ?? "__end__",
|
|
115
|
+
skipIfCodeExists: true,
|
|
116
|
+
});
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Generic connection form: Zod-driven form with secrets support, a library
|
|
122
|
+
* picker, and Back/Add buttons. Used by both database and storage forms.
|
|
123
|
+
*/
|
|
124
|
+
export const ConnectionForm = <T extends FieldValues, L extends string>({
|
|
125
|
+
schema,
|
|
126
|
+
libraries,
|
|
127
|
+
preferredLibrary: initialPreferred,
|
|
128
|
+
displayNames,
|
|
129
|
+
libraryLabel,
|
|
130
|
+
generateCode,
|
|
131
|
+
onSubmit,
|
|
132
|
+
onBack,
|
|
133
|
+
}: {
|
|
134
|
+
schema: z.ZodType<T, FieldValues>;
|
|
135
|
+
libraries: L[];
|
|
136
|
+
preferredLibrary: L;
|
|
137
|
+
displayNames: Record<L, string>;
|
|
138
|
+
libraryLabel?: string;
|
|
139
|
+
generateCode: (values: T, library: L) => string;
|
|
140
|
+
onSubmit: () => void;
|
|
141
|
+
onBack: () => void;
|
|
142
|
+
}) => {
|
|
143
|
+
const defaults = getDefaults(schema);
|
|
144
|
+
const form = useForm<T>({
|
|
145
|
+
defaultValues: defaults as DefaultValues<T>,
|
|
146
|
+
resolver: zodResolver(schema),
|
|
147
|
+
reValidateMode: "onChange",
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
const [preferredLibrary, setPreferredLibrary] =
|
|
151
|
+
React.useState<L>(initialPreferred);
|
|
152
|
+
const insertCode = useInsertCode();
|
|
153
|
+
|
|
154
|
+
const handleSubmit = (values: T) => {
|
|
155
|
+
insertCode(generateCode(values, preferredLibrary));
|
|
156
|
+
onSubmit();
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
return (
|
|
160
|
+
<form onSubmit={form.handleSubmit(handleSubmit)} className="space-y-4">
|
|
161
|
+
<SecretsProvider>
|
|
162
|
+
<ZodForm schema={schema} form={form} renderers={RENDERERS}>
|
|
163
|
+
<FormErrorsBanner />
|
|
164
|
+
</ZodForm>
|
|
165
|
+
</SecretsProvider>
|
|
166
|
+
<ConnectionFormFooter
|
|
167
|
+
onBack={onBack}
|
|
168
|
+
isValid={form.formState.isValid}
|
|
169
|
+
libraries={libraries}
|
|
170
|
+
preferredLibrary={preferredLibrary}
|
|
171
|
+
onLibraryChange={setPreferredLibrary}
|
|
172
|
+
displayNames={displayNames}
|
|
173
|
+
libraryLabel={libraryLabel}
|
|
174
|
+
/>
|
|
175
|
+
</form>
|
|
176
|
+
);
|
|
177
|
+
};
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
2
|
import { describe, expect, it } from "vitest";
|
|
3
|
+
import { prefixSecret } from "../../secrets";
|
|
3
4
|
import { type ConnectionLibrary, generateDatabaseCode } from "../as-code";
|
|
4
5
|
import type { DatabaseConnection } from "../schemas";
|
|
5
|
-
import { prefixSecret } from "../secrets";
|
|
6
6
|
|
|
7
7
|
describe("generateDatabaseCode", () => {
|
|
8
8
|
// Test fixtures
|