mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-07-16 07:22:10 +00:00
feat: sidebar plugin config UI
This commit is contained in:
@@ -4,6 +4,7 @@
|
||||
#include "GUI_App.hpp"
|
||||
#include "I18N.hpp"
|
||||
#include "OrcaCloudServiceAgent.hpp"
|
||||
#include "slic3r/plugin/PluginConfig.hpp"
|
||||
#include "slic3r/plugin/PluginFsUtils.hpp"
|
||||
#include "slic3r/plugin/PluginManager.hpp"
|
||||
#include "slic3r/plugin/PythonInterpreter.hpp"
|
||||
@@ -16,11 +17,8 @@
|
||||
#include <slic3r/GUI/format.hpp>
|
||||
|
||||
#include <slic3r/plugin/PluginDescriptor.hpp>
|
||||
#include <slic3r/plugin/PythonPluginInterface.hpp>
|
||||
#include <slic3r/plugin/PluginLoader.hpp>
|
||||
|
||||
#include <pybind11/embed.h>
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
@@ -222,6 +220,17 @@ nlohmann::json build_plugin_payload_item(const PluginDialogItem& dialog_item)
|
||||
}
|
||||
payload_item["capabilities"] = std::move(caps);
|
||||
|
||||
// The Config tab's sidebar, built by the shared builder so it stays identical to
|
||||
// PluginsConfigDialog's. Deliberately not the `capabilities` array above: that one is the list
|
||||
// tab's, carrying enable/run state and descriptor-only rows for plugins that are not activated
|
||||
// yet — neither of which the config view has any use for.
|
||||
std::vector<PluginCapabilityIdentifier> config_ids;
|
||||
for (const PluginCapabilityView& capability : dialog_item.capabilities)
|
||||
if (!capability.name.empty())
|
||||
config_ids.push_back(PluginCapabilityIdentifier{plugin_capability_type_from_string(capability.type_key),
|
||||
capability.name, dialog_item.plugin_key});
|
||||
payload_item["config_capabilities"] = PluginConfig::capabilities_payload(config_ids);
|
||||
|
||||
nlohmann::json changelog = nlohmann::json::array();
|
||||
for (const PluginChangelogView& entry : dialog_item.changelog) {
|
||||
nlohmann::json c;
|
||||
@@ -918,153 +927,32 @@ wxString PluginsDialog::plugin_display_name(const std::string& plugin_key) const
|
||||
return from_u8(plugin_key);
|
||||
}
|
||||
|
||||
// Replies to the web page with one capability's stored config, plus the custom HTML UI when the
|
||||
// capability provides one. Config is sent as a JSON value, not text: the default editor
|
||||
// pretty-prints it into its textarea, and a custom UI receives it as-is through window.orca.
|
||||
void PluginsDialog::send_capability_config(const std::string& plugin_key,
|
||||
PluginCapabilityType type,
|
||||
const std::string& capability_name)
|
||||
{
|
||||
nlohmann::json response;
|
||||
response["command"] = "capability_config";
|
||||
response["plugin_key"] = plugin_key;
|
||||
response["capability_name"] = capability_name;
|
||||
response["capability_type"] = plugin_capability_type_to_string(type);
|
||||
response["config"] = nlohmann::json::object();
|
||||
response["custom_html"] = "";
|
||||
response["error"] = "";
|
||||
|
||||
// Scoped to (plugin_key, type, capability_name), so a stale request from a page that has not
|
||||
// caught up with a refresh cannot read a different plugin's config — it just misses.
|
||||
auto cap = get_capability(plugin_key, type, capability_name);
|
||||
if (!cap) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "Ignoring config request for a capability that is no longer loaded. plugin_key="
|
||||
<< plugin_key << " capability_name=" << capability_name;
|
||||
response["error"] = into_u8(_L("This capability is no longer available."));
|
||||
call_web_handler(response);
|
||||
return;
|
||||
}
|
||||
|
||||
response["config"] = PluginManager::instance().get_config().get_config(plugin_key, capability_name).config;
|
||||
|
||||
if (cap->has_config_ui) {
|
||||
// Plugin-authored HTML. A raising or empty get_config_ui() costs the capability only its
|
||||
// custom UI: we report the failure and let the page fall back to the default JSON editor,
|
||||
// which edits the very same stored config.
|
||||
std::string html;
|
||||
std::string error;
|
||||
{
|
||||
wxBusyCursor busy;
|
||||
try {
|
||||
PythonGILState gil;
|
||||
html = cap->instance->get_config_ui();
|
||||
} catch (const std::exception& ex) {
|
||||
error = ex.what();
|
||||
} catch (...) {
|
||||
error = "Unknown error";
|
||||
}
|
||||
}
|
||||
|
||||
if (!error.empty()) {
|
||||
BOOST_LOG_TRIVIAL(error) << "Plugin capability get_config_ui() failed. plugin_key=" << plugin_key
|
||||
<< " capability_name=" << capability_name << " error=" << error;
|
||||
response["error"] = into_u8(format_wxstr(_L("The plugin's configuration UI failed to load (%1%). Showing the default editor."),
|
||||
from_u8(error)));
|
||||
} else if (html.empty()) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "Plugin capability reports a config UI but returned no HTML. plugin_key=" << plugin_key
|
||||
<< " capability_name=" << capability_name;
|
||||
response["error"] = into_u8(_L("The plugin's configuration UI was empty. Showing the default editor."));
|
||||
} else {
|
||||
response["custom_html"] = html;
|
||||
}
|
||||
}
|
||||
|
||||
call_web_handler(response);
|
||||
call_web_handler(PluginConfig::get_config_response({type, capability_name, plugin_key}));
|
||||
}
|
||||
|
||||
// Persists one capability's config. `config` arrives either as text straight from the default
|
||||
// editor (validated here, so invalid JSON can never reach the file) or as a structured value from
|
||||
// a custom UI. Only cap_config is written; identity and version metadata stay host-managed.
|
||||
void PluginsDialog::save_capability_config(const std::string& plugin_key,
|
||||
PluginCapabilityType type,
|
||||
const std::string& capability_name,
|
||||
const nlohmann::json& config)
|
||||
{
|
||||
nlohmann::json response;
|
||||
response["command"] = "capability_config_saved";
|
||||
response["plugin_key"] = plugin_key;
|
||||
response["capability_name"] = capability_name;
|
||||
response["capability_type"] = plugin_capability_type_to_string(type);
|
||||
response["ok"] = false;
|
||||
response["error"] = "";
|
||||
|
||||
auto cap = get_capability(plugin_key, type, capability_name);
|
||||
if (!cap) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "Refusing to save config for a capability that is no longer loaded. plugin_key="
|
||||
<< plugin_key << " capability_name=" << capability_name;
|
||||
response["error"] = into_u8(_L("This capability is no longer available. Your changes were not saved."));
|
||||
call_web_handler(response);
|
||||
return;
|
||||
}
|
||||
|
||||
nlohmann::json parsed = config;
|
||||
if (config.is_string()) {
|
||||
// The page validates as the user types, but it is not the authority: re-parse here so a
|
||||
// malformed document is rejected before it can reach config.json.
|
||||
parsed = nlohmann::json::parse(config.get<std::string>(), nullptr, /* allow_exceptions */ false);
|
||||
if (parsed.is_discarded()) {
|
||||
response["error"] = into_u8(_L("The configuration is not valid JSON. Your changes were not saved."));
|
||||
call_web_handler(response);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!PluginManager::instance().get_config().store_capability_config(plugin_key, capability_name, parsed)) {
|
||||
BOOST_LOG_TRIVIAL(error) << "Failed to write the plugin config file. plugin_key=" << plugin_key
|
||||
<< " capability_name=" << capability_name;
|
||||
response["error"] = into_u8(_L("The configuration could not be written to disk. Your changes were not saved."));
|
||||
call_web_handler(response);
|
||||
return;
|
||||
}
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << "Saved plugin capability config. plugin_key=" << plugin_key
|
||||
<< " capability_name=" << capability_name;
|
||||
|
||||
// Echo the persisted value back so the editor reloads from what is actually stored rather
|
||||
// than from what the user typed.
|
||||
response["ok"] = true;
|
||||
response["config"] = PluginManager::instance().get_config().get_config(plugin_key, capability_name).config;
|
||||
const nlohmann::json response = PluginConfig::save_config_response({type, capability_name, plugin_key}, config);
|
||||
call_web_handler(response);
|
||||
|
||||
show_status(_L("Configuration saved."), "success");
|
||||
if (response.value("ok", false))
|
||||
show_status(_L("Configuration saved."), "success");
|
||||
}
|
||||
|
||||
// Overwrites one capability's stored config with the value its get_default_config() hands back.
|
||||
// The host does not invent that value: a capability that does not override the hook restores an
|
||||
// empty config, which is exactly right for one that applies its own defaults on read.
|
||||
void PluginsDialog::restore_capability_config(const std::string& plugin_key,
|
||||
PluginCapabilityType type,
|
||||
const std::string& capability_name)
|
||||
{
|
||||
nlohmann::json response;
|
||||
response["command"] = "capability_config_saved";
|
||||
response["plugin_key"] = plugin_key;
|
||||
response["capability_name"] = capability_name;
|
||||
response["capability_type"] = plugin_capability_type_to_string(type);
|
||||
response["ok"] = false;
|
||||
response["error"] = "";
|
||||
|
||||
auto cap = get_capability(plugin_key, type, capability_name);
|
||||
if (!cap) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "Refusing to restore config for a capability that is no longer loaded. plugin_key="
|
||||
<< plugin_key << " capability_name=" << capability_name;
|
||||
response["error"] = into_u8(_L("This capability is no longer available."));
|
||||
call_web_handler(response);
|
||||
return;
|
||||
}
|
||||
|
||||
// Discards whatever the user had stored, so confirm first — same as the other destructive
|
||||
// actions in this dialog.
|
||||
// actions in this dialog. The confirmation stays here rather than in PluginConfig: it needs a
|
||||
// parent window, and it is the dialog's business to ask.
|
||||
const int rc = wxMessageBox(wxString::Format(_L("Restore the default configuration for \"%s\"?\n\n"
|
||||
"This discards the settings currently saved for this capability."),
|
||||
from_u8(capability_name)),
|
||||
@@ -1072,49 +960,11 @@ void PluginsDialog::restore_capability_config(const std::string& plugin_key,
|
||||
if (rc != wxYES)
|
||||
return;
|
||||
|
||||
nlohmann::json defaults;
|
||||
std::string error;
|
||||
{
|
||||
wxBusyCursor busy;
|
||||
try {
|
||||
PythonGILState gil;
|
||||
defaults = cap->instance->get_default_config();
|
||||
} catch (const std::exception& ex) {
|
||||
error = ex.what();
|
||||
} catch (...) {
|
||||
error = "Unknown error";
|
||||
}
|
||||
}
|
||||
|
||||
// A raising hook leaves the stored config exactly as it was: better to restore nothing than to
|
||||
// wipe the user's settings on the strength of a broken plugin.
|
||||
if (!error.empty()) {
|
||||
BOOST_LOG_TRIVIAL(error) << "Plugin capability get_default_config() failed. plugin_key=" << plugin_key
|
||||
<< " capability_name=" << capability_name << " error=" << error;
|
||||
response["error"] = into_u8(format_wxstr(_L("The plugin could not supply a default configuration (%1%). "
|
||||
"Nothing was changed."),
|
||||
from_u8(error)));
|
||||
call_web_handler(response);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!PluginManager::instance().get_config().store_capability_config(plugin_key, capability_name, defaults)) {
|
||||
BOOST_LOG_TRIVIAL(error) << "Failed to write the plugin config file while restoring defaults. plugin_key=" << plugin_key
|
||||
<< " capability_name=" << capability_name;
|
||||
response["error"] = into_u8(_L("The configuration could not be written to disk. Nothing was changed."));
|
||||
call_web_handler(response);
|
||||
return;
|
||||
}
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << "Restored default plugin capability config. plugin_key=" << plugin_key
|
||||
<< " capability_name=" << capability_name;
|
||||
|
||||
// Reuses the saved reply, so both editors reload from what was actually persisted.
|
||||
response["ok"] = true;
|
||||
response["config"] = PluginManager::instance().get_config().get_config(plugin_key, capability_name).config;
|
||||
const nlohmann::json response = PluginConfig::restore_config_response({type, capability_name, plugin_key});
|
||||
call_web_handler(response);
|
||||
|
||||
show_status(_L("Default configuration restored."), "success");
|
||||
if (response.value("ok", false))
|
||||
show_status(_L("Default configuration restored."), "success");
|
||||
}
|
||||
|
||||
void PluginsDialog::run_script_plugin(const std::string& plugin_key, const std::string& capability_name)
|
||||
|
||||
Reference in New Issue
Block a user