Étend le port/usecases window et layout.customPluginLayout pour ouvrir et piloter des fenêtres OS hébergeant un layout plugin, en réutilisant contributes.layouts plutôt qu'un système de fenêtres parallèle. Câble la commande Tauri et l'état app-tauri correspondants. Cargo test -p domain --test window : 5/5 Cargo test -p application --test window_usecases : 7/7 Cargo test -p infrastructure --test window_state_store : 1/1 Cargo test -p app-tauri view_window_tests : 8/8 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
165 lines
6.2 KiB
Rust
165 lines
6.2 KiB
Rust
//! Tauri-managed application state.
|
|
//!
|
|
//! The non-Tauri composition root lives in `backend::BackendCore`. This type is
|
|
//! the desktop driving-adapter envelope: it adds Tauri transport bridges and
|
|
//! OS-window focus state, then dereferences to the shared core so existing
|
|
//! commands keep their field/method access unchanged.
|
|
|
|
use std::collections::HashMap;
|
|
use std::ops::Deref;
|
|
use std::path::PathBuf;
|
|
use std::sync::{Arc, Mutex};
|
|
|
|
use backend::BackendCore;
|
|
use domain::PersistedPluginLayoutWindow;
|
|
use infrastructure::{TemplateToolProvider, TicketToolProvider};
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
use crate::chat::ChatBridge;
|
|
use crate::embedded_server::EmbeddedServerController;
|
|
use crate::pty::PtyBridge;
|
|
use crate::templates::AppTemplateToolProvider;
|
|
use crate::tickets::AppTicketToolProvider;
|
|
|
|
pub use backend::ResumeContext;
|
|
|
|
/// Focused project snapshot shared with detached panel windows.
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct FocusedProjectDto {
|
|
/// Project id as a string.
|
|
pub id: String,
|
|
/// Project display name.
|
|
pub name: String,
|
|
/// Absolute project root.
|
|
pub root: String,
|
|
}
|
|
|
|
/// Desktop adapter state managed by Tauri.
|
|
pub struct AppState {
|
|
core: Arc<BackendCore>,
|
|
/// Generic PTY to Tauri Channel bridge registry.
|
|
pub pty_bridge: Arc<PtyBridge>,
|
|
/// Structured reply to Tauri Channel bridge registry.
|
|
pub chat_bridge: Arc<ChatBridge>,
|
|
/// Desktop-owned embedded HTTP server lifecycle manager.
|
|
pub embedded_server: Arc<EmbeddedServerController>,
|
|
/// Project currently focused by the main window; panel-only windows follow it.
|
|
focused_project: Mutex<Option<FocusedProjectDto>>,
|
|
/// Detached plugin-layout surfaces keyed by Tauri window label.
|
|
plugin_window_surfaces: Mutex<HashMap<String, PersistedPluginLayoutWindow>>,
|
|
}
|
|
|
|
impl AppState {
|
|
/// Builds the shared backend core and wraps it with desktop-only transport
|
|
/// state. `app_data_dir` is resolved by the Tauri adapter before crossing
|
|
/// into the backend core.
|
|
#[must_use]
|
|
pub fn build(app_data_dir: PathBuf) -> Self {
|
|
Self::build_with_resource_dir(app_data_dir, None)
|
|
}
|
|
|
|
/// Builds the shared backend core with the Tauri bundle resource directory
|
|
/// available to desktop-only adapters.
|
|
#[must_use]
|
|
pub fn build_with_resource_dir(app_data_dir: PathBuf, resource_dir: Option<PathBuf>) -> Self {
|
|
let core = Arc::new(BackendCore::build(app_data_dir.clone()));
|
|
core.ticket_tool_binder
|
|
.bind(Arc::new(AppTicketToolProvider {
|
|
create: Arc::clone(&core.create_issue),
|
|
read: Arc::clone(&core.read_issue),
|
|
list: Arc::clone(&core.list_issues),
|
|
update: Arc::clone(&core.update_issue),
|
|
bulk_update_status: Arc::clone(&core.bulk_update_issue_status),
|
|
bulk_update_priority: Arc::clone(&core.bulk_update_issue_priority),
|
|
bulk_delete: Arc::clone(&core.bulk_delete_issues),
|
|
read_carnet: Arc::clone(&core.read_issue_carnet),
|
|
update_carnet: Arc::clone(&core.update_issue_carnet),
|
|
add_attachment: Arc::clone(&core.add_issue_attachment),
|
|
read_attachment: Arc::clone(&core.read_issue_attachment),
|
|
mark_attachment_summarized: Arc::clone(&core.mark_issue_attachment_summarized),
|
|
link: Arc::clone(&core.link_issues),
|
|
unlink: Arc::clone(&core.unlink_issues),
|
|
list_sprints: Arc::clone(&core.list_sprints),
|
|
}) as Arc<dyn TicketToolProvider>);
|
|
core.template_tool_binder
|
|
.bind(Arc::new(AppTemplateToolProvider {
|
|
create: Arc::clone(&core.create_template),
|
|
read: Arc::clone(&core.read_template),
|
|
list: Arc::clone(&core.list_templates),
|
|
update: Arc::clone(&core.update_template),
|
|
delete: Arc::clone(&core.delete_template),
|
|
}) as Arc<dyn TemplateToolProvider>);
|
|
|
|
Self {
|
|
core,
|
|
pty_bridge: Arc::new(PtyBridge::new()),
|
|
chat_bridge: Arc::new(ChatBridge::new()),
|
|
embedded_server: Arc::new(EmbeddedServerController::with_resource_dir(
|
|
app_data_dir,
|
|
resource_dir,
|
|
)),
|
|
focused_project: Mutex::new(None),
|
|
plugin_window_surfaces: Mutex::new(HashMap::new()),
|
|
}
|
|
}
|
|
|
|
/// Clones the shared backend core for adapter components that must share
|
|
/// the desktop composition root.
|
|
#[must_use]
|
|
pub fn core(&self) -> Arc<BackendCore> {
|
|
Arc::clone(&self.core)
|
|
}
|
|
|
|
/// Updates the focused project state. `None` means no project is focused/open.
|
|
pub fn set_focused_project(&self, project: Option<FocusedProjectDto>) {
|
|
*self
|
|
.focused_project
|
|
.lock()
|
|
.expect("focused project mutex poisoned") = project;
|
|
}
|
|
|
|
/// Returns the current focused project, if any.
|
|
#[must_use]
|
|
pub fn get_focused_project(&self) -> Option<FocusedProjectDto> {
|
|
self.focused_project
|
|
.lock()
|
|
.expect("focused project mutex poisoned")
|
|
.clone()
|
|
}
|
|
|
|
/// Records the plugin layout surface hosted by a detached window.
|
|
pub fn set_plugin_window_surface(&self, label: String, surface: PersistedPluginLayoutWindow) {
|
|
self.plugin_window_surfaces
|
|
.lock()
|
|
.expect("plugin window surface mutex poisoned")
|
|
.insert(label, surface);
|
|
}
|
|
|
|
/// Reads the plugin layout surface hosted by a detached window.
|
|
#[must_use]
|
|
pub fn get_plugin_window_surface(&self, label: &str) -> Option<PersistedPluginLayoutWindow> {
|
|
self.plugin_window_surfaces
|
|
.lock()
|
|
.expect("plugin window surface mutex poisoned")
|
|
.get(label)
|
|
.cloned()
|
|
}
|
|
|
|
/// Removes a detached plugin layout surface.
|
|
pub fn clear_plugin_window_surface(&self, label: &str) {
|
|
self.plugin_window_surfaces
|
|
.lock()
|
|
.expect("plugin window surface mutex poisoned")
|
|
.remove(label);
|
|
}
|
|
}
|
|
|
|
impl Deref for AppState {
|
|
type Target = BackendCore;
|
|
|
|
fn deref(&self) -> &Self::Target {
|
|
&self.core
|
|
}
|
|
}
|