Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src-tauri/capabilities/default.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "default",
"description": "enables the default permissions",
"windows": ["main", "visual-explain", "er-diagram", "task-manager", "json-viewer-*"],
"windows": ["main", "visual-explain", "er-diagram", "task-manager", "json-viewer-*", "results-window-*"],
"permissions": [
"core:default",
"core:window:allow-set-title",
Expand Down
4 changes: 4 additions & 0 deletions src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ pub mod heartbeat;
pub mod heartbeat_tests;
pub mod json_viewer;
pub mod keychain_utils;
pub mod results_window;
pub mod k8s_tunnel;
pub mod log_commands;
pub mod logger;
Expand Down Expand Up @@ -178,6 +179,7 @@ pub fn run() {
))
.manage(explain_import::PendingExplainFile::default())
.manage(json_viewer::JsonViewerStore::default())
.manage(results_window::ResultsWindowStore::default())
.manage(query_history::QueryHistoryState::default())
.setup(move |app| {
// Read persisted config to know which external plugins are enabled.
Expand Down Expand Up @@ -472,6 +474,8 @@ pub fn run() {
json_viewer::open_json_viewer_window,
json_viewer::get_json_viewer_session,
json_viewer::complete_json_viewer_session,
results_window::open_results_window,
results_window::close_results_window,
// Task Manager
task_manager::get_process_list,
task_manager::get_system_stats,
Expand Down
142 changes: 142 additions & 0 deletions src-tauri/src/results_window.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
use std::sync::Mutex;

use tauri::{AppHandle, Emitter, Manager, WebviewUrl, WebviewWindowBuilder, WindowEvent};
use urlencoding::encode;

/// Persisted geometry so a re-opened detached results window restores where the
/// user last left it. Mirrors the pattern used by `json_viewer.rs`.
#[derive(Debug, Clone, Copy)]
pub struct WindowBounds {
pub x: i32,
pub y: i32,
pub width: u32,
pub height: u32,
}

#[derive(Default)]
pub struct ResultsWindowStore {
pub last_bounds: Mutex<Option<WindowBounds>>,
}

/// One detached results window per editor tab. The frontend keeps each window's
/// data in sync over Tauri events keyed by `tab_id`.
fn window_label(tab_id: &str) -> String {
format!("results-window-{}", tab_id)
}

/// Open (or focus) the detached results window for a given tab. Result data is
/// streamed from the main window via the `results-window:sync` event (keyed by
/// `tabId`), so no payload is passed here.
#[tauri::command]
pub async fn open_results_window(
app: AppHandle,
store: tauri::State<'_, ResultsWindowStore>,
tab_id: String,
title: Option<String>,
) -> Result<(), String> {
let label = window_label(&tab_id);

// If it already exists, just bring it to the front.
if let Some(window) = app.get_webview_window(&label) {
let _ = window.unminimize();
let _ = window.set_focus();
return Ok(());
}

let window_title = title.unwrap_or_else(|| "Query Results".to_string());

let remembered = store
.last_bounds
.lock()
.map_err(|e| format!("Failed to acquire bounds lock: {}", e))?
.clone();

let url = format!("/results-window?tab={}", encode(&tab_id));
let mut builder =
WebviewWindowBuilder::new(&app, &label, WebviewUrl::App(url.into()))
.title(&window_title)
.min_inner_size(500.0, 300.0)
.background_color(tauri::webview::Color(2, 6, 23, 255));

builder = match remembered {
Some(b) => builder
.inner_size(b.width as f64, b.height as f64)
.position(b.x as f64, b.y as f64),
None => builder.inner_size(900.0, 600.0).center(),
};

match builder.build() {
Err(e) => Err(format!("Failed to create results window: {}", e)),
Ok(window) => {
let app_handle = app.clone();
let captured_label = label.clone();
let captured_tab_id = tab_id.clone();
window.on_window_event(move |event| {
if let WindowEvent::CloseRequested { .. } = event {
if let Some(win) = app_handle.get_webview_window(&captured_label) {
if let (Ok(pos), Ok(size)) = (win.outer_position(), win.outer_size()) {
let store = app_handle.state::<ResultsWindowStore>();
if let Ok(mut bounds) = store.last_bounds.lock() {
*bounds = Some(WindowBounds {
x: pos.x,
y: pos.y,
width: size.width,
height: size.height,
});
};
}
}
// Let the main window re-attach this tab's results panel.
let _ = app_handle.emit(
"results-window:closed",
serde_json::json!({ "tabId": captured_tab_id }),
);
}
});
Ok(())
}
}
}

/// Programmatically close a tab's detached results window (used by the
/// "Re-attach" button and when the bound tab is closed).
#[tauri::command]
pub async fn close_results_window(app: AppHandle, tab_id: String) -> Result<(), String> {
if let Some(window) = app.get_webview_window(&window_label(&tab_id)) {
window
.close()
.map_err(|e| format!("Failed to close results window: {}", e))?;
}
Ok(())
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn bounds_round_trip() {
let store = ResultsWindowStore::default();
{
let mut bounds = store.last_bounds.lock().unwrap();
*bounds = Some(WindowBounds {
x: 100,
y: 200,
width: 800,
height: 600,
});
}
let bounds = store.last_bounds.lock().unwrap();
let b = bounds.unwrap();
assert_eq!(b.x, 100);
assert_eq!(b.y, 200);
assert_eq!(b.width, 800);
assert_eq!(b.height, 600);
}

#[test]
fn bounds_default_is_none() {
let store = ResultsWindowStore::default();
assert!(store.last_bounds.lock().unwrap().is_none());
}
}
5 changes: 5 additions & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { SchemaDiagramPage } from "./pages/SchemaDiagramPage";
import { TaskManagerPage } from "./pages/TaskManagerPage";
import { VisualExplainPage } from "./pages/VisualExplainPage";
import { JsonViewerPage } from "./pages/JsonViewerPage";
import { ResultsWindowPage } from "./pages/ResultsWindowPage";
import { ConnectionHealthMonitor } from "./components/ConnectionHealthMonitor";
import { EditorErrorBoundary } from "./components/ui/EditorErrorBoundary";
import { UpdateNotificationModal } from "./components/modals/UpdateNotificationModal";
Expand Down Expand Up @@ -138,6 +139,10 @@ export function App() {
<Route path="/task-manager" element={<TaskManagerPage />} />
<Route path="/visual-explain" element={<VisualExplainPage />} />
<Route path="/json-viewer" element={<JsonViewerPage />} />
<Route
path="/results-window"
element={<ResultsWindowPage />}
/>
</Routes>
</ConnectionLayoutProvider>
</PluginModalProvider>
Expand Down
9 changes: 9 additions & 0 deletions src/i18n/locales/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -828,6 +828,15 @@
"jumpToPage": "Klicken, um zur Seite zu springen",
"loadRowCount": "Zeilenanzahl laden",
"executePrompt": "Führe eine Abfrage aus, um Ergebnisse zu sehen",
"results": {
"minimize": "Minimieren",
"maximize": "Maximieren (Editor ausblenden)",
"restore": "Editor wiederherstellen",
"close": "Schließen",
"detach": "In separatem Fenster ablösen",
"detached": "Ergebnisse in separatem Fenster abgelöst",
"reattach": "Wieder andocken"
},
"tableRunPrompt": "Drücke Ausführen (Ctrl/Command+F5), um Tabellendaten zu laden",
"closeTab": "Tab schließen",
"closeOthers": "Andere Tabs schließen",
Expand Down
9 changes: 9 additions & 0 deletions src/i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -845,6 +845,15 @@
"loadRowCount": "Load row count",
"executePrompt": "Execute a query to see results",
"tableRunPrompt": "Press Run (Ctrl/Command+F5) to load table data",
"results": {
"minimize": "Minimize",
"maximize": "Maximize (hide editor)",
"restore": "Restore editor",
"close": "Close",
"detach": "Detach to a separate window",
"detached": "Results detached to a separate window",
"reattach": "Re-attach"
},
"closeTab": "Close Tab",
"closeOthers": "Close Other Tabs",
"closeRight": "Close Tabs to Right",
Expand Down
9 changes: 9 additions & 0 deletions src/i18n/locales/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -827,6 +827,15 @@
"jumpToPage": "Clic para ir a la página",
"loadRowCount": "Cargar conteo de filas",
"executePrompt": "Ejecuta una consulta para ver resultados",
"results": {
"minimize": "Minimizar",
"maximize": "Maximizar (ocultar editor)",
"restore": "Restaurar editor",
"close": "Cerrar",
"detach": "Separar en una ventana aparte",
"detached": "Resultados separados en una ventana aparte",
"reattach": "Volver a acoplar"
},
"closeTab": "Cerrar Pestaña",
"closeOthers": "Cerrar Otras Pestañas",
"closeRight": "Cerrar Pestañas a la Derecha",
Expand Down
9 changes: 9 additions & 0 deletions src/i18n/locales/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -828,6 +828,15 @@
"jumpToPage": "Cliquer pour aller à la page",
"loadRowCount": "Charger le nombre de lignes",
"executePrompt": "Exécutez une requête pour voir les résultats",
"results": {
"minimize": "Réduire",
"maximize": "Agrandir (masquer l’éditeur)",
"restore": "Restaurer l’éditeur",
"close": "Fermer",
"detach": "Détacher dans une fenêtre séparée",
"detached": "Résultats détachés dans une fenêtre séparée",
"reattach": "Rattacher"
},
"tableRunPrompt": "Appuyez sur Exécuter (Ctrl/Commande+F5) pour charger les données de la table",
"closeTab": "Fermer l’onglet",
"closeOthers": "Fermer les autres onglets",
Expand Down
9 changes: 9 additions & 0 deletions src/i18n/locales/it.json
Original file line number Diff line number Diff line change
Expand Up @@ -812,6 +812,15 @@
"loadRowCount": "Carica conteggio righe",
"executePrompt": "Esegui una query per vedere i risultati",
"tableRunPrompt": "Premi Esegui (Ctrl/Command+F5) per caricare i dati della tabella",
"results": {
"minimize": "Riduci a icona",
"maximize": "Massimizza (nascondi editor)",
"restore": "Ripristina editor",
"close": "Chiudi",
"detach": "Sgancia in una finestra separata",
"detached": "Risultati sganciati in una finestra separata",
"reattach": "Riaggancia"
},
"closeTab": "Chiudi scheda",
"closeOthers": "Chiudi altre schede",
"closeRight": "Chiudi schede a destra",
Expand Down
9 changes: 9 additions & 0 deletions src/i18n/locales/ja.json
Original file line number Diff line number Diff line change
Expand Up @@ -841,6 +841,15 @@
"jumpToPage": "クリックでページ移動",
"loadRowCount": "行数を読み込む",
"executePrompt": "クエリを実行すると結果が表示されます",
"results": {
"minimize": "最小化",
"maximize": "最大化(エディターを隠す)",
"restore": "エディターを復元",
"close": "閉じる",
"detach": "別ウィンドウに切り離す",
"detached": "結果を別ウィンドウに切り離しました",
"reattach": "元に戻す"
},
"tableRunPrompt": "Run (Ctrl/Command+F5) を押してテーブルデータを読み込んでください",
"closeTab": "タブを閉じる",
"closeOthers": "他のタブを閉じる",
Expand Down
9 changes: 9 additions & 0 deletions src/i18n/locales/ru.json
Original file line number Diff line number Diff line change
Expand Up @@ -823,6 +823,15 @@
"jumpToPage": "Нажмите для перехода на страницу",
"loadRowCount": "Загрузить количество строк",
"executePrompt": "Выполните запрос, чтобы увидеть результаты",
"results": {
"minimize": "Свернуть",
"maximize": "Развернуть (скрыть редактор)",
"restore": "Восстановить редактор",
"close": "Закрыть",
"detach": "Открепить в отдельное окно",
"detached": "Результаты откреплены в отдельное окно",
"reattach": "Прикрепить обратно"
},
"tableRunPrompt": "Нажмите «Запустить» (Ctrl/Command+F5) для загрузки данных таблицы",
"closeTab": "Закрыть вкладку",
"closeOthers": "Закрыть остальные вкладки",
Expand Down
9 changes: 9 additions & 0 deletions src/i18n/locales/zh.json
Original file line number Diff line number Diff line change
Expand Up @@ -796,6 +796,15 @@
"jumpToPage": "点击跳转到页面",
"loadRowCount": "加载行数",
"executePrompt": "执行查询以查看结果",
"results": {
"minimize": "最小化",
"maximize": "最大化(隐藏编辑器)",
"restore": "恢复编辑器",
"close": "关闭",
"detach": "分离到独立窗口",
"detached": "结果已分离到独立窗口",
"reattach": "重新附加"
},
"tableRunPrompt": "按运行(Ctrl/Command+F5)加载表数据",
"closeTab": "关闭标签",
"closeOthers": "关闭其他标签",
Expand Down
Loading