Install plugin packages

This commit is contained in:
2026-05-07 22:53:39 -04:00
parent ea41c4656d
commit 6bd2e3f69f
6 changed files with 500 additions and 4 deletions
+1 -1
View File
@@ -25,7 +25,7 @@ impl ElyShell {
"ely://downloads" => self.render_downloads_page(snapshot, cx),
"ely://history" => self.render_history_page(snapshot, cx),
"ely://archive" => self.render_archive_page(snapshot, cx),
"ely://settings/plugins" => self.render_plugins_page(snapshot),
"ely://settings/plugins" => self.render_plugins_page(snapshot, cx),
_ => render_default_page(tab),
}
}
@@ -1,12 +1,22 @@
use ely_browser_core::{BrowserSnapshot, InstalledPlugin, PluginAuditAction, PluginAuditEvent};
use ely_design_system::colors;
use gpui::{AnyElement, IntoElement, ParentElement, Styled, div, px, rgb};
use gpui_component::{StyledExt, scroll::ScrollableElement};
use gpui::prelude::FluentBuilder;
use gpui::{AnyElement, Context, IntoElement, ParentElement, Styled, div, px, rgb};
use gpui_component::{
IconName, Sizable, StyledExt,
button::{Button, ButtonVariants},
scroll::ScrollableElement,
};
use super::super::plugins::PendingPluginInstall;
use super::{ElyShell, render_canvas_surface};
impl ElyShell {
pub(super) fn render_plugins_page(&mut self, snapshot: &BrowserSnapshot) -> AnyElement {
pub(super) fn render_plugins_page(
&mut self,
snapshot: &BrowserSnapshot,
cx: &mut Context<Self>,
) -> AnyElement {
render_canvas_surface(
div()
.size_full()
@@ -51,12 +61,115 @@ impl ElyShell {
)),
),
)
.child(
div()
.flex()
.items_center()
.justify_between()
.gap_3()
.child(
div()
.text_xs()
.text_color(rgb(colors::MUTED))
.child("Install signed .rplug packages from local disk."),
)
.child(
Button::new("install-plugin-package")
.primary()
.small()
.icon(IconName::Plus)
.label("Install")
.tooltip("Install Plugin from File")
.on_click(cx.listener(|shell, _, window, cx| {
shell.choose_plugin_package(window, cx);
})),
),
)
.when_some(self.plugin_install_error.clone(), |this, message| {
this.child(render_plugin_install_error(message))
})
.when_some(self.pending_plugin_install.clone(), |this, pending| {
this.child(render_plugin_install_confirmation(&pending, cx))
})
.child(render_plugin_list(snapshot))
.child(render_plugin_audit_list(snapshot)),
)
}
}
fn render_plugin_install_error(message: String) -> AnyElement {
div()
.rounded_md()
.border_1()
.border_color(rgb(colors::ERROR))
.px_3()
.py_2()
.text_xs()
.text_color(rgb(colors::ERROR))
.child(message)
.into_any_element()
}
fn render_plugin_install_confirmation(
pending: &PendingPluginInstall,
cx: &mut Context<ElyShell>,
) -> AnyElement {
div()
.rounded_md()
.border_1()
.border_color(rgb(colors::ERROR))
.px_3()
.py_2()
.flex()
.items_center()
.justify_between()
.gap_3()
.child(
div()
.min_w_0()
.flex()
.flex_col()
.gap_1()
.child(
div()
.text_xs()
.font_semibold()
.text_color(rgb(colors::ERROR))
.child(format!("Confirm install for {}", pending.manifest().name())),
)
.child(
div()
.text_xs()
.truncate()
.text_color(rgb(colors::MUTED))
.child(high_risk_permissions_label(pending)),
),
)
.child(
div()
.flex()
.items_center()
.gap_2()
.child(
Button::new("cancel-plugin-install").ghost().xsmall().label("Cancel").on_click(
cx.listener(|shell, _, _, cx| {
shell.cancel_plugin_install(cx);
}),
),
)
.child(
Button::new("confirm-plugin-install")
.danger()
.xsmall()
.label("Confirm")
.on_click(cx.listener(|shell, _, _, cx| {
shell.confirm_plugin_install(cx);
})),
),
)
.into_any_element()
}
fn render_plugin_list(snapshot: &BrowserSnapshot) -> AnyElement {
if snapshot.installed_plugins.is_empty() {
return div()
@@ -180,3 +293,13 @@ fn plugin_audit_action_label(action: &PluginAuditAction) -> &'static str {
PluginAuditAction::Disabled => "Disabled",
}
}
fn high_risk_permissions_label(pending: &PendingPluginInstall) -> String {
let permissions = pending
.high_risk_permissions()
.iter()
.map(|permission| permission.as_str())
.collect::<Vec<_>>()
.join(", ");
format!("High risk permissions: {permissions}")
}
+6
View File
@@ -1,5 +1,6 @@
mod downloads;
mod internal_pages;
mod plugins;
mod render;
use ely_browser_core::{BrowserCore, InitialBrowserConfig};
@@ -8,6 +9,7 @@ use gpui::{App, AppContext, Context, Entity, FocusHandle, Focusable, Subscriptio
use gpui_component::input::{InputEvent, InputState, SelectAll};
use downloads::PendingDownloadFileAction;
use plugins::PendingPluginInstall;
use crate::{
CloseCurrentTab, FocusAddressBar, FocusCommandMode, OpenDownloads, OpenHistory, OpenNewTab,
@@ -28,6 +30,8 @@ pub struct ElyShell {
download_action_error: Option<String>,
download_clear_confirmation: bool,
download_security_confirmation: Option<PendingDownloadFileAction>,
plugin_install_error: Option<String>,
pending_plugin_install: Option<PendingPluginInstall>,
_command_subscription: Subscription,
}
@@ -86,6 +90,8 @@ impl ElyShell {
download_action_error: None,
download_clear_confirmation: false,
download_security_confirmation: None,
plugin_install_error: None,
pending_plugin_install: None,
_command_subscription: command_subscription,
}
}
+134
View File
@@ -0,0 +1,134 @@
use std::path::PathBuf;
use ely_domain::{PluginManifest, PluginPermission};
use gpui::{Context, PathPromptOptions, Window};
use crate::services::plugin_packages::{PluginPackageError, PluginPackageReader};
use super::{ElyShell, ShellState};
#[derive(Clone, Debug)]
pub(super) struct PendingPluginInstall {
manifest: PluginManifest,
high_risk_permissions: Vec<PluginPermission>,
}
impl PendingPluginInstall {
fn new(manifest: PluginManifest, high_risk_permissions: Vec<PluginPermission>) -> Self {
Self { manifest, high_risk_permissions }
}
pub(super) fn manifest(&self) -> &PluginManifest {
&self.manifest
}
pub(super) fn high_risk_permissions(&self) -> &[PluginPermission] {
&self.high_risk_permissions
}
}
impl ElyShell {
pub(super) fn choose_plugin_package(&mut self, window: &mut Window, cx: &mut Context<Self>) {
let prompt = cx.prompt_for_paths(PathPromptOptions {
files: false,
directories: true,
multiple: false,
prompt: Some("Select .rplug package".into()),
});
cx.spawn_in(window, async move |shell, window| {
let selected_path = match prompt.await {
Ok(Ok(Some(paths))) => paths.into_iter().next(),
Ok(Ok(None)) => None,
Ok(Err(error)) => {
_ = shell.update_in(window, |shell, _, cx| {
shell.plugin_install_error = Some(error.to_string());
cx.notify();
});
return;
}
Err(error) => {
_ = shell.update_in(window, |shell, _, cx| {
shell.plugin_install_error = Some(error.to_string());
cx.notify();
});
return;
}
};
let Some(path) = selected_path else {
return;
};
let result =
window.background_executor().spawn(async move { load_plugin_package(path) }).await;
_ = shell.update_in(window, |shell, _, cx| {
shell.handle_plugin_package_result(result, cx);
});
})
.detach();
}
pub(super) fn confirm_plugin_install(&mut self, cx: &mut Context<Self>) {
let Some(pending) = self.pending_plugin_install.take() else {
cx.notify();
return;
};
self.install_plugin_manifest(pending.manifest, true, cx);
}
pub(super) fn cancel_plugin_install(&mut self, cx: &mut Context<Self>) {
self.pending_plugin_install = None;
cx.notify();
}
fn handle_plugin_package_result(
&mut self,
result: Result<PluginManifest, PluginPackageError>,
cx: &mut Context<Self>,
) {
match result {
Ok(manifest) => self.install_plugin_manifest(manifest, false, cx),
Err(error) => {
self.plugin_install_error = Some(error.to_string());
self.pending_plugin_install = None;
cx.notify();
}
}
}
fn install_plugin_manifest(
&mut self,
manifest: PluginManifest,
high_risk_confirmed: bool,
cx: &mut Context<Self>,
) {
let high_risk_permissions = manifest.high_risk_permissions().cloned().collect::<Vec<_>>();
if !high_risk_confirmed && !high_risk_permissions.is_empty() {
self.pending_plugin_install =
Some(PendingPluginInstall::new(manifest, high_risk_permissions));
self.plugin_install_error = None;
cx.notify();
return;
}
let result = match &mut self.state {
ShellState::Ready(core) => core
.install_plugin(manifest, high_risk_confirmed)
.map(|_| ())
.map_err(|error| error.to_string()),
ShellState::StartupError(message) => Err(message.clone()),
};
self.plugin_install_error = result.err();
if self.plugin_install_error.is_none() {
self.pending_plugin_install = None;
}
cx.notify();
}
}
fn load_plugin_package(path: PathBuf) -> Result<PluginManifest, PluginPackageError> {
PluginPackageReader::read_directory_package(&path)
}