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.html b/resources/web/connect_loading.html index bfe8401d2d..018926044c 100644 --- a/resources/web/connect_loading.html +++ b/resources/web/connect_loading.html @@ -71,6 +71,7 @@

+ diff --git a/resources/web/connection_failed.html b/resources/web/connection_failed.html deleted file mode 100644 index 72cb686232..0000000000 --- a/resources/web/connection_failed.html +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - Connection failed - - - -
-

Connection failed

-

Something went wrong.

-
- - 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 new file mode 100644 index 0000000000..805c6f058c --- /dev/null +++ b/resources/web/other_error.html @@ -0,0 +1,106 @@ + + + + + + Connect-Slicer integration + + + + +
+
+

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

+

Something went wrong.

+ +
+
+ + \ No newline at end of file diff --git a/resources/web/loading.html b/resources/web/other_loading.html similarity index 93% rename from resources/web/loading.html rename to resources/web/other_loading.html index 0f50d68325..68033cdc37 100644 --- a/resources/web/loading.html +++ b/resources/web/other_loading.html @@ -62,6 +62,7 @@

+ 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/ConfigWizardWebViewPage.cpp b/src/slic3r/GUI/ConfigWizardWebViewPage.cpp index 7d1a915c66..c765c4784c 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(), {"ExternalApp"}); + 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())); @@ -47,11 +49,13 @@ ConfigWizardWebViewPage::ConfigWizardWebViewPage(ConfigWizard *parent) append(m_browser_sizer, 1, wxEXPAND); m_browser_sizer->Show(!logged); + Layout(); // Connect the webview events Bind(wxEVT_WEBVIEW_ERROR, &ConfigWizardWebViewPage::on_error, this, m_browser->GetId()); Bind(wxEVT_WEBVIEW_NAVIGATING, &ConfigWizardWebViewPage::on_navigation_request, this, m_browser->GetId()); Bind(wxEVT_IDLE, &ConfigWizardWebViewPage::on_idle, this); + Bind(wxEVT_WEBVIEW_SCRIPT_MESSAGE_RECEIVED, &ConfigWizardWebViewPage::on_script_message, this, m_browser->GetId()); } bool ConfigWizardWebViewPage::login_changed() @@ -113,7 +117,7 @@ void ConfigWizardWebViewPage::on_idle(wxIdleEvent &WXUNUSED(evt)) { if (!m_vetoed && m_load_error_page) { m_load_error_page = false; m_browser->LoadURL(GUI::format_wxstr( - "file://%1%/web/connection_failed.html", + "file://%1%/web/other_error.html", boost::filesystem::path(resources_dir()).generic_string() )); } @@ -125,7 +129,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"); @@ -145,4 +149,11 @@ void ConfigWizardWebViewPage::on_navigation_request(wxWebViewEvent &evt) } } } + +void ConfigWizardWebViewPage::on_script_message(wxWebViewEvent& evt) +{ + // only reaload button on erro page + m_browser->LoadURL(p_user_account->generate_login_redirect_url()); +} + }} // namespace Slic3r::GUI \ No newline at end of file diff --git a/src/slic3r/GUI/ConfigWizardWebViewPage.hpp b/src/slic3r/GUI/ConfigWizardWebViewPage.hpp index 2a873de805..696aa9fab9 100644 --- a/src/slic3r/GUI/ConfigWizardWebViewPage.hpp +++ b/src/slic3r/GUI/ConfigWizardWebViewPage.hpp @@ -53,6 +53,7 @@ public: void on_error(wxWebViewEvent &evt); void on_navigation_request(wxWebViewEvent &evt); void on_idle(wxIdleEvent &evt); + void on_script_message(wxWebViewEvent& evt); void load_error_page(); // returns true if logged in - wizard needs to update repos bool login_changed(); diff --git a/src/slic3r/GUI/ConnectRequestHandler.cpp b/src/slic3r/GUI/ConnectRequestHandler.cpp new file mode 100644 index 0000000000..4af2f92148 --- /dev/null +++ b/src/slic3r/GUI/ConnectRequestHandler.cpp @@ -0,0 +1,135 @@ +#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); + m_actions["RELOAD_HOME_PAGE"] = std::bind(&ConnectRequestHandler::on_reload_event, 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_v2.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..017d68f0de --- /dev/null +++ b/src/slic3r/GUI/ConnectRequestHandler.hpp @@ -0,0 +1,45 @@ +#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 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); + 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 on_reload_event(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..5234cded6b 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,16 +73,31 @@ 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) +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() @@ -116,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); @@ -133,23 +144,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; @@ -158,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(); @@ -184,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 7a401bf8d0..7de1996e90 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) @@ -86,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 @@ -112,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) { } @@ -279,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); } @@ -309,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 37a59ec30e..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,13 +29,18 @@ 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); + 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 879fc090ba..a79d6dbc2f 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" @@ -4114,6 +4113,50 @@ void GUI_App::show_printer_webview_tab() } +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)); + + //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) +{ + 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) +{ + 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 08ef7667ca..a85c8a8480 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -436,7 +436,11 @@ 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_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(); void init_app_config(); diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index f67643b55d..02a19f51ce 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,14 +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""); + // 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) { @@ -819,13 +844,13 @@ 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 = ""; 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() @@ -839,26 +864,69 @@ 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) { - 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); } +void MainFrame::show_printables_tab(const std::string& url) +{ + if (!m_printables_webview_added) { + return; + } + // 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->set_next_show_url(url); + m_tabpanel->SetSelection(m_tabpanel->FindPage(m_printables_webview)); +} +void MainFrame::add_printables_webview_tab() +{ + if (m_printables_webview_added) { + return; + } + + 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 = ""; + m_tabpanel->InsertNewPage(n, page, text, bmp_name, false); + m_printables_webview->set_create_browser(); + m_printables_webview_added = true; +} + +// no longer needed? +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; + m_printables_webview->destroy_browser(); +} void MainFrame::show_printer_webview_tab(DynamicPrintConfig* dpc) { + + remove_printer_webview_tab(); // if physical printer is selected if (dpc && dpc->option>("host_type")->value != htPrusaConnect) { std::string url = dpc->opt_string("print_host"); - if (url.find("http://") != 0 && url.find("https://") != 0) { url = "http://" + url; } - // set password / api key if (dynamic_cast*>(dpc->option("printhost_authorization_type"))->value == AuthorizationType::atKeyPassword) { set_printer_webview_api_key(dpc->opt_string("printhost_apikey")); @@ -866,59 +934,33 @@ void MainFrame::show_printer_webview_tab(DynamicPrintConfig* dpc) else { set_printer_webview_credentials(dpc->opt_string("printhost_user"), dpc->opt_string("printhost_password")); } - // add printer or change url - if (get_printer_webview_tab_added()) { - set_printer_webview_tab_url(from_u8(url)); - } - else { - add_printer_webview_tab(from_u8(url)); - } - } - // if physical printer isn't selected, so delete page from TopBar - else { - if (m_tabpanel->GetPageText(m_tabpanel->GetSelection()) == _L("Physical Printer")) - select_tab(size_t(0)); - remove_printer_webview_tab(); + add_printer_webview_tab(from_u8(url)); } } void MainFrame::add_printer_webview_tab(const wxString& url) { if (m_printer_webview_added) { - set_printer_webview_tab_url(url); + //set_printer_webview_tab_url(url); return; } m_printer_webview_added = true; // 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() { if (!m_printer_webview_added) { return; } + if (m_tabpanel->GetPageText(m_tabpanel->GetSelection()) == _L("Physical Printer")) + select_tab(size_t(0)); m_printer_webview_added = false; m_printer_webview->Hide(); m_tabpanel->RemovePage(m_tabpanel->FindPage(m_printer_webview)); -} -void MainFrame::set_printer_webview_tab_url(const wxString& url) -{ - if (!m_printer_webview_added) { - add_printer_webview_tab(url); - return; - } - // TODO: this will reset already filled credential when bundle loaded, - // what's the reason of clearing credentials here? - //m_printer_webview->clear(); - m_printer_webview->set_default_url(url); - - if (m_tabpanel->GetSelection() == m_tabpanel->FindPage(m_printer_webview)) { - m_printer_webview->load_url(url); - } else { - m_printer_webview->load_default_url_delayed(); - } + m_printer_webview->destroy_browser(); } void MainFrame::set_printer_webview_api_key(const std::string& key) @@ -930,6 +972,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 @@ -1193,6 +1258,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) @@ -1630,6 +1697,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 Web Content") + "\tF5", _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 4b2306ec23..7d94cca4f6 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 PrintablesWebViewPanel; 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 }; + PrintablesWebViewPanel* 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; @@ -124,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 @@ -214,18 +220,25 @@ public: void add_to_recent_projects(const wxString& filename); void technology_changed(); - void add_connect_webview_tab(); - void remove_connect_webview_tab(); - void show_connect_tab(const wxString &url); + 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 show_printables_tab(const std::string& 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); void remove_printer_webview_tab(); - void set_printer_webview_tab_url(const wxString& url); 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/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/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 681d12ca83..e22d7a260a 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -915,12 +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->on_account_logout(); this->main_frame->refresh_account_menu(true); // Update sidebar printer status sidebar->update_printer_presets_combobox(); @@ -937,15 +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(); // Update User name in TopBar this->main_frame->refresh_account_menu(); wxGetApp().update_wizard_login_page(); @@ -958,7 +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->on_account_logout(); // Update User name in TopBar this->main_frame->refresh_account_menu(true); // Update sidebar printer status @@ -971,7 +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->on_account_logout(); // Update User name in TopBar this->main_frame->refresh_account_menu(true); // Update sidebar printer status @@ -1032,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); @@ -6006,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/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..9048f9bef7 100644 --- a/src/slic3r/GUI/WebView.cpp +++ b/src/slic3r/GUI/WebView.cpp @@ -1,13 +1,16 @@ #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 #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); @@ -18,44 +21,40 @@ wxWebView* WebView::CreateWebView(wxWindow * parent, const wxString& url, const wxWebView* webView = nullptr; if (backend_available) webView = wxWebView::New(); - - if (webView) { - wxString correct_url = url.empty() ? wxString("") : wxURI(url).BuildURI(); - -#ifdef __WIN32__ - webView->SetUserAgent(SLIC3R_APP_FULL_NAME); - webView->Create(parent, wxID_ANY, correct_url, wxDefaultPosition, wxDefaultSize); - //We register the wxfs:// protocol for testing purposes - //webView->RegisterHandler(wxSharedPtr(new wxWebViewArchiveHandler("wxfs"))); - //And the memory: file system - //webView->RegisterHandler(wxSharedPtr(new wxWebViewFSHandler("memory"))); -#else - // With WKWebView handlers need to be registered before creation - //webView->RegisterHandler(wxSharedPtr(new wxWebViewArchiveHandler("wxfs"))); - // 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)); -#endif -#ifndef __WIN32__ - Slic3r::GUI::wxGetApp().CallAfter([message_handlers, webView] { -#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; - } - } -#ifndef __WIN32__ - }); -#endif - webView->EnableContextMenu(false); - } else { - // TODO: dialog to user !!! + if (!webView) BOOST_LOG_TRIVIAL(error) << "Failed to create wxWebView object."; - } return webView; } - - +void WebView::webview_create(wxWebView* webView, wxWindow *parent, const wxString& url, const std::vector& message_handlers) +{ + assert(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(user_agent); + webView->Create(parent, wxID_ANY, correct_url, wxDefaultPosition, wxDefaultSize, wxNO_BORDER); + //We register the wxfs:// protocol for testing purposes + //webView->RegisterHandler(wxSharedPtr(new wxWebViewArchiveHandler("wxfs"))); + //And the memory: file system + //webView->RegisterHandler(wxSharedPtr(new wxWebViewFSHandler("memory"))); +#else + // With WKWebView handlers need to be registered before creation + //webView->RegisterHandler(wxSharedPtr(new wxWebViewArchiveHandler("wxfs"))); + // And the memory: file system + //webView->RegisterHandler(wxSharedPtr(new wxWebViewFSHandler("memory"))); + webView->Create(parent, wxID_ANY, correct_url, wxDefaultPosition, wxDefaultSize); + webView->SetUserAgent(user_agent); +#endif +#ifndef __WIN32__ + Slic3r::GUI::wxGetApp().CallAfter([message_handlers, webView] { +#endif + for (const std::string& handler : message_handlers) { + if (!webView->AddScriptMessageHandler(Slic3r::GUI::from_u8(handler))) { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << "Could not add script message handler " << handler; + } + } +#ifndef __WIN32__ + }); +#endif + webView->EnableContextMenu(false); +} \ 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 9ce243c6ca..485c974c27 100644 --- a/src/slic3r/GUI/WebViewDialog.cpp +++ b/src/slic3r/GUI/WebViewDialog.cpp @@ -17,16 +17,12 @@ #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 - wxDEFINE_EVENT(EVT_OPEN_EXTERNAL_LOGIN, wxCommandEvent); namespace pt = boost::property_tree; @@ -34,905 +30,7 @@ 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"*/) +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/* = "other_loading"*/) : DPIDialog(parent, wxID_ANY, dialog_name, wxDefaultPosition, size, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) , m_loading_html(loading_html) , m_script_message_hadler_names (message_handler_names) @@ -976,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)); @@ -1057,7 +158,7 @@ void WebViewDialog::on_idle(wxIdleEvent& WXUNUSED(evt)) wxSetCursor(wxNullCursor); if (m_load_error_page) { m_load_error_page = false; - m_browser->LoadURL(GUI::format_wxstr("file://%1%/web/connection_failed.html", boost::filesystem::path(resources_dir()).generic_string())); + m_browser->LoadURL(GUI::format_wxstr("file://%1%/web/error_no_reload.html", boost::filesystem::path(resources_dir()).generic_string())); } } #ifdef DEBUG_URL_PANEL @@ -1339,13 +440,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 / 2, 100 * wxGetApp().em_unit()), std::max(parent->GetClientSize().y / 2, 50 * wxGetApp().em_unit())) + , wxSize(parent->GetClientSize().x / 4 * 3, parent->GetClientSize().y/ 4 * 3) ,{"_prusaSlicer"} , "connect_loading") , m_ret_val(ret_val) { + + 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) +{ + wxDisplay display(wxDisplay::GetFromWindow(this)); + wxRect geometry = display.GetGeometry(); + SetMinSize(wxSize(geometry.GetWidth() / 2, geometry.GetHeight() / 2)); + Fit(); + Refresh(); +} + void PrinterPickWebViewDialog::on_show(wxShowEvent& evt) { /* @@ -1471,9 +586,9 @@ void PrinterPickWebViewDialog::request_compatible_printers_FFF() { "\"filament_abrasive\": %6%," "\"high_flow\": %7%" "}" - , filament_type_serialized, nozzle_diameter_serialized, printer_model_serialized, uuid, filename, nozzle_high_flow_serialized, filament_abrasive_serialized); + , filament_type_serialized, nozzle_diameter_serialized, printer_model_serialized, uuid, filename, filament_abrasive_serialized, nozzle_high_flow_serialized); - wxString script = GUI::format_wxstr("window._prusaConnect_v1.requestCompatiblePrinter(%1%)", request); + wxString script = GUI::format_wxstr("window._prusaConnect_v2.requestCompatiblePrinter(%1%)", request); run_script(script); } void PrinterPickWebViewDialog::request_compatible_printers_SLA() @@ -1506,21 +621,44 @@ void PrinterPickWebViewDialog::request_compatible_printers_SLA() "\"filename\": \"%4%\" " "}", material_type_serialized, printer_model_serialized, uuid, filename); - wxString script = GUI::format_wxstr("window._prusaConnect_v1.requestCompatiblePrinter(%1%)", request); + wxString script = GUI::format_wxstr("window._prusaConnect_v2.requestCompatiblePrinter(%1%)", request); run_script(script); } -void PrinterPickWebViewDialog::on_dpi_changed(const wxRect &suggested_rect) + + +void PrinterPickWebViewDialog::on_reload_event(const std::string& message_data) { - 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); + if (!m_browser) { + return; + } + 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/ 4 * 3) + ,{"_prusaSlicer"} + , "connect_loading") +{ + 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) +{ + wxDisplay display(wxDisplay::GetFromWindow(this)); + wxRect geometry = display.GetGeometry(); + SetMinSize(wxSize(geometry.GetWidth() / 2, geometry.GetHeight() / 2)); 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()), {}) , m_ret_val(ret_val) @@ -1532,7 +670,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/WebViewDialog.hpp b/src/slic3r/GUI/WebViewDialog.hpp index 05efed3453..fc24eab489 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,95 +21,10 @@ 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: - 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"); + WebViewDialog(wxWindow* parent, const wxString& url, const wxString& dialog_name, const wxSize& size, const std::vector& message_handler_names, const std::string& loading_html = "other_loading"); virtual ~WebViewDialog(); virtual void on_show(wxShowEvent& evt) {}; @@ -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: @@ -271,15 +104,18 @@ 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; }; -class SourceViewDialog : public wxDialog +class PrintablesConnectUploadDialog : public WebViewDialog { public: - SourceViewDialog(wxWindow* parent, wxString source); + PrintablesConnectUploadDialog(wxWindow* parent, const std::string url); +protected: + void on_dpi_changed(const wxRect &suggested_rect) override; + }; class LoginWebViewDialog : public WebViewDialog @@ -297,4 +133,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..583c7ec960 --- /dev/null +++ b/src/slic3r/GUI/WebViewPanel.cpp @@ -0,0 +1,1334 @@ +#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/GUI/MsgDialog.hpp" +#include "slic3r/GUI/Field.hpp" +#include "libslic3r/AppConfig.hpp" +#include "libslic3r/Config.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::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) + return; + + this->on_page_will_load(); + + this->Show(); + this->Raise(); +#ifdef DEBUG_URL_PANEL + m_url->SetLabelText(url); +#endif + 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, bool do_create) + : 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); + 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); + + Bind(wxEVT_SHOW, &WebViewPanel::on_show, this); + Bind(wxEVT_IDLE, &WebViewPanel::on_idle, this); + +#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"); + + // 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 + + // Create the webview + if (!do_create) { + m_do_late_webview_create = true; + return; + } + m_do_late_webview_create = false; + late_create(); +} + +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() +{ + assert(!m_default_url.empty()); + m_load_default_url = true; +} + +void WebViewPanel::load_error_page() +{ + if (!m_browser || m_do_late_webview_create) { + return; + } + + m_browser->Stop(); + m_load_error_page = true; +} + +void WebViewPanel::on_show(wxShowEvent& evt) +{ + m_shown = evt.IsShown(); + 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 || m_do_late_webview_create) + 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_default_url(); + } else { + load_url(GUI::format_wxstr("file://%1%/web/%2%.html", boost::filesystem::path(resources_dir()).generic_string(), m_error_html)); + // This is a fix of broken message handling after error. + // F.e. if there is an error but we do AddUserScript & Reload, the handling will break. + // So we just reset the handler here. + if (!m_script_message_hadler_names.empty()) { + m_browser->RemoveScriptMessageHandler(Slic3r::GUI::from_u8(m_script_message_hadler_names.front())); + bool b = m_browser->AddScriptMessageHandler(Slic3r::GUI::from_u8(m_script_message_hadler_names.front())); + } + + } + } + } +#ifdef DEBUG_URL_PANEL + m_button_stop->Enable(m_browser->IsBusy()); +#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 + */ +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; + if (!m_browser->CanGoBack()) + return; + m_browser->GoBack(); +} + +/** + * Callback invoked when user pressed the "forward" button + */ +void WebViewPanel::on_forward_button(wxCommandEvent& WXUNUSED(evt)) +{ + if (!m_browser) + return; + if (!m_browser->CanGoForward()) + 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) +{ + BOOST_LOG_TRIVIAL(error) << "unhandled script message: " << evt.GetString(); +} + +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)) +{ + if (!m_browser) { + return; + } + 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) << 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" + + "'" + category + "'", wxICON_ERROR); +#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 || m_do_late_webview_create) { + return; + } + load_url(m_default_url); +} + +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", "connect_error", false) +{ + auto* plater = wxGetApp().plater(); + plater->Bind(EVT_UA_LOGGEDOUT, &ConnectWebViewPanel::on_user_logged_out, this); +} + +void ConnectWebViewPanel::late_create() +{ + WebViewPanel::late_create(); + if (!m_browser) { + return; + } + + // This code used to be inside plater->Bind(EVT_UA_ID_USER_SUCCESS, &ConnectWebViewPanel::on_user_token, this) + 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() +{ +} + +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() +{ + if (!m_browser) { + return; + } + auto javascript = get_login_script(false); + BOOST_LOG_TRIVIAL(debug) << "RunScript " << javascript << "\n"; + m_browser->AddUserScript(javascript); +} + +void ConnectWebViewPanel::on_user_logged_out(UserAccountSuccessEvent& e) +{ + e.Skip(); + if (!m_browser) + return; + // 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::on_reload_event(const std::string& message_data) +{ + load_default_url(); +} + +void ConnectWebViewPanel::logout() +{ + if (!m_browser || m_do_late_webview_create) { + return; + } + wxString script = L"window._prusaConnect_v2.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, {"ExternalApp"}, "other_loading", "other_error", false) +{ +} + +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()) { + send_credentials(); + } +} +void PrinterWebViewPanel::on_script_message(wxWebViewEvent& evt) +{ + // Only reload messages are being sent now. + load_default_url(); +} + +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" }, "other_loading", "other_error", false) +{ + + + 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); + 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) +{ + + 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_navigation_request(wxWebViewEvent &evt) +{ + const wxString url = evt.GetURL(); + if (url.StartsWith(m_default_url)) { + m_reached_default_url = true; + } else if (m_reached_default_url && url.StartsWith("http")) { + BOOST_LOG_TRIVIAL(info) << evt.GetURL() << " does not start with default url. Vetoing."; + evt.Veto(); + } +} + +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) const +{ + // 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::after_on_show(wxShowEvent& evt) +{ + // 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, + // 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(m_next_show_url); + } else { + login(access_token, m_next_show_url); + } + m_next_show_url.clear(); +} + +void PrintablesWebViewPanel::logout(const std::string& override_url/* = std::string()*/) +{ + if (!m_shown || !m_browser) { + return; + } + 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(next_url)); +#else + // We cannot do simple reload here, it would keep the access token in the header + load_request(m_browser, next_url, std::string()); +#endif // + +} +void PrintablesWebViewPanel::login(const std::string& access_token, const std::string& override_url/* = std::string()*/) +{ + 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); + + 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() +{ + if (!m_browser) { + return; + } + 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) { + 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 && m_browser) { + load_url(GUI::from_u8(get_url_lang_theme(m_browser->GetCurrentURL()))); + } + 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) +{ + // { "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(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 { + 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(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; + 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(debug) << __FUNCTION__ << " " << message_data; + wxGetApp().printables_login_request(); +} + +void PrintablesWebViewPanel::show_download_notification(const std::string& 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'); + 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; + 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 */ + } + .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% +
%2% +
+ `; + 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_filename, message_dest); + 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 new file mode 100644 index 0000000000..3034e23d47 --- /dev/null +++ b/src/slic3r/GUI/WebViewPanel.hpp @@ -0,0 +1,224 @@ +#ifndef slic3r_WebViewPanel_hpp_ +#define slic3r_WebViewPanel_hpp_ + +#include +#include +#include + +#include "GUI_Utils.hpp" +#include "UserAccountSession.hpp" +#include "ConnectRequestHandler.hpp" +#include "slic3r/Utils/ServiceConfig.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, 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(); + + // 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); + virtual void on_loaded(wxWebViewEvent& 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); + + 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(); + + 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 late_create(); + + 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; + std::string m_error_html; + //DECLARE_EVENT_TABLE() + + 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; +}; + +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 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; + 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; + void on_reload_event(const std::string& message_data) override; +private: + static wxString get_login_script(bool refresh); + static wxString get_logout_script(); + void on_user_logged_out(UserAccountSuccessEvent& e); +}; + +class PrinterWebViewPanel : public WebViewPanel +{ +public: + PrinterWebViewPanel(wxWindow* parent, const wxString& default_url); + + void on_loaded(wxWebViewEvent& evt) override; + void on_script_message(wxWebViewEvent& evt) override; + void send_api_key(); + void send_credentials(); + void set_api_key(const std::string &key) + { + clear(); + m_api_key = key; + } + void set_credentials(const std::string &usr, const std::string &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) override; + void after_on_show(wxShowEvent& evt) override; + void on_script_message(wxWebViewEvent& evt) override; + void sys_color_changed() override; + + 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(); + 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); + void on_printables_event_access_token_expired(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) const; + void show_download_notification(const std::string& filename); + + std::map> m_events; + std::string m_next_show_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 +*/ +}; +} // 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..2beeed0dce 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,148 @@ 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/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. 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}; };