diff --git a/src/pages/Editor.tsx b/src/pages/Editor.tsx index 8937afea..00a829e5 100644 --- a/src/pages/Editor.tsx +++ b/src/pages/Editor.tsx @@ -4,6 +4,7 @@ import { useTranslation } from "react-i18next"; import { reconstructTableQuery } from "../utils/editor"; import { shouldShowStatementSuccess } from "../utils/resultPresentation"; import { formatRowsForCopy, copyTextToClipboard } from "../utils/clipboard"; +import { formatResultForExport } from "../utils/resultExport"; import { serializePkKey, buildPkMap } from "../utils/dataGrid"; import { buildKeylessUpdatePlan, @@ -70,6 +71,7 @@ import { } from "lucide-react"; import { invoke } from "@tauri-apps/api/core"; import { listen, emit } from "@tauri-apps/api/event"; +import { writeTextFile } from "@tauri-apps/plugin-fs"; import { TableToolbar } from "../components/ui/TableToolbar"; import { DataGrid } from "../components/ui/DataGrid"; import { MultiResultPanel } from "../components/ui/MultiResultPanel"; @@ -98,6 +100,7 @@ import { removeOtherEntries, removeEntriesToRight, removeEntriesToLeft, + findActiveEntry, } from "../utils/multiResult"; import { extractQueryParams, @@ -468,6 +471,16 @@ export const Editor = ({ commandScopeId }: EditorProps) => { const isMultiDb = usesMultiDatabaseLayout(activeCapabilities, selectedDatabases); const isEditorOpen = !isTableTab && (activeTab?.isEditorOpen ?? activeTab?.type !== "table"); + const activeResultEntry = useMemo( + () => + activeTab?.results + ? findActiveEntry(activeTab.results, activeTab.activeResultId) + : undefined, + [activeTab?.activeResultId, activeTab?.results], + ); + const activeExportResult = activeResultEntry?.result ?? activeTab?.result; + const canExportActiveResult = + !!activeExportResult && activeExportResult.rows.length > 0; const handleCloseTab = useCallback( (tabId: string) => { @@ -1346,7 +1359,15 @@ export const Editor = ({ commandScopeId }: EditorProps) => { applied.add(idx); applyStatement(idx, item); }); - updateTab(targetTabId, { isLoading: false }); + const firstResultEntry = batchResults.findIndex( + (item) => (item.result?.rows.length ?? 0) > 0, + ); + updateTab(targetTabId, { + isLoading: false, + ...(firstResultEntry >= 0 + ? { activeResultId: entries[firstResultEntry].id } + : {}), + }); }, [ activeConnectionId, @@ -3273,6 +3294,49 @@ export const Editor = ({ commandScopeId }: EditorProps) => { const handleExportCommon = async (format: "csv" | "json" | "markdown") => { if (!activeTab || !activeConnectionId) return; + const extension = format === "markdown" ? "md" : format; + const multiResult = activeResultEntry?.result; + if (multiResult?.rows.length) { + try { + const filePath = await save({ + filters: [ + { + name: format === "markdown" ? "Markdown" : format.toUpperCase(), + extensions: [extension], + }, + ], + defaultPath: `result_${Date.now()}.${extension}`, + }); + + if (!filePath) return; + + setExportState({ + isOpen: true, + status: "exporting", + rowsProcessed: multiResult.rows.length, + fileName: filePath.split(/[/\\]/).pop() || filePath, + }); + setExportMenuOpen(false); + + await writeTextFile( + filePath, + formatResultForExport(multiResult, format, csvDelimiter), + ); + + setExportState((prev) => ({ + ...prev, + status: "completed", + })); + } catch (e) { + setExportState((prev) => ({ + ...prev, + status: "error", + errorMessage: String(e), + })); + } + return; + } + const effectiveSchema = activeCapabilities?.schemas === true ? activeTab.schema : undefined; const tabForQuery = { ...activeTab, schema: effectiveSchema }; @@ -3284,7 +3348,6 @@ export const Editor = ({ commandScopeId }: EditorProps) => { if (!query || !query.trim()) return; try { - const extension = format === "markdown" ? "md" : format; const filePath = await save({ filters: [ { @@ -3813,7 +3876,7 @@ export const Editor = ({ commandScopeId }: EditorProps) => {