From 6fd1176661433d209dd02a4e182e2e316fc5530b Mon Sep 17 00:00:00 2001 From: SoftFever Date: Fri, 3 Jul 2026 14:57:35 +0800 Subject: [PATCH] add status bar for plugin/capability status instead of message box --- resources/web/dialog/PluginsDialog/index.html | 15 +- resources/web/dialog/PluginsDialog/index.js | 20 ++- resources/web/dialog/PluginsDialog/styles.css | 79 ++++++++++- src/slic3r/GUI/PluginsDialog.cpp | 131 +++++++++++++----- src/slic3r/GUI/PluginsDialog.hpp | 12 ++ 5 files changed, 208 insertions(+), 49 deletions(-) diff --git a/resources/web/dialog/PluginsDialog/index.html b/resources/web/dialog/PluginsDialog/index.html index 60e08bf21d..2cc79fdeb5 100644 --- a/resources/web/dialog/PluginsDialog/index.html +++ b/resources/web/dialog/PluginsDialog/index.html @@ -18,6 +18,9 @@
+ @@ -27,10 +30,10 @@
@@ -129,11 +132,9 @@
-
- -
Close
+
+ +
diff --git a/resources/web/dialog/PluginsDialog/index.js b/resources/web/dialog/PluginsDialog/index.js index b627881c93..7310201a92 100644 --- a/resources/web/dialog/PluginsDialog/index.js +++ b/resources/web/dialog/PluginsDialog/index.js @@ -1,7 +1,7 @@ const pluginsById = new Map(); const pluginInstallActions = { "explore": { - label: "Browse plugins", + label: "Install plugin", command: "open_plugin_hub" }, "install-local": { @@ -40,7 +40,6 @@ function OnInit() { exploreMenu?.addEventListener("click", OnExploreMenuClick); document.getElementById("open_terminal")?.addEventListener("click", () => SendMessage("open_terminal")); document.getElementById("detailUpdateBtn")?.addEventListener("click", UpdateSelectedPlugin); - document.getElementById("close_btn")?.addEventListener("click", () => SendMessage("close_page")); document.querySelectorAll("[role='tab']").forEach((tab) => { tab.addEventListener("click", () => ActivateDetailTab(String(tab.dataset.tab || ""))); @@ -216,9 +215,26 @@ function HandleStudio(value) { if (payload.command === "list_plugins") { SetSelectedInstallAction(payload.install_action, false); ApplyPlugins(payload.data || []); + } else if (payload.command === "status_message") { + ShowStatusMessage(String(payload.message || ""), String(payload.level || "info")); } } +// Renders the latest plugin/capability operation result in the footer status bar. The result +// persists until the next operation replaces it; the native side already localizes the text. +function ShowStatusMessage(message, level) { + const bar = document.getElementById("statusBar"); + const text = document.getElementById("statusText"); + if (!bar || !text) + return; + + const normalizedLevel = ["success", "warn", "error", "info"].includes(level) ? level : "info"; + text.textContent = message; + text.title = message; + bar.classList.remove("is-empty", "level-success", "level-warn", "level-error", "level-info"); + bar.classList.add(`level-${normalizedLevel}`); +} + function SafeJsonParse(value) { try { return JSON.parse(value); diff --git a/resources/web/dialog/PluginsDialog/styles.css b/resources/web/dialog/PluginsDialog/styles.css index 0c571e38dc..acd567d450 100644 --- a/resources/web/dialog/PluginsDialog/styles.css +++ b/resources/web/dialog/PluginsDialog/styles.css @@ -65,9 +65,22 @@ body { gap: 8px; } +/* Compact toolbar buttons */ +.toolbar .ButtonTypeChoice { + height: 26px !important; + line-height: 25px !important; + font-size: 13px !important; +} + +/* Only the plain buttons (Open Terminal, Refresh) tighten horizontal padding; the explore dropdown + buttons (grandchildren) keep their own padding/width. */ +.toolbar > .ButtonTypeChoice { + padding: 0 10px !important; +} + .explore-dropdown { - --explore-arrow-width: 26px; - --explore-action-width: 160px; + --explore-arrow-width: 22px; + --explore-action-width: 140px; position: relative; display: inline-flex; align-items: stretch; @@ -652,8 +665,68 @@ body { } } -#AcceptArea { +/* Footer status bar: single-line, fixed-height strip that shows the latest script run result. + Reuses the shared status color tokens so it themes (light/dark) like the plugin status chips. */ +.status-bar { flex: 0 0 auto; + display: flex; + align-items: center; + gap: 8px; + min-height: 28px; + padding: 6px 14px; + box-sizing: border-box; + border-top: 1px solid var(--border); + background: var(--panel); + color: var(--text); + font-size: 13px; +} + +.status-dot { + flex: 0 0 auto; + width: 8px; + height: 8px; + border-radius: 50%; + background: var(--muted); +} + +.status-text { + min-width: 0; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} + +/* Idle (no run yet): keep the strip height stable but hide the indicator dot. */ +.status-bar.is-empty .status-dot { + visibility: hidden; +} + +.status-bar.level-success .status-dot { + background: var(--plugin-status-ok); +} + +.status-bar.level-success .status-text { + color: var(--plugin-status-ok); +} + +.status-bar.level-error .status-dot { + background: var(--plugin-status-danger); +} + +.status-bar.level-error .status-text { + color: var(--plugin-status-danger); +} + +.status-bar.level-warn .status-dot { + background: var(--plugin-status-warn); +} + +.status-bar.level-warn .status-text { + color: var(--plugin-status-warn); +} + +.status-bar.level-info .status-dot { + background: var(--muted); } .ctx { diff --git a/src/slic3r/GUI/PluginsDialog.cpp b/src/slic3r/GUI/PluginsDialog.cpp index fc11b6dd7c..9b0b253698 100644 --- a/src/slic3r/GUI/PluginsDialog.cpp +++ b/src/slic3r/GUI/PluginsDialog.cpp @@ -421,6 +421,28 @@ void PluginsDialog::update_plugin_dialog_ui() // Called after the shared catalog is already updated, for example from the // cloud-plugin state callback. Do not fetch here or the callback can re-enter. send_plugins(); + resolve_pending_activation(); +} + +void PluginsDialog::resolve_pending_activation() +{ + if (m_activating_plugin_key.empty()) + return; + + PluginLoader& loader = PluginManager::instance().get_loader(); + if (loader.is_plugin_load_in_progress(m_activating_plugin_key)) + return; // Still loading: keep the "Activating..." message until the load resolves. + + const std::string plugin_key = m_activating_plugin_key; + m_activating_plugin_key.clear(); + + // Mirror the row's status precedence (Error before Activated) so the message matches the list. + PluginDescriptor descriptor; + if (get_descriptor(plugin_key, descriptor) && descriptor.has_error()) + show_status(wxString::Format(_L("Failed to activate \"%s\"."), plugin_display_name(plugin_key)), "error"); + else if (loader.is_plugin_loaded(plugin_key)) + show_status(wxString::Format(_L("Activated \"%s\"."), plugin_display_name(plugin_key)), "success"); + // Otherwise it ended up inactive (toggled off or cancelled mid-load): stay silent. } void PluginsDialog::on_script_message(const nlohmann::json& payload) @@ -607,13 +629,17 @@ void PluginsDialog::toggle_plugin(const std::string& plugin_key, bool enabled) if (!enabled) { if (!manager.get_loader().unload_plugin(plugin_key)) { BOOST_LOG_TRIVIAL(error) << "Failed to unload plugin from Plugins dialog: " << plugin_key; - wxMessageBox(_L("Failed to unload plugin."), _L("Plugins"), wxOK | wxICON_WARNING, this); + show_status(_L("Failed to unload plugin."), "warn"); send_plugins(); return; } BOOST_LOG_TRIVIAL(info) << "Plugin unloaded from Plugins dialog: " << plugin_key; + // A prior activation of this plugin is moot now; drop it so no stale "Activated" arrives later. + if (m_activating_plugin_key == plugin_key) + m_activating_plugin_key.clear(); send_plugins(); + show_status(wxString::Format(_L("Deactivated \"%s\"."), plugin_display_name(plugin_key)), "success"); return; } @@ -624,7 +650,7 @@ void PluginsDialog::toggle_plugin(const std::string& plugin_key, bool enabled) if (dialog_item.unauthorized && available_actions.toggle_installs_cloud_plugin == false && row_data.has_local_package() == false) { const std::string install_error = "Unauthorized cloud plugins cannot be installed."; manager.set_plugin_error(plugin_key, install_error); - wxMessageBox(from_u8(install_error), _L("Plugins"), wxOK | wxICON_WARNING, this); + show_status(from_u8(install_error), "warn"); } send_plugins(); return; @@ -658,7 +684,7 @@ void PluginsDialog::toggle_plugin(const std::string& plugin_key, bool enabled) const PluginDescriptor* descriptor_ptr = manager.get_catalog().find_valid_plugin_descriptor(plugin_key); if (!descriptor_ptr) { - wxMessageBox(_L("Plugin manifest was not found."), _L("Plugins"), wxOK | wxICON_WARNING, this); + show_status(_L("Plugin manifest was not found."), "warn"); send_plugins(); return; } @@ -671,6 +697,10 @@ void PluginsDialog::toggle_plugin(const std::string& plugin_key, bool enabled) BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": Starting plugin load from web dialog: " << plugin_key; manager.get_loader().load_plugin(manager.get_catalog(), plugin_key); send_plugins(); + // Loading is asynchronous: show a pending message now; resolve_pending_activation() turns it into + // Activated/Failed once the loader's completion callback runs update_plugin_dialog_ui(). + m_activating_plugin_key = plugin_key; + show_status(wxString::Format(_L("Activating \"%s\"..."), plugin_display_name(plugin_key)), "info"); } void PluginsDialog::toggle_plugin_capability(const std::string& plugin_key, PluginCapabilityType type, @@ -705,6 +735,7 @@ void PluginsDialog::toggle_plugin_capability(const std::string& plugin_key, Plug } send_plugins(); + show_status(wxString::Format(enabled ? _L("Enabled \"%s\".") : _L("Disabled \"%s\"."), from_u8(capability_name)), "success"); } void PluginsDialog::handle_plugin_menu_action(const std::string& plugin_key, const std::string& action) @@ -761,7 +792,7 @@ bool PluginsDialog::install_plugin_package(const std::string& package_path) std::transform(extension.begin(), extension.end(), extension.begin(), [](unsigned char ch) { return static_cast(std::tolower(ch)); }); if (extension != ".py" && extension != ".whl") { - wxMessageBox(_L("Select a .py or .whl plugin package."), _L("Plugins"), wxOK | wxICON_INFORMATION, this); + show_status(_L("Select a .py or .whl plugin package."), "info"); return false; } @@ -769,7 +800,7 @@ bool PluginsDialog::install_plugin_package(const std::string& package_path) bool existing_installation = false; auto report_inspection_failure = [&]() { BOOST_LOG_TRIVIAL(error) << "Plugin package inspection failed for " << package_path << " error=" << error; - wxMessageBox(_L("Failed to install plugin package. See the log for details."), _L("Plugins"), wxOK | wxICON_WARNING, this); + show_status(_L("Failed to install plugin package. See the log for details."), "warn"); send_plugins(); return false; }; @@ -814,12 +845,14 @@ bool PluginsDialog::install_plugin_package(const std::string& package_path) if (!installed) { BOOST_LOG_TRIVIAL(error) << "Plugin package installation failed for " << package_path << " error=" << error; - wxMessageBox(_L("Failed to install plugin package. See the log for details."), _L("Plugins"), wxOK | wxICON_WARNING, this); + show_status(_L("Failed to install plugin package. See the log for details."), "warn"); send_plugins(); return false; } BOOST_LOG_TRIVIAL(info) << "Plugin package installed successfully from " << package_path; + const wxString installed_name = from_u8(plugin_descriptor.name.empty() ? package_file.filename().string() : plugin_descriptor.name); + show_status(wxString::Format(_L("Installed \"%s\"."), installed_name), "success"); refresh_plugin_catalog_async(_L("Refreshing"), _L("Refreshing plugins data"), kUseCurrentCloudCatalog); return true; } @@ -849,7 +882,7 @@ bool PluginsDialog::install_cloud_plugin(const std::string& plugin_key, const st if (!downloaded) { BOOST_LOG_TRIVIAL(error) << "Cloud plugin download failed. plugin_key=" << plugin_key << " error=" << error; PluginManager::instance().set_plugin_error(plugin_key, error); - wxMessageBox(from_u8(error), _L("Plugin Download Failed"), wxOK | wxICON_ERROR, this); + show_status(error.empty() ? _L("Plugin download failed.") : from_u8(error), "error"); return false; } @@ -857,6 +890,23 @@ bool PluginsDialog::install_cloud_plugin(const std::string& plugin_key, const st return true; } +void PluginsDialog::show_status(const wxString& message, const char* level) +{ + nlohmann::json payload; + payload["command"] = "status_message"; + payload["level"] = level; + payload["message"] = into_u8(message); + call_web_handler(payload); +} + +wxString PluginsDialog::plugin_display_name(const std::string& plugin_key) const +{ + PluginDescriptor descriptor; + if (get_descriptor(plugin_key, descriptor) && !descriptor.name.empty()) + return from_u8(descriptor.name); + return from_u8(plugin_key); +} + void PluginsDialog::run_script_plugin(const std::string& plugin_key, const std::string& capability_name) { if (plugin_key.empty() || capability_name.empty()) { @@ -893,8 +943,7 @@ void PluginsDialog::run_script_plugin(const std::string& plugin_key, const std:: BOOST_LOG_TRIVIAL(info) << "Run script plugin requested from Plugins dialog. plugin_key=" << plugin_key << " capability_name=" << capability_name; - auto complete_with_error = [this, &manager, &plugin_key](const std::string& plugin_error, const wxString& dialog_message, - const wxString& dialog_title, long dialog_style) { + auto complete_with_error = [this, &manager, &plugin_key](const std::string& plugin_error, const wxString& status_message) { const std::string normalized_error = plugin_error.empty() ? "Script plugin failed." : plugin_error; if (!manager.get_catalog().set_plugin_error(plugin_key, normalized_error)) BOOST_LOG_TRIVIAL(warning) << "Failed to record plugin error. plugin_key=" << plugin_key; @@ -904,19 +953,21 @@ void PluginsDialog::run_script_plugin(const std::string& plugin_key, const std:: send_plugins(); - const wxString message = dialog_message.empty() ? from_u8(normalized_error) : dialog_message; - wxMessageBox(message, dialog_title, dialog_style, this); + // The row now shows an "Error" status and the Diagnostics tab holds the full text, so surface + // the outcome in the footer status bar instead of a modal box (prefer the friendlier override). + const wxString message = status_message.empty() ? from_u8(normalized_error) : status_message; + show_status(message, "error"); }; PluginDescriptor descriptor; if (!get_descriptor(plugin_key, descriptor)) { BOOST_LOG_TRIVIAL(error) << "Cannot run script plugin because manifest was not found. plugin_key=" << plugin_key; - complete_with_error("Plugin manifest was not found.", _L("Plugin manifest was not found."), _L("Plugins"), wxOK | wxICON_WARNING); + complete_with_error("Plugin manifest was not found.", _L("Plugin manifest was not found.")); return; } if (descriptor.has_error()) { - complete_with_error(descriptor.normalized_error(), wxString(), _L("Plugins"), wxOK | wxICON_WARNING); + complete_with_error(descriptor.normalized_error(), wxString()); return; } @@ -929,8 +980,7 @@ void PluginsDialog::run_script_plugin(const std::string& plugin_key, const std:: BOOST_LOG_TRIVIAL(error) << "Cannot run plugin because its metadata is invalid. plugin_key=" << plugin_key << " is_metadata_valid=" << descriptor.is_metadata_valid() << " type=" << plugin_capability_type_to_string(descriptor.primary_capability_type()); - complete_with_error(plugin_error, _L("Only plugins with valid metadata can be run from this dialog."), _L("Plugins"), - wxOK | wxICON_WARNING); + complete_with_error(plugin_error, _L("Only plugins with valid metadata can be run from this dialog.")); return; } @@ -938,7 +988,7 @@ void PluginsDialog::run_script_plugin(const std::string& plugin_key, const std:: if (!manager.get_loader().is_plugin_loaded(plugin_key)) { BOOST_LOG_TRIVIAL(warning) << "Cannot run script plugin because it is not loaded. plugin_key=" << plugin_key; complete_with_error("Load the script plugin before running it: Cannot run script plugin because it is not loaded.", - _L("Load the script plugin before running it."), _L("Plugins"), wxOK | wxICON_INFORMATION); + _L("Load the script plugin before running it.")); return; } @@ -946,7 +996,7 @@ void PluginsDialog::run_script_plugin(const std::string& plugin_key, const std:: if (!plugin) { BOOST_LOG_TRIVIAL(error) << "Loaded plugin does not implement ScriptPluginCapability. plugin_key=" << plugin_key; complete_with_error("The selected plugin is not a runnable script plugin: Loaded plugin does not implement ScriptPluginCapability.", - _L("The selected plugin is not a runnable script plugin."), _L("Plugins"), wxOK | wxICON_WARNING); + _L("The selected plugin is not a runnable script plugin.")); return; } @@ -977,29 +1027,29 @@ void PluginsDialog::run_script_plugin(const std::string& plugin_key, const std:: if (!error.empty()) { plugin.reset(); cap.reset(); - complete_with_error(error, wxString(), _L("Script Plugin Failed"), wxOK | wxICON_ERROR); + complete_with_error(error, wxString()); return; } BOOST_LOG_TRIVIAL(info) << "Script plugin execution completed. plugin_key=" << plugin_key << " status=" << static_cast(result.status) << " message=" << result.message << " data=" << result.data; - const bool failed = result.status == PluginResult::RecoverableError || result.status == PluginResult::FatalError; - const std::string fallback_message = failed ? "Script plugin failed." : - result.status == PluginResult::Skipped ? "Script plugin skipped." : - "Script plugin finished."; + const bool failed = result.status == PluginResult::RecoverableError || result.status == PluginResult::FatalError; if (failed) { plugin.reset(); cap.reset(); - complete_with_error(result.message.empty() ? fallback_message : result.message, wxString(), _L("Script Plugin"), - wxOK | wxICON_ERROR); + // complete_with_error normalizes an empty message to "Script plugin failed." and reports via the status bar. + complete_with_error(result.message, wxString()); return; } manager.clear_plugin_error(plugin_key); send_plugins(); - const wxString message = from_u8(result.message.empty() ? fallback_message : result.message); - wxMessageBox(message, _L("Script Plugin"), wxOK | wxICON_INFORMATION, this); + + const bool skipped = result.status == PluginResult::Skipped; + const wxString fallback = skipped ? _L("Script plugin skipped.") : _L("Script plugin finished."); + const wxString message = result.message.empty() ? fallback : from_u8(result.message); + show_status(message, skipped ? "info" : "success"); } void PluginsDialog::update_plugin(const std::string& plugin_key) @@ -1029,7 +1079,7 @@ void PluginsDialog::update_plugin(const std::string& plugin_key) if (!updated) { BOOST_LOG_TRIVIAL(error) << "Cloud plugin update failed. plugin_key=" << plugin_key << " error=" << error; - wxMessageBox(from_u8(error), _L("Failed to update plugin"), wxOK | wxICON_ERROR, this); + show_status(error.empty() ? _L("Failed to update plugin.") : from_u8(error), "error"); send_plugins(); return; } @@ -1037,6 +1087,7 @@ void PluginsDialog::update_plugin(const std::string& plugin_key) // update_cloud_plugin installs the package and updates the in-memory descriptor // (installed=true, update_available=false) on success. send_plugins(); + show_status(wxString::Format(_L("Updated \"%s\"."), name), "success"); } void PluginsDialog::open_plugin_folder(const PluginDescriptor& plugin) @@ -1044,7 +1095,7 @@ void PluginsDialog::open_plugin_folder(const PluginDescriptor& plugin) const boost::filesystem::path plugin_folder = resolve_plugin_root_from_descriptor(plugin); if (plugin_folder.empty()) { - wxMessageBox(_L("Plugin folder could not be determined."), _L("Plugins"), wxOK | wxICON_WARNING, this); + show_status(_L("Plugin folder could not be determined."), "warn"); return; } @@ -1101,14 +1152,15 @@ void PluginsDialog::delete_local_plugin(const PluginDescriptor& plugin) refresh_plugin_catalog_blocking(kFetchCloudCatalog); store_plugin_operation_result(state, succeeded, std::move(error)); }, - [this, state]() { + [this, state, plugin_name]() { std::string error; if (!take_plugin_operation_result(state, error)) { - wxMessageBox(error.empty() ? _L("Failed to delete plugin.") : from_u8(error), _L("Plugins"), wxOK | wxICON_ERROR, this); + show_status(error.empty() ? _L("Failed to delete plugin.") : from_u8(error), "error"); return; } send_plugins(); + show_status(wxString::Format(_L("Deleted \"%s\"."), plugin_name), "success"); }, _L("Deleting plugin"), _L("Deleting plugin...")); } @@ -1130,14 +1182,15 @@ void PluginsDialog::unsubscribe_cloud_plugin(const PluginDescriptor& plugin) const bool succeeded = PluginManager::instance().delete_and_unsubscribe_cloud_plugin(plugin_key, error); store_plugin_operation_result(state, succeeded, std::move(error)); }, - [this, state]() { + [this, state, plugin_name]() { std::string error; if (!take_plugin_operation_result(state, error)) { - wxMessageBox(error.empty() ? _L("Failed to unsubscribe plugin.") : from_u8(error), _L("Plugins"), wxOK | wxICON_ERROR, this); + show_status(error.empty() ? _L("Failed to unsubscribe plugin.") : from_u8(error), "error"); return; } send_plugins(); + show_status(wxString::Format(_L("Unsubscribed \"%s\"."), plugin_name), "success"); }, _L("Unsubscribing plugin"), _L("Deleting local files and unsubscribing plugin...")); } @@ -1150,7 +1203,7 @@ void PluginsDialog::reinstall_local_plugin(const std::string& plugin_key) PluginManager& manager = PluginManager::instance(); const bool was_loaded = manager.get_loader().is_plugin_loaded(plugin_key); if (!manager.get_loader().unload_plugin(plugin_key)) { - wxMessageBox(_L("Failed to unload plugin."), _L("Plugins"), wxOK | wxICON_WARNING, this); + show_status(_L("Failed to unload plugin."), "warn"); send_plugins(); return; } @@ -1158,10 +1211,13 @@ void PluginsDialog::reinstall_local_plugin(const std::string& plugin_key) manager.get_loader().load_plugin(manager.get_catalog(), plugin_key, false); if (!was_loaded && !manager.get_loader().unload_plugin(plugin_key)) { - wxMessageBox(_L("Plugin reloaded, but failed to restore the inactive state."), _L("Plugins"), wxOK | wxICON_WARNING, this); + show_status(_L("Plugin reloaded, but failed to restore the inactive state."), "warn"); + send_plugins(); + return; } send_plugins(); + show_status(wxString::Format(_L("Reloaded \"%s\"."), plugin_display_name(plugin_key)), "success"); } void PluginsDialog::reinstall_cloud_plugin(const PluginDescriptor& plugin) @@ -1176,7 +1232,7 @@ void PluginsDialog::reinstall_cloud_plugin(const PluginDescriptor& plugin) std::string error; if (plugin.has_local_package()) { if (!manager.delete_plugin(plugin_key, error)) { - wxMessageBox(from_u8(error.empty() ? "Failed to delete plugin." : error), _L("Plugins"), wxOK | wxICON_ERROR, this); + show_status(error.empty() ? _L("Failed to delete plugin.") : from_u8(error), "error"); send_plugins(); return; } @@ -1194,6 +1250,7 @@ void PluginsDialog::reinstall_cloud_plugin(const PluginDescriptor& plugin) } send_plugins(); + show_status(wxString::Format(_L("Reloaded \"%s\"."), plugin_display_name(plugin_key)), "success"); } void PluginsDialog::delete_mine_local_and_cloud_plugin(const std::string& plugin_key) @@ -1211,8 +1268,7 @@ void PluginsDialog::delete_mine_local_and_cloud_plugin(const std::string& plugin std::string error; if (!PluginManager::instance().delete_mine_local_and_cloud_plugin(plugin_key, error)) { - wxMessageBox(error.empty() ? _L("Failed to delete plugin from local and cloud.") : from_u8(error), _L("Plugins"), - wxOK | wxICON_ERROR, this); + show_status(error.empty() ? _L("Failed to delete plugin from local and cloud.") : from_u8(error), "error"); return; } @@ -1221,6 +1277,7 @@ void PluginsDialog::delete_mine_local_and_cloud_plugin(const std::string& plugin // re-syncs the cloud list itself), so a UI refresh is sufficient here — an extra // clearing rescan + cloud fetch would be redundant. send_plugins(); + show_status(wxString::Format(_L("Deleted \"%s\"."), plugin_name), "success"); } }} // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/PluginsDialog.hpp b/src/slic3r/GUI/PluginsDialog.hpp index bfd0c6b33f..f7e657ecdf 100644 --- a/src/slic3r/GUI/PluginsDialog.hpp +++ b/src/slic3r/GUI/PluginsDialog.hpp @@ -78,6 +78,14 @@ private: bool install_plugin_package(const std::string& package_path); bool install_cloud_plugin(const std::string& uuid, const std::string& version, const wxString& name); void run_script_plugin(const std::string& plugin_key, const std::string& capability_name); + // Pushes a one-line result into the web footer status bar (level: "success" | "warn" | "error" | "info"), + // used for every plugin/capability operation instead of a modal box so the dialog stays non-disruptive. + void show_status(const wxString& message, const char* level); + // Best-effort human-readable name for a plugin_key (falls back to the key itself). + wxString plugin_display_name(const std::string& plugin_key) const; + // Turns the pending "Activating..." status into "Activated"/"Failed to activate" once an + // asynchronous plugin load reported via update_plugin_dialog_ui() finishes. No-op otherwise. + void resolve_pending_activation(); void update_plugin(const std::string& plugin_key); void open_plugin_folder(const Slic3r::PluginDescriptor& plugin); @@ -218,6 +226,10 @@ private: // (message/show_dialog) or the result message box pumps a nested event loop, which could // re-dispatch the web "run_script_plugin" command and start a second, overlapping run. bool m_script_running = false; + + // Plugin whose asynchronous activation is in flight, awaited by resolve_pending_activation(). + // Empty when no activation is pending. + std::string m_activating_plugin_key; }; } // namespace GUI