diff --git a/src/components/modals/ExportProgressModal.tsx b/src/components/modals/ExportProgressModal.tsx index c30ae46e..2713f6f3 100644 --- a/src/components/modals/ExportProgressModal.tsx +++ b/src/components/modals/ExportProgressModal.tsx @@ -1,4 +1,11 @@ -import { X, FileText, Loader2, CheckCircle, AlertCircle } from "lucide-react"; +import { + X, + FileText, + Loader2, + CheckCircle, + AlertCircle, + AlertTriangle, +} from "lucide-react"; import { useTranslation } from "react-i18next"; import { Modal } from "../ui/Modal"; @@ -10,6 +17,7 @@ interface ExportProgressModalProps { rowsProcessed: number; fileName: string; errorMessage?: string; + warningMessage?: string; onCancel: () => void; onClose: () => void; } @@ -22,6 +30,7 @@ export const ExportProgressModal = ({ onClose, fileName, errorMessage, + warningMessage, }: ExportProgressModalProps) => { const { t } = useTranslation(); @@ -62,12 +71,20 @@ export const ExportProgressModal = ({ {errorMessage}

) : ( -

- {t("editor.rowsProcessed")}:{" "} - - {rowsProcessed.toLocaleString()} - -

+ <> +

+ {t("editor.rowsProcessed")}:{" "} + + {rowsProcessed.toLocaleString()} + +

+ {warningMessage && ( +
+ + {warningMessage} +
+ )} + )} diff --git a/src/i18n/locales/de.json b/src/i18n/locales/de.json index 198aedfd..d9384d77 100644 --- a/src/i18n/locales/de.json +++ b/src/i18n/locales/de.json @@ -1182,6 +1182,7 @@ "executingQuery": "Abfrage wird ausgeführt...", "exporting": "Wird exportiert...", "rowsProcessed": "Verarbeitete Zeilen", + "exportLoadedRowsWarning": "Es wurden nur {{loaded}} geladene Zeilen von insgesamt {{total}} Zeilen exportiert. Blättere durch das Ergebnis oder führe eine eingeschränktere Abfrage aus, wenn du mehr Zeilen exportieren musst.", "queryParameters": "Abfrageparameter", "formatSql": "SQL formatieren", "convertToConsole": "In Konsole umwandeln", diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index f0342854..4968944e 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -1230,6 +1230,7 @@ "executingQuery": "Executing query...", "exporting": "Exporting...", "rowsProcessed": "Rows Processed", + "exportLoadedRowsWarning": "Only {{loaded}} loaded rows were exported out of {{total}} total rows. Page through the result or run a narrower query before exporting if you need more rows.", "queryParameters": "Query Parameters", "formatSql": "Format SQL", "convertToConsole": "Convert to Console", diff --git a/src/i18n/locales/es.json b/src/i18n/locales/es.json index 9d0639f7..c24d9555 100644 --- a/src/i18n/locales/es.json +++ b/src/i18n/locales/es.json @@ -1200,6 +1200,7 @@ "executingQuery": "Ejecutando consulta...", "exporting": "Exportando...", "rowsProcessed": "Filas Procesadas", + "exportLoadedRowsWarning": "Solo se exportaron {{loaded}} filas cargadas de {{total}} filas totales. Recorre las páginas del resultado o ejecuta una consulta más específica antes de exportar si necesitas más filas.", "queryParameters": "Parámetros de Consulta", "formatSql": "Formatear SQL", "convertToConsole": "Convertir a Consola", diff --git a/src/i18n/locales/fr.json b/src/i18n/locales/fr.json index e62286f1..42957551 100644 --- a/src/i18n/locales/fr.json +++ b/src/i18n/locales/fr.json @@ -1220,6 +1220,7 @@ "executingQuery": "Exécution de la requête...", "exporting": "Exportation...", "rowsProcessed": "Lignes traitées", + "exportLoadedRowsWarning": "Seules {{loaded}} lignes chargées sur {{total}} lignes au total ont été exportées. Parcourez les pages du résultat ou exécutez une requête plus restrictive avant d'exporter si vous avez besoin de plus de lignes.", "queryParameters": "Paramètres de requête", "formatSql": "Formater SQL", "convertToConsole": "Convertir en console", diff --git a/src/i18n/locales/it.json b/src/i18n/locales/it.json index c45f47f8..b93eec2e 100644 --- a/src/i18n/locales/it.json +++ b/src/i18n/locales/it.json @@ -1204,6 +1204,7 @@ "executingQuery": "Esecuzione query...", "exporting": "Esportazione in corso...", "rowsProcessed": "Righe processate", + "exportLoadedRowsWarning": "Sono state esportate solo {{loaded}} righe caricate su {{total}} righe totali. Scorri le pagine del risultato o esegui una query più mirata prima di esportare se ti servono più righe.", "queryParameters": "Parametri Query", "formatSql": "Formatta SQL", "convertToConsole": "Converti in Console", diff --git a/src/i18n/locales/ja.json b/src/i18n/locales/ja.json index 59b119f0..84e7f018 100644 --- a/src/i18n/locales/ja.json +++ b/src/i18n/locales/ja.json @@ -1193,6 +1193,7 @@ "executingQuery": "クエリを実行中...", "exporting": "エクスポート中...", "rowsProcessed": "処理行数", + "exportLoadedRowsWarning": "合計 {{total}} 行のうち、読み込み済みの {{loaded}} 行のみをエクスポートしました。さらに多くの行が必要な場合は、結果をページ送りするか、より絞り込んだクエリを実行してからエクスポートしてください。", "queryParameters": "クエリパラメータ", "formatSql": "SQLをフォーマット", "convertToConsole": "コンソールに変換", diff --git a/src/i18n/locales/ko.json b/src/i18n/locales/ko.json index 4f12917d..daaf5560 100644 --- a/src/i18n/locales/ko.json +++ b/src/i18n/locales/ko.json @@ -1157,6 +1157,7 @@ "executingQuery": "쿼리를 실행하는 중...", "exporting": "내보내는 중...", "rowsProcessed": "처리된 행", + "exportLoadedRowsWarning": "총 {{total}}개 행 중 로드된 {{loaded}}개 행만 내보냈습니다. 더 많은 행이 필요하면 결과 페이지를 넘기거나 더 좁은 쿼리를 실행한 뒤 내보내세요.", "queryParameters": "쿼리 매개변수", "formatSql": "SQL 포맷", "convertToConsole": "콘솔로 변환", diff --git a/src/i18n/locales/pt-BR.json b/src/i18n/locales/pt-BR.json index 3d8b5c47..90f60922 100644 --- a/src/i18n/locales/pt-BR.json +++ b/src/i18n/locales/pt-BR.json @@ -1196,6 +1196,7 @@ "executingQuery": "Executando consulta...", "exporting": "Exportando...", "rowsProcessed": "Linhas Processadas", + "exportLoadedRowsWarning": "Apenas {{loaded}} linhas carregadas de {{total}} linhas totais foram exportadas. Navegue pelas páginas do resultado ou execute uma consulta mais restrita antes de exportar se precisar de mais linhas.", "queryParameters": "Parâmetros da Consulta", "formatSql": "Formatar SQL", "convertToConsole": "Converter para Console", diff --git a/src/i18n/locales/ru.json b/src/i18n/locales/ru.json index a17204ef..8e073d26 100644 --- a/src/i18n/locales/ru.json +++ b/src/i18n/locales/ru.json @@ -1178,6 +1178,7 @@ "executingQuery": "Выполнение запроса...", "exporting": "Экспорт...", "rowsProcessed": "Обработано строк", + "exportLoadedRowsWarning": "Экспортированы только {{loaded}} загруженных строк из {{total}} строк. Если нужны дополнительные строки, пролистайте результат или выполните более узкий запрос перед экспортом.", "queryParameters": "Параметры запроса", "formatSql": "Форматировать SQL", "convertToConsole": "Преобразовать в консоль", diff --git a/src/i18n/locales/tl.json b/src/i18n/locales/tl.json index 43f76781..05ebbf24 100644 --- a/src/i18n/locales/tl.json +++ b/src/i18n/locales/tl.json @@ -1213,6 +1213,7 @@ "executingQuery": "Isinasakatuparan ang query...", "exporting": "Nag-e-export...", "rowsProcessed": "Mga Row na Naproseso", + "exportLoadedRowsWarning": "{{loaded}} lang na na-load na row ang na-export mula sa kabuuang {{total}} row. Mag-page sa resulta o magpatakbo ng mas makitid na query bago mag-export kung kailangan mo ng mas maraming row.", "queryParameters": "Mga Parameter ng Query", "formatSql": "I-format ang SQL", "convertToConsole": "I-convert sa Console", diff --git a/src/i18n/locales/zh.json b/src/i18n/locales/zh.json index 7a5f42fe..92ce62b5 100644 --- a/src/i18n/locales/zh.json +++ b/src/i18n/locales/zh.json @@ -1148,6 +1148,7 @@ "executingQuery": "执行查询中...", "exporting": "导出中...", "rowsProcessed": "已处理行数", + "exportLoadedRowsWarning": "仅导出了总计 {{total}} 行中的 {{loaded}} 行已加载数据。如果需要更多行,请先翻页加载结果或运行范围更窄的查询后再导出。", "queryParameters": "查询参数", "formatSql": "格式化 SQL", "convertToConsole": "转换为控制台", diff --git a/src/pages/Editor.tsx b/src/pages/Editor.tsx index c3e87c52..32cdaefd 100644 --- a/src/pages/Editor.tsx +++ b/src/pages/Editor.tsx @@ -4,6 +4,10 @@ import { useTranslation } from "react-i18next"; import { reconstructTableQuery } from "../utils/editor"; import { shouldShowStatementSuccess } from "../utils/resultPresentation"; import { formatRowsForCopy, copyTextToClipboard } from "../utils/clipboard"; +import { + formatResultForExport, + getLoadedRowsExportLimit, +} from "../utils/resultExport"; import { serializePkKey, buildPkMap } from "../utils/dataGrid"; import { buildKeylessUpdatePlan, @@ -70,6 +74,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 +103,7 @@ import { removeOtherEntries, removeEntriesToRight, removeEntriesToLeft, + findActiveEntry, } from "../utils/multiResult"; import { extractQueryParams, @@ -264,6 +270,7 @@ export const Editor = ({ commandScopeId }: EditorProps) => { rowsProcessed: number; fileName: string; errorMessage?: string; + warningMessage?: string; }>({ isOpen: false, status: "exporting", @@ -468,6 +475,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 +1363,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, @@ -3283,6 +3308,59 @@ 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 loadedRowsLimit = getLoadedRowsExportLimit(multiResult); + const warningMessage = loadedRowsLimit + ? t("editor.exportLoadedRowsWarning", { + loaded: loadedRowsLimit.loadedRows.toLocaleString(), + total: loadedRowsLimit.totalRows.toLocaleString(), + }) + : undefined; + + 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, + errorMessage: undefined, + warningMessage, + }); + 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 }; @@ -3294,7 +3372,6 @@ export const Editor = ({ commandScopeId }: EditorProps) => { if (!query || !query.trim()) return; try { - const extension = format === "markdown" ? "md" : format; const filePath = await save({ filters: [ { @@ -3312,6 +3389,8 @@ export const Editor = ({ commandScopeId }: EditorProps) => { status: "exporting", rowsProcessed: 0, fileName: filePath.split(/[/\\]/).pop() || filePath, // Show only filename + errorMessage: undefined, + warningMessage: undefined, }); setExportMenuOpen(false); @@ -3823,7 +3902,7 @@ export const Editor = ({ commandScopeId }: EditorProps) => {