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: 1 addition & 1 deletion Backend/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": "Capability for the main window",
"windows": ["main"],
"windows": ["main", "output-log"],
"permissions": [
"core:default",
"opener:default"
Expand Down
14 changes: 14 additions & 0 deletions Backend/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use tauri::{Emitter, Manager};
use std::sync::Arc;
use openvcs_core::{backend_id, BackendId};
use tauri_plugin_updater::UpdaterExt;
use tauri::WindowEvent;

mod utilities;
mod tauri_commands;
Expand All @@ -12,6 +13,7 @@ mod settings;
mod repo_settings;
mod logging;
mod themes;
mod output_log;

#[cfg(feature = "with-git")]
#[allow(unused_imports)]
Expand Down Expand Up @@ -103,6 +105,14 @@ pub fn run() {

Ok(())
})
.on_window_event(|window, event| {
// If the main window is closed, exit the app even if auxiliary windows are open.
if window.label() == "main" {
if let WindowEvent::CloseRequested { .. } = event {
window.app_handle().exit(0);
}
}
})
.plugin(tauri_plugin_opener::init())
.plugin(tauri_plugin_dialog::init())
.plugin(tauri_plugin_updater::Builder::new().build())
Expand Down Expand Up @@ -177,9 +187,13 @@ fn build_invoke_handler<R: tauri::Runtime>() -> impl Fn(tauri::ipc::Invoke<R>) -
tauri_commands::set_global_settings,
tauri_commands::get_repo_settings,
tauri_commands::set_repo_settings,
tauri_commands::ssh_trust_host,
tauri_commands::updater_install_now,
tauri_commands::open_repo_dotfile,
tauri_commands::open_docs,
tauri_commands::open_output_log_window,
tauri_commands::get_output_log,
tauri_commands::clear_output_log,
tauri_commands::exit_app,
tauri_commands::check_for_updates,
]
Expand Down
24 changes: 24 additions & 0 deletions Backend/src/output_log.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum OutputLevel {
Info,
Warn,
Error,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OutputLogEntry {
pub ts_ms: i64,
pub level: OutputLevel,
pub source: String,
pub message: String,
}

impl OutputLogEntry {
pub fn new(ts_ms: i64, level: OutputLevel, source: impl Into<String>, message: impl Into<String>) -> Self {
Self { ts_ms, level, source: source.into(), message: message.into() }
}
}

12 changes: 11 additions & 1 deletion Backend/src/repo_settings.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RemoteConfig {
pub name: String,
pub url: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RepoConfig {
/// Repository-local user.name (if set)
Expand All @@ -11,10 +17,14 @@ pub struct RepoConfig {
/// Convenience: the URL for the 'origin' remote (if present)
#[serde(skip_serializing_if = "Option::is_none")]
pub origin_url: Option<String>,
/// Desired configured remotes (name + url). When provided, `set_repo_settings` will
/// ensure these exist and remove any others.
#[serde(skip_serializing_if = "Option::is_none")]
pub remotes: Option<Vec<RemoteConfig>>,
}

impl Default for RepoConfig {
fn default() -> Self {
Self { user_name: None, user_email: None, origin_url: None }
Self { user_name: None, user_email: None, origin_url: None, remotes: None }
}
}
25 changes: 25 additions & 0 deletions Backend/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use parking_lot::RwLock;
use openvcs_core::Repo;
use crate::settings::AppConfig;
use crate::repo_settings::RepoConfig;
use crate::output_log::OutputLogEntry;
use directories::ProjectDirs;
use serde::{Deserialize, Serialize};

Expand All @@ -24,6 +25,9 @@ pub struct AppState {
/// Repository-specific settings (in-memory for now)
repo_config: RwLock<RepoConfig>,

/// In-memory output log (VCS commands/output)
output_log: RwLock<Vec<OutputLogEntry>>,

/// Currently open repository
current_repo: RwLock<Option<Arc<Repo>>>,

Expand All @@ -37,6 +41,7 @@ impl AppState {
let s = Self {
config: RwLock::new(cfg),
repo_config: RwLock::new(RepoConfig::default()),
output_log: RwLock::new(Vec::new()),
..Default::default()
};
// Attempt to load recents from app data (not config dir)
Expand Down Expand Up @@ -89,6 +94,26 @@ impl AppState {
Ok(())
}

/* -------- output log -------- */

pub fn push_output_log(&self, entry: OutputLogEntry) {
const MAX: usize = 2000;
let mut log = self.output_log.write();
log.push(entry);
if log.len() > MAX {
let extra = log.len() - MAX;
log.drain(0..extra);
}
}

pub fn output_log(&self) -> Vec<OutputLogEntry> {
self.output_log.read().clone()
}

pub fn clear_output_log(&self) {
self.output_log.write().clear();
}

/// Transactional edit: clone → mutate → validate → save → swap.
/// Keep the closure FAST (no blocking/async in here).
pub fn edit_config<F>(&self, f: F) -> Result<(), String>
Expand Down
4 changes: 4 additions & 0 deletions Backend/src/tauri_commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ mod settings;
mod shared;
mod stash;
mod status;
mod ssh;
mod output_log;
mod updater;
mod themes;

Expand All @@ -22,6 +24,8 @@ pub use remotes::*;
pub use settings::*;
pub use stash::*;
pub use status::*;
pub use ssh::*;
pub use output_log::*;
pub use updater::*;
pub use themes::*;

Expand Down
34 changes: 34 additions & 0 deletions Backend/src/tauri_commands/output_log.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
use tauri::{Manager, Runtime, Window, WebviewUrl, WebviewWindowBuilder};

use crate::output_log::OutputLogEntry;
use crate::state::AppState;

#[tauri::command]
pub fn get_output_log(state: tauri::State<'_, AppState>) -> Vec<OutputLogEntry> {
state.output_log()
}

#[tauri::command]
pub fn clear_output_log(state: tauri::State<'_, AppState>) {
state.clear_output_log();
}

#[tauri::command]
pub fn open_output_log_window<R: Runtime>(window: Window<R>) -> Result<(), String> {
let app = window.app_handle().clone();
if let Some(existing) = app.get_webview_window("output-log") {
let _ = existing.show();
let _ = existing.set_focus();
return Ok(());
}

WebviewWindowBuilder::new(&app, "output-log", WebviewUrl::App("index.html?view=output-log".into()))
.title("Output Log")
.inner_size(900.0, 600.0)
.resizable(true)
.build()
.map_err(|e| e.to_string())?;

Ok(())
}

Loading
Loading