@hienlh/ppm 0.9.65 → 0.9.67
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/CHANGELOG.md +26 -0
- package/dist/web/assets/{_basePickBy-3Xe18azI.js → _basePickBy-5PGDJbfF.js} +1 -1
- package/dist/web/assets/{_baseUniq-Yy35llnn.js → _baseUniq-BT4Ow4Kk.js} +1 -1
- package/dist/web/assets/{api-settings-CgBII8jW.js → api-settings-Bn-bIxD1.js} +1 -1
- package/dist/web/assets/{arc-B9n1Gvb5.js → arc-BAOivWpI.js} +1 -1
- package/dist/web/assets/architecture-PBZL5I3N-DEO2f3VD.js +1 -0
- package/dist/web/assets/{architectureDiagram-2XIMDMQ5-DqAZP_F6.js → architectureDiagram-2XIMDMQ5-Z-4eN4za.js} +1 -1
- package/dist/web/assets/{blockDiagram-WCTKOSBZ-h3cDF2vI.js → blockDiagram-WCTKOSBZ-BCLqzhuZ.js} +1 -1
- package/dist/web/assets/{c4Diagram-IC4MRINW--pF1r5lr.js → c4Diagram-IC4MRINW-0Vp0Jeas.js} +1 -1
- package/dist/web/assets/channel-By7bn0Yq.js +1 -0
- package/dist/web/assets/chat-tab-onkz52iv.js +10 -0
- package/dist/web/assets/{chunk-4BX2VUAB-C3aZvW7B.js → chunk-4BX2VUAB-D4tOov49.js} +1 -1
- package/dist/web/assets/{chunk-55IACEB6-D5cABeB9.js → chunk-55IACEB6-DJ6BynZ4.js} +1 -1
- package/dist/web/assets/{chunk-7E7YKBS2-CkFGv6Zs.js → chunk-7E7YKBS2-CiyUJxNI.js} +1 -1
- package/dist/web/assets/{chunk-7R4GIKGN-Dvbyu4Zw.js → chunk-7R4GIKGN-Dv-4cAYn.js} +2 -2
- package/dist/web/assets/{chunk-C72U2L5F-CtqKiH4q.js → chunk-C72U2L5F-D21mS_6G.js} +1 -1
- package/dist/web/assets/{chunk-EGIJ26TM-Cpr87sBR.js → chunk-EGIJ26TM-DzqmU2Z7.js} +1 -1
- package/dist/web/assets/{chunk-FMBD7UC4-D23YVTOU.js → chunk-FMBD7UC4-DXncblvW.js} +1 -1
- package/dist/web/assets/{chunk-GEFDOKGD-tDjHsAUs.js → chunk-GEFDOKGD-D-pKjlVd.js} +1 -1
- package/dist/web/assets/chunk-GLR3WWYH-DKikpoJM.js +2 -0
- package/dist/web/assets/chunk-HHEYEP7N-C7vxA5i9.js +1 -0
- package/dist/web/assets/{chunk-JSJVCQXG-BBmymCjA.js → chunk-JSJVCQXG-99JzIdPr.js} +1 -1
- package/dist/web/assets/{chunk-KX2RTZJC-DP36BDiU.js → chunk-KX2RTZJC-CRq1OBZv.js} +1 -1
- package/dist/web/assets/{chunk-KYZI473N-Djw13C-3.js → chunk-KYZI473N-Bb0MCaIO.js} +1 -1
- package/dist/web/assets/{chunk-L3YUKLVL-HG_eMj_C.js → chunk-L3YUKLVL-C7qGJrfV.js} +1 -1
- package/dist/web/assets/{chunk-MX3YWQON-C2UEioMs.js → chunk-MX3YWQON-BpS_PtKp.js} +1 -1
- package/dist/web/assets/{chunk-NQ4KR5QH-DXUTQ-BL.js → chunk-NQ4KR5QH-z_blpjxi.js} +1 -1
- package/dist/web/assets/{chunk-O4XLMI2P-BsUWb9d0.js → chunk-O4XLMI2P-nDhi_cVu.js} +1 -1
- package/dist/web/assets/{chunk-OZEHJAEY-rG0P22U9.js → chunk-OZEHJAEY-BXhYx3nO.js} +1 -1
- package/dist/web/assets/{chunk-PQ6SQG4A-DX0xW7kO.js → chunk-PQ6SQG4A-TF58UVMU.js} +1 -1
- package/dist/web/assets/{chunk-PU5JKC2W-C7Gry6md.js → chunk-PU5JKC2W-ek7k4QVB.js} +1 -1
- package/dist/web/assets/chunk-QZHKN3VN-CYaTbeZf.js +1 -0
- package/dist/web/assets/{chunk-R5LLSJPH-CMY0PkRK.js → chunk-R5LLSJPH-CFwSJijQ.js} +1 -1
- package/dist/web/assets/{chunk-WL4C6EOR-CXuQvlyu.js → chunk-WL4C6EOR-ByUrSRin.js} +1 -1
- package/dist/web/assets/{chunk-XIRO2GV7-DRJEb7Zb.js → chunk-XIRO2GV7-Djlmrely.js} +1 -1
- package/dist/web/assets/{chunk-XPW4576I-BPEX8KhL.js → chunk-XPW4576I-BPQQBakK.js} +1 -1
- package/dist/web/assets/{chunk-XZSTWKYB-Cb0iqycX.js → chunk-XZSTWKYB-DxAOx4hG.js} +1 -1
- package/dist/web/assets/{chunk-YBOYWFTD-av5aeHLq.js → chunk-YBOYWFTD-rQG3QH5s.js} +1 -1
- package/dist/web/assets/classDiagram-VBA2DB6C-BA8Nj-_C.js +1 -0
- package/dist/web/assets/classDiagram-v2-RAHNMMFH-DjYu-6mn.js +1 -0
- package/dist/web/assets/clone-LRxlvnMj.js +1 -0
- package/dist/web/assets/code-editor-BixOXePn.js +8 -0
- package/dist/web/assets/{cose-bilkent-S5V4N54A-qudEiMCT.js → cose-bilkent-S5V4N54A-B_AWZsOP.js} +1 -1
- package/dist/web/assets/csv-parser-CNNw2RVA.js +6 -0
- package/dist/web/assets/{csv-preview-sx6DC51G.js → csv-preview-D2pJJj3K.js} +3 -8
- package/dist/web/assets/{dagre-BFcnKyBF.js → dagre-DHq9bhnd.js} +1 -1
- package/dist/web/assets/{dagre-KLK3FWXG-C3O-MTLf.js → dagre-KLK3FWXG-BdJr7Byp.js} +1 -1
- package/dist/web/assets/database-viewer-DfLe8ewt.js +2 -0
- package/dist/web/assets/{diagram-E7M64L7V-DxPjK7_c.js → diagram-E7M64L7V-_db4pBVA.js} +1 -1
- package/dist/web/assets/{diagram-IFDJBPK2-sqTog_XV.js → diagram-IFDJBPK2-xKoeuiJx.js} +1 -1
- package/dist/web/assets/{diagram-P4PSJMXO-hzmp0GHK.js → diagram-P4PSJMXO-C8tjJsev.js} +1 -1
- package/dist/web/assets/diff-viewer-eFO08m_L.js +4 -0
- package/dist/web/assets/dist-DIV6WgAG.js +41 -0
- package/dist/web/assets/{erDiagram-INFDFZHY-DLeYhAAT.js → erDiagram-INFDFZHY-BSh2z9Df.js} +1 -1
- package/dist/web/assets/{extension-webview-2XjXXtyy.js → extension-webview-4CL9kCKR.js} +1 -1
- package/dist/web/assets/{flowDiagram-PKNHOUZH-CRxlE9Sr.js → flowDiagram-PKNHOUZH-oYaovqyp.js} +1 -1
- package/dist/web/assets/{ganttDiagram-A5KZAMGK-BdjmoMLS.js → ganttDiagram-A5KZAMGK-DmL26q2P.js} +1 -1
- package/dist/web/assets/git-graph-BqeE_o17.js +1 -0
- package/dist/web/assets/gitGraph-HDMCJU4V-Bwna3and.js +1 -0
- package/dist/web/assets/{gitGraphDiagram-K3NZZRJ6-BeHSX7kk.js → gitGraphDiagram-K3NZZRJ6-CMoukSrY.js} +1 -1
- package/dist/web/assets/{graphlib-Duh_bWLa.js → graphlib-BcsNnGcW.js} +1 -1
- package/dist/web/assets/index-BWLy2h18.css +2 -0
- package/dist/web/assets/index-DwrCg0TN.js +30 -0
- package/dist/web/assets/info-3K5VOQVL-_vRxVNUm.js +1 -0
- package/dist/web/assets/infoDiagram-LFFYTUFH-DWwumDkq.js +2 -0
- package/dist/web/assets/{isEmpty-B9L-Ge-H.js → isEmpty-bnrF3Qbc.js} +1 -1
- package/dist/web/assets/{ishikawaDiagram-PHBUUO56-Cu0Rt1Ok.js → ishikawaDiagram-PHBUUO56-D05_LyL7.js} +1 -1
- package/dist/web/assets/{journeyDiagram-4ABVD52K-CgDI-UG4.js → journeyDiagram-4ABVD52K-B_L20qMe.js} +1 -1
- package/dist/web/assets/{kanban-definition-K7BYSVSG-h4g10UHL.js → kanban-definition-K7BYSVSG-CZ535BbZ.js} +1 -1
- package/dist/web/assets/keybindings-store-BNBONtSd.js +1 -0
- package/dist/web/assets/{line-B75-Rx70.js → line-CVvo3dRu.js} +1 -1
- package/dist/web/assets/{linear-Bcjv9FQt.js → linear-DP4mkX3m.js} +1 -1
- package/dist/web/assets/{markdown-renderer-Bb7OSpxF.js → markdown-renderer-BUqab2os.js} +5 -5
- package/dist/web/assets/{mermaid-parser.core-8u2leTXI.js → mermaid-parser.core-C7UwoIh6.js} +2 -2
- package/dist/web/assets/{mindmap-definition-YRQLILUH-BaOBwb-W.js → mindmap-definition-YRQLILUH-x0MTutJp.js} +1 -1
- package/dist/web/assets/{ordinal-LFEjVtwQ.js → ordinal-_K3x1fkz.js} +1 -1
- package/dist/web/assets/packet-RMMSAZCW-DY5PNnZU.js +1 -0
- package/dist/web/assets/pie-UPGHQEXC-BHncZutv.js +1 -0
- package/dist/web/assets/{pieDiagram-SKSYHLDU-At5Kz0KK.js → pieDiagram-SKSYHLDU-C1Gjrtzy.js} +1 -1
- package/dist/web/assets/{port-forwarding-tab-bD8MKumH.js → port-forwarding-tab-CfO-UJ84.js} +1 -1
- package/dist/web/assets/postgres-viewer-BVJZ44eU.js +13 -0
- package/dist/web/assets/{quadrantDiagram-337W2JSQ-CdjGIDfw.js → quadrantDiagram-337W2JSQ-C8bzJCjQ.js} +1 -1
- package/dist/web/assets/radar-KQ55EAFF-DH0AOkUy.js +1 -0
- package/dist/web/assets/{requirementDiagram-Z7DCOOCP-B9F_Cx_p.js → requirementDiagram-Z7DCOOCP-pQyah6WB.js} +1 -1
- package/dist/web/assets/{sankeyDiagram-WA2Y5GQK-RolPi8bU.js → sankeyDiagram-WA2Y5GQK-T6RgG-N8.js} +1 -1
- package/dist/web/assets/{sequenceDiagram-2WXFIKYE-DM-tMAhx.js → sequenceDiagram-2WXFIKYE-BQDJ4CVs.js} +1 -1
- package/dist/web/assets/settings-tab-C6hdJujW.js +1 -0
- package/dist/web/assets/sql-completion-provider-DM9Qov6L.js +1 -0
- package/dist/web/assets/sql-query-editor-OhZa4Z9F.js +3 -0
- package/dist/web/assets/sqlite-viewer-C8p1_jz4.js +1 -0
- package/dist/web/assets/{stateDiagram-RAJIS63D-C4EMl6jf.js → stateDiagram-RAJIS63D-66vhiIuk.js} +1 -1
- package/dist/web/assets/stateDiagram-v2-FVOUBMTO-BGVqj_g9.js +1 -0
- package/dist/web/assets/{terminal-tab-Cq6vQ9W9.js → terminal-tab-CaO0WnIo.js} +2 -2
- package/dist/web/assets/text-wrap-BWNOVswA.js +1 -0
- package/dist/web/assets/{timeline-definition-YZTLITO2-A4PN_Efm.js → timeline-definition-YZTLITO2-DwZqB3nn.js} +1 -1
- package/dist/web/assets/treemap-KZPCXAKY-B2Xkyv-K.js +1 -0
- package/dist/web/assets/use-monaco-theme-U9ZhfvHB.js +11 -0
- package/dist/web/assets/{vennDiagram-LZ73GAT5-ywK7LMaH.js → vennDiagram-LZ73GAT5-s9Z71fz-.js} +1 -1
- package/dist/web/assets/x-D2_KzIET.js +1 -0
- package/dist/web/assets/{xychartDiagram-JWTSCODW-DylHYNtJ.js → xychartDiagram-JWTSCODW-DRa_TH4B.js} +1 -1
- package/dist/web/index.html +9 -8
- package/dist/web/sw.js +1 -1
- package/package.json +1 -1
- package/src/cli/commands/bot-cmd.ts +4 -0
- package/src/cli/commands/db-cmd.ts +4 -3
- package/src/server/routes/database.ts +126 -9
- package/src/services/cloud.service.ts +2 -1
- package/src/services/database/sqlite-adapter.ts +1 -0
- package/src/services/db.service.ts +42 -3
- package/src/services/postgres.service.ts +37 -6
- package/src/services/sqlite.service.ts +18 -4
- package/src/services/table-cache.service.ts +2 -3
- package/src/types/database.ts +2 -0
- package/src/web/components/database/connection-form-dialog.tsx +17 -8
- package/src/web/components/database/connection-list.tsx +191 -139
- package/src/web/components/database/data-grid.tsx +634 -0
- package/src/web/components/database/database-sidebar.tsx +4 -1
- package/src/web/components/database/database-viewer.tsx +204 -225
- package/src/web/components/database/export-button.tsx +100 -0
- package/src/web/components/database/sql-completion-provider.ts +301 -0
- package/src/web/components/database/sql-query-editor.tsx +123 -0
- package/src/web/components/database/use-connections.ts +21 -1
- package/src/web/components/database/use-database.ts +59 -7
- package/src/web/components/editor/code-editor.tsx +224 -16
- package/src/web/components/sqlite/sqlite-query-editor.tsx +3 -90
- package/src/web/components/sqlite/sqlite-viewer.tsx +0 -2
- package/src/web/components/sqlite/use-sqlite.ts +1 -1
- package/dist/web/assets/architecture-PBZL5I3N-CFzkFKEL.js +0 -1
- package/dist/web/assets/channel-C2fMafck.js +0 -1
- package/dist/web/assets/chat-tab-CfdMDCBK.js +0 -10
- package/dist/web/assets/chunk-GLR3WWYH-DBdWQ3zy.js +0 -2
- package/dist/web/assets/chunk-HHEYEP7N-BBw_z0fW.js +0 -1
- package/dist/web/assets/chunk-QZHKN3VN-DFKFM_C1.js +0 -1
- package/dist/web/assets/classDiagram-VBA2DB6C-Dp4Kk3Yb.js +0 -1
- package/dist/web/assets/classDiagram-v2-RAHNMMFH-D8IvcV_B.js +0 -1
- package/dist/web/assets/clone-B2hUek6n.js +0 -1
- package/dist/web/assets/code-editor-DhCfJIpG.js +0 -2
- package/dist/web/assets/database-viewer-DLkAUBpm.js +0 -1
- package/dist/web/assets/diff-viewer-DWVWsekJ.js +0 -4
- package/dist/web/assets/dist-C40JmyoH.js +0 -13
- package/dist/web/assets/dist-DRTW9IWi.js +0 -41
- package/dist/web/assets/git-graph-Ds5bs1cM.js +0 -1
- package/dist/web/assets/gitGraph-HDMCJU4V-CNlas3Rz.js +0 -1
- package/dist/web/assets/index-8b0LM6IC.js +0 -30
- package/dist/web/assets/index-BnMECpW3.css +0 -2
- package/dist/web/assets/info-3K5VOQVL-BDzTLc11.js +0 -1
- package/dist/web/assets/infoDiagram-LFFYTUFH-ZZmpgc6t.js +0 -2
- package/dist/web/assets/keybindings-store-C06Z0Zhk.js +0 -1
- package/dist/web/assets/packet-RMMSAZCW-IVa5F-go.js +0 -1
- package/dist/web/assets/pie-UPGHQEXC-CvXHKAzp.js +0 -1
- package/dist/web/assets/postgres-viewer-C3OND65T.js +0 -1
- package/dist/web/assets/radar-KQ55EAFF-Z-Tr5wtS.js +0 -1
- package/dist/web/assets/settings-tab-cuMIkUNV.js +0 -1
- package/dist/web/assets/sqlite-viewer-CpjnwDtk.js +0 -1
- package/dist/web/assets/stateDiagram-v2-FVOUBMTO-B-UjZch3.js +0 -1
- package/dist/web/assets/treemap-KZPCXAKY-C9TYRE0k.js +0 -1
- package/dist/web/assets/use-monaco-theme-BH9sQ-Yu.js +0 -11
- /package/dist/web/assets/{api-client-BKIT_Qeg.js → api-client-BfBM3I7n.js} +0 -0
- /package/dist/web/assets/{array-DqLCdDFv.js → array-B9UHiPd-.js} +0 -0
- /package/dist/web/assets/{chevron-right-5HgK6l7K.js → chevron-right-4zq1jPv6.js} +0 -0
- /package/dist/web/assets/{columns-2-cEVJHYd7.js → columns-2-BoZAN-iw.js} +0 -0
- /package/dist/web/assets/{cytoscape.esm-CWPXKqbJ.js → cytoscape.esm-BW-DbntU.js} +0 -0
- /package/dist/web/assets/{defaultLocale-CrJzLgRD.js → defaultLocale-5eAKkKJC.js} +0 -0
- /package/dist/web/assets/{dist-Cep75xXf.js → dist-CSJdAyA9.js} +0 -0
- /package/dist/web/assets/{init-C0r9Gk5G.js → init-DlZdxViB.js} +0 -0
- /package/dist/web/assets/{isArrayLikeObject-CGBoxvCD.js → isArrayLikeObject-B_v2FtYn.js} +0 -0
- /package/dist/web/assets/{katex-DzXRfQ_m.js → katex-Bqvo_ZG0.js} +0 -0
- /package/dist/web/assets/{lib-mag4ySk-.js → lib-DurwGtQO.js} +0 -0
- /package/dist/web/assets/{math-y9zN1W-N.js → math-069Z4SuC.js} +0 -0
- /package/dist/web/assets/{path-DIKpVbHL.js → path-6uRLdFF7.js} +0 -0
- /package/dist/web/assets/{rough.esm-nHaDi0Kw.js → rough.esm-JX0wREDd.js} +0 -0
- /package/dist/web/assets/{src-Dw4QhedI.js → src-BqX54PbV.js} +0 -0
- /package/dist/web/assets/{utils-DMiycH3O.js → utils-BNytJOb1.js} +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useEffect, useState, useCallback, useRef } from "react";
|
|
1
|
+
import { useEffect, useState, useCallback, useRef, useMemo } from "react";
|
|
2
2
|
import Editor, { type OnMount } from "@monaco-editor/react";
|
|
3
3
|
import type * as MonacoType from "monaco-editor";
|
|
4
4
|
import { MarkdownRenderer } from "@/components/shared/markdown-renderer";
|
|
@@ -7,10 +7,12 @@ import { useTabStore } from "@/stores/tab-store";
|
|
|
7
7
|
import { useSettingsStore } from "@/stores/settings-store";
|
|
8
8
|
import { basename } from "@/lib/utils";
|
|
9
9
|
import { useMonacoTheme } from "@/lib/use-monaco-theme";
|
|
10
|
-
import { Loader2, FileWarning, ExternalLink } from "lucide-react";
|
|
10
|
+
import { Loader2, FileWarning, ExternalLink, Play, Database } from "lucide-react";
|
|
11
11
|
import { EditorBreadcrumb } from "./editor-breadcrumb";
|
|
12
12
|
import { EditorToolbar } from "./editor-toolbar";
|
|
13
13
|
import { lazy, Suspense } from "react";
|
|
14
|
+
import { createSqlCompletionProvider, clearCompletionCache, type SchemaInfo } from "../database/sql-completion-provider";
|
|
15
|
+
import { useConnections, type Connection } from "../database/use-connections";
|
|
14
16
|
|
|
15
17
|
const CsvPreview = lazy(() => import("./csv-preview").then((m) => ({ default: m.CsvPreview })));
|
|
16
18
|
|
|
@@ -33,6 +35,7 @@ function getMonacoLanguage(filename: string): string {
|
|
|
33
35
|
json: "json", md: "markdown", mdx: "markdown",
|
|
34
36
|
yaml: "yaml", yml: "yaml",
|
|
35
37
|
sh: "shell", bash: "shell",
|
|
38
|
+
sql: "sql",
|
|
36
39
|
};
|
|
37
40
|
return map[ext] ?? "plaintext";
|
|
38
41
|
}
|
|
@@ -45,7 +48,10 @@ interface CodeEditorProps {
|
|
|
45
48
|
export function CodeEditor({ metadata, tabId }: CodeEditorProps) {
|
|
46
49
|
const filePath = metadata?.filePath as string | undefined;
|
|
47
50
|
const projectName = metadata?.projectName as string | undefined;
|
|
48
|
-
|
|
51
|
+
// Inline content mode: read-only Monaco with pre-loaded content (e.g. cell viewer)
|
|
52
|
+
const inlineContent = metadata?.inlineContent as string | undefined;
|
|
53
|
+
const inlineLanguage = metadata?.inlineLanguage as string | undefined;
|
|
54
|
+
const [content, setContent] = useState<string | null>(inlineContent ?? null);
|
|
49
55
|
const [encoding, setEncoding] = useState<string>("utf-8");
|
|
50
56
|
const [loading, setLoading] = useState(true);
|
|
51
57
|
const [error, setError] = useState<string | null>(null);
|
|
@@ -64,9 +70,85 @@ export function CodeEditor({ metadata, tabId }: CodeEditorProps) {
|
|
|
64
70
|
const isSqlite = SQLITE_EXTS.has(ext);
|
|
65
71
|
const isMarkdown = ext === "md" || ext === "mdx";
|
|
66
72
|
const isCsv = ext === "csv";
|
|
73
|
+
const isSql = ext === "sql";
|
|
67
74
|
const [mdMode, setMdMode] = useState<"edit" | "preview">("preview");
|
|
68
75
|
const [csvMode, setCsvMode] = useState<"table" | "raw">("table");
|
|
69
76
|
|
|
77
|
+
// SQL file: connection picker + autocomplete + run in DB viewer
|
|
78
|
+
const { connections, cachedTables, refreshTables } = useConnections();
|
|
79
|
+
const [sqlConnId, setSqlConnId] = useState<number | null>(() => {
|
|
80
|
+
if (!isSql || !filePath) return null;
|
|
81
|
+
const stored = localStorage.getItem(`ppm:sql-conn:${filePath}`);
|
|
82
|
+
return stored ? Number(stored) : null;
|
|
83
|
+
});
|
|
84
|
+
const monacoInstanceRef = useRef<typeof MonacoType | null>(null);
|
|
85
|
+
const completionDisposable = useRef<MonacoType.IDisposable | null>(null);
|
|
86
|
+
|
|
87
|
+
const selectedSqlConn = useMemo(() => connections.find((c) => c.id === sqlConnId) ?? null, [connections, sqlConnId]);
|
|
88
|
+
|
|
89
|
+
// Persist selected connection per file
|
|
90
|
+
const handleSqlConnChange = useCallback((connId: number) => {
|
|
91
|
+
setSqlConnId(connId);
|
|
92
|
+
if (filePath) localStorage.setItem(`ppm:sql-conn:${filePath}`, String(connId));
|
|
93
|
+
// Refresh tables for autocomplete
|
|
94
|
+
refreshTables(connId).catch(() => {});
|
|
95
|
+
}, [filePath, refreshTables]);
|
|
96
|
+
|
|
97
|
+
// Build SchemaInfo for .sql file autocomplete
|
|
98
|
+
const sqlSchemaInfo = useMemo<SchemaInfo | undefined>(() => {
|
|
99
|
+
if (!isSql || !sqlConnId) return undefined;
|
|
100
|
+
const tables = (cachedTables.get(sqlConnId) ?? []).map((t) => ({ name: t.tableName, schema: t.schemaName }));
|
|
101
|
+
if (tables.length === 0) return undefined;
|
|
102
|
+
return {
|
|
103
|
+
tables,
|
|
104
|
+
getColumns: async (table: string, schema?: string) => {
|
|
105
|
+
return api.get<{ name: string; type: string }[]>(
|
|
106
|
+
`/api/db/connections/${sqlConnId}/schema?table=${encodeURIComponent(table)}${schema ? `&schema=${encodeURIComponent(schema)}` : ""}`,
|
|
107
|
+
);
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
}, [isSql, sqlConnId, cachedTables]);
|
|
111
|
+
|
|
112
|
+
// Register/dispose completion provider when connection changes
|
|
113
|
+
useEffect(() => {
|
|
114
|
+
if (!monacoInstanceRef.current || !sqlSchemaInfo) return;
|
|
115
|
+
completionDisposable.current?.dispose();
|
|
116
|
+
clearCompletionCache();
|
|
117
|
+
completionDisposable.current = monacoInstanceRef.current.languages.registerCompletionItemProvider(
|
|
118
|
+
"sql",
|
|
119
|
+
createSqlCompletionProvider(monacoInstanceRef.current, sqlSchemaInfo),
|
|
120
|
+
);
|
|
121
|
+
return () => { completionDisposable.current?.dispose(); };
|
|
122
|
+
}, [sqlSchemaInfo]);
|
|
123
|
+
|
|
124
|
+
// Run in DB Viewer
|
|
125
|
+
const openTab = useTabStore((s) => s.openTab);
|
|
126
|
+
const runSqlInViewer = useCallback((sqlText: string) => {
|
|
127
|
+
if (!selectedSqlConn) return;
|
|
128
|
+
openTab({
|
|
129
|
+
type: "database",
|
|
130
|
+
title: `${selectedSqlConn.name} · Query`,
|
|
131
|
+
projectId: null,
|
|
132
|
+
closable: true,
|
|
133
|
+
metadata: { connectionId: selectedSqlConn.id, connectionName: selectedSqlConn.name, dbType: selectedSqlConn.type, initialSql: sqlText },
|
|
134
|
+
});
|
|
135
|
+
}, [selectedSqlConn, openTab]);
|
|
136
|
+
|
|
137
|
+
const handleRunInDbViewer = useCallback(() => {
|
|
138
|
+
if (!editorRef.current || !selectedSqlConn) return;
|
|
139
|
+
const editor = editorRef.current;
|
|
140
|
+
const selection = editor.getSelection();
|
|
141
|
+
const sqlText = selection && !selection.isEmpty()
|
|
142
|
+
? editor.getModel()?.getValueInRange(selection) ?? editor.getValue()
|
|
143
|
+
: editor.getValue();
|
|
144
|
+
runSqlInViewer(sqlText);
|
|
145
|
+
}, [selectedSqlConn, runSqlInViewer]);
|
|
146
|
+
|
|
147
|
+
// CodeLens: inline Run buttons between SQL statements
|
|
148
|
+
const codeLensDisposable = useRef<MonacoType.IDisposable[]>([]);
|
|
149
|
+
const runSqlRef = useRef(runSqlInViewer);
|
|
150
|
+
runSqlRef.current = runSqlInViewer;
|
|
151
|
+
|
|
70
152
|
// Redirect .db files to sqlite viewer by changing tab type
|
|
71
153
|
useEffect(() => {
|
|
72
154
|
if (isSqlite && tabId) updateTab(tabId, { type: "sqlite" });
|
|
@@ -77,6 +159,7 @@ export function CodeEditor({ metadata, tabId }: CodeEditorProps) {
|
|
|
77
159
|
|
|
78
160
|
// Load file content
|
|
79
161
|
useEffect(() => {
|
|
162
|
+
if (inlineContent != null) { setLoading(false); return; }
|
|
80
163
|
if (!filePath) return;
|
|
81
164
|
if (!isExternalFile && !projectName) return;
|
|
82
165
|
if (isImage || isPdf) { setLoading(false); return; }
|
|
@@ -141,33 +224,84 @@ export function CodeEditor({ metadata, tabId }: CodeEditorProps) {
|
|
|
141
224
|
const lineNumber = metadata?.lineNumber as number | undefined;
|
|
142
225
|
const handleEditorMount: OnMount = useCallback((editor, monaco) => {
|
|
143
226
|
editorRef.current = editor;
|
|
227
|
+
monacoInstanceRef.current = monaco;
|
|
144
228
|
if (lineNumber && lineNumber > 0) {
|
|
145
|
-
// Defer until content is rendered
|
|
146
229
|
setTimeout(() => {
|
|
147
230
|
editor.revealLineInCenter(lineNumber);
|
|
148
231
|
editor.setPosition({ lineNumber, column: 1 });
|
|
149
232
|
editor.focus();
|
|
150
233
|
}, 100);
|
|
151
234
|
}
|
|
152
|
-
// Alt+Z → toggle word wrap
|
|
153
235
|
editor.addCommand(
|
|
154
236
|
monaco.KeyMod.Alt | monaco.KeyCode.KeyZ,
|
|
155
237
|
() => useSettingsStore.getState().toggleWordWrap(),
|
|
156
238
|
);
|
|
157
|
-
// Disable all diagnostics — PPM is a lightweight editor, not a full IDE
|
|
158
239
|
monaco.languages.typescript.typescriptDefaults.setDiagnosticsOptions({
|
|
159
|
-
noSemanticValidation: true,
|
|
160
|
-
noSyntaxValidation: true,
|
|
161
|
-
noSuggestionDiagnostics: true,
|
|
240
|
+
noSemanticValidation: true, noSyntaxValidation: true, noSuggestionDiagnostics: true,
|
|
162
241
|
});
|
|
163
242
|
monaco.languages.typescript.javascriptDefaults.setDiagnosticsOptions({
|
|
164
|
-
noSemanticValidation: true,
|
|
165
|
-
noSyntaxValidation: true,
|
|
166
|
-
noSuggestionDiagnostics: true,
|
|
243
|
+
noSemanticValidation: true, noSyntaxValidation: true, noSuggestionDiagnostics: true,
|
|
167
244
|
});
|
|
168
|
-
|
|
245
|
+
// Register SQL completion if schema available
|
|
246
|
+
if (sqlSchemaInfo) {
|
|
247
|
+
completionDisposable.current?.dispose();
|
|
248
|
+
completionDisposable.current = monaco.languages.registerCompletionItemProvider(
|
|
249
|
+
"sql", createSqlCompletionProvider(monaco, sqlSchemaInfo),
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Register CodeLens for inline Run buttons on .sql files
|
|
254
|
+
if (isSql) {
|
|
255
|
+
codeLensDisposable.current.forEach((d) => d.dispose());
|
|
256
|
+
codeLensDisposable.current = [];
|
|
257
|
+
|
|
258
|
+
const cmdId = editor.addCommand(0, (_accessor: unknown, sql: string) => {
|
|
259
|
+
if (sql) runSqlRef.current(sql);
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
if (cmdId) {
|
|
263
|
+
const provider = monaco.languages.registerCodeLensProvider("sql", {
|
|
264
|
+
provideCodeLenses: (model: MonacoType.editor.ITextModel) => {
|
|
265
|
+
const lenses: MonacoType.languages.CodeLens[] = [];
|
|
266
|
+
const lines = model.getValue().split("\n");
|
|
267
|
+
let stmtStartLine = -1;
|
|
268
|
+
let stmtLines: string[] = [];
|
|
269
|
+
|
|
270
|
+
const addLens = (line: number, stmt: string) => {
|
|
271
|
+
const trimmed = stmt.trim();
|
|
272
|
+
if (!trimmed || trimmed.startsWith("--")) return;
|
|
273
|
+
lenses.push({
|
|
274
|
+
range: { startLineNumber: line, startColumn: 1, endLineNumber: line, endColumn: 1 },
|
|
275
|
+
command: { id: cmdId, title: "\u25B7 Run", arguments: [trimmed] },
|
|
276
|
+
});
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
for (let i = 0; i < lines.length; i++) {
|
|
280
|
+
const trimmed = lines[i]!.trim();
|
|
281
|
+
if (stmtStartLine === -1) {
|
|
282
|
+
if (!trimmed || trimmed.startsWith("--")) continue;
|
|
283
|
+
stmtStartLine = i + 1;
|
|
284
|
+
stmtLines = [];
|
|
285
|
+
}
|
|
286
|
+
stmtLines.push(lines[i]!);
|
|
287
|
+
if (trimmed.endsWith(";")) {
|
|
288
|
+
addLens(stmtStartLine, stmtLines.join("\n"));
|
|
289
|
+
stmtStartLine = -1;
|
|
290
|
+
stmtLines = [];
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
if (stmtStartLine > 0 && stmtLines.join("").trim()) {
|
|
294
|
+
addLens(stmtStartLine, stmtLines.join("\n"));
|
|
295
|
+
}
|
|
296
|
+
return { lenses, dispose: () => {} };
|
|
297
|
+
},
|
|
298
|
+
});
|
|
299
|
+
codeLensDisposable.current.push(provider);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}, [sqlSchemaInfo]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
169
303
|
|
|
170
|
-
if (!filePath || (!isExternalFile && !projectName)) {
|
|
304
|
+
if (!inlineContent && (!filePath || (!isExternalFile && !projectName))) {
|
|
171
305
|
return (
|
|
172
306
|
<div className="flex items-center justify-center h-full text-text-secondary text-sm">
|
|
173
307
|
No file selected.
|
|
@@ -203,8 +337,72 @@ export function CodeEditor({ metadata, tabId }: CodeEditorProps) {
|
|
|
203
337
|
);
|
|
204
338
|
}
|
|
205
339
|
|
|
340
|
+
/** SQL connection picker bar (shared between breadcrumb and standalone) */
|
|
341
|
+
const sqlPickerBar = isSql ? (
|
|
342
|
+
<div className="shrink-0 flex items-center gap-1 px-2 border-l border-border">
|
|
343
|
+
<Database className="size-3 text-muted-foreground" />
|
|
344
|
+
<select
|
|
345
|
+
value={sqlConnId ?? ""}
|
|
346
|
+
onChange={(e) => { const v = Number(e.target.value); if (v) handleSqlConnChange(v); }}
|
|
347
|
+
className="h-5 text-[10px] bg-transparent border border-border rounded px-1 text-foreground outline-none max-w-[140px]"
|
|
348
|
+
title="Select connection for autocomplete"
|
|
349
|
+
>
|
|
350
|
+
<option value="">Connection…</option>
|
|
351
|
+
{connections.map((c) => <option key={c.id} value={c.id}>{c.name}</option>)}
|
|
352
|
+
</select>
|
|
353
|
+
<button
|
|
354
|
+
type="button"
|
|
355
|
+
onClick={handleRunInDbViewer}
|
|
356
|
+
disabled={!selectedSqlConn}
|
|
357
|
+
className="p-0.5 rounded text-muted-foreground hover:text-primary disabled:opacity-30 transition-colors"
|
|
358
|
+
title="Run all in DB Viewer"
|
|
359
|
+
>
|
|
360
|
+
<Play className="size-3.5" />
|
|
361
|
+
</button>
|
|
362
|
+
</div>
|
|
363
|
+
) : null;
|
|
364
|
+
|
|
365
|
+
// Beautify for inline content
|
|
366
|
+
const canBeautifyInline = inlineContent != null && (inlineLanguage === "json" || inlineLanguage === "xml");
|
|
367
|
+
const [isBeautified, setIsBeautified] = useState(false);
|
|
368
|
+
const handleBeautifyInline = useCallback(() => {
|
|
369
|
+
if (!inlineContent) return;
|
|
370
|
+
if (isBeautified) {
|
|
371
|
+
setContent(inlineContent);
|
|
372
|
+
setIsBeautified(false);
|
|
373
|
+
} else {
|
|
374
|
+
const trimmed = inlineContent.trimStart();
|
|
375
|
+
if (inlineLanguage === "json") {
|
|
376
|
+
try { setContent(JSON.stringify(JSON.parse(trimmed), null, 2)); setIsBeautified(true); } catch { /* not valid */ }
|
|
377
|
+
} else if (inlineLanguage === "xml") {
|
|
378
|
+
let indent = 0;
|
|
379
|
+
const formatted = trimmed.replace(/(>)(<)(\/*)/g, "$1\n$2$3")
|
|
380
|
+
.split("\n")
|
|
381
|
+
.map((line) => {
|
|
382
|
+
const l = line.trim();
|
|
383
|
+
if (l.startsWith("</")) indent = Math.max(0, indent - 1);
|
|
384
|
+
const padded = " ".repeat(indent) + l;
|
|
385
|
+
if (l.startsWith("<") && !l.startsWith("</") && !l.endsWith("/>") && !l.includes("</")) indent++;
|
|
386
|
+
return padded;
|
|
387
|
+
})
|
|
388
|
+
.join("\n");
|
|
389
|
+
setContent(formatted);
|
|
390
|
+
setIsBeautified(true);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
}, [inlineContent, inlineLanguage, isBeautified]);
|
|
394
|
+
|
|
206
395
|
return (
|
|
207
396
|
<div className="flex flex-col h-full w-full overflow-hidden">
|
|
397
|
+
{/* Inline content toolbar (cell viewer mode) */}
|
|
398
|
+
{inlineContent != null && canBeautifyInline && (
|
|
399
|
+
<div className="flex items-center h-7 border-b border-border bg-background shrink-0 px-2 gap-2">
|
|
400
|
+
<button type="button" onClick={handleBeautifyInline}
|
|
401
|
+
className="text-[10px] px-2 py-0.5 rounded border border-border hover:bg-muted transition-colors text-foreground">
|
|
402
|
+
{isBeautified ? "Raw" : "Beautify"}
|
|
403
|
+
</button>
|
|
404
|
+
</div>
|
|
405
|
+
)}
|
|
208
406
|
{/* Breadcrumb + Toolbar bar — desktop only */}
|
|
209
407
|
{filePath && projectName && tabId && (
|
|
210
408
|
<div className="hidden md:flex items-center h-7 border-b border-border bg-background shrink-0">
|
|
@@ -214,6 +412,7 @@ export function CodeEditor({ metadata, tabId }: CodeEditorProps) {
|
|
|
214
412
|
tabId={tabId}
|
|
215
413
|
className="flex items-center flex-1 min-w-0 overflow-x-auto scrollbar-none px-2 gap-0.5"
|
|
216
414
|
/>
|
|
415
|
+
{sqlPickerBar}
|
|
217
416
|
<EditorToolbar
|
|
218
417
|
ext={ext}
|
|
219
418
|
mdMode={mdMode}
|
|
@@ -229,6 +428,14 @@ export function CodeEditor({ metadata, tabId }: CodeEditorProps) {
|
|
|
229
428
|
</div>
|
|
230
429
|
)}
|
|
231
430
|
|
|
431
|
+
{/* Standalone SQL toolbar for external files (no breadcrumb available) */}
|
|
432
|
+
{isSql && (!projectName || !tabId) && (
|
|
433
|
+
<div className="hidden md:flex items-center h-7 border-b border-border bg-background shrink-0 px-2">
|
|
434
|
+
<span className="text-xs text-muted-foreground truncate flex-1">{filePath ? basename(filePath) : "SQL"}</span>
|
|
435
|
+
{sqlPickerBar}
|
|
436
|
+
</div>
|
|
437
|
+
)}
|
|
438
|
+
|
|
232
439
|
{/* Content area */}
|
|
233
440
|
{isCsv && csvMode === "table" ? (
|
|
234
441
|
<Suspense fallback={<div className="flex items-center justify-center h-full"><Loader2 className="size-5 animate-spin text-text-subtle" /></div>}>
|
|
@@ -240,9 +447,9 @@ export function CodeEditor({ metadata, tabId }: CodeEditorProps) {
|
|
|
240
447
|
<div className="flex-1 overflow-hidden">
|
|
241
448
|
<Editor
|
|
242
449
|
height="100%"
|
|
243
|
-
language={getMonacoLanguage(filePath)}
|
|
450
|
+
language={inlineLanguage ?? getMonacoLanguage(filePath ?? "")}
|
|
244
451
|
value={content ?? ""}
|
|
245
|
-
onChange={handleChange}
|
|
452
|
+
onChange={inlineContent != null ? undefined : handleChange}
|
|
246
453
|
onMount={handleEditorMount}
|
|
247
454
|
theme={monacoTheme}
|
|
248
455
|
options={{
|
|
@@ -255,6 +462,7 @@ export function CodeEditor({ metadata, tabId }: CodeEditorProps) {
|
|
|
255
462
|
lineNumbers: "on",
|
|
256
463
|
folding: true,
|
|
257
464
|
bracketPairColorization: { enabled: true },
|
|
465
|
+
readOnly: inlineContent != null,
|
|
258
466
|
}}
|
|
259
467
|
loading={<Loader2 className="size-5 animate-spin text-text-subtle" />}
|
|
260
468
|
/>
|
|
@@ -1,97 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import CodeMirror from "@uiw/react-codemirror";
|
|
3
|
-
import { sql, SQLite } from "@codemirror/lang-sql";
|
|
4
|
-
import { Play, Loader2 } from "lucide-react";
|
|
5
|
-
import type { QueryResult } from "./use-sqlite";
|
|
1
|
+
import { SqlQueryEditor } from "../database/sql-query-editor";
|
|
6
2
|
|
|
7
3
|
interface Props {
|
|
8
4
|
onExecute: (sql: string) => void;
|
|
9
|
-
result: QueryResult | null;
|
|
10
|
-
error: string | null;
|
|
11
5
|
loading: boolean;
|
|
12
6
|
}
|
|
13
7
|
|
|
14
|
-
export function SqliteQueryEditor({ onExecute,
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const handleExecute = useCallback(() => {
|
|
18
|
-
const trimmed = query.trim();
|
|
19
|
-
if (!trimmed) return;
|
|
20
|
-
onExecute(trimmed);
|
|
21
|
-
}, [query, onExecute]);
|
|
22
|
-
|
|
23
|
-
const handleKeyDown = useCallback((e: React.KeyboardEvent) => {
|
|
24
|
-
if ((e.metaKey || e.ctrlKey) && e.key === "Enter") {
|
|
25
|
-
e.preventDefault();
|
|
26
|
-
handleExecute();
|
|
27
|
-
}
|
|
28
|
-
}, [handleExecute]);
|
|
29
|
-
|
|
30
|
-
return (
|
|
31
|
-
<div className="flex flex-col h-full overflow-hidden">
|
|
32
|
-
{/* Editor area */}
|
|
33
|
-
<div className="flex items-start gap-1 border-b border-border bg-background" onKeyDown={handleKeyDown}>
|
|
34
|
-
<div className="flex-1 max-h-[120px] overflow-auto">
|
|
35
|
-
<CodeMirror
|
|
36
|
-
value={query}
|
|
37
|
-
onChange={setQuery}
|
|
38
|
-
extensions={[sql({ dialect: SQLite })]}
|
|
39
|
-
basicSetup={{ lineNumbers: false, foldGutter: false, highlightActiveLine: false }}
|
|
40
|
-
className="text-xs [&_.cm-editor]:!outline-none [&_.cm-scroller]:!overflow-auto"
|
|
41
|
-
/>
|
|
42
|
-
</div>
|
|
43
|
-
<button
|
|
44
|
-
type="button"
|
|
45
|
-
onClick={handleExecute}
|
|
46
|
-
disabled={loading}
|
|
47
|
-
className="shrink-0 m-1 p-1.5 rounded bg-primary text-primary-foreground hover:bg-primary/90 disabled:opacity-50 transition-colors"
|
|
48
|
-
title="Execute (Cmd+Enter)"
|
|
49
|
-
>
|
|
50
|
-
{loading ? <Loader2 className="size-3.5 animate-spin" /> : <Play className="size-3.5" />}
|
|
51
|
-
</button>
|
|
52
|
-
</div>
|
|
53
|
-
|
|
54
|
-
{/* Results area */}
|
|
55
|
-
<div className="flex-1 overflow-auto text-xs">
|
|
56
|
-
{error && (
|
|
57
|
-
<div className="px-3 py-2 text-destructive bg-destructive/5">{error}</div>
|
|
58
|
-
)}
|
|
59
|
-
|
|
60
|
-
{result && result.changeType === "modify" && (
|
|
61
|
-
<div className="px-3 py-2 text-green-500">
|
|
62
|
-
Query executed. {result.rowsAffected} row(s) affected.
|
|
63
|
-
</div>
|
|
64
|
-
)}
|
|
65
|
-
|
|
66
|
-
{result && result.changeType === "select" && result.rows.length > 0 && (
|
|
67
|
-
<table className="w-full border-collapse">
|
|
68
|
-
<thead className="sticky top-0 bg-muted">
|
|
69
|
-
<tr>
|
|
70
|
-
{result.columns.map((col) => (
|
|
71
|
-
<th key={col} className="px-2 py-1 text-left font-medium text-muted-foreground border-b border-border whitespace-nowrap">
|
|
72
|
-
{col}
|
|
73
|
-
</th>
|
|
74
|
-
))}
|
|
75
|
-
</tr>
|
|
76
|
-
</thead>
|
|
77
|
-
<tbody>
|
|
78
|
-
{result.rows.map((row, i) => (
|
|
79
|
-
<tr key={i} className="hover:bg-muted/30 border-b border-border/50">
|
|
80
|
-
{result.columns.map((col) => (
|
|
81
|
-
<td key={col} className="px-2 py-1 max-w-[300px] truncate" title={row[col] == null ? "NULL" : String(row[col])}>
|
|
82
|
-
{row[col] == null ? <span className="text-muted-foreground/40 italic">NULL</span> : String(row[col])}
|
|
83
|
-
</td>
|
|
84
|
-
))}
|
|
85
|
-
</tr>
|
|
86
|
-
))}
|
|
87
|
-
</tbody>
|
|
88
|
-
</table>
|
|
89
|
-
)}
|
|
90
|
-
|
|
91
|
-
{result && result.changeType === "select" && result.rows.length === 0 && (
|
|
92
|
-
<div className="px-3 py-2 text-muted-foreground">No results</div>
|
|
93
|
-
)}
|
|
94
|
-
</div>
|
|
95
|
-
</div>
|
|
96
|
-
);
|
|
8
|
+
export function SqliteQueryEditor({ onExecute, loading }: Props) {
|
|
9
|
+
return <SqlQueryEditor onExecute={onExecute} loading={loading} />;
|
|
97
10
|
}
|
|
@@ -136,8 +136,6 @@ function SqliteViewerInner({
|
|
|
136
136
|
<div className="border-t border-border h-[40%] shrink-0">
|
|
137
137
|
<SqliteQueryEditor
|
|
138
138
|
onExecute={sqlite.executeQuery}
|
|
139
|
-
result={sqlite.queryResult}
|
|
140
|
-
error={sqlite.queryError}
|
|
141
139
|
loading={sqlite.queryLoading}
|
|
142
140
|
/>
|
|
143
141
|
</div>
|
|
@@ -3,7 +3,7 @@ import { api, projectUrl } from "@/lib/api-client";
|
|
|
3
3
|
|
|
4
4
|
export interface TableInfo { name: string; rowCount: number }
|
|
5
5
|
export interface ColumnInfo { cid: number; name: string; type: string; notnull: boolean; pk: boolean; dflt_value: string | null }
|
|
6
|
-
export interface QueryResult { columns: string[]; rows: Record<string, unknown>[]; rowsAffected: number; changeType: "select" | "modify" }
|
|
6
|
+
export interface QueryResult { columns: string[]; rows: Record<string, unknown>[]; rowsAffected: number; changeType: "select" | "modify"; executionTimeMs?: number }
|
|
7
7
|
interface TableData { columns: string[]; rows: Record<string, unknown>[]; total: number; page: number; limit: number }
|
|
8
8
|
|
|
9
9
|
export function useSqlite(projectName: string, dbPath: string, connectionId?: number) {
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import"./chunk-XZSTWKYB-Cb0iqycX.js";import{n as e}from"./chunk-R5LLSJPH-CMY0PkRK.js";export{e as createArchitectureServices};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{it as e,rt as t}from"./chunk-7R4GIKGN-Dvbyu4Zw.js";var n=(n,r)=>e.lang.round(t.parse(n)[r]);export{n as t};
|