From e8de4b7199d0a1b999a57ca9306f402b6e6b638b Mon Sep 17 00:00:00 2001 From: David Kocik Date: Tue, 20 Aug 2024 12:24:07 +0200 Subject: [PATCH 01/12] Create Printables tab --- src/slic3r/GUI/MainFrame.cpp | 28 ++++++++++++++++++++++++++++ src/slic3r/GUI/MainFrame.hpp | 14 ++++++++++---- src/slic3r/GUI/Plater.cpp | 4 ++++ 3 files changed, 42 insertions(+), 4 deletions(-) diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index f67643b55d..cbfba459a5 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -803,6 +803,7 @@ void MainFrame::create_preset_tabs() m_connect_webview = new ConnectWebViewPanel(m_tabpanel); m_printer_webview = new PrinterWebViewPanel(m_tabpanel, L""); + m_printables_webview = new WebViewPanel(m_tabpanel, GUI::from_u8("https://www.printables.com"), { "_prusaSlicer" }); // new created tabs have to be hidden by default m_connect_webview->Hide(); m_printer_webview->Hide(); @@ -849,6 +850,33 @@ void MainFrame::show_connect_tab(const wxString& url) m_connect_webview->load_url(url); } +void MainFrame::add_printables_webview_tab() +{ + if (m_printables_webview_added) { + return; + } + + int n = m_tabpanel->FindPage(m_connect_webview) + 1; + wxWindow* page = m_printables_webview; + const wxString text(L"Printables"); + const std::string bmp_name = ""; + bool bSelect = false; + m_tabpanel->InsertNewPage(n, page, text, bmp_name, bSelect); + m_printables_webview->load_default_url_delayed(); + m_printables_webview_added = true; +} +void MainFrame::remove_printables_webview_tab() +{ + if (!m_printables_webview_added) { + return; + } + int n = m_tabpanel->FindPage(m_printables_webview); + if (m_tabpanel->GetSelection() == n) + m_tabpanel->SetSelection(0); + m_tabpanel->RemovePage(size_t(n)); + m_printables_webview_added = false; +} + void MainFrame::show_printer_webview_tab(DynamicPrintConfig* dpc) { // if physical printer is selected diff --git a/src/slic3r/GUI/MainFrame.hpp b/src/slic3r/GUI/MainFrame.hpp index 4b2306ec23..4cb184605d 100644 --- a/src/slic3r/GUI/MainFrame.hpp +++ b/src/slic3r/GUI/MainFrame.hpp @@ -47,6 +47,7 @@ class PreferencesDialog; class GalleryDialog; class ConnectWebViewPanel; class PrinterWebViewPanel; +class WebViewPanel; enum QuickSlice { @@ -98,10 +99,12 @@ class MainFrame : public DPIFrame size_t m_last_selected_tab; Search::OptionsSearcher m_searcher; - ConnectWebViewPanel* m_connect_webview{ nullptr }; - bool m_connect_webview_added{ false }; - PrinterWebViewPanel* m_printer_webview{ nullptr }; - bool m_printer_webview_added{ false }; + ConnectWebViewPanel* m_connect_webview{ nullptr }; + bool m_connect_webview_added{ false }; + WebViewPanel* m_printables_webview{ nullptr }; + bool m_printables_webview_added{ false }; + PrinterWebViewPanel* m_printer_webview{ nullptr }; + bool m_printer_webview_added{ false }; std::string get_base_name(const wxString &full_name, const char *extension = nullptr) const; std::string get_dir_name(const wxString &full_name) const; @@ -218,6 +221,9 @@ public: void remove_connect_webview_tab(); void show_connect_tab(const wxString &url); + void add_printables_webview_tab(); + void remove_printables_webview_tab(); + void show_printer_webview_tab(DynamicPrintConfig* dpc); void add_printer_webview_tab(const wxString& url); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 681d12ca83..3d84240587 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -921,6 +921,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) this->notification_manager->close_notification_of_type(NotificationType::UserAccountID); this->notification_manager->push_notification(NotificationType::UserAccountID, NotificationManager::NotificationLevel::ImportantNotificationLevel, text); this->main_frame->remove_connect_webview_tab(); + this->main_frame->remove_printables_webview_tab(); this->main_frame->refresh_account_menu(true); // Update sidebar printer status sidebar->update_printer_presets_combobox(); @@ -946,6 +947,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) this->notification_manager->push_notification(NotificationType::UserAccountID, NotificationManager::NotificationLevel::ImportantNotificationLevel, text); } this->main_frame->add_connect_webview_tab(); + this->main_frame->add_printables_webview_tab(); // Update User name in TopBar this->main_frame->refresh_account_menu(); wxGetApp().update_wizard_login_page(); @@ -959,6 +961,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) this->notification_manager->close_notification_of_type(NotificationType::UserAccountID); this->notification_manager->push_notification(NotificationType::UserAccountID, NotificationManager::NotificationLevel::WarningNotificationLevel, _u8L("Failed to connect to Prusa Account.")); this->main_frame->remove_connect_webview_tab(); + this->main_frame->remove_printables_webview_tab(); // Update User name in TopBar this->main_frame->refresh_account_menu(true); // Update sidebar printer status @@ -972,6 +975,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) this->notification_manager->close_notification_of_type(NotificationType::UserAccountID); this->notification_manager->push_notification(NotificationType::UserAccountID, NotificationManager::NotificationLevel::WarningNotificationLevel, _u8L("Failed to connect to Prusa Account.")); this->main_frame->remove_connect_webview_tab(); + this->main_frame->remove_printables_webview_tab(); // Update User name in TopBar this->main_frame->refresh_account_menu(true); // Update sidebar printer status From 1040332c87b89e1870d80756d8eb5fd7483f9c2b Mon Sep 17 00:00:00 2001 From: David Kocik Date: Wed, 4 Sep 2024 15:06:19 +0200 Subject: [PATCH 02/12] SPE-2468: Printables WebView class read address and download model Printables as pernament tab webview debug panel adding token to request header Logout to Printables Correct user-agent Receiving printables events Improved refreshing of token to printables User settings to printables url merge changes Changed login to Printables during runtime Logging in plater fix --- src/slic3r/CMakeLists.txt | 4 + src/slic3r/GUI/ConnectRequestHandler.cpp | 133 +++ src/slic3r/GUI/ConnectRequestHandler.hpp | 44 + src/slic3r/GUI/Downloader.cpp | 41 +- src/slic3r/GUI/DownloaderFileGet.cpp | 15 - src/slic3r/GUI/DownloaderFileGet.hpp | 1 - src/slic3r/GUI/GUI_App.cpp | 7 +- src/slic3r/GUI/GUI_App.hpp | 2 +- src/slic3r/GUI/MainFrame.cpp | 36 +- src/slic3r/GUI/MainFrame.hpp | 13 +- src/slic3r/GUI/Plater.cpp | 27 +- src/slic3r/GUI/UserAccountSession.cpp | 2 + src/slic3r/GUI/UserAccountSession.hpp | 1 + src/slic3r/GUI/WebView.cpp | 11 +- src/slic3r/GUI/WebViewDialog.cpp | 903 --------------- src/slic3r/GUI/WebViewDialog.hpp | 177 +-- src/slic3r/GUI/WebViewPanel.cpp | 1077 ++++++++++++++++++ src/slic3r/GUI/WebViewPanel.hpp | 195 ++++ src/slic3r/GUI/WebViewPlatformUtils.hpp | 3 + src/slic3r/GUI/WebViewPlatformUtilsLinux.cpp | 39 +- src/slic3r/GUI/WebViewPlatformUtilsMac.mm | 22 +- src/slic3r/GUI/WebViewPlatformUtilsWin32.cpp | 145 +++ src/slic3r/Utils/ServiceConfig.cpp | 5 +- src/slic3r/Utils/ServiceConfig.hpp | 3 + 24 files changed, 1769 insertions(+), 1137 deletions(-) create mode 100644 src/slic3r/GUI/ConnectRequestHandler.cpp create mode 100644 src/slic3r/GUI/ConnectRequestHandler.hpp create mode 100644 src/slic3r/GUI/WebViewPanel.cpp create mode 100644 src/slic3r/GUI/WebViewPanel.hpp diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 608043efdf..9604c77d66 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -29,6 +29,10 @@ set(SLIC3R_GUI_SOURCES GUI/UserAccount.hpp GUI/WebViewDialog.cpp GUI/WebViewDialog.hpp + GUI/WebViewPanel.cpp + GUI/WebViewPanel.hpp + GUI/ConnectRequestHandler.cpp + GUI/ConnectRequestHandler.hpp GUI/WebView.cpp GUI/WebView.hpp GUI/WebViewPlatformUtils.hpp diff --git a/src/slic3r/GUI/ConnectRequestHandler.cpp b/src/slic3r/GUI/ConnectRequestHandler.cpp new file mode 100644 index 0000000000..7a156bc78b --- /dev/null +++ b/src/slic3r/GUI/ConnectRequestHandler.cpp @@ -0,0 +1,133 @@ +#include "ConnectRequestHandler.hpp" + +#include "slic3r/GUI/I18N.hpp" +#include "slic3r/GUI/format.hpp" +#include "slic3r/GUI/GUI_App.hpp" +#include "slic3r/GUI/GUI.hpp" +#include "slic3r/GUI/Plater.hpp" +#include "slic3r/GUI/UserAccount.hpp" + +#include +#include +#include + +namespace pt = boost::property_tree; + +namespace Slic3r::GUI { + +ConnectRequestHandler::ConnectRequestHandler() +{ + m_actions["REQUEST_LOGIN"] = std::bind(&ConnectRequestHandler::on_connect_action_request_login, this, std::placeholders::_1); + m_actions["REQUEST_CONFIG"] = std::bind(&ConnectRequestHandler::on_connect_action_request_config, this, std::placeholders::_1); + m_actions["WEBAPP_READY"] = std::bind(&ConnectRequestHandler::on_connect_action_webapp_ready,this, std::placeholders::_1); + m_actions["SELECT_PRINTER"] = std::bind(&ConnectRequestHandler::on_connect_action_select_printer, this, std::placeholders::_1); + m_actions["PRINT"] = std::bind(&ConnectRequestHandler::on_connect_action_print, this, std::placeholders::_1); + m_actions["REQUEST_OPEN_IN_BROWSER"] = std::bind(&ConnectRequestHandler::on_connect_action_request_open_in_browser, this, std::placeholders::_1); + m_actions["ERROR"] = std::bind(&ConnectRequestHandler::on_connect_action_error, this, std::placeholders::_1); + m_actions["LOG"] = std::bind(&ConnectRequestHandler::on_connect_action_log, this, std::placeholders::_1); +} +ConnectRequestHandler::~ConnectRequestHandler() +{ +} +void ConnectRequestHandler::handle_message(const std::string& message) +{ + // read msg and choose action + /* + v0: + {"type":"request","detail":{"action":"requestAccessToken"}} + v1: + {"action":"REQUEST_ACCESS_TOKEN"} + */ + std::string action_string; + try { + std::stringstream ss(message); + pt::ptree ptree; + pt::read_json(ss, ptree); + // v1: + if (const auto action = ptree.get_optional("action"); action) { + action_string = *action; + } + } + catch (const std::exception& e) { + BOOST_LOG_TRIVIAL(error) << "Could not parse _prusaConnect message. " << e.what(); + return; + } + + if (action_string.empty()) { + BOOST_LOG_TRIVIAL(error) << "Recieved invalid message from _prusaConnect (missing action). Message: " << message; + return; + } + assert(m_actions.find(action_string) != m_actions.end()); // this assert means there is a action that has no handling. + if (m_actions.find(action_string) != m_actions.end()) { + m_actions[action_string](message); + } +} + +void ConnectRequestHandler::on_connect_action_error(const std::string &message_data) +{ + BOOST_LOG_TRIVIAL(error) << "WebView runtime error: " << message_data; +} + +void ConnectRequestHandler::resend_config() +{ + on_connect_action_request_config({}); +} + +void ConnectRequestHandler::on_connect_action_log(const std::string& message_data) +{ + BOOST_LOG_TRIVIAL(info) << "WebView log: " << message_data; +} + +void ConnectRequestHandler::on_connect_action_request_login(const std::string &message_data) +{} + +void ConnectRequestHandler::on_connect_action_request_config(const std::string& message_data) +{ + /* + accessToken?: string; + clientVersion?: string; + colorMode?: "LIGHT" | "DARK"; + language?: ConnectLanguage; + sessionId?: string; + */ + const std::string token = wxGetApp().plater()->get_user_account()->get_access_token(); + //const std::string sesh = wxGetApp().plater()->get_user_account()->get_shared_session_key(); + const std::string dark_mode = wxGetApp().dark_mode() ? "DARK" : "LIGHT"; + wxString language = GUI::wxGetApp().current_language_code(); + language = language.SubString(0, 1); + const std::string init_options = GUI::format("{\"accessToken\": \"%4%\",\"clientVersion\": \"%1%\", \"colorMode\": \"%2%\", \"language\": \"%3%\"}", SLIC3R_VERSION, dark_mode, language, token ); + wxString script = GUI::format_wxstr("window._prusaConnect_v1.init(%1%)", init_options); + run_script_bridge(script); + +} +void ConnectRequestHandler::on_connect_action_request_open_in_browser(const std::string& message_data) +{ + try { + std::stringstream ss(message_data); + pt::ptree ptree; + pt::read_json(ss, ptree); + if (const auto url = ptree.get_optional("url"); url) { + wxGetApp().open_browser_with_warning_dialog(GUI::from_u8(*url)); + } + } catch (const std::exception &e) { + BOOST_LOG_TRIVIAL(error) << "Could not parse _prusaConnect message. " << e.what(); + return; + } +} + +SourceViewDialog::SourceViewDialog(wxWindow* parent, wxString source) : + wxDialog(parent, wxID_ANY, "Source Code", + wxDefaultPosition, wxSize(700,500), + wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) +{ + wxTextCtrl* text = new wxTextCtrl(this, wxID_ANY, source, + wxDefaultPosition, wxDefaultSize, + wxTE_MULTILINE | + wxTE_RICH | + wxTE_READONLY); + + wxBoxSizer* sizer = new wxBoxSizer(wxVERTICAL); + sizer->Add(text, 1, wxEXPAND); + SetSizer(sizer); +} +} // namespace Slic3r::GUI \ No newline at end of file diff --git a/src/slic3r/GUI/ConnectRequestHandler.hpp b/src/slic3r/GUI/ConnectRequestHandler.hpp new file mode 100644 index 0000000000..ea64913977 --- /dev/null +++ b/src/slic3r/GUI/ConnectRequestHandler.hpp @@ -0,0 +1,44 @@ +#ifndef slic3r_ConnectRequestHandler_hpp_ +#define slic3r_ConnectRequestHandler_hpp_ + +#include +#include +#include +#include +#include +#include + +//#define DEBUG_URL_PANEL + +namespace Slic3r::GUI { +class ConnectRequestHandler +{ +public: + ConnectRequestHandler(); + ~ConnectRequestHandler(); + + void handle_message(const std::string& message); + void resend_config(); +protected: + // action callbacs stored in m_actions + virtual void on_connect_action_log(const std::string& message_data); + virtual void on_connect_action_error(const std::string& message_data); + virtual void on_connect_action_request_login(const std::string& message_data); + virtual void on_connect_action_request_config(const std::string& message_data); + virtual void on_connect_action_request_open_in_browser(const std::string& message_data); + virtual void on_connect_action_select_printer(const std::string& message_data) = 0; + virtual void on_connect_action_print(const std::string& message_data) = 0; + virtual void on_connect_action_webapp_ready(const std::string& message_data) = 0; + virtual void run_script_bridge(const wxString &script) = 0; + + std::map> m_actions; +}; + +class SourceViewDialog : public wxDialog +{ +public: + SourceViewDialog(wxWindow* parent, wxString source); +}; + +} // namespace Slic3r::GUI +#endif /* slic3r_ConnectRequestHandler_hpp_ */ \ No newline at end of file diff --git a/src/slic3r/GUI/Downloader.cpp b/src/slic3r/GUI/Downloader.cpp index c5ee0ff373..61e69227bb 100644 --- a/src/slic3r/GUI/Downloader.cpp +++ b/src/slic3r/GUI/Downloader.cpp @@ -9,6 +9,7 @@ #include #include +#include namespace Slic3r { namespace GUI { @@ -72,6 +73,21 @@ std::string filename_from_url(const std::string& url) return std::string(); return std::string(url_plain.begin() + slash + 1, url_plain.end()); } +std::string unescape_url(const std::string& unescaped) +{ + std::string ret_val; + CURL* curl = curl_easy_init(); + if (curl) { + int decodelen; + char* decoded = curl_easy_unescape(curl, unescaped.c_str(), unescaped.size(), &decodelen); + if (decoded) { + ret_val = std::string(decoded); + curl_free(decoded); + } + curl_easy_cleanup(curl); + } + return ret_val; +} } Download::Download(int ID, std::string url, wxEvtHandler* evt_handler, const boost::filesystem::path& dest_folder) @@ -133,23 +149,18 @@ void Downloader::start_download(const std::string& full_url) { assert(m_initialized); - // TODO: There is a misterious slash appearing in recieved msg on windows -#ifdef _WIN32 - if (!boost::starts_with(full_url, "prusaslicer://open/?file=")) { -#else - if (!boost::starts_with(full_url, "prusaslicer://open?file=")) { -#endif - BOOST_LOG_TRIVIAL(error) << "Could not start download due to wrong URL: " << full_url; - // TODO: show error? + std::string escaped_url = unescape_url(full_url); + if (boost::starts_with(escaped_url, "prusaslicer://open?file=")) { + escaped_url = escaped_url.substr(24); + }else if (boost::starts_with(escaped_url, "prusaslicer://open/?file=")) { + escaped_url = escaped_url.substr(25); + } else { + BOOST_LOG_TRIVIAL(error) << "Could not start download due to wrong URL: " << full_url; return; - } + } + size_t id = get_next_id(); - // TODO: still same mistery -#ifdef _WIN32 - std::string escaped_url = FileGet::escape_url(full_url.substr(25)); -#else - std::string escaped_url = FileGet::escape_url(full_url.substr(24)); -#endif + if (!boost::starts_with(escaped_url, "https://") || !FileGet::is_subdomain(escaped_url, "printables.com")) { std::string msg = format(_L("Download won't start. Download URL doesn't point to https://printables.com : %1%"), escaped_url); BOOST_LOG_TRIVIAL(error) << msg; diff --git a/src/slic3r/GUI/DownloaderFileGet.cpp b/src/slic3r/GUI/DownloaderFileGet.cpp index 7a401bf8d0..8cbb89abcd 100644 --- a/src/slic3r/GUI/DownloaderFileGet.cpp +++ b/src/slic3r/GUI/DownloaderFileGet.cpp @@ -23,21 +23,6 @@ namespace GUI { const size_t DOWNLOAD_MAX_CHUNK_SIZE = 10 * 1024 * 1024; const size_t DOWNLOAD_SIZE_LIMIT = 1024 * 1024 * 1024; -std::string FileGet::escape_url(const std::string& unescaped) -{ - std::string ret_val; - CURL* curl = curl_easy_init(); - if (curl) { - int decodelen; - char* decoded = curl_easy_unescape(curl, unescaped.c_str(), unescaped.size(), &decodelen); - if (decoded) { - ret_val = std::string(decoded); - curl_free(decoded); - } - curl_easy_cleanup(curl); - } - return ret_val; -} bool FileGet::is_subdomain(const std::string& url, const std::string& domain) { // domain should be f.e. printables.com (.com including) diff --git a/src/slic3r/GUI/DownloaderFileGet.hpp b/src/slic3r/GUI/DownloaderFileGet.hpp index 37a59ec30e..7859d52f62 100644 --- a/src/slic3r/GUI/DownloaderFileGet.hpp +++ b/src/slic3r/GUI/DownloaderFileGet.hpp @@ -27,7 +27,6 @@ public: void cancel(); void pause(); void resume(); - static std::string escape_url(const std::string& url); static bool is_subdomain(const std::string& url, const std::string& domain); private: std::unique_ptr p; diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 879fc090ba..a9836a36f7 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -103,7 +103,6 @@ #include "WifiConfigDialog.hpp" #include "UserAccount.hpp" #include "UserAccountUtils.hpp" -#include "WebViewDialog.hpp" #include "LoginDialog.hpp" // IWYU pragma: keep #include "PresetArchiveDatabase.hpp" @@ -4113,6 +4112,12 @@ void GUI_App::show_printer_webview_tab() mainframe->show_printer_webview_tab(preset_bundle->physical_printers.get_selected_printer_config()); } +void GUI_App::printables_request(const std::string& url) +{ + this->mainframe->select_tab(size_t(0)); + start_download(url); +} + bool LogGui::ignorred_message(const wxString& msg) diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index 08ef7667ca..7b18a0d324 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -436,7 +436,7 @@ public: void request_project_download(std::string project_id) {} void request_open_project(std::string project_id) {} void request_remove_project(std::string project_id) {} - + void printables_request(const std::string& url); private: bool on_init_inner(); void init_app_config(); diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index cbfba459a5..94a18ba293 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -64,7 +64,7 @@ #include "GalleryDialog.hpp" #include "NotificationManager.hpp" #include "Preferences.hpp" -#include "WebViewDialog.hpp" +#include "WebViewPanel.hpp" #include "UserAccount.hpp" #ifdef _WIN32 @@ -801,15 +801,39 @@ void MainFrame::create_preset_tabs() add_created_tab(new TabSLAMaterial(m_tabpanel), "resin"); add_created_tab(new TabPrinter(m_tabpanel), wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptFFF ? "printer" : "sla_printer"); + m_printables_webview = new PrintablesWebViewPanel(m_tabpanel); + add_printables_webview_tab(); + m_connect_webview = new ConnectWebViewPanel(m_tabpanel); m_printer_webview = new PrinterWebViewPanel(m_tabpanel, L""); - m_printables_webview = new WebViewPanel(m_tabpanel, GUI::from_u8("https://www.printables.com"), { "_prusaSlicer" }); + // new created tabs have to be hidden by default m_connect_webview->Hide(); m_printer_webview->Hide(); } +void MainFrame::on_account_login(const std::string& token) +{ + add_connect_webview_tab(); + assert (m_printables_webview); + m_printables_webview->login(token); +} +void MainFrame::on_account_will_refresh() +{ + m_printables_webview->send_will_refresh(); +} +void MainFrame::on_account_did_refresh(const std::string& token) +{ + m_printables_webview->send_refreshed_token(token); +} +void MainFrame::on_account_logout() +{ + remove_connect_webview_tab(); + assert (m_printables_webview); + m_printables_webview->logout(); +} + void MainFrame::add_connect_webview_tab() { if (m_connect_webview_added) { @@ -820,7 +844,7 @@ void MainFrame::add_connect_webview_tab() // insert "Connect" tab to position next to "Printer" tab // order of tabs: Plater - Print Settings - Filaments - Printers - Prusa Connect - Prusa Link - int n = m_tabpanel->FindPage(wxGetApp().get_tab(Preset::TYPE_PRINTER)) + 1; + int n = m_tabpanel->FindPage(m_printables_webview) + 1; wxWindow* page = m_connect_webview; const wxString text(L"Prusa Connect"); const std::string bmp_name = ""; @@ -856,7 +880,7 @@ void MainFrame::add_printables_webview_tab() return; } - int n = m_tabpanel->FindPage(m_connect_webview) + 1; + int n = m_tabpanel->FindPage(wxGetApp().get_tab(Preset::TYPE_PRINTER)) + 1; wxWindow* page = m_printables_webview; const wxString text(L"Printables"); const std::string bmp_name = ""; @@ -865,6 +889,8 @@ void MainFrame::add_printables_webview_tab() m_printables_webview->load_default_url_delayed(); m_printables_webview_added = true; } + +// no longer needed? void MainFrame::remove_printables_webview_tab() { if (!m_printables_webview_added) { @@ -1221,6 +1247,8 @@ void MainFrame::on_sys_color_changed() for (Tab* tab : wxGetApp().tabs_list) tab->sys_color_changed(); + if (m_printables_webview) + m_printables_webview->sys_color_changed(); if (m_connect_webview) m_connect_webview->sys_color_changed(); if (m_printer_webview) diff --git a/src/slic3r/GUI/MainFrame.hpp b/src/slic3r/GUI/MainFrame.hpp index 4cb184605d..359961fe90 100644 --- a/src/slic3r/GUI/MainFrame.hpp +++ b/src/slic3r/GUI/MainFrame.hpp @@ -47,7 +47,7 @@ class PreferencesDialog; class GalleryDialog; class ConnectWebViewPanel; class PrinterWebViewPanel; -class WebViewPanel; +class PrintablesWebViewPanel; enum QuickSlice { @@ -101,7 +101,7 @@ class MainFrame : public DPIFrame ConnectWebViewPanel* m_connect_webview{ nullptr }; bool m_connect_webview_added{ false }; - WebViewPanel* m_printables_webview{ nullptr }; + PrintablesWebViewPanel* m_printables_webview{ nullptr }; bool m_printables_webview_added{ false }; PrinterWebViewPanel* m_printer_webview{ nullptr }; bool m_printer_webview_added{ false }; @@ -127,6 +127,9 @@ class MainFrame : public DPIFrame bool can_delete_all() const; bool can_reslice() const; + void add_connect_webview_tab(); + void remove_connect_webview_tab(); + // MenuBar items changeable in respect to printer technology enum MenuItems { // FFF SLA @@ -217,8 +220,10 @@ public: void add_to_recent_projects(const wxString& filename); void technology_changed(); - void add_connect_webview_tab(); - void remove_connect_webview_tab(); + void on_account_login(const std::string& token); + void on_account_will_refresh(); + void on_account_did_refresh(const std::string& token); + void on_account_logout(); void show_connect_tab(const wxString &url); void add_printables_webview_tab(); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 3d84240587..395f29dc8d 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -915,13 +915,16 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) this->q->Bind(EVT_OPEN_EXTERNAL_LOGIN_WIZARD, open_external_login); this->q->Bind(EVT_OPEN_EXTERNAL_LOGIN, open_external_login); + // void on_account_login(const std::string& token); + // void on_account_will_refresh(); + // void on_account_did_refresh(const std::string& token); + // void on_account_logout(); this->q->Bind(EVT_UA_LOGGEDOUT, [this](UserAccountSuccessEvent& evt) { user_account->clear(); std::string text = _u8L("Logged out from Prusa Account."); this->notification_manager->close_notification_of_type(NotificationType::UserAccountID); this->notification_manager->push_notification(NotificationType::UserAccountID, NotificationManager::NotificationLevel::ImportantNotificationLevel, text); - this->main_frame->remove_connect_webview_tab(); - this->main_frame->remove_printables_webview_tab(); + this->main_frame->on_account_logout(); this->main_frame->refresh_account_menu(true); // Update sidebar printer status sidebar->update_printer_presets_combobox(); @@ -938,16 +941,19 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) std::string who = user_account->get_username(); std::string username; if (user_account->on_user_id_success(evt.data, username)) { - // Do not show notification on refresh. if (who != username) { + // show notification only on login (not refresh). std::string text = format(_u8L("Logged to Prusa Account as %1%."), username); // login notification this->notification_manager->close_notification_of_type(NotificationType::UserAccountID); // show connect tab this->notification_manager->push_notification(NotificationType::UserAccountID, NotificationManager::NotificationLevel::ImportantNotificationLevel, text); + + this->main_frame->on_account_login(user_account->get_access_token()); + } else { + // refresh do different operations than on_account_login + this->main_frame->on_account_did_refresh(user_account->get_access_token()); } - this->main_frame->add_connect_webview_tab(); - this->main_frame->add_printables_webview_tab(); // Update User name in TopBar this->main_frame->refresh_account_menu(); wxGetApp().update_wizard_login_page(); @@ -960,8 +966,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) user_account->clear(); this->notification_manager->close_notification_of_type(NotificationType::UserAccountID); this->notification_manager->push_notification(NotificationType::UserAccountID, NotificationManager::NotificationLevel::WarningNotificationLevel, _u8L("Failed to connect to Prusa Account.")); - this->main_frame->remove_connect_webview_tab(); - this->main_frame->remove_printables_webview_tab(); + this->main_frame->on_account_logout(); // Update User name in TopBar this->main_frame->refresh_account_menu(true); // Update sidebar printer status @@ -974,8 +979,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) user_account->clear(); this->notification_manager->close_notification_of_type(NotificationType::UserAccountID); this->notification_manager->push_notification(NotificationType::UserAccountID, NotificationManager::NotificationLevel::WarningNotificationLevel, _u8L("Failed to connect to Prusa Account.")); - this->main_frame->remove_connect_webview_tab(); - this->main_frame->remove_printables_webview_tab(); + this->main_frame->on_account_logout(); // Update User name in TopBar this->main_frame->refresh_account_menu(true); // Update sidebar printer status @@ -1036,7 +1040,10 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) this->q->Bind(EVT_UA_REFRESH_TIME, [this](UserAccountTimeEvent& evt) { this->user_account->set_refresh_time(evt.data); - }); + }); + this->q->Bind(EVT_UA_ENQUEUED_REFRESH, [this](SimpleEvent& evt) { + this->main_frame->on_account_will_refresh(); + }); } wxGetApp().other_instance_message_handler()->init(this->q); diff --git a/src/slic3r/GUI/UserAccountSession.cpp b/src/slic3r/GUI/UserAccountSession.cpp index 9c18cad514..1ff4f22f18 100644 --- a/src/slic3r/GUI/UserAccountSession.cpp +++ b/src/slic3r/GUI/UserAccountSession.cpp @@ -31,6 +31,7 @@ wxDEFINE_EVENT(EVT_UA_FAIL, UserAccountFailEvent); wxDEFINE_EVENT(EVT_UA_RESET, UserAccountFailEvent); wxDEFINE_EVENT(EVT_UA_PRUSACONNECT_PRINTER_DATA_FAIL, UserAccountFailEvent); wxDEFINE_EVENT(EVT_UA_REFRESH_TIME, UserAccountTimeEvent); +wxDEFINE_EVENT(EVT_UA_ENQUEUED_REFRESH, SimpleEvent); void UserActionPost::perform(/*UNUSED*/ wxEvtHandler* evt_handler, /*UNUSED*/ const std::string& access_token, UserActionSuccessFn success_callback, UserActionFailFn fail_callback, const std::string& input) const { @@ -227,6 +228,7 @@ void UserAccountSession::enqueue_test_with_refresh() void UserAccountSession::enqueue_refresh(const std::string& body) { + wxQueueEvent(p_evt_handler, new SimpleEvent(EVT_UA_ENQUEUED_REFRESH)); std::string post_fields; { std::lock_guard lock(m_credentials_mutex); diff --git a/src/slic3r/GUI/UserAccountSession.hpp b/src/slic3r/GUI/UserAccountSession.hpp index 223af17d30..6b3d793d3c 100644 --- a/src/slic3r/GUI/UserAccountSession.hpp +++ b/src/slic3r/GUI/UserAccountSession.hpp @@ -30,6 +30,7 @@ wxDECLARE_EVENT(EVT_UA_FAIL, UserAccountFailEvent); // Soft fail - clears only a wxDECLARE_EVENT(EVT_UA_RESET, UserAccountFailEvent); // Hard fail - clears all wxDECLARE_EVENT(EVT_UA_PRUSACONNECT_PRINTER_DATA_FAIL, UserAccountFailEvent); // Failed to get data for printer to select, soft fail, action does not repeat wxDECLARE_EVENT(EVT_UA_REFRESH_TIME, UserAccountTimeEvent); +wxDECLARE_EVENT(EVT_UA_ENQUEUED_REFRESH, SimpleEvent); typedef std::function UserActionSuccessFn; typedef std::function UserActionFailFn; diff --git a/src/slic3r/GUI/WebView.cpp b/src/slic3r/GUI/WebView.cpp index 85a537ae6c..b236b29155 100644 --- a/src/slic3r/GUI/WebView.cpp +++ b/src/slic3r/GUI/WebView.cpp @@ -1,6 +1,9 @@ #include "WebView.hpp" #include "slic3r/GUI/GUI_App.hpp" #include "slic3r/GUI/GUI.hpp" +#include "slic3r/GUI/format.hpp" + +#include "libslic3r/Platform.hpp" #include #include @@ -21,9 +24,9 @@ wxWebView* WebView::CreateWebView(wxWindow * parent, const wxString& url, const if (webView) { wxString correct_url = url.empty() ? wxString("") : wxURI(url).BuildURI(); - + wxString user_agent = Slic3r::GUI::format_wxstr("%1%/%2% (%3%)",SLIC3R_APP_FULL_NAME, SLIC3R_VERSION, Slic3r::platform_to_string(Slic3r::platform())); #ifdef __WIN32__ - webView->SetUserAgent(SLIC3R_APP_FULL_NAME); + webView->SetUserAgent(user_agent); webView->Create(parent, wxID_ANY, correct_url, wxDefaultPosition, wxDefaultSize); //We register the wxfs:// protocol for testing purposes //webView->RegisterHandler(wxSharedPtr(new wxWebViewArchiveHandler("wxfs"))); @@ -35,7 +38,7 @@ wxWebView* WebView::CreateWebView(wxWindow * parent, const wxString& url, const // And the memory: file system //webView->RegisterHandler(wxSharedPtr(new wxWebViewFSHandler("memory"))); webView->Create(parent, wxID_ANY, correct_url, wxDefaultPosition, wxDefaultSize); - webView->SetUserAgent(wxString::FromUTF8(SLIC3R_APP_FULL_NAME)); + webView->SetUserAgent(user_agent); #endif #ifndef __WIN32__ Slic3r::GUI::wxGetApp().CallAfter([message_handlers, webView] { @@ -57,5 +60,3 @@ wxWebView* WebView::CreateWebView(wxWindow * parent, const wxString& url, const } return webView; } - - diff --git a/src/slic3r/GUI/WebViewDialog.cpp b/src/slic3r/GUI/WebViewDialog.cpp index 9ce243c6ca..52bacaa391 100644 --- a/src/slic3r/GUI/WebViewDialog.cpp +++ b/src/slic3r/GUI/WebViewDialog.cpp @@ -22,11 +22,6 @@ #include #include -// if set to 1 the fetch() JS function gets override to include JWT in authorization header -// if set to 0, the /slicer/login is invoked from WebKit (passing JWT token only to this request) -// to set authorization cookie for all WebKit requests to Connect -#define AUTH_VIA_FETCH_OVERRIDE 0 - wxDEFINE_EVENT(EVT_OPEN_EXTERNAL_LOGIN, wxCommandEvent); namespace pt = boost::property_tree; @@ -34,904 +29,6 @@ namespace pt = boost::property_tree; namespace Slic3r { namespace GUI { - -WebViewPanel::~WebViewPanel() -{ - SetEvtHandlerEnabled(false); -#ifdef DEBUG_URL_PANEL - delete m_tools_menu; -#endif -} - -void WebViewPanel::load_url(const wxString& url) -{ - if (!m_browser) - return; - - this->on_page_will_load(); - - this->Show(); - this->Raise(); -#ifdef DEBUG_URL_PANEL - m_url->SetLabelText(url); -#endif - m_browser->LoadURL(url); - m_browser->SetFocus(); -} - - -WebViewPanel::WebViewPanel(wxWindow *parent, const wxString& default_url, const std::vector& message_handler_names, const std::string& loading_html/* = "loading"*/) - : wxPanel(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize) - , m_default_url (default_url) - , m_loading_html(loading_html) - , m_script_message_hadler_names(message_handler_names) -{ - wxBoxSizer* topsizer = new wxBoxSizer(wxVERTICAL); -#ifdef DEBUG_URL_PANEL - // Create the button - bSizer_toolbar = new wxBoxSizer(wxHORIZONTAL); - - m_button_back = new wxButton(this, wxID_ANY, wxT("Back"), wxDefaultPosition, wxDefaultSize, 0); - m_button_back->Enable(false); - bSizer_toolbar->Add(m_button_back, 0, wxALL, 5); - - m_button_forward = new wxButton(this, wxID_ANY, wxT("Forward"), wxDefaultPosition, wxDefaultSize, 0); - m_button_forward->Enable(false); - bSizer_toolbar->Add(m_button_forward, 0, wxALL, 5); - - m_button_stop = new wxButton(this, wxID_ANY, wxT("Stop"), wxDefaultPosition, wxDefaultSize, 0); - - bSizer_toolbar->Add(m_button_stop, 0, wxALL, 5); - - m_button_reload = new wxButton(this, wxID_ANY, wxT("Reload"), wxDefaultPosition, wxDefaultSize, 0); - bSizer_toolbar->Add(m_button_reload, 0, wxALL, 5); - - m_url = new wxTextCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_PROCESS_ENTER); - bSizer_toolbar->Add(m_url, 1, wxALL | wxEXPAND, 5); - - m_button_tools = new wxButton(this, wxID_ANY, wxT("Tools"), wxDefaultPosition, wxDefaultSize, 0); - bSizer_toolbar->Add(m_button_tools, 0, wxALL, 5); - - // Create panel for find toolbar. - wxPanel* panel = new wxPanel(this); - topsizer->Add(bSizer_toolbar, 0, wxEXPAND, 0); - topsizer->Add(panel, wxSizerFlags().Expand()); - - // Create sizer for panel. - wxBoxSizer* panel_sizer = new wxBoxSizer(wxVERTICAL); - panel->SetSizer(panel_sizer); - - // Create the info panel - m_info = new wxInfoBar(this); - topsizer->Add(m_info, wxSizerFlags().Expand()); -#endif - - SetSizer(topsizer); - - // Create the webview - m_browser = WebView::CreateWebView(this, /*m_default_url*/ GUI::format_wxstr("file://%1%/web/%2%.html", boost::filesystem::path(resources_dir()).generic_string(), m_loading_html), m_script_message_hadler_names); - if (Utils::ServiceConfig::instance().webdev_enabled()) { - m_browser->EnableContextMenu(); - m_browser->EnableAccessToDevTools(); - } - if (!m_browser) { - wxStaticText* text = new wxStaticText(this, wxID_ANY, _L("Failed to load a web browser.")); - topsizer->Add(text, 0, wxALIGN_LEFT | wxBOTTOM, 10); - return; - } - topsizer->Add(m_browser, wxSizerFlags().Expand().Proportion(1)); -#ifdef DEBUG_URL_PANEL - // Create the Tools menu - m_tools_menu = new wxMenu(); - wxMenuItem* viewSource = m_tools_menu->Append(wxID_ANY, "View Source"); - wxMenuItem* viewText = m_tools_menu->Append(wxID_ANY, "View Text"); - m_tools_menu->AppendSeparator(); - - wxMenu* script_menu = new wxMenu; - - m_script_custom = script_menu->Append(wxID_ANY, "Custom script"); - m_tools_menu->AppendSubMenu(script_menu, "Run Script"); - wxMenuItem* addUserScript = m_tools_menu->Append(wxID_ANY, "Add user script"); - wxMenuItem* setCustomUserAgent = m_tools_menu->Append(wxID_ANY, "Set custom user agent"); - - m_context_menu = m_tools_menu->AppendCheckItem(wxID_ANY, "Enable Context Menu"); - m_dev_tools = m_tools_menu->AppendCheckItem(wxID_ANY, "Enable Dev Tools"); - -#endif - - Bind(wxEVT_SHOW, &WebViewPanel::on_show, this); - - // Connect the webview events - Bind(wxEVT_WEBVIEW_ERROR, &WebViewPanel::on_error, this, m_browser->GetId()); - Bind(wxEVT_WEBVIEW_SCRIPT_MESSAGE_RECEIVED, &WebViewPanel::on_script_message, this, m_browser->GetId()); - Bind(wxEVT_WEBVIEW_NAVIGATING, &WebViewPanel::on_navigation_request, this, m_browser->GetId()); - -#ifdef DEBUG_URL_PANEL - // Connect the button events - Bind(wxEVT_BUTTON, &WebViewPanel::on_back_button, this, m_button_back->GetId()); - Bind(wxEVT_BUTTON, &WebViewPanel::on_forward_button, this, m_button_forward->GetId()); - Bind(wxEVT_BUTTON, &WebViewPanel::on_stop_button, this, m_button_stop->GetId()); - Bind(wxEVT_BUTTON, &WebViewPanel::on_reload_button, this, m_button_reload->GetId()); - Bind(wxEVT_BUTTON, &WebViewPanel::on_tools_clicked, this, m_button_tools->GetId()); - Bind(wxEVT_TEXT_ENTER, &WebViewPanel::on_url, this, m_url->GetId()); - - // Connect the menu events - Bind(wxEVT_MENU, &WebViewPanel::on_view_source_request, this, viewSource->GetId()); - Bind(wxEVT_MENU, &WebViewPanel::on_view_text_request, this, viewText->GetId()); - Bind(wxEVT_MENU, &WebViewPanel::On_enable_context_menu, this, m_context_menu->GetId()); - Bind(wxEVT_MENU, &WebViewPanel::On_enable_dev_tools, this, m_dev_tools->GetId()); - - Bind(wxEVT_MENU, &WebViewPanel::on_run_script_custom, this, m_script_custom->GetId()); - Bind(wxEVT_MENU, &WebViewPanel::on_add_user_script, this, addUserScript->GetId()); -#endif - //Connect the idle events - Bind(wxEVT_IDLE, &WebViewPanel::on_idle, this); -} - -void WebViewPanel::load_default_url_delayed() -{ - assert(!m_default_url.empty()); - m_load_default_url = true; -} - -void WebViewPanel::load_error_page() -{ - if (!m_browser) - return; - - m_browser->Stop(); - m_load_error_page = true; -} - -void WebViewPanel::on_show(wxShowEvent& evt) -{ - m_shown = evt.IsShown(); - if (evt.IsShown() && m_load_default_url) { - m_load_default_url = false; - load_url(m_default_url); - } -} - -void WebViewPanel::on_idle(wxIdleEvent& WXUNUSED(evt)) -{ - if (!m_browser) - return; - if (m_browser->IsBusy()) { - wxSetCursor(wxCURSOR_ARROWWAIT); - } else { - wxSetCursor(wxNullCursor); - - if (m_shown && m_load_error_page) { - m_load_error_page = false; - if (m_load_default_url_on_next_error) { - m_load_default_url_on_next_error = false; - load_url(m_default_url); - } else { - load_url(GUI::format_wxstr("file://%1%/web/connection_failed.html", boost::filesystem::path(resources_dir()).generic_string())); - } - } - } -#ifdef DEBUG_URL_PANEL - m_button_stop->Enable(m_browser->IsBusy()); -#endif -} - -/** - * Callback invoked when user entered an URL and pressed enter - */ -void WebViewPanel::on_url(wxCommandEvent& WXUNUSED(evt)) -{ - if (!m_browser) - return; -#ifdef DEBUG_URL_PANEL - m_browser->LoadURL(m_url->GetValue()); - m_browser->SetFocus(); -#endif -} - -/** - * Callback invoked when user pressed the "back" button - */ -void WebViewPanel::on_back_button(wxCommandEvent& WXUNUSED(evt)) -{ - if (!m_browser) - return; - m_browser->GoBack(); -} - -/** - * Callback invoked when user pressed the "forward" button - */ -void WebViewPanel::on_forward_button(wxCommandEvent& WXUNUSED(evt)) -{ - if (!m_browser) - return; - m_browser->GoForward(); -} - -/** - * Callback invoked when user pressed the "stop" button - */ -void WebViewPanel::on_stop_button(wxCommandEvent& WXUNUSED(evt)) -{ - if (!m_browser) - return; - m_browser->Stop(); -} - -/** - * Callback invoked when user pressed the "reload" button - */ -void WebViewPanel::on_reload_button(wxCommandEvent& WXUNUSED(evt)) -{ - if (!m_browser) - return; - m_browser->Reload(); -} - -void WebViewPanel::on_script_message(wxWebViewEvent& evt) -{ -} - -void WebViewPanel::on_navigation_request(wxWebViewEvent &evt) -{ -} - -void WebViewPanel::on_page_will_load() -{ -} - -/** - * Invoked when user selects the "View Source" menu item - */ -void WebViewPanel::on_view_source_request(wxCommandEvent& WXUNUSED(evt)) -{ - if (!m_browser) - return; - - SourceViewDialog dlg(this, m_browser->GetPageSource()); - dlg.ShowModal(); -} - -/** - * Invoked when user selects the "View Text" menu item - */ -void WebViewPanel::on_view_text_request(wxCommandEvent& WXUNUSED(evt)) -{ - if (!m_browser) - return; - - wxDialog textViewDialog(this, wxID_ANY, "Page Text", - wxDefaultPosition, wxSize(700, 500), - wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER); - - wxTextCtrl* text = new wxTextCtrl(this, wxID_ANY, m_browser->GetPageText(), - wxDefaultPosition, wxDefaultSize, - wxTE_MULTILINE | - wxTE_RICH | - wxTE_READONLY); - - wxBoxSizer* sizer = new wxBoxSizer(wxVERTICAL); - sizer->Add(text, 1, wxEXPAND); - SetSizer(sizer); - textViewDialog.ShowModal(); -} - -/** - * Invoked when user selects the "Menu" item - */ -void WebViewPanel::on_tools_clicked(wxCommandEvent& WXUNUSED(evt)) -{ - if (!m_browser) - return; - -#ifdef DEBUG_URL_PANEL - m_context_menu->Check(m_browser->IsContextMenuEnabled()); - m_dev_tools->Check(m_browser->IsAccessToDevToolsEnabled()); - - wxPoint position = ScreenToClient(wxGetMousePosition()); - PopupMenu(m_tools_menu, position.x, position.y); -#endif -} - -void WebViewPanel::run_script(const wxString& javascript) -{ - if (!m_browser || !m_shown) - return; - // 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; - BOOST_LOG_TRIVIAL(debug) << "RunScript " << javascript << "\n"; - m_browser->RunScriptAsync(javascript); -} - - -void WebViewPanel::on_run_script_custom(wxCommandEvent& WXUNUSED(evt)) -{ - wxTextEntryDialog dialog - ( - this, - "Please enter JavaScript code to execute", - wxGetTextFromUserPromptStr, - m_javascript, - wxOK | wxCANCEL | wxCENTRE | wxTE_MULTILINE - ); - if (dialog.ShowModal() != wxID_OK) - return; - - run_script(dialog.GetValue()); -} - -void WebViewPanel::on_add_user_script(wxCommandEvent& WXUNUSED(evt)) -{ - wxString userScript = "window.wx_test_var = 'wxWidgets webview sample';"; - wxTextEntryDialog dialog - ( - this, - "Enter the JavaScript code to run as the initialization script that runs before any script in the HTML document.", - wxGetTextFromUserPromptStr, - userScript, - wxOK | wxCANCEL | wxCENTRE | wxTE_MULTILINE - ); - if (dialog.ShowModal() != wxID_OK) - return; - - const wxString& javascript = dialog.GetValue(); - BOOST_LOG_TRIVIAL(debug) << "RunScript " << javascript << "\n"; - if (!m_browser->AddUserScript(javascript)) - wxLogError("Could not add user script"); -} - -void WebViewPanel::on_set_custom_user_agent(wxCommandEvent& WXUNUSED(evt)) -{ - if (!m_browser) - return; - - wxString customUserAgent = "Mozilla/5.0 (iPhone; CPU iPhone OS 13_1_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.1 Mobile/15E148 Safari/604.1"; - wxTextEntryDialog dialog - ( - this, - "Enter the custom user agent string you would like to use.", - wxGetTextFromUserPromptStr, - customUserAgent, - wxOK | wxCANCEL | wxCENTRE - ); - if (dialog.ShowModal() != wxID_OK) - return; - - if (!m_browser->SetUserAgent(customUserAgent)) - wxLogError("Could not set custom user agent"); -} - -void WebViewPanel::on_clear_selection(wxCommandEvent& WXUNUSED(evt)) -{ - if (!m_browser) - return; - - m_browser->ClearSelection(); -} - -void WebViewPanel::on_delete_selection(wxCommandEvent& WXUNUSED(evt)) -{ - if (!m_browser) - return; - - m_browser->DeleteSelection(); -} - -void WebViewPanel::on_select_all(wxCommandEvent& WXUNUSED(evt)) -{ - if (!m_browser) - return; - - m_browser->SelectAll(); -} - -void WebViewPanel::On_enable_context_menu(wxCommandEvent& evt) -{ - if (!m_browser) - return; - - m_browser->EnableContextMenu(evt.IsChecked()); -} -void WebViewPanel::On_enable_dev_tools(wxCommandEvent& evt) -{ - if (!m_browser) - return; - - m_browser->EnableAccessToDevTools(evt.IsChecked()); -} - -/** - * Callback invoked when a loading error occurs - */ -void WebViewPanel::on_error(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); - } - - BOOST_LOG_TRIVIAL(error) << "WebViewPanel error: " << category; - load_error_page(); -#ifdef DEBUG_URL_PANEL - m_info->ShowMessage(wxString("An error occurred loading ") + evt.GetURL() + "\n" + - "'" + category + "'", wxICON_ERROR); -#endif -} - -void WebViewPanel::sys_color_changed() -{ -#ifdef _WIN32 - wxGetApp().UpdateDarkUI(this); -#endif -} - -SourceViewDialog::SourceViewDialog(wxWindow* parent, wxString source) : - wxDialog(parent, wxID_ANY, "Source Code", - wxDefaultPosition, wxSize(700,500), - wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) -{ - wxTextCtrl* text = new wxTextCtrl(this, wxID_ANY, source, - wxDefaultPosition, wxDefaultSize, - wxTE_MULTILINE | - wxTE_RICH | - wxTE_READONLY); - - wxBoxSizer* sizer = new wxBoxSizer(wxVERTICAL); - sizer->Add(text, 1, wxEXPAND); - SetSizer(sizer); -} - -ConnectRequestHandler::ConnectRequestHandler() -{ - m_actions["REQUEST_LOGIN"] = std::bind(&ConnectRequestHandler::on_connect_action_request_login, this, std::placeholders::_1); - m_actions["REQUEST_CONFIG"] = std::bind(&ConnectRequestHandler::on_connect_action_request_config, this, std::placeholders::_1); - m_actions["WEBAPP_READY"] = std::bind(&ConnectRequestHandler::on_connect_action_webapp_ready,this, std::placeholders::_1); - m_actions["SELECT_PRINTER"] = std::bind(&ConnectRequestHandler::on_connect_action_select_printer, this, std::placeholders::_1); - m_actions["PRINT"] = std::bind(&ConnectRequestHandler::on_connect_action_print, this, std::placeholders::_1); - m_actions["REQUEST_OPEN_IN_BROWSER"] = std::bind(&ConnectRequestHandler::on_connect_action_request_open_in_browser, this, std::placeholders::_1); - m_actions["ERROR"] = std::bind(&ConnectRequestHandler::on_connect_action_error, this, std::placeholders::_1); - m_actions["LOG"] = std::bind(&ConnectRequestHandler::on_connect_action_log, this, std::placeholders::_1); -} -ConnectRequestHandler::~ConnectRequestHandler() -{ -} -void ConnectRequestHandler::handle_message(const std::string& message) -{ - // read msg and choose action - /* - v0: - {"type":"request","detail":{"action":"requestAccessToken"}} - v1: - {"action":"REQUEST_ACCESS_TOKEN"} - */ - std::string action_string; - try { - std::stringstream ss(message); - pt::ptree ptree; - pt::read_json(ss, ptree); - // v1: - if (const auto action = ptree.get_optional("action"); action) { - action_string = *action; - } - } - catch (const std::exception& e) { - BOOST_LOG_TRIVIAL(error) << "Could not parse _prusaConnect message. " << e.what(); - return; - } - - if (action_string.empty()) { - BOOST_LOG_TRIVIAL(error) << "Recieved invalid message from _prusaConnect (missing action). Message: " << message; - return; - } - assert(m_actions.find(action_string) != m_actions.end()); // this assert means there is a action that has no handling. - if (m_actions.find(action_string) != m_actions.end()) { - m_actions[action_string](message); - } -} - -void ConnectRequestHandler::on_connect_action_error(const std::string &message_data) -{ - BOOST_LOG_TRIVIAL(error) << "WebView runtime error: " << message_data; -} - -void ConnectRequestHandler::resend_config() -{ - on_connect_action_request_config({}); -} - -void ConnectRequestHandler::on_connect_action_log(const std::string& message_data) -{ - BOOST_LOG_TRIVIAL(info) << "WebView log: " << message_data; -} - -void ConnectRequestHandler::on_connect_action_request_login(const std::string &message_data) -{} - - -void ConnectRequestHandler::on_connect_action_request_config(const std::string& message_data) -{ - /* - accessToken?: string; - clientVersion?: string; - colorMode?: "LIGHT" | "DARK"; - language?: ConnectLanguage; - sessionId?: string; - */ - const std::string token = wxGetApp().plater()->get_user_account()->get_access_token(); - //const std::string sesh = wxGetApp().plater()->get_user_account()->get_shared_session_key(); - const std::string dark_mode = wxGetApp().dark_mode() ? "DARK" : "LIGHT"; - wxString language = GUI::wxGetApp().current_language_code(); - language = language.SubString(0, 1); - const std::string init_options = GUI::format("{\"accessToken\": \"%4%\",\"clientVersion\": \"%1%\", \"colorMode\": \"%2%\", \"language\": \"%3%\"}", SLIC3R_VERSION, dark_mode, language, token ); - wxString script = GUI::format_wxstr("window._prusaConnect_v1.init(%1%)", init_options); - run_script_bridge(script); - -} -void ConnectRequestHandler::on_connect_action_request_open_in_browser(const std::string& message_data) -{ - try { - std::stringstream ss(message_data); - pt::ptree ptree; - pt::read_json(ss, ptree); - if (const auto url = ptree.get_optional("url"); url) { - wxGetApp().open_browser_with_warning_dialog(GUI::from_u8(*url)); - } - } catch (const std::exception &e) { - BOOST_LOG_TRIVIAL(error) << "Could not parse _prusaConnect message. " << e.what(); - return; - } -} - -ConnectWebViewPanel::ConnectWebViewPanel(wxWindow* parent) - : WebViewPanel(parent, GUI::from_u8(Utils::ServiceConfig::instance().connect_url()), { "_prusaSlicer" }, "connect_loading") -{ - // m_browser->RegisterHandler(wxSharedPtr(new WebViewHandler("https"))); - - auto* plater = wxGetApp().plater(); - plater->Bind(EVT_UA_ID_USER_SUCCESS, &ConnectWebViewPanel::on_user_token, this); - plater->Bind(EVT_UA_LOGGEDOUT, &ConnectWebViewPanel::on_user_logged_out, this); -} - -ConnectWebViewPanel::~ConnectWebViewPanel() -{ - m_browser->Unbind(EVT_UA_ID_USER_SUCCESS, &ConnectWebViewPanel::on_user_token, this); -} - -wxString ConnectWebViewPanel::get_login_script(bool refresh) -{ - Plater* plater = wxGetApp().plater(); - const std::string& access_token = plater->get_user_account()->get_access_token(); - assert(!access_token.empty()); - auto javascript = wxString::Format( - -#if AUTH_VIA_FETCH_OVERRIDE - refresh - ? - "window.__access_token = '%s';window.__access_token_version = (window.__access_token_version || 0) + 1;console.log('Updated Auth token', window.__access_token);" - : - /* - * Notes: - * - The fetch() function has two distinct prototypes (i.e. input args): - * 1. fetch(url: string, options: object | undefined) - * 2. fetch(req: Request, options: object | undefined) - * - For some reason I can't explain the headers can be extended only via Request object - * i.e. the fetch prototype (2). So we need to convert (1) call into (2) before - * - */ - R"( - if (window.__fetch === undefined) { - window.__fetch = fetch; - window.fetch = function(req, opts = {}) { - if (typeof req === 'string') { - req = new Request(req, opts); - opts = {}; - } - if (window.__access_token && (req.url[0] == '/' || req.url.indexOf('prusa3d.com') > 0)) { - req.headers.set('Authorization', 'Bearer ' + window.__access_token); - console.log('Header updated: ', req.headers.get('Authorization')); - console.log('AT Version: ', __access_token_version); - } - //console.log('Injected fetch used', req, opts); - return __fetch(req, opts); - }; - } - window.__access_token = '%s'; - window.__access_token_version = 0; - )", -#else - refresh - ? - R"( - if (location.protocol === 'https:') { - if (window._prusaSlicer_initLogin !== undefined) { - console.log('Init login'); - if (window._prusaSlicer !== undefined) - _prusaSlicer.postMessage({action: 'LOG', message: 'Refreshing login'}); - _prusaSlicer_initLogin('%s'); - } else { - console.log('Refreshing login skipped as no _prusaSlicer_login defined (yet?)'); - if (window._prusaSlicer === undefined) { - console.log('Message handler _prusaSlicer not defined yet'); - } else { - _prusaSlicer.postMessage({action: 'LOG', message: 'Refreshing login skipped as no _prusaSlicer_initLogin defined (yet?)'}); - } - } - } - )" - : - R"( - function _prusaSlicer_log(msg) { - console.log(msg); - if (window._prusaSlicer !== undefined) - _prusaSlicer.postMessage({action: 'LOG', message: msg}); - } - function _prusaSlicer_errorHandler(err) { - const msg = { - action: 'ERROR', - error: typeof(err) === 'string' ? err : JSON.stringify(err), - critical: false - }; - console.error('Login error occurred', msg); - window._prusaSlicer.postMessage(msg); - }; - - function _prusaSlicer_delay(ms) { - return new Promise((resolve, reject) => { - setTimeout(resolve, ms); - }); - } - - async function _prusaSlicer_initLogin(token) { - const parts = token.split('.'); - const claims = JSON.parse(atob(parts[1])); - const now = new Date().getTime() / 1000; - if (claims.exp <= now) { - _prusaSlicer_log('Skipping initLogin as token is expired'); - return; - } - - let retry = false; - let backoff = 1000; - const maxBackoff = 64000; - do { - - let error = false; - - try { - _prusaSlicer_log('Slicer Login request ' + token.substring(token.length - 8)); - let resp = await fetch('/slicer/login', {method: 'POST', headers: {Authorization: 'Bearer ' + token}}); - let body = await resp.text(); - _prusaSlicer_log('Slicer Login resp ' + resp.status + ' (' + token.substring(token.length - 8) + ') body: ' + body); - if (resp.status >= 500 || resp.status == 408) { - retry = true; - } else { - retry = false; - if (resp.status >= 400) - _prusaSlicer_errorHandler({status: resp.status, body}); - } - } catch (e) { - _prusaSlicer_log('Slicer Login failed: ' + e.toString()); - console.error('Slicer Login failed', e.toString()); - retry = true; - } - - if (retry) { - await _prusaSlicer_delay(backoff + 1000 * Math.random()); - if (backoff < maxBackoff) { - backoff *= 2; - } - } - } while (retry); - } - - if (location.protocol === 'https:' && window._prusaSlicer) { - _prusaSlicer_log('Requesting login'); - _prusaSlicer.postMessage({action: 'REQUEST_LOGIN'}); - } - )", -#endif - access_token - ); - return javascript; -} - -wxString ConnectWebViewPanel::get_logout_script() -{ - return "sessionStorage.removeItem('_slicer_token');"; -} - -void ConnectWebViewPanel::on_page_will_load() -{ - auto javascript = get_login_script(false); - BOOST_LOG_TRIVIAL(debug) << "RunScript " << javascript << "\n"; - m_browser->AddUserScript(javascript); -} - -void ConnectWebViewPanel::on_user_token(UserAccountSuccessEvent& e) -{ - e.Skip(); - auto access_token = wxGetApp().plater()->get_user_account()->get_access_token(); - assert(!access_token.empty()); - - wxString javascript = get_login_script(true); - BOOST_LOG_TRIVIAL(debug) << "RunScript " << javascript << "\n"; - m_browser->RunScriptAsync(javascript); - resend_config(); -} - -void ConnectWebViewPanel::on_user_logged_out(UserAccountSuccessEvent& e) -{ - e.Skip(); - // clear token from session storage - m_browser->RunScriptAsync(get_logout_script()); -} - -void ConnectWebViewPanel::on_script_message(wxWebViewEvent& evt) -{ - BOOST_LOG_TRIVIAL(debug) << "received message from Prusa Connect FE: " << evt.GetString(); - handle_message(into_u8(evt.GetString())); -} -void ConnectWebViewPanel::on_navigation_request(wxWebViewEvent &evt) -{ -#ifdef DEBUG_URL_PANEL - m_url->SetValue(evt.GetURL()); -#endif - BOOST_LOG_TRIVIAL(debug) << "Navigation requested to: " << into_u8(evt.GetURL()); - if (evt.GetURL() == m_default_url) { - m_reached_default_url = true; - return; - } - if (evt.GetURL() == (GUI::format_wxstr("file:///%1%/web/connection_failed.html", boost::filesystem::path(resources_dir()).generic_string()))) { - return; - } - if (m_reached_default_url && !evt.GetURL().StartsWith(m_default_url)) { - BOOST_LOG_TRIVIAL(info) << evt.GetURL() << " does not start with default url. Vetoing."; - evt.Veto(); - } -} - -void ConnectWebViewPanel::on_connect_action_error(const std::string &message_data) -{ - ConnectRequestHandler::on_connect_action_error(message_data); - // TODO: make this more user friendly (and make sure only once opened if multiple errors happen) -// MessageDialog dialog( -// this, -// GUI::format_wxstr(_L("WebKit Runtime Error encountered:\n\n%s"), message_data), -// "WebKit Runtime Error", -// wxOK -// ); -// dialog.ShowModal(); - -} - -void ConnectWebViewPanel::logout() -{ - wxString script = L"window._prusaConnect_v1.logout()"; - run_script(script); - - Plater* plater = wxGetApp().plater(); - auto javascript = wxString::Format( - R"( - console.log('Preparing logout'); - window.fetch('/slicer/logout', {method: 'POST', headers: {Authorization: 'Bearer %s'}}) - .then(function (resp){ - console.log('Logout resp', resp); - resp.text().then(function (json) { console.log('Logout resp body', json) }); - }); - )", - plater->get_user_account()->get_access_token() - ); - BOOST_LOG_TRIVIAL(debug) << "RunScript " << javascript << "\n"; - m_browser->RunScript(javascript); - -} - -void ConnectWebViewPanel::sys_color_changed() -{ - resend_config(); -} - -void ConnectWebViewPanel::on_connect_action_request_login(const std::string &message_data) -{ - run_script_bridge(get_login_script(true)); -} - - -void ConnectWebViewPanel::on_connect_action_select_printer(const std::string& message_data) -{ - assert(!message_data.empty()); - wxGetApp().handle_connect_request_printer_select(message_data); -} -void ConnectWebViewPanel::on_connect_action_print(const std::string& message_data) -{ - // PRINT request is not defined for ConnectWebViewPanel - assert(true); -} - -PrinterWebViewPanel::PrinterWebViewPanel(wxWindow* parent, const wxString& default_url) - : WebViewPanel(parent, default_url, {}) -{ - if (!m_browser) - return; - - m_browser->Bind(wxEVT_WEBVIEW_LOADED, &PrinterWebViewPanel::on_loaded, this); -#ifndef NDEBUG - m_browser->EnableAccessToDevTools(); - m_browser->EnableContextMenu(); -#endif -} - -void PrinterWebViewPanel::on_loaded(wxWebViewEvent& evt) -{ - if (evt.GetURL().IsEmpty()) - return; - if (!m_api_key.empty()) { - send_api_key(); - } else if (!m_usr.empty() && !m_psk.empty()) { - send_credentials(); - } -} - -void PrinterWebViewPanel::send_api_key() -{ - if (!m_browser || m_api_key_sent) - return; - m_api_key_sent = true; - wxString key = from_u8(m_api_key); - wxString script = wxString::Format(R"( - // Check if window.fetch exists before overriding - if (window.originalFetch === undefined) { - console.log('Patching fetch with API key'); - window.originalFetch = window.fetch; - window.fetch = function(input, init = {}) { - init.headers = init.headers || {}; - init.headers['X-Api-Key'] = sessionStorage.getItem('apiKey'); - console.log('Patched fetch', input, init); - return window.originalFetch(input, init); - }; - } - sessionStorage.setItem('authType', 'ApiKey'); - sessionStorage.setItem('apiKey', '%s'); -)", - key); - - m_browser->RemoveAllUserScripts(); - BOOST_LOG_TRIVIAL(debug) << "RunScript " << script << "\n"; - m_browser->AddUserScript(script); - m_browser->Reload(); - remove_webview_credentials(m_browser); -} - -void PrinterWebViewPanel::send_credentials() -{ - if (!m_browser || m_api_key_sent) - return; - m_browser->RemoveAllUserScripts(); - m_browser->AddUserScript("sessionStorage.removeItem('authType'); sessionStorage.removeItem('apiKey'); console.log('Session Storage cleared');"); - m_browser->Reload(); - m_api_key_sent = true; - setup_webview_with_credentials(m_browser, m_usr, m_psk); -} - -void PrinterWebViewPanel::sys_color_changed() -{ -} - WebViewDialog::WebViewDialog(wxWindow* parent, const wxString& url, const wxString& dialog_name, const wxSize& size, const std::vector& message_handler_names, const std::string& loading_html/* = "loading"*/) : DPIDialog(parent, wxID_ANY, dialog_name, wxDefaultPosition, size, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) , m_loading_html(loading_html) diff --git a/src/slic3r/GUI/WebViewDialog.hpp b/src/slic3r/GUI/WebViewDialog.hpp index 05efed3453..82ff2f9739 100644 --- a/src/slic3r/GUI/WebViewDialog.hpp +++ b/src/slic3r/GUI/WebViewDialog.hpp @@ -1,14 +1,13 @@ #ifndef slic3r_WebViewDialog_hpp_ #define slic3r_WebViewDialog_hpp_ -//#define DEBUG_URL_PANEL - #include #include #include #include "GUI_Utils.hpp" #include "UserAccountSession.hpp" +#include "ConnectRequestHandler.hpp" #ifdef DEBUG_URL_PANEL #include @@ -22,91 +21,6 @@ wxDECLARE_EVENT(EVT_OPEN_EXTERNAL_LOGIN, wxCommandEvent); namespace Slic3r { namespace GUI { -class WebViewPanel : public wxPanel -{ -public: - WebViewPanel(wxWindow *parent, const wxString& default_url, const std::vector& message_handler_names, const std::string& loading_html = "loading"); - virtual ~WebViewPanel(); - - void load_url(const wxString& url); - void load_default_url_delayed(); - void load_error_page(); - - void on_show(wxShowEvent& evt); - virtual void on_script_message(wxWebViewEvent& evt); - - void on_idle(wxIdleEvent& evt); - void on_url(wxCommandEvent& evt); - void on_back_button(wxCommandEvent& evt); - void on_forward_button(wxCommandEvent& evt); - void on_stop_button(wxCommandEvent& evt); - void on_reload_button(wxCommandEvent& evt); - - void on_view_source_request(wxCommandEvent& evt); - void on_view_text_request(wxCommandEvent& evt); - void on_tools_clicked(wxCommandEvent& evt); - void on_error(wxWebViewEvent& evt); - - void run_script(const wxString& javascript); - void on_run_script_custom(wxCommandEvent& evt); - void on_add_user_script(wxCommandEvent& evt); - void on_set_custom_user_agent(wxCommandEvent& evt); - void on_clear_selection(wxCommandEvent& evt); - void on_delete_selection(wxCommandEvent& evt); - void on_select_all(wxCommandEvent& evt); - void On_enable_context_menu(wxCommandEvent& evt); - void On_enable_dev_tools(wxCommandEvent& evt); - virtual void on_navigation_request(wxWebViewEvent &evt); - - wxString get_default_url() const { return m_default_url; } - void set_default_url(const wxString& url) { m_default_url = url; } - - virtual void sys_color_changed(); - - void set_load_default_url_on_next_error(bool val) { m_load_default_url_on_next_error = val; } - -protected: - - virtual void on_page_will_load(); - - wxWebView* m_browser { nullptr }; - bool m_load_default_url { false }; -#ifdef DEBUG_URL_PANEL - - wxBoxSizer *bSizer_toolbar; - wxButton * m_button_back; - wxButton * m_button_forward; - wxButton * m_button_stop; - wxButton * m_button_reload; - wxTextCtrl *m_url; - wxButton * m_button_tools; - - wxMenu* m_tools_menu; - wxMenuItem* m_script_custom; - - wxInfoBar *m_info; - wxStaticText* m_info_text; - - wxMenuItem* m_context_menu; - wxMenuItem* m_dev_tools; -#endif - - // Last executed JavaScript snippet, for convenience. - wxString m_javascript; - wxString m_response_js; - wxString m_default_url; - - std::string m_loading_html; - //DECLARE_EVENT_TABLE() - - bool m_load_error_page { false }; - bool m_shown { false }; - bool m_load_default_url_on_next_error { false }; - - std::vector m_script_message_hadler_names; -}; - - class WebViewDialog : public DPIDialog { public: @@ -176,87 +90,6 @@ protected: std::vector m_script_message_hadler_names; }; -class ConnectRequestHandler -{ -public: - ConnectRequestHandler(); - ~ConnectRequestHandler(); - - void handle_message(const std::string& message); - void resend_config(); -protected: - // action callbacs stored in m_actions - virtual void on_connect_action_log(const std::string& message_data); - virtual void on_connect_action_error(const std::string& message_data); - virtual void on_connect_action_request_login(const std::string& message_data); - virtual void on_connect_action_request_config(const std::string& message_data); - virtual void on_connect_action_request_open_in_browser(const std::string& message_data); - virtual void on_connect_action_select_printer(const std::string& message_data) = 0; - virtual void on_connect_action_print(const std::string& message_data) = 0; - virtual void on_connect_action_webapp_ready(const std::string& message_data) = 0; - virtual void run_script_bridge(const wxString &script) = 0; - - std::map> m_actions; -}; - -class ConnectWebViewPanel : public WebViewPanel, public ConnectRequestHandler -{ -public: - ConnectWebViewPanel(wxWindow* parent); - ~ConnectWebViewPanel() override; - void on_script_message(wxWebViewEvent& evt) override; - void logout(); - void sys_color_changed() override; - void on_navigation_request(wxWebViewEvent &evt) override; -protected: - void on_connect_action_request_login(const std::string &message_data) override; - void on_connect_action_select_printer(const std::string& message_data) override; - void on_connect_action_print(const std::string& message_data) override; - void on_connect_action_webapp_ready(const std::string& message_data) override {} - void run_script_bridge(const wxString& script) override {run_script(script); } - void on_page_will_load() override; - void on_connect_action_error(const std::string &message_data) override; -private: - static wxString get_login_script(bool refresh); - static wxString get_logout_script(); - void on_user_token(UserAccountSuccessEvent& e); - void on_user_logged_out(UserAccountSuccessEvent& e); - bool m_reached_default_url {false}; -}; - -class PrinterWebViewPanel : public WebViewPanel -{ -public: - PrinterWebViewPanel(wxWindow* parent, const wxString& default_url); - - void on_loaded(wxWebViewEvent& evt); - - void send_api_key(); - void send_credentials(); - void set_api_key(const std::string &key) - { - if (m_api_key != key) { - clear(); - m_api_key = key; - } - } - void set_credentials(const std::string &usr, const std::string &psk) - { - if (m_usr != usr || m_psk != psk) { - clear(); - m_usr = usr; - m_psk = psk; - } - } - void clear() { m_api_key.clear(); m_usr.clear(); m_psk.clear(); m_api_key_sent = false; } - void sys_color_changed() override; -private: - std::string m_api_key; - std::string m_usr; - std::string m_psk; - bool m_api_key_sent {false}; -}; - class PrinterPickWebViewDialog : public WebViewDialog, public ConnectRequestHandler { public: @@ -276,12 +109,6 @@ private: std::string& m_ret_val; }; -class SourceViewDialog : public wxDialog -{ -public: - SourceViewDialog(wxWindow* parent, wxString source); -}; - class LoginWebViewDialog : public WebViewDialog { public: @@ -297,4 +124,4 @@ private: } // GUI } // Slic3r -#endif /* slic3r_Tab_hpp_ */ +#endif /* slic3r_WebViewDialog_hpp_ */ diff --git a/src/slic3r/GUI/WebViewPanel.cpp b/src/slic3r/GUI/WebViewPanel.cpp new file mode 100644 index 0000000000..12c8c1ff5b --- /dev/null +++ b/src/slic3r/GUI/WebViewPanel.cpp @@ -0,0 +1,1077 @@ +#include "WebViewPanel.hpp" + +#include "slic3r/GUI/I18N.hpp" +#include "slic3r/GUI/GUI_App.hpp" +#include "slic3r/GUI/MainFrame.hpp" +#include "slic3r/GUI/Plater.hpp" +#include "slic3r/GUI/UserAccount.hpp" +#include "slic3r/GUI/format.hpp" +#include "slic3r/GUI/WebView.hpp" +#include "slic3r/GUI/WebViewPlatformUtils.hpp" +#include "slic3r/Utils/ServiceConfig.hpp" + +#include "slic3r/GUI/MsgDialog.hpp" +#include "slic3r/GUI/Field.hpp" + +#include // IWYU pragma: keep + +#include +#include + +#include +#include +#include + +#include + +// if set to 1 the fetch() JS function gets override to include JWT in authorization header +// if set to 0, the /slicer/login is invoked from WebKit (passing JWT token only to this request) +// to set authorization cookie for all WebKit requests to Connect +#define AUTH_VIA_FETCH_OVERRIDE 0 + +namespace pt = boost::property_tree; + +namespace Slic3r::GUI { + +WebViewPanel::~WebViewPanel() +{ + SetEvtHandlerEnabled(false); +#ifdef DEBUG_URL_PANEL + delete m_tools_menu; +#endif +} + +void WebViewPanel::load_url(const wxString& url) +{ + if (!m_browser) + return; + + this->on_page_will_load(); + + this->Show(); + this->Raise(); +#ifdef DEBUG_URL_PANEL + m_url->SetLabelText(url); +#endif + m_browser->LoadURL(url); + m_browser->SetFocus(); +} + + +WebViewPanel::WebViewPanel(wxWindow *parent, const wxString& default_url, const std::vector& message_handler_names, const std::string& loading_html/* = "loading"*/) + : wxPanel(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize) + , m_default_url (default_url) + , m_loading_html(loading_html) + , m_script_message_hadler_names(message_handler_names) +{ + topsizer = new wxBoxSizer(wxVERTICAL); + m_sizer_top = new wxBoxSizer(wxHORIZONTAL); + topsizer->Add(m_sizer_top, 0, wxEXPAND, 0); + +#ifdef DEBUG_URL_PANEL + // Create the button + bSizer_toolbar = new wxBoxSizer(wxHORIZONTAL); + + m_button_back = new wxButton(this, wxID_ANY, wxT("Back"), wxDefaultPosition, wxDefaultSize, 0); + //m_button_back->Enable(false); + bSizer_toolbar->Add(m_button_back, 0, wxALL, 5); + + m_button_forward = new wxButton(this, wxID_ANY, wxT("Forward"), wxDefaultPosition, wxDefaultSize, 0); + //m_button_forward->Enable(false); + bSizer_toolbar->Add(m_button_forward, 0, wxALL, 5); + + m_button_stop = new wxButton(this, wxID_ANY, wxT("Stop"), wxDefaultPosition, wxDefaultSize, 0); + + bSizer_toolbar->Add(m_button_stop, 0, wxALL, 5); + + m_button_reload = new wxButton(this, wxID_ANY, wxT("Reload"), wxDefaultPosition, wxDefaultSize, 0); + bSizer_toolbar->Add(m_button_reload, 0, wxALL, 5); + + m_url = new wxTextCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_PROCESS_ENTER); + bSizer_toolbar->Add(m_url, 1, wxALL | wxEXPAND, 5); + + m_button_tools = new wxButton(this, wxID_ANY, wxT("Tools"), wxDefaultPosition, wxDefaultSize, 0); + bSizer_toolbar->Add(m_button_tools, 0, wxALL, 5); + + // Create panel for find toolbar. + wxPanel* panel = new wxPanel(this); + topsizer->Add(bSizer_toolbar, 0, wxEXPAND, 0); + topsizer->Add(panel, wxSizerFlags().Expand()); + + // Create sizer for panel. + wxBoxSizer* panel_sizer = new wxBoxSizer(wxVERTICAL); + panel->SetSizer(panel_sizer); + + // Create the info panel + m_info = new wxInfoBar(this); + topsizer->Add(m_info, wxSizerFlags().Expand()); +#endif + + SetSizer(topsizer); + + // Create the webview + m_browser = WebView::CreateWebView(this, /*m_default_url*/ GUI::format_wxstr("file://%1%/web/%2%.html", boost::filesystem::path(resources_dir()).generic_string(), m_loading_html), m_script_message_hadler_names); + if (Utils::ServiceConfig::instance().webdev_enabled()) { + m_browser->EnableContextMenu(); + m_browser->EnableAccessToDevTools(); + } + if (!m_browser) { + wxStaticText* text = new wxStaticText(this, wxID_ANY, _L("Failed to load a web browser.")); + topsizer->Add(text, 0, wxALIGN_LEFT | wxBOTTOM, 10); + return; + } + topsizer->Add(m_browser, wxSizerFlags().Expand().Proportion(1)); +#ifdef DEBUG_URL_PANEL + // Create the Tools menu + m_tools_menu = new wxMenu(); + wxMenuItem* viewSource = m_tools_menu->Append(wxID_ANY, "View Source"); + wxMenuItem* viewText = m_tools_menu->Append(wxID_ANY, "View Text"); + m_tools_menu->AppendSeparator(); + + wxMenu* script_menu = new wxMenu; + + m_script_custom = script_menu->Append(wxID_ANY, "Custom script"); + m_tools_menu->AppendSubMenu(script_menu, "Run Script"); + wxMenuItem* addUserScript = m_tools_menu->Append(wxID_ANY, "Add user script"); + wxMenuItem* setCustomUserAgent = m_tools_menu->Append(wxID_ANY, "Set custom user agent"); + + m_context_menu = m_tools_menu->AppendCheckItem(wxID_ANY, "Enable Context Menu"); + m_dev_tools = m_tools_menu->AppendCheckItem(wxID_ANY, "Enable Dev Tools"); + +#endif + + Bind(wxEVT_SHOW, &WebViewPanel::on_show, this); + + // Connect the webview events + Bind(wxEVT_WEBVIEW_ERROR, &WebViewPanel::on_error, this, m_browser->GetId()); + Bind(wxEVT_WEBVIEW_SCRIPT_MESSAGE_RECEIVED, &WebViewPanel::on_script_message, this, m_browser->GetId()); + Bind(wxEVT_WEBVIEW_NAVIGATING, &WebViewPanel::on_navigation_request, this, m_browser->GetId()); + +#ifdef DEBUG_URL_PANEL + // Connect the button events + Bind(wxEVT_BUTTON, &WebViewPanel::on_back_button, this, m_button_back->GetId()); + Bind(wxEVT_BUTTON, &WebViewPanel::on_forward_button, this, m_button_forward->GetId()); + Bind(wxEVT_BUTTON, &WebViewPanel::on_stop_button, this, m_button_stop->GetId()); + Bind(wxEVT_BUTTON, &WebViewPanel::on_reload_button, this, m_button_reload->GetId()); + Bind(wxEVT_BUTTON, &WebViewPanel::on_tools_clicked, this, m_button_tools->GetId()); + Bind(wxEVT_TEXT_ENTER, &WebViewPanel::on_url, this, m_url->GetId()); + + // Connect the menu events + Bind(wxEVT_MENU, &WebViewPanel::on_view_source_request, this, viewSource->GetId()); + Bind(wxEVT_MENU, &WebViewPanel::on_view_text_request, this, viewText->GetId()); + Bind(wxEVT_MENU, &WebViewPanel::On_enable_context_menu, this, m_context_menu->GetId()); + Bind(wxEVT_MENU, &WebViewPanel::On_enable_dev_tools, this, m_dev_tools->GetId()); + + Bind(wxEVT_MENU, &WebViewPanel::on_run_script_custom, this, m_script_custom->GetId()); + Bind(wxEVT_MENU, &WebViewPanel::on_add_user_script, this, addUserScript->GetId()); +#endif + //Connect the idle events + Bind(wxEVT_IDLE, &WebViewPanel::on_idle, this); +} + +void WebViewPanel::load_default_url_delayed() +{ + assert(!m_default_url.empty()); + m_load_default_url = true; +} + +void WebViewPanel::load_error_page() +{ + if (!m_browser) + return; + + m_browser->Stop(); + m_load_error_page = true; +} + +void WebViewPanel::on_show(wxShowEvent& evt) +{ + m_shown = evt.IsShown(); + if (evt.IsShown() && m_load_default_url) { + load_url(m_default_url); + } +} + +void WebViewPanel::on_idle(wxIdleEvent& WXUNUSED(evt)) +{ + if (!m_browser) + return; + if (m_browser->IsBusy()) { + wxSetCursor(wxCURSOR_ARROWWAIT); + } else { + wxSetCursor(wxNullCursor); + + if (m_shown && m_load_error_page) { + m_load_error_page = false; + if (m_load_default_url_on_next_error) { + m_load_default_url_on_next_error = false; + load_url(m_default_url); + } else { + load_url(GUI::format_wxstr("file://%1%/web/connection_failed.html", boost::filesystem::path(resources_dir()).generic_string())); + } + } + } +#ifdef DEBUG_URL_PANEL + m_button_stop->Enable(m_browser->IsBusy()); +#endif +} + +/** + * Callback invoked when user entered an URL and pressed enter + */ +void WebViewPanel::on_url(wxCommandEvent& WXUNUSED(evt)) +{ + if (!m_browser) + return; +#ifdef DEBUG_URL_PANEL + m_browser->LoadURL(m_url->GetValue()); + m_browser->SetFocus(); +#endif +} + +/** + * Callback invoked when user pressed the "back" button + */ +void WebViewPanel::on_back_button(wxCommandEvent& WXUNUSED(evt)) +{ + if (!m_browser) + return; + m_browser->GoBack(); +} + +/** + * Callback invoked when user pressed the "forward" button + */ +void WebViewPanel::on_forward_button(wxCommandEvent& WXUNUSED(evt)) +{ + if (!m_browser) + return; + m_browser->GoForward(); +} + +/** + * Callback invoked when user pressed the "stop" button + */ +void WebViewPanel::on_stop_button(wxCommandEvent& WXUNUSED(evt)) +{ + if (!m_browser) + return; + m_browser->Stop(); +} + +/** + * Callback invoked when user pressed the "reload" button + */ +void WebViewPanel::on_reload_button(wxCommandEvent& WXUNUSED(evt)) +{ + if (!m_browser) + return; + m_browser->Reload(); +} + +void WebViewPanel::on_script_message(wxWebViewEvent& evt) +{ +} + +void WebViewPanel::on_navigation_request(wxWebViewEvent &evt) +{ +} + +void WebViewPanel::on_page_will_load() +{ +} + +/** + * Invoked when user selects the "View Source" menu item + */ +void WebViewPanel::on_view_source_request(wxCommandEvent& WXUNUSED(evt)) +{ + if (!m_browser) + return; + + SourceViewDialog dlg(this, m_browser->GetPageSource()); + dlg.ShowModal(); +} + +/** + * Invoked when user selects the "View Text" menu item + */ +void WebViewPanel::on_view_text_request(wxCommandEvent& WXUNUSED(evt)) +{ + if (!m_browser) + return; + + wxDialog textViewDialog(this, wxID_ANY, "Page Text", + wxDefaultPosition, wxSize(700, 500), + wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER); + + wxTextCtrl* text = new wxTextCtrl(this, wxID_ANY, m_browser->GetPageText(), + wxDefaultPosition, wxDefaultSize, + wxTE_MULTILINE | + wxTE_RICH | + wxTE_READONLY); + + wxBoxSizer* sizer = new wxBoxSizer(wxVERTICAL); + sizer->Add(text, 1, wxEXPAND); + SetSizer(sizer); + textViewDialog.ShowModal(); +} + +/** + * Invoked when user selects the "Menu" item + */ +void WebViewPanel::on_tools_clicked(wxCommandEvent& WXUNUSED(evt)) +{ + if (!m_browser) + return; + +#ifdef DEBUG_URL_PANEL + m_context_menu->Check(m_browser->IsContextMenuEnabled()); + m_dev_tools->Check(m_browser->IsAccessToDevToolsEnabled()); + + wxPoint position = ScreenToClient(wxGetMousePosition()); + PopupMenu(m_tools_menu, position.x, position.y); +#endif +} + +void WebViewPanel::run_script(const wxString& javascript) +{ + if (!m_browser || !m_shown) + return; + // 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; + BOOST_LOG_TRIVIAL(debug) << "RunScript " << javascript << "\n"; + m_browser->RunScriptAsync(javascript); +} + + +void WebViewPanel::on_run_script_custom(wxCommandEvent& WXUNUSED(evt)) +{ + wxTextEntryDialog dialog + ( + this, + "Please enter JavaScript code to execute", + wxGetTextFromUserPromptStr, + m_javascript, + wxOK | wxCANCEL | wxCENTRE | wxTE_MULTILINE + ); + if (dialog.ShowModal() != wxID_OK) + return; + + run_script(dialog.GetValue()); +} + +void WebViewPanel::on_add_user_script(wxCommandEvent& WXUNUSED(evt)) +{ + wxString userScript = "window.wx_test_var = 'wxWidgets webview sample';"; + wxTextEntryDialog dialog + ( + this, + "Enter the JavaScript code to run as the initialization script that runs before any script in the HTML document.", + wxGetTextFromUserPromptStr, + userScript, + wxOK | wxCANCEL | wxCENTRE | wxTE_MULTILINE + ); + if (dialog.ShowModal() != wxID_OK) + return; + + const wxString& javascript = dialog.GetValue(); + BOOST_LOG_TRIVIAL(debug) << "RunScript " << javascript << "\n"; + if (!m_browser->AddUserScript(javascript)) + wxLogError("Could not add user script"); +} + +void WebViewPanel::on_set_custom_user_agent(wxCommandEvent& WXUNUSED(evt)) +{ + if (!m_browser) + return; + + wxString customUserAgent = "Mozilla/5.0 (iPhone; CPU iPhone OS 13_1_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.1 Mobile/15E148 Safari/604.1"; + wxTextEntryDialog dialog + ( + this, + "Enter the custom user agent string you would like to use.", + wxGetTextFromUserPromptStr, + customUserAgent, + wxOK | wxCANCEL | wxCENTRE + ); + if (dialog.ShowModal() != wxID_OK) + return; + + if (!m_browser->SetUserAgent(customUserAgent)) + wxLogError("Could not set custom user agent"); +} + +void WebViewPanel::on_clear_selection(wxCommandEvent& WXUNUSED(evt)) +{ + if (!m_browser) + return; + + m_browser->ClearSelection(); +} + +void WebViewPanel::on_delete_selection(wxCommandEvent& WXUNUSED(evt)) +{ + if (!m_browser) + return; + + m_browser->DeleteSelection(); +} + +void WebViewPanel::on_select_all(wxCommandEvent& WXUNUSED(evt)) +{ + if (!m_browser) + return; + + m_browser->SelectAll(); +} + +void WebViewPanel::On_enable_context_menu(wxCommandEvent& evt) +{ + if (!m_browser) + return; + + m_browser->EnableContextMenu(evt.IsChecked()); +} +void WebViewPanel::On_enable_dev_tools(wxCommandEvent& evt) +{ + if (!m_browser) + return; + + m_browser->EnableAccessToDevTools(evt.IsChecked()); +} + +/** + * Callback invoked when a loading error occurs + */ +void WebViewPanel::on_error(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); + } + + BOOST_LOG_TRIVIAL(error) << "WebViewPanel error: " << category; + load_error_page(); +#ifdef DEBUG_URL_PANEL + m_info->ShowMessage(wxString("An error occurred loading ") + evt.GetURL() + "\n" + + "'" + category + "'", wxICON_ERROR); +#endif +} + +void WebViewPanel::sys_color_changed() +{ +#ifdef _WIN32 + wxGetApp().UpdateDarkUI(this); +#endif +} + +ConnectWebViewPanel::ConnectWebViewPanel(wxWindow* parent) + : WebViewPanel(parent, GUI::from_u8(Utils::ServiceConfig::instance().connect_url()), { "_prusaSlicer" }, "connect_loading") +{ + // m_browser->RegisterHandler(wxSharedPtr(new WebViewHandler("https"))); + + auto* plater = wxGetApp().plater(); + plater->Bind(EVT_UA_ID_USER_SUCCESS, &ConnectWebViewPanel::on_user_token, this); + plater->Bind(EVT_UA_LOGGEDOUT, &ConnectWebViewPanel::on_user_logged_out, this); +} + +ConnectWebViewPanel::~ConnectWebViewPanel() +{ + m_browser->Unbind(EVT_UA_ID_USER_SUCCESS, &ConnectWebViewPanel::on_user_token, this); +} + +wxString ConnectWebViewPanel::get_login_script(bool refresh) +{ + Plater* plater = wxGetApp().plater(); + const std::string& access_token = plater->get_user_account()->get_access_token(); + assert(!access_token.empty()); + auto javascript = wxString::Format( + +#if AUTH_VIA_FETCH_OVERRIDE + refresh + ? + "window.__access_token = '%s';window.__access_token_version = (window.__access_token_version || 0) + 1;console.log('Updated Auth token', window.__access_token);" + : + /* + * Notes: + * - The fetch() function has two distinct prototypes (i.e. input args): + * 1. fetch(url: string, options: object | undefined) + * 2. fetch(req: Request, options: object | undefined) + * - For some reason I can't explain the headers can be extended only via Request object + * i.e. the fetch prototype (2). So we need to convert (1) call into (2) before + * + */ + R"( + if (window.__fetch === undefined) { + window.__fetch = fetch; + window.fetch = function(req, opts = {}) { + if (typeof req === 'string') { + req = new Request(req, opts); + opts = {}; + } + if (window.__access_token && (req.url[0] == '/' || req.url.indexOf('prusa3d.com') > 0)) { + req.headers.set('Authorization', 'Bearer ' + window.__access_token); + console.log('Header updated: ', req.headers.get('Authorization')); + console.log('AT Version: ', __access_token_version); + } + //console.log('Injected fetch used', req, opts); + return __fetch(req, opts); + }; + } + window.__access_token = '%s'; + window.__access_token_version = 0; + )", +#else + refresh + ? + R"( + if (location.protocol === 'https:') { + if (window._prusaSlicer_initLogin !== undefined) { + console.log('Init login'); + if (window._prusaSlicer !== undefined) + _prusaSlicer.postMessage({action: 'LOG', message: 'Refreshing login'}); + _prusaSlicer_initLogin('%s'); + } else { + console.log('Refreshing login skipped as no _prusaSlicer_login defined (yet?)'); + if (window._prusaSlicer === undefined) { + console.log('Message handler _prusaSlicer not defined yet'); + } else { + _prusaSlicer.postMessage({action: 'LOG', message: 'Refreshing login skipped as no _prusaSlicer_initLogin defined (yet?)'}); + } + } + } + )" + : + R"( + function _prusaSlicer_log(msg) { + console.log(msg); + if (window._prusaSlicer !== undefined) + _prusaSlicer.postMessage({action: 'LOG', message: msg}); + } + function _prusaSlicer_errorHandler(err) { + const msg = { + action: 'ERROR', + error: typeof(err) === 'string' ? err : JSON.stringify(err), + critical: false + }; + console.error('Login error occurred', msg); + window._prusaSlicer.postMessage(msg); + }; + + function _prusaSlicer_delay(ms) { + return new Promise((resolve, reject) => { + setTimeout(resolve, ms); + }); + } + + async function _prusaSlicer_initLogin(token) { + const parts = token.split('.'); + const claims = JSON.parse(atob(parts[1])); + const now = new Date().getTime() / 1000; + if (claims.exp <= now) { + _prusaSlicer_log('Skipping initLogin as token is expired'); + return; + } + + let retry = false; + let backoff = 1000; + const maxBackoff = 64000; + do { + + let error = false; + + try { + _prusaSlicer_log('Slicer Login request ' + token.substring(token.length - 8)); + let resp = await fetch('/slicer/login', {method: 'POST', headers: {Authorization: 'Bearer ' + token}}); + let body = await resp.text(); + _prusaSlicer_log('Slicer Login resp ' + resp.status + ' (' + token.substring(token.length - 8) + ') body: ' + body); + if (resp.status >= 500 || resp.status == 408) { + retry = true; + } else { + retry = false; + if (resp.status >= 400) + _prusaSlicer_errorHandler({status: resp.status, body}); + } + } catch (e) { + _prusaSlicer_log('Slicer Login failed: ' + e.toString()); + console.error('Slicer Login failed', e.toString()); + retry = true; + } + + if (retry) { + await _prusaSlicer_delay(backoff + 1000 * Math.random()); + if (backoff < maxBackoff) { + backoff *= 2; + } + } + } while (retry); + } + + if (location.protocol === 'https:' && window._prusaSlicer) { + _prusaSlicer_log('Requesting login'); + _prusaSlicer.postMessage({action: 'REQUEST_LOGIN'}); + } + )", +#endif + access_token + ); + return javascript; +} + +wxString ConnectWebViewPanel::get_logout_script() +{ + return "sessionStorage.removeItem('_slicer_token');"; +} + +void ConnectWebViewPanel::on_page_will_load() +{ + auto javascript = get_login_script(false); + BOOST_LOG_TRIVIAL(debug) << "RunScript " << javascript << "\n"; + m_browser->AddUserScript(javascript); +} + +void ConnectWebViewPanel::on_user_token(UserAccountSuccessEvent& e) +{ + e.Skip(); + auto access_token = wxGetApp().plater()->get_user_account()->get_access_token(); + assert(!access_token.empty()); + + wxString javascript = get_login_script(true); + BOOST_LOG_TRIVIAL(debug) << "RunScript " << javascript << "\n"; + m_browser->RunScriptAsync(javascript); + resend_config(); +} + +void ConnectWebViewPanel::on_user_logged_out(UserAccountSuccessEvent& e) +{ + e.Skip(); + // clear token from session storage + m_browser->RunScriptAsync(get_logout_script()); +} + +void ConnectWebViewPanel::on_script_message(wxWebViewEvent& evt) +{ + BOOST_LOG_TRIVIAL(debug) << "received message from Prusa Connect FE: " << evt.GetString(); + handle_message(into_u8(evt.GetString())); +} +void ConnectWebViewPanel::on_navigation_request(wxWebViewEvent &evt) +{ +#ifdef DEBUG_URL_PANEL + m_url->SetValue(evt.GetURL()); +#endif + BOOST_LOG_TRIVIAL(debug) << "Navigation requested to: " << into_u8(evt.GetURL()); + if (evt.GetURL() == m_default_url) { + m_reached_default_url = true; + return; + } + if (evt.GetURL() == (GUI::format_wxstr("file:///%1%/web/connection_failed.html", boost::filesystem::path(resources_dir()).generic_string()))) { + return; + } + if (m_reached_default_url && !evt.GetURL().StartsWith(m_default_url)) { + BOOST_LOG_TRIVIAL(info) << evt.GetURL() << " does not start with default url. Vetoing."; + evt.Veto(); + } +} + +void ConnectWebViewPanel::on_connect_action_error(const std::string &message_data) +{ + ConnectRequestHandler::on_connect_action_error(message_data); + // TODO: make this more user friendly (and make sure only once opened if multiple errors happen) +// MessageDialog dialog( +// this, +// GUI::format_wxstr(_L("WebKit Runtime Error encountered:\n\n%s"), message_data), +// "WebKit Runtime Error", +// wxOK +// ); +// dialog.ShowModal(); + +} + +void ConnectWebViewPanel::logout() +{ + wxString script = L"window._prusaConnect_v1.logout()"; + run_script(script); + + Plater* plater = wxGetApp().plater(); + auto javascript = wxString::Format( + R"( + console.log('Preparing logout'); + window.fetch('/slicer/logout', {method: 'POST', headers: {Authorization: 'Bearer %s'}}) + .then(function (resp){ + console.log('Logout resp', resp); + resp.text().then(function (json) { console.log('Logout resp body', json) }); + }); + )", + plater->get_user_account()->get_access_token() + ); + BOOST_LOG_TRIVIAL(debug) << "RunScript " << javascript << "\n"; + m_browser->RunScript(javascript); + +} + +void ConnectWebViewPanel::sys_color_changed() +{ + resend_config(); +} + +void ConnectWebViewPanel::on_connect_action_request_login(const std::string &message_data) +{ + run_script_bridge(get_login_script(true)); +} + + +void ConnectWebViewPanel::on_connect_action_select_printer(const std::string& message_data) +{ + assert(!message_data.empty()); + wxGetApp().handle_connect_request_printer_select(message_data); +} +void ConnectWebViewPanel::on_connect_action_print(const std::string& message_data) +{ + // PRINT request is not defined for ConnectWebViewPanel + assert(true); +} + +PrinterWebViewPanel::PrinterWebViewPanel(wxWindow* parent, const wxString& default_url) + : WebViewPanel(parent, default_url, {}) +{ + if (!m_browser) + return; + + m_browser->Bind(wxEVT_WEBVIEW_LOADED, &PrinterWebViewPanel::on_loaded, this); +#ifndef NDEBUG + m_browser->EnableAccessToDevTools(); + m_browser->EnableContextMenu(); +#endif +} + +void PrinterWebViewPanel::on_loaded(wxWebViewEvent& evt) +{ + if (evt.GetURL().IsEmpty()) + return; + if (!m_api_key.empty()) { + send_api_key(); + } else if (!m_usr.empty() && !m_psk.empty()) { + send_credentials(); + } +} + +void PrinterWebViewPanel::send_api_key() +{ + if (!m_browser || m_api_key_sent) + return; + m_api_key_sent = true; + wxString key = from_u8(m_api_key); + wxString script = wxString::Format(R"( + // Check if window.fetch exists before overriding + if (window.originalFetch === undefined) { + console.log('Patching fetch with API key'); + window.originalFetch = window.fetch; + window.fetch = function(input, init = {}) { + init.headers = init.headers || {}; + init.headers['X-Api-Key'] = sessionStorage.getItem('apiKey'); + console.log('Patched fetch', input, init); + return window.originalFetch(input, init); + }; + } + sessionStorage.setItem('authType', 'ApiKey'); + sessionStorage.setItem('apiKey', '%s'); +)", + key); + + m_browser->RemoveAllUserScripts(); + BOOST_LOG_TRIVIAL(debug) << "RunScript " << script << "\n"; + m_browser->AddUserScript(script); + m_browser->Reload(); + remove_webview_credentials(m_browser); +} + +void PrinterWebViewPanel::send_credentials() +{ + if (!m_browser || m_api_key_sent) + return; + m_browser->RemoveAllUserScripts(); + m_browser->AddUserScript("sessionStorage.removeItem('authType'); sessionStorage.removeItem('apiKey'); console.log('Session Storage cleared');"); + m_browser->Reload(); + m_api_key_sent = true; + setup_webview_with_credentials(m_browser, m_usr, m_psk); +} + +void PrinterWebViewPanel::sys_color_changed() +{ +} + + +PrintablesWebViewPanel::PrintablesWebViewPanel(wxWindow* parent) + : WebViewPanel(parent, GUI::from_u8(Utils::ServiceConfig::instance().printables_url()), { "ExternalApp" }, "loading") +{ + m_browser->Bind(wxEVT_WEBVIEW_LOADED, &PrintablesWebViewPanel::on_loaded, this); + m_events["accessTokenExpired"] = std::bind(&PrintablesWebViewPanel::on_printables_event_access_token_expired, this, std::placeholders::_1); + m_events["printGcode"] = std::bind(&PrintablesWebViewPanel::on_printables_event_print_gcode, this, std::placeholders::_1); + m_events["reloadHomePage"] = std::bind(&PrintablesWebViewPanel::on_reload_event, this, std::placeholders::_1); +} + +void PrintablesWebViewPanel::handle_message(const std::string& message) +{ + + std::string event_string; + try { + std::stringstream ss(message); + pt::ptree ptree; + pt::read_json(ss, ptree); + if (const auto action = ptree.get_optional("event"); action) { + event_string = *action; + } + } + catch (const std::exception& e) { + BOOST_LOG_TRIVIAL(error) << "Could not parse printables message. " << e.what(); + return; + } + + if (event_string.empty()) { + BOOST_LOG_TRIVIAL(error) << "Received invalid message from printables (missing event). Message: " << message; + return; + } + assert(m_events.find(event_string) != m_events.end()); // this assert means there is an event that has no handling. + if (m_events.find(event_string) != m_events.end()) { + m_events[event_string](message); + } +} +void PrintablesWebViewPanel::on_printables_event_access_token_expired(const std::string& message_data) +{ + // accessTokenExpired + // Printables pozaduje refresh access tokenu, muze byt volano nekolikrat.Nechme na Mobilni aplikaci at zaridi to ze zareaguje jen jednou +} +void PrintablesWebViewPanel::on_printables_event_print_gcode(const std::string& message_data) +{ + // printGcode + // Uzivatel kliknul na tlacitko tisk u gcode. Dalsi posilane atributy jsou url, material, printer, nozzlediam +} +void PrintablesWebViewPanel::on_reload_event(const std::string& message_data) +{ + load_default_url(); +} +/* +Eventy Slicer -> Printables +accessTokenWillChange +WebUI zavola event predtim nez udela refresh access tokenu proti Prusa Accountu na Printables to bude znamenat pozastaveni requestu Mobile app muze chtit udelat refresh i bez explicitni predchozi printables zadosti skrz accessTokenExpired event +accessTokenChange +window postMessage JSON stringify { event 'accessTokenChange' token 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVC' } +volani po uspesne rotaci tokenu +historyBack +navigace zpet triggerovana z mobilni aplikace +historyForward +navigace vpred triggerovana z mobilni aplikace +*/ + +void PrintablesWebViewPanel::on_navigation_request(wxWebViewEvent &evt) +{ + const wxString url = evt.GetURL(); + // download with url + if (url.StartsWith(L"prusaslicer")) { + evt.Veto(); + wxGetApp().printables_request(into_u8(url)); + return; + } + if (url.StartsWith(m_default_url)) { + m_reached_default_url = true; + } else if (m_reached_default_url) { + BOOST_LOG_TRIVIAL(info) << evt.GetURL() << " does not start with default url. Vetoing."; + evt.Veto(); + } + + +} + +void PrintablesWebViewPanel::load_default_url() +{ + std::string actual_default_url = get_url_lang_theme(Utils::ServiceConfig::instance().printables_url()); + const std::string access_token = wxGetApp().plater()->get_user_account()->get_access_token(); + + // in case of opening printables logged out - delete cookies and localstorage to get rid of last login + if (access_token.empty()) { + delete_cookies(m_browser, Utils::ServiceConfig::instance().printables_url()); + m_browser->AddUserScript("localStorage.clear();"); + load_url(actual_default_url); + return; + } + // add token to first request +#ifdef _WIN32 + add_request_authorization(m_browser, m_default_url, access_token); + load_url(GUI::from_u8(actual_default_url)); +#else + load_request(m_browser, actual_default_url, access_token); +#endif +} + +void PrintablesWebViewPanel::on_loaded(wxWebViewEvent& evt) +{ +#ifdef _WIN32 + // This is needed only once after add_request_authorization + remove_request_authorization(m_browser); +#endif +} + +std::string PrintablesWebViewPanel::get_url_lang_theme(const wxString& url) +{ + // situations and reaction: + // 1) url is just a path (no query no fragment) -> query with lang and theme is added + // 2) url has query that contains lang and theme -> query and lang values are modified + // 3) url has query with just one of lang or theme -> query is modified and missing value is added + // 4) url has query of query and fragment without lang and theme -> query with lang and theme is added to the end of query + + std::string url_string = into_u8(url); + std::string theme = wxGetApp().dark_mode() ? "dark" : "light"; + wxString language = GUI::wxGetApp().current_language_code(); + if (language.size() > 2) + language = language.SubString(0, 1); + + // Replace lang and theme if already in url + bool lang_found = false; + std::regex lang_regex(R"((lang=)[^&#]*)"); + if (std::regex_search(url_string, lang_regex)) { + url_string = std::regex_replace(url_string, lang_regex, "$1" + into_u8(language)); + lang_found = true; + } + bool theme_found = false; + std::regex theme_regex(R"((theme=)[^&#]*)"); + if (std::regex_search(url_string, theme_regex)) { + url_string = std::regex_replace(url_string, theme_regex, "$1" + theme); + theme_found = true; + } + if (lang_found && theme_found) + return url_string; + + // missing params string + std::string new_params = lang_found ? GUI::format("theme=%1%", theme) + : theme_found ? GUI::format("lang=%1%", language) + : GUI::format("lang=%1%&theme=%2%", language, theme); + + // Regex to capture query and optional fragment + std::regex query_regex(R"((\?.*?)(#.*)?$)"); + + if (std::regex_search(url_string, query_regex)) { + // Append params before the fragment (if it exists) + return std::regex_replace(url_string, query_regex, "$1&" + new_params + "$2"); + } + std::regex fragment_regex(R"(#.*$)"); + if (std::regex_search(url_string, fragment_regex)) { + // Add params before the fragment + return std::regex_replace(url_string, fragment_regex, "?" + new_params + "$&"); + } + + return url_string + "?" + new_params; +} + +void PrintablesWebViewPanel::on_show(wxShowEvent& evt) +{ + m_shown = evt.IsShown(); + if (!m_shown) { + return; + } + if (m_load_default_url) { + m_load_default_url = false; + load_default_url(); + return; + } + // in case login changed, resend login / logout + // DK: it seems to me, it is safer to do login / logout (where logout means requesting the page again) + // on every show of panel, + // than to keep information if we have printables page in same state as slicer in terms of login + // But im afraid it will be concidered not pretty... + const std::string access_token = wxGetApp().plater()->get_user_account()->get_access_token(); + if (access_token.empty()) { + logout(); + } else { + login(access_token); + } + +} + +void PrintablesWebViewPanel::logout() +{ + if (!m_shown) { + return; + } + delete_cookies(m_browser, Utils::ServiceConfig::instance().printables_url()); + m_browser->RunScript("localStorage.clear();"); + +#ifdef _WIN32 + load_url(GUI::from_u8(get_url_lang_theme(m_browser->GetCurrentURL()))); +#else + // We cannot do simple reload here, it would keep the access token in the header + load_request(m_browser, get_url_lang_theme(m_browser->GetCurrentURL()), std::string()); +#endif // + +} +void PrintablesWebViewPanel::login(const std::string access_token) +{ + if (!m_shown) { + return; + } + // We cannot add token to header as when making the first request. + // In fact, we shall not do request here, only run scripts. + // postMessage accessTokenWillChange -> postMessage accessTokenChange -> window.location.reload(); + + wxString script = "window.postMessage(JSON.stringify({ event: 'accessTokenWillChange' }))"; + run_script(script); + + script = GUI::format_wxstr("window.postMessage(JSON.stringify({" + "event: 'accessTokenChange'," + "token: '%1%'" + "}));" + , access_token); + run_script(script); + + run_script("window.location.reload();"); +} +void PrintablesWebViewPanel::send_refreshed_token(const std::string access_token) +{ + if (m_load_default_url) { + return; + } + wxString script = GUI::format_wxstr("window.postMessage(JSON.stringify({" + "event: 'accessTokenChange'," + "token: '%1%'" + "}));" + , access_token); + run_script(script); +} +void PrintablesWebViewPanel::send_will_refresh() +{ + if (m_load_default_url) { + return; + } + wxString script = "window.postMessage(JSON.stringify({ event: 'accessTokenWillChange' }))"; + run_script(script); +} + +void PrintablesWebViewPanel::on_script_message(wxWebViewEvent& evt) +{ + BOOST_LOG_TRIVIAL(error) << "received message from Printables: " << evt.GetString(); + handle_message(into_u8(evt.GetString())); +} + +void PrintablesWebViewPanel::sys_color_changed() +{ + if (m_shown) { + load_url(GUI::from_u8(get_url_lang_theme(m_browser->GetCurrentURL()))); + } + WebViewPanel::sys_color_changed(); +} + +} // namespace slic3r::GUI \ No newline at end of file diff --git a/src/slic3r/GUI/WebViewPanel.hpp b/src/slic3r/GUI/WebViewPanel.hpp new file mode 100644 index 0000000000..eaeeb433de --- /dev/null +++ b/src/slic3r/GUI/WebViewPanel.hpp @@ -0,0 +1,195 @@ +#ifndef slic3r_WebViewPanel_hpp_ +#define slic3r_WebViewPanel_hpp_ + +#include +#include +#include + +#include "GUI_Utils.hpp" +#include "UserAccountSession.hpp" +#include "ConnectRequestHandler.hpp" + +#ifdef DEBUG_URL_PANEL +#include +#endif + +class wxWebView; +class wxWebViewEvent; + +wxDECLARE_EVENT(EVT_OPEN_EXTERNAL_LOGIN, wxCommandEvent); + +namespace Slic3r::GUI { + +class WebViewPanel : public wxPanel +{ +public: + WebViewPanel(wxWindow *parent, const wxString& default_url, const std::vector& message_handler_names, const std::string& loading_html = "loading"); + virtual ~WebViewPanel(); + + void load_url(const wxString& url); + void load_default_url_delayed(); + void load_error_page(); + + virtual void on_show(wxShowEvent& evt); + virtual void on_script_message(wxWebViewEvent& evt); + + void on_idle(wxIdleEvent& evt); + void on_url(wxCommandEvent& evt); + virtual void on_back_button(wxCommandEvent& evt); + virtual void on_forward_button(wxCommandEvent& evt); + void on_stop_button(wxCommandEvent& evt); + virtual void on_reload_button(wxCommandEvent& evt); + + void on_view_source_request(wxCommandEvent& evt); + void on_view_text_request(wxCommandEvent& evt); + void on_tools_clicked(wxCommandEvent& evt); + void on_error(wxWebViewEvent& evt); + + void run_script(const wxString& javascript); + void on_run_script_custom(wxCommandEvent& evt); + void on_add_user_script(wxCommandEvent& evt); + void on_set_custom_user_agent(wxCommandEvent& evt); + void on_clear_selection(wxCommandEvent& evt); + void on_delete_selection(wxCommandEvent& evt); + void on_select_all(wxCommandEvent& evt); + void On_enable_context_menu(wxCommandEvent& evt); + void On_enable_dev_tools(wxCommandEvent& evt); + virtual void on_navigation_request(wxWebViewEvent &evt); + + wxString get_default_url() const { return m_default_url; } + void set_default_url(const wxString& url) { m_default_url = url; } + + virtual void sys_color_changed(); + + void set_load_default_url_on_next_error(bool val) { m_load_default_url_on_next_error = val; } + +protected: + virtual void on_page_will_load(); + + wxWebView* m_browser { nullptr }; + bool m_load_default_url { false }; + + wxBoxSizer* topsizer; + wxBoxSizer* m_sizer_top; +#ifdef DEBUG_URL_PANEL + + wxBoxSizer *bSizer_toolbar; + wxButton * m_button_back; + wxButton * m_button_forward; + wxButton * m_button_stop; + wxButton * m_button_reload; + wxTextCtrl *m_url; + wxButton * m_button_tools; + + wxMenu* m_tools_menu; + wxMenuItem* m_script_custom; + + wxInfoBar *m_info; + wxStaticText* m_info_text; + + wxMenuItem* m_context_menu; + wxMenuItem* m_dev_tools; +#endif + + // Last executed JavaScript snippet, for convenience. + wxString m_javascript; + wxString m_response_js; + wxString m_default_url; + bool m_reached_default_url {false}; + + std::string m_loading_html; + //DECLARE_EVENT_TABLE() + + bool m_load_error_page { false }; + bool m_shown { false }; + bool m_load_default_url_on_next_error { false }; + + std::vector m_script_message_hadler_names; +}; + +class ConnectWebViewPanel : public WebViewPanel, public ConnectRequestHandler +{ +public: + ConnectWebViewPanel(wxWindow* parent); + ~ConnectWebViewPanel() override; + void on_script_message(wxWebViewEvent& evt) override; + void logout(); + void sys_color_changed() override; + void on_navigation_request(wxWebViewEvent &evt) override; +protected: + void on_connect_action_request_login(const std::string &message_data) override; + void on_connect_action_select_printer(const std::string& message_data) override; + void on_connect_action_print(const std::string& message_data) override; + void on_connect_action_webapp_ready(const std::string& message_data) override {} + void run_script_bridge(const wxString& script) override {run_script(script); } + void on_page_will_load() override; + void on_connect_action_error(const std::string &message_data) override; +private: + static wxString get_login_script(bool refresh); + static wxString get_logout_script(); + void on_user_token(UserAccountSuccessEvent& e); + void on_user_logged_out(UserAccountSuccessEvent& e); +}; + +class PrinterWebViewPanel : public WebViewPanel +{ +public: + PrinterWebViewPanel(wxWindow* parent, const wxString& default_url); + + void on_loaded(wxWebViewEvent& evt); + + void send_api_key(); + void send_credentials(); + void set_api_key(const std::string &key) + { + if (m_api_key != key) { + clear(); + m_api_key = key; + } + } + void set_credentials(const std::string &usr, const std::string &psk) + { + if (m_usr != usr || m_psk != psk) { + clear(); + m_usr = usr; + m_psk = psk; + } + } + void clear() { m_api_key.clear(); m_usr.clear(); m_psk.clear(); m_api_key_sent = false; } + void sys_color_changed() override; +private: + std::string m_api_key; + std::string m_usr; + std::string m_psk; + bool m_api_key_sent {false}; +}; + +class PrintablesWebViewPanel : public WebViewPanel +{ +public: + PrintablesWebViewPanel(wxWindow* parent); + void on_navigation_request(wxWebViewEvent &evt) override; + void on_loaded(wxWebViewEvent& evt); + void on_show(wxShowEvent& evt) override; + void on_script_message(wxWebViewEvent& evt) override; + void sys_color_changed() override; + + void logout(); + void login(const std::string access_token); + void send_refreshed_token(const std::string access_token); + void send_will_refresh(); +private: + void handle_message(const std::string& message); + void on_printables_event_access_token_expired(const std::string& message_data); + void on_printables_event_print_gcode(const std::string& message_data); + void on_reload_event(const std::string& message_data); + + void load_default_url(); + std::string get_url_lang_theme(const wxString& url); + + std::map> m_events; + +}; +} // namespace Slic3r::GUI + +#endif /* slic3r_WebViewPanel_hpp_ */ \ No newline at end of file diff --git a/src/slic3r/GUI/WebViewPlatformUtils.hpp b/src/slic3r/GUI/WebViewPlatformUtils.hpp index 2c1429d21a..f6d5a8b2ed 100644 --- a/src/slic3r/GUI/WebViewPlatformUtils.hpp +++ b/src/slic3r/GUI/WebViewPlatformUtils.hpp @@ -7,5 +7,8 @@ namespace Slic3r::GUI { void setup_webview_with_credentials(wxWebView* web_view, const std::string& username, const std::string& password); void remove_webview_credentials(wxWebView* web_view); void delete_cookies(wxWebView* web_view, const std::string& url); + void add_request_authorization(wxWebView* web_view, const wxString& address, const std::string& token); + void remove_request_authorization(wxWebView* web_view); + void load_request(wxWebView* web_view, const std::string& address, const std::string& token); } diff --git a/src/slic3r/GUI/WebViewPlatformUtilsLinux.cpp b/src/slic3r/GUI/WebViewPlatformUtilsLinux.cpp index 35161a1c45..80290293c6 100644 --- a/src/slic3r/GUI/WebViewPlatformUtilsLinux.cpp +++ b/src/slic3r/GUI/WebViewPlatformUtilsLinux.cpp @@ -6,6 +6,7 @@ #include "WebViewPlatformUtils.hpp" #include +#include namespace Slic3r::GUI { @@ -60,7 +61,7 @@ void delete_cookie_callback (GObject* source_object, GAsyncResult* result, void* { WebKitCookieManager *cookie_manager = WEBKIT_COOKIE_MANAGER(source_object); GError* err = nullptr; - gboolean b = webkit_cookie_manager_delete_cookie_finish(cookie_manager, result, &err); + webkit_cookie_manager_delete_cookie_finish(cookie_manager, result, &err); if (err) { BOOST_LOG_TRIVIAL(error) << "Error deleting cookies: " << err->message; g_error_free(err); @@ -107,4 +108,40 @@ void delete_cookies(wxWebView* web_view, const std::string& url) WebKitCookieManager* cookieManager = webkit_web_context_get_cookie_manager(context); webkit_cookie_manager_get_cookies(cookieManager, uri, nullptr, (GAsyncReadyCallback)Slic3r::GUI::get_cookie_callback, nullptr); } + +void add_request_authorization(wxWebView* web_view, const wxString& address, const std::string& token) +{ + // unused on Linux + assert(true); +} +void remove_request_authorization(wxWebView* web_view) +{ + // unused on Linux + assert(true); +} + +void load_request(wxWebView* web_view, const std::string& address, const std::string& token) +{ + WebKitWebView* native_backend = static_cast(web_view->GetNativeBackend()); + WebKitURIRequest* request = webkit_uri_request_new(address.c_str()); + if(!request) + { + BOOST_LOG_TRIVIAL(error) << "load_request failed: request is nullptr. address: " << address; + return; + } + SoupMessageHeaders* soup_headers = webkit_uri_request_get_http_headers(request); + if (!soup_headers) + { + BOOST_LOG_TRIVIAL(error) << "load_request failed: soup_headers is nullptr."; + return; + } + if (!token.empty()) + { + soup_message_headers_append(soup_headers, "Authorization", ("External " + token).c_str()); + } + + // Load the request in the WebView + webkit_web_view_load_request(native_backend, request); +} + } diff --git a/src/slic3r/GUI/WebViewPlatformUtilsMac.mm b/src/slic3r/GUI/WebViewPlatformUtilsMac.mm index b09c69f31f..ad04db60d2 100644 --- a/src/slic3r/GUI/WebViewPlatformUtilsMac.mm +++ b/src/slic3r/GUI/WebViewPlatformUtilsMac.mm @@ -155,7 +155,27 @@ void delete_cookies(wxWebView* web_view, const std::string& url) } } }]; - +} +void add_request_authorization(wxWebView* web_view, const wxString& address, const std::string& token) +{ + // unused on MacOS + assert(true); +} +void remove_request_authorization(wxWebView* web_view) +{ + // unused on MacOS + assert(true); +} +void load_request(wxWebView* web_view, const std::string& address, const std::string& token) +{ + WKWebView* backend = static_cast(web_view->GetNativeBackend()); + NSString *url_string = [NSString stringWithCString:address.c_str() encoding:[NSString defaultCStringEncoding]]; + NSString *token_string = [NSString stringWithCString:token.c_str() encoding:[NSString defaultCStringEncoding]]; + NSURL *url = [NSURL URLWithString:url_string]; + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; + NSString *auth_value = [NSString stringWithFormat:@"External %@", token_string]; + [request setValue:auth_value forHTTPHeaderField:@"Authorization"]; + [backend loadRequest:request]; } } diff --git a/src/slic3r/GUI/WebViewPlatformUtilsWin32.cpp b/src/slic3r/GUI/WebViewPlatformUtilsWin32.cpp index 4e98a0d7dc..7445031258 100644 --- a/src/slic3r/GUI/WebViewPlatformUtilsWin32.cpp +++ b/src/slic3r/GUI/WebViewPlatformUtilsWin32.cpp @@ -135,6 +135,7 @@ void delete_cookies(wxWebView* webview, const std::string& url) std::string domain = cookie.second.get("domain"); // Delete cookie by name and domain wxString name_and_domain = GUI::format_wxstr(L"{\"name\": \"%1%\", \"domain\": \"%2%\"}", name, domain); + BOOST_LOG_TRIVIAL(debug) << "Deleting cookie: " << name_and_domain; webView2->CallDevToolsProtocolMethod(L"Network.deleteCookies", name_and_domain.c_str(), Microsoft::WRL::Callback( [](HRESULT errorCode, LPCWSTR resultJson) -> HRESULT { return S_OK; }).Get()); @@ -145,6 +146,150 @@ void delete_cookies(wxWebView* webview, const std::string& url) } +static EventRegistrationToken m_webResourceRequestedTokenForImageBlocking = {}; +static wxString filter_patern; +namespace { +void RequestHeadersToLog(ICoreWebView2HttpRequestHeaders* requestHeaders) +{ + wxCOMPtr iterator; + requestHeaders->GetIterator(&iterator); + BOOL hasCurrent = FALSE; + BOOST_LOG_TRIVIAL(info) <<"Logging request headers:"; + + while (SUCCEEDED(iterator->get_HasCurrentHeader(&hasCurrent)) && hasCurrent) + { + wchar_t* name = nullptr; + wchar_t* value = nullptr; + + iterator->GetCurrentHeader(&name, &value); + BOOST_LOG_TRIVIAL(debug) <<"name: " << name << L", value: " << value; + if (name) { + CoTaskMemFree(name); + } + if (value) { + CoTaskMemFree(value); + } + + BOOL hasNext = FALSE; + iterator->MoveNext(&hasNext); + } +} +} + +void add_request_authorization(wxWebView* webview, const wxString& address, const std::string& token) +{ + // This function adds a filter so when pattern document is being requested, callback is triggered + // Inside add_WebResourceRequested callback, there is a Authorization header added. + // The filter needs to be removed to stop adding the auth header + ICoreWebView2 *webView2 = static_cast(webview->GetNativeBackend()); + if (!webView2) { + return; + } + wxCOMPtr wv2_2; + HRESULT hr = webView2->QueryInterface(IID_PPV_ARGS(&wv2_2)); + if (FAILED(hr)) { + return; + } + filter_patern = address + "/*"; + webView2->AddWebResourceRequestedFilter( filter_patern.c_str(), COREWEBVIEW2_WEB_RESOURCE_CONTEXT_DOCUMENT); + + if (FAILED(webView2->add_WebResourceRequested( + Microsoft::WRL::Callback( + [token](ICoreWebView2 *sender, ICoreWebView2WebResourceRequestedEventArgs *args) { + // Get the web resource request + wxCOMPtr request; + HRESULT hr = args->get_Request(&request); + if (FAILED(hr)) + { + BOOST_LOG_TRIVIAL(error) << "Adding request Authorization: Failed to get_Request."; + return S_OK; + } + // Get the request headers + wxCOMPtr headers; + hr = request->get_Headers(&headers); + if (FAILED(hr)) + { + BOOST_LOG_TRIVIAL(error) << "Adding request Authorization: Failed to get_Headers."; + return S_OK; + } + LPWSTR wideUri = nullptr; + request->get_Uri(&wideUri); + std::wstring ws(wideUri); + + std::string val = "External " + token; + // Add or modify the Authorization header + hr = headers->SetHeader(L"Authorization", GUI::from_u8(val).c_str()); + BOOST_LOG_TRIVIAL(debug) << "add_WebResourceRequested " << ws; + + // This function is only needed for debug purpose + RequestHeadersToLog(headers.Get()); + return S_OK; + } + ).Get(), &m_webResourceRequestedTokenForImageBlocking + ))) { + + BOOST_LOG_TRIVIAL(error) << "Adding request Authorization: Failed to add callback."; + } + + +} + +void remove_request_authorization(wxWebView* webview) +{ + ICoreWebView2 *webView2 = static_cast(webview->GetNativeBackend()); + if (!webView2) { + return; + } + webView2->RemoveWebResourceRequestedFilter(filter_patern.c_str(), COREWEBVIEW2_WEB_RESOURCE_CONTEXT_DOCUMENT); + if(FAILED(webView2->remove_WebResourceRequested( m_webResourceRequestedTokenForImageBlocking))) { + BOOST_LOG_TRIVIAL(error) << "WebView: Failed to remove resources"; + } +} + +void load_request(wxWebView* web_view, const std::string& address, const std::string& token) +{ + // This function should create its own GET request and send it (works on linux) + // For that we would use NavigateWithWebResourceRequest. + // For that we need ICoreWebView2Environment smart pointer. + // Such pointer does exists inside wxWebView edge backend. (wxWebViewEdgeImpl::m_webViewEnvironment) + // But its currently private and not getable. (It wouldn't be such problem to create the getter) + + ICoreWebView2 *webView2 = static_cast(web_view->GetNativeBackend()); + if (!webView2) { + return; + } + + // GetEnviroment does not exists + wxCOMPtr webViewEnvironment; + //webViewEnvironment = static_cast(web_view->GetEnviroment()); + if (!webViewEnvironment.Get()) { + return; + } + + wxCOMPtr webViewEnvironment2; + if (FAILED(webViewEnvironment->QueryInterface(IID_PPV_ARGS(&webViewEnvironment2)))) + { + return; + } + wxCOMPtr webResourceRequest; + + if (FAILED(webViewEnvironment2->CreateWebResourceRequest( + L"https://www.printables.com/", L"GET", NULL, + L"Content-Type: application/x-www-form-urlencoded", &webResourceRequest))) + { + return; + } + wxCOMPtr wv2_2; + if (FAILED(webView2->QueryInterface(IID_PPV_ARGS(&wv2_2)))) { + return; + } + if (FAILED(wv2_2->NavigateWithWebResourceRequest(webResourceRequest.get()))) + { + return; + } + +} + } // namespace Slic3r::GUI #endif // WIN32 diff --git a/src/slic3r/Utils/ServiceConfig.cpp b/src/slic3r/Utils/ServiceConfig.cpp index e5bf44141c..84b66bb2e9 100644 --- a/src/slic3r/Utils/ServiceConfig.cpp +++ b/src/slic3r/Utils/ServiceConfig.cpp @@ -24,7 +24,9 @@ ServiceConfig::ServiceConfig() , m_account_url("https://account.prusa3d.com") , m_account_client_id("oamhmhZez7opFosnwzElIgE2oGgI2iJORSkw587O") , m_media_url("https://media.printables.com") - , m_preset_repo_url("https://preset-repo-api.prusa3d.com") { + , m_preset_repo_url("https://preset-repo-api.prusa3d.com") + , m_printables_url("https://www.printables.com") +{ #ifdef SLIC3R_REPO_URL m_preset_repo_url = SLIC3R_REPO_URL; #endif @@ -34,6 +36,7 @@ ServiceConfig::ServiceConfig() update_from_env(m_account_client_id, "PRUSA_ACCOUNT_CLIENT_ID"); update_from_env(m_media_url, "PRUSA_MEDIA_URL", true); update_from_env(m_preset_repo_url, "PRUSA_PRESET_REPO_URL", true); + update_from_env(m_printables_url, "PRUSA_PRINTABLES_URL", true); } ServiceConfig& ServiceConfig::instance() diff --git a/src/slic3r/Utils/ServiceConfig.hpp b/src/slic3r/Utils/ServiceConfig.hpp index fdcf40d034..6bfe5d9a94 100644 --- a/src/slic3r/Utils/ServiceConfig.hpp +++ b/src/slic3r/Utils/ServiceConfig.hpp @@ -30,6 +30,8 @@ public: bool webdev_enabled() const { return m_webdev_enabled; } void set_webdev_enabled(bool enabled) { m_webdev_enabled = enabled; } + const std::string& printables_url() const { return m_printables_url; } + static ServiceConfig& instance(); private: std::string m_connect_url; @@ -37,6 +39,7 @@ private: std::string m_account_client_id; std::string m_media_url; std::string m_preset_repo_url; + std::string m_printables_url; bool m_webdev_enabled{false}; }; From 20af63211585e9356da4bc002cbe484ff44898a1 Mon Sep 17 00:00:00 2001 From: David Kocik Date: Tue, 22 Oct 2024 13:05:29 +0200 Subject: [PATCH 03/12] SPE-2515: Reload button inside html pages and button in menu --- resources/web/connect_connection_failed.html | 28 ++++++++++ ...ading.html => connect_loading_reload.html} | 1 + ...iled.html => other_connection_failed.html} | 1 + ...loading.html => other_loading_reload.html} | 1 + src/slic3r/GUI/ConnectRequestHandler.cpp | 2 + src/slic3r/GUI/ConnectRequestHandler.hpp | 5 +- src/slic3r/GUI/MainFrame.cpp | 31 ++++++++++- src/slic3r/GUI/MainFrame.hpp | 2 + src/slic3r/GUI/WebView.cpp | 1 - src/slic3r/GUI/WebViewDialog.cpp | 9 ++++ src/slic3r/GUI/WebViewDialog.hpp | 2 +- src/slic3r/GUI/WebViewPanel.cpp | 53 ++++++++++++++++--- src/slic3r/GUI/WebViewPanel.hpp | 9 +++- src/slic3r/GUI/WebViewPlatformUtilsWin32.cpp | 4 +- 14 files changed, 131 insertions(+), 18 deletions(-) create mode 100644 resources/web/connect_connection_failed.html rename resources/web/{connect_loading.html => connect_loading_reload.html} (97%) rename resources/web/{connection_failed.html => other_connection_failed.html} (76%) rename resources/web/{loading.html => other_loading_reload.html} (93%) diff --git a/resources/web/connect_connection_failed.html b/resources/web/connect_connection_failed.html new file mode 100644 index 0000000000..7936470b41 --- /dev/null +++ b/resources/web/connect_connection_failed.html @@ -0,0 +1,28 @@ + + + + + + Connection failed + + + +
+

Connection failed

+

Something went wrong.

+ +
+ + diff --git a/resources/web/connect_loading.html b/resources/web/connect_loading_reload.html similarity index 97% rename from resources/web/connect_loading.html rename to resources/web/connect_loading_reload.html index bfe8401d2d..7a1909a5de 100644 --- a/resources/web/connect_loading.html +++ b/resources/web/connect_loading_reload.html @@ -71,6 +71,7 @@

+ diff --git a/resources/web/connection_failed.html b/resources/web/other_connection_failed.html similarity index 76% rename from resources/web/connection_failed.html rename to resources/web/other_connection_failed.html index 72cb686232..3ea607b3be 100644 --- a/resources/web/connection_failed.html +++ b/resources/web/other_connection_failed.html @@ -22,6 +22,7 @@

Connection failed

Something went wrong.

+
diff --git a/resources/web/loading.html b/resources/web/other_loading_reload.html similarity index 93% rename from resources/web/loading.html rename to resources/web/other_loading_reload.html index 0f50d68325..e07a212c4c 100644 --- a/resources/web/loading.html +++ b/resources/web/other_loading_reload.html @@ -62,6 +62,7 @@

+ diff --git a/src/slic3r/GUI/ConnectRequestHandler.cpp b/src/slic3r/GUI/ConnectRequestHandler.cpp index 7a156bc78b..1fe6ac3b0d 100644 --- a/src/slic3r/GUI/ConnectRequestHandler.cpp +++ b/src/slic3r/GUI/ConnectRequestHandler.cpp @@ -25,6 +25,8 @@ ConnectRequestHandler::ConnectRequestHandler() m_actions["REQUEST_OPEN_IN_BROWSER"] = std::bind(&ConnectRequestHandler::on_connect_action_request_open_in_browser, this, std::placeholders::_1); m_actions["ERROR"] = std::bind(&ConnectRequestHandler::on_connect_action_error, this, std::placeholders::_1); m_actions["LOG"] = std::bind(&ConnectRequestHandler::on_connect_action_log, this, std::placeholders::_1); + m_actions["RELOAD_HOME_PAGE"] = std::bind(&ConnectRequestHandler::on_reload_event, this, std::placeholders::_1); + } ConnectRequestHandler::~ConnectRequestHandler() { diff --git a/src/slic3r/GUI/ConnectRequestHandler.hpp b/src/slic3r/GUI/ConnectRequestHandler.hpp index ea64913977..b783194782 100644 --- a/src/slic3r/GUI/ConnectRequestHandler.hpp +++ b/src/slic3r/GUI/ConnectRequestHandler.hpp @@ -8,7 +8,7 @@ #include #include -//#define DEBUG_URL_PANEL +#define DEBUG_URL_PANEL namespace Slic3r::GUI { class ConnectRequestHandler @@ -20,7 +20,7 @@ public: void handle_message(const std::string& message); void resend_config(); protected: - // action callbacs stored in m_actions + // action callbacks stored in m_actions virtual void on_connect_action_log(const std::string& message_data); virtual void on_connect_action_error(const std::string& message_data); virtual void on_connect_action_request_login(const std::string& message_data); @@ -29,6 +29,7 @@ protected: virtual void on_connect_action_select_printer(const std::string& message_data) = 0; virtual void on_connect_action_print(const std::string& message_data) = 0; virtual void on_connect_action_webapp_ready(const std::string& message_data) = 0; + virtual void on_reload_event(const std::string& message_data) = 0; virtual void run_script_bridge(const wxString &script) = 0; std::map> m_actions; diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 94a18ba293..ba37ccc6e7 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -868,7 +868,9 @@ void MainFrame::remove_connect_webview_tab() void MainFrame::show_connect_tab(const wxString& url) { - assert(m_connect_webview_added); + if (!m_connect_webview_added) { + return; + } m_tabpanel->SetSelection(m_tabpanel->FindPage(m_connect_webview)); m_connect_webview->set_load_default_url_on_next_error(true); m_connect_webview->load_url(url); @@ -984,6 +986,29 @@ void MainFrame::set_printer_webview_credentials(const std::string& usr, const st m_printer_webview->set_credentials(usr, psk); } +bool MainFrame::is_any_webview_selected() +{ + int selection = m_tabpanel->GetSelection(); + if ( selection == m_tabpanel->FindPage(m_printables_webview)) + return true; + if (m_connect_webview_added && selection == m_tabpanel->FindPage(m_connect_webview)) + return true; + if (m_printer_webview_added && selection == m_tabpanel->FindPage(m_printer_webview)) + return true; + return false; +} + +void MainFrame::reload_selected_webview() +{ + int selection = m_tabpanel->GetSelection(); + if ( selection == m_tabpanel->FindPage(m_printables_webview)) + m_printables_webview->do_reload(); + if (m_connect_webview_added && selection == m_tabpanel->FindPage(m_connect_webview)) + m_connect_webview->do_reload(); + if (m_printer_webview_added && selection == m_tabpanel->FindPage(m_printer_webview)) + m_printer_webview->do_reload(); +} + void Slic3r::GUI::MainFrame::refresh_account_menu(bool avatar/* = false */) { // Update User name in TopBar @@ -1686,6 +1711,10 @@ void MainFrame::init_menubar_as_editor() wxFULLSCREEN_NOSTATUSBAR | wxFULLSCREEN_NOBORDER | wxFULLSCREEN_NOCAPTION); }, this, []() { return true; }, [this]() { return this->IsFullScreen(); }, this); #endif // __APPLE__ + + viewMenu->AppendSeparator(); + append_menu_item(viewMenu, wxID_ANY, _L("&Reload WebView"), _L("Reload WebView"), + [this](wxCommandEvent&) { reload_selected_webview(); }, "", nullptr, [this]() {return is_any_webview_selected(); }, this); } // Help menu diff --git a/src/slic3r/GUI/MainFrame.hpp b/src/slic3r/GUI/MainFrame.hpp index 359961fe90..2856b695f8 100644 --- a/src/slic3r/GUI/MainFrame.hpp +++ b/src/slic3r/GUI/MainFrame.hpp @@ -237,6 +237,8 @@ public: bool get_printer_webview_tab_added() const { return m_printer_webview_added; } void set_printer_webview_api_key(const std::string& key); void set_printer_webview_credentials(const std::string& usr, const std::string& psk); + bool is_any_webview_selected(); + void reload_selected_webview(); void refresh_account_menu(bool avatar = false); diff --git a/src/slic3r/GUI/WebView.cpp b/src/slic3r/GUI/WebView.cpp index b236b29155..e4f04205e5 100644 --- a/src/slic3r/GUI/WebView.cpp +++ b/src/slic3r/GUI/WebView.cpp @@ -45,7 +45,6 @@ wxWebView* WebView::CreateWebView(wxWindow * parent, const wxString& url, const #endif for (const std::string& handler : message_handlers) { if (!webView->AddScriptMessageHandler(Slic3r::GUI::into_u8(handler))) { - // TODO: dialog to user !!! //wxLogError("Could not add script message handler"); BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << "Could not add script message handler " << handler; } diff --git a/src/slic3r/GUI/WebViewDialog.cpp b/src/slic3r/GUI/WebViewDialog.cpp index 52bacaa391..c3d920567f 100644 --- a/src/slic3r/GUI/WebViewDialog.cpp +++ b/src/slic3r/GUI/WebViewDialog.cpp @@ -618,6 +618,15 @@ void PrinterPickWebViewDialog::on_dpi_changed(const wxRect &suggested_rect) Refresh(); } +void PrinterPickWebViewDialog::on_reload_event(const std::string& message_data) +{ + if (!m_browser) { + return; + } + m_browser->LoadURL(m_default_url); +} + + LoginWebViewDialog::LoginWebViewDialog(wxWindow *parent, std::string &ret_val, const wxString& url, wxEvtHandler* evt_handler) : WebViewDialog(parent, url, _L("Log in dialog"), wxSize(50 * wxGetApp().em_unit(), 80 * wxGetApp().em_unit()), {}) , m_ret_val(ret_val) diff --git a/src/slic3r/GUI/WebViewDialog.hpp b/src/slic3r/GUI/WebViewDialog.hpp index 82ff2f9739..d9e890fc3f 100644 --- a/src/slic3r/GUI/WebViewDialog.hpp +++ b/src/slic3r/GUI/WebViewDialog.hpp @@ -104,7 +104,7 @@ protected: void request_compatible_printers_SLA(); void run_script_bridge(const wxString& script) override { run_script(script); } void on_dpi_changed(const wxRect &suggested_rect) override; - + void on_reload_event(const std::string& message_data) override; private: std::string& m_ret_val; }; diff --git a/src/slic3r/GUI/WebViewPanel.cpp b/src/slic3r/GUI/WebViewPanel.cpp index 12c8c1ff5b..3329658c81 100644 --- a/src/slic3r/GUI/WebViewPanel.cpp +++ b/src/slic3r/GUI/WebViewPanel.cpp @@ -58,10 +58,11 @@ void WebViewPanel::load_url(const wxString& url) } -WebViewPanel::WebViewPanel(wxWindow *parent, const wxString& default_url, const std::vector& message_handler_names, const std::string& loading_html/* = "loading"*/) +WebViewPanel::WebViewPanel(wxWindow *parent, const wxString& default_url, const std::vector& message_handler_names, const std::string& loading_html, const std::string& error_html) : wxPanel(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize) , m_default_url (default_url) , m_loading_html(loading_html) + , m_error_html(error_html) , m_script_message_hadler_names(message_handler_names) { topsizer = new wxBoxSizer(wxVERTICAL); @@ -188,7 +189,9 @@ void WebViewPanel::on_show(wxShowEvent& evt) { m_shown = evt.IsShown(); if (evt.IsShown() && m_load_default_url) { - load_url(m_default_url); + m_load_default_url = false; + //load_url(m_default_url); + load_error_page(); } } @@ -207,7 +210,7 @@ void WebViewPanel::on_idle(wxIdleEvent& WXUNUSED(evt)) m_load_default_url_on_next_error = false; load_url(m_default_url); } else { - load_url(GUI::format_wxstr("file://%1%/web/connection_failed.html", boost::filesystem::path(resources_dir()).generic_string())); + load_url(GUI::format_wxstr("file://%1%/web/%2%.html", boost::filesystem::path(resources_dir()).generic_string(), m_error_html)); } } } @@ -271,6 +274,7 @@ void WebViewPanel::on_reload_button(wxCommandEvent& WXUNUSED(evt)) void WebViewPanel::on_script_message(wxWebViewEvent& evt) { + BOOST_LOG_TRIVIAL(error) << "unhandled script message: " << evt.GetString(); } void WebViewPanel::on_navigation_request(wxWebViewEvent &evt) @@ -465,7 +469,7 @@ case type: \ WX_ERROR_CASE(wxWEBVIEW_NAV_ERR_OTHER); } - BOOST_LOG_TRIVIAL(error) << "WebViewPanel error: " << category; + BOOST_LOG_TRIVIAL(error) << this <<" WebViewPanel error: " << category; load_error_page(); #ifdef DEBUG_URL_PANEL m_info->ShowMessage(wxString("An error occurred loading ") + evt.GetURL() + "\n" + @@ -473,6 +477,28 @@ case type: \ #endif } +void WebViewPanel::do_reload() +{ + if (!m_browser) { + return; + } + const wxString current_url = m_browser->GetCurrentURL(); + if (current_url.StartsWith(m_default_url)) + { + m_browser->Reload(); + return; + } + load_default_url(); +} + +void WebViewPanel::load_default_url() +{ + if (!m_browser) { + return; + } + load_url(m_default_url); +} + void WebViewPanel::sys_color_changed() { #ifdef _WIN32 @@ -481,7 +507,7 @@ void WebViewPanel::sys_color_changed() } ConnectWebViewPanel::ConnectWebViewPanel(wxWindow* parent) - : WebViewPanel(parent, GUI::from_u8(Utils::ServiceConfig::instance().connect_url()), { "_prusaSlicer" }, "connect_loading") + : WebViewPanel(parent, GUI::from_u8(Utils::ServiceConfig::instance().connect_url()), { "_prusaSlicer" }, "connect_loading_reload", "connect_connection_failed") { // m_browser->RegisterHandler(wxSharedPtr(new WebViewHandler("https"))); @@ -702,6 +728,11 @@ void ConnectWebViewPanel::on_connect_action_error(const std::string &message_dat } +void ConnectWebViewPanel::on_reload_event(const std::string& message_data) +{ + load_default_url(); +} + void ConnectWebViewPanel::logout() { wxString script = L"window._prusaConnect_v1.logout()"; @@ -747,7 +778,7 @@ void ConnectWebViewPanel::on_connect_action_print(const std::string& message_dat } PrinterWebViewPanel::PrinterWebViewPanel(wxWindow* parent, const wxString& default_url) - : WebViewPanel(parent, default_url, {}) + : WebViewPanel(parent, default_url, {"ExternalApp"}, "other_loading_reload", "other_connection_failed") { if (!m_browser) return; @@ -757,6 +788,7 @@ PrinterWebViewPanel::PrinterWebViewPanel(wxWindow* parent, const wxString& defau m_browser->EnableAccessToDevTools(); m_browser->EnableContextMenu(); #endif + } void PrinterWebViewPanel::on_loaded(wxWebViewEvent& evt) @@ -769,6 +801,11 @@ void PrinterWebViewPanel::on_loaded(wxWebViewEvent& evt) send_credentials(); } } +void PrinterWebViewPanel::on_script_message(wxWebViewEvent& evt) +{ + // Only reload messages are being sent now. + load_default_url(); +} void PrinterWebViewPanel::send_api_key() { @@ -817,7 +854,7 @@ void PrinterWebViewPanel::sys_color_changed() PrintablesWebViewPanel::PrintablesWebViewPanel(wxWindow* parent) - : WebViewPanel(parent, GUI::from_u8(Utils::ServiceConfig::instance().printables_url()), { "ExternalApp" }, "loading") + : WebViewPanel(parent, GUI::from_u8(Utils::ServiceConfig::instance().printables_url()), { "ExternalApp" }, "other_loading_reload", "other_connection_failed") { m_browser->Bind(wxEVT_WEBVIEW_LOADED, &PrintablesWebViewPanel::on_loaded, this); m_events["accessTokenExpired"] = std::bind(&PrintablesWebViewPanel::on_printables_event_access_token_expired, this, std::placeholders::_1); @@ -899,7 +936,7 @@ void PrintablesWebViewPanel::on_navigation_request(wxWebViewEvent &evt) void PrintablesWebViewPanel::load_default_url() { - std::string actual_default_url = get_url_lang_theme(Utils::ServiceConfig::instance().printables_url()); + std::string actual_default_url = get_url_lang_theme(Utils::ServiceConfig::instance().printables_url() + "/homepage"); const std::string access_token = wxGetApp().plater()->get_user_account()->get_access_token(); // in case of opening printables logged out - delete cookies and localstorage to get rid of last login diff --git a/src/slic3r/GUI/WebViewPanel.hpp b/src/slic3r/GUI/WebViewPanel.hpp index eaeeb433de..bddb30cb6a 100644 --- a/src/slic3r/GUI/WebViewPanel.hpp +++ b/src/slic3r/GUI/WebViewPanel.hpp @@ -23,7 +23,7 @@ namespace Slic3r::GUI { class WebViewPanel : public wxPanel { public: - WebViewPanel(wxWindow *parent, const wxString& default_url, const std::vector& message_handler_names, const std::string& loading_html = "loading"); + WebViewPanel(wxWindow *parent, const wxString& default_url, const std::vector& message_handler_names, const std::string& loading_html, const std::string& error_html); virtual ~WebViewPanel(); void load_url(const wxString& url); @@ -58,6 +58,8 @@ public: wxString get_default_url() const { return m_default_url; } void set_default_url(const wxString& url) { m_default_url = url; } + virtual void do_reload(); + virtual void load_default_url(); virtual void sys_color_changed(); @@ -98,6 +100,7 @@ protected: bool m_reached_default_url {false}; std::string m_loading_html; + std::string m_error_html; //DECLARE_EVENT_TABLE() bool m_load_error_page { false }; @@ -124,6 +127,7 @@ protected: void run_script_bridge(const wxString& script) override {run_script(script); } void on_page_will_load() override; void on_connect_action_error(const std::string &message_data) override; + void on_reload_event(const std::string& message_data) override; private: static wxString get_login_script(bool refresh); static wxString get_logout_script(); @@ -137,6 +141,7 @@ public: PrinterWebViewPanel(wxWindow* parent, const wxString& default_url); void on_loaded(wxWebViewEvent& evt); + void on_script_message(wxWebViewEvent& evt) override; void send_api_key(); void send_credentials(); @@ -184,7 +189,7 @@ private: void on_printables_event_print_gcode(const std::string& message_data); void on_reload_event(const std::string& message_data); - void load_default_url(); + void load_default_url() override; std::string get_url_lang_theme(const wxString& url); std::map> m_events; diff --git a/src/slic3r/GUI/WebViewPlatformUtilsWin32.cpp b/src/slic3r/GUI/WebViewPlatformUtilsWin32.cpp index 7445031258..2beeed0dce 100644 --- a/src/slic3r/GUI/WebViewPlatformUtilsWin32.cpp +++ b/src/slic3r/GUI/WebViewPlatformUtilsWin32.cpp @@ -286,10 +286,8 @@ void load_request(wxWebView* web_view, const std::string& address, const std::st if (FAILED(wv2_2->NavigateWithWebResourceRequest(webResourceRequest.get()))) { return; - } - + } } - } // namespace Slic3r::GUI #endif // WIN32 From 2d41c295057480d822807d182f4be9fe1afb3af2 Mon Sep 17 00:00:00 2001 From: David Kocik Date: Thu, 26 Sep 2024 16:00:15 +0200 Subject: [PATCH 04/12] SPE-2507: Connect upload dialog size --- src/slic3r/GUI/ConnectRequestHandler.hpp | 2 +- src/slic3r/GUI/WebViewDialog.cpp | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/ConnectRequestHandler.hpp b/src/slic3r/GUI/ConnectRequestHandler.hpp index b783194782..017d68f0de 100644 --- a/src/slic3r/GUI/ConnectRequestHandler.hpp +++ b/src/slic3r/GUI/ConnectRequestHandler.hpp @@ -8,7 +8,7 @@ #include #include -#define DEBUG_URL_PANEL +//#define DEBUG_URL_PANEL namespace Slic3r::GUI { class ConnectRequestHandler diff --git a/src/slic3r/GUI/WebViewDialog.cpp b/src/slic3r/GUI/WebViewDialog.cpp index c3d920567f..8c3c2f781a 100644 --- a/src/slic3r/GUI/WebViewDialog.cpp +++ b/src/slic3r/GUI/WebViewDialog.cpp @@ -436,11 +436,12 @@ PrinterPickWebViewDialog::PrinterPickWebViewDialog(wxWindow* parent, std::string : WebViewDialog(parent , GUI::from_u8(Utils::ServiceConfig::instance().connect_select_printer_url()) , _L("Choose a printer") - , wxSize(std::max(parent->GetClientSize().x / 2, 100 * wxGetApp().em_unit()), std::max(parent->GetClientSize().y / 2, 50 * wxGetApp().em_unit())) + , wxSize(std::max(parent->GetClientSize().x / 4 * 3, 150 * wxGetApp().em_unit()), std::max(parent->GetClientSize().y / 6 * 5, 100 * wxGetApp().em_unit())) ,{"_prusaSlicer"} , "connect_loading") , m_ret_val(ret_val) { + SetMinSize(wxSize(std::max(parent->GetClientSize().x / 2, 100 * wxGetApp().em_unit()), std::max(parent->GetClientSize().y / 2, 50 * wxGetApp().em_unit()))); Centre(); } void PrinterPickWebViewDialog::on_show(wxShowEvent& evt) From 7ffa854ec279cc3585c5dc141dd1064ccc126b58 Mon Sep 17 00:00:00 2001 From: David Kocik Date: Wed, 23 Oct 2024 09:59:44 +0200 Subject: [PATCH 05/12] SPE-2535: Handling Printables events Fix of loading url and deleting cookies Handling Printables events: download and slice Link from notification to Printables tab Webview download notification cleanup No reload button on loading screen --- resources/web/connect_loading_reload.html | 2 +- resources/web/other_loading_reload.html | 2 +- src/slic3r/GUI/ConfigWizardWebViewPage.cpp | 2 +- src/slic3r/GUI/Downloader.cpp | 47 +++-- src/slic3r/GUI/Downloader.hpp | 7 +- src/slic3r/GUI/DownloaderFileGet.cpp | 34 +--- src/slic3r/GUI/DownloaderFileGet.hpp | 14 +- src/slic3r/GUI/GUI_App.cpp | 39 +++- src/slic3r/GUI/GUI_App.hpp | 5 +- src/slic3r/GUI/MainFrame.cpp | 13 +- src/slic3r/GUI/MainFrame.hpp | 3 +- src/slic3r/GUI/NotificationManager.cpp | 61 +++++- src/slic3r/GUI/NotificationManager.hpp | 17 ++ src/slic3r/GUI/WebViewDialog.cpp | 2 +- src/slic3r/GUI/WebViewPanel.cpp | 212 +++++++++++++++++---- src/slic3r/GUI/WebViewPanel.hpp | 24 ++- 16 files changed, 382 insertions(+), 102 deletions(-) diff --git a/resources/web/connect_loading_reload.html b/resources/web/connect_loading_reload.html index 7a1909a5de..018926044c 100644 --- a/resources/web/connect_loading_reload.html +++ b/resources/web/connect_loading_reload.html @@ -71,7 +71,7 @@

- + diff --git a/resources/web/other_loading_reload.html b/resources/web/other_loading_reload.html index e07a212c4c..68033cdc37 100644 --- a/resources/web/other_loading_reload.html +++ b/resources/web/other_loading_reload.html @@ -62,7 +62,7 @@

- + diff --git a/src/slic3r/GUI/ConfigWizardWebViewPage.cpp b/src/slic3r/GUI/ConfigWizardWebViewPage.cpp index 7d1a915c66..075eb9539a 100644 --- a/src/slic3r/GUI/ConfigWizardWebViewPage.cpp +++ b/src/slic3r/GUI/ConfigWizardWebViewPage.cpp @@ -125,7 +125,7 @@ void ConfigWizardWebViewPage::on_navigation_request(wxWebViewEvent &evt) { wxString url = evt.GetURL(); if (url.starts_with(L"prusaslicer")) { - delete_cookies(m_browser, "https://account.prusa3d.com"); + delete_cookies(m_browser, Utils::ServiceConfig::instance().account_url()); delete_cookies(m_browser, "https://accounts.google.com"); delete_cookies(m_browser, "https://appleid.apple.com"); delete_cookies(m_browser, "https://facebook.com"); diff --git a/src/slic3r/GUI/Downloader.cpp b/src/slic3r/GUI/Downloader.cpp index 61e69227bb..5234cded6b 100644 --- a/src/slic3r/GUI/Downloader.cpp +++ b/src/slic3r/GUI/Downloader.cpp @@ -90,14 +90,14 @@ std::string unescape_url(const std::string& unescaped) } } -Download::Download(int ID, std::string url, wxEvtHandler* evt_handler, const boost::filesystem::path& dest_folder) +Download::Download(int ID, std::string url, wxEvtHandler* evt_handler, const boost::filesystem::path& dest_folder, bool load_after) : m_id(ID) , m_filename(filename_from_url(url)) , m_dest_folder(dest_folder) { assert(boost::filesystem::is_directory(dest_folder)); m_final_path = dest_folder / m_filename; - m_file_get = std::make_shared(ID, std::move(url), m_filename, evt_handler, dest_folder); + m_file_get = std::make_shared(ID, std::move(url), m_filename, evt_handler, dest_folder, load_after); } void Download::start() @@ -132,11 +132,6 @@ void Download::resume() Downloader::Downloader() : wxEvtHandler() { - //Bind(EVT_DWNLDR_FILE_COMPLETE, [](const wxCommandEvent& evt) {}); - //Bind(EVT_DWNLDR_FILE_PROGRESS, [](const wxCommandEvent& evt) {}); - //Bind(EVT_DWNLDR_FILE_ERROR, [](const wxCommandEvent& evt) {}); - //Bind(EVT_DWNLDR_FILE_NAME_CHANGE, [](const wxCommandEvent& evt) {}); - Bind(EVT_DWNLDR_FILE_COMPLETE, &Downloader::on_complete, this); Bind(EVT_DWNLDR_FILE_PROGRESS, &Downloader::on_progress, this); Bind(EVT_DWNLDR_FILE_ERROR, &Downloader::on_error, this); @@ -169,14 +164,38 @@ void Downloader::start_download(const std::string& full_url) return; } - std::string text(escaped_url); - m_downloads.emplace_back(std::make_unique(id, std::move(escaped_url), this, m_dest_folder)); + m_downloads.emplace_back(std::make_unique(id, std::move(escaped_url), this, m_dest_folder, true)); NotificationManager* ntf_mngr = wxGetApp().notification_manager(); ntf_mngr->push_download_URL_progress_notification(id, m_downloads.back()->get_filename(), std::bind(&Downloader::user_action_callback, this, std::placeholders::_1, std::placeholders::_2)); m_downloads.back()->start(); BOOST_LOG_TRIVIAL(debug) << "started download"; } +void Downloader::start_download_printables(const std::string& url, bool load_after, const std::string& printables_url, GUI_App* app) +{ + assert(m_initialized); + + size_t id = get_next_id(); + + if (!boost::starts_with(url, "https://") || !FileGet::is_subdomain(url, "printables.com")) { + std::string msg = format(_L("Download won't start. Download URL doesn't point to https://printables.com : %1%"), url); + BOOST_LOG_TRIVIAL(error) << msg; + NotificationManager* ntf_mngr = wxGetApp().notification_manager(); + ntf_mngr->push_notification(NotificationType::CustomNotification, NotificationManager::NotificationLevel::RegularNotificationLevel, msg); + return; + } + + m_downloads.emplace_back(std::make_unique(id, url, this, m_dest_folder, load_after)); + NotificationManager* ntf_mngr = wxGetApp().notification_manager(); + ntf_mngr->push_download_URL_progress_notification_with_printables_link( id + , m_downloads.back()->get_filename() + , printables_url + , std::bind(&Downloader::user_action_callback, this, std::placeholders::_1, std::placeholders::_2) + , std::bind(&GUI_App::open_link_in_printables, app, std::placeholders::_1) + ); + m_downloads.back()->start(); +} + void Downloader::on_progress(wxCommandEvent& event) { size_t id = event.GetInt(); @@ -195,14 +214,14 @@ void Downloader::on_error(wxCommandEvent& event) ntf_mngr->set_download_URL_error(id, into_u8(event.GetString())); show_error(nullptr, format_wxstr(L"%1%\n%2%", _L("The download has failed") + ":", event.GetString())); } -void Downloader::on_complete(wxCommandEvent& event) +void Downloader::on_complete(Event& event) { - // TODO: is this always true? : // here we open the file itself, notification should get 1.f progress from on progress. - set_download_state(event.GetInt(), DownloadState::DownloadDone); + set_download_state(event.data.id, DownloadState::DownloadDone); wxArrayString paths; - paths.Add(event.GetString()); - wxGetApp().plater()->load_files(paths); + paths.Add(event.data.path); + if (event.data.load_after) + wxGetApp().plater()->load_files(paths); } bool Downloader::user_action_callback(DownloaderUserAction action, int id) { diff --git a/src/slic3r/GUI/Downloader.hpp b/src/slic3r/GUI/Downloader.hpp index 4fe896f5ce..c9011fab2e 100644 --- a/src/slic3r/GUI/Downloader.hpp +++ b/src/slic3r/GUI/Downloader.hpp @@ -13,6 +13,7 @@ namespace Slic3r { namespace GUI { class NotificationManager; +class GUI_App; enum DownloadState { @@ -35,7 +36,7 @@ enum DownloaderUserAction class Download { public: - Download(int ID, std::string url, wxEvtHandler* evt_handler, const boost::filesystem::path& dest_folder); + Download(int ID, std::string url, wxEvtHandler* evt_handler, const boost::filesystem::path& dest_folder, bool load_after); void start(); void cancel(); void pause(); @@ -67,6 +68,8 @@ public: m_initialized = true; } void start_download(const std::string& full_url); + + void start_download_printables(const std::string& url, bool load_after, const std::string& printables_url, GUI_App* app); // cancel = false -> just pause bool user_action_callback(DownloaderUserAction action, int id); private: @@ -80,7 +83,7 @@ private: void on_progress(wxCommandEvent& event); void on_error(wxCommandEvent& event); - void on_complete(wxCommandEvent& event); + void on_complete(Event& event); void on_name_change(wxCommandEvent& event); void on_paused(wxCommandEvent& event); void on_canceled(wxCommandEvent& event); diff --git a/src/slic3r/GUI/DownloaderFileGet.cpp b/src/slic3r/GUI/DownloaderFileGet.cpp index 8cbb89abcd..7de1996e90 100644 --- a/src/slic3r/GUI/DownloaderFileGet.cpp +++ b/src/slic3r/GUI/DownloaderFileGet.cpp @@ -71,7 +71,7 @@ unsigned get_current_pid() } // int = DOWNLOAD ID; string = file path -wxDEFINE_EVENT(EVT_DWNLDR_FILE_COMPLETE, wxCommandEvent); +wxDEFINE_EVENT(EVT_DWNLDR_FILE_COMPLETE, Event); // int = DOWNLOAD ID; string = error msg wxDEFINE_EVENT(EVT_DWNLDR_FILE_ERROR, wxCommandEvent); // int = DOWNLOAD ID; string = progress percent @@ -97,17 +97,19 @@ struct FileGet::priv std::atomic_bool m_stopped { false }; // either canceled or paused - download is not running size_t m_written { 0 }; size_t m_absolute_size { 0 }; - priv(int ID, std::string&& url, const std::string& filename, wxEvtHandler* evt_handler, const boost::filesystem::path& dest_folder); + bool m_load_after; + priv(int ID, std::string&& url, const std::string& filename, wxEvtHandler* evt_handler, const boost::filesystem::path& dest_folder, bool load_after); void get_perform(); }; -FileGet::priv::priv(int ID, std::string&& url, const std::string& filename, wxEvtHandler* evt_handler, const boost::filesystem::path& dest_folder) +FileGet::priv::priv(int ID, std::string&& url, const std::string& filename, wxEvtHandler* evt_handler, const boost::filesystem::path& dest_folder, bool load_after) : m_id(ID) , m_url(std::move(url)) , m_filename(filename) , m_evt_handler(evt_handler) , m_dest_folder(dest_folder) + , m_load_after(load_after) { } @@ -264,23 +266,8 @@ void FileGet::priv::get_perform() m_evt_handler->QueueEvent(evt); }) .on_complete([&](std::string body, unsigned /* http_status */) { - - // TODO: perform a body size check - // - //size_t body_size = body.size(); - //if (body_size != expected_size) { - // return; - //} try { - /* - if (m_written < body.size()) - { - // this code should never be entered. As there should be on_progress call after last bit downloaded. - std::string part_for_write = body.substr(m_written); - fwrite(part_for_write.c_str(), 1, part_for_write.size(), file); - } - */ fclose(file); boost::filesystem::rename(m_tmp_path, dest_path); } @@ -294,18 +281,15 @@ void FileGet::priv::get_perform() m_evt_handler->QueueEvent(evt); return; } - - wxCommandEvent* evt = new wxCommandEvent(EVT_DWNLDR_FILE_COMPLETE); - evt->SetString(dest_path.wstring()); - evt->SetInt(m_id); - m_evt_handler->QueueEvent(evt); + DownloadEventData event_data = {m_id, dest_path.wstring(), m_load_after}; + wxQueueEvent(m_evt_handler, new Event(EVT_DWNLDR_FILE_COMPLETE, event_data)); }) .perform_sync(); } -FileGet::FileGet(int ID, std::string url, const std::string& filename, wxEvtHandler* evt_handler, const boost::filesystem::path& dest_folder) - : p(new priv(ID, std::move(url), filename, evt_handler, dest_folder)) +FileGet::FileGet(int ID, std::string url, const std::string& filename, wxEvtHandler* evt_handler, const boost::filesystem::path& dest_folder, bool load_after) + : p(new priv(ID, std::move(url), filename, evt_handler, dest_folder, load_after)) {} FileGet::FileGet(FileGet&& other) : p(std::move(other.p)) {} diff --git a/src/slic3r/GUI/DownloaderFileGet.hpp b/src/slic3r/GUI/DownloaderFileGet.hpp index 7859d52f62..caf94b2c26 100644 --- a/src/slic3r/GUI/DownloaderFileGet.hpp +++ b/src/slic3r/GUI/DownloaderFileGet.hpp @@ -5,6 +5,8 @@ #ifndef slic3r_DownloaderFileGet_hpp_ #define slic3r_DownloaderFileGet_hpp_ +#include "Event.hpp" + #include "../Utils/Http.hpp" #include @@ -19,7 +21,7 @@ class FileGet : public std::enable_shared_from_this { private: struct priv; public: - FileGet(int ID, std::string url, const std::string& filename, wxEvtHandler* evt_handler,const boost::filesystem::path& dest_folder); + FileGet(int ID, std::string url, const std::string& filename, wxEvtHandler* evt_handler,const boost::filesystem::path& dest_folder, bool load_after); FileGet(FileGet&& other); ~FileGet(); @@ -27,12 +29,18 @@ public: void cancel(); void pause(); void resume(); - static bool is_subdomain(const std::string& url, const std::string& domain); + static bool is_subdomain(const std::string& url, const std::string& domain); private: std::unique_ptr p; }; +struct DownloadEventData +{ + int id; + wxString path; + bool load_after; +}; // int = DOWNLOAD ID; string = file path -wxDECLARE_EVENT(EVT_DWNLDR_FILE_COMPLETE, wxCommandEvent); +wxDECLARE_EVENT(EVT_DWNLDR_FILE_COMPLETE, Event); // int = DOWNLOAD ID; string = error msg wxDECLARE_EVENT(EVT_DWNLDR_FILE_PROGRESS, wxCommandEvent); // int = DOWNLOAD ID; string = progress percent diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index a9836a36f7..a938865bb9 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -4112,13 +4112,46 @@ void GUI_App::show_printer_webview_tab() mainframe->show_printer_webview_tab(preset_bundle->physical_printers.get_selected_printer_config()); } -void GUI_App::printables_request(const std::string& url) + +void GUI_App::printables_download_request(const std::string& download_url, const std::string& model_url) +{ + //this->mainframe->select_tab(size_t(0)); + + //lets always init so if the download dest folder was changed, new dest is used + boost::filesystem::path dest_folder(app_config->get("url_downloader_dest")); + if (dest_folder.empty() || !boost::filesystem::is_directory(dest_folder)) { + std::string msg = _u8L("Could not start URL download. Destination folder is not set. Please choose destination folder in Configuration Wizard."); + BOOST_LOG_TRIVIAL(error) << msg; + show_error(nullptr, msg); + return; + } + m_downloader->init(dest_folder); + m_downloader->start_download_printables(download_url, false, model_url, this); +} +void GUI_App::printables_slice_request(const std::string& download_url, const std::string& model_url) { this->mainframe->select_tab(size_t(0)); - start_download(url); + + //lets always init so if the download dest folder was changed, new dest is used + boost::filesystem::path dest_folder(app_config->get("url_downloader_dest")); + if (dest_folder.empty() || !boost::filesystem::is_directory(dest_folder)) { + std::string msg = _u8L("Could not start URL download. Destination folder is not set. Please choose destination folder in Configuration Wizard."); + BOOST_LOG_TRIVIAL(error) << msg; + show_error(nullptr, msg); + return; + } + m_downloader->init(dest_folder); + m_downloader->start_download_printables(download_url, true, model_url, this); +} +void GUI_App::printables_print_request(const std::string& download_url, const std::string& model_url) +{ + } - +void GUI_App::open_link_in_printables(const std::string& url) +{ + mainframe->show_printables_tab(url); +} bool LogGui::ignorred_message(const wxString& msg) { diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index 7b18a0d324..cf3c30c287 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -436,7 +436,10 @@ public: void request_project_download(std::string project_id) {} void request_open_project(std::string project_id) {} void request_remove_project(std::string project_id) {} - void printables_request(const std::string& url); + void printables_download_request(const std::string& download_url, const std::string& model_url); + void printables_slice_request(const std::string& download_url, const std::string& model_url); + void printables_print_request(const std::string& download_url, const std::string& model_url); + void open_link_in_printables(const std::string& url); private: bool on_init_inner(); void init_app_config(); diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index ba37ccc6e7..eca7401888 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -875,7 +875,15 @@ void MainFrame::show_connect_tab(const wxString& url) m_connect_webview->set_load_default_url_on_next_error(true); m_connect_webview->load_url(url); } - +void MainFrame::show_printables_tab(const std::string& url) +{ + if (!m_printables_webview_added) { + return; + } + m_tabpanel->SetSelection(m_tabpanel->FindPage(m_printables_webview)); + m_printables_webview->set_load_default_url_on_next_error(true); + m_printables_webview->load_url_from_outside(url); +} void MainFrame::add_printables_webview_tab() { if (m_printables_webview_added) { @@ -886,8 +894,7 @@ void MainFrame::add_printables_webview_tab() wxWindow* page = m_printables_webview; const wxString text(L"Printables"); const std::string bmp_name = ""; - bool bSelect = false; - m_tabpanel->InsertNewPage(n, page, text, bmp_name, bSelect); + m_tabpanel->InsertNewPage(n, page, text, bmp_name, false); m_printables_webview->load_default_url_delayed(); m_printables_webview_added = true; } diff --git a/src/slic3r/GUI/MainFrame.hpp b/src/slic3r/GUI/MainFrame.hpp index 2856b695f8..20614681b6 100644 --- a/src/slic3r/GUI/MainFrame.hpp +++ b/src/slic3r/GUI/MainFrame.hpp @@ -224,7 +224,8 @@ public: void on_account_will_refresh(); void on_account_did_refresh(const std::string& token); void on_account_logout(); - void show_connect_tab(const wxString &url); + void show_connect_tab(const wxString& url); + void show_printables_tab(const std::string& url); void add_printables_webview_tab(); void remove_printables_webview_tab(); diff --git a/src/slic3r/GUI/NotificationManager.cpp b/src/slic3r/GUI/NotificationManager.cpp index 4edae8d1d6..c9ed154e09 100644 --- a/src/slic3r/GUI/NotificationManager.cpp +++ b/src/slic3r/GUI/NotificationManager.cpp @@ -936,9 +936,8 @@ void NotificationManager::ProgressBarNotification::render_text(const float win_s render_cancel_button(win_size_x, win_size_y, win_pos_x, win_pos_y); render_bar(win_size_x, win_size_y, win_pos_x, win_pos_y); } - - } + void NotificationManager::ProgressBarNotification::render_bar(const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) { ImVec4 orange_color = ImVec4(.99f, .313f, .0f, 1.0f); @@ -1073,6 +1072,51 @@ void NotificationManager::ProgressBarWithCancelNotification::render_bar(const fl ImGuiPureWrap::text(text.c_str()); } +//------URLDownloadWithPrintablesLinkNotification---------------- +void NotificationManager::URLDownloadWithPrintablesLinkNotification::init() +{ + PopNotification::init(); + //m_lines_count++; + if (m_endlines.empty()) { + m_endlines.push_back(0); + } + + m_lines_count = 3; + m_multiline = true; + while (m_endlines.size() < 3) + m_endlines.push_back(m_endlines.back()); + + if(m_state == EState::Shown) + m_state = EState::NotFading; +} +bool NotificationManager::URLDownloadWithPrintablesLinkNotification::on_text_click() +{ + m_hypertext_callback_override(m_hypertext); + return false; +} +void NotificationManager::URLDownloadWithPrintablesLinkNotification::render_text(const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) +{ + assert(m_multiline); + assert(m_text1.size() >= m_endlines[0] || m_text1.size() >= m_endlines[1]); + if(m_endlines[0] > m_text1.size() || m_endlines[1] > m_text1.size()) + return; + // 1 lines text (what doesn't fit, wont show), 1 line hypertext, 1 line bar + ImGui::SetCursorPosX(m_left_indentation); + ImGui::SetCursorPosY(m_line_height / 4); + ImGuiPureWrap::text(m_text1.substr(0, m_endlines[0]).c_str()); + + ImGui::SetCursorPosX(m_left_indentation); + ImGui::SetCursorPosY(m_line_height + m_line_height / 4); + std::string line = _u8L("Open Printables project page"); + //ImGuiPureWrap::text(line.c_str()); + render_hypertext(m_left_indentation, m_line_height + m_line_height / 4, line); + + if (m_has_cancel_button) + render_cancel_button(win_size_x, win_size_y, win_pos_x, win_pos_y); + render_bar(win_size_x, win_size_y, win_pos_x, win_pos_y); +} + + //------URLDownloadNotification---------------- void NotificationManager::URLDownloadNotification::render_close_button(const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) @@ -2495,6 +2539,19 @@ void NotificationManager::push_download_URL_progress_notification(size_t id, con push_notification_data(std::make_unique(data, m_id_provider, m_evt_handler, id, user_action_callback), 0); } +void NotificationManager::push_download_URL_progress_notification_with_printables_link(size_t id, const std::string& text, const std::string& url, std::function user_action_callback, std::function hypertext_callback) +{ + // If already exists + for (std::unique_ptr& notification : m_pop_notifications) { + if (notification->get_type() == NotificationType::URLDownload && dynamic_cast(notification.get())->get_download_id() == id) { + return; + } + } + // push new one + NotificationData data{ NotificationType::URLDownload, NotificationLevel::ProgressBarNotificationLevel, 30, _u8L("Download") + ": " + text, url }; + push_notification_data(std::make_unique(data, m_id_provider, m_evt_handler, id, user_action_callback, hypertext_callback), 0); +} + void NotificationManager::set_download_URL_progress(size_t id, float percentage) { for (std::unique_ptr& notification : m_pop_notifications) { diff --git a/src/slic3r/GUI/NotificationManager.hpp b/src/slic3r/GUI/NotificationManager.hpp index 22e48c51d4..0c6390f2ec 100644 --- a/src/slic3r/GUI/NotificationManager.hpp +++ b/src/slic3r/GUI/NotificationManager.hpp @@ -244,6 +244,7 @@ public: void set_download_progress_percentage(float percentage); // Download URL progress notif void push_download_URL_progress_notification(size_t id, const std::string& text, std::function user_action_callback); + void push_download_URL_progress_notification_with_printables_link(size_t id, const std::string& text, const std::string& url, std::function user_action_callback, std::function hypertext_callback); void set_download_URL_progress(size_t id, float percentage); void set_download_URL_paused(size_t id); void set_download_URL_canceled(size_t id); @@ -577,6 +578,22 @@ private: std::string m_error_message; }; + class URLDownloadWithPrintablesLinkNotification : public URLDownloadNotification + { + public: + URLDownloadWithPrintablesLinkNotification(const NotificationData& n, NotificationIDProvider& id_provider, wxEvtHandler* evt_handler, size_t download_id, std::function user_action_callback, std::function hypertext_callback) + : URLDownloadNotification(n, id_provider, evt_handler, download_id, user_action_callback) + , m_hypertext_callback_override(hypertext_callback) + { + } + protected: + void render_text(const float win_size_x, const float win_size_y, + const float win_pos_x, const float win_pos_y) override; + void init() override; + bool on_text_click() override; + std::function m_hypertext_callback_override; + }; + class PrintHostUploadNotification : public ProgressBarNotification { public: diff --git a/src/slic3r/GUI/WebViewDialog.cpp b/src/slic3r/GUI/WebViewDialog.cpp index 8c3c2f781a..3279fb5c5f 100644 --- a/src/slic3r/GUI/WebViewDialog.cpp +++ b/src/slic3r/GUI/WebViewDialog.cpp @@ -639,7 +639,7 @@ void LoginWebViewDialog::on_navigation_request(wxWebViewEvent &evt) { wxString url = evt.GetURL(); if (url.starts_with(L"prusaslicer")) { - delete_cookies(m_browser, "https://account.prusa3d.com"); + delete_cookies(m_browser, Utils::ServiceConfig::instance().account_url()); delete_cookies(m_browser, "https://accounts.google.com"); delete_cookies(m_browser, "https://appleid.apple.com"); delete_cookies(m_browser, "https://facebook.com"); diff --git a/src/slic3r/GUI/WebViewPanel.cpp b/src/slic3r/GUI/WebViewPanel.cpp index 3329658c81..9ec5767ce4 100644 --- a/src/slic3r/GUI/WebViewPanel.cpp +++ b/src/slic3r/GUI/WebViewPanel.cpp @@ -190,8 +190,7 @@ void WebViewPanel::on_show(wxShowEvent& evt) m_shown = evt.IsShown(); if (evt.IsShown() && m_load_default_url) { m_load_default_url = false; - //load_url(m_default_url); - load_error_page(); + load_url(m_default_url); } } @@ -239,6 +238,8 @@ void WebViewPanel::on_back_button(wxCommandEvent& WXUNUSED(evt)) { if (!m_browser) return; + if (!m_browser->CanGoBack()) + return; m_browser->GoBack(); } @@ -249,6 +250,8 @@ void WebViewPanel::on_forward_button(wxCommandEvent& WXUNUSED(evt)) { if (!m_browser) return; + if (!m_browser->CanGoForward()) + return; m_browser->GoForward(); } @@ -469,7 +472,7 @@ case type: \ WX_ERROR_CASE(wxWEBVIEW_NAV_ERR_OTHER); } - BOOST_LOG_TRIVIAL(error) << this <<" WebViewPanel error: " << category; + BOOST_LOG_TRIVIAL(error) << this <<" WebViewPanel error: " << category << " url: " << evt.GetURL(); load_error_page(); #ifdef DEBUG_URL_PANEL m_info->ShowMessage(wxString("An error occurred loading ") + evt.GetURL() + "\n" + @@ -857,9 +860,14 @@ PrintablesWebViewPanel::PrintablesWebViewPanel(wxWindow* parent) : WebViewPanel(parent, GUI::from_u8(Utils::ServiceConfig::instance().printables_url()), { "ExternalApp" }, "other_loading_reload", "other_connection_failed") { m_browser->Bind(wxEVT_WEBVIEW_LOADED, &PrintablesWebViewPanel::on_loaded, this); + m_events["accessTokenExpired"] = std::bind(&PrintablesWebViewPanel::on_printables_event_access_token_expired, this, std::placeholders::_1); - m_events["printGcode"] = std::bind(&PrintablesWebViewPanel::on_printables_event_print_gcode, this, std::placeholders::_1); m_events["reloadHomePage"] = std::bind(&PrintablesWebViewPanel::on_reload_event, this, std::placeholders::_1); + m_events["printGcode"] = std::bind(&PrintablesWebViewPanel::on_printables_event_print_gcode, this, std::placeholders::_1); + m_events["downloadFile"] = std::bind(&PrintablesWebViewPanel::on_printables_event_download_file, this, std::placeholders::_1); + m_events["sliceFile"] = std::bind(&PrintablesWebViewPanel::on_printables_event_slice_file, this, std::placeholders::_1); + m_events["requiredLogin"] = std::bind(&PrintablesWebViewPanel::on_printables_event_required_login, this, std::placeholders::_1); + } void PrintablesWebViewPanel::handle_message(const std::string& message) @@ -888,50 +896,16 @@ void PrintablesWebViewPanel::handle_message(const std::string& message) m_events[event_string](message); } } -void PrintablesWebViewPanel::on_printables_event_access_token_expired(const std::string& message_data) -{ - // accessTokenExpired - // Printables pozaduje refresh access tokenu, muze byt volano nekolikrat.Nechme na Mobilni aplikaci at zaridi to ze zareaguje jen jednou -} -void PrintablesWebViewPanel::on_printables_event_print_gcode(const std::string& message_data) -{ - // printGcode - // Uzivatel kliknul na tlacitko tisk u gcode. Dalsi posilane atributy jsou url, material, printer, nozzlediam -} -void PrintablesWebViewPanel::on_reload_event(const std::string& message_data) -{ - load_default_url(); -} -/* -Eventy Slicer -> Printables -accessTokenWillChange -WebUI zavola event predtim nez udela refresh access tokenu proti Prusa Accountu na Printables to bude znamenat pozastaveni requestu Mobile app muze chtit udelat refresh i bez explicitni predchozi printables zadosti skrz accessTokenExpired event -accessTokenChange -window postMessage JSON stringify { event 'accessTokenChange' token 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVC' } -volani po uspesne rotaci tokenu -historyBack -navigace zpet triggerovana z mobilni aplikace -historyForward -navigace vpred triggerovana z mobilni aplikace -*/ void PrintablesWebViewPanel::on_navigation_request(wxWebViewEvent &evt) { const wxString url = evt.GetURL(); - // download with url - if (url.StartsWith(L"prusaslicer")) { - evt.Veto(); - wxGetApp().printables_request(into_u8(url)); - return; - } if (url.StartsWith(m_default_url)) { m_reached_default_url = true; } else if (m_reached_default_url) { BOOST_LOG_TRIVIAL(info) << evt.GetURL() << " does not start with default url. Vetoing."; evt.Veto(); } - - } void PrintablesWebViewPanel::load_default_url() @@ -1055,7 +1029,7 @@ void PrintablesWebViewPanel::logout() #endif // } -void PrintablesWebViewPanel::login(const std::string access_token) +void PrintablesWebViewPanel::login(const std::string& access_token) { if (!m_shown) { return; @@ -1076,7 +1050,7 @@ void PrintablesWebViewPanel::login(const std::string access_token) run_script("window.location.reload();"); } -void PrintablesWebViewPanel::send_refreshed_token(const std::string access_token) +void PrintablesWebViewPanel::send_refreshed_token(const std::string& access_token) { if (m_load_default_url) { return; @@ -1097,6 +1071,11 @@ void PrintablesWebViewPanel::send_will_refresh() run_script(script); } +void PrintablesWebViewPanel::load_url_from_outside(const std::string& url) +{ + load_url(from_u8(Utils::ServiceConfig::instance().printables_url() + url)); +} + void PrintablesWebViewPanel::on_script_message(wxWebViewEvent& evt) { BOOST_LOG_TRIVIAL(error) << "received message from Printables: " << evt.GetString(); @@ -1111,4 +1090,157 @@ void PrintablesWebViewPanel::sys_color_changed() WebViewPanel::sys_color_changed(); } +void PrintablesWebViewPanel::on_printables_event_access_token_expired(const std::string& message_data) +{ + // accessTokenExpired + // Printables pozaduje refresh access tokenu, muze byt volano nekolikrat.Nechme na Mobilni aplikaci at zaridi to ze zareaguje jen jednou + + // We do no react on this event now - Our Acount managment should know when to renew our tokens. +} + +void PrintablesWebViewPanel::on_reload_event(const std::string& message_data) +{ + // Event from our error / loading html pages + load_default_url(); +} +void PrintablesWebViewPanel::on_printables_event_print_gcode(const std::string& message_data) +{ + BOOST_LOG_TRIVIAL(error) << __FUNCTION__<< " " << message_data; +} +void PrintablesWebViewPanel::on_printables_event_download_file(const std::string& message_data) +{ + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " " << message_data; + // { "event": "downloadFile", "url": "https://media.printables.com/somesecure.stl", "modelUrl": "https://www.printables.com/model/123" } + std::string download_url; + std::string model_url; + try { + std::stringstream ss(message_data); + pt::ptree ptree; + pt::read_json(ss, ptree); + if (const auto url = ptree.get_optional("url"); url) { + download_url = *url; + } + if (const auto url = ptree.get_optional("modelUrl"); url) { + model_url = *url; + } + } catch (const std::exception &e) { + BOOST_LOG_TRIVIAL(error) << "Could not parse printables message. " << e.what(); + return; + } + assert(!download_url.empty() && !model_url.empty()); + boost::filesystem::path url_path(download_url); + show_download_notification(url_path.filename().string()); + wxGetApp().printables_download_request(download_url, model_url); +} +void PrintablesWebViewPanel::on_printables_event_slice_file(const std::string& message_data) +{ + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " " << message_data; + // { "event": "sliceFile", "url": "https://media.printables.com/somesecure.zip", "modelUrl": "https://www.printables.com/model/123" } + std::string download_url; + std::string model_url; + try { + std::stringstream ss(message_data); + pt::ptree ptree; + pt::read_json(ss, ptree); + if (const auto url = ptree.get_optional("url"); url) { + download_url = *url; + } + if (const auto url = ptree.get_optional("modelUrl"); url) { + model_url = *url; + } + } catch (const std::exception &e) { + BOOST_LOG_TRIVIAL(error) << "Could not parse printables message. " << e.what(); + return; + } + assert(!download_url.empty() && !model_url.empty()); + wxGetApp().printables_slice_request(download_url, model_url); +} + +void PrintablesWebViewPanel::on_printables_event_required_login(const std::string& message_data) +{ + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " " << message_data; +} + +void PrintablesWebViewPanel::show_download_notification(const std::string& filename) +{ + std::string message = GUI::format(_u8L("Downloading %1%"),filename); + std::string script = GUI::format(R"( + // Inject custom CSS + var style = document.createElement('style'); + style.innerHTML = ` + body { + /* Add your body styles here */ + } + .notification-popup { + position: fixed; + right: 10px; + bottom: 10px; + background-color: #333333; /* Dark background */ + padding: 10px; + border-radius: 6px; /* Slightly rounded corners */ + color: #ffffff; /* White text */ + font-family: Arial, sans-serif; + font-size: 12px; + display: flex; + justify-content: space-between; + align-items: center; + box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.3); /* Add a subtle shadow */ + min-width: 350px; /* Ensure it has a minimum width */ + min-height: 50px; + } + + .notification-popup a:hover { + text-decoration: underline; /* Underline on hover */ + } + .notification-popup .close-button { + display: inline-block; + width: 20px; + height: 20px; + border: 2px solid #ffa500; /* Orange border for the button */ + border-radius: 4px; + text-align: center; + font-size: 16px; + line-height: 16px; + cursor: pointer; + padding-top: 1px; + } + .notification-popup .close-button:hover { + background-color: #ffa500; /* Orange background on hover */ + color: #333333; /* Dark color for the "X" on hover */ + } + .notification-popup .close-button:before { + content: 'X'; + color: #ffa500; /* Orange "X" */ + font-weight: bold; + } + `; + document.head.appendChild(style); + + // Define the notification functions + function appendNotification() { + const body = document.getElementsByTagName('body')[0]; + const notifDiv = document.createElement('div'); + notifDiv.innerHTML = ` +
+ PrusaSlicer: %1% +
+ `; + notifDiv.className = 'notification-popup'; + notifDiv.id = 'slicer-notification'; + body.appendChild(notifDiv); + + window.setTimeout(removeNotification, 5000); + } + + function removeNotification() { + const notifDiv = document.getElementById('slicer-notification'); + if (notifDiv) + notifDiv.remove(); + } + + appendNotification(); +)", message); + run_script(script); +} + } // namespace slic3r::GUI \ No newline at end of file diff --git a/src/slic3r/GUI/WebViewPanel.hpp b/src/slic3r/GUI/WebViewPanel.hpp index bddb30cb6a..be9e181f22 100644 --- a/src/slic3r/GUI/WebViewPanel.hpp +++ b/src/slic3r/GUI/WebViewPanel.hpp @@ -180,20 +180,36 @@ public: void sys_color_changed() override; void logout(); - void login(const std::string access_token); - void send_refreshed_token(const std::string access_token); + void login(const std::string& access_token); + void send_refreshed_token(const std::string& access_token); void send_will_refresh(); + void load_url_from_outside(const std::string& url); private: void handle_message(const std::string& message); void on_printables_event_access_token_expired(const std::string& message_data); - void on_printables_event_print_gcode(const std::string& message_data); void on_reload_event(const std::string& message_data); - + void on_printables_event_print_gcode(const std::string& message_data); + void on_printables_event_download_file(const std::string& message_data); + void on_printables_event_slice_file(const std::string& message_data); + void on_printables_event_required_login(const std::string& message_data); void load_default_url() override; std::string get_url_lang_theme(const wxString& url); + void show_download_notification(const std::string& filename); std::map> m_events; +/* +Eventy Slicer -> Printables +accessTokenWillChange +WebUI zavola event predtim nez udela refresh access tokenu proti Prusa Accountu na Printables to bude znamenat pozastaveni requestu Mobile app muze chtit udelat refresh i bez explicitni predchozi printables zadosti skrz accessTokenExpired event +accessTokenChange +window postMessage JSON stringify { event 'accessTokenChange' token 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVC' } +volani po uspesne rotaci tokenu +historyBack +navigace zpet triggerovana z mobilni aplikace +historyForward +navigace vpred triggerovana z mobilni aplikace +*/ }; } // namespace Slic3r::GUI From 93b0adf96475cb889163f588522a3a55114bc4fb Mon Sep 17 00:00:00 2001 From: David Kocik Date: Fri, 25 Oct 2024 12:15:03 +0200 Subject: [PATCH 06/12] SPE-2507: Resize Connect upload dialog --- src/slic3r/GUI/WebViewDialog.cpp | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/src/slic3r/GUI/WebViewDialog.cpp b/src/slic3r/GUI/WebViewDialog.cpp index 3279fb5c5f..fe33241109 100644 --- a/src/slic3r/GUI/WebViewDialog.cpp +++ b/src/slic3r/GUI/WebViewDialog.cpp @@ -436,14 +436,27 @@ PrinterPickWebViewDialog::PrinterPickWebViewDialog(wxWindow* parent, std::string : WebViewDialog(parent , GUI::from_u8(Utils::ServiceConfig::instance().connect_select_printer_url()) , _L("Choose a printer") - , wxSize(std::max(parent->GetClientSize().x / 4 * 3, 150 * wxGetApp().em_unit()), std::max(parent->GetClientSize().y / 6 * 5, 100 * wxGetApp().em_unit())) + , wxSize(parent->GetClientSize().x / 4 * 3, parent->GetClientSize().y/ 5 * 4) ,{"_prusaSlicer"} , "connect_loading") , m_ret_val(ret_val) { - SetMinSize(wxSize(std::max(parent->GetClientSize().x / 2, 100 * wxGetApp().em_unit()), std::max(parent->GetClientSize().y / 2, 50 * wxGetApp().em_unit()))); + SetMinSize(wxSize(std::max(parent->GetClientSize().x / 2, 100 * wxGetApp().em_unit()), std::max(parent->GetClientSize().y / 2, 70 * wxGetApp().em_unit()))); Centre(); } + +void PrinterPickWebViewDialog::on_dpi_changed(const wxRect &suggested_rect) +{ + wxWindow *parent = GetParent(); + const wxSize &size = wxSize( + std::max(parent->GetClientSize().x / 2, 100 * wxGetApp().em_unit()), + std::max(parent->GetClientSize().y / 2, 70 * wxGetApp().em_unit()) + ); + SetMinSize(size); + Fit(); + Refresh(); +} + void PrinterPickWebViewDialog::on_show(wxShowEvent& evt) { /* @@ -607,17 +620,7 @@ void PrinterPickWebViewDialog::request_compatible_printers_SLA() wxString script = GUI::format_wxstr("window._prusaConnect_v1.requestCompatiblePrinter(%1%)", request); run_script(script); } -void PrinterPickWebViewDialog::on_dpi_changed(const wxRect &suggested_rect) -{ - wxWindow *parent = GetParent(); - const wxSize &size = wxSize( - std::max(parent->GetClientSize().x / 2, 100 * wxGetApp().em_unit()), - std::max(parent->GetClientSize().y / 2, 50 * wxGetApp().em_unit()) - ); - SetMinSize(size); - Fit(); - Refresh(); -} + void PrinterPickWebViewDialog::on_reload_event(const std::string& message_data) { From da49e8edfb0b6e2f1afb4c498c5bc680106de13b Mon Sep 17 00:00:00 2001 From: David Kocik Date: Mon, 28 Oct 2024 10:39:58 +0100 Subject: [PATCH 07/12] SPE-2535: Improvments of Printables events Missing include Printables event Require login Improved error page Prepare opening Printables to Connect upload dialog debug fix Another debug fix prevent reload on load request Load new url already in on_show Improved javascript notification --- resources/web/other_connection_failed.html | 28 ----- resources/web/other_error.html | 70 +++++++++++ src/slic3r/GUI/GUI_App.cpp | 5 + src/slic3r/GUI/GUI_App.hpp | 1 + src/slic3r/GUI/MainFrame.cpp | 7 +- src/slic3r/GUI/Plater.cpp | 12 ++ src/slic3r/GUI/Plater.hpp | 1 + src/slic3r/GUI/WebViewDialog.cpp | 25 ++++ src/slic3r/GUI/WebViewDialog.hpp | 9 ++ src/slic3r/GUI/WebViewPanel.cpp | 133 +++++++++++++-------- src/slic3r/GUI/WebViewPanel.hpp | 9 +- src/slic3r/GUI/format.hpp | 1 + 12 files changed, 218 insertions(+), 83 deletions(-) delete mode 100644 resources/web/other_connection_failed.html create mode 100644 resources/web/other_error.html diff --git a/resources/web/other_connection_failed.html b/resources/web/other_connection_failed.html deleted file mode 100644 index 3ea607b3be..0000000000 --- a/resources/web/other_connection_failed.html +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - Connection failed - - - -
-

Connection failed

-

Something went wrong.

- -
- - diff --git a/resources/web/other_error.html b/resources/web/other_error.html new file mode 100644 index 0000000000..24b29d72d2 --- /dev/null +++ b/resources/web/other_error.html @@ -0,0 +1,70 @@ + + + + + + Connect-Slicer integration + + + +
+
+

+ + + + + + + + + + + + + + + + + + + + + + + + + + +

+

Something went wrong.

+ +
+
+ + \ No newline at end of file diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index a938865bb9..a79d6dbc2f 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -4145,7 +4145,12 @@ void GUI_App::printables_slice_request(const std::string& download_url, const st } void GUI_App::printables_print_request(const std::string& download_url, const std::string& model_url) { + plater()->printables_to_connect_gcode(Utils::ServiceConfig::instance().printables_url() + model_url); +} +void GUI_App::printables_login_request() +{ + plater_->get_user_account()->do_login(); } void GUI_App::open_link_in_printables(const std::string& url) diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index cf3c30c287..a85c8a8480 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -439,6 +439,7 @@ public: void printables_download_request(const std::string& download_url, const std::string& model_url); void printables_slice_request(const std::string& download_url, const std::string& model_url); void printables_print_request(const std::string& download_url, const std::string& model_url); + void printables_login_request(); void open_link_in_printables(const std::string& url); private: bool on_init_inner(); diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index eca7401888..0f2bd93484 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -880,9 +880,12 @@ void MainFrame::show_printables_tab(const std::string& url) if (!m_printables_webview_added) { return; } - m_tabpanel->SetSelection(m_tabpanel->FindPage(m_printables_webview)); + // we have to set next url first, than show the tab + // printables_tab has to reload on show everytime + // so it is not possible load_url right after show m_printables_webview->set_load_default_url_on_next_error(true); - m_printables_webview->load_url_from_outside(url); + m_printables_webview->set_next_show_url(url); + m_tabpanel->SetSelection(m_tabpanel->FindPage(m_printables_webview)); } void MainFrame::add_printables_webview_tab() { diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 395f29dc8d..e22d7a260a 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -6017,6 +6017,18 @@ bool load_secret(const std::string& id, const std::string& opt, std::string& usr #endif // wxUSE_SECRETSTORE } } + + +void Plater::printables_to_connect_gcode(const std::string& url) +{ + { + PrintablesConnectUploadDialog dialog(this, url); + if (dialog.ShowModal() != wxID_OK) { + return; + } + } +} + void Plater::connect_gcode() { assert(p->user_account->is_logged()); diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index c492912d3a..e610e552d4 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -229,6 +229,7 @@ public: void send_gcode_inner(DynamicPrintConfig* physical_printer_config); void eject_drive(); void connect_gcode(); + void printables_to_connect_gcode(const std::string& url); std::string get_upload_filename(); void take_snapshot(const std::string &snapshot_name); diff --git a/src/slic3r/GUI/WebViewDialog.cpp b/src/slic3r/GUI/WebViewDialog.cpp index fe33241109..b77e298be2 100644 --- a/src/slic3r/GUI/WebViewDialog.cpp +++ b/src/slic3r/GUI/WebViewDialog.cpp @@ -630,6 +630,31 @@ void PrinterPickWebViewDialog::on_reload_event(const std::string& message_data) m_browser->LoadURL(m_default_url); } +PrintablesConnectUploadDialog::PrintablesConnectUploadDialog(wxWindow* parent, const std::string url) + : WebViewDialog(parent + , GUI::from_u8(url) + , _L("Choose a printer") + , wxSize(parent->GetClientSize().x / 4 * 3, parent->GetClientSize().y/ 5 * 4) + ,{"_prusaSlicer"} + , "connect_loading") +{ + SetMinSize(wxSize(std::max(parent->GetClientSize().x / 2, 100 * wxGetApp().em_unit()), std::max(parent->GetClientSize().y / 2, 70 * wxGetApp().em_unit()))); + Centre(); +} + +void PrintablesConnectUploadDialog::on_dpi_changed(const wxRect &suggested_rect) +{ + wxWindow *parent = GetParent(); + const wxSize &size = wxSize( + std::max(parent->GetClientSize().x / 2, 100 * wxGetApp().em_unit()), + std::max(parent->GetClientSize().y / 2, 70 * wxGetApp().em_unit()) + ); + SetMinSize(size); + Fit(); + Refresh(); +} + + LoginWebViewDialog::LoginWebViewDialog(wxWindow *parent, std::string &ret_val, const wxString& url, wxEvtHandler* evt_handler) : WebViewDialog(parent, url, _L("Log in dialog"), wxSize(50 * wxGetApp().em_unit(), 80 * wxGetApp().em_unit()), {}) diff --git a/src/slic3r/GUI/WebViewDialog.hpp b/src/slic3r/GUI/WebViewDialog.hpp index d9e890fc3f..78fdd35c74 100644 --- a/src/slic3r/GUI/WebViewDialog.hpp +++ b/src/slic3r/GUI/WebViewDialog.hpp @@ -109,6 +109,15 @@ private: std::string& m_ret_val; }; +class PrintablesConnectUploadDialog : public WebViewDialog +{ +public: + PrintablesConnectUploadDialog(wxWindow* parent, const std::string url); +protected: + void on_dpi_changed(const wxRect &suggested_rect) override; + +}; + class LoginWebViewDialog : public WebViewDialog { public: diff --git a/src/slic3r/GUI/WebViewPanel.cpp b/src/slic3r/GUI/WebViewPanel.cpp index 9ec5767ce4..acb0fcc67b 100644 --- a/src/slic3r/GUI/WebViewPanel.cpp +++ b/src/slic3r/GUI/WebViewPanel.cpp @@ -8,10 +8,10 @@ #include "slic3r/GUI/format.hpp" #include "slic3r/GUI/WebView.hpp" #include "slic3r/GUI/WebViewPlatformUtils.hpp" -#include "slic3r/Utils/ServiceConfig.hpp" - #include "slic3r/GUI/MsgDialog.hpp" #include "slic3r/GUI/Field.hpp" +#include "libslic3r/AppConfig.hpp" +#include "libslic3r/Config.hpp" #include // IWYU pragma: keep @@ -207,7 +207,7 @@ void WebViewPanel::on_idle(wxIdleEvent& WXUNUSED(evt)) m_load_error_page = false; if (m_load_default_url_on_next_error) { m_load_default_url_on_next_error = false; - load_url(m_default_url); + load_default_url(); } else { load_url(GUI::format_wxstr("file://%1%/web/%2%.html", boost::filesystem::path(resources_dir()).generic_string(), m_error_html)); } @@ -781,7 +781,7 @@ void ConnectWebViewPanel::on_connect_action_print(const std::string& message_dat } PrinterWebViewPanel::PrinterWebViewPanel(wxWindow* parent, const wxString& default_url) - : WebViewPanel(parent, default_url, {"ExternalApp"}, "other_loading_reload", "other_connection_failed") + : WebViewPanel(parent, default_url, {"ExternalApp"}, "other_loading_reload", "other_error") { if (!m_browser) return; @@ -857,7 +857,7 @@ void PrinterWebViewPanel::sys_color_changed() PrintablesWebViewPanel::PrintablesWebViewPanel(wxWindow* parent) - : WebViewPanel(parent, GUI::from_u8(Utils::ServiceConfig::instance().printables_url()), { "ExternalApp" }, "other_loading_reload", "other_connection_failed") + : WebViewPanel(parent, GUI::from_u8(Utils::ServiceConfig::instance().printables_url()), { "ExternalApp" }, "other_loading_reload", "other_error") { m_browser->Bind(wxEVT_WEBVIEW_LOADED, &PrintablesWebViewPanel::on_loaded, this); @@ -908,27 +908,6 @@ void PrintablesWebViewPanel::on_navigation_request(wxWebViewEvent &evt) } } -void PrintablesWebViewPanel::load_default_url() -{ - std::string actual_default_url = get_url_lang_theme(Utils::ServiceConfig::instance().printables_url() + "/homepage"); - const std::string access_token = wxGetApp().plater()->get_user_account()->get_access_token(); - - // in case of opening printables logged out - delete cookies and localstorage to get rid of last login - if (access_token.empty()) { - delete_cookies(m_browser, Utils::ServiceConfig::instance().printables_url()); - m_browser->AddUserScript("localStorage.clear();"); - load_url(actual_default_url); - return; - } - // add token to first request -#ifdef _WIN32 - add_request_authorization(m_browser, m_default_url, access_token); - load_url(GUI::from_u8(actual_default_url)); -#else - load_request(m_browser, actual_default_url, access_token); -#endif -} - void PrintablesWebViewPanel::on_loaded(wxWebViewEvent& evt) { #ifdef _WIN32 @@ -1000,20 +979,20 @@ void PrintablesWebViewPanel::on_show(wxShowEvent& evt) return; } // in case login changed, resend login / logout - // DK: it seems to me, it is safer to do login / logout (where logout means requesting the page again) + // DK1: it seems to me, it is safer to do login / logout (where logout means requesting the page again) // on every show of panel, // than to keep information if we have printables page in same state as slicer in terms of login // But im afraid it will be concidered not pretty... const std::string access_token = wxGetApp().plater()->get_user_account()->get_access_token(); if (access_token.empty()) { - logout(); + logout(m_next_show_url); } else { - login(access_token); + login(access_token, m_next_show_url); } - + m_next_show_url.clear(); } -void PrintablesWebViewPanel::logout() +void PrintablesWebViewPanel::logout(const std::string& override_url/* = std::string()*/) { if (!m_shown) { return; @@ -1021,15 +1000,18 @@ void PrintablesWebViewPanel::logout() delete_cookies(m_browser, Utils::ServiceConfig::instance().printables_url()); m_browser->RunScript("localStorage.clear();"); + std::string next_url = override_url.empty() + ? get_url_lang_theme(m_browser->GetCurrentURL()) + : get_url_lang_theme(from_u8(override_url)); #ifdef _WIN32 - load_url(GUI::from_u8(get_url_lang_theme(m_browser->GetCurrentURL()))); + load_url(GUI::from_u8(next_url)); #else // We cannot do simple reload here, it would keep the access token in the header - load_request(m_browser, get_url_lang_theme(m_browser->GetCurrentURL()), std::string()); + load_request(m_browser, next_url, std::string()); #endif // } -void PrintablesWebViewPanel::login(const std::string& access_token) +void PrintablesWebViewPanel::login(const std::string& access_token, const std::string& override_url/* = std::string()*/) { if (!m_shown) { return; @@ -1048,8 +1030,34 @@ void PrintablesWebViewPanel::login(const std::string& access_token) , access_token); run_script(script); - run_script("window.location.reload();"); + if ( override_url.empty()) { + run_script("window.location.reload();"); + } else { + load_url(GUI::from_u8(get_url_lang_theme(from_u8(override_url)))); + } } + +void PrintablesWebViewPanel::load_default_url() +{ + std::string actual_default_url = get_url_lang_theme(from_u8(Utils::ServiceConfig::instance().printables_url() + "/homepage")); + const std::string access_token = wxGetApp().plater()->get_user_account()->get_access_token(); + + // in case of opening printables logged out - delete cookies and localstorage to get rid of last login + if (access_token.empty()) { + delete_cookies(m_browser, Utils::ServiceConfig::instance().printables_url()); + m_browser->AddUserScript("localStorage.clear();"); + load_url(actual_default_url); + return; + } + // add token to first request +#ifdef _WIN32 + add_request_authorization(m_browser, m_default_url, access_token); + load_url(GUI::from_u8(actual_default_url)); +#else + load_request(m_browser, actual_default_url, access_token); +#endif +} + void PrintablesWebViewPanel::send_refreshed_token(const std::string& access_token) { if (m_load_default_url) { @@ -1071,11 +1079,6 @@ void PrintablesWebViewPanel::send_will_refresh() run_script(script); } -void PrintablesWebViewPanel::load_url_from_outside(const std::string& url) -{ - load_url(from_u8(Utils::ServiceConfig::instance().printables_url() + url)); -} - void PrintablesWebViewPanel::on_script_message(wxWebViewEvent& evt) { BOOST_LOG_TRIVIAL(error) << "received message from Printables: " << evt.GetString(); @@ -1106,11 +1109,31 @@ void PrintablesWebViewPanel::on_reload_event(const std::string& message_data) void PrintablesWebViewPanel::on_printables_event_print_gcode(const std::string& message_data) { BOOST_LOG_TRIVIAL(error) << __FUNCTION__<< " " << message_data; + + // { "event": "downloadFile", "url": "https://media.printables.com/somesecure.stl", "modelUrl": "https://www.printables.com/model/123" } + std::string download_url; + std::string model_url; + try { + std::stringstream ss(message_data); + pt::ptree ptree; + pt::read_json(ss, ptree); + if (const auto url = ptree.get_optional("url"); url) { + download_url = *url; + } + if (const auto url = ptree.get_optional("modelUrl"); url) { + model_url = *url; + } + } catch (const std::exception &e) { + BOOST_LOG_TRIVIAL(error) << "Could not parse printables message. " << e.what(); + return; + } + assert(!download_url.empty() && !model_url.empty()); + wxGetApp().printables_print_request(download_url, model_url); } void PrintablesWebViewPanel::on_printables_event_download_file(const std::string& message_data) { - BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " " << message_data; - // { "event": "downloadFile", "url": "https://media.printables.com/somesecure.stl", "modelUrl": "https://www.printables.com/model/123" } + BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << " " << message_data; + // { "event": "printGcode", "url": "https://media.printables.com/somesecure.gcode", "modelUrl": "https://www.printables.com/model/123" } std::string download_url; std::string model_url; try { @@ -1130,11 +1153,11 @@ void PrintablesWebViewPanel::on_printables_event_download_file(const std::string assert(!download_url.empty() && !model_url.empty()); boost::filesystem::path url_path(download_url); show_download_notification(url_path.filename().string()); - wxGetApp().printables_download_request(download_url, model_url); + wxGetApp().printables_download_request(download_url, model_url); } void PrintablesWebViewPanel::on_printables_event_slice_file(const std::string& message_data) { - BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " " << message_data; + BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << " " << message_data; // { "event": "sliceFile", "url": "https://media.printables.com/somesecure.zip", "modelUrl": "https://www.printables.com/model/123" } std::string download_url; std::string model_url; @@ -1158,12 +1181,14 @@ void PrintablesWebViewPanel::on_printables_event_slice_file(const std::string& m void PrintablesWebViewPanel::on_printables_event_required_login(const std::string& message_data) { - BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " " << message_data; + BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << " " << message_data; + wxGetApp().printables_login_request(); } void PrintablesWebViewPanel::show_download_notification(const std::string& filename) { - std::string message = GUI::format(_u8L("Downloading %1%"),filename); + std::string message_filename = GUI::format(_u8L("Downloading %1%"),filename); + std::string message_dest = GUI::format(_u8L("To %1%"), escape_string_cstyle(wxGetApp().app_config->get("url_downloader_dest"))); std::string script = GUI::format(R"( // Inject custom CSS var style = document.createElement('style'); @@ -1185,10 +1210,19 @@ void PrintablesWebViewPanel::show_download_notification(const std::string& filen justify-content: space-between; align-items: center; box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.3); /* Add a subtle shadow */ - min-width: 350px; /* Ensure it has a minimum width */ + min-width: 350px; + max-width: 350px; min-height: 50px; } - + .notification-popup div { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + padding-right: 20px; /* Add padding to make text truncate earlier */ + } + .notification-popup b { + color: #ffa500; + } .notification-popup a:hover { text-decoration: underline; /* Underline on hover */ } @@ -1223,6 +1257,7 @@ void PrintablesWebViewPanel::show_download_notification(const std::string& filen notifDiv.innerHTML = `
PrusaSlicer: %1% +
%2%
`; notifDiv.className = 'notification-popup'; @@ -1239,7 +1274,7 @@ void PrintablesWebViewPanel::show_download_notification(const std::string& filen } appendNotification(); -)", message); +)", message_filename, message_dest); run_script(script); } diff --git a/src/slic3r/GUI/WebViewPanel.hpp b/src/slic3r/GUI/WebViewPanel.hpp index be9e181f22..d5d276cb62 100644 --- a/src/slic3r/GUI/WebViewPanel.hpp +++ b/src/slic3r/GUI/WebViewPanel.hpp @@ -8,6 +8,7 @@ #include "GUI_Utils.hpp" #include "UserAccountSession.hpp" #include "ConnectRequestHandler.hpp" +#include "slic3r/Utils/ServiceConfig.hpp" #ifdef DEBUG_URL_PANEL #include @@ -179,11 +180,11 @@ public: void on_script_message(wxWebViewEvent& evt) override; void sys_color_changed() override; - void logout(); - void login(const std::string& access_token); + void logout(const std::string& override_url = std::string()); + void login(const std::string& access_token, const std::string& override_url = std::string()); void send_refreshed_token(const std::string& access_token); void send_will_refresh(); - void load_url_from_outside(const std::string& url); + void set_next_show_url(const std::string& url) {m_next_show_url = Utils::ServiceConfig::instance().printables_url() + url; } private: void handle_message(const std::string& message); void on_printables_event_access_token_expired(const std::string& message_data); @@ -197,7 +198,7 @@ private: void show_download_notification(const std::string& filename); std::map> m_events; - + std::string m_next_show_url; /* Eventy Slicer -> Printables accessTokenWillChange diff --git a/src/slic3r/GUI/format.hpp b/src/slic3r/GUI/format.hpp index e80017174d..9c80907ee0 100644 --- a/src/slic3r/GUI/format.hpp +++ b/src/slic3r/GUI/format.hpp @@ -12,6 +12,7 @@ // This wrapper also manages implicit conversion from wxString to UTF8 and format_wxstr() variants are provided to format into wxString. #include +#include namespace Slic3r::internal::format { // Wrapper around wxScopedCharBuffer to indicate that the content is UTF8 formatted. From 40458c664998035e62b164d56119402efa331612 Mon Sep 17 00:00:00 2001 From: David Kocik Date: Thu, 31 Oct 2024 12:06:53 +0100 Subject: [PATCH 08/12] SPE-2507: Resize upload dialogs --- src/slic3r/GUI/WebViewDialog.cpp | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/slic3r/GUI/WebViewDialog.cpp b/src/slic3r/GUI/WebViewDialog.cpp index b77e298be2..c9ddf614a2 100644 --- a/src/slic3r/GUI/WebViewDialog.cpp +++ b/src/slic3r/GUI/WebViewDialog.cpp @@ -17,6 +17,7 @@ #include +#include #include #include @@ -436,23 +437,23 @@ PrinterPickWebViewDialog::PrinterPickWebViewDialog(wxWindow* parent, std::string : WebViewDialog(parent , GUI::from_u8(Utils::ServiceConfig::instance().connect_select_printer_url()) , _L("Choose a printer") - , wxSize(parent->GetClientSize().x / 4 * 3, parent->GetClientSize().y/ 5 * 4) + , wxSize(parent->GetClientSize().x / 4 * 3, parent->GetClientSize().y/ 4 * 3) ,{"_prusaSlicer"} , "connect_loading") , m_ret_val(ret_val) { - SetMinSize(wxSize(std::max(parent->GetClientSize().x / 2, 100 * wxGetApp().em_unit()), std::max(parent->GetClientSize().y / 2, 70 * wxGetApp().em_unit()))); + + wxDisplay display(wxDisplay::GetFromWindow(this)); + wxRect geometry = display.GetGeometry(); + SetMinSize(wxSize(geometry.GetWidth() / 2, geometry.GetHeight() / 2)); Centre(); } void PrinterPickWebViewDialog::on_dpi_changed(const wxRect &suggested_rect) { - wxWindow *parent = GetParent(); - const wxSize &size = wxSize( - std::max(parent->GetClientSize().x / 2, 100 * wxGetApp().em_unit()), - std::max(parent->GetClientSize().y / 2, 70 * wxGetApp().em_unit()) - ); - SetMinSize(size); + wxDisplay display(wxDisplay::GetFromWindow(this)); + wxRect geometry = display.GetGeometry(); + SetMinSize(wxSize(geometry.GetWidth() / 2, geometry.GetHeight() / 2)); Fit(); Refresh(); } @@ -634,22 +635,21 @@ PrintablesConnectUploadDialog::PrintablesConnectUploadDialog(wxWindow* parent, c : WebViewDialog(parent , GUI::from_u8(url) , _L("Choose a printer") - , wxSize(parent->GetClientSize().x / 4 * 3, parent->GetClientSize().y/ 5 * 4) + , wxSize(parent->GetClientSize().x / 4 * 3, parent->GetClientSize().y/ 4 * 3) ,{"_prusaSlicer"} , "connect_loading") { - SetMinSize(wxSize(std::max(parent->GetClientSize().x / 2, 100 * wxGetApp().em_unit()), std::max(parent->GetClientSize().y / 2, 70 * wxGetApp().em_unit()))); + wxDisplay display(wxDisplay::GetFromWindow(this)); + wxRect geometry = display.GetGeometry(); + SetMinSize(wxSize(geometry.GetWidth() / 2, geometry.GetHeight() / 2)); Centre(); } void PrintablesConnectUploadDialog::on_dpi_changed(const wxRect &suggested_rect) { - wxWindow *parent = GetParent(); - const wxSize &size = wxSize( - std::max(parent->GetClientSize().x / 2, 100 * wxGetApp().em_unit()), - std::max(parent->GetClientSize().y / 2, 70 * wxGetApp().em_unit()) - ); - SetMinSize(size); + wxDisplay display(wxDisplay::GetFromWindow(this)); + wxRect geometry = display.GetGeometry(); + SetMinSize(wxSize(geometry.GetWidth() / 2, geometry.GetHeight() / 2)); Fit(); Refresh(); } From 9f423946295b8baef1780f06769b2831f2cbe777 Mon Sep 17 00:00:00 2001 From: David Kocik Date: Thu, 31 Oct 2024 12:31:29 +0100 Subject: [PATCH 09/12] SPE-2512: Create Webview only when it is being shown Destroy webview on tab close --- src/slic3r/GUI/ConfigWizardWebViewPage.cpp | 4 +- src/slic3r/GUI/MainFrame.cpp | 9 +- src/slic3r/GUI/WebView.cpp | 9 +- src/slic3r/GUI/WebView.hpp | 5 +- src/slic3r/GUI/WebViewDialog.cpp | 13 +- src/slic3r/GUI/WebViewPanel.cpp | 194 ++++++++++++++------- src/slic3r/GUI/WebViewPanel.hpp | 23 ++- 7 files changed, 179 insertions(+), 78 deletions(-) diff --git a/src/slic3r/GUI/ConfigWizardWebViewPage.cpp b/src/slic3r/GUI/ConfigWizardWebViewPage.cpp index 075eb9539a..1ab9f7a180 100644 --- a/src/slic3r/GUI/ConfigWizardWebViewPage.cpp +++ b/src/slic3r/GUI/ConfigWizardWebViewPage.cpp @@ -27,13 +27,15 @@ ConfigWizardWebViewPage::ConfigWizardWebViewPage(ConfigWizard *parent) // Create the webview m_browser_sizer = new wxBoxSizer(wxHORIZONTAL); - m_browser = WebView::CreateWebView(this, p_user_account->generate_login_redirect_url(), {}); + m_browser = WebView::webview_new(); if (!m_browser) { // TRN Config wizard page with a log in page. wxStaticText* fail_text = new wxStaticText(this, wxID_ANY, _L("Failed to load a web browser. Logging in is not possible in the moment.")); append(fail_text); return; } + WebView::webview_create(m_browser, this, p_user_account->generate_login_redirect_url(), {}); + if (logged) { // TRN Config wizard page with a log in web. m_text = new wxStaticText(this, wxID_ANY, format_wxstr(_L("You are logged as %1%."), p_user_account->get_username())); diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 0f2bd93484..5b4ca8a8da 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -850,7 +850,7 @@ void MainFrame::add_connect_webview_tab() const std::string bmp_name = ""; bool bSelect = false; m_tabpanel->InsertNewPage(n, page, text, bmp_name, bSelect); - m_connect_webview->load_default_url_delayed(); + m_connect_webview->set_create_browser(); m_connect_webview_added = true; } void MainFrame::remove_connect_webview_tab() @@ -864,6 +864,7 @@ void MainFrame::remove_connect_webview_tab() m_tabpanel->RemovePage(size_t(n)); m_connect_webview_added = false; m_connect_webview->logout(); + m_connect_webview->destroy_browser(); } void MainFrame::show_connect_tab(const wxString& url) @@ -898,7 +899,7 @@ void MainFrame::add_printables_webview_tab() const wxString text(L"Printables"); const std::string bmp_name = ""; m_tabpanel->InsertNewPage(n, page, text, bmp_name, false); - m_printables_webview->load_default_url_delayed(); + m_printables_webview->set_create_browser(); m_printables_webview_added = true; } @@ -913,6 +914,7 @@ void MainFrame::remove_printables_webview_tab() m_tabpanel->SetSelection(0); m_tabpanel->RemovePage(size_t(n)); m_printables_webview_added = false; + m_printables_webview->destroy_browser(); } void MainFrame::show_printer_webview_tab(DynamicPrintConfig* dpc) @@ -958,7 +960,7 @@ void MainFrame::add_printer_webview_tab(const wxString& url) // add as the last (rightmost) panel m_tabpanel->AddNewPage(m_printer_webview, _L("Physical Printer"), ""); m_printer_webview->set_default_url(url); - m_printer_webview->load_default_url_delayed(); + m_printer_webview->set_create_browser(); } void MainFrame::remove_printer_webview_tab() { @@ -968,6 +970,7 @@ void MainFrame::remove_printer_webview_tab() m_printer_webview_added = false; m_printer_webview->Hide(); m_tabpanel->RemovePage(m_tabpanel->FindPage(m_printer_webview)); + m_printer_webview->destroy_browser(); } void MainFrame::set_printer_webview_tab_url(const wxString& url) { diff --git a/src/slic3r/GUI/WebView.cpp b/src/slic3r/GUI/WebView.cpp index e4f04205e5..a43df3a232 100644 --- a/src/slic3r/GUI/WebView.cpp +++ b/src/slic3r/GUI/WebView.cpp @@ -10,7 +10,7 @@ #include -wxWebView* WebView::CreateWebView(wxWindow * parent, const wxString& url, const std::vector& message_handlers) +wxWebView* WebView::webview_new() { #if wxUSE_WEBVIEW_EDGE bool backend_available = wxWebView::IsBackendAvailable(wxWebViewBackendEdge); @@ -22,6 +22,10 @@ wxWebView* WebView::CreateWebView(wxWindow * parent, const wxString& url, const if (backend_available) webView = wxWebView::New(); + return webView; +} +void WebView::webview_create(wxWebView* webView, wxWindow *parent, const wxString& url, const std::vector& message_handlers) +{ if (webView) { wxString correct_url = url.empty() ? wxString("") : wxURI(url).BuildURI(); wxString user_agent = Slic3r::GUI::format_wxstr("%1%/%2% (%3%)",SLIC3R_APP_FULL_NAME, SLIC3R_VERSION, Slic3r::platform_to_string(Slic3r::platform())); @@ -57,5 +61,4 @@ wxWebView* WebView::CreateWebView(wxWindow * parent, const wxString& url, const // TODO: dialog to user !!! BOOST_LOG_TRIVIAL(error) << "Failed to create wxWebView object."; } - return webView; -} +} \ No newline at end of file diff --git a/src/slic3r/GUI/WebView.hpp b/src/slic3r/GUI/WebView.hpp index c7c4c79ef2..40ec2fa534 100644 --- a/src/slic3r/GUI/WebView.hpp +++ b/src/slic3r/GUI/WebView.hpp @@ -10,7 +10,10 @@ class wxString; namespace WebView { - wxWebView *CreateWebView(wxWindow *parent, const wxString& url, const std::vector& message_handlers); + // When using WebView in Panel, it is benfitable to call create only when it is being shown. (lower CPU usage) + // But when using WebView in Dialog, call both. + wxWebView* webview_new(); + void webview_create(wxWebView* webview, wxWindow *parent, const wxString& url, const std::vector& message_handlers); }; #endif // !slic3r_GUI_WebView_hpp_ diff --git a/src/slic3r/GUI/WebViewDialog.cpp b/src/slic3r/GUI/WebViewDialog.cpp index c9ddf614a2..b874368be0 100644 --- a/src/slic3r/GUI/WebViewDialog.cpp +++ b/src/slic3r/GUI/WebViewDialog.cpp @@ -74,16 +74,19 @@ WebViewDialog::WebViewDialog(wxWindow* parent, const wxString& url, const wxStri SetSizerAndFit(topsizer); // Create the webview - m_browser = WebView::CreateWebView(this, GUI::format_wxstr("file://%1%/web/%2%.html", boost::filesystem::path(resources_dir()).generic_string(), m_loading_html), m_script_message_hadler_names); - if (Utils::ServiceConfig::instance().webdev_enabled()) { - m_browser->EnableContextMenu(); - m_browser->EnableAccessToDevTools(); - } + m_browser = WebView::webview_new(); if (!m_browser) { wxStaticText* text = new wxStaticText(this, wxID_ANY, _L("Failed to load a web browser.")); topsizer->Add(text, 0, wxALIGN_LEFT | wxBOTTOM, 10); return; } + WebView::webview_create(m_browser, this, GUI::format_wxstr("file://%1%/web/%2%.html", boost::filesystem::path(resources_dir()).generic_string(), m_loading_html), m_script_message_hadler_names); + + if (Utils::ServiceConfig::instance().webdev_enabled()) { + m_browser->EnableContextMenu(); + m_browser->EnableAccessToDevTools(); + } + topsizer->Add(m_browser, wxSizerFlags().Expand().Proportion(1)); diff --git a/src/slic3r/GUI/WebViewPanel.cpp b/src/slic3r/GUI/WebViewPanel.cpp index acb0fcc67b..816782d011 100644 --- a/src/slic3r/GUI/WebViewPanel.cpp +++ b/src/slic3r/GUI/WebViewPanel.cpp @@ -41,6 +41,17 @@ WebViewPanel::~WebViewPanel() #endif } +void WebViewPanel::destroy_browser() +{ + if (!m_browser || m_do_late_webview_create) { + return; + } + topsizer->Detach(m_browser); + m_browser->Destroy(); + m_browser = nullptr; +} + + void WebViewPanel::load_url(const wxString& url) { if (!m_browser) @@ -53,12 +64,13 @@ void WebViewPanel::load_url(const wxString& url) #ifdef DEBUG_URL_PANEL m_url->SetLabelText(url); #endif - m_browser->LoadURL(url); + wxString correct_url = url.empty() ? wxString("") : wxURI(url).BuildURI(); + m_browser->LoadURL(correct_url); m_browser->SetFocus(); } -WebViewPanel::WebViewPanel(wxWindow *parent, const wxString& default_url, const std::vector& message_handler_names, const std::string& loading_html, const std::string& error_html) +WebViewPanel::WebViewPanel(wxWindow *parent, const wxString& default_url, const std::vector& message_handler_names, const std::string& loading_html, const std::string& error_html, bool do_create) : wxPanel(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize) , m_default_url (default_url) , m_loading_html(loading_html) @@ -109,19 +121,10 @@ WebViewPanel::WebViewPanel(wxWindow *parent, const wxString& default_url, const #endif SetSizer(topsizer); + + Bind(wxEVT_SHOW, &WebViewPanel::on_show, this); + Bind(wxEVT_IDLE, &WebViewPanel::on_idle, this); - // Create the webview - m_browser = WebView::CreateWebView(this, /*m_default_url*/ GUI::format_wxstr("file://%1%/web/%2%.html", boost::filesystem::path(resources_dir()).generic_string(), m_loading_html), m_script_message_hadler_names); - if (Utils::ServiceConfig::instance().webdev_enabled()) { - m_browser->EnableContextMenu(); - m_browser->EnableAccessToDevTools(); - } - if (!m_browser) { - wxStaticText* text = new wxStaticText(this, wxID_ANY, _L("Failed to load a web browser.")); - topsizer->Add(text, 0, wxALIGN_LEFT | wxBOTTOM, 10); - return; - } - topsizer->Add(m_browser, wxSizerFlags().Expand().Proportion(1)); #ifdef DEBUG_URL_PANEL // Create the Tools menu m_tools_menu = new wxMenu(); @@ -139,16 +142,6 @@ WebViewPanel::WebViewPanel(wxWindow *parent, const wxString& default_url, const m_context_menu = m_tools_menu->AppendCheckItem(wxID_ANY, "Enable Context Menu"); m_dev_tools = m_tools_menu->AppendCheckItem(wxID_ANY, "Enable Dev Tools"); -#endif - - Bind(wxEVT_SHOW, &WebViewPanel::on_show, this); - - // Connect the webview events - Bind(wxEVT_WEBVIEW_ERROR, &WebViewPanel::on_error, this, m_browser->GetId()); - Bind(wxEVT_WEBVIEW_SCRIPT_MESSAGE_RECEIVED, &WebViewPanel::on_script_message, this, m_browser->GetId()); - Bind(wxEVT_WEBVIEW_NAVIGATING, &WebViewPanel::on_navigation_request, this, m_browser->GetId()); - -#ifdef DEBUG_URL_PANEL // Connect the button events Bind(wxEVT_BUTTON, &WebViewPanel::on_back_button, this, m_button_back->GetId()); Bind(wxEVT_BUTTON, &WebViewPanel::on_forward_button, this, m_button_forward->GetId()); @@ -166,8 +159,59 @@ WebViewPanel::WebViewPanel(wxWindow *parent, const wxString& default_url, const Bind(wxEVT_MENU, &WebViewPanel::on_run_script_custom, this, m_script_custom->GetId()); Bind(wxEVT_MENU, &WebViewPanel::on_add_user_script, this, addUserScript->GetId()); #endif - //Connect the idle events - Bind(wxEVT_IDLE, &WebViewPanel::on_idle, this); + + // Create the webview + if (!do_create) { + m_do_late_webview_create = true; + return; + } + m_do_late_webview_create = false; + + m_browser = WebView::webview_new(); + if (!m_browser) { + wxStaticText* text = new wxStaticText(this, wxID_ANY, _L("Failed to load a web browser.")); + topsizer->Add(text, 0, wxALIGN_LEFT | wxBOTTOM, 10); + return; + } + WebView::webview_create(m_browser,this, GUI::format_wxstr("file://%1%/web/%2%.html", boost::filesystem::path(resources_dir()).generic_string(), m_loading_html), m_script_message_hadler_names); + if (Utils::ServiceConfig::instance().webdev_enabled()) { + m_browser->EnableContextMenu(); + m_browser->EnableAccessToDevTools(); + } + topsizer->Add(m_browser, wxSizerFlags().Expand().Proportion(1)); + + + // Connect the webview events + Bind(wxEVT_WEBVIEW_ERROR, &WebViewPanel::on_error, this, m_browser->GetId()); + Bind(wxEVT_WEBVIEW_SCRIPT_MESSAGE_RECEIVED, &WebViewPanel::on_script_message, this, m_browser->GetId()); + Bind(wxEVT_WEBVIEW_NAVIGATING, &WebViewPanel::on_navigation_request, this, m_browser->GetId()); + +} + +void WebViewPanel::late_create() +{ + m_do_late_webview_create = false; + m_browser = WebView::webview_new(); + + if (!m_browser) { + wxStaticText* text = new wxStaticText(this, wxID_ANY, _L("Failed to load a web browser.")); + topsizer->Add(text, 0, wxALIGN_LEFT | wxBOTTOM, 10); + return; + } + WebView::webview_create(m_browser,this, GUI::format_wxstr("file://%1%/web/%2%.html", boost::filesystem::path(resources_dir()).generic_string(), m_loading_html), m_script_message_hadler_names); + + if (Utils::ServiceConfig::instance().webdev_enabled()) { + m_browser->EnableContextMenu(); + m_browser->EnableAccessToDevTools(); + } + topsizer->Add(m_browser, wxSizerFlags().Expand().Proportion(1)); + + // Connect the webview events + Bind(wxEVT_WEBVIEW_ERROR, &WebViewPanel::on_error, this, m_browser->GetId()); + Bind(wxEVT_WEBVIEW_SCRIPT_MESSAGE_RECEIVED, &WebViewPanel::on_script_message, this, m_browser->GetId()); + Bind(wxEVT_WEBVIEW_NAVIGATING, &WebViewPanel::on_navigation_request, this, m_browser->GetId()); + Bind(wxEVT_WEBVIEW_LOADED, &WebViewPanel::on_loaded, this, m_browser->GetId()); + Layout(); } void WebViewPanel::load_default_url_delayed() @@ -178,8 +222,9 @@ void WebViewPanel::load_default_url_delayed() void WebViewPanel::load_error_page() { - if (!m_browser) + if (!m_browser || m_do_late_webview_create) { return; + } m_browser->Stop(); m_load_error_page = true; @@ -188,15 +233,25 @@ void WebViewPanel::load_error_page() void WebViewPanel::on_show(wxShowEvent& evt) { m_shown = evt.IsShown(); - if (evt.IsShown() && m_load_default_url) { - m_load_default_url = false; - load_url(m_default_url); + if (!m_shown) { + return; } + if (m_do_late_webview_create) { + m_do_late_webview_create = false; + late_create(); + } + if (m_load_default_url) { + m_load_default_url = false; + load_default_url(); + return; + } + + after_on_show(evt); } void WebViewPanel::on_idle(wxIdleEvent& WXUNUSED(evt)) { - if (!m_browser) + if (!m_browser || m_do_late_webview_create) return; if (m_browser->IsBusy()) { wxSetCursor(wxCURSOR_ARROWWAIT); @@ -218,6 +273,13 @@ void WebViewPanel::on_idle(wxIdleEvent& WXUNUSED(evt)) #endif } +void WebViewPanel::on_loaded(wxWebViewEvent& evt) +{ + if (evt.GetURL().IsEmpty()) + return; + m_load_default_url_on_next_error = false; +} + /** * Callback invoked when user entered an URL and pressed enter */ @@ -496,9 +558,9 @@ void WebViewPanel::do_reload() void WebViewPanel::load_default_url() { - if (!m_browser) { + if (!m_browser || m_do_late_webview_create) { return; - } + } load_url(m_default_url); } @@ -510,18 +572,35 @@ void WebViewPanel::sys_color_changed() } ConnectWebViewPanel::ConnectWebViewPanel(wxWindow* parent) - : WebViewPanel(parent, GUI::from_u8(Utils::ServiceConfig::instance().connect_url()), { "_prusaSlicer" }, "connect_loading_reload", "connect_connection_failed") + : WebViewPanel(parent, GUI::from_u8(Utils::ServiceConfig::instance().connect_url()), { "_prusaSlicer" }, "connect_loading_reload", "connect_connection_failed", false) { // m_browser->RegisterHandler(wxSharedPtr(new WebViewHandler("https"))); auto* plater = wxGetApp().plater(); - plater->Bind(EVT_UA_ID_USER_SUCCESS, &ConnectWebViewPanel::on_user_token, this); + //plater->Bind(EVT_UA_ID_USER_SUCCESS, &ConnectWebViewPanel::on_user_token, this); plater->Bind(EVT_UA_LOGGEDOUT, &ConnectWebViewPanel::on_user_logged_out, this); } +void ConnectWebViewPanel::late_create() +{ + WebViewPanel::late_create(); + if (!m_browser) { + return; + } + + // from ConnectWebViewPanel::on_user_token + auto access_token = wxGetApp().plater()->get_user_account()->get_access_token(); + assert(!access_token.empty()); + + wxString javascript = get_login_script(true); + BOOST_LOG_TRIVIAL(debug) << "RunScript " << javascript << "\n"; + m_browser->RunScriptAsync(javascript); + resend_config(); +} + ConnectWebViewPanel::~ConnectWebViewPanel() { - m_browser->Unbind(EVT_UA_ID_USER_SUCCESS, &ConnectWebViewPanel::on_user_token, this); + //m_browser->Unbind(EVT_UA_ID_USER_SUCCESS, &ConnectWebViewPanel::on_user_token, this); } wxString ConnectWebViewPanel::get_login_script(bool refresh) @@ -677,6 +756,7 @@ void ConnectWebViewPanel::on_page_will_load() void ConnectWebViewPanel::on_user_token(UserAccountSuccessEvent& e) { e.Skip(); + /* auto access_token = wxGetApp().plater()->get_user_account()->get_access_token(); assert(!access_token.empty()); @@ -684,6 +764,7 @@ void ConnectWebViewPanel::on_user_token(UserAccountSuccessEvent& e) BOOST_LOG_TRIVIAL(debug) << "RunScript " << javascript << "\n"; m_browser->RunScriptAsync(javascript); resend_config(); + */ } void ConnectWebViewPanel::on_user_logged_out(UserAccountSuccessEvent& e) @@ -738,6 +819,9 @@ void ConnectWebViewPanel::on_reload_event(const std::string& message_data) void ConnectWebViewPanel::logout() { + if (!m_browser || m_do_late_webview_create) { + return; + } wxString script = L"window._prusaConnect_v1.logout()"; run_script(script); @@ -755,7 +839,6 @@ void ConnectWebViewPanel::logout() ); BOOST_LOG_TRIVIAL(debug) << "RunScript " << javascript << "\n"; m_browser->RunScript(javascript); - } void ConnectWebViewPanel::sys_color_changed() @@ -781,23 +864,16 @@ void ConnectWebViewPanel::on_connect_action_print(const std::string& message_dat } PrinterWebViewPanel::PrinterWebViewPanel(wxWindow* parent, const wxString& default_url) - : WebViewPanel(parent, default_url, {"ExternalApp"}, "other_loading_reload", "other_error") + : WebViewPanel(parent, default_url, {"ExternalApp"}, "other_loading_reload", "other_error", false) { - if (!m_browser) - return; - - m_browser->Bind(wxEVT_WEBVIEW_LOADED, &PrinterWebViewPanel::on_loaded, this); -#ifndef NDEBUG - m_browser->EnableAccessToDevTools(); - m_browser->EnableContextMenu(); -#endif - } + void PrinterWebViewPanel::on_loaded(wxWebViewEvent& evt) { if (evt.GetURL().IsEmpty()) return; + m_load_default_url_on_next_error = false; if (!m_api_key.empty()) { send_api_key(); } else if (!m_usr.empty() && !m_psk.empty()) { @@ -857,9 +933,9 @@ void PrinterWebViewPanel::sys_color_changed() PrintablesWebViewPanel::PrintablesWebViewPanel(wxWindow* parent) - : WebViewPanel(parent, GUI::from_u8(Utils::ServiceConfig::instance().printables_url()), { "ExternalApp" }, "other_loading_reload", "other_error") + : WebViewPanel(parent, GUI::from_u8(Utils::ServiceConfig::instance().printables_url()), { "ExternalApp" }, "other_loading_reload", "other_error", false) { - m_browser->Bind(wxEVT_WEBVIEW_LOADED, &PrintablesWebViewPanel::on_loaded, this); + m_events["accessTokenExpired"] = std::bind(&PrintablesWebViewPanel::on_printables_event_access_token_expired, this, std::placeholders::_1); m_events["reloadHomePage"] = std::bind(&PrintablesWebViewPanel::on_reload_event, this, std::placeholders::_1); @@ -868,6 +944,7 @@ PrintablesWebViewPanel::PrintablesWebViewPanel(wxWindow* parent) m_events["sliceFile"] = std::bind(&PrintablesWebViewPanel::on_printables_event_slice_file, this, std::placeholders::_1); m_events["requiredLogin"] = std::bind(&PrintablesWebViewPanel::on_printables_event_required_login, this, std::placeholders::_1); + } void PrintablesWebViewPanel::handle_message(const std::string& message) @@ -908,15 +985,21 @@ void PrintablesWebViewPanel::on_navigation_request(wxWebViewEvent &evt) } } +wxString PrintablesWebViewPanel::get_default_url() const +{ + return GUI::from_u8(get_url_lang_theme(GUI::from_u8(Utils::ServiceConfig::instance().printables_url() + "/homepage"))); +} + void PrintablesWebViewPanel::on_loaded(wxWebViewEvent& evt) { #ifdef _WIN32 // This is needed only once after add_request_authorization remove_request_authorization(m_browser); #endif + m_load_default_url_on_next_error = false; } -std::string PrintablesWebViewPanel::get_url_lang_theme(const wxString& url) +std::string PrintablesWebViewPanel::get_url_lang_theme(const wxString& url) const { // situations and reaction: // 1) url is just a path (no query no fragment) -> query with lang and theme is added @@ -967,17 +1050,8 @@ std::string PrintablesWebViewPanel::get_url_lang_theme(const wxString& url) return url_string + "?" + new_params; } -void PrintablesWebViewPanel::on_show(wxShowEvent& evt) +void PrintablesWebViewPanel::after_on_show(wxShowEvent& evt) { - m_shown = evt.IsShown(); - if (!m_shown) { - return; - } - if (m_load_default_url) { - m_load_default_url = false; - load_default_url(); - return; - } // in case login changed, resend login / logout // DK1: it seems to me, it is safer to do login / logout (where logout means requesting the page again) // on every show of panel, diff --git a/src/slic3r/GUI/WebViewPanel.hpp b/src/slic3r/GUI/WebViewPanel.hpp index d5d276cb62..8323ff40ff 100644 --- a/src/slic3r/GUI/WebViewPanel.hpp +++ b/src/slic3r/GUI/WebViewPanel.hpp @@ -24,17 +24,25 @@ namespace Slic3r::GUI { class WebViewPanel : public wxPanel { public: - WebViewPanel(wxWindow *parent, const wxString& default_url, const std::vector& message_handler_names, const std::string& loading_html, const std::string& error_html); + WebViewPanel(wxWindow *parent, const wxString& default_url, const std::vector& message_handler_names, const std::string& loading_html, const std::string& error_html, bool do_create); virtual ~WebViewPanel(); + void destroy_browser(); + void set_create_browser() {m_do_late_webview_create = true; m_load_default_url = true; } void load_url(const wxString& url); void load_default_url_delayed(); void load_error_page(); - virtual void on_show(wxShowEvent& evt); + // Let WebViewPanel do on_show so it can create webview properly + // and load default page + // override after_on_show for more actions in on_show + void on_show(wxShowEvent& evt); + virtual void after_on_show(wxShowEvent& evt) {} + virtual void on_script_message(wxWebViewEvent& evt); void on_idle(wxIdleEvent& evt); + void on_loaded(wxWebViewEvent& evt); void on_url(wxCommandEvent& evt); virtual void on_back_button(wxCommandEvent& evt); virtual void on_forward_button(wxCommandEvent& evt); @@ -57,7 +65,7 @@ public: void On_enable_dev_tools(wxCommandEvent& evt); virtual void on_navigation_request(wxWebViewEvent &evt); - wxString get_default_url() const { return m_default_url; } + virtual wxString get_default_url() const { return m_default_url; } void set_default_url(const wxString& url) { m_default_url = url; } virtual void do_reload(); virtual void load_default_url(); @@ -67,6 +75,8 @@ public: void set_load_default_url_on_next_error(bool val) { m_load_default_url_on_next_error = val; } protected: + virtual void late_create(); + virtual void on_page_will_load(); wxWebView* m_browser { nullptr }; @@ -107,6 +117,7 @@ protected: bool m_load_error_page { false }; bool m_shown { false }; bool m_load_default_url_on_next_error { false }; + bool m_do_late_webview_create {false}; std::vector m_script_message_hadler_names; }; @@ -121,6 +132,7 @@ public: void sys_color_changed() override; void on_navigation_request(wxWebViewEvent &evt) override; protected: + void late_create() override; void on_connect_action_request_login(const std::string &message_data) override; void on_connect_action_select_printer(const std::string& message_data) override; void on_connect_action_print(const std::string& message_data) override; @@ -176,7 +188,7 @@ public: PrintablesWebViewPanel(wxWindow* parent); void on_navigation_request(wxWebViewEvent &evt) override; void on_loaded(wxWebViewEvent& evt); - void on_show(wxShowEvent& evt) override; + void after_on_show(wxShowEvent& evt) override; void on_script_message(wxWebViewEvent& evt) override; void sys_color_changed() override; @@ -184,6 +196,7 @@ public: void login(const std::string& access_token, const std::string& override_url = std::string()); void send_refreshed_token(const std::string& access_token); void send_will_refresh(); + wxString get_default_url() const override; void set_next_show_url(const std::string& url) {m_next_show_url = Utils::ServiceConfig::instance().printables_url() + url; } private: void handle_message(const std::string& message); @@ -194,7 +207,7 @@ private: void on_printables_event_slice_file(const std::string& message_data); void on_printables_event_required_login(const std::string& message_data); void load_default_url() override; - std::string get_url_lang_theme(const wxString& url); + std::string get_url_lang_theme(const wxString& url) const; void show_download_notification(const std::string& filename); std::map> m_events; From 85094c5c1999a713bc50bc9aa8b8deda424814ae Mon Sep 17 00:00:00 2001 From: David Kocik Date: Thu, 31 Oct 2024 13:02:22 +0100 Subject: [PATCH 10/12] SPE-2515: Improved reload functionality Fix of script message after error with credentials /api key Reload improvements additional webview checks Reload webview on F5 for mac / linux Rename button Fix of loading error page --- resources/web/connect_connection_failed.html | 28 ----- resources/web/connect_error.html | 106 ++++++++++++++++++ ...ading_reload.html => connect_loading.html} | 0 resources/web/error_no_reload.html | 82 ++++++++++++++ resources/web/other_error.html | 40 ++++++- ...loading_reload.html => other_loading.html} | 0 src/slic3r/GUI/ConfigWizardWebViewPage.cpp | 13 ++- src/slic3r/GUI/ConfigWizardWebViewPage.hpp | 1 + src/slic3r/GUI/MainFrame.cpp | 41 ++----- src/slic3r/GUI/MainFrame.hpp | 1 - src/slic3r/GUI/WebView.cpp | 54 +++++---- src/slic3r/GUI/WebViewDialog.cpp | 4 +- src/slic3r/GUI/WebViewDialog.hpp | 2 +- src/slic3r/GUI/WebViewPanel.cpp | 77 +++++-------- src/slic3r/GUI/WebViewPanel.hpp | 22 ++-- 15 files changed, 309 insertions(+), 162 deletions(-) delete mode 100644 resources/web/connect_connection_failed.html create mode 100644 resources/web/connect_error.html rename resources/web/{connect_loading_reload.html => connect_loading.html} (100%) create mode 100644 resources/web/error_no_reload.html rename resources/web/{other_loading_reload.html => other_loading.html} (100%) diff --git a/resources/web/connect_connection_failed.html b/resources/web/connect_connection_failed.html deleted file mode 100644 index 7936470b41..0000000000 --- a/resources/web/connect_connection_failed.html +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - Connection failed - - - -
-

Connection failed

-

Something went wrong.

- -
- - diff --git a/resources/web/connect_error.html b/resources/web/connect_error.html new file mode 100644 index 0000000000..37d18ce594 --- /dev/null +++ b/resources/web/connect_error.html @@ -0,0 +1,106 @@ + + + + + + Connect-Slicer integration + + + + +
+
+

+ + + + + + + + + + + + + + + + + + + + + + + + + + +

+

Something went wrong.

+ +
+
+ + \ No newline at end of file diff --git a/resources/web/connect_loading_reload.html b/resources/web/connect_loading.html similarity index 100% rename from resources/web/connect_loading_reload.html rename to resources/web/connect_loading.html diff --git a/resources/web/error_no_reload.html b/resources/web/error_no_reload.html new file mode 100644 index 0000000000..c3418b06f9 --- /dev/null +++ b/resources/web/error_no_reload.html @@ -0,0 +1,82 @@ + + + + + + Connect-Slicer integration + + + + +
+
+

+ + + + + + + + + + + + + + + + + + + + + + + + + + +

+

Something went wrong.

+
+
+ + \ No newline at end of file diff --git a/resources/web/other_error.html b/resources/web/other_error.html index 24b29d72d2..805c6f058c 100644 --- a/resources/web/other_error.html +++ b/resources/web/other_error.html @@ -4,6 +4,42 @@ Connect-Slicer integration + @@ -14,7 +50,7 @@ viewBox='-250 -250 500 500' xmlns='http://www.w3.org/2000/svg'>