feat(window): support des fenêtres plugin-hébergées (backend)
É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>
This commit is contained in:
@ -9,10 +9,18 @@ use std::sync::Arc;
|
||||
use domain::ids::{TabId, WindowId};
|
||||
use std::collections::HashSet;
|
||||
|
||||
use domain::layout::{PersistedWindowKind, PersistedWindowState, WindowStateSnapshot, Workspace};
|
||||
use domain::ports::{IdGenerator, ProjectStore, WindowStateStore};
|
||||
use domain::layout::{
|
||||
PersistedPluginLayoutWindow, PersistedWindowKind, PersistedWindowState, WindowStateSnapshot,
|
||||
Workspace,
|
||||
};
|
||||
use domain::ports::{
|
||||
IdGenerator, PluginManifestValidator, PluginPackageStore, PluginRegistryStore, ProjectStore,
|
||||
WindowStateStore,
|
||||
};
|
||||
use domain::{PluginId, PluginLayoutType};
|
||||
|
||||
use crate::error::AppError;
|
||||
use crate::plugin::{ensure_runtime_plugin_layout_contribution, PluginRuntimeLayoutContribution};
|
||||
|
||||
/// Input for [`MoveTabToNewWindow::execute`].
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
@ -116,15 +124,27 @@ pub struct RestoreOpenWindowsOutput {
|
||||
pub struct RestoreOpenWindows {
|
||||
windows: Arc<dyn WindowStateStore>,
|
||||
_projects: Arc<dyn ProjectStore>,
|
||||
packages: Arc<dyn PluginPackageStore>,
|
||||
registry: Arc<dyn PluginRegistryStore>,
|
||||
validator: Arc<dyn PluginManifestValidator>,
|
||||
}
|
||||
|
||||
impl RestoreOpenWindows {
|
||||
/// Builds the use case from its ports.
|
||||
#[must_use]
|
||||
pub fn new(windows: Arc<dyn WindowStateStore>, projects: Arc<dyn ProjectStore>) -> Self {
|
||||
pub fn new(
|
||||
windows: Arc<dyn WindowStateStore>,
|
||||
projects: Arc<dyn ProjectStore>,
|
||||
packages: Arc<dyn PluginPackageStore>,
|
||||
registry: Arc<dyn PluginRegistryStore>,
|
||||
validator: Arc<dyn PluginManifestValidator>,
|
||||
) -> Self {
|
||||
Self {
|
||||
windows,
|
||||
_projects: projects,
|
||||
packages,
|
||||
registry,
|
||||
validator,
|
||||
}
|
||||
}
|
||||
|
||||
@ -154,9 +174,103 @@ impl RestoreOpenWindows {
|
||||
}
|
||||
windows.push(window);
|
||||
}
|
||||
PersistedWindowKind::PluginLayout => {
|
||||
let Some(surface) = &window.plugin_layout else {
|
||||
continue;
|
||||
};
|
||||
if self.validate_plugin_layout_surface(surface).await.is_ok() {
|
||||
windows.push(window);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(RestoreOpenWindowsOutput { windows })
|
||||
}
|
||||
|
||||
async fn validate_plugin_layout_surface(
|
||||
&self,
|
||||
surface: &PersistedPluginLayoutWindow,
|
||||
) -> Result<(), AppError> {
|
||||
ensure_runtime_plugin_layout_contribution(
|
||||
self.packages.as_ref(),
|
||||
self.registry.as_ref(),
|
||||
self.validator.as_ref(),
|
||||
&surface.plugin_id,
|
||||
&surface.layout_type,
|
||||
)
|
||||
.await
|
||||
.map(|_| ())
|
||||
}
|
||||
}
|
||||
|
||||
/// Input for [`OpenPluginLayoutWindow::execute`].
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct OpenPluginLayoutWindowInput {
|
||||
/// Provider plugin id.
|
||||
pub plugin_id: PluginId,
|
||||
/// Layout type declared by the provider plugin.
|
||||
pub layout_type: PluginLayoutType,
|
||||
/// Opaque plugin-owned initial/window state.
|
||||
pub state: serde_json::Value,
|
||||
}
|
||||
|
||||
/// Output of [`OpenPluginLayoutWindow::execute`].
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct OpenPluginLayoutWindowOutput {
|
||||
/// Runtime contribution that was validated.
|
||||
pub contribution: PluginRuntimeLayoutContribution,
|
||||
/// Persistable plugin layout surface.
|
||||
pub surface: PersistedPluginLayoutWindow,
|
||||
}
|
||||
|
||||
/// Validates a plugin layout contribution before a detached OS window hosts it.
|
||||
pub struct OpenPluginLayoutWindow {
|
||||
packages: Arc<dyn PluginPackageStore>,
|
||||
registry: Arc<dyn PluginRegistryStore>,
|
||||
validator: Arc<dyn PluginManifestValidator>,
|
||||
}
|
||||
|
||||
impl OpenPluginLayoutWindow {
|
||||
/// Builds the use case from plugin runtime ports.
|
||||
#[must_use]
|
||||
pub fn new(
|
||||
packages: Arc<dyn PluginPackageStore>,
|
||||
registry: Arc<dyn PluginRegistryStore>,
|
||||
validator: Arc<dyn PluginManifestValidator>,
|
||||
) -> Self {
|
||||
Self {
|
||||
packages,
|
||||
registry,
|
||||
validator,
|
||||
}
|
||||
}
|
||||
|
||||
/// Executes the validation.
|
||||
///
|
||||
/// # Errors
|
||||
/// [`AppError::Invalid`] when the plugin is inactive or does not declare the
|
||||
/// requested layout contribution; other errors bubble from plugin loading.
|
||||
pub async fn execute(
|
||||
&self,
|
||||
input: OpenPluginLayoutWindowInput,
|
||||
) -> Result<OpenPluginLayoutWindowOutput, AppError> {
|
||||
let contribution = ensure_runtime_plugin_layout_contribution(
|
||||
self.packages.as_ref(),
|
||||
self.registry.as_ref(),
|
||||
self.validator.as_ref(),
|
||||
&input.plugin_id,
|
||||
&input.layout_type,
|
||||
)
|
||||
.await?;
|
||||
let surface = PersistedPluginLayoutWindow {
|
||||
plugin_id: input.plugin_id,
|
||||
layout_type: input.layout_type,
|
||||
state: input.state,
|
||||
};
|
||||
Ok(OpenPluginLayoutWindowOutput {
|
||||
contribution,
|
||||
surface,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user