forked from 1b-agent/opencadstudio-upstream
feat(plugins): redesign plugin manager
This commit is contained in:
1 parent
d80637cda7
commit
4e25e88c31
11 files changed
+741
-74
No files matched your search
@@ -528,10 +528,19 @@ pub(super) struct OpenCADStudio {
|
||||
plugin_repos: Vec<String>,
|
||||
/// Add-repository text field in the Plugin Manager.
|
||||
plugin_repo_input: String,
|
||||
/// Live filter for installed and available plugin cards.
|
||||
plugin_search_input: String,
|
||||
/// Installable release tags fetched per linked repo (for the dropdown).
|
||||
repo_release_tags: rustc_hash::FxHashMap<String, Vec<String>>,
|
||||
/// The release tag currently selected per linked repo.
|
||||
repo_selected_tag: rustc_hash::FxHashMap<String, String>,
|
||||
/// Repository currently shown in the Plugin Manager detail pane.
|
||||
selected_plugin_repo: Option<String>,
|
||||
/// Parsed GitHub README content or the last fetch error, cached per repo.
|
||||
plugin_readmes:
|
||||
rustc_hash::FxHashMap<String, Result<iced::widget::markdown::Content, String>>,
|
||||
/// README requests in flight, used to render a deterministic loading state.
|
||||
plugin_readme_loading: rustc_hash::FxHashSet<String>,
|
||||
/// Last marketplace status / error line shown in the Plugin Manager.
|
||||
marketplace_status: String,
|
||||
/// PDSIZE text buffer for the Point Style (DDPTYPE) dialog.
|
||||
@@ -2059,6 +2068,8 @@ pub enum Message {
|
||||
// ── Plugin marketplace (install from a linked repo's releases) ─────────
|
||||
/// Edit the add-repository text field.
|
||||
PluginRepoInput(String),
|
||||
/// Filter installed and available plugin cards.
|
||||
PluginSearchInput(String),
|
||||
/// Link the repository currently in the text field.
|
||||
PluginRepoAdd,
|
||||
/// Unlink a repository.
|
||||
@@ -2079,8 +2090,14 @@ pub enum Message {
|
||||
PluginReleasesFetched(String, Result<Vec<String>, String>),
|
||||
/// Choose a release tag for a repo (`repo`, `tag`).
|
||||
PluginReleaseSelect(String, String),
|
||||
/// Select a plugin source and show its GitHub README in the detail pane.
|
||||
PluginReadmeSelect(String),
|
||||
/// A GitHub README fetch finished (`repo`, Markdown or error).
|
||||
PluginReadmeFetched(String, Result<String, String>),
|
||||
/// Install the selected release of `owner/repo`.
|
||||
PluginInstall(String),
|
||||
/// Install a specific newer release from an installed plugin card.
|
||||
PluginUpdate(String, String),
|
||||
/// Result of an install: the plugin id, or an error message.
|
||||
PluginInstalled(Result<String, String>),
|
||||
/// Delete an installed plugin's folder (effective next restart).
|
||||
@@ -2594,8 +2611,12 @@ impl OpenCADStudio {
|
||||
plugin_registry: Vec::new(),
|
||||
plugin_repos: Vec::new(),
|
||||
plugin_repo_input: String::new(),
|
||||
plugin_search_input: String::new(),
|
||||
repo_release_tags: rustc_hash::FxHashMap::default(),
|
||||
repo_selected_tag: rustc_hash::FxHashMap::default(),
|
||||
selected_plugin_repo: None,
|
||||
plugin_readmes: rustc_hash::FxHashMap::default(),
|
||||
plugin_readme_loading: rustc_hash::FxHashSet::default(),
|
||||
marketplace_status: String::new(),
|
||||
point_size_buf: String::new(),
|
||||
point_size_relative: true,
|
||||
|
||||
+16
-1
@@ -295,6 +295,21 @@ impl OpenCADStudio {
|
||||
Task::none()
|
||||
}
|
||||
|
||||
/// Background task: fetch a repository README from its default branch.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub(in crate::app) fn fetch_plugin_readme_task(&self, repo: String) -> Task<Message> {
|
||||
let label = repo.clone();
|
||||
Task::perform(
|
||||
async move { crate::plugin::marketplace::fetch_readme(&repo) },
|
||||
move |result| Message::PluginReadmeFetched(label, result),
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub(in crate::app) fn fetch_plugin_readme_task(&self, _repo: String) -> Task<Message> {
|
||||
Task::none()
|
||||
}
|
||||
|
||||
/// Background task: download and install the `tag` release of `owner/repo`.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub(in crate::app) fn install_task(&self, repo: String, tag: String) -> Task<Message> {
|
||||
@@ -305,7 +320,7 @@ impl OpenCADStudio {
|
||||
.into_iter()
|
||||
.find(|r| r.tag == tag)
|
||||
.ok_or_else(|| format!("release {tag} not found"))?;
|
||||
crate::plugin::marketplace::install(&rel)
|
||||
crate::plugin::marketplace::install(&rel, &repo)
|
||||
},
|
||||
Message::PluginInstalled,
|
||||
)
|
||||
|
||||
+126
-12
@@ -3951,17 +3951,42 @@ impl OpenCADStudio {
|
||||
// Refresh the on-disk external-plugin list each time the manager
|
||||
// opens so newly dropped-in packages show up.
|
||||
self.external_plugins = crate::plugin::external::discover();
|
||||
self.marketplace_status.clear();
|
||||
self.active_modal = Some(super::ModalKind::PluginManager);
|
||||
// Fetch the curated registry and release lists for linked repos.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
{
|
||||
let mut tasks = vec![self.fetch_registry_task()];
|
||||
let release_repos: rustc_hash::FxHashSet<String> = self
|
||||
.plugin_repos
|
||||
.iter()
|
||||
.cloned()
|
||||
.chain(
|
||||
self.external_plugins
|
||||
.iter()
|
||||
.filter_map(|plugin| plugin.repository.clone()),
|
||||
)
|
||||
.collect();
|
||||
tasks.extend(
|
||||
self.plugin_repos
|
||||
.clone()
|
||||
release_repos
|
||||
.into_iter()
|
||||
.map(|r| self.fetch_releases_task(r)),
|
||||
);
|
||||
if self.selected_plugin_repo.is_none() {
|
||||
self.selected_plugin_repo = self
|
||||
.external_plugins
|
||||
.iter()
|
||||
.find_map(|plugin| plugin.repository.clone())
|
||||
.or_else(|| self.plugin_registry.first().map(|entry| entry.repo.clone()))
|
||||
.or_else(|| self.plugin_repos.first().cloned());
|
||||
}
|
||||
if let Some(repo) = self.selected_plugin_repo.clone() {
|
||||
if !self.plugin_readmes.contains_key(&repo)
|
||||
&& self.plugin_readme_loading.insert(repo.clone())
|
||||
{
|
||||
tasks.push(self.fetch_plugin_readme_task(repo));
|
||||
}
|
||||
}
|
||||
return Task::batch(tasks);
|
||||
}
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
@@ -3985,26 +4010,66 @@ impl OpenCADStudio {
|
||||
self.plugin_repo_input = s;
|
||||
Task::none()
|
||||
}
|
||||
Message::PluginSearchInput(s) => {
|
||||
self.plugin_search_input = s;
|
||||
Task::none()
|
||||
}
|
||||
Message::PluginRepoAdd => {
|
||||
let repo = self
|
||||
.plugin_repo_input
|
||||
.trim()
|
||||
.trim_start_matches("https://github.com/")
|
||||
.trim_end_matches('/')
|
||||
.to_string();
|
||||
if repo.is_empty() || self.plugin_repos.contains(&repo) {
|
||||
let Some(repo) =
|
||||
crate::plugin::external::normalize_repository(&self.plugin_repo_input)
|
||||
else {
|
||||
self.marketplace_status =
|
||||
"Enter a GitHub URL or repository in owner/repo format.".to_string();
|
||||
return Task::none();
|
||||
};
|
||||
if self.plugin_repos.contains(&repo)
|
||||
|| self.plugin_registry.iter().any(|entry| entry.repo == repo)
|
||||
{
|
||||
self.marketplace_status = format!("{repo} is already in the catalog.");
|
||||
self.selected_plugin_repo = Some(repo.clone());
|
||||
if !self.plugin_readmes.contains_key(&repo)
|
||||
&& self.plugin_readme_loading.insert(repo.clone())
|
||||
{
|
||||
return self.fetch_plugin_readme_task(repo);
|
||||
}
|
||||
return Task::none();
|
||||
}
|
||||
if self
|
||||
.external_plugins
|
||||
.iter()
|
||||
.any(|plugin| plugin.repository.as_deref() == Some(repo.as_str()))
|
||||
{
|
||||
self.marketplace_status = format!("{repo} is already installed.");
|
||||
self.selected_plugin_repo = Some(repo.clone());
|
||||
if !self.plugin_readmes.contains_key(&repo)
|
||||
&& self.plugin_readme_loading.insert(repo.clone())
|
||||
{
|
||||
return self.fetch_plugin_readme_task(repo);
|
||||
}
|
||||
return Task::none();
|
||||
}
|
||||
self.plugin_repos.push(repo.clone());
|
||||
self.plugin_repo_input.clear();
|
||||
self.persist_settings_if_changed();
|
||||
self.marketplace_status = format!("Fetching releases for {repo}…");
|
||||
self.fetch_releases_task(repo)
|
||||
self.selected_plugin_repo = Some(repo.clone());
|
||||
self.plugin_readmes.remove(&repo);
|
||||
self.plugin_readme_loading.insert(repo.clone());
|
||||
Task::batch(vec![
|
||||
self.fetch_releases_task(repo.clone()),
|
||||
self.fetch_plugin_readme_task(repo),
|
||||
])
|
||||
}
|
||||
Message::PluginRepoRemove(repo) => {
|
||||
self.plugin_repos.retain(|r| r != &repo);
|
||||
self.repo_release_tags.remove(&repo);
|
||||
self.repo_selected_tag.remove(&repo);
|
||||
if self.selected_plugin_repo.as_deref() == Some(repo.as_str())
|
||||
&& !self.plugin_registry.iter().any(|entry| entry.repo == repo)
|
||||
{
|
||||
self.selected_plugin_repo =
|
||||
self.plugin_registry.first().map(|entry| entry.repo.clone());
|
||||
}
|
||||
self.persist_settings_if_changed();
|
||||
Task::none()
|
||||
}
|
||||
@@ -4012,11 +4077,32 @@ impl OpenCADStudio {
|
||||
// Fetch releases for every curated repo so the dropdowns fill in.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
{
|
||||
let tasks: Vec<_> = entries
|
||||
if self.selected_plugin_repo.is_none() {
|
||||
self.selected_plugin_repo = self
|
||||
.external_plugins
|
||||
.iter()
|
||||
.find_map(|plugin| {
|
||||
plugin.repository.clone().or_else(|| {
|
||||
entries
|
||||
.iter()
|
||||
.find(|entry| entry.name.eq_ignore_ascii_case(&plugin.name))
|
||||
.map(|entry| entry.repo.clone())
|
||||
})
|
||||
})
|
||||
.or_else(|| entries.first().map(|entry| entry.repo.clone()));
|
||||
}
|
||||
let mut tasks: Vec<_> = entries
|
||||
.iter()
|
||||
.map(|e| self.fetch_releases_task(e.repo.clone()))
|
||||
.collect();
|
||||
self.plugin_registry = entries;
|
||||
if let Some(repo) = self.selected_plugin_repo.clone() {
|
||||
if !self.plugin_readmes.contains_key(&repo)
|
||||
&& self.plugin_readme_loading.insert(repo.clone())
|
||||
{
|
||||
tasks.push(self.fetch_plugin_readme_task(repo));
|
||||
}
|
||||
}
|
||||
return Task::batch(tasks);
|
||||
}
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
@@ -4073,7 +4159,10 @@ impl OpenCADStudio {
|
||||
.entry(repo.clone())
|
||||
.or_insert_with(|| first.clone());
|
||||
}
|
||||
self.marketplace_status = format!("{repo}: {} installable release(s)", tags.len());
|
||||
if self.marketplace_status == format!("Fetching releases for {repo}…") {
|
||||
self.marketplace_status =
|
||||
format!("Repository added. {} installable release(s) found.", tags.len());
|
||||
}
|
||||
self.repo_release_tags.insert(repo, tags);
|
||||
Task::none()
|
||||
}
|
||||
@@ -4085,6 +4174,27 @@ impl OpenCADStudio {
|
||||
self.repo_selected_tag.insert(repo, tag);
|
||||
Task::none()
|
||||
}
|
||||
Message::PluginReadmeSelect(repo) => {
|
||||
self.selected_plugin_repo = Some(repo.clone());
|
||||
if self.plugin_readme_loading.contains(&repo) {
|
||||
return Task::none();
|
||||
}
|
||||
if matches!(self.plugin_readmes.get(&repo), Some(Ok(_))) {
|
||||
return Task::none();
|
||||
}
|
||||
// A second click on an error state acts as retry.
|
||||
self.plugin_readmes.remove(&repo);
|
||||
self.plugin_readme_loading.insert(repo.clone());
|
||||
self.fetch_plugin_readme_task(repo)
|
||||
}
|
||||
Message::PluginReadmeFetched(repo, result) => {
|
||||
self.plugin_readme_loading.remove(&repo);
|
||||
self.plugin_readmes.insert(
|
||||
repo,
|
||||
result.map(|source| iced::widget::markdown::Content::parse(&source)),
|
||||
);
|
||||
Task::none()
|
||||
}
|
||||
Message::PluginInstall(repo) => {
|
||||
let Some(tag) = self.repo_selected_tag.get(&repo).cloned() else {
|
||||
return Task::none();
|
||||
@@ -4092,6 +4202,10 @@ impl OpenCADStudio {
|
||||
self.marketplace_status = format!("Installing {repo} {tag}…");
|
||||
self.install_task(repo, tag)
|
||||
}
|
||||
Message::PluginUpdate(repo, tag) => {
|
||||
self.marketplace_status = format!("Updating {repo} to {tag}…");
|
||||
self.install_task(repo, tag)
|
||||
}
|
||||
Message::PluginInstalled(Ok(id)) => {
|
||||
self.marketplace_status = format!("Installed '{id}'. Restart to load it.");
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
|
||||
+1
-1
@@ -1537,7 +1537,7 @@ impl OpenCADStudio {
|
||||
About => (440, 360),
|
||||
Shortcuts => (720, 520),
|
||||
Options => (480, 190),
|
||||
PluginManager => (520, 460),
|
||||
PluginManager => (940, 600),
|
||||
UpdateNotice => (560, 460),
|
||||
Layers => (900, 360),
|
||||
Plot => (760, 540),
|
||||
|
||||
@@ -79,14 +79,19 @@ impl OpenCADStudio {
|
||||
crate::ui::window::plugin_manager::MarketView {
|
||||
registry: &self.plugin_registry,
|
||||
input: &self.plugin_repo_input,
|
||||
search: &self.plugin_search_input,
|
||||
repos: &self.plugin_repos,
|
||||
release_tags: &self.repo_release_tags,
|
||||
selected_tag: &self.repo_selected_tag,
|
||||
selected_repo: self.selected_plugin_repo.as_deref(),
|
||||
readmes: &self.plugin_readmes,
|
||||
readme_loading: &self.plugin_readme_loading,
|
||||
status: &self.marketplace_status,
|
||||
},
|
||||
&self.active_theme,
|
||||
),
|
||||
520,
|
||||
460,
|
||||
940,
|
||||
600,
|
||||
),
|
||||
super::super::ModalKind::UpdateNotice => {
|
||||
let latest = self.update_notice_version.as_deref().unwrap_or("?");
|
||||
|
||||
Reference in new issue
Block a user