Skip to content
Merged
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: 2 additions & 0 deletions src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ pointy = "0.4"
url = "2.5.7"
filetime = "0.2"
pmtiles = { version = "0.19", default-features = false, features = ["mmap-async-tokio", "http-async", "write", "reqwest-rustls"] }
[target.'cfg(target_os = "linux")'.dependencies]
gtk = "0.18.2"

[dev-dependencies]
tempfile = "3"
51 changes: 48 additions & 3 deletions src-tauri/src/commands/archive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ use std::backtrace::Backtrace;
use std::path::{Path, PathBuf};
use serde::{Serialize};
use tauri::{Emitter, Manager};
#[cfg(target_os = "linux")]
use gtk::prelude::{BinExt, Cast, GtkWindowExt, HeaderBarExt};
#[cfg(target_os = "linux")]
use gtk::{EventBox, HeaderBar};

use crate::dwca::Archive;
use crate::error::{ChuckError, Result};
Expand Down Expand Up @@ -99,7 +103,11 @@ pub(crate) fn get_archives_dir<R: tauri::Runtime>(app: tauri::AppHandle<R>) -> R
}

#[tauri::command]
pub async fn open_archive(app: tauri::AppHandle, path: String) -> Result<ArchiveInfo> {
pub async fn open_archive(
app: tauri::AppHandle,
window: tauri::WebviewWindow,
path: String,
) -> Result<ArchiveInfo> {
use std::sync::mpsc;

let base_dir = get_archives_dir(app.clone())?;
Expand Down Expand Up @@ -150,6 +158,10 @@ pub async fn open_archive(app: tauri::AppHandle, path: String) -> Result<Archive
)
.map_err(|e| ChuckError::Tauri(e.to_string()))?;

// Set window title from Rust for reliable cross-platform behavior.
// The JS setTitle() call does not work on Linux (Ubuntu).
set_archive_window_title(&window, &info);

Ok(info)
}
Ok(Err(e)) => {
Expand Down Expand Up @@ -191,16 +203,49 @@ pub fn get_opened_file(app: tauri::AppHandle) -> Option<String> {
state.0.lock().ok()?.take()
}

fn set_archive_window_title(window: &tauri::WebviewWindow, info: &ArchiveInfo) {
let title = format!("{} \u{2013} {} occurrences", info.name, info.core_count);
if let Err(e) = window.set_title(&title) {
log::warn!("Failed to set window title: {e}");
}

// In GTK (default in Ubuntu), the above method of setting the window
// title doesn't change the HeaderBar the user actually sees, so we're
// using the approach described in
// https://github.com/tauri-apps/tauri/issues/13749#issuecomment-3027697386.
// This closure / ? approach is to avoid a ton of unwraps.
#[cfg(target_os = "linux")]
(|| -> Option<()> {
let header_bar = window.gtk_window().ok()?
.titlebar()?
.downcast::<EventBox>().ok()?
.child()?
.downcast::<HeaderBar>().ok()?;
header_bar.set_title(Some(&title));
Some(())
})();
}

#[tauri::command]
pub fn current_archive(app: tauri::AppHandle) -> Result<ArchiveInfo> {
Archive::current(&get_archives_dir(app)?).map_err(|e| {
let info = Archive::current(&get_archives_dir(app.clone())?).map_err(|e| {
log::error!(
"Failed to get current archive: {}, backtrace: {}",
e,
Backtrace::capture()
);
e
})?.info()
})?.info()?;
// Set window title in a spawned task to avoid interfering with the command response.
// Using WebviewWindow as a command parameter breaks the return value in Tauri 2.
let app_clone = app;
let info_clone = info.clone();
tauri::async_runtime::spawn(async move {
if let Some(window) = app_clone.get_webview_window("main") {
set_archive_window_title(&window, &info_clone);
}
});
Ok(info)
}

#[tauri::command]
Expand Down
7 changes: 2 additions & 5 deletions src/routes/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ import {
exportDwca,
exportKml,
getCurrentWebview,
getCurrentWindow,
invoke,
listen,
showOpenDialog,
Expand Down Expand Up @@ -316,12 +315,10 @@ async function handleSearchChange(params: SearchParams) {
}
}

// Set window title and initialize filtered total when archive loads
// Initialize filtered total and trigger initial search when archive loads.
// Window title is set from Rust (see open_archive / current_archive commands).
$effect(() => {
if (archive) {
getCurrentWindow().setTitle(
`${archive.name} – ${archive.coreCount} occurrences`,
);
filteredTotal = archive.coreCount;

// Set default sort to Core ID
Expand Down
11 changes: 0 additions & 11 deletions tests/app.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,17 +55,6 @@ test.describe('Frontend', () => {
expect(rows.length).toBeGreaterThan(0);
});

test('should display archive info in window title', async ({ page }) => {
// This tests that the mock window.setTitle is being called
// In a real app, we'd check the actual window title, but in our mock
// we just verify the function is called (checked via console logs)

await openArchive(page);

// Just verify the main content loaded, which triggers setTitle
await expect(page.locator('main')).toBeVisible();
});

test('should display sort controls in filters sidebar', async ({ page }) => {
await openArchive(page);

Expand Down