@marimo-team/frontend 0.23.11-dev1 → 0.23.11-dev11
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-e0ex7Iei.js → CellStatus-CLGvVxcw.js} +1 -1
- package/dist/assets/{JsonOutput-DRNPZOvX.js → JsonOutput-uEJijGXp.js} +5 -5
- package/dist/assets/{MarimoErrorOutput-BH6hs0Ir.js → MarimoErrorOutput-DzoKyXWR.js} +2 -2
- package/dist/assets/{RenderHTML-BQ1PO4Wd.js → RenderHTML-DAt48X-F.js} +1 -1
- package/dist/assets/{RunButton-F8pLIvFp.js → RunButton-B7msyyYi.js} +1 -1
- package/dist/assets/{add-cell-with-ai-C2OtCghK.js → add-cell-with-ai-CAkcousR.js} +8 -8
- package/dist/assets/{add-connection-dialog-AhwxOztN.js → add-connection-dialog-ZyXXfAVG.js} +32 -32
- package/dist/assets/{agent-panel-D6ryE8xb.js → agent-panel-BfIguQXh.js} +3 -3
- package/dist/assets/{ai-model-dropdown-CG4B4rqH.js → ai-model-dropdown-rE64ytSX.js} +3 -3
- package/dist/assets/{app-config-button-D_sFrSql.js → app-config-button-DLRZ5d9c.js} +1 -1
- package/dist/assets/{cell-editor-XNa3wJ3P.js → cell-editor-2JIpvFn4.js} +5 -5
- package/dist/assets/{cell-link-Bj4-yOIx.js → cell-link-DY95GSb7.js} +1 -1
- package/dist/assets/{cells-DOA0Gew8.js → cells-D-v2lBet.js} +67 -67
- package/dist/assets/{chat-display-CezkHLLR.js → chat-display-DO-tqsbY.js} +1 -1
- package/dist/assets/{chat-panel-DEBTPtwE.js → chat-panel-BT5gLfs6.js} +1 -1
- package/dist/assets/{chat-ui-CIr1o1hq.js → chat-ui-Be4yDBZS.js} +1 -1
- package/dist/assets/column-preview-BzvFdLI5.js +1 -0
- package/dist/assets/{command-palette-_VEEjmkI.js → command-palette-CKKzfpGt.js} +1 -1
- package/dist/assets/{common-fDFYY_sv.js → common-Do4N1uVK.js} +1 -1
- package/dist/assets/{components--C6N-DXq.js → components-DUC0f1XD.js} +1 -1
- package/dist/assets/{datasource-CR6RRpTi.js → datasource-BQGI7c_u.js} +2 -2
- package/dist/assets/{dependency-graph-panel-BEgkvdX0.js → dependency-graph-panel-Czoya7c2.js} +1 -1
- package/dist/assets/{documentation-panel-HvbKykbI.js → documentation-panel-LokVYDIZ.js} +1 -1
- package/dist/assets/{download-DhxnAw14.js → download-WGU5w_3m.js} +3 -3
- package/dist/assets/{edit-page-D-02q6iz.js → edit-page-BPQO1Uuz.js} +6 -6
- package/dist/assets/{error-panel-WLBQ3q9p.js → error-panel-BCm_e4eM.js} +1 -1
- package/dist/assets/{file-explorer-panel-DtzGlnzT.js → file-explorer-panel-D1NN-z8x.js} +3 -3
- package/dist/assets/{file-icons-D2f3nfbq.js → file-icons-CF-YT4hq.js} +1 -1
- package/dist/assets/{file-name-input-BNYf9WWM.js → file-name-input-BRVXQ3OU.js} +1 -1
- package/dist/assets/{floating-outline-BvHFWRoz.js → floating-outline-D33Zu-Ad.js} +1 -1
- package/dist/assets/{focus-Ldqh99xE.js → focus-BfFAxl9X.js} +1 -1
- package/dist/assets/{form-CxBAInfg.js → form-CVBFx2z3.js} +1 -1
- package/dist/assets/{home-page-DwFpLpXK.js → home-page-CL9Hv1Hg.js} +2 -2
- package/dist/assets/{hooks-5gNQitDd.js → hooks-83y0XCiC.js} +1 -1
- package/dist/assets/{html-to-image-CyAtzePO.js → html-to-image-IPNqWX7V.js} +2 -2
- package/dist/assets/index-BNWrEIlp.css +2 -0
- package/dist/assets/{index-CGCBsDqs.js → index-CuhY66ZR.js} +14 -14
- package/dist/assets/{kiosk-mode-CTWHjzXs.js → kiosk-mode-BSufcIZH.js} +1 -1
- package/dist/assets/{layout-D3pd3R21.js → layout-ks0WJ2vR.js} +5 -5
- package/dist/assets/{logs-panel-BVLYycQb.js → logs-panel-DXN6sUaA.js} +1 -1
- package/dist/assets/{markdown-renderer-C9Ujvj0b.js → markdown-renderer-DWRPo52L.js} +1 -1
- package/dist/assets/{name-cell-input-DabvuruX.js → name-cell-input-BiDIx6YQ.js} +1 -1
- package/dist/assets/{outline-panel-B-ncbNWI.js → outline-panel-DVSGNRgP.js} +1 -1
- package/dist/assets/{packages-panel-CESeW_3T.js → packages-panel-DTX6CBnm.js} +1 -1
- package/dist/assets/{panels-CTy3hq0L.js → panels-NhGV0SBq.js} +1 -1
- package/dist/assets/{process-output-pPgH0ANl.js → process-output-BiWV34b7.js} +1 -1
- package/dist/assets/{radio-group-D7rh6Zek.js → radio-group-DV-lQvil.js} +1 -1
- package/dist/assets/{readonly-python-code-CvPx4CU_.js → readonly-python-code-B0glFTKW.js} +1 -1
- package/dist/assets/{reveal-component-lTtEZQh1.js → reveal-component-C2M2FixA.js} +12 -12
- package/dist/assets/{run-page-BB6ghGfO.js → run-page-B6I-Lshx.js} +1 -1
- package/dist/assets/{scratchpad-panel-Bvb8RxUv.js → scratchpad-panel-DU7perVk.js} +1 -1
- package/dist/assets/session-panel-B4UTmf-j.js +1 -0
- package/dist/assets/{snippets-panel-BSgcoo-M.js → snippets-panel-KRdl4bVk.js} +1 -1
- package/dist/assets/{state-BWM3qRkR.js → state-BSm0OM93.js} +1 -1
- package/dist/assets/{state-C8yHPSLN.js → state-CbkvENzk.js} +2 -2
- package/dist/assets/{textarea-bBuxbFm7.js → textarea-CBbixTxi.js} +1 -1
- package/dist/assets/{tracing-pt7eDHWc.js → tracing-D8LGpuXa.js} +1 -1
- package/dist/assets/{tracing-panel-BveZ5DZw.js → tracing-panel-TUP6kAd5.js} +2 -2
- package/dist/assets/{tree-actions-1KQDSu1H.js → tree-actions-BvrKFU0g.js} +1 -1
- package/dist/assets/{useCellActionButton-C4V7J6PS.js → useCellActionButton-CtOZIXD0.js} +1 -1
- package/dist/assets/{useDeleteCell-BvnurAZ9.js → useDeleteCell-BLvzLWAT.js} +1 -1
- package/dist/assets/{useDependencyPanelTab-DNlXaxbt.js → useDependencyPanelTab-BP0sNfUz.js} +1 -1
- package/dist/assets/{useNotebookActions-BIQkawja.js → useNotebookActions-BI5dWutQ.js} +1 -1
- package/dist/assets/{useRunCells-cSZpNqnR.js → useRunCells-BClkx9Pq.js} +1 -1
- package/dist/assets/{useSplitCell-s7dSiBUa.js → useSplitCell-CpddCX_u.js} +1 -1
- package/dist/index.html +23 -23
- package/package.json +1 -1
- package/src/components/datasources/__tests__/column-preview.test.tsx +97 -0
- package/src/components/datasources/__tests__/filter-empty.test.ts +81 -0
- package/src/components/datasources/__tests__/utils.test.ts +62 -1
- package/src/components/datasources/column-preview.tsx +2 -4
- package/src/components/datasources/components.tsx +15 -7
- package/src/components/datasources/datasources.tsx +311 -178
- package/src/components/datasources/utils.ts +40 -1
- package/src/components/editor/connections/components.tsx +13 -0
- package/src/components/editor/connections/storage/__tests__/__snapshots__/as-code.test.ts.snap +4 -4
- package/src/components/editor/connections/storage/as-code.ts +11 -4
- package/src/core/cells/__tests__/cells.test.ts +33 -0
- package/src/core/cells/cells.ts +1 -1
- package/src/core/datasets/__tests__/data-source.test.ts +226 -0
- package/src/core/datasets/data-source-connections.ts +88 -24
- package/dist/assets/column-preview-nu3Qo2OW.js +0 -1
- package/dist/assets/index-BAYF7dcV.css +0 -2
- package/dist/assets/session-panel-CTJPYbAa.js +0 -1
|
@@ -4,15 +4,54 @@ import { BigQueryDialect } from "@marimo-team/codemirror-sql/dialects";
|
|
|
4
4
|
import { isKnownDialect } from "@/core/codemirror/language/languages/sql/utils";
|
|
5
5
|
import type { SQLTableContext } from "@/core/datasets/data-source-connections";
|
|
6
6
|
import { DUCKDB_ENGINE } from "@/core/datasets/engines";
|
|
7
|
-
import type {
|
|
7
|
+
import type {
|
|
8
|
+
Database,
|
|
9
|
+
DatabaseSchema,
|
|
10
|
+
DataTable,
|
|
11
|
+
DataType,
|
|
12
|
+
} from "@/core/kernel/messages";
|
|
8
13
|
import { logNever } from "@/utils/assertNever";
|
|
9
14
|
import type { ColumnHeaderStatsKey } from "../data-table/types";
|
|
10
15
|
|
|
16
|
+
/**
|
|
17
|
+
* Stable id for a table node in the datasources tree.
|
|
18
|
+
*
|
|
19
|
+
* schemaPath already includes the leaf schema for nested namespaces, so use it
|
|
20
|
+
* when present and fall back to the flat schema name otherwise (avoids
|
|
21
|
+
* duplicating the leaf, e.g. `top.nested.nested.table`).
|
|
22
|
+
*/
|
|
23
|
+
export function tableUniqueId(
|
|
24
|
+
sqlTableContext: SQLTableContext | undefined,
|
|
25
|
+
tableName: string,
|
|
26
|
+
): string {
|
|
27
|
+
if (!sqlTableContext) {
|
|
28
|
+
return tableName;
|
|
29
|
+
}
|
|
30
|
+
const segments = (
|
|
31
|
+
sqlTableContext.schemaPath?.length
|
|
32
|
+
? sqlTableContext.schemaPath
|
|
33
|
+
: [sqlTableContext.schema]
|
|
34
|
+
).filter(Boolean);
|
|
35
|
+
return [sqlTableContext.database, ...segments, tableName].join(".");
|
|
36
|
+
}
|
|
37
|
+
|
|
11
38
|
// Some databases have no schemas, so we don't show it (eg. Clickhouse)
|
|
12
39
|
export function isSchemaless(schemaName: string) {
|
|
13
40
|
return schemaName === "";
|
|
14
41
|
}
|
|
15
42
|
|
|
43
|
+
// Lazy discovery: the `*_resolved` flags default to `true` and are only `false`
|
|
44
|
+
// when enumeration was deferred. Helper functions to centralize logic
|
|
45
|
+
export function areSchemasResolved(database: Database): boolean {
|
|
46
|
+
return database.schemas_resolved !== false;
|
|
47
|
+
}
|
|
48
|
+
export function areTablesResolved(schema: DatabaseSchema): boolean {
|
|
49
|
+
return schema.tables_resolved !== false;
|
|
50
|
+
}
|
|
51
|
+
export function areChildSchemasResolved(schema: DatabaseSchema): boolean {
|
|
52
|
+
return schema.child_schemas_resolved !== false;
|
|
53
|
+
}
|
|
54
|
+
|
|
16
55
|
interface SqlCodeFormatter {
|
|
17
56
|
/**
|
|
18
57
|
* Format the table path based on dialect-specific rules
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
2
|
|
|
3
3
|
import { zodResolver } from "@hookform/resolvers/zod";
|
|
4
|
+
import { useAtomValue } from "jotai";
|
|
4
5
|
import React from "react";
|
|
5
6
|
import { type DefaultValues, type FieldValues, useForm } from "react-hook-form";
|
|
6
7
|
import type { z } from "zod";
|
|
@@ -16,8 +17,10 @@ import {
|
|
|
16
17
|
SelectTrigger,
|
|
17
18
|
SelectValue,
|
|
18
19
|
} from "@/components/ui/select";
|
|
20
|
+
import { maybeAddMarimoImport } from "@/core/cells/add-missing-import";
|
|
19
21
|
import { useCellActions } from "@/core/cells/cells";
|
|
20
22
|
import { useLastFocusedCellId } from "@/core/cells/focus";
|
|
23
|
+
import { autoInstantiateAtom } from "@/core/config/config";
|
|
21
24
|
import { ENV_RENDERER, SecretsProvider } from "./form-renderers";
|
|
22
25
|
|
|
23
26
|
const RENDERERS: FormRenderer[] = [ENV_RENDERER];
|
|
@@ -106,8 +109,18 @@ export const ConnectionFormFooter = <L extends string>({
|
|
|
106
109
|
export function useInsertCode() {
|
|
107
110
|
const { createNewCell } = useCellActions();
|
|
108
111
|
const lastFocusedCellId = useLastFocusedCellId();
|
|
112
|
+
const autoInstantiate = useAtomValue(autoInstantiateAtom);
|
|
109
113
|
|
|
110
114
|
return (code: string) => {
|
|
115
|
+
// Ensure `mo` is importable when the generated code references it
|
|
116
|
+
if (/\bmo\./.test(code)) {
|
|
117
|
+
maybeAddMarimoImport({
|
|
118
|
+
autoInstantiate,
|
|
119
|
+
createNewCell,
|
|
120
|
+
fromCellId: lastFocusedCellId,
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
111
124
|
createNewCell({
|
|
112
125
|
code,
|
|
113
126
|
before: false,
|
package/src/components/editor/connections/storage/__tests__/__snapshots__/as-code.test.ts.snap
CHANGED
|
@@ -85,14 +85,14 @@ store = GCSStore("my-bucket")"
|
|
|
85
85
|
exports[`generateStorageCode > Google Drive > with default auth (no credentials) 1`] = `
|
|
86
86
|
"from gdrive_fsspec import GoogleDriveFileSystem
|
|
87
87
|
|
|
88
|
-
fs = GoogleDriveFileSystem(use_listings_cache=False)"
|
|
88
|
+
fs = GoogleDriveFileSystem(use_listings_cache=False, skip_instance_cache=True)"
|
|
89
89
|
`;
|
|
90
90
|
|
|
91
91
|
exports[`generateStorageCode > Google Drive > with embedded auth (no credentials) 1`] = `
|
|
92
92
|
"from gdrive_fsspec import GoogleDriveFileSystem
|
|
93
93
|
|
|
94
|
-
fs = GoogleDriveFileSystem(use_listings_cache=False, auth_kwargs={"use_local_webserver": False})
|
|
95
|
-
|
|
94
|
+
fs = GoogleDriveFileSystem(use_listings_cache=False, skip_instance_cache=True, auth_kwargs={"use_local_webserver": False})
|
|
95
|
+
mo.output.clear_console()"
|
|
96
96
|
`;
|
|
97
97
|
|
|
98
98
|
exports[`generateStorageCode > Google Drive > with service account credentials 1`] = `
|
|
@@ -100,7 +100,7 @@ exports[`generateStorageCode > Google Drive > with service account credentials 1
|
|
|
100
100
|
import json
|
|
101
101
|
|
|
102
102
|
_creds = json.loads("""{"type": "service_account", "client_email": "test@test.iam.gserviceaccount.com"}""")
|
|
103
|
-
fs = GoogleDriveFileSystem(creds=_creds, token="service_account", use_listings_cache=False)"
|
|
103
|
+
fs = GoogleDriveFileSystem(creds=_creds, token="service_account", use_listings_cache=False, skip_instance_cache=True)"
|
|
104
104
|
`;
|
|
105
105
|
|
|
106
106
|
exports[`generateStorageCode > S3 > basic connection with all fields 1`] = `
|
|
@@ -168,6 +168,10 @@ function generateGDriveCode(
|
|
|
168
168
|
connection: Extract<StorageConnection, { type: "gdrive" }>,
|
|
169
169
|
options: { secrets: SecretContainer; isEmbedded?: boolean },
|
|
170
170
|
): { imports: Set<string>; code: string } {
|
|
171
|
+
/**
|
|
172
|
+
* Skip instance cache True so you can create multiple connections which don't reference the same creds.
|
|
173
|
+
* Use listings cache False so we don't get stale reads.
|
|
174
|
+
*/
|
|
171
175
|
const { secrets, isEmbedded = false } = options;
|
|
172
176
|
const imports = new Set(["from gdrive_fsspec import GoogleDriveFileSystem"]);
|
|
173
177
|
|
|
@@ -179,18 +183,21 @@ function generateGDriveCode(
|
|
|
179
183
|
);
|
|
180
184
|
const code = dedent(`
|
|
181
185
|
_creds = json.loads("""${connection.credentials_json?.startsWith("ENV:") ? `{${creds}}` : connection.credentials_json}""")
|
|
182
|
-
fs = GoogleDriveFileSystem(creds=_creds, token="service_account", use_listings_cache=False)
|
|
186
|
+
fs = GoogleDriveFileSystem(creds=_creds, token="service_account", use_listings_cache=False, skip_instance_cache=True)
|
|
183
187
|
`);
|
|
184
188
|
return { imports, code };
|
|
185
189
|
}
|
|
186
190
|
|
|
191
|
+
// In the iframe (embedded) flow we authenticate via the console-based OOB
|
|
192
|
+
// flow, which prints an auth URL and reads the code from stdin. Clear the
|
|
193
|
+
// console afterwards so the (single-use) auth code doesn't linger.
|
|
187
194
|
const code = isEmbedded
|
|
188
195
|
? dedent(`
|
|
189
|
-
fs = GoogleDriveFileSystem(use_listings_cache=False, auth_kwargs={"use_local_webserver": False})
|
|
190
|
-
|
|
196
|
+
fs = GoogleDriveFileSystem(use_listings_cache=False, skip_instance_cache=True, auth_kwargs={"use_local_webserver": False})
|
|
197
|
+
mo.output.clear_console()
|
|
191
198
|
`)
|
|
192
199
|
: dedent(`
|
|
193
|
-
fs = GoogleDriveFileSystem(use_listings_cache=False)
|
|
200
|
+
fs = GoogleDriveFileSystem(use_listings_cache=False, skip_instance_cache=True)
|
|
194
201
|
`);
|
|
195
202
|
return { imports, code };
|
|
196
203
|
}
|
|
@@ -2920,6 +2920,39 @@ describe("cell reducer", () => {
|
|
|
2920
2920
|
expect(state.untouchedNewCells.has(newCellId)).toBe(true);
|
|
2921
2921
|
});
|
|
2922
2922
|
|
|
2923
|
+
it("adds interactively-created hidden cell with boilerplate code to untouchedNewCells", () => {
|
|
2924
|
+
// Markdown cells are created with hideCode and non-empty default code
|
|
2925
|
+
// (e.g. `mo.md(r"""\n""")`). They are user-initiated, so their editor
|
|
2926
|
+
// should be shown until first blur.
|
|
2927
|
+
actions.createNewCell({
|
|
2928
|
+
cellId: "__end__",
|
|
2929
|
+
before: false,
|
|
2930
|
+
code: 'mo.md(r"""\n""")',
|
|
2931
|
+
hideCode: true,
|
|
2932
|
+
autoFocus: true,
|
|
2933
|
+
});
|
|
2934
|
+
|
|
2935
|
+
const newCellId =
|
|
2936
|
+
state.cellIds.inOrderIds[state.cellIds.inOrderIds.length - 1];
|
|
2937
|
+
expect(state.untouchedNewCells.has(newCellId)).toBe(true);
|
|
2938
|
+
});
|
|
2939
|
+
|
|
2940
|
+
it("does not add programmatically-created hidden cell with code to untouchedNewCells", () => {
|
|
2941
|
+
// Cells created by the kernel (e.g. via code_mode) carry code and
|
|
2942
|
+
// autoFocus=false; their hide_code must take effect immediately.
|
|
2943
|
+
actions.createNewCell({
|
|
2944
|
+
cellId: "__end__",
|
|
2945
|
+
before: false,
|
|
2946
|
+
code: "x = 1",
|
|
2947
|
+
hideCode: true,
|
|
2948
|
+
autoFocus: false,
|
|
2949
|
+
});
|
|
2950
|
+
|
|
2951
|
+
const newCellId =
|
|
2952
|
+
state.cellIds.inOrderIds[state.cellIds.inOrderIds.length - 1];
|
|
2953
|
+
expect(state.untouchedNewCells.has(newCellId)).toBe(false);
|
|
2954
|
+
});
|
|
2955
|
+
|
|
2923
2956
|
it("does not add cell to untouchedNewCells when hideCode is false", () => {
|
|
2924
2957
|
actions.createNewCell({
|
|
2925
2958
|
cellId: "__end__",
|
package/src/core/cells/cells.ts
CHANGED
|
@@ -2,9 +2,12 @@
|
|
|
2
2
|
import { beforeEach, describe, expect, it } from "vitest";
|
|
3
3
|
import { variableName } from "@/__tests__/branded";
|
|
4
4
|
import type { DatabaseSchema, DataTable } from "@/core/kernel/messages";
|
|
5
|
+
import { store } from "@/core/state/jotai";
|
|
5
6
|
import type { VariableName } from "@/core/variables/types";
|
|
6
7
|
import {
|
|
8
|
+
allTablesAtom,
|
|
7
9
|
type DataSourceConnection,
|
|
10
|
+
dataSourceConnectionsAtom,
|
|
8
11
|
type DataSourceState,
|
|
9
12
|
exportedForTesting,
|
|
10
13
|
type SQLTableContext,
|
|
@@ -575,3 +578,226 @@ describe("add table", () => {
|
|
|
575
578
|
expect(db1?.schemas.length).toBe(1);
|
|
576
579
|
});
|
|
577
580
|
});
|
|
581
|
+
|
|
582
|
+
describe("nested namespaces", () => {
|
|
583
|
+
// Iceberg-style: database "top" with a schemaless schema and a nested
|
|
584
|
+
// namespace "nested" that has a deferred child namespace "deep".
|
|
585
|
+
const nestedConnections: DataSourceConnection[] = [
|
|
586
|
+
{
|
|
587
|
+
name: "ice" as ConnectionName,
|
|
588
|
+
source: "iceberg",
|
|
589
|
+
display_name: "Iceberg",
|
|
590
|
+
dialect: "iceberg",
|
|
591
|
+
databases: [
|
|
592
|
+
{
|
|
593
|
+
name: "top",
|
|
594
|
+
dialect: "iceberg",
|
|
595
|
+
schemas_resolved: true,
|
|
596
|
+
schemas: [
|
|
597
|
+
{ name: "", tables: [], tables_resolved: true },
|
|
598
|
+
{
|
|
599
|
+
name: "nested",
|
|
600
|
+
tables: [],
|
|
601
|
+
tables_resolved: false,
|
|
602
|
+
child_schemas: [],
|
|
603
|
+
child_schemas_resolved: false,
|
|
604
|
+
},
|
|
605
|
+
],
|
|
606
|
+
},
|
|
607
|
+
],
|
|
608
|
+
},
|
|
609
|
+
];
|
|
610
|
+
|
|
611
|
+
let baseState: DataSourceState;
|
|
612
|
+
|
|
613
|
+
beforeEach(() => {
|
|
614
|
+
baseState = addConnection(nestedConnections, initialState());
|
|
615
|
+
});
|
|
616
|
+
|
|
617
|
+
const findSchema = (
|
|
618
|
+
state: DataSourceState,
|
|
619
|
+
path: string[],
|
|
620
|
+
): DatabaseSchema | undefined => {
|
|
621
|
+
const conn = state.connectionsMap.get("ice" as ConnectionName);
|
|
622
|
+
const db = conn?.databases.find((d) => d.name === "top");
|
|
623
|
+
let schemas = db?.schemas ?? [];
|
|
624
|
+
let found: DatabaseSchema | undefined;
|
|
625
|
+
for (const segment of path) {
|
|
626
|
+
found = schemas.find((s) => s.name === segment);
|
|
627
|
+
schemas = found?.child_schemas ?? [];
|
|
628
|
+
}
|
|
629
|
+
return found;
|
|
630
|
+
};
|
|
631
|
+
|
|
632
|
+
it("sets child namespaces at a nested path", () => {
|
|
633
|
+
const children: DatabaseSchema[] = [
|
|
634
|
+
{ name: "deep", tables: [], tables_resolved: false },
|
|
635
|
+
];
|
|
636
|
+
const newState = reducer(baseState, {
|
|
637
|
+
type: "addSchemaList",
|
|
638
|
+
payload: {
|
|
639
|
+
schemas: children,
|
|
640
|
+
sqlSchemaContext: {
|
|
641
|
+
engine: "ice",
|
|
642
|
+
database: "top",
|
|
643
|
+
schemaPath: ["nested"],
|
|
644
|
+
},
|
|
645
|
+
},
|
|
646
|
+
});
|
|
647
|
+
|
|
648
|
+
const nested = findSchema(newState, ["nested"]);
|
|
649
|
+
expect(nested?.child_schemas_resolved).toBe(true);
|
|
650
|
+
expect(nested?.child_schemas?.map((s) => s.name)).toEqual(["deep"]);
|
|
651
|
+
// The schemaless sibling is untouched.
|
|
652
|
+
expect(findSchema(newState, [""])?.name).toBe("");
|
|
653
|
+
});
|
|
654
|
+
|
|
655
|
+
it("sets tables at a nested path", () => {
|
|
656
|
+
const tables: DataTable[] = [
|
|
657
|
+
{
|
|
658
|
+
name: "table4",
|
|
659
|
+
columns: [],
|
|
660
|
+
num_columns: 0,
|
|
661
|
+
num_rows: 0,
|
|
662
|
+
variable_name: null,
|
|
663
|
+
source: "iceberg",
|
|
664
|
+
source_type: "catalog",
|
|
665
|
+
type: "table",
|
|
666
|
+
},
|
|
667
|
+
];
|
|
668
|
+
const newState = reducer(baseState, {
|
|
669
|
+
type: "addTableList",
|
|
670
|
+
payload: {
|
|
671
|
+
tables,
|
|
672
|
+
sqlTableContext: {
|
|
673
|
+
engine: "ice",
|
|
674
|
+
database: "top",
|
|
675
|
+
schema: "nested",
|
|
676
|
+
dialect: "iceberg",
|
|
677
|
+
schemaPath: ["nested"],
|
|
678
|
+
},
|
|
679
|
+
},
|
|
680
|
+
});
|
|
681
|
+
|
|
682
|
+
const nested = findSchema(newState, ["nested"]);
|
|
683
|
+
expect(nested?.tables_resolved).toBe(true);
|
|
684
|
+
expect(nested?.tables.map((t) => t.name)).toEqual(["table4"]);
|
|
685
|
+
});
|
|
686
|
+
|
|
687
|
+
it("replaces a single table at a nested path", () => {
|
|
688
|
+
const makeTable = (numRows: number): DataTable => ({
|
|
689
|
+
name: "table4",
|
|
690
|
+
columns: [],
|
|
691
|
+
num_columns: 0,
|
|
692
|
+
num_rows: numRows,
|
|
693
|
+
variable_name: null,
|
|
694
|
+
source: "iceberg",
|
|
695
|
+
source_type: "catalog",
|
|
696
|
+
type: "table",
|
|
697
|
+
});
|
|
698
|
+
const context = {
|
|
699
|
+
engine: "ice",
|
|
700
|
+
database: "top",
|
|
701
|
+
schema: "nested",
|
|
702
|
+
dialect: "iceberg",
|
|
703
|
+
schemaPath: ["nested"],
|
|
704
|
+
};
|
|
705
|
+
let state = reducer(baseState, {
|
|
706
|
+
type: "addTableList",
|
|
707
|
+
payload: { tables: [makeTable(1)], sqlTableContext: context },
|
|
708
|
+
});
|
|
709
|
+
state = reducer(state, {
|
|
710
|
+
type: "addTable",
|
|
711
|
+
payload: { table: makeTable(42), sqlTableContext: context },
|
|
712
|
+
});
|
|
713
|
+
|
|
714
|
+
const nested = findSchema(state, ["nested"]);
|
|
715
|
+
expect(nested?.tables).toHaveLength(1);
|
|
716
|
+
expect(nested?.tables[0].num_rows).toBe(42);
|
|
717
|
+
});
|
|
718
|
+
|
|
719
|
+
it("does not change anything for a missing nested path", () => {
|
|
720
|
+
const newState = reducer(baseState, {
|
|
721
|
+
type: "addSchemaList",
|
|
722
|
+
payload: {
|
|
723
|
+
schemas: [{ name: "deep", tables: [] }],
|
|
724
|
+
sqlSchemaContext: {
|
|
725
|
+
engine: "ice",
|
|
726
|
+
database: "top",
|
|
727
|
+
schemaPath: ["does_not_exist"],
|
|
728
|
+
},
|
|
729
|
+
},
|
|
730
|
+
});
|
|
731
|
+
// The result is unchanged: nested namespace stays unresolved and the
|
|
732
|
+
// database keeps its two schemas (schemaless + nested).
|
|
733
|
+
const newDb = newState.connectionsMap
|
|
734
|
+
.get("ice" as ConnectionName)
|
|
735
|
+
?.databases.find((d) => d.name === "top");
|
|
736
|
+
expect(findSchema(newState, ["nested"])?.child_schemas_resolved).toBe(
|
|
737
|
+
false,
|
|
738
|
+
);
|
|
739
|
+
expect(newDb?.schemas.length).toBe(2);
|
|
740
|
+
});
|
|
741
|
+
});
|
|
742
|
+
|
|
743
|
+
describe("allTablesAtom with nested namespaces", () => {
|
|
744
|
+
it("enumerates tables from nested namespaces", () => {
|
|
745
|
+
const table = (name: string): DataTable => ({
|
|
746
|
+
name,
|
|
747
|
+
columns: [],
|
|
748
|
+
num_columns: 0,
|
|
749
|
+
num_rows: 0,
|
|
750
|
+
variable_name: null,
|
|
751
|
+
source: "iceberg",
|
|
752
|
+
source_type: "catalog",
|
|
753
|
+
type: "table",
|
|
754
|
+
});
|
|
755
|
+
|
|
756
|
+
const state = addConnection(
|
|
757
|
+
[
|
|
758
|
+
{
|
|
759
|
+
name: "ice" as ConnectionName,
|
|
760
|
+
source: "iceberg",
|
|
761
|
+
display_name: "Iceberg",
|
|
762
|
+
dialect: "iceberg",
|
|
763
|
+
databases: [
|
|
764
|
+
{
|
|
765
|
+
name: "top",
|
|
766
|
+
dialect: "iceberg",
|
|
767
|
+
schemas_resolved: true,
|
|
768
|
+
schemas: [
|
|
769
|
+
{
|
|
770
|
+
name: "",
|
|
771
|
+
tables: [table("toptable")],
|
|
772
|
+
tables_resolved: true,
|
|
773
|
+
},
|
|
774
|
+
{
|
|
775
|
+
name: "nested",
|
|
776
|
+
tables: [table("nestedtable")],
|
|
777
|
+
tables_resolved: true,
|
|
778
|
+
child_schemas_resolved: true,
|
|
779
|
+
child_schemas: [
|
|
780
|
+
{
|
|
781
|
+
name: "deep",
|
|
782
|
+
tables: [table("deeptable")],
|
|
783
|
+
tables_resolved: true,
|
|
784
|
+
child_schemas: [],
|
|
785
|
+
child_schemas_resolved: true,
|
|
786
|
+
},
|
|
787
|
+
],
|
|
788
|
+
},
|
|
789
|
+
],
|
|
790
|
+
},
|
|
791
|
+
],
|
|
792
|
+
},
|
|
793
|
+
],
|
|
794
|
+
initialState(),
|
|
795
|
+
);
|
|
796
|
+
|
|
797
|
+
store.set(dataSourceConnectionsAtom, state);
|
|
798
|
+
const names = [...store.get(allTablesAtom).values()].map((t) => t.name);
|
|
799
|
+
expect(names).toContain("toptable");
|
|
800
|
+
expect(names).toContain("nestedtable");
|
|
801
|
+
expect(names).toContain("deeptable");
|
|
802
|
+
});
|
|
803
|
+
});
|
|
@@ -49,6 +49,9 @@ export interface DataSourceState {
|
|
|
49
49
|
export interface SQLSchemaContext {
|
|
50
50
|
engine: string;
|
|
51
51
|
database: string;
|
|
52
|
+
// Parent schema path (relative to `database`) for nested schemas.
|
|
53
|
+
// Empty/undefined for the database's top level.
|
|
54
|
+
schemaPath?: string[];
|
|
52
55
|
}
|
|
53
56
|
|
|
54
57
|
export interface SQLTableContext {
|
|
@@ -58,6 +61,49 @@ export interface SQLTableContext {
|
|
|
58
61
|
dialect: string;
|
|
59
62
|
defaultSchema?: string | null;
|
|
60
63
|
defaultDatabase?: string | null;
|
|
64
|
+
// Nested schema path (relative to `database`). Empty/undefined at top level.
|
|
65
|
+
schemaPath?: string[];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Immutably descend `path` (schema segment names) into a nested schema list
|
|
70
|
+
* and apply `update` to the matching schema. Unmatched branches are unchanged.
|
|
71
|
+
*/
|
|
72
|
+
function updateSchemaAtPath(
|
|
73
|
+
schemas: DatabaseSchema[],
|
|
74
|
+
path: string[],
|
|
75
|
+
update: (schema: DatabaseSchema) => DatabaseSchema,
|
|
76
|
+
): DatabaseSchema[] {
|
|
77
|
+
if (path.length === 0) {
|
|
78
|
+
return schemas;
|
|
79
|
+
}
|
|
80
|
+
const [head, ...rest] = path;
|
|
81
|
+
return schemas.map((schema) => {
|
|
82
|
+
if (schema.name !== head) {
|
|
83
|
+
return schema;
|
|
84
|
+
}
|
|
85
|
+
if (rest.length === 0) {
|
|
86
|
+
return update(schema);
|
|
87
|
+
}
|
|
88
|
+
return {
|
|
89
|
+
...schema,
|
|
90
|
+
child_schemas: updateSchemaAtPath(
|
|
91
|
+
schema.child_schemas ?? [],
|
|
92
|
+
rest,
|
|
93
|
+
update,
|
|
94
|
+
),
|
|
95
|
+
};
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* The path (schema/namespace segment names within a database) that locates the
|
|
101
|
+
* schema holding a set of tables. For nested namespaces this is the
|
|
102
|
+
* `schemaPath`; otherwise it is the single (possibly schemaless) schema name.
|
|
103
|
+
*/
|
|
104
|
+
function tableSchemaPath(sqlTableContext: SQLTableContext): string[] {
|
|
105
|
+
const { schemaPath, schema } = sqlTableContext;
|
|
106
|
+
return schemaPath && schemaPath.length > 0 ? schemaPath : [schema];
|
|
61
107
|
}
|
|
62
108
|
|
|
63
109
|
function initialState(): DataSourceState {
|
|
@@ -159,6 +205,7 @@ const {
|
|
|
159
205
|
return state;
|
|
160
206
|
}
|
|
161
207
|
|
|
208
|
+
const schemaPath = sqlSchemaContext.schemaPath ?? [];
|
|
162
209
|
const newMap = new Map(connectionsMap);
|
|
163
210
|
const newConn: DataSourceConnection = {
|
|
164
211
|
...conn,
|
|
@@ -166,10 +213,22 @@ const {
|
|
|
166
213
|
if (db.name !== sqlSchemaContext.database) {
|
|
167
214
|
return db;
|
|
168
215
|
}
|
|
216
|
+
// Top level: replace the database's schemas.
|
|
217
|
+
if (schemaPath.length === 0) {
|
|
218
|
+
return {
|
|
219
|
+
...db,
|
|
220
|
+
schemas: schemas,
|
|
221
|
+
schemas_resolved: true,
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
// Nested namespace: replace the child schemas of the namespace at path.
|
|
169
225
|
return {
|
|
170
226
|
...db,
|
|
171
|
-
schemas: schemas,
|
|
172
|
-
|
|
227
|
+
schemas: updateSchemaAtPath(db.schemas, schemaPath, (schema) => ({
|
|
228
|
+
...schema,
|
|
229
|
+
child_schemas: schemas,
|
|
230
|
+
child_schemas_resolved: true,
|
|
231
|
+
})),
|
|
173
232
|
};
|
|
174
233
|
}),
|
|
175
234
|
};
|
|
@@ -198,6 +257,7 @@ const {
|
|
|
198
257
|
return state;
|
|
199
258
|
}
|
|
200
259
|
|
|
260
|
+
const path = tableSchemaPath(sqlTableContext);
|
|
201
261
|
const newMap = new Map(connectionsMap);
|
|
202
262
|
const newConn: DataSourceConnection = {
|
|
203
263
|
...conn,
|
|
@@ -207,16 +267,11 @@ const {
|
|
|
207
267
|
}
|
|
208
268
|
return {
|
|
209
269
|
...db,
|
|
210
|
-
schemas: db.schemas
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
...schema,
|
|
216
|
-
tables: tables,
|
|
217
|
-
tables_resolved: true,
|
|
218
|
-
};
|
|
219
|
-
}),
|
|
270
|
+
schemas: updateSchemaAtPath(db.schemas, path, (schema) => ({
|
|
271
|
+
...schema,
|
|
272
|
+
tables: tables,
|
|
273
|
+
tables_resolved: true,
|
|
274
|
+
})),
|
|
220
275
|
};
|
|
221
276
|
}),
|
|
222
277
|
};
|
|
@@ -246,6 +301,7 @@ const {
|
|
|
246
301
|
return state;
|
|
247
302
|
}
|
|
248
303
|
|
|
304
|
+
const path = tableSchemaPath(sqlTableContext);
|
|
249
305
|
const newMap = new Map(connectionsMap);
|
|
250
306
|
const newConn: DataSourceConnection = {
|
|
251
307
|
...conn,
|
|
@@ -253,21 +309,15 @@ const {
|
|
|
253
309
|
if (db.name !== sqlTableContext.database) {
|
|
254
310
|
return db;
|
|
255
311
|
}
|
|
256
|
-
|
|
257
312
|
return {
|
|
258
313
|
...db,
|
|
259
|
-
schemas: db.schemas
|
|
260
|
-
if (schema.name !== sqlTableContext.schema) {
|
|
261
|
-
return schema;
|
|
262
|
-
}
|
|
263
|
-
|
|
314
|
+
schemas: updateSchemaAtPath(db.schemas, path, (schema) => {
|
|
264
315
|
// If tables array is empty, add the table
|
|
265
316
|
// Otherwise, replace existing table or keep unchanged tables
|
|
266
317
|
const tables =
|
|
267
318
|
schema.tables.length === 0
|
|
268
319
|
? [table]
|
|
269
320
|
: schema.tables.map((t) => (t.name === tableName ? table : t));
|
|
270
|
-
|
|
271
321
|
return {
|
|
272
322
|
...schema,
|
|
273
323
|
tables,
|
|
@@ -327,9 +377,14 @@ export const allTablesAtom = atom((get) => {
|
|
|
327
377
|
const isDefaultDb =
|
|
328
378
|
database.name === conn.default_database || conn.databases.length === 1;
|
|
329
379
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
380
|
+
// Walk schemas recursively so nested namespaces (e.g. Iceberg
|
|
381
|
+
// `top.nested`) are enumerated. `segments` is the path of named
|
|
382
|
+
// (non-schemaless) namespace segments down to this schema.
|
|
383
|
+
const walkSchema = (schema: DatabaseSchema, segments: string[]): void => {
|
|
384
|
+
const schemalessDb = segments.length === 0;
|
|
385
|
+
const isDefaultSchema =
|
|
386
|
+
segments.length === 1 && segments[0] === conn.default_schema;
|
|
387
|
+
const schemaQualifier = segments.join(".");
|
|
333
388
|
|
|
334
389
|
for (const table of schema.tables) {
|
|
335
390
|
let nameToSave: string;
|
|
@@ -358,14 +413,14 @@ export const allTablesAtom = atom((get) => {
|
|
|
358
413
|
continue;
|
|
359
414
|
}
|
|
360
415
|
|
|
361
|
-
nameToSave = `${
|
|
416
|
+
nameToSave = `${schemaQualifier}.${table.name}`;
|
|
362
417
|
|
|
363
418
|
if (isDefaultDb && !tableNames.has(nameToSave)) {
|
|
364
419
|
tableNames.set(nameToSave, table);
|
|
365
420
|
continue;
|
|
366
421
|
}
|
|
367
422
|
|
|
368
|
-
nameToSave = `${database.name}.${
|
|
423
|
+
nameToSave = `${database.name}.${schemaQualifier}.${table.name}`;
|
|
369
424
|
|
|
370
425
|
if (tableNames.has(nameToSave)) {
|
|
371
426
|
Logger.warn(`Table name collision for ${nameToSave}. Skipping.`);
|
|
@@ -373,6 +428,15 @@ export const allTablesAtom = atom((get) => {
|
|
|
373
428
|
tableNames.set(nameToSave, table);
|
|
374
429
|
}
|
|
375
430
|
}
|
|
431
|
+
|
|
432
|
+
// Recurse into nested child namespaces. Children are always named.
|
|
433
|
+
for (const child of schema.child_schemas ?? []) {
|
|
434
|
+
walkSchema(child, [...segments, child.name]);
|
|
435
|
+
}
|
|
436
|
+
};
|
|
437
|
+
|
|
438
|
+
for (const schema of database.schemas) {
|
|
439
|
+
walkSchema(schema, isSchemaless(schema.name) ? [] : [schema.name]);
|
|
376
440
|
}
|
|
377
441
|
}
|
|
378
442
|
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{s as I}from"./chunk-LvLJmgfZ.js";import{u as F}from"./useEvent-D91BmmQi.js";import{t as H}from"./react-Bj1aDYRI.js";import{Br as J,D as R,Hr as M}from"./cells-DOA0Gew8.js";import{t as G}from"./compiler-runtime-B3qBwwSJ.js";import{c as K}from"./utils-CvI-39U6.js";import{t as U}from"./jsx-runtime-BqBOg78p.js";import{c as V,d as W,gt as X}from"./JsonOutput-DRNPZOvX.js";import{t as q}from"./tooltip-B_PkSKN3.js";import{r as D,t as A}from"./button-BbCh-29a.js";import{r as Y}from"./requests-DIwGYs0l.js";import{t as Z}from"./createLucideIcon-D5guW7EU.js";import{t as ee}from"./useLifecycle-DieWOfXE.js";import{t as te}from"./spinner-Bhir8k53.js";import{r as ae}from"./useTheme-CI2eq4XN.js";import{t as re}from"./copy-icon-BuRdHNPA.js";import{t as se}from"./context-QkTujrKn.js";import{i as oe}from"./numbers-DGxQfQ-A.js";import{n as ne}from"./html-to-image-CyAtzePO.js";import{o as ce}from"./focus-Ldqh99xE.js";var P=Z("square-plus",[["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2",key:"afitv7"}],["path",{d:"M8 12h8",key:"1wcyev"}],["path",{d:"M12 8v8",key:"napkw2"}]]),L=G(),le=I(H(),1),s=I(U(),1);const ie=i=>{let e=(0,L.c)(53),{table:t,column:a,preview:r,onAddColumnChart:c,sqlTableContext:o}=i,{theme:u}=ae(),{previewDatasetColumn:l}=Y(),{locale:d}=se(),m;e[0]!==a.name||e[1]!==l||e[2]!==o||e[3]!==t.name||e[4]!==t.source||e[5]!==t.source_type?(m=()=>{l({source:t.source,tableName:t.name,columnName:a.name,sourceType:t.source_type,fullyQualifiedTableName:o?`${o.database}.${o.schema}.${t.name}`:t.name})},e[0]=a.name,e[1]=l,e[2]=o,e[3]=t.name,e[4]=t.source,e[5]=t.source_type,e[6]=m):m=e[6];let p=m,f;if(e[7]!==r||e[8]!==p||e[9]!==t.source_type?(f=()=>{r||t.source_type==="connection"||t.source_type==="catalog"||p()},e[7]=r,e[8]=p,e[9]=t.source_type,e[10]=f):f=e[10],ee(f),t.source_type==="connection"){let n=a.name,E=a.external_type,x;e[11]!==a.name||e[12]!==c||e[13]!==o||e[14]!==t?(x=D.stopPropagation(()=>{c(M({table:t,columnName:a.name,sqlTableContext:o}))}),e[11]=a.name,e[12]=c,e[13]=o,e[14]=t,e[15]=x):x=e[15];let C;e[16]===Symbol.for("react.memo_cache_sentinel")?(C=(0,s.jsx)(P,{className:"h-3 w-3 mr-1"}),e[16]=C):C=e[16];let h;e[17]===x?h=e[18]:(h=(0,s.jsxs)(A,{variant:"outline",size:"xs",onClick:x,children:[C," Add SQL cell"]}),e[17]=x,e[18]=h);let S;return e[19]!==a.external_type||e[20]!==a.name||e[21]!==h?(S=(0,s.jsxs)("span",{className:"text-xs text-muted-foreground gap-2 flex items-center justify-between pl-7",children:[n," (",E,")",h]}),e[19]=a.external_type,e[20]=a.name,e[21]=h,e[22]=S):S=e[22],S}if(t.source_type==="catalog"){let n;return e[23]!==a.external_type||e[24]!==a.name?(n=(0,s.jsxs)("span",{className:"text-xs text-muted-foreground gap-2 flex items-center justify-between pl-7",children:[a.name," (",a.external_type,")"]}),e[23]=a.external_type,e[24]=a.name,e[25]=n):n=e[25],n}if(!r){let n;return e[26]===Symbol.for("react.memo_cache_sentinel")?(n=(0,s.jsx)("span",{className:"text-xs text-muted-foreground",children:"Loading..."}),e[26]=n):n=e[26],n}let g;e[27]!==r.error||e[28]!==r.missing_packages||e[29]!==p?(g=r.error&&O({error:r.error,missingPackages:r.missing_packages,refetchPreview:p}),e[27]=r.error,e[28]=r.missing_packages,e[29]=p,e[30]=g):g=e[30];let y=g,_;e[31]!==a.type||e[32]!==d||e[33]!==r.stats?(_=r.stats&&Q({stats:r.stats,dataType:a.type,locale:d}),e[31]=a.type,e[32]=d,e[33]=r.stats,e[34]=_):_=e[34];let b=_,j;e[35]!==r.chart_spec||e[36]!==u?(j=r.chart_spec&&$(r.chart_spec,u),e[35]=r.chart_spec,e[36]=u,e[37]=j):j=e[37];let N=j,k;e[38]!==r.chart_code||e[39]!==t.source_type?(k=r.chart_code&&t.source_type==="local"&&(0,s.jsx)(B,{chartCode:r.chart_code}),e[38]=r.chart_code,e[39]=t.source_type,e[40]=k):k=e[40];let z=k,v;e[41]!==a.name||e[42]!==c||e[43]!==o||e[44]!==t?(v=t.source_type==="duckdb"&&(0,s.jsx)(q,{content:"Add SQL cell",delayDuration:400,children:(0,s.jsx)(A,{variant:"outline",size:"icon",className:"z-10 bg-background absolute right-1 -top-1",onClick:D.stopPropagation(()=>{c(M({table:t,columnName:a.name,sqlTableContext:o}))}),children:(0,s.jsx)(P,{className:"h-3 w-3"})})}),e[41]=a.name,e[42]=c,e[43]=o,e[44]=t,e[45]=v):v=e[45];let T=v;if(!y&&!b&&!N){let n;return e[46]===Symbol.for("react.memo_cache_sentinel")?(n=(0,s.jsx)("span",{className:"text-xs text-muted-foreground",children:"No data"}),e[46]=n):n=e[46],n}let w;return e[47]!==z||e[48]!==T||e[49]!==N||e[50]!==y||e[51]!==b?(w=(0,s.jsxs)(W,{children:[y,z,T,N,b]}),e[47]=z,e[48]=T,e[49]=N,e[50]=y,e[51]=b,e[52]=w):w=e[52],w};function O({error:i,missingPackages:e,refetchPreview:t}){return(0,s.jsxs)("div",{className:"text-xs text-muted-foreground p-2 border border-border rounded flex items-center justify-between",children:[(0,s.jsx)("span",{children:i}),e&&(0,s.jsx)(V,{packages:e,showMaxPackages:1,className:"w-32",onInstall:t})]})}function Q({stats:i,dataType:e,locale:t}){return(0,s.jsx)("div",{className:"gap-x-16 gap-y-1 grid grid-cols-2-fit border rounded p-2 empty:hidden",children:Object.entries(i).map(([a,r])=>r==null?null:(0,s.jsxs)("div",{className:"flex items-center gap-1 group",children:[(0,s.jsx)("span",{className:"text-xs min-w-[60px] capitalize",children:J(a,e)}),(0,s.jsx)("span",{className:"text-xs font-bold text-muted-foreground tracking-wide",children:oe(r,t)}),(0,s.jsx)(re,{className:"h-3 w-3 invisible group-hover:visible",value:String(r)})]},a))})}var me=(0,s.jsx)("div",{className:"flex justify-center",children:(0,s.jsx)(te,{className:"size-4"})});function $(i,e){let t=a=>({...a,background:"transparent",config:{...a.config,background:"transparent"}});return(0,s.jsx)(le.Suspense,{fallback:me,children:(0,s.jsx)(X,{"data-container-width":"container",spec:t(JSON.parse(i)),options:{theme:e==="dark"?"dark":"vox",height:100,width:"container",actions:!1,renderer:"canvas"}})})}const B=i=>{let e=(0,L.c)(10),{chartCode:t}=i,a=F(K),r=ce(),{createNewCell:c}=R(),o;e[0]!==a||e[1]!==c||e[2]!==r?(o=p=>{p.includes("alt")&&ne({autoInstantiate:a,createNewCell:c,fromCellId:r}),c({code:p,before:!1,cellId:r??"__end__"})},e[0]=a,e[1]=c,e[2]=r,e[3]=o):o=e[3];let u=o,l;e[4]!==t||e[5]!==u?(l=D.stopPropagation(()=>u(t)),e[4]=t,e[5]=u,e[6]=l):l=e[6];let d;e[7]===Symbol.for("react.memo_cache_sentinel")?(d=(0,s.jsx)(P,{className:"h-3 w-3"}),e[7]=d):d=e[7];let m;return e[8]===l?m=e[9]:(m=(0,s.jsx)(q,{content:"Add chart to notebook",delayDuration:400,children:(0,s.jsx)(A,{variant:"outline",size:"icon",className:"z-10 bg-background absolute right-1 -top-0.5",onClick:l,children:d})}),e[8]=l,e[9]=m),m};export{Q as a,O as i,ie as n,P as o,$ as r,B as t};
|