Translate the web dialogs and pages through the .po catalogs (#16363)

This commit is contained in:
SoftFever
2026-10-11 21:52:41 +08:00
committed by GitHub
parent bd125d32da
commit cab7a7d75f
86 changed files with 12351 additions and 3825 deletions
-2
View File
@@ -571,8 +571,6 @@ set(SLIC3R_GUI_SOURCES
GUI/UserNotification.hpp
GUI/UVEditorCanvas.cpp
GUI/UVEditorCanvas.hpp
GUI/WebDownPluginDlg.cpp
GUI/WebDownPluginDlg.hpp
GUI/WebGuideDialog.cpp
GUI/WebGuideDialog.hpp
GUI/WebUserLoginDialog.cpp
@@ -64,6 +64,20 @@ void ExportPresetBundleDialog::Init()
wxSize(820, 660), wxSize(640, 640));
}
nlohmann::json ExportPresetBundleDialog::localized_strings() const
{
return {
{"printer", _u8L("Printer")},
{"filament", _u8L("Filament")},
{"process", _u8L("Process")},
{"all", _u8L("All")},
{"no_items", _u8L("No items")},
{"export_cloud", _u8L("Export to OrcaCloud")},
{"export_folder", _u8L("Export to folder")},
{"close", _u8L("Close")},
};
}
void ExportPresetBundleDialog::on_script_message(const nlohmann::json& j)
{
if (handle_common_script_command(j))
@@ -8,6 +8,7 @@
#include "libslic3r/AppConfig.hpp"
#include "libslic3r/Preset.hpp"
#include <slic3r/GUI/GUI.hpp>
#include <nlohmann/json_fwd.hpp>
#include <wx/chartype.h>
#include <unordered_map>
#include <string>
@@ -66,6 +67,7 @@ public:
void InitExportData();
void on_script_message(const nlohmann::json& payload) override;
nlohmann::json localized_strings() const override;
void OnRequestPresets();
void OnExportData(const wxString& path, const wxString& name, nlohmann::json data);
+14
View File
@@ -54,6 +54,20 @@ PluginsConfigDialog::PluginsConfigDialog(wxWindow* parent, Preset::Type type, co
PluginsConfigDialog::~PluginsConfigDialog() { m_alive->store(false, std::memory_order_release); }
nlohmann::json PluginsConfigDialog::localized_strings() const
{
return {
{"no_capabilities", _u8L("This preset does not use any plugin capabilities")},
{"capabilities_label", _u8L("Capabilities used by this preset")},
{"config_json_label", _u8L("Capability configuration (JSON)")},
{"config_frame_title", _u8L("Plugin configuration")},
{"restore_title", _u8L("Discard this preset's override and use the global configuration again")},
{"restore_defaults", _u8L("Restore defaults")},
{"save", _u8L("Save")},
{"invalid_json", _u8L("Invalid JSON")},
};
}
const Preset* PluginsConfigDialog::current_preset() const
{
const PresetBundle* bundle = wxGetApp().preset_bundle;
+2
View File
@@ -2,6 +2,7 @@
#include <slic3r/GUI/Widgets/WebViewHostDialog.hpp>
#include <libslic3r/Preset.hpp>
#include <nlohmann/json_fwd.hpp>
#include <slic3r/plugin/PluginConfig.hpp>
#include <atomic>
@@ -31,6 +32,7 @@ public:
private:
void on_script_message(const nlohmann::json& payload) override;
nlohmann::json localized_strings() const override;
// Runs one web command on a clean main-loop stack; see on_script_message.
void handle_web_command(const nlohmann::json& payload);
+93 -2
View File
@@ -175,7 +175,7 @@ nlohmann::json build_plugin_payload_item(const PluginDialogItem& dialog_item)
for (const PluginCapabilityView& capability : dialog_item.capabilities) {
nlohmann::json c;
c["name"] = capability.id.name;
c["type"] = plugin_capability_type_display_name(capability.id.type);
c["type"] = PluginConfig::capability_type_label(capability.id.type);
c["type_key"] = plugin_capability_type_to_string(capability.id.type);
c["enabled"] = capability.enabled;
c["can_toggle"] = capability.can_toggle;
@@ -326,7 +326,7 @@ PluginDialogItem build_plugin_dialog_item(const PluginDescriptor& descriptor)
item.type_labels.push_back(label);
} else {
for (const auto& capability : capabilities) {
const std::string label = plugin_capability_type_display_name(capability->type());
const std::string label = PluginConfig::capability_type_label(capability->type());
if (std::find(item.type_labels.begin(), item.type_labels.end(), label) == item.type_labels.end())
item.type_labels.push_back(label);
}
@@ -586,6 +586,97 @@ PluginsDialog::PluginsDialog(wxWindow* parent, wxWindowID id, const wxString&, c
: WebViewHostDialog(parent, id, _L("Plugins"), pos, size, style)
{ create_webview("web/dialog/PluginsDialog/index.html", _L("Plugins"), wxSize(900, 820), wxSize(760, 715)); }
nlohmann::json PluginsDialog::localized_strings() const
{
return {
{"search_plugins", _u8L("Search plugins")},
{"clear", _u8L("Clear")},
{"clear_search", _u8L("Clear search")},
{"match_case", _u8L("Match case")},
{"match_whole_word", _u8L("Match whole word")},
{"refresh", _u8L("Refresh")},
{"install_options", _u8L("More plugin install options")},
{"install_plugin", _u8L("Install plugin")},
{"install_local_plugin", _u8L("Install local plugin")},
{"activate", _u8L("Activate")},
{"sort_by_name", _u8L("Sort by name")},
{"name", _u8L("Name")},
{"sort_by_version", _u8L("Sort by version")},
{"plugin_version", _u8L("Plugin Version")},
{"sort_by_source", _u8L("Sort by source")},
{"source", _u8L("Source")},
{"sort_by_status", _u8L("Sort by status")},
{"status", _u8L_CONTEXT("Status", "Plugin")},
{"resize_hint", _u8L("Drag to resize; double-click to reset")},
{"resize_list", _u8L("Resize the plugin list")},
{"details_label", _u8L("Plugin details")},
{"tab_info", _u8L("Plugin Info")},
{"description", _u8L("Description")},
{"tab_config", _u8L("Config")},
{"tab_changelog", _u8L("Changelog")},
{"tab_diagnostics", _u8L("Diagnostics")},
{"types", _u8L("Types")},
{"author", _u8L("Author")},
{"installed_version", _u8L("Installed Version")},
{"latest_version", _u8L("Latest Version")},
{"update_available", _u8L("Update available")},
{"update_title", _u8L("Update to the latest version")},
{"update", _u8L("Update")},
{"no_description", _u8L("No description available")},
{"config_select_plugin", _u8L("Select a plugin to configure its capabilities")},
{"configurable_capabilities", _u8L("Configurable capabilities")},
{"config_json_label", _u8L("Capability configuration (JSON)")},
{"config_frame_title", _u8L("Plugin configuration")},
{"restore_title", _u8L("Discard the settings saved for this capability and restore the plugin's defaults")},
{"restore_defaults", _u8L("Restore defaults")},
{"save", _u8L("Save")},
{"changelog_label", _u8L("Plugin changelog")},
{"version", _u8L("Version")},
{"date", _u8L("Date")},
{"changes", _u8L("Changes")},
{"no_changelog", _u8L("No changelog available")},
{"no_issues", _u8L("No issues detected")},
{"no_plugins", _u8L("No plugins found")},
{"no_match", _u8L("No plugins match \"%s\"")},
{"source_mine", _u8L("Mine")},
{"source_subscribed", _u8L("Subscribed")},
{"source_orphaned", _u8L("Orphaned")},
{"source_local", _u8L("Local")},
{"mixed_capabilities", _u8L("Some plugin capabilities are enabled")},
{"collapse_named", _u8L("Collapse %s capabilities")},
{"expand_named", _u8L("Expand %s capabilities")},
{"collapse", _u8L("Collapse capabilities")},
{"expand", _u8L("Expand capabilities")},
{"open_in_browser", _u8L("Open this plugin in your browser")},
{"capability_count", _u8L("Capabilities: %s")},
{"capabilities", _u8L_CONTEXT("Capabilities", "Plugin")},
{"run_named", _u8L("Run \"%s\"")},
{"enable_named", _u8L("Enable %s")},
{"not_available", _u8L("N/A")},
{"unauthorized", _u8L("Unauthorized for updates")},
{"not_installed", _u8L("Not installed")},
{"config_loading", _u8L("Loading the plugin…")},
{"config_no_capabilities", _u8L("This plugin exposes no capabilities")},
{"config_activate", _u8L("Activate this plugin to configure its capabilities")},
{"invalid_json", _u8L("Invalid JSON")},
{"view_on_cloud", _u8L("View on OrcaCloud <a>here</a>")},
{"status_activated", _u8L("Activated")},
{"status_loading", _u8L("Loading")},
{"status_error", _u8L("Error")},
{"status_runtime_error", _u8L("Runtime error")},
{"status_inactive", _u8L("Inactive")},
{"status_activated_desc", _u8L("This plugin is active and ready to be used.")},
{"status_loading_desc", _u8L("This plugin is still loading.")},
{"status_error_desc", _u8L("This plugin is blocked until its error is fixed.")},
{"status_runtime_error_desc", _u8L("This plugin is loaded but a capability reported an error.")},
{"status_inactive_desc", _u8L("This plugin is inactive. Activate it to install or load it.")},
{"select_plugin", _u8L("Select a plugin to view its status and details.")},
{"orphaned_note", _u8L("Orphaned: This plugin is no longer subscribed or available in OrcaCloud. The local copy remains installed and can still be used.")},
{"newer_version_note", _u8L("A newer plugin version is available.")},
{"unauthorized_note", _u8L("Cloud updates are unavailable because this plugin is unauthorized for updates.")},
};
}
PluginsDialog::~PluginsDialog() { m_alive->store(false, std::memory_order_release); }
void PluginsDialog::set_open_terminal_dlg_fn()
+2
View File
@@ -11,6 +11,7 @@
#include <functional>
#include <memory>
#include <mutex>
#include <nlohmann/json_fwd.hpp>
#include <optional>
#include <string>
#include <thread>
@@ -237,6 +238,7 @@ private:
void open_plugin_on_cloud(const std::string& sharing_token);
void open_plugin_hub();
void on_script_message(const nlohmann::json& payload) override;
nlohmann::json localized_strings() const override;
// Runs one web command on a clean main-loop stack; see on_script_message.
void handle_web_command(const nlohmann::json& payload);
// Re-raises this dialog after a transient modal it opened (file dialog, message box,
+25
View File
@@ -208,6 +208,31 @@ void PresetBundleDialog::create()
wxSize(820, 660), wxSize(640, 640));
}
nlohmann::json PresetBundleDialog::localized_strings() const
{
return {
{"refresh", _u8L("Refresh")},
{"name", _u8L("Name")},
{"type", _u8L("Type")},
{"version", _u8L("Version")},
{"update", _u8L("Update")},
{"export_presets", _u8L("Export Presets")},
{"close", _u8L("Close")},
{"open_folder", _u8L("Open folder in explorer")},
{"delete_bundle", _u8L("Delete bundle")},
{"unsubscribe_bundle", _u8L("Unsubscribe bundle")},
{"update_available", _u8L("Update available")},
{"unauthorized_bundle", _u8L("Unauthorized bundle")},
{"open_in_browser", _u8L("Open this bundle in your browser")},
{"type_subscribed", _u8L("Subscribed")},
{"type_local", _u8L("Local")},
{"type_default", _u8L("Default")},
{"printer", _u8L("Printer")},
{"filament", _u8L("Filament")},
{"process", _u8L("Process")},
};
}
bool PresetBundleDialog::DeleteBundleById(const wxString& id)
{
auto* b = wxGetApp().preset_bundle;
+2
View File
@@ -8,6 +8,7 @@
#include <atomic>
#include <boost/thread/detail/thread.hpp>
#include <libslic3r/PresetBundle.hpp>
#include <nlohmann/json_fwd.hpp>
#include <memory>
#include <slic3r/GUI/GUI.hpp>
#include <string>
@@ -63,6 +64,7 @@ public:
// webview events
void on_script_message(const nlohmann::json& payload) override;
nlohmann::json localized_strings() const override;
void StartDialogWorker();
void StopDialogWorker();
+16
View File
@@ -20,6 +20,9 @@
#include <wx/toolbar.h>
#include <slic3r/GUI/Widgets/WebView.hpp>
#include <slic3r/GUI/Widgets/WebViewHostDialog.hpp>
#include "I18N.hpp"
#include <nlohmann/json.hpp>
#include <wx/utils.h>
#include <wx/webview.h>
@@ -107,6 +110,17 @@ static void inject_vue_resize_workaround(wxWebView *webView)
}
#endif
// The strings of the page shown when the printer has no connection set up
// (resources/web/orca/missing_connection.html), keyed by its data-i18n ids.
static void add_page_strings(wxWebView *webView)
{
const nlohmann::json strings = {
{"printer_connection", _u8L("Printer Connection")},
{"printer_connection_setup", _u8L("Please set up your printer connection to view the device.")},
};
webView->AddUserScript(wxString::FromUTF8(WebViewHostDialog::localized_strings_script(strings)));
}
PrinterWebView::PrinterWebView(wxWindow *parent)
: wxPanel(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize)
, m_browser(nullptr)
@@ -161,6 +175,7 @@ PrinterWebView::~PrinterWebView()
void PrinterWebView::create_browser()
{
m_browser = WebView::CreateWebView(this, "");
add_page_strings(m_browser);
#ifdef __linux__
inject_vue_resize_workaround(m_browser);
@@ -278,6 +293,7 @@ void PrinterWebView::SendAPIKey()
// Re-inject the vue-resize/WebKitGTK workaround that RemoveAllUserScripts just cleared.
inject_vue_resize_workaround(m_browser);
#endif
add_page_strings(m_browser);
m_browser->AddUserScript(script);
m_browser->Reload();
+29 -1
View File
@@ -51,7 +51,9 @@
#include <wx/tglbtn.h>
#include "GUI_App.hpp"
#include "I18N.hpp"
#include <slic3r/GUI/Widgets/WebView.hpp>
#include <slic3r/GUI/Widgets/WebViewHostDialog.hpp>
#include "slic3r/GUI/Jobs/SendJob.hpp"
#include "slic3r/GUI/Plater.hpp"
@@ -65,6 +67,29 @@ namespace Slic3r { namespace GUI {
wxDEFINE_EVENT(EVT_PROJECT_RELOAD, wxCommandEvent);
// The project page's strings (resources/web/model), keyed by their data-i18n and T() ids.
static json project_page_strings()
{
return {
{"no_model_info", _u8L("No model information")},
{"edit_project_info", _u8L("Edit Project Info")},
{"model_information", _u8L("Model Information")},
{"accessories", _u8L("Accessories")},
{"profile_information", _u8L("Profile Information")},
{"model_name", _u8L("Model name")},
{"created_by", _u8L("Created by")},
{"remixed_by", _u8L("Remixed by")},
{"shared_by", _u8L("Shared by")},
{"model_description", _u8L("Model description")},
{"bom", _u8L("BOM")},
{"assembly_guide", _u8L("Assembly Guide")},
{"other", _u8L("Other")},
{"profile_name", _u8L("Profile name")},
{"profile_author", _u8L("Profile Author")},
{"profile_description", _u8L("Profile description")},
};
}
const std::vector<std::string> license_list = {
"BSD License",
"Apache License",
@@ -117,10 +142,13 @@ void ProjectPanel::shutdown()
void ProjectPanel::create_browser()
{
m_browser = WebView::CreateWebView(this, m_project_home_url);
// Created empty and loaded after the strings are added, so the first load already has them.
m_browser = WebView::CreateWebView(this, wxEmptyString);
m_browser->AddUserScript(wxString::FromUTF8(WebViewHostDialog::localized_strings_script(project_page_strings())));
m_browser->Bind(wxEVT_WEBVIEW_NAVIGATED, &ProjectPanel::on_navigated, this);
m_browser->Bind(wxEVT_WEBVIEW_SCRIPT_MESSAGE_RECEIVED, &ProjectPanel::OnScriptMessage, this, m_browser->GetId());
m_browser->Bind(wxEVT_WEBVIEW_NAVIGATING, &ProjectPanel::onWebNavigating, this);
WebView::LoadUrl(m_browser, m_project_home_url);
}
void ProjectPanel::reset_browser()
+3 -14
View File
@@ -84,9 +84,8 @@ void focus_webview(wxWebView* browser, bool page_ready)
browser->RunScript("focusInput();");
}
// Localized strings for the Speed Dial page, injected as a document-start user script. The page's
// T() reads window.ORCA_UI_STRINGS, so these flow through the same .po pipeline as the rest of the
// UI (the JS literals are only a fallback before the script runs / in the node vm test).
// Localized strings for the Speed Dial page (see WebViewHostDialog::localized_strings). The page's own T()
// reads them; its JS literals are only a fallback before the script runs / in the node vm test.
// %% is a literal '%': T() collapses it after substituting %s. Keep the shortcut tokens out of the
// translated text so the platform prefix (Alt+/⌥+, Ctrl+/⌘+) stays correct.
nlohmann::json speed_dial_ui_strings()
@@ -175,17 +174,7 @@ SpeedDialWebDialog::SpeedDialWebDialog(wxWindow* parent)
SpeedDialWebDialog::~SpeedDialWebDialog() { m_alive->store(false, std::memory_order_release); }
// Document-start hook: hand the page its translated strings before speeddial.js runs, so the first
// paint is already localized. The table is built when the dialog is created; a live language switch
// rebuilds the GUI (and with it this dialog), so the next open re-injects the new locale.
void SpeedDialWebDialog::add_user_scripts()
{
if (wxWebView* wv = browser()) {
const std::string js = "window.ORCA_UI_STRINGS = " +
speed_dial_ui_strings().dump(-1, ' ', false, nlohmann::json::error_handler_t::ignore) + ";";
wv->AddUserScript(wxString::FromUTF8(js));
}
}
nlohmann::json SpeedDialWebDialog::localized_strings() const { return speed_dial_ui_strings(); }
void SpeedDialWebDialog::request_show()
{
+1 -1
View File
@@ -22,7 +22,7 @@ public:
void request_show();
private:
void add_user_scripts() override;
nlohmann::json localized_strings() const override;
void on_script_message(const nlohmann::json& payload) override;
void handle_web_command(const nlohmann::json& payload);
void resize_to_content(int height);
+10
View File
@@ -68,6 +68,16 @@ TerminalDialog::TerminalDialog(wxWindow* parent, wxWindowID id, const wxString&
wxSize(820, 600), wxSize(640, 480));
}
nlohmann::json TerminalDialog::localized_strings() const
{
return {
{"command_hint", _u8L("python ... or uv ...")},
{"run", _u8L("Run")},
{"ready", _u8L("Plugin terminal ready.")},
{"exit_code", _u8L("Process exited with code: %s")},
};
}
TerminalDialog::~TerminalDialog()
{
if (m_runner && m_runner->is_running())
+2
View File
@@ -4,6 +4,7 @@
#include "Widgets/WebViewHostDialog.hpp"
#include <memory>
#include <nlohmann/json_fwd.hpp>
#include <string>
#include <vector>
#include <wx/event.h>
@@ -32,6 +33,7 @@ public:
private:
void on_script_message(const nlohmann::json& payload) override;
nlohmann::json localized_strings() const override;
void resolve_and_run(const std::string& cmd);
-356
View File
@@ -1,356 +0,0 @@
#include "WebDownPluginDlg.hpp"
#include "ConfigWizard.hpp"
#include "slic3r/GUI/GUI.hpp"
#include <boost/filesystem/path.hpp>
#include <boost/log/trivial.hpp>
#include <nlohmann/json.hpp>
#include <exception>
#include <string.h>
#include "I18N.hpp"
#include "libslic3r/AppConfig.hpp"
#include "slic3r/GUI/wxExtensions.hpp"
#include "slic3r/GUI/GUI_App.hpp"
#include "libslic3r_version.h"
#include <wx/log.h>
#include <wx/gdicmn.h>
#include <wx/event.h>
#include <wx/setup.h>
#include <wx/sizer.h>
#include <wx/string.h>
#include <wx/toolbar.h>
#include <wx/textdlg.h>
#include <wx/webview.h>
#include <wx/utils.h>
#include <wx/wx.h>
#include <wx/fileconf.h>
#include <wx/file.h>
#include <wx/wfstream.h>
#include <boost/cast.hpp>
#include <boost/lexical_cast.hpp>
#include "MainFrame.hpp"
#include <boost/dll.hpp>
#include <slic3r/GUI/Widgets/WebView.hpp>
#include <slic3r/Utils/Http.hpp>
#include <libslic3r/miniz_extension.hpp>
#include <libslic3r/Utils.hpp>
using namespace nlohmann;
namespace Slic3r { namespace GUI {
DownPluginFrame::DownPluginFrame(GUI_App *pGUI) : wxDialog((wxWindow *) (pGUI->mainframe), wxID_ANY, "Orca Slicer"), m_appconfig_new()
{
// INI
m_MainPtr = pGUI;
// set the frame icon
wxBoxSizer *topsizer = new wxBoxSizer(wxVERTICAL);
wxString TargetUrl = file_url_from_path(boost::filesystem::path(resources_dir()) / "web/guide/6/index.html");
// Create the webview
m_browser = WebView::CreateWebView(this, TargetUrl);
if (m_browser == nullptr) {
wxLogError("Could not init m_browser");
return;
}
SetSizer(topsizer);
topsizer->Add(m_browser, wxSizerFlags().Expand().Proportion(1));
// Log backend information
// wxLogMessage(wxWebView::GetBackendVersionInfo().ToString());
// wxLogMessage("Backend: %s Version: %s",
// m_browser->GetClassInfo()->GetClassName(),wxWebView::GetBackendVersionInfo().ToString());
// wxLogMessage("User Agent: %s", m_browser->GetUserAgent());
// Set a more sensible size for web browsing
wxSize pSize = FromDIP(wxSize(410, 200));
SetSize(pSize);
CenterOnParent();
// int screenheight = wxSystemSettings::GetMetric(wxSYS_SCREEN_Y, NULL);
// int screenwidth = wxSystemSettings::GetMetric(wxSYS_SCREEN_X, NULL);
// int MaxY = (screenheight - pSize.y) > 0 ? (screenheight - pSize.y) / 2 : 0;
// MoveWindow(this->m_hWnd, (screenwidth - pSize.x) / 2, MaxY, pSize.x, pSize.y, TRUE);
// Connect the webview events
Bind(wxEVT_WEBVIEW_NAVIGATING, &DownPluginFrame::OnNavigationRequest, this, m_browser->GetId());
Bind(wxEVT_WEBVIEW_NAVIGATED, &DownPluginFrame::OnNavigationComplete, this, m_browser->GetId());
Bind(wxEVT_WEBVIEW_LOADED, &DownPluginFrame::OnDocumentLoaded, this, m_browser->GetId());
Bind(wxEVT_WEBVIEW_ERROR, &DownPluginFrame::OnError, this, m_browser->GetId());
Bind(wxEVT_WEBVIEW_NEWWINDOW, &DownPluginFrame::OnNewWindow, this, m_browser->GetId());
Bind(wxEVT_WEBVIEW_TITLE_CHANGED, &DownPluginFrame::OnTitleChanged, this, m_browser->GetId());
Bind(wxEVT_WEBVIEW_FULLSCREEN_CHANGED, &DownPluginFrame::OnFullScreenChanged, this, m_browser->GetId());
Bind(wxEVT_WEBVIEW_SCRIPT_MESSAGE_RECEIVED, &DownPluginFrame::OnScriptMessage, this, m_browser->GetId());
}
DownPluginFrame::~DownPluginFrame()
{
if (m_browser) {
delete m_browser;
m_browser = nullptr;
}
}
void DownPluginFrame::load_url(wxString &url)
{
BOOST_LOG_TRIVIAL(trace) << "app_start: DownPluginFrame url=" << url.ToStdString();
this->Show();
m_browser->LoadURL(url);
m_browser->SetFocus();
UpdateState();
}
/**
* Method that retrieves the current state from the web control and updates
* the GUI the reflect this current state.
*/
void DownPluginFrame::UpdateState()
{
// SetTitle(m_browser->GetCurrentTitle());
}
void DownPluginFrame::OnIdle(wxIdleEvent &WXUNUSED(evt))
{
if (m_browser->IsBusy()) {
wxSetCursor(wxCURSOR_ARROWWAIT);
} else {
wxSetCursor(wxNullCursor);
}
}
// void DownPluginFrame::OnClose(wxCloseEvent& evt)
//{
// this->Hide();
//}
/**
* Callback invoked when there is a request to load a new page (for instance
* when the user clicks a link)
*/
void DownPluginFrame::OnNavigationRequest(wxWebViewEvent &evt)
{
// wxLogMessage("%s", "Navigation request to '" + evt.GetURL() + "'
// (target='" + evt.GetTarget() + "')");
UpdateState();
}
/**
* Callback invoked when a navigation request was accepted
*/
void DownPluginFrame::OnNavigationComplete(wxWebViewEvent &evt)
{
// wxLogMessage("%s", "Navigation complete; url='" + evt.GetURL() + "'");
wxString NewUrl = evt.GetURL();
UpdateState();
}
/**
* Callback invoked when a page is finished loading
*/
void DownPluginFrame::OnDocumentLoaded(wxWebViewEvent &evt)
{
// Only notify if the document is the main frame, not a subframe
wxString tmpUrl = evt.GetURL();
wxString NowUrl = m_browser->GetCurrentURL();
if (evt.GetURL() == m_browser->GetCurrentURL()) {
// wxLogMessage("%s", "Document loaded; url='" + evt.GetURL() + "'");
}
UpdateState();
// wxCommandEvent *event = new
// wxCommandEvent(EVT_WEB_RESPONSE_MESSAGE,this->GetId()); wxQueueEvent(this,
// event);
}
/**
* On new window, we veto to stop extra windows appearing
*/
void DownPluginFrame::OnNewWindow(wxWebViewEvent &evt)
{
wxString flag = " (other)";
wxString NewUrl = evt.GetURL();
wxLaunchDefaultBrowser(NewUrl);
// if (evt.GetNavigationAction() == wxWEBVIEW_NAV_ACTION_USER) { flag = " (user)"; }
// wxLogMessage("%s", "New window; url='" + evt.GetURL() + "'" + flag);
// If we handle new window events then just load them in this window as we
// are a single window browser
// if (m_tools_handle_new_window->IsChecked())
// m_browser->LoadURL(evt.GetURL());
UpdateState();
}
void DownPluginFrame::OnTitleChanged(wxWebViewEvent &evt)
{
// SetTitle(evt.GetString());
// wxLogMessage("%s", "Title changed; title='" + evt.GetString() + "'");
}
void DownPluginFrame::OnFullScreenChanged(wxWebViewEvent &evt)
{
// wxLogMessage("Full screen changed; status = %d", evt.GetInt());
ShowFullScreen(evt.GetInt() != 0);
}
void DownPluginFrame::OnScriptMessage(wxWebViewEvent &evt)
{
try {
wxString strInput = evt.GetString();
json j = json::parse(strInput.utf8_string());
wxString strCmd = j["command"];
if (strCmd == "Begin_Download_network_plugin") {
wxGetApp().CallAfter([this] { DownloadPlugin(); });
}
else if (strCmd == "netplugin_download_cancel") {
wxGetApp().cancel_networking_install();
this->EndModal(wxID_CANCEL);
this->Close();
}
else if (strCmd == "begin_install_plugin") {
wxGetApp().CallAfter([this] { InstallPlugin(); });
}
else if (strCmd == "restart_studio") {
wxGetApp().restart_networking();
this->EndModal(wxID_OK);
this->Close();
}
else if (strCmd == "close_download_dialog") {
this->EndModal(wxID_OK);
this->Close();
}
else if (strCmd == "open_plugin_folder") {
auto plugin_folder = (boost::filesystem::path(wxStandardPaths::Get().GetUserDataDir().ToUTF8().data()) / "plugins").make_preferred().string();
desktop_open_any_folder(plugin_folder);
}
} catch (std::exception &) {
// wxMessageBox(e.what(), "json Exception", MB_OK);
}
}
void DownPluginFrame::RunScript(const wxString &javascript)
{
// Remember the script we run in any case, so the next time the user opens
// the "Run Script" dialog box, it is shown there for convenient updating.
// m_javascript = javascript;
// wxLogMessage("Running JavaScript:\n%s\n", javascript);
if (!m_browser) return;
WebView::RunScript(m_browser, javascript);
}
#if wxUSE_WEBVIEW_IE
void DownPluginFrame::OnRunScriptObjectWithEmulationLevel(wxCommandEvent &WXUNUSED(evt))
{
wxWebViewIE::MSWSetModernEmulationLevel();
RunScript("function f(){var person = new Object();person.name = 'Foo'; \
person.lastName = 'Bar';return person;}f();");
wxWebViewIE::MSWSetModernEmulationLevel(false);
}
void DownPluginFrame::OnRunScriptDateWithEmulationLevel(wxCommandEvent &WXUNUSED(evt))
{
wxWebViewIE::MSWSetModernEmulationLevel();
RunScript("function f(){var d = new Date('10/08/2017 21:30:40'); \
var tzoffset = d.getTimezoneOffset() * 60000; return \
new Date(d.getTime() - tzoffset);}f();");
wxWebViewIE::MSWSetModernEmulationLevel(false);
}
void DownPluginFrame::OnRunScriptArrayWithEmulationLevel(wxCommandEvent &WXUNUSED(evt))
{
wxWebViewIE::MSWSetModernEmulationLevel();
RunScript("function f(){ return [\"foo\", \"bar\"]; }f();");
wxWebViewIE::MSWSetModernEmulationLevel(false);
}
#endif
/**
* Callback invoked when a loading error occurs
*/
void DownPluginFrame::OnError(wxWebViewEvent &evt)
{
#define WX_ERROR_CASE(type) \
case type: category = #type; break;
wxString category;
switch (evt.GetInt()) {
WX_ERROR_CASE(wxWEBVIEW_NAV_ERR_CONNECTION);
WX_ERROR_CASE(wxWEBVIEW_NAV_ERR_CERTIFICATE);
WX_ERROR_CASE(wxWEBVIEW_NAV_ERR_AUTH);
WX_ERROR_CASE(wxWEBVIEW_NAV_ERR_SECURITY);
WX_ERROR_CASE(wxWEBVIEW_NAV_ERR_NOT_FOUND);
WX_ERROR_CASE(wxWEBVIEW_NAV_ERR_REQUEST);
WX_ERROR_CASE(wxWEBVIEW_NAV_ERR_USER_CANCELLED);
WX_ERROR_CASE(wxWEBVIEW_NAV_ERR_OTHER);
}
// wxLogMessage("%s", "Error; url='" + evt.GetURL() + "', error='" +
// category + " (" + evt.GetString() + ")'");
// Show the info bar with an error
// m_info->ShowMessage(_L("An error occurred loading ") + evt.GetURL() +
// "\n" + "'" + category + "'", wxICON_ERROR);
BOOST_LOG_TRIVIAL(info) << "DownPluginFrame::OnError: An error occurred loading " << evt.GetURL() << category;
UpdateState();
}
void DownPluginFrame::OnScriptResponseMessage(wxCommandEvent &WXUNUSED(evt))
{
}
int DownPluginFrame::DownloadPlugin()
{
return wxGetApp().download_plugin(
"plugins", "network_plugin.zip", [this](int status, int percent, bool &cancel) { return ShowPluginStatus(status, percent, cancel); }, nullptr);
}
int DownPluginFrame::InstallPlugin()
{
return wxGetApp().install_plugin(
"plugins", "network_plugin.zip", [this](int status, int percent, bool &cancel) { return ShowPluginStatus(status, percent, cancel); });
}
int DownPluginFrame::ShowPluginStatus(int status, int percent, bool &cancel)
{
static int nPercent = 0;
if (nPercent == percent)
return 0;
nPercent = percent;
json m_Data = json::object();
m_Data["status"] = status;
m_Data["percent"] = percent;
json m_Res = json::object();
m_Res["command"] = "ShowStatusPercent";
m_Res["sequence_id"] = "10001";
m_Res["data"] = m_Data;
wxString strJS = wxString::Format("HandleStudio(%s)", m_Res.dump(-1, ' ', false, json::error_handler_t::ignore));
wxGetApp().CallAfter([this, strJS] { RunScript(strJS); });
return 0;
}
}} // namespace Slic3r::GUI
-87
View File
@@ -1,87 +0,0 @@
#ifndef slic3r_WebDialytipDialog_hpp_
#define slic3r_WebDialytipDialog_hpp_
#include "wx/artprov.h"
#include "wx/cmdline.h"
#include "wx/notifmsg.h"
#include "wx/settings.h"
#include "wx/webview.h"
#include <wx/setup.h>
#include <wx/string.h>
#include <wx/event.h>
#if wxUSE_WEBVIEW_IE
#include "wx/msw/webview_ie.h"
#endif
#if wxUSE_WEBVIEW_EDGE
#include "wx/msw/webview_edge.h"
#endif
#include "wx/webviewarchivehandler.h"
#include "wx/webviewfshandler.h"
#include "wx/numdlg.h"
#include "wx/infobar.h"
#include "wx/filesys.h"
#include "wx/fs_arc.h"
#include "wx/fs_mem.h"
#include "wx/stdpaths.h"
#include <wx/frame.h>
#include <wx/tbarbase.h>
#include "wx/textctrl.h"
#include "GUI_App.hpp"
namespace Slic3r { namespace GUI {
class DownPluginFrame : public wxDialog
{
public:
DownPluginFrame(GUI_App *pGUI);
virtual ~DownPluginFrame();
// Web Function
void load_url(wxString &url);
void UpdateState();
void OnIdle(wxIdleEvent &evt);
// void OnClose(wxCloseEvent &evt);
void OnNavigationRequest(wxWebViewEvent &evt);
void OnNavigationComplete(wxWebViewEvent &evt);
void OnDocumentLoaded(wxWebViewEvent &evt);
void OnNewWindow(wxWebViewEvent &evt);
void OnError(wxWebViewEvent &evt);
void OnTitleChanged(wxWebViewEvent &evt);
void OnFullScreenChanged(wxWebViewEvent &evt);
void OnScriptMessage(wxWebViewEvent &evt);
void OnScriptResponseMessage(wxCommandEvent &evt);
void RunScript(const wxString &javascript);
// install plugin
int DownloadPlugin();
int InstallPlugin();
int ShowPluginStatus(int status, int percent, bool &cancel);
private:
GUI_App * m_MainPtr;
AppConfig m_appconfig_new;
wxWebView *m_browser;
#if wxUSE_WEBVIEW_IE
wxMenuItem *m_script_object_el;
wxMenuItem *m_script_date_el;
wxMenuItem *m_script_array_el;
#endif
// Last executed JavaScript snippet, for convenience.
wxString m_javascript;
wxString m_response_js;
// DECLARE_EVENT_TABLE()
};
}} // namespace Slic3r::GUI
#endif /* slic3r_Tab_hpp_ */
+71 -5
View File
@@ -73,6 +73,7 @@
#include "Plater.hpp"
#include <boost/dll.hpp>
#include <slic3r/GUI/Widgets/WebView.hpp>
#include <slic3r/GUI/Widgets/WebViewHostDialog.hpp>
#include <libslic3r/Utils.hpp>
#include "CreatePresetsDialog.hpp"
#include "libslic3r/PrintConfig.hpp"
@@ -85,6 +86,71 @@ using namespace nlohmann;
namespace Slic3r { namespace GUI {
// The strings of every setup wizard page (resources/web/guide), keyed by their data-i18n and T() ids.
// The pages share one web view, so they share one table.
static json guide_strings()
{
return {
{"loading", _u8L("Loading…")},
{"welcome", _u8L("Welcome to Orca Slicer")},
{"setup_intro", _u8L("Orca Slicer will be set up in several steps. Let's start!")},
{"get_started", _u8L("Get Started")},
{"login_region", _u8L("Please select your login region")},
{"asia_pacific", _u8L("Asia-Pacific")},
{"china", _u8L("China")},
{"europe", _u8L("Europe")},
{"north_america", _u8L("North America")},
{"others", _u8L("Others")},
{"back", _u8L_CONTEXT("Back", "Navigation")},
{"next", _u8L("Next")},
{"finish", _u8L("Finish")},
{"confirm", _u8L("Confirm")},
{"cancel", _u8L("Cancel")},
{"ok", _u8L("OK")},
{"yes", _u8L("Yes")},
{"no", _u8L("No")},
{"error", _u8L("Error")},
{"information", _u8L("Information")},
{"printer_selection", _u8L("Printer Selection")},
{"vendors", _u8L("Vendors")},
{"search", _u8L("Search")},
{"custom_printer", _u8L("Custom Printer")},
{"nozzle", _u8L("Nozzle")},
{"create", _u8L("Create")},
{"check_new_printers", _u8L("Check for new printers")},
{"printer_required", _u8L("At least one printer must be selected.")},
{"new_printers_found", _u8L("New printers found")},
{"new_vendors_installed", _u8L("New printer vendors installed: %s")},
{"vendors_declined", _u8L("New printer vendors were found, but installation was cancelled.")},
{"no_new_printers", _u8L("No new printers found.")},
{"filament_selection", _u8L("Filament Selection")},
{"system_filaments", _u8L("System Filaments")},
{"custom_filaments", _u8L("Custom Filaments")},
{"create_new", _u8L("Create New")},
{"printer", _u8L("Printer")},
{"filament_type", _u8L("Filament type")},
{"vendor", _u8L("Vendor")},
{"all", _u8L("All")},
{"no_items", _u8L("No items")},
{"filter_items", _u8L("Filter items")},
{"checked", _u8L("Checked")},
{"unchecked", _u8L("Unchecked")},
{"select_filtered", _u8L("Select filtered")},
{"select_visible", _u8L("Select visible")},
{"clear_all", _u8L("Clear all")},
{"filament_required", _u8L("At least one filament must be selected.")},
{"use_default_filament", _u8L("Do you want to use the default filament?")},
{"stealth_mode", _u8L("Stealth Mode")},
{"stealth_mode_desc", _u8L("This disables all cloud features, including Orca Cloud profile syncing. Users who prefer to work entirely offline can enable this option.")},
{"stealth_mode_note", _u8L("Note: When Stealth Mode is enabled, your user profiles will not be backed up to Orca Cloud.")},
{"enable_stealth_mode", _u8L("Enable Stealth Mode.")},
{"proprietary_plugins", _u8L("Proprietary Plugins")},
{"proprietary_plugins_desc", _u8L("Some printer vendors require proprietary plugins for communication with their printers. Please select the corresponding plugin if you use such printers.")},
{"proprietary_plugins_risk", _u8L("Please be aware that these plugins are not developed or maintained by OrcaSlicer. They should be used at your own discretion and risk.")},
{"install_bambu_plugin", _u8L("Install Bambu Network plug-in")},
};
}
static wxString update_custom_filaments()
{
json m_Res = json::object();
@@ -157,6 +223,7 @@ GuideFrame::GuideFrame(GUI_App *pGUI, long style)
SetBackgroundColour(*wxWHITE);
// INI
m_SectionName = "firstguide";
m_page = BBL_WELCOME;
PrivacyUse = false;
StealthMode = false;
InstallNetplugin = false;
@@ -166,15 +233,14 @@ GuideFrame::GuideFrame(GUI_App *pGUI, long style)
// set the frame icon
wxBoxSizer *topsizer = new wxBoxSizer(wxVERTICAL);
wxString TargetUrl = SetStartPage(BBL_WELCOME, false);
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", set start page to welcome ");
// Create the webview
m_browser = WebView::CreateWebView(this, TargetUrl);
// Created empty and loaded by SetStartPage(BBL_REGION) below, after the strings are added, so the
// first load already has them.
m_browser = WebView::CreateWebView(this, wxEmptyString);
if (m_browser == nullptr) {
wxLogError("Could not init m_browser");
return;
}
m_browser->AddUserScript(wxString::FromUTF8(WebViewHostDialog::localized_strings_script(guide_strings())));
m_browser->Hide();
m_browser->SetSize(0, 0);
+24 -1
View File
@@ -38,6 +38,7 @@
#include <wx/url.h>
#include <slic3r/GUI/Widgets/WebView.hpp>
#include <slic3r/GUI/Widgets/WebViewHostDialog.hpp>
#include <wx/webview.h>
#include <wx/utils.h>
#include <wx/window.h>
@@ -54,6 +55,25 @@ namespace GUI {
#define LOGIN_INFO_UPDATE_TIMER_ID 10002
// The home page's strings (resources/web/homepage), keyed by their data-i18n and T() ids.
static json home_page_strings()
{
return {
{"login", _u8L("Login")},
{"not_signed_in", _u8L("Not signed in")},
{"plugin_not_detected", _u8L("Bambu Network plug-in not detected. Click <a>here</a> to install it.")},
{"log_out", _u8L("Log Out")},
{"recent", _u8L("Recent")},
{"new_project", _u8L("New Project")},
{"create_new_project", _u8L("Create new project")},
{"open_project", _u8L("Open Project")},
{"recently_opened", _u8L("Recently opened")},
{"clear_all", _u8L("Clear all")},
{"remove", _u8L("Remove")},
{"open_containing_folder", _u8L("Open Containing Folder")},
};
}
BEGIN_EVENT_TABLE(WebViewPanel, wxPanel)
EVT_TIMER(LOGIN_INFO_UPDATE_TIMER_ID, WebViewPanel::OnFreshLoginStatus)
END_EVENT_TABLE()
@@ -263,7 +283,10 @@ WebViewPanel::~WebViewPanel()
void WebViewPanel::create_browser()
{
m_browser = WebView::CreateWebView(this, m_home_url);
// Created empty and loaded after the strings are added, so the first load already has them.
m_browser = WebView::CreateWebView(this, wxEmptyString);
m_browser->AddUserScript(wxString::FromUTF8(WebViewHostDialog::localized_strings_script(home_page_strings())));
WebView::LoadUrl(m_browser, m_home_url);
m_browser->Hide();
}
+10 -1
View File
@@ -19,6 +19,7 @@
#include <wx/chartype.h>
#include <wx/log.h>
#include <wx/sizer.h>
#include <wx/string.h>
#include <algorithm>
#include <wx/webview.h>
@@ -152,6 +153,11 @@ std::string WebViewHostDialog::document_start_injector(const std::string& markup
return s;
}
std::string WebViewHostDialog::localized_strings_script(const nlohmann::json& strings)
{
return "window.ORCA_UI_STRINGS = " + strings.dump(-1, ' ', false, nlohmann::json::error_handler_t::ignore) + ";";
}
WebViewHostDialog::WebViewHostDialog(wxWindow* parent,
wxWindowID id,
const wxString& title,
@@ -174,7 +180,8 @@ bool WebViewHostDialog::create_webview(const std::string& resource_path,
const wxString target_url = build_resource_url(resource_path);
wxBoxSizer* topsizer = new wxBoxSizer(wxVERTICAL);
m_browser = WebView::CreateWebView(this, target_url);
// Created empty and loaded below, after the user scripts are added, so the first load already has them.
m_browser = WebView::CreateWebView(this, wxEmptyString);
if (m_browser == nullptr) {
wxLogError("Could not init m_browser");
delete topsizer;
@@ -284,6 +291,8 @@ void WebViewHostDialog::register_theme_user_scripts()
// some backends RemoveAllUserScripts() drops it too, which would break
// window.wx.postMessage / HandleStudio. Live re-theme goes through apply_theme_live().
m_browser->AddUserScript(wxString::FromUTF8(theme_user_script()));
if (const nlohmann::json strings = localized_strings(); !strings.is_null())
m_browser->AddUserScript(wxString::FromUTF8(localized_strings_script(strings)));
add_user_scripts();
}
@@ -63,6 +63,13 @@ public:
static std::string element_defaults_user_script();
// Re-themes an already-loaded page in place, for web views hosted outside a dialog.
static std::string theme_apply_script();
// Document-start user script that hands a bundled page its translated strings as
// window.ORCA_UI_STRINGS, keyed by the ids its T() lookups and data-i18n attributes use
// (resources/web/include/i18n.js). The strings come from _u8L, so they go through the same .po
// pipeline as the rest of the UI. They are in place before any page script runs; the page's
// static markup shows its English until the page's init calls TranslatePage(). A host builds them
// when it creates its web view; a language switch rebuilds the GUI and with it the host.
static std::string localized_strings_script(const nlohmann::json& strings);
protected:
wxWebView* browser() const { return m_browser; }
@@ -87,6 +94,10 @@ protected:
// added, by register_theme_user_scripts(). Default: none.
virtual void add_user_scripts() {}
// The page's translated strings, injected through localized_strings_script() when the
// dialog is created. Default: none.
virtual nlohmann::json localized_strings() const { return nullptr; }
// Pushes the current app theme into the already-loaded document without a reload
// (updates the injected :root variables and the data-orca-theme attribute).
void apply_theme_live();
+16 -1
View File
@@ -518,6 +518,21 @@ bool capability_save_config(const PluginCapabilityInterface& capability, const n
return PluginManager::instance().get_config().store_capability_config(capability_identity(capability, "save_config"), config);
}
std::string PluginConfig::capability_type_label(PluginCapabilityType type)
{
switch (type) {
case PluginCapabilityType::PrinterConnection: return _u8L("Printer connection");
case PluginCapabilityType::Pages: return _u8L("Pages");
case PluginCapabilityType::Analysis: return _u8L("Analysis");
case PluginCapabilityType::Importer: return _u8L("Importer");
case PluginCapabilityType::Exporter: return _u8L("Exporter");
case PluginCapabilityType::Visualization: return _u8L("Visualization");
case PluginCapabilityType::Script: return _u8L("Script");
case PluginCapabilityType::SlicingPipeline: return _u8L("Slicing Pipeline");
default: return _u8L("Unknown");
}
}
nlohmann::json PluginConfig::capabilities_payload(const std::vector<PluginCapabilityId>& caps)
{
nlohmann::json payload = nlohmann::json::array();
@@ -530,7 +545,7 @@ nlohmann::json PluginConfig::capabilities_payload(const std::vector<PluginCapabi
nlohmann::json entry;
entry["plugin_key"] = id.plugin_key;
entry["name"] = id.name;
entry["type"] = plugin_capability_type_display_name(id.type);
entry["type"] = capability_type_label(id.type);
entry["type_key"] = plugin_capability_type_to_string(id.type);
entry["has_config_ui"] = capability->config_ui_available();
payload.push_back(std::move(entry));
+3
View File
@@ -109,6 +109,9 @@ public:
bool dirty() const;
// The capability type as the plugin dialogs show it, translated; plugin_capability_type_display_name()
// is the English name the logs use.
static std::string capability_type_label(PluginCapabilityType type);
static nlohmann::json capabilities_payload(const std::vector<PluginCapabilityId>& caps);
static nlohmann::json get_config_response(const PluginCapabilityId& id);
static nlohmann::json save_config_response(const PluginCapabilityId& id, const nlohmann::json& config);